From 416efaa418eb48795132f9f94d70b2a5e55a9b31 Mon Sep 17 00:00:00 2001 From: "deepin-community-bot[bot]" <156989552+deepin-community-bot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 12:34:14 +0000 Subject: [PATCH] feat: update libscram-java to 3.2-1 --- .editorconfig | 9 + .gitignore | 105 ++- .gitlab-ci.yml | 8 +- .mvn/maven.config | 7 + .mvn/wrapper/MavenWrapperDownloader.java | 155 ++-- .mvn/wrapper/maven-wrapper.properties | 9 +- CHANGELOG | 33 - CHANGELOG.md | 50 ++ LICENSE | 2 +- NOTICE | 25 - README.md | 116 +-- SECURITY.md | 25 + checks/checkstyle-header.txt | 4 + checks/checkstyle-suppressions.xml | 8 + checks/checkstyle.xml | 310 ++++++++ checks/forbiddenapis.txt | 2 + checks/pmd-ruleset.xml | 41 + checks/spotbugs-exclude.xml | 39 + client/README.md | 103 --- client/pom.xml | 94 --- .../ongres/scram/client/NonceSupplier.java | 27 - .../com/ongres/scram/client/ScramClient.java | 381 --------- .../com/ongres/scram/client/ScramSession.java | 283 ------- .../ongres/scram/client/ScramClientTest.java | 201 ----- .../ongres/scram/client/ScramSessionTest.java | 67 -- common/pom.xml | 48 -- common/spotbugs-exclude.xml | 7 - .../scram/common/ScramAttributeValue.java | 59 -- .../ongres/scram/common/ScramAttributes.java | 158 ---- .../ongres/scram/common/ScramFunctions.java | 250 ------ .../ongres/scram/common/ScramMechanism.java | 80 -- .../ongres/scram/common/ScramMechanisms.java | 220 ------ .../scram/common/ScramStringFormatting.java | 149 ---- .../common/bouncycastle/base64/Base64.java | 196 ----- .../bouncycastle/base64/Base64Encoder.java | 399 ---------- .../bouncycastle/base64/DecoderException.java | 44 -- .../common/bouncycastle/base64/Encoder.java | 39 - .../bouncycastle/base64/EncoderException.java | 44 -- .../common/bouncycastle/pbkdf2/Arrays.java | 75 -- .../bouncycastle/pbkdf2/CipherParameters.java | 30 - .../pbkdf2/DataLengthException.java | 51 -- .../common/bouncycastle/pbkdf2/Digest.java | 73 -- .../bouncycastle/pbkdf2/DigestFactory.java | 34 - .../bouncycastle/pbkdf2/EncodableDigest.java | 39 - .../bouncycastle/pbkdf2/ExtendedDigest.java | 35 - .../bouncycastle/pbkdf2/GeneralDigest.java | 178 ----- .../common/bouncycastle/pbkdf2/HMac.java | 245 ------ .../common/bouncycastle/pbkdf2/Integers.java | 34 - .../bouncycastle/pbkdf2/KeyParameter.java | 50 -- .../scram/common/bouncycastle/pbkdf2/Mac.java | 93 --- .../common/bouncycastle/pbkdf2/Memoable.java | 48 -- .../pbkdf2/PBEParametersGenerator.java | 116 --- .../pbkdf2/PKCS5S2ParametersGenerator.java | 124 --- .../common/bouncycastle/pbkdf2/Pack.java | 82 -- .../pbkdf2/RuntimeCryptoException.java | 48 -- .../bouncycastle/pbkdf2/SHA256Digest.java | 382 --------- .../common/bouncycastle/pbkdf2/Strings.java | 133 ---- .../common/exception/ScramException.java | 52 -- .../ScramInvalidServerSignatureException.java | 47 -- .../common/exception/ScramParseException.java | 47 -- .../exception/ScramServerErrorException.java | 62 -- .../common/gssapi/Gs2AttributeValue.java | 61 -- .../scram/common/gssapi/Gs2Attributes.java | 83 -- .../scram/common/gssapi/Gs2CbindFlag.java | 73 -- .../ongres/scram/common/gssapi/Gs2Header.java | 132 ---- .../common/message/ClientFinalMessage.java | 136 ---- .../common/message/ClientFirstMessage.java | 200 ----- .../common/message/ServerFinalMessage.java | 219 ------ .../common/message/ServerFirstMessage.java | 161 ---- .../common/stringprep/StringPreparation.java | 38 - .../common/stringprep/StringPreparations.java | 73 -- .../util/AbstractCharAttributeValue.java | 67 -- .../common/util/AbstractStringWritable.java | 34 - .../scram/common/util/CharAttribute.java | 36 - .../scram/common/util/CharAttributeValue.java | 36 - .../ongres/scram/common/util/CryptoUtil.java | 180 ----- .../scram/common/util/Preconditions.java | 88 --- .../scram/common/util/StringWritable.java | 37 - .../scram/common/util/StringWritableCsv.java | 123 --- .../scram/common/util/UsAsciiUtils.java | 53 -- .../ongres/scram/common/RfcExampleSha1.java | 49 -- .../ongres/scram/common/RfcExampleSha256.java | 49 -- .../scram/common/ScramAttributeValueTest.java | 107 --- .../scram/common/ScramFunctionsTest.java | 255 ------ .../scram/common/ScramMechanismsTest.java | 133 ---- .../common/ScramStringFormattingTest.java | 84 -- .../common/gssapi/Gs2AttributeValueTest.java | 85 -- .../scram/common/gssapi/Gs2HeaderTest.java | 100 --- .../message/ClientFinalMessageTest.java | 44 -- .../message/ClientFirstMessageTest.java | 137 ---- .../message/ServerFinalMessageTest.java | 73 -- .../message/ServerFirstMessageTest.java | 54 -- .../scram/common/stringprep/SaslPrepTest.java | 87 --- .../stringprep/StringPreparationTest.java | 155 ---- .../util/AbstractCharAttributeValueTest.java | 101 --- .../ongres/scram/common/util/Base64Test.java | 51 -- .../scram/common/util/CryptoUtilTest.java | 63 -- .../common/util/StringWritableCsvTest.java | 146 ---- .../scram/common/util/UsAsciiUtilsTest.java | 94 --- coverage-report/pom.xml | 50 ++ debian/changelog | 24 + debian/control | 13 +- debian/libscram-java.poms | 5 +- debian/maven.ignoreRules | 1 + debian/maven.properties | 2 - debian/patches/01-multi-release-jar.patch | 18 + debian/patches/02-reactor-dependencies.patch | 13 + debian/patches/series | 2 + debian/rules | 3 - mvnw | 363 +++++---- mvnw.cmd | 68 +- pom.xml | 288 ++----- scram-client/pom.xml | 52 ++ .../it/jpms-scram-client/invoker.properties | 3 + scram-client/src/it/jpms-scram-client/pom.xml | 49 ++ .../src/test/java/module-info.java | 12 + .../test/java/test/scram/ScramClientTest.java | 48 ++ scram-client/src/it/settings.xml | 35 + .../scram/client/ClientFinalProcessor.java | 127 +++ .../com/ongres/scram/client/MessageFlow.java | 35 + .../com/ongres/scram/client/ScramClient.java | 549 +++++++++++++ .../scram/client/ServerFirstProcessor.java | 103 +++ .../com/ongres/scram/client/package-info.java | 12 + scram-client/src/main/java9/module-info.java | 9 + .../ChannelBindingNegotiationTest.java | 95 +++ .../java/com/example/ScramClientTest.java | 111 +++ .../java/com/ongres/scram/JarFileCheckIT.java | 72 ++ .../ongres/scram/client/RfcExampleSha1.java | 35 + .../ongres/scram/client/ScramBuilderTest.java | 125 +++ .../src/test/resources/SHA3-512withECDSA.pem | 16 + .../src/test/resources/SHA512withRSA.pem | 54 ++ scram-common/pom.xml | 52 ++ .../common/AbstractCharAttributeValue.java | 48 ++ .../scram/common/AbstractScramMessage.java | 23 + .../com/ongres/scram/common/CharSupplier.java | 20 + .../scram/common/ClientFinalMessage.java | 161 ++++ .../scram/common/ClientFirstMessage.java | 197 +++++ .../com/ongres/scram/common/CryptoUtil.java | 151 ++++ .../scram/common/Gs2AttributeValue.java | 43 ++ .../ongres/scram/common/Gs2Attributes.java | 75 ++ .../com/ongres/scram/common/Gs2CbindFlag.java | 57 ++ .../com/ongres/scram/common/Gs2Header.java | 197 +++++ .../scram/common/ScramAttributeValue.java | 41 + .../ongres/scram/common/ScramAttributes.java | 141 ++++ .../ongres/scram/common/ScramFunctions.java | 268 +++++++ .../ongres/scram/common/ScramMechanism.java | 272 +++++++ .../scram/common/ScramStringFormatting.java | 149 ++++ .../scram/common/ServerFinalMessage.java | 163 ++++ .../scram/common/ServerFirstMessage.java | 188 +++++ .../scram/common/StringPreparation.java | 87 +++ .../ongres/scram/common/StringWritable.java | 23 + .../scram/common/StringWritableCsv.java | 115 +++ .../com/ongres/scram/common/UsAsciiUtils.java | 57 ++ .../common/exception/ScramException.java | 33 + .../ScramInvalidServerSignatureException.java | 38 + .../common/exception/ScramParseException.java | 33 + .../exception/ScramRuntimeException.java | 33 + .../exception/ScramServerErrorException.java | 54 ++ .../common/exception/ServerErrorValue.java | 53 ++ .../scram/common/exception/package-info.java | 11 + .../com/ongres/scram/common/package-info.java | 13 + .../scram/common/util/Preconditions.java | 128 +++ .../scram/common/util/TlsServerEndpoint.java | 89 +++ scram-common/src/main/java9/module-info.java | 11 + .../java/com/ongres/scram/JarFileCheckIT.java | 72 ++ .../scram/common/ClientFinalMessageTest.java | 22 + .../scram/common/ClientFirstMessageTest.java | 125 +++ .../scram/common/Gs2AttributeValueTest.java | 77 ++ .../ongres/scram/common/Gs2HeaderTest.java | 85 ++ .../ongres/scram/common/RfcExampleSha1.java | 35 + .../ongres/scram/common/RfcExampleSha256.java | 37 + .../com/ongres/scram/common/SaslPrepTest.java | 42 + .../scram/common/ScramAttributeValueTest.java | 85 ++ .../scram/common/ScramFunctionsTest.java | 270 +++++++ .../scram/common/ScramMechanismTest.java | 89 +++ .../common/ScramStringFormattingTest.java | 67 ++ .../scram/common/ServerFinalMessageTest.java | 63 ++ .../scram/common/ServerFirstMessageTest.java | 34 + .../scram/common/StringPreparationTest.java | 145 ++++ .../scram/common/StringWritableCsvTest.java | 117 +++ .../ongres/scram/common/UsAsciiUtilsTest.java | 59 ++ .../ongres/scram/common/util/NonceTest.java | 48 ++ scram-parent/javadoc/style.css | 40 + scram-parent/pom.xml | 728 ++++++++++++++++++ thirdparty/bouncycastle-LICENSE | 7 - thirdparty/passlib_lambda-LICENSE | 22 - thirdparty/saslprep-LICENSE | 22 - 187 files changed, 7952 insertions(+), 9327 deletions(-) create mode 100644 .editorconfig create mode 100644 .mvn/maven.config delete mode 100644 CHANGELOG create mode 100644 CHANGELOG.md delete mode 100644 NOTICE create mode 100644 SECURITY.md create mode 100644 checks/checkstyle-header.txt create mode 100644 checks/checkstyle-suppressions.xml create mode 100644 checks/checkstyle.xml create mode 100644 checks/forbiddenapis.txt create mode 100644 checks/pmd-ruleset.xml create mode 100644 checks/spotbugs-exclude.xml delete mode 100644 client/README.md delete mode 100644 client/pom.xml delete mode 100644 client/src/main/java/com/ongres/scram/client/NonceSupplier.java delete mode 100644 client/src/main/java/com/ongres/scram/client/ScramClient.java delete mode 100644 client/src/main/java/com/ongres/scram/client/ScramSession.java delete mode 100644 client/src/test/java/com/ongres/scram/client/ScramClientTest.java delete mode 100644 client/src/test/java/com/ongres/scram/client/ScramSessionTest.java delete mode 100644 common/pom.xml delete mode 100644 common/spotbugs-exclude.xml delete mode 100644 common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java delete mode 100644 common/src/main/java/com/ongres/scram/common/ScramAttributes.java delete mode 100644 common/src/main/java/com/ongres/scram/common/ScramFunctions.java delete mode 100644 common/src/main/java/com/ongres/scram/common/ScramMechanism.java delete mode 100644 common/src/main/java/com/ongres/scram/common/ScramMechanisms.java delete mode 100644 common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64Encoder.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/base64/DecoderException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Encoder.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/base64/EncoderException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Arrays.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/CipherParameters.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DataLengthException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Digest.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DigestFactory.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/EncodableDigest.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/ExtendedDigest.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/GeneralDigest.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/HMac.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Integers.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/KeyParameter.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Mac.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Memoable.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PBEParametersGenerator.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PKCS5S2ParametersGenerator.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Pack.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/RuntimeCryptoException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/SHA256Digest.java delete mode 100644 common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Strings.java delete mode 100644 common/src/main/java/com/ongres/scram/common/exception/ScramException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java delete mode 100644 common/src/main/java/com/ongres/scram/common/gssapi/Gs2AttributeValue.java delete mode 100644 common/src/main/java/com/ongres/scram/common/gssapi/Gs2Attributes.java delete mode 100644 common/src/main/java/com/ongres/scram/common/gssapi/Gs2CbindFlag.java delete mode 100644 common/src/main/java/com/ongres/scram/common/gssapi/Gs2Header.java delete mode 100644 common/src/main/java/com/ongres/scram/common/message/ClientFinalMessage.java delete mode 100644 common/src/main/java/com/ongres/scram/common/message/ClientFirstMessage.java delete mode 100644 common/src/main/java/com/ongres/scram/common/message/ServerFinalMessage.java delete mode 100644 common/src/main/java/com/ongres/scram/common/message/ServerFirstMessage.java delete mode 100644 common/src/main/java/com/ongres/scram/common/stringprep/StringPreparation.java delete mode 100644 common/src/main/java/com/ongres/scram/common/stringprep/StringPreparations.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/AbstractCharAttributeValue.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/AbstractStringWritable.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/CharAttribute.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/CharAttributeValue.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/CryptoUtil.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/Preconditions.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/StringWritable.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/StringWritableCsv.java delete mode 100644 common/src/main/java/com/ongres/scram/common/util/UsAsciiUtils.java delete mode 100644 common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java delete mode 100644 common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java delete mode 100644 common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/ScramMechanismsTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/gssapi/Gs2AttributeValueTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/gssapi/Gs2HeaderTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/message/ClientFinalMessageTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/message/ClientFirstMessageTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/message/ServerFinalMessageTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/message/ServerFirstMessageTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/stringprep/SaslPrepTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/stringprep/StringPreparationTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/util/AbstractCharAttributeValueTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/util/Base64Test.java delete mode 100644 common/src/test/java/com/ongres/scram/common/util/CryptoUtilTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/util/StringWritableCsvTest.java delete mode 100644 common/src/test/java/com/ongres/scram/common/util/UsAsciiUtilsTest.java create mode 100644 coverage-report/pom.xml create mode 100644 debian/patches/01-multi-release-jar.patch create mode 100644 debian/patches/02-reactor-dependencies.patch create mode 100644 debian/patches/series create mode 100644 scram-client/pom.xml create mode 100644 scram-client/src/it/jpms-scram-client/invoker.properties create mode 100644 scram-client/src/it/jpms-scram-client/pom.xml create mode 100644 scram-client/src/it/jpms-scram-client/src/test/java/module-info.java create mode 100644 scram-client/src/it/jpms-scram-client/src/test/java/test/scram/ScramClientTest.java create mode 100644 scram-client/src/it/settings.xml create mode 100644 scram-client/src/main/java/com/ongres/scram/client/ClientFinalProcessor.java create mode 100644 scram-client/src/main/java/com/ongres/scram/client/MessageFlow.java create mode 100644 scram-client/src/main/java/com/ongres/scram/client/ScramClient.java create mode 100644 scram-client/src/main/java/com/ongres/scram/client/ServerFirstProcessor.java create mode 100644 scram-client/src/main/java/com/ongres/scram/client/package-info.java create mode 100644 scram-client/src/main/java9/module-info.java create mode 100644 scram-client/src/test/java/com/example/ChannelBindingNegotiationTest.java create mode 100644 scram-client/src/test/java/com/example/ScramClientTest.java create mode 100644 scram-client/src/test/java/com/ongres/scram/JarFileCheckIT.java create mode 100644 scram-client/src/test/java/com/ongres/scram/client/RfcExampleSha1.java create mode 100644 scram-client/src/test/java/com/ongres/scram/client/ScramBuilderTest.java create mode 100644 scram-client/src/test/resources/SHA3-512withECDSA.pem create mode 100644 scram-client/src/test/resources/SHA512withRSA.pem create mode 100644 scram-common/pom.xml create mode 100644 scram-common/src/main/java/com/ongres/scram/common/AbstractCharAttributeValue.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/AbstractScramMessage.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/CharSupplier.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ClientFinalMessage.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ClientFirstMessage.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/CryptoUtil.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/Gs2AttributeValue.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/Gs2Attributes.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/Gs2CbindFlag.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/Gs2Header.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ScramAttributes.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ScramFunctions.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ScramMechanism.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ServerFinalMessage.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/ServerFirstMessage.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/StringPreparation.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/StringWritable.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/StringWritableCsv.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/UsAsciiUtils.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/exception/ScramException.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/exception/ScramRuntimeException.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/exception/ServerErrorValue.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/exception/package-info.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/package-info.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/util/Preconditions.java create mode 100644 scram-common/src/main/java/com/ongres/scram/common/util/TlsServerEndpoint.java create mode 100644 scram-common/src/main/java9/module-info.java create mode 100644 scram-common/src/test/java/com/ongres/scram/JarFileCheckIT.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ClientFinalMessageTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ClientFirstMessageTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/Gs2AttributeValueTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/Gs2HeaderTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/SaslPrepTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ScramMechanismTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ServerFinalMessageTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/ServerFirstMessageTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/StringPreparationTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/StringWritableCsvTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/UsAsciiUtilsTest.java create mode 100644 scram-common/src/test/java/com/ongres/scram/common/util/NonceTest.java create mode 100644 scram-parent/javadoc/style.css create mode 100644 scram-parent/pom.xml delete mode 100644 thirdparty/bouncycastle-LICENSE delete mode 100644 thirdparty/passlib_lambda-LICENSE delete mode 100644 thirdparty/saslprep-LICENSE diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fff7c5f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true diff --git a/.gitignore b/.gitignore index 224e7f0..ab5ee6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,104 @@ -.pc/ +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders +.pmd + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +# VSCode +.vscode/ + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +/target +/*/target + +.flattened-pom.xml + +*pom.xml.versionsBackup diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7738d4e..c33cb1d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: openjdk:8-stretch +image: eclipse-temurin:21-jdk stages: - build @@ -13,8 +13,4 @@ build: paths: - .m2/ script: - - bash mvnw clean verify -P safer - -build-oracle-jdk-7: - extends: build - image: registry.gitlab.com/ongresinc/scram/oracle-jdk:7 + - ./mvnw clean verify diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000..b080f98 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,7 @@ +--strict-checksums +--show-version +--errors +--fail-fast +-DinstallAtEnd=true +-DdeployAtEnd=true +-DrootDirectory=${session.rootDirectory} diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index 066726d..7e9c1e3 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -1,100 +1,69 @@ /* - * Copyright 2007-present the original author or authors. + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - private static final String WRAPPER_VERSION = "0.5.5"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.ThreadLocalRandom; - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; +public final class MavenWrapperDownloader { + private static final String WRAPPER_VERSION = "3.3.4"; - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; + private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + public static void main(String[] args) { + log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION); - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } + if (args.length != 2) { + System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing"); + System.exit(1); } - System.out.println("- Downloading from: " + url); - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); + log(" - Downloader started"); + final URL wrapperUrl = URI.create(args[0]).toURL(); + final Path baseDir = Paths.get(".").toAbsolutePath().normalize(); + final Path wrapperJarPath = baseDir.resolve(args[1]).normalize(); + if (!wrapperJarPath.startsWith(baseDir)) { + throw new IOException("Invalid path: outside of allowed directory"); + } + downloadFileFromURL(wrapperUrl, wrapperJarPath); + log("Done"); + } catch (IOException e) { + System.err.println("- Error downloading: " + e.getMessage()); + if (VERBOSE) { + e.printStackTrace(); + } System.exit(1); } } - private static void downloadFileFromURL(String urlString, File destination) throws Exception { + private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath) + throws IOException { + log(" - Downloading to: " + wrapperJarPath); if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { final String username = System.getenv("MVNW_USERNAME"); final char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); @@ -105,13 +74,23 @@ protected PasswordAuthentication getPasswordAuthentication() { } }); } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); + Path temp = wrapperJarPath + .getParent() + .resolve(wrapperJarPath.getFileName() + "." + + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); + try (InputStream inStream = wrapperUrl.openStream()) { + Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING); + Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING); + } finally { + Files.deleteIfExists(temp); + } + log(" - Downloader complete"); + } + + private static void log(String msg) { + if (VERBOSE) { + System.out.println(msg); + } } } diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index fa87ad7..13e2391 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,7 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar +wrapperVersion=3.3.4 +distributionType=source +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip +distributionSha256Sum=0d7125e8c91097b36edb990ea5934e6c68b4440eef4ea96510a0f6815e7eeadb +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar +wrapperSha256Sum=4e2fbf6554bc8a4702cdfdd3bef464f423393d784ddbb037216320ce55d5e4e1 +alwaysUnpack=true diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 930bd39..0000000 --- a/CHANGELOG +++ /dev/null @@ -1,33 +0,0 @@ -# 2.1 - -* Updated saslprep to version 1.1 to remove a build dependency coming from stringprep module - -# 2.0 - -* Out of beta testing - -# 2.0-beta3 - -* Fixed licenses issues - -# 2.0-beta2 - -* Added saslprep tests - -# 2.0-beta1 - -* Add new dependency StringPrep - -# 1.9-beta1 - -* API change to be compatible with Java 7 -* Added standard SASLPrep -* Failover to bouncy castle implementation of PBKDF2WithHmacSHA256 to support Oracle JDK 7 - -# 1.0.0-beta.2 - -* Fix maven issue and javadoc - -# 1.0.0-beta.1 - -* First version diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5a74e5e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog +All notable changes to this project will be documented in this file. + +## [Unreleased] + +## [3.2] - 2025-09-16 +### :lock: Security +- Fix Timing Attack Vulnerability in SCRAM Authentication + +### :ghost: Maintenance +- Updated dependencies and maven plugins. +- Use `central-publishing-maven-plugin` to deploy to Maven Central. + +## [3.1] - 2024-06-26 +### :building_construction: Improvements +- Ensure the `LICENSE` file is included in the Jar file. +- Update of the `saslprep` dependency to 2.2. + +### :ghost: Maintenance +- Added coverage report module. +- Updated dependencies and maven plugins. +- Remove `nexus-staging-maven-plugin`. + +## [3.0] - 2024-04-03 +### :boom: Breaking changes +- :warning: Full refactor of the `scram` java implementation, this release is compatible with Java 8+, but it's incompatible with previous releases :warning: + +### :rocket: New features +- Fully rewrite the `ScramClient` allowing negotiation of channel-binding properly. +- Create Multi-release Modular JARs, the modules names are: + - `com.ongres.scram.common` for the common scram messages. + - `com.ongres.scram.client` for the scram client implementation. +- Add `StringPreparation.POSTGRESQL_PREPARATION`, for any error in SASL preparation, it falls back to return the raw string. +- Now the released jars are reproducible. +- Publish CycloneDX SBOM. +- Implementation of `tls-server-end-point` channel binding data extraction. + +### :building_construction: Improvements +- Update of the `saslprep` dependency to 2.1. +- Now the password is passed as a `char[]`. +- Improve Javadoc documentation. + +### :ghost: Maintenance +- Migrate the main repo back to GitHub. +- Remove the shaded Bouncy Castle pbkdf2 and base64 implementation used for Java 7 support. + +[3.0]: https://github.com/ongres/scram/compare/2.1...3.0 +[3.1]: https://github.com/ongres/scram/compare/3.0...3.1 +[3.2]: https://github.com/ongres/scram/compare/3.1...3.2 +[Unreleased]: https://github.com/ongres/scram/compare/3.2...main diff --git a/LICENSE b/LICENSE index 13fdb10..6837c1e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017, OnGres. +Copyright (c) 2017 OnGres, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/NOTICE b/NOTICE deleted file mode 100644 index 168a4fd..0000000 --- a/NOTICE +++ /dev/null @@ -1,25 +0,0 @@ -This source code includes source code from other projects. Below the list -of the project were the source code has been copied together with the -path of the copied source files, their license, their notice (if any) -and the modification that have been done. - -1. saslprep - -Dependency SaslPrep is used from project stringprep developed by various authors (https://gitlab.com/ongresinc/stringprep) -and licensed under the BSD 2-Clause "Simplified" License (see thirdparty/saslprep-LICENSE). - -2. Tests of SaslPrep - -File common/src/test/java/com/ongres/scram/common/stringprep/ScramFunctionsTest contains tests -from project passlib_lambda developed by davidchua (https://github.com/davidchua/passlib_lambda) -and licensed under the MIT license (see thirdparty/passlib_lambda-LICENSE). -The source has been modified to be included under the Java package -com.ongres.scram.common.ScramFunctionsTest and to only include used parts. - -3. bouncy castle - -All Java source files included into folder common/src/main/java/com/ongres/scram/common/bouncycastle -are copied from project bouncy castle developed by various authors (https://www.bouncycastle.org/) -and licensed under the bouncy castle license (see thirdparty/bouncycastle-LICENSE). -The source has been modified to be included under the Java package com.ongres.scram.common.bouncycastle -and to only include used parts. diff --git a/README.md b/README.md index caa651c..138111e 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,72 @@ # SCRAM Java Implementation -## Overview - -SCRAM (Salted Challenge Response Authentication Mechanism) is part of the family -of Simple Authentication and Security Layer -([SASL, RFC 4422](https://tools.ietf.org/html/rfc4422)) authentication -mechanisms. - -It is described as part of [RFC 5802](https://tools.ietf.org/html/rfc5802) and -[RFC7677](https://tools.ietf.org/html/rfc7677). - -This project will serve for the basis of -[PostgreSQL's](https://www.postgresql.org) [JDBC](https://jdbc.postgresql.org/) -driver SCRAM support (coming in PostgreSQL 10). - -The code is licensed under the BSD "Simplified 2 Clause" license (see [LICENSE](LICENSE)). - - -## Goals - -This project aims to provide a complete clean-room implementation of SCRAM. It -is written in Java and provided in a modular, re-usable way, independent of -other software or programs. - -Current functionality includes: +![Maven Central Version](https://img.shields.io/maven-central/v/com.ongres.scram/scram-aggregator) +[![Reproducible Builds](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jvm-repo-rebuild/reproducible-central/master/content/com/ongres/scram/badge.json)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/com/ongres/scram/README.md) +![GitHub License](https://img.shields.io/github/license/ongres/scram) -* [Common infrastructure](common) for building both client and server SCRAM implementations. -* A [Client API](client) for using SCRAM as a client. -* Support for both SHA-1 and SHA-256. -* Basic support for channel binding. -* No runtime external dependencies. -* Well tested (+75 tests). - - -Current limitations: - -* Server API and integration tests will be added soon. - - -## How to use the client API - -Please read [Client's README.md](client). - -Javadoc: [![Javadocs](http://javadoc.io/badge/com.ongres.scram/client.svg?label=client)](http://javadoc.io/doc/com.ongres.scram/client) +> Salted Challenge Response Authentication Mechanism (SCRAM) +## Overview +SCRAM (Salted Challenge Response Authentication Mechanism) is part of the family of +Simple Authentication and Security Layer +([SASL, RFC 4422](https://datatracker.ietf.org/doc/html/rfc4422)) authentication mechanisms. It is described as part of [RFC 5802](https://datatracker.ietf.org/doc/html/rfc5802) and +[RFC 7677](https://datatracker.ietf.org/doc/html/rfc7677). -## Common API +This project provides a robust and well-tested implementation of the Salted Challenge +Response Authentication Mechanism (SCRAM) in Java. It adheres to the specifications +outlined in RFC 5802 and RFC 7677, ensuring secure user authentication. -'common' is the module that contains code common to both client and server SCRAM projects. -If you with to develop either a client or server API, you may very well build on top of this -API. Import maven dependency: +This SCRAM Java implementation can be used for [PostgreSQL](https://www.postgresql.org) (which supports [SASL authentication](https://www.postgresql.org/docs/current/sasl-authentication.html) since PostgreSQL 10) through the [PostgreSQL JDBC Driver](https://jdbc.postgresql.org/) and others projects that connect from Java. - - com.ongres.scram - common - - -and check the Javadoc: [![Javadocs](http://javadoc.io/badge/com.ongres.scram/common.svg)](http://javadoc.io/doc/com.ongres.scram/common) +The code is licensed under the BSD "Simplified 2 Clause" license (see [LICENSE](LICENSE)). +## Key Features + +* Clean-room Implementation: The code is written from scratch, offering a reliable and independent solution. +* Modular Structure: The library is designed for modularity, promoting reusability and maintainability. +* Client-Server Support: The implementation caters to both client and server-side SCRAM usage in the `scram-common` module. For the moment only the `scram-client` module is implemented. +* Multiple Hashing Algorithms: It supports `SHA-1` and `SHA-256` as described in the official RFC 5802 and RFC 7677 respectively, and also provides `SHA-224`, `SHA-384` and `SHA-512` for flexible security strength selection. +* Channel Binding support: The library supports client mechanism negotiation with support of channel binding data provided externally. +* Extensive Testing: The codebase is thoroughly tested to guarantee its functionality and correctness. +* Minimal Dependencies: The library operates with a single dependency based on the implementation of the [SASLprep](https://github.com/ongres/stringprep) required by the RFC 5802. + +## How to use the SCRAM Client API + +[![Maven Central](https://img.shields.io/badge/maven--central-scram_client-informational?style=for-the-badge&logo=apache-maven&logoColor=red)](https://maven-badges.herokuapp.com/maven-central/com.ongres.scram/scram-client) + +Javadoc: [![Javadocs](http://javadoc.io/badge/com.ongres.scram/scram-client.svg?label=scram-client)](http://javadoc.io/doc/com.ongres.scram/scram-client) + +### Example of use: +```java +byte[] cbindData = ... +ScramClient scramClient = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) + .username("user") + .password("pencil".toCharArray()) + .channelBinding("tls-server-end-point", cbindData) // client supports channel binding + .build(); + // The build() call negotiates the SCRAM mechanism to be used. In this example, + // since the server advertise support for the SCRAM-SHA-256-PLUS mechanism, + // and the builder is set with the channel binding type and data, the constructed + // scramClient will use the "SCRAM-SHA-256-PLUS" mechanism for authentication. + +// FE-> Send the client-first-message ("p=...,,n=...,r=...") +ClientFirstMessage clientFirstMsg = scramClient.clientFirstMessage(); +... +// <-BE Receive the server-first-message +ServerFirstMessage serverFirstMsg = scramClient.serverFirstMessage("r=...,s=...,i=..."); +... +// FE-> Send the client-final-message ("c=...,r=...,p=...") +ClientFinalMessage clientFinalMsg = scramClient.clientFinalMessage(); +... +// <-BE Receive the server-final-message, throw an ScramException on error or invalid signature +ServerFinalMessage serverFinalMsg = scramClient.serverFinalMessage("v=..."); +``` ## Contributing -Please submit [Merge Requests](https://gitlab.com/ongresinc/scram) for code contributions. -Make sure to compile with `mvn -Psafer` before submitting a MR. - -Releases (on the master branch only) must be verified with: +We welcome contributions to this project! Feel free to submit pull requests that improve the codebase, add features, or fix bugs. Please make sure your contributions adhere to coding style guidelines and include thorough testing. +Make sure to compile with `./mvnw verify -Pchecks` before submitting a PR. - mvn -Psafer -Pmaster-branch +By making a contribution to this project, you certify that you adhere to requirements of the [DCO](https://developercertificate.org/) by signing-off your commits (`git commit -s`).: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5e9539c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Supported Versions + +The following table outlines which versions of `scram` are actively supported with security updates. Please upgrade to a supported release to ensure you receive patches for any security issues. + +| Version | Supported | Java support | +| ------- | ------------------ | ------------ | +| 3.x | :white_check_mark: | Java 8+ | +| < 3.0 | :x: | Java 7+ | + +## Reporting a Vulnerability + +If you believe you have found a security vulnerability, please report it to us privately through GitHub’s security advisory system: +[Report a vulnerability](../../security/advisories/new) + +We will investigate promptly and work with you to fix the issue. + +--- + +## Security Best Practices for Users + +- Always use the latest supported version of `scram`. +- Monitor [GitHub Releases](https://github.com/ongres/scram/releases) for security patches. +- Consider subscribing to repository notifications for updates. diff --git a/checks/checkstyle-header.txt b/checks/checkstyle-header.txt new file mode 100644 index 0000000..2444622 --- /dev/null +++ b/checks/checkstyle-header.txt @@ -0,0 +1,4 @@ +^\/\*$ +^ \* Copyright \(c\) 20[12]\d OnGres, Inc\.$ +^ \* SPDX-License-Identifier: BSD-2-Clause$ +^ \*\/$ \ No newline at end of file diff --git a/checks/checkstyle-suppressions.xml b/checks/checkstyle-suppressions.xml new file mode 100644 index 0000000..7129fc8 --- /dev/null +++ b/checks/checkstyle-suppressions.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/checks/checkstyle.xml b/checks/checkstyle.xml new file mode 100644 index 0000000..cc50358 --- /dev/null +++ b/checks/checkstyle.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/checks/forbiddenapis.txt b/checks/forbiddenapis.txt new file mode 100644 index 0000000..57bd571 --- /dev/null +++ b/checks/forbiddenapis.txt @@ -0,0 +1,2 @@ + +java.util.Arrays#equals(byte[],byte[]) @ Replace with java.security.MessageDigest#isEqual(byte[],byte[]) diff --git a/checks/pmd-ruleset.xml b/checks/pmd-ruleset.xml new file mode 100644 index 0000000..34937b9 --- /dev/null +++ b/checks/pmd-ruleset.xml @@ -0,0 +1,41 @@ + + + + Custom Rules + + .*/target/.* + .*/generated/.* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/checks/spotbugs-exclude.xml b/checks/spotbugs-exclude.xml new file mode 100644 index 0000000..e5e6a79 --- /dev/null +++ b/checks/spotbugs-exclude.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/README.md b/client/README.md deleted file mode 100644 index ca67bf5..0000000 --- a/client/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# SCRAM Client API - -[![Javadocs](http://javadoc.io/badge/com.ongres.scram/client.svg?label=client)](http://javadoc.io/doc/com.ongres.scram/client) - -For general description, please refer to [the main README.md](https://gitlab.com/ongresinc/scram). - - -## How to use the client API - -1. Add Maven (or equivalent) dependencies : -```xml - - com.ongres.scram - client - VERSION - -``` - -2. Get a ```ScramClient```. A ```ScramClient``` can be configured with several parameters, - and matches a given server. From the client, several ```ScramSession```s can be created, - for potentially different users. Under certain conditions (read Javadoc) it is thread safe. - - A simple example could be: -```java -ScramClient scramClient = ScramClient - .channelBinding(ChannelBinding.NO) - .stringPreparation(NO_PREPARATION) - .serverMechanisms("SCRAM-SHA-1") - .setup(); -``` - - More configuration methods and options are available, as shown below: -```java -ScramClient scramClient = ScramClient - .channelBinding(ChannelBinding.YES) - .stringPreparation(NO_PREPARATION) - .serverMechanisms("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS") - .nonceSupplier(() -> generateNonce()) - .secureRandomAlgorithmProvider("algorithm", "provider") - .setup(); -``` - -3. For each authentication round and/or user, create a ```ScramSession``` and get the client-first-message: -```java -ScramSession scramSession = scramClient.scramSession("user"); -scramSession.clientFirstMessage() -``` - -4. Receive the server-first-message: -```java -ScramSession.ServerFirstProcessor serverFirst = scramSession.receiveServerFirstMessage(message); -// Read the salt and iterations: -serverFirst.getSalt() -serverFirst.getIteration() -``` - -5. Generate the client-last-message: -```java -ScramSession.ClientFinalProcessor clientFinal = serverFirst.finalMessagesHandler("password"); -clientFinal.clientFinalMessage() -``` - -6. Receive the server-last-message, check if is valid or error, etc: -```java -ScramSession.ServerFinalProcessor serverFinal = clientFinal.receiveServerFinalMessage(message); -// Methods to check if it is error, get error, verify signature -serverFinal.isError() -serverFinal.getErrorMessage() -serverFinal.verifyServerSignature() -``` - -## Example - -The following example runs a full SCRAM session, using the RFC's data as an example. - -```java -ScramClient scramClient = ScramClient - .channelBinding(ScramClient.ChannelBinding.NO) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1") - .nonceSupplier(() -> "fyko+d2lbbFgONRv9qkxdawL") - .setup(); - -ScramSession scramSession = scramClient.scramSession("user"); -System.out.println(scramSession.clientFirstMessage()); - -ScramSession.ServerFirstProcessor serverFirstProcessor = scramSession.receiveServerFirstMessage( - "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096" -); -System.out.println("Salt: " + serverFirstProcessor.getSalt() + ", i: " + serverFirstProcessor.getIteration()); - -ScramSession.ClientFinalProcessor clientFinalProcessor - = serverFirstProcessor.clientFinalProcessor("pencil"); -System.out.println(clientFinalProcessor.clientFinalMessage()); - -ScramSession.ServerFinalProcessor serverFinalProcessor - = clientFinalProcessor.receiveServerFinalMessage("v=rmF9pqV8S7suAoZWja4dJRkFsKQ="); -if(serverFinalProcessor.isError()) { - // throw authentication exception -} - -System.out.println(serverFinalProcessor.verifyServerSignature()); -``` diff --git a/client/pom.xml b/client/pom.xml deleted file mode 100644 index e3eeba3..0000000 --- a/client/pom.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - 4.0.0 - - - parent - com.ongres.scram - 2.1 - - - client - - SCRAM - client - - - - com.ongres.scram - common - ${project.version} - - - - junit - junit - test - - - com.ongres.scram - common - ${project.version} - test - test-jar - - - - - - javadoc.io-links - - - !maven.javadoc.skip - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.8 - - - unpack-javadoc - package - - unpack - - - - - ${project.groupId} - common - javadoc - ${project.version} - false - ${project.build.directory}/common-javadoc - - - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - - http://static.javadoc.io/${project.groupId}/common/${project.version} - ${project.build.directory}/common-javadoc - - - - - - - - - - diff --git a/client/src/main/java/com/ongres/scram/client/NonceSupplier.java b/client/src/main/java/com/ongres/scram/client/NonceSupplier.java deleted file mode 100644 index 0489793..0000000 --- a/client/src/main/java/com/ongres/scram/client/NonceSupplier.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.client; - -public interface NonceSupplier { - String get(); -} diff --git a/client/src/main/java/com/ongres/scram/client/ScramClient.java b/client/src/main/java/com/ongres/scram/client/ScramClient.java deleted file mode 100644 index 22de0ca..0000000 --- a/client/src/main/java/com/ongres/scram/client/ScramClient.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.client; - - -import com.ongres.scram.common.ScramMechanism; -import com.ongres.scram.common.ScramMechanisms; -import com.ongres.scram.common.gssapi.Gs2CbindFlag; -import com.ongres.scram.common.stringprep.StringPreparation; -import com.ongres.scram.common.util.CryptoUtil; - -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.List; - -import static com.ongres.scram.common.util.Preconditions.*; - - -/** - * A class that can be parametrized to generate {@link ScramSession}s. - * This class supports the channel binding and string preparation mechanisms that are provided by module scram-common. - * - * The class is fully configurable, including options to selected the desired channel binding, - * automatically pick the best client SCRAM mechanism based on those supported (advertised) by the server, - * selecting an externally-provided SecureRandom instance or an external nonceProvider, or choosing the nonce length. - * - * This class is thread-safe if the two following conditions are met: - *
    - *
  • The SecureRandom used ({@link SecureRandom} by default) are thread-safe too. - * The contract of {@link java.util.Random} marks it as thread-safe, so inherited classes are also expected - * to maintain it. - *
  • - *
  • No external nonceSupplier is provided; or if provided, it is thread-safe.
  • - *
- * So this class, once instantiated via the {@link Builder#setup()}} method, can serve for multiple users and - * authentications. - */ -public class ScramClient { - /** - * Length (in characters, bytes) of the nonce generated by default (if no nonce supplier is provided) - */ - public static final int DEFAULT_NONCE_LENGTH = 24; - - /** - * Select whether this client will support channel binding or not - */ - public enum ChannelBinding { - /** - * Don't use channel binding. Server must support at least one non-channel binding mechanism. - */ - NO(Gs2CbindFlag.CLIENT_NOT), - - /** - * Force use of channel binding. Server must support at least one channel binding mechanism. - * Channel binding data will need to be provided as part of the ClientFirstMessage. - */ - YES(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED), - - /** - * Channel binding is preferred. Non-channel binding mechanisms will be used if either the server does not - * support channel binding, or no channel binding data is provided as part of the ClientFirstMessage - */ - IF_SERVER_SUPPORTS_IT(Gs2CbindFlag.CLIENT_YES_SERVER_NOT) - ; - - private final Gs2CbindFlag gs2CbindFlag; - - ChannelBinding(Gs2CbindFlag gs2CbindFlag) { - this.gs2CbindFlag = gs2CbindFlag; - } - - public Gs2CbindFlag gs2CbindFlag() { - return gs2CbindFlag; - } - } - - private final ChannelBinding channelBinding; - private final StringPreparation stringPreparation; - private final ScramMechanism scramMechanism; - private final SecureRandom secureRandom; - private final NonceSupplier nonceSupplier; - - private ScramClient( - ChannelBinding channelBinding, StringPreparation stringPreparation, - ScramMechanism nonChannelBindingMechanism, ScramMechanism channelBindingMechanism, - SecureRandom secureRandom, NonceSupplier nonceSupplier - ) { - assert null != channelBinding : "channelBinding"; - assert null != stringPreparation : "stringPreparation"; - assert null != nonChannelBindingMechanism || null != channelBindingMechanism - : "Either a channel-binding or a non-binding mechanism must be present"; - assert null != secureRandom : "secureRandom"; - assert null != nonceSupplier : "nonceSupplier"; - - - this.channelBinding = channelBinding; - this.stringPreparation = stringPreparation; - this.scramMechanism = null != nonChannelBindingMechanism ? nonChannelBindingMechanism : channelBindingMechanism; - this.secureRandom = secureRandom; - this.nonceSupplier = nonceSupplier; - } - - /** - * Selects for the client whether to use channel binding. - * Refer to {@link ChannelBinding} documentation for the description of the possible values. - * @param channelBinding The channel binding setting - * @return The next step in the chain (PreBuilder1). - * @throws IllegalArgumentException If channelBinding is null - */ - public static PreBuilder1 channelBinding(ChannelBinding channelBinding) throws IllegalArgumentException { - return new PreBuilder1(checkNotNull(channelBinding, "channelBinding")); - } - - /** - * This class is not meant to be used directly. - * Use {@link ScramClient#channelBinding(ChannelBinding)} instead. - */ - public static class PreBuilder1 { - protected final ChannelBinding channelBinding; - - private PreBuilder1(ChannelBinding channelBinding) { - this.channelBinding = channelBinding; - } - - /** - * Selects the string preparation algorithm to use by the client. - * @param stringPreparation The string preparation algorithm - * @throws IllegalArgumentException If stringPreparation is null - */ - public PreBuilder2 stringPreparation(StringPreparation stringPreparation) throws IllegalArgumentException { - return new PreBuilder2(channelBinding, checkNotNull(stringPreparation, "stringPreparation")); - } - } - - /** - * This class is not meant to be used directly. - * Use {@link ScramClient#channelBinding(ChannelBinding)}.{#stringPreparation(StringPreparation)} instead. - */ - public static class PreBuilder2 extends PreBuilder1 { - protected final StringPreparation stringPreparation; - protected ScramMechanism nonChannelBindingMechanism = null; - protected ScramMechanism channelBindingMechanism = null; - - private PreBuilder2(ChannelBinding channelBinding, StringPreparation stringPreparation) { - super(channelBinding); - this.stringPreparation = stringPreparation; - } - - /** - * Inform the client of the SCRAM mechanisms supported by the server. - * Based on this list, the channel binding settings previously specified, - * and the relative strength of the supported SCRAM mechanisms for this client, - * the client will have enough data to select which mechanism to use for future interactions with the server. - * All names provided here need to be standar IANA Registry names for SCRAM mechanisms, or will be ignored. - * - * @see - * SASL SCRAM Family Mechanisms - * - * @param serverMechanisms One or more IANA-registered SCRAM mechanism names, as advertised by the server - * @throws IllegalArgumentException If no server mechanisms are provided - */ - public Builder selectMechanismBasedOnServerAdvertised(String... serverMechanisms) { - checkArgument(null != serverMechanisms && serverMechanisms.length > 0, "serverMechanisms"); - - nonChannelBindingMechanism = ScramMechanisms.selectMatchingMechanism(false, serverMechanisms); - if(channelBinding == ChannelBinding.NO && null == nonChannelBindingMechanism) { - throw new IllegalArgumentException("Server does not support non channel binding mechanisms"); - } - - channelBindingMechanism = ScramMechanisms.selectMatchingMechanism(true, serverMechanisms); - if(channelBinding == ChannelBinding.YES && null == channelBindingMechanism) { - throw new IllegalArgumentException("Server does not support channel binding mechanisms"); - } - - if(null == channelBindingMechanism && null == nonChannelBindingMechanism) { - throw new IllegalArgumentException("There are no matching mechanisms between client and server"); - } - - return new Builder(channelBinding, stringPreparation, nonChannelBindingMechanism, channelBindingMechanism); - } - - /** - * Inform the client of the SCRAM mechanisms supported by the server. - * Calls {@link Builder#selectMechanismBasedOnServerAdvertised(String...)} - * with the results of splitting the received comma-separated values. - * @param serverMechanismsCsv A CSV (Comma-Separated Values) String, containining all the SCRAM mechanisms - * supported by the server - * @throws IllegalArgumentException If selectMechanismBasedOnServerAdvertisedCsv is null - */ - public Builder selectMechanismBasedOnServerAdvertisedCsv(String serverMechanismsCsv) - throws IllegalArgumentException { - return selectMechanismBasedOnServerAdvertised( - checkNotNull(serverMechanismsCsv, "serverMechanismsCsv").split(",") - ); - } - - /** - * Select a fixed client mechanism. It must be compatible with the channel binding selection previously - * performed. If automatic selection based on server advertised mechanisms is preferred, please use methods - * {@link Builder#selectMechanismBasedOnServerAdvertised(String...)} or - * {@link Builder#selectMechanismBasedOnServerAdvertisedCsv(String)}. - * @param scramMechanism The selected scram mechanism - * @throws IllegalArgumentException If the selected mechanism is null or not compatible with the prior - * channel binding selection, - * or channel binding selection is dependent on the server advertised methods - */ - public Builder selectClientMechanism(ScramMechanism scramMechanism) { - checkNotNull(scramMechanism, "scramMechanism"); - if(channelBinding == ChannelBinding.IF_SERVER_SUPPORTS_IT) { - throw new IllegalArgumentException( - "If server selection is considered, no direct client selection should be performed" - ); - } - if( - channelBinding == ChannelBinding.YES && ! scramMechanism.supportsChannelBinding() - || - channelBinding == ChannelBinding.NO && scramMechanism.supportsChannelBinding() - ) { - throw new IllegalArgumentException("Incompatible selection of mechanism and channel binding"); - } - - if(scramMechanism.supportsChannelBinding()) { - return new Builder(channelBinding, stringPreparation, null, scramMechanism); - } else { - return new Builder(channelBinding, stringPreparation, scramMechanism, null); - } - } - } - - /** - * This class is not meant to be used directly. - * Use instead {@link ScramClient#channelBinding(ChannelBinding)} and chained methods. - */ - public static class Builder extends PreBuilder2 { - private final ScramMechanism nonChannelBindingMechanism; - private final ScramMechanism channelBindingMechanism; - - private SecureRandom secureRandom = new SecureRandom(); - private NonceSupplier nonceSupplier; - private int nonceLength = DEFAULT_NONCE_LENGTH; - - private Builder( - ChannelBinding channelBinding, StringPreparation stringPreparation, - ScramMechanism nonChannelBindingMechanism, ScramMechanism channelBindingMechanism - ) { - super(channelBinding, stringPreparation); - this.nonChannelBindingMechanism = nonChannelBindingMechanism; - this.channelBindingMechanism = channelBindingMechanism; - } - - /** - * Optional call. Selects a non-default SecureRandom instance, - * based on the given algorithm and optionally provider. - * This SecureRandom instance will be used to generate secure random values, - * like the ones required to generate the nonce - * (unless an external nonce provider is given via {@link Builder#nonceSupplier(NonceSupplier)}). - * Algorithm and provider names are those supported by the {@link SecureRandom} class. - * @param algorithm The name of the algorithm to use. - * @param provider The name of the provider of SecureRandom. Might be null. - * @return The same class - * @throws IllegalArgumentException If algorithm is null, or either the algorithm or provider are not supported - */ - public Builder secureRandomAlgorithmProvider(String algorithm, String provider) - throws IllegalArgumentException { - checkNotNull(algorithm, "algorithm"); - try { - secureRandom = null == provider ? - SecureRandom.getInstance(algorithm) : - SecureRandom.getInstance(algorithm, provider); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { - throw new IllegalArgumentException("Invalid algorithm or provider", e); - } - - return this; - } - - /** - * Optional call. The client will use a default nonce generator, - * unless an external one is provided by this method. * - * @param nonceSupplier A supplier of valid nonce Strings. - * Please note that according to the - * SCRAM RFC - * only ASCII printable characters (except the comma, ',') are permitted on a nonce. - * Length is not limited. - * @return The same class - * @throws IllegalArgumentException If nonceSupplier is null - */ - public Builder nonceSupplier(NonceSupplier nonceSupplier) throws IllegalArgumentException { - this.nonceSupplier = checkNotNull(nonceSupplier, "nonceSupplier"); - - return this; - } - - /** - * Sets a non-default ({@link ScramClient#DEFAULT_NONCE_LENGTH}) length for the nonce generation, - * if no alternate nonceSupplier is provided via {@link Builder#nonceSupplier(NonceSupplier)}. - * @param length The length of the nonce. Must be positive and greater than 0 - * @return The same class - * @throws IllegalArgumentException If length is less than 1 - */ - public Builder nonceLength(int length) throws IllegalArgumentException { - this.nonceLength = gt0(length, "length"); - - return this; - } - - /** - * Gets the client, fully constructed and configured, with the provided channel binding, string preparation - * properties, and the selected SCRAM mechanism based on server supported mechanisms. - * If no SecureRandom algorithm and provider were provided, a default one would be used. - * If no nonceSupplier was provided, a default nonce generator would be used, - * of the {@link ScramClient#DEFAULT_NONCE_LENGTH} length, unless {@link Builder#nonceLength(int)} is called. - * @return The fully built instance. - */ - public ScramClient setup() { - return new ScramClient( - channelBinding, stringPreparation, nonChannelBindingMechanism, channelBindingMechanism, - secureRandom, - nonceSupplier != null ? nonceSupplier : new NonceSupplier() { - @Override - public String get() { - return CryptoUtil.nonce(nonceLength, secureRandom); - } - } - - ); - } - } - - public StringPreparation getStringPreparation() { - return stringPreparation; - } - - public ScramMechanism getScramMechanism() { - return scramMechanism; - } - - /** - * List all the supported SCRAM mechanisms by this client implementation - * @return A list of the IANA-registered, SCRAM supported mechanisms - */ - public static List supportedMechanisms() { - List supportedMechanisms = new ArrayList<>(); - for (ScramMechanisms scramMechanisms : ScramMechanisms.values()) { - supportedMechanisms.add(scramMechanisms.getName()); - } - return supportedMechanisms; - } - - /** - * Instantiates a {@link ScramSession} for the specified user and this parametrized generator. - * @param user The username of the authentication exchange - * @return The ScramSession instance - */ - public ScramSession scramSession(String user) { - return new ScramSession(scramMechanism, stringPreparation, checkNotEmpty(user, "user"), nonceSupplier.get()); - } -} diff --git a/client/src/main/java/com/ongres/scram/client/ScramSession.java b/client/src/main/java/com/ongres/scram/client/ScramSession.java deleted file mode 100644 index 45d94a3..0000000 --- a/client/src/main/java/com/ongres/scram/client/ScramSession.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.client; - - -import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - -import com.ongres.scram.common.ScramFunctions; -import com.ongres.scram.common.ScramMechanism; -import com.ongres.scram.common.bouncycastle.base64.Base64; -import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.exception.ScramServerErrorException; -import com.ongres.scram.common.gssapi.Gs2CbindFlag; -import com.ongres.scram.common.message.ClientFinalMessage; -import com.ongres.scram.common.message.ClientFirstMessage; -import com.ongres.scram.common.message.ServerFinalMessage; -import com.ongres.scram.common.message.ServerFirstMessage; -import com.ongres.scram.common.stringprep.StringPreparation; - - -/** - * A class that represents a SCRAM client. Use this class to perform a SCRAM negotiation with a SCRAM server. - * This class performs an authentication execution for a given user, and has state related to it. - * Thus, it cannot be shared across users or authentication executions. - */ -public class ScramSession { - private final ScramMechanism scramMechanism; - private final StringPreparation stringPreparation; - private final String user; - private final String nonce; - private ClientFirstMessage clientFirstMessage; - private String serverFirstMessageString; - - /** - * Constructs a SCRAM client, to perform an authentication for a given user. - * This class can be instantiated directly, - * but it is recommended that a {@link ScramClient} is used instead. - * @param scramMechanism The SCRAM mechanism that will be using this client - * @param stringPreparation - * @param user - * @param nonce - */ - public ScramSession(ScramMechanism scramMechanism, StringPreparation stringPreparation, String user, String nonce) { - this.scramMechanism = checkNotNull(scramMechanism, "scramMechanism"); - this.stringPreparation = checkNotNull(stringPreparation, "stringPreparation"); - this.user = checkNotEmpty(user, "user"); - this.nonce = checkNotEmpty(nonce, "nonce"); - } - - private String setAndReturnClientFirstMessage(ClientFirstMessage clientFirstMessage) { - this.clientFirstMessage = clientFirstMessage; - - return clientFirstMessage.toString(); - } - - /** - * Returns the text representation of a SCRAM client-first-message, with the GSS-API header values indicated. - * @param gs2CbindFlag The channel binding flag - * @param cbindName The channel binding algorithm name, if channel binding is supported, or null - * @param authzid The optional - * @return The message - */ - public String clientFirstMessage(Gs2CbindFlag gs2CbindFlag, String cbindName, String authzid) { - return setAndReturnClientFirstMessage(new ClientFirstMessage(gs2CbindFlag, authzid, cbindName, user, nonce)); - } - - /** - * Returns the text representation of a SCRAM client-first-message, with no channel binding nor authzid. - * @return The message - */ - public String clientFirstMessage() { - return setAndReturnClientFirstMessage(new ClientFirstMessage(user, nonce)); - } - - /** - * Process a received server-first-message. - * Generate by calling {@link #receiveServerFirstMessage(String)}. - */ - public class ServerFirstProcessor { - private final ServerFirstMessage serverFirstMessage; - - private ServerFirstProcessor(String receivedServerFirstMessage) throws ScramParseException { - serverFirstMessageString = receivedServerFirstMessage; - serverFirstMessage = ServerFirstMessage.parseFrom(receivedServerFirstMessage, nonce); - } - - public String getSalt() { - return serverFirstMessage.getSalt(); - } - - public int getIteration() { - return serverFirstMessage.getIteration(); - } - - /** - * Generates a {@link ClientFinalProcessor}, that allows to generate the client-final-message and also - * receive and parse the server-first-message. It is based on the user's password. - * @param password The user's password - * @return The handler - * @throws IllegalArgumentException If the message is null or empty - */ - public ClientFinalProcessor clientFinalProcessor(String password) throws IllegalArgumentException { - return new ClientFinalProcessor( - serverFirstMessage.getNonce(), - checkNotEmpty(password, "password"), - getSalt(), - getIteration() - ); - } - - /** - * Generates a {@link ClientFinalProcessor}, that allows to generate the client-final-message and also - * receive and parse the server-first-message. It is based on the clientKey and storedKey, - * which, if available, provide an optimized path versus providing the original user's password. - * @param clientKey The client key, as per the SCRAM algorithm. - * It can be generated with: - * {@link ScramFunctions#clientKey(ScramMechanism, StringPreparation, String, byte[], int)} - * @param storedKey The stored key, as per the SCRAM algorithm. - * It can be generated from the client key with: - * {@link ScramFunctions#storedKey(ScramMechanism, byte[])} - * @return The handler - * @throws IllegalArgumentException If the message is null or empty - */ - public ClientFinalProcessor clientFinalProcessor(byte[] clientKey, byte[] storedKey) - throws IllegalArgumentException { - return new ClientFinalProcessor( - serverFirstMessage.getNonce(), - checkNotNull(clientKey, "clientKey"), - checkNotNull(storedKey, "storedKey") - ); - } - } - - /** - * Processor that allows to generate the client-final-message, - * as well as process the server-final-message and verify server's signature. - * Generate the processor by calling either {@link ServerFirstProcessor#clientFinalProcessor(String)} - * or {@link ServerFirstProcessor#clientFinalProcessor(byte[], byte[])}. - */ - public class ClientFinalProcessor { - private final String nonce; - private final byte[] clientKey; - private final byte[] storedKey; - private final byte[] serverKey; - private String authMessage; - - private ClientFinalProcessor(String nonce, byte[] clientKey, byte[] storedKey, byte[] serverKey) { - assert null != clientKey : "clientKey"; - assert null != storedKey : "storedKey"; - assert null != serverKey : "serverKey"; - - this.nonce = nonce; - this.clientKey = clientKey; - this.storedKey = storedKey; - this.serverKey = serverKey; - } - - private ClientFinalProcessor(String nonce, byte[] clientKey, byte[] serverKey) { - this(nonce, clientKey, ScramFunctions.storedKey(scramMechanism, clientKey), serverKey); - } - - private ClientFinalProcessor(String nonce, byte[] saltedPassword) { - this( - nonce, - ScramFunctions.clientKey(scramMechanism, saltedPassword), - ScramFunctions.serverKey(scramMechanism, saltedPassword) - ); - } - - private ClientFinalProcessor(String nonce, String password, String salt, int iteration) { - this( - nonce, - ScramFunctions.saltedPassword( - scramMechanism, stringPreparation, password, Base64.decode(salt), iteration - ) - ); - } - - private synchronized void generateAndCacheAuthMessage(byte[] cbindData) { - if(null != authMessage) { - return; - } - - authMessage = clientFirstMessage.writeToWithoutGs2Header(new StringBuffer()) - .append(",") - .append(serverFirstMessageString) - .append(",") - .append(ClientFinalMessage.writeToWithoutProof(clientFirstMessage.getGs2Header(), cbindData, nonce)) - .toString(); - } - - /** - * Generates the SCRAM representation of the client-final-message, including the given channel-binding data. - * @param cbindData The bytes of the channel-binding data - * @return The message - * @throws IllegalArgumentException If the channel binding data is null - */ - private String clientFinalMessage(byte[] cbindData) throws IllegalArgumentException { - if(null == authMessage) { - generateAndCacheAuthMessage(cbindData); - } - - ClientFinalMessage clientFinalMessage = new ClientFinalMessage( - clientFirstMessage.getGs2Header(), - cbindData, - nonce, - ScramFunctions.clientProof( - clientKey, - ScramFunctions.clientSignature(scramMechanism, storedKey, authMessage) - ) - ); - - return clientFinalMessage.toString(); - } - - /** - * Generates the SCRAM representation of the client-final-message. - * @return The message - */ - public String clientFinalMessage() { - return clientFinalMessage(null); - } - - /** - * Receive and process the server-final-message. - * Server SCRAM signatures is verified. - * @param serverFinalMessage The received server-final-message - * @throws ScramParseException If the message is not a valid server-final-message - * @throws ScramServerErrorException If the server-final-message contained an error - * @throws IllegalArgumentException If the message is null or empty - */ - public void receiveServerFinalMessage(String serverFinalMessage) - throws ScramParseException, ScramServerErrorException, ScramInvalidServerSignatureException, - IllegalArgumentException { - checkNotEmpty(serverFinalMessage, "serverFinalMessage"); - - ServerFinalMessage message = ServerFinalMessage.parseFrom(serverFinalMessage); - if(message.isError()) { - throw new ScramServerErrorException(message.getError()); - } - if(! ScramFunctions.verifyServerSignature( - scramMechanism, serverKey, authMessage, message.getVerifier() - )) { - throw new ScramInvalidServerSignatureException("Invalid server SCRAM signature"); - } - } - } - - /** - * Constructs a handler for the server-first-message, from its String representation. - * @param serverFirstMessage The message - * @return The handler - * @throws ScramParseException If the message is not a valid server-first-message - * @throws IllegalArgumentException If the message is null or empty - */ - public ServerFirstProcessor receiveServerFirstMessage(String serverFirstMessage) - throws ScramParseException, IllegalArgumentException { - return new ServerFirstProcessor(checkNotEmpty(serverFirstMessage, "serverFirstMessage")); - } -} diff --git a/client/src/test/java/com/ongres/scram/client/ScramClientTest.java b/client/src/test/java/com/ongres/scram/client/ScramClientTest.java deleted file mode 100644 index 2c92e17..0000000 --- a/client/src/test/java/com/ongres/scram/client/ScramClientTest.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.client; - - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.util.Arrays; - -import org.junit.Test; - -import com.ongres.scram.common.ScramMechanisms; -import com.ongres.scram.common.stringprep.StringPreparations; -import com.ongres.scram.common.util.CryptoUtil; - - -public class ScramClientTest { - @Test - public void getValid() { - ScramClient client1 = ScramClient - .channelBinding(ScramClient.ChannelBinding.NO) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1") - .setup(); - ScramClient client2 = ScramClient - .channelBinding(ScramClient.ChannelBinding.YES) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS") - .nonceLength(64) - .setup(); - ScramClient client3 = ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") - .nonceSupplier - (new NonceSupplier() { - @Override - public String get() { - return CryptoUtil.nonce(36); - } - }) - .setup(); - ScramClient client4 = ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertisedCsv("SCRAM-SHA-1,SCRAM-SHA-256-PLUS") - .secureRandomAlgorithmProvider("SHA1PRNG", "SUN") - .nonceLength(64) - .setup(); - ScramClient client5 = ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertisedCsv("SCRAM-SHA-1,SCRAM-SHA-256-PLUS") - .secureRandomAlgorithmProvider("SHA1PRNG", null) - .nonceLength(64) - .setup(); - ScramClient client6 = ScramClient - .channelBinding(ScramClient.ChannelBinding.NO) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1) - .setup(); - ScramClient client7 = ScramClient - .channelBinding(ScramClient.ChannelBinding.YES) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectClientMechanism(ScramMechanisms.SCRAM_SHA_256_PLUS) - .setup(); - - for (ScramClient client : new ScramClient[] { - client1, client2, client3, client4, client5, client6, client7 - }) { - assertNotNull(client); - } - } - - @Test - public void getInvalid() { - int n = 0; - - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.NO) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1-PLUS") - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.YES) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1-PLUS,SCRAM-SAH-256-PLUS") - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("INVALID-SCRAM-MECHANISM") - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") - .nonceSupplier(null) - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") - .nonceLength(0) - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") - .secureRandomAlgorithmProvider("Invalid algorithm", null) - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") - .secureRandomAlgorithmProvider("SHA1PRNG", "Invalid provider") - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.YES) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1) - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.NO) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1_PLUS) - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - try { - assertNotNull(ScramClient - .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1) - .setup() - ); - } catch (IllegalArgumentException e) { n++; } - - assertEquals(10, n); - } - - @Test - public void supportedMechanismsTestAll() { - String[] expecteds = new String[] { "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" }; - Arrays.sort(expecteds); - String[] actuals = ScramClient.supportedMechanisms().toArray(new String[0]); - Arrays.sort(actuals); - assertArrayEquals( - expecteds, - actuals - ); - } -} diff --git a/client/src/test/java/com/ongres/scram/client/ScramSessionTest.java b/client/src/test/java/com/ongres/scram/client/ScramSessionTest.java deleted file mode 100644 index 40b670d..0000000 --- a/client/src/test/java/com/ongres/scram/client/ScramSessionTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.client; - - -import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.exception.ScramServerErrorException; -import com.ongres.scram.common.stringprep.StringPreparations; -import org.junit.Test; - -import static com.ongres.scram.common.RfcExampleSha1.*; -import static org.junit.Assert.*; - -public class ScramSessionTest { - private final ScramClient scramClient = ScramClient - .channelBinding(ScramClient.ChannelBinding.NO) - .stringPreparation(StringPreparations.NO_PREPARATION) - .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1") - .nonceSupplier - (new NonceSupplier() { - @Override - public String get() { - return CLIENT_NONCE; - } - }) - .setup(); - - @Test - public void completeTest() - throws ScramParseException, ScramInvalidServerSignatureException, ScramServerErrorException { - ScramSession scramSession = scramClient.scramSession(USER); - assertEquals(CLIENT_FIRST_MESSAGE, scramSession.clientFirstMessage()); - - ScramSession.ServerFirstProcessor serverFirstProcessor = scramSession.receiveServerFirstMessage( - SERVER_FIRST_MESSAGE - ); - assertEquals(SERVER_SALT, serverFirstProcessor.getSalt()); - assertEquals(SERVER_ITERATIONS, serverFirstProcessor.getIteration()); - - ScramSession.ClientFinalProcessor clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(PASSWORD); - assertEquals(CLIENT_FINAL_MESSAGE, clientFinalProcessor.clientFinalMessage()); - - clientFinalProcessor.receiveServerFinalMessage(SERVER_FINAL_MESSAGE); - } -} diff --git a/common/pom.xml b/common/pom.xml deleted file mode 100644 index 4eeb741..0000000 --- a/common/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - 4.0.0 - - - parent - com.ongres.scram - 2.1 - - common - - SCRAM - common - - - - junit - junit - test - - - com.ongres.stringprep - saslprep - 1.1 - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - - test-jar - - - - - - - - diff --git a/common/spotbugs-exclude.xml b/common/spotbugs-exclude.xml deleted file mode 100644 index 92400d0..0000000 --- a/common/spotbugs-exclude.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java b/common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java deleted file mode 100644 index 48b77a3..0000000 --- a/common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.util.AbstractCharAttributeValue; - -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * Parse and write SCRAM Attribute-Value pairs. - */ -public class ScramAttributeValue extends AbstractCharAttributeValue { - public ScramAttributeValue(ScramAttributes attribute, String value) { - super(attribute, checkNotNull(value, "value")); - } - - public static StringBuffer writeTo(StringBuffer sb, ScramAttributes attribute, String value) { - return new ScramAttributeValue(attribute, value).writeTo(sb); - } - - /** - * Parses a potential ScramAttributeValue String. - * @param value The string that contains the Attribute-Value pair. - * @return The parsed class - * @throws ScramParseException If the argument is empty or an invalid Attribute-Value - */ - public static ScramAttributeValue parse(String value) - throws ScramParseException { - if(null == value || value.length() < 3 || value.charAt(1) != '=') { - throw new ScramParseException("Invalid ScramAttributeValue '" + value + "'"); - } - - return new ScramAttributeValue(ScramAttributes.byChar(value.charAt(0)), value.substring(2)); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/ScramAttributes.java b/common/src/main/java/com/ongres/scram/common/ScramAttributes.java deleted file mode 100644 index 84dea0d..0000000 --- a/common/src/main/java/com/ongres/scram/common/ScramAttributes.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.util.CharAttribute; - -import java.util.HashMap; -import java.util.Map; - -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * SCRAM Attributes as defined in Section 5.1 of the RFC. - * - * Not all the available attributes may be available in this implementation. - */ -public enum ScramAttributes implements CharAttribute { - /** - * This attribute specifies the name of the user whose password is used for authentication - * (a.k.a. "authentication identity" [RFC4422]). - * If the "a" attribute is not specified (which would normally be the case), this username is also the identity - * that will be associated with the connection subsequent to authentication and authorization. - * - * The client SHOULD prepare the username using the "SASLprep" profile - * [RFC4013] of the "stringprep" algorithm - * [RFC3454] treating it as a query string - * (i.e., unassigned Unicode code points are allowed). - * - * The characters ',' or '=' in usernames are sent as '=2C' and '=3D' respectively. - */ - USERNAME('n'), - - /** - * This is an optional attribute, and is part of the GS2 [RFC5801] - * bridge between the GSS-API and SASL. This attribute specifies an authorization identity. - * A client may include it in its first message to the server if it wants to authenticate as one user, - * but subsequently act as a different user. This is typically used by an administrator to perform some management - * task on behalf of another user, or by a proxy in some situations. - * - * If this attribute is omitted (as it normally would be), the authorization identity is assumed to be derived - * from the username specified with the (required) "n" attribute. - * - * The server always authenticates the user specified by the "n" attribute. - * If the "a" attribute specifies a different user, the server associates that identity with the connection after - * successful authentication and authorization checks. - * - * The syntax of this field is the same as that of the "n" field with respect to quoting of '=' and ','. - */ - AUTHZID('a'), - - /** - * This attribute specifies a sequence of random printable ASCII characters excluding ',' - * (which forms the nonce used as input to the hash function). No quoting is applied to this string. - */ - NONCE('r'), - - /** - * This REQUIRED attribute specifies the base64-encoded GS2 header and channel binding data. - * The attribute data consist of: - *
    - *
  • - * the GS2 header from the client's first message - * (recall that the GS2 header contains a channel binding flag and an optional authzid). - * This header is going to include channel binding type prefix - * (see [RFC5056]), - * if and only if the client is using channel binding; - *
  • - *
  • - * followed by the external channel's channel binding data, - * if and only if the client is using channel binding. - *
  • - *
- */ - CHANNEL_BINDING('c'), - - /** - * This attribute specifies the base64-encoded salt used by the server for this user. - */ - SALT('s'), - - /** - * This attribute specifies an iteration count for the selected hash function and user. - */ - ITERATION('i'), - - /** - * This attribute specifies a base64-encoded ClientProof. - */ - CLIENT_PROOF('p'), - - /** - * This attribute specifies a base64-encoded ServerSignature. - */ - SERVER_SIGNATURE('v'), - - /** - * This attribute specifies an error that occurred during authentication exchange. - * Can help diagnose the reason for the authentication exchange failure. - */ - ERROR('e') - ; - - private final char attributeChar; - - ScramAttributes(char attributeChar) { - this.attributeChar = checkNotNull(attributeChar, "attributeChar"); - } - - @Override - public char getChar() { - return attributeChar; - } - - private static final Map REVERSE_MAPPING = new HashMap(); - static { - for(ScramAttributes scramAttribute : values()) { - REVERSE_MAPPING.put(scramAttribute.getChar(), scramAttribute); - } - } - - /** - * Find a SCRAMAttribute by its character. - * @param c The character. - * @return The SCRAMAttribute that has that character. - * @throws ScramParseException If no SCRAMAttribute has this character. - */ - public static ScramAttributes byChar(char c) throws ScramParseException { - if(! REVERSE_MAPPING.containsKey(c)) { - throw new ScramParseException("Attribute with char '" + c + "' does not exist"); - } - - return REVERSE_MAPPING.get(c); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/ScramFunctions.java b/common/src/main/java/com/ongres/scram/common/ScramFunctions.java deleted file mode 100644 index 5f02706..0000000 --- a/common/src/main/java/com/ongres/scram/common/ScramFunctions.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import com.ongres.scram.common.stringprep.StringPreparation; -import com.ongres.scram.common.util.CryptoUtil; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -/** - * Utility functions (e.g. crypto) for SCRAM. - */ -public class ScramFunctions { - private static final byte[] CLIENT_KEY_HMAC_KEY = "Client Key".getBytes(StandardCharsets.UTF_8); - private static final byte[] SERVER_KEY_HMAC_KEY = "Server Key".getBytes(StandardCharsets.UTF_8); - - /** - * Compute the salted password, based on the given SCRAM mechanism, the String preparation algorithm, - * the provided salt and the number of iterations. - * - * {@code - * SaltedPassword := Hi(Normalize(password), salt, i) - * } - * - * @param scramMechanism The SCRAM mechanism - * @param stringPreparation The String preparation - * @param password The non-salted password - * @param salt The bytes representing the salt - * @param iteration The number of iterations - * @return The salted password - */ - public static byte[] saltedPassword( - ScramMechanism scramMechanism, StringPreparation stringPreparation, String password, byte[] salt, - int iteration - ) { - return scramMechanism.saltedPassword(stringPreparation, password, salt, iteration); - } - - /** - * Computes the HMAC of the message and key, using the given SCRAM mechanism. - * @param scramMechanism The SCRAM mechanism - * @param message The message to compute the HMAC - * @param key The key used to initialize the MAC - * @return The computed HMAC - */ - public static byte[] hmac(ScramMechanism scramMechanism, byte[] message, byte[] key) { - return scramMechanism.hmac(key, message); - } - - /** - * Generates a client key, from the salted password. - * - * {@code - * ClientKey := HMAC(SaltedPassword, "Client Key") - * } - * - * @param scramMechanism The SCRAM mechanism - * @param saltedPassword The salted password - * @return The client key - */ - public static byte[] clientKey(ScramMechanism scramMechanism, byte[] saltedPassword) { - return hmac(scramMechanism, CLIENT_KEY_HMAC_KEY, saltedPassword); - } - - /** - * Generates a client key from the password and salt. - * - * {@code - * SaltedPassword := Hi(Normalize(password), salt, i) - * ClientKey := HMAC(SaltedPassword, "Client Key") - * } - * - * @param scramMechanism The SCRAM mechanism - * @param stringPreparation The String preparation - * @param password The non-salted password - * @param salt The bytes representing the salt - * @param iteration The number of iterations - * @return The client key - */ - public static byte[] clientKey( - ScramMechanism scramMechanism, StringPreparation stringPreparation, String password, byte[] salt, - int iteration - ) { - return clientKey(scramMechanism, saltedPassword(scramMechanism, stringPreparation, password, salt, iteration)); - } - - /** - * Generates a server key, from the salted password. - * - * {@code - * ServerKey := HMAC(SaltedPassword, "Server Key") - * } - * - * @param scramMechanism The SCRAM mechanism - * @param saltedPassword The salted password - * @return The server key - */ - public static byte[] serverKey(ScramMechanism scramMechanism, byte[] saltedPassword) { - return hmac(scramMechanism, SERVER_KEY_HMAC_KEY, saltedPassword); - } - - /** - * Generates a server key from the password and salt. - * - * {@code - * SaltedPassword := Hi(Normalize(password), salt, i) - * ServerKey := HMAC(SaltedPassword, "Server Key") - * } - * - * @param scramMechanism The SCRAM mechanism - * @param stringPreparation The String preparation - * @param password The non-salted password - * @param salt The bytes representing the salt - * @param iteration The number of iterations - * @return The server key - */ - public static byte[] serverKey( - ScramMechanism scramMechanism, StringPreparation stringPreparation, String password, byte[] salt, - int iteration - ) { - return serverKey(scramMechanism, saltedPassword(scramMechanism, stringPreparation, password, salt, iteration)); - } - - /** - * Computes the hash function of a given value, based on the SCRAM mechanism hash function. - * @param scramMechanism The SCRAM mechanism - * @param value The value to hash - * @return The hashed value - */ - public static byte[] hash(ScramMechanism scramMechanism, byte[] value) { - return scramMechanism.digest(value); - } - - /** - * Generates a stored key, from the salted password. - * - * {@code - * StoredKey := H(ClientKey) - * } - * - * @param scramMechanism The SCRAM mechanism - * @param clientKey The client key - * @return The stored key - */ - public static byte[] storedKey(ScramMechanism scramMechanism, byte[] clientKey) { - return hash(scramMechanism, clientKey); - } - - /** - * Computes the SCRAM client signature. - * - * {@code - * ClientSignature := HMAC(StoredKey, AuthMessage) - * } - * - * @param scramMechanism The SCRAM mechanism - * @param storedKey The stored key - * @param authMessage The auth message - * @return The client signature - */ - public static byte[] clientSignature(ScramMechanism scramMechanism, byte[] storedKey, String authMessage) { - return hmac(scramMechanism, authMessage.getBytes(StandardCharsets.UTF_8), storedKey); - } - - /** - * Computes the SCRAM client proof to be sent to the server on the client-final-message. - * - * {@code - * ClientProof := ClientKey XOR ClientSignature - * } - * - * @param clientKey The client key - * @param clientSignature The client signature - * @return The client proof - */ - public static byte[] clientProof(byte[] clientKey, byte[] clientSignature) { - return CryptoUtil.xor(clientKey, clientSignature); - } - - /** - * Compute the SCRAM server signature. - * - * {@code - * ServerSignature := HMAC(ServerKey, AuthMessage) - * } - * - * @param scramMechanism The SCRAM mechanism - * @param serverKey The server key - * @param authMessage The auth message - * @return The server signature - */ - public static byte[] serverSignature(ScramMechanism scramMechanism, byte[] serverKey, String authMessage) { - return clientSignature(scramMechanism, serverKey, authMessage); - } - - /** - * Verifies that a provided client proof is correct. - * @param scramMechanism The SCRAM mechanism - * @param clientProof The provided client proof - * @param storedKey The stored key - * @param authMessage The auth message - * @return True if the client proof is correct - */ - public static boolean verifyClientProof( - ScramMechanism scramMechanism, byte[] clientProof, byte[] storedKey, String authMessage - ) { - byte[] clientSignature = clientSignature(scramMechanism, storedKey, authMessage); - byte[] clientKey = CryptoUtil.xor(clientSignature, clientProof); - byte[] computedStoredKey = hash(scramMechanism, clientKey); - - return Arrays.equals(storedKey, computedStoredKey); - } - - /** - * Verifies that a provided server proof is correct. - * @param scramMechanism The SCRAM mechanism - * @param serverKey The server key - * @param authMessage The auth message - * @param serverSignature The provided server signature - * @return True if the server signature is correct - */ - public static boolean verifyServerSignature( - ScramMechanism scramMechanism, byte[] serverKey, String authMessage, byte[] serverSignature - ) { - return Arrays.equals(serverSignature(scramMechanism, serverKey, authMessage), serverSignature); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/ScramMechanism.java b/common/src/main/java/com/ongres/scram/common/ScramMechanism.java deleted file mode 100644 index 8f6fc29..0000000 --- a/common/src/main/java/com/ongres/scram/common/ScramMechanism.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import com.ongres.scram.common.stringprep.StringPreparation; - - -/** - * Definition of the functionality to be provided by every ScramMechanism. - * - * Every ScramMechanism implemented must provide implementations of their respective digest and hmac - * function that will not throw a RuntimeException on any JVM, to guarantee true portability of this library. - */ -public interface ScramMechanism { - /** - * The name of the mechanism, which must be a value registered under IANA: - * - * SASL SCRAM Family Mechanisms - * @return The mechanism name - */ - String getName(); - - /** - * Calculate a message digest, according to the algorithm of the SCRAM mechanism. - * @param message the message - * @return The calculated message digest - * @throws RuntimeException If the algorithm is not provided by current JVM or any included implementations - */ - byte[] digest(byte[] message) throws RuntimeException; - - /** - * Calculate the hmac of a key and a message, according to the algorithm of the SCRAM mechanism. - * @param key the key - * @param message the message - * @return The calculated message hmac instance - * @throws RuntimeException If the algorithm is not provided by current JVM or any included implementations - */ - byte[] hmac(byte[] key, byte[] message) throws RuntimeException; - - /** - * Returns the length of the key length of the algorithm. - * @return The length (in bits) - */ - int algorithmKeyLength(); - - /** - * Whether this mechanism supports channel binding - * @return True if it supports channel binding, false otherwise - */ - boolean supportsChannelBinding(); - - /** - * Compute the salted password - * @return The salted password - */ - byte[] saltedPassword(StringPreparation stringPreparation, String password, - byte[] salt, int iteration); -} diff --git a/common/src/main/java/com/ongres/scram/common/ScramMechanisms.java b/common/src/main/java/com/ongres/scram/common/ScramMechanisms.java deleted file mode 100644 index c865f96..0000000 --- a/common/src/main/java/com/ongres/scram/common/ScramMechanisms.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import static com.ongres.scram.common.util.Preconditions.checkNotNull; -import static com.ongres.scram.common.util.Preconditions.gt0; - -import com.ongres.scram.common.bouncycastle.pbkdf2.DigestFactory; -import com.ongres.scram.common.bouncycastle.pbkdf2.KeyParameter; -import com.ongres.scram.common.bouncycastle.pbkdf2.PBEParametersGenerator; -import com.ongres.scram.common.bouncycastle.pbkdf2.PKCS5S2ParametersGenerator; -import com.ongres.scram.common.stringprep.StringPreparation; -import com.ongres.scram.common.util.CryptoUtil; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.Mac; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.SecretKeySpec; - -/** - * SCRAM Mechanisms supported by this library. - * At least, SCRAM-SHA-1 and SCRAM-SHA-256 are provided, since both the hash and the HMAC implementations - * are provided by the Java JDK version 6 or greater. - * - * {@link java.security.MessageDigest}: "Every implementation of the Java platform is required to support the - * following standard MessageDigest algorithms: MD5, SHA-1, SHA-256". - * - * {@link javax.crypto.Mac}: "Every implementation of the Java platform is required to support the following - * standard Mac algorithms: HmacMD5, HmacSHA1, HmacSHA256". - * - * @see - * SASL SCRAM Family Mechanisms - */ -public enum ScramMechanisms implements ScramMechanism { - SCRAM_SHA_1 ( "SHA-1", "SHA-1", 160, "HmacSHA1", false, 1 ), - SCRAM_SHA_1_PLUS ( "SHA-1", "SHA-1", 160, "HmacSHA1", true, 1 ), - SCRAM_SHA_256 ( "SHA-256", "SHA-256", 256, "HmacSHA256", false, 10 ), - SCRAM_SHA_256_PLUS ( "SHA-256", "SHA-256", 256, "HmacSHA256", true, 10 ) - ; - - private static final String SCRAM_MECHANISM_NAME_PREFIX = "SCRAM-"; - private static final String CHANNEL_BINDING_SUFFIX = "-PLUS"; - private static final String PBKDF2_PREFIX_ALGORITHM_NAME = "PBKDF2With"; - private static final Map BY_NAME_MAPPING = valuesAsMap(); - - private final String mechanismName; - private final String hashAlgorithmName; - private final int keyLength; - private final String hmacAlgorithmName; - private final boolean channelBinding; - private final int priority; - - ScramMechanisms( - String name, String hashAlgorithmName, int keyLength, String hmacAlgorithmName, boolean channelBinding, - int priority - ) { - this.mechanismName = SCRAM_MECHANISM_NAME_PREFIX - + checkNotNull(name, "name") - + (channelBinding ? CHANNEL_BINDING_SUFFIX : "") - ; - this.hashAlgorithmName = checkNotNull(hashAlgorithmName, "hashAlgorithmName"); - this.keyLength = gt0(keyLength, "keyLength"); - this.hmacAlgorithmName = checkNotNull(hmacAlgorithmName, "hmacAlgorithmName"); - this.channelBinding = channelBinding; - this.priority = gt0(priority, "priority"); - } - - /** - * Method that returns the name of the hash algorithm. - * It is protected since should be of no interest for direct users. - * The instance is supposed to provide abstractions over the algorithm names, - * and are not meant to be directly exposed. - * @return The name of the hash algorithm - */ - protected String getHashAlgorithmName() { - return hashAlgorithmName; - } - - /** - * Method that returns the name of the HMAC algorithm. - * It is protected since should be of no interest for direct users. - * The instance is supposed to provide abstractions over the algorithm names, - * and are not meant to be directly exposed. - * @return The name of the HMAC algorithm - */ - protected String getHmacAlgorithmName() { - return hmacAlgorithmName; - } - - @Override - public String getName() { - return mechanismName; - } - - @Override - public boolean supportsChannelBinding() { - return channelBinding; - } - - @Override - public int algorithmKeyLength() { - return keyLength; - } - - @Override - public byte[] digest(byte[] message) { - try { - return MessageDigest.getInstance(hashAlgorithmName).digest(message); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Algorithm " + hashAlgorithmName + " not present in current JVM"); - } - } - - @Override - public byte[] hmac(byte[] key, byte[] message) { - try { - return CryptoUtil.hmac(new SecretKeySpec(key, hmacAlgorithmName), Mac.getInstance(hmacAlgorithmName), message); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("MAC Algorithm " + hmacAlgorithmName + " not present in current JVM"); - } - } - - @Override - public byte[] saltedPassword(StringPreparation stringPreparation, String password, byte[] salt, - int iterations) { - char[] normalizedString = stringPreparation.normalize(password).toCharArray(); - try { - return CryptoUtil.hi( - SecretKeyFactory.getInstance(PBKDF2_PREFIX_ALGORITHM_NAME + hmacAlgorithmName), - algorithmKeyLength(), - normalizedString, - salt, - iterations); - } catch (NoSuchAlgorithmException e) { - if(!ScramMechanisms.SCRAM_SHA_256.getHmacAlgorithmName().equals(getHmacAlgorithmName())) { - throw new RuntimeException("Unsupported PBKDF2 for " + mechanismName); - } - - PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(DigestFactory.createSHA256()); - generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(normalizedString), salt, iterations); - KeyParameter params = (KeyParameter)generator.generateDerivedParameters(algorithmKeyLength()); - return params.getKey(); - } - } - - /** - * Gets a SCRAM mechanism, given its standard IANA name. - * @param name The standard IANA full name of the mechanism. - * @return An Optional instance that contains the ScramMechanism if it was found, or empty otherwise. - */ - public static ScramMechanisms byName(String name) { - checkNotNull(name, "name"); - - return BY_NAME_MAPPING.get(name); - } - - /** - * This class classifies SCRAM mechanisms by two properties: whether they support channel binding; - * and a priority, which is higher for safer algorithms (like SHA-256 vs SHA-1). - * - * Given a list of SCRAM mechanisms supported by the peer, pick one that matches the channel binding requirements - * and has the highest priority. - * - * @param channelBinding The type of matching mechanism searched for - * @param peerMechanisms The mechanisms supported by the other peer - * @return The selected mechanism, or null if no mechanism matched - */ - public static ScramMechanism selectMatchingMechanism(boolean channelBinding, String... peerMechanisms) { - ScramMechanisms selectedScramMechanisms = null; - for (String peerMechanism : peerMechanisms) { - ScramMechanisms matchedScramMechanisms = BY_NAME_MAPPING.get(peerMechanism); - if (matchedScramMechanisms != null) { - for (ScramMechanisms candidateScramMechanisms : ScramMechanisms.values()) { - if (channelBinding == candidateScramMechanisms.channelBinding - && candidateScramMechanisms.mechanismName.equals(matchedScramMechanisms.mechanismName) - && (selectedScramMechanisms == null - || selectedScramMechanisms.priority < candidateScramMechanisms.priority)) { - selectedScramMechanisms = candidateScramMechanisms; - } - } - } - } - return selectedScramMechanisms; - } - - private static Map valuesAsMap() { - Map mapScramMechanisms = new HashMap<>(values().length); - for (ScramMechanisms scramMechanisms : values()) { - mapScramMechanisms.put(scramMechanisms.getName(), scramMechanisms); - } - return mapScramMechanisms; - } - -} diff --git a/common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java b/common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java deleted file mode 100644 index 2094e79..0000000 --- a/common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - -import java.nio.charset.StandardCharsets; - -import com.ongres.scram.common.bouncycastle.base64.Base64; - -/** - * Class with static methods that provide support for converting to/from salNames. - * @see [RFC5802] Section 7: Formal Syntax - */ -public class ScramStringFormatting { - - /** - * Given a value-safe-char (normalized UTF-8 String), - * return one where characters ',' and '=' are represented by '=2C' or '=3D', respectively. - * @param value The value to convert so saslName - * @return The saslName, with caracter escaped (if any) - */ - public static String toSaslName(String value) { - if(null == value || value.isEmpty()) { - return value; - } - - int nComma = 0, nEqual = 0; - char[] originalChars = value.toCharArray(); - - // Fast path - for(char c : originalChars) { - if(',' == c) { nComma++; } - else if('=' == c) { nEqual++; } - } - if(nComma == 0 && nEqual == 0) { - return value; - } - - // Replace chars - char[] saslChars = new char[originalChars.length + nComma * 2 + nEqual * 2]; - int i = 0; - for(char c : originalChars) { - if(',' == c) { - saslChars[i++] = '='; - saslChars[i++] = '2'; - saslChars[i++] = 'C'; - } else if('=' == c) { - saslChars[i++] = '='; - saslChars[i++] = '3'; - saslChars[i++] = 'D'; - } else { - saslChars[i++] = c; - } - } - - return new String(saslChars); - } - - /** - * Given a saslName, return a non-escaped String. - * @param value The saslName - * @return The saslName, unescaped - * @throws IllegalArgumentException If a ',' character is present, or a '=' not followed by either '2C' or '3D' - */ - public static String fromSaslName(String value) throws IllegalArgumentException { - if(null == value || value.isEmpty()) { - return value; - } - - int nEqual = 0; - char[] orig = value.toCharArray(); - - // Fast path - for(int i = 0; i < orig.length; i++) { - if(orig[i] == ',') { - throw new IllegalArgumentException("Invalid ',' character present in saslName"); - } - if(orig[i] == '=') { - nEqual++; - if(i + 2 > orig.length - 1) { - throw new IllegalArgumentException("Invalid '=' character present in saslName"); - } - if(! (orig[i+1] == '2' && orig[i+2] == 'C' || orig[i+1] == '3' && orig[i+2] == 'D')) { - throw new IllegalArgumentException( - "Invalid char '=" + orig[i+1] + orig[i+2] + "' found in saslName" - ); - } - } - } - if(nEqual == 0) { - return value; - } - - // Replace characters - char[] replaced = new char[orig.length - nEqual * 2]; - - for(int r = 0, o = 0; r < replaced.length; r++) { - if('=' == orig[o]) { - if(orig[o+1] == '2' && orig[o+2] == 'C') { - replaced[r] = ','; - } else if(orig[o+1] == '3' && orig[o+2] == 'D') { - replaced[r] = '='; - } - o += 3; - } else { - replaced[r] = orig[o]; - o += 1; - } - } - - return new String(replaced); - } - - public static String base64Encode(byte[] value) throws IllegalArgumentException { - return Base64.toBase64String(checkNotNull(value, "value")); - } - - public static String base64Encode(String value) throws IllegalArgumentException { - return base64Encode(checkNotEmpty(value, "value").getBytes(StandardCharsets.UTF_8)); - } - - public static byte[] base64Decode(String value) throws IllegalArgumentException { - return Base64.decode(checkNotEmpty(value, "value")); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64.java deleted file mode 100644 index 7b0eadd..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.base64; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import com.ongres.scram.common.bouncycastle.pbkdf2.Strings; - -/** - * Utility class for converting Base64 data to bytes and back again. - */ -public class Base64 -{ - private static final Encoder encoder = new Base64Encoder(); - - public static String toBase64String( - byte[] data) - { - return toBase64String(data, 0, data.length); - } - - public static String toBase64String( - byte[] data, - int off, - int length) - { - byte[] encoded = encode(data, off, length); - return Strings.fromByteArray(encoded); - } - - /** - * encode the input data producing a base 64 encoded byte array. - * - * @return a byte array containing the base 64 encoded data. - */ - public static byte[] encode( - byte[] data) - { - return encode(data, 0, data.length); - } - - /** - * encode the input data producing a base 64 encoded byte array. - * - * @return a byte array containing the base 64 encoded data. - */ - public static byte[] encode( - byte[] data, - int off, - int length) - { - int len = (length + 2) / 3 * 4; - ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); - - try - { - encoder.encode(data, off, length, bOut); - } - catch (Exception e) - { - throw new EncoderException("exception encoding base64 string: " + e.getMessage(), e); - } - - return bOut.toByteArray(); - } - - /** - * Encode the byte data to base 64 writing it to the given output stream. - * - * @return the number of bytes produced. - */ - public static int encode( - byte[] data, - OutputStream out) - throws IOException - { - return encoder.encode(data, 0, data.length, out); - } - - /** - * Encode the byte data to base 64 writing it to the given output stream. - * - * @return the number of bytes produced. - */ - public static int encode( - byte[] data, - int off, - int length, - OutputStream out) - throws IOException - { - return encoder.encode(data, off, length, out); - } - - /** - * decode the base 64 encoded input data. It is assumed the input data is valid. - * - * @return a byte array representing the decoded data. - */ - public static byte[] decode( - byte[] data) - { - int len = data.length / 4 * 3; - ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); - - try - { - encoder.decode(data, 0, data.length, bOut); - } - catch (Exception e) - { - throw new DecoderException("unable to decode base64 data: " + e.getMessage(), e); - } - - return bOut.toByteArray(); - } - - /** - * decode the base 64 encoded String data - whitespace will be ignored. - * - * @return a byte array representing the decoded data. - */ - public static byte[] decode( - String data) - { - int len = data.length() / 4 * 3; - ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); - - try - { - encoder.decode(data, bOut); - } - catch (Exception e) - { - throw new DecoderException("unable to decode base64 string: " + e.getMessage(), e); - } - - return bOut.toByteArray(); - } - - /** - * decode the base 64 encoded String data writing it to the given output stream, - * whitespace characters will be ignored. - * - * @return the number of bytes produced. - */ - public static int decode( - String data, - OutputStream out) - throws IOException - { - return encoder.decode(data, out); - } - - /** - * Decode to an output stream; - * - * @param base64Data The source data. - * @param start Start position. - * @param length the length. - * @param out The output stream to write to. - */ - public static int decode(byte[] base64Data, int start, int length, OutputStream out) - { - try - { - return encoder.decode(base64Data, start, length, out); - } - catch (Exception e) - { - throw new DecoderException("unable to decode base64 data: " + e.getMessage(), e); - } - - } -} \ No newline at end of file diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64Encoder.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64Encoder.java deleted file mode 100644 index f3cbf02..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Base64Encoder.java +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.base64; -import java.io.IOException; -import java.io.OutputStream; - -/** - * A streaming Base64 encoder. - */ -public class Base64Encoder - implements Encoder -{ - protected final byte[] encodingTable = - { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', - (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', - (byte)'7', (byte)'8', (byte)'9', - (byte)'+', (byte)'/' - }; - - protected byte padding = (byte)'='; - - /* - * set up the decoding table. - */ - protected final byte[] decodingTable = new byte[128]; - - protected void initialiseDecodingTable() - { - for (int i = 0; i < decodingTable.length; i++) - { - decodingTable[i] = (byte)0xff; - } - - for (int i = 0; i < encodingTable.length; i++) - { - decodingTable[encodingTable[i]] = (byte)i; - } - } - - public Base64Encoder() - { - initialiseDecodingTable(); - } - - /** - * encode the input data producing a base 64 output stream. - * - * @return the number of bytes produced. - */ - public int encode( - byte[] data, - int off, - int length, - OutputStream out) - throws IOException - { - int modulus = length % 3; - int dataLength = (length - modulus); - int a1, a2, a3; - - for (int i = off; i < off + dataLength; i += 3) - { - a1 = data[i] & 0xff; - a2 = data[i + 1] & 0xff; - a3 = data[i + 2] & 0xff; - - out.write(encodingTable[(a1 >>> 2) & 0x3f]); - out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); - out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); - out.write(encodingTable[a3 & 0x3f]); - } - - /* - * process the tail end. - */ - int b1, b2, b3; - int d1, d2; - - switch (modulus) - { - case 0: /* nothing left to do */ - break; - case 1: - d1 = data[off + dataLength] & 0xff; - b1 = (d1 >>> 2) & 0x3f; - b2 = (d1 << 4) & 0x3f; - - out.write(encodingTable[b1]); - out.write(encodingTable[b2]); - out.write(padding); - out.write(padding); - break; - case 2: - d1 = data[off + dataLength] & 0xff; - d2 = data[off + dataLength + 1] & 0xff; - - b1 = (d1 >>> 2) & 0x3f; - b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; - b3 = (d2 << 2) & 0x3f; - - out.write(encodingTable[b1]); - out.write(encodingTable[b2]); - out.write(encodingTable[b3]); - out.write(padding); - break; - } - - return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); - } - - private boolean ignore( - char c) - { - return (c == '\n' || c =='\r' || c == '\t' || c == ' '); - } - - /** - * decode the base 64 encoded byte data writing it to the given output stream, - * whitespace characters will be ignored. - * - * @return the number of bytes produced. - */ - public int decode( - byte[] data, - int off, - int length, - OutputStream out) - throws IOException - { - byte b1, b2, b3, b4; - int outLen = 0; - - int end = off + length; - - while (end > off) - { - if (!ignore((char)data[end - 1])) - { - break; - } - - end--; - } - - // empty data! - if (end == 0) - { - return 0; - } - - int i = 0; - int finish = end; - - while (finish > off && i != 4) - { - if (!ignore((char)data[finish - 1])) - { - i++; - } - - finish--; - } - - i = nextI(data, off, finish); - - while (i < finish) - { - b1 = decodingTable[data[i++]]; - - i = nextI(data, i, finish); - - b2 = decodingTable[data[i++]]; - - i = nextI(data, i, finish); - - b3 = decodingTable[data[i++]]; - - i = nextI(data, i, finish); - - b4 = decodingTable[data[i++]]; - - if ((b1 | b2 | b3 | b4) < 0) - { - throw new IOException("invalid characters encountered in base64 data"); - } - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - out.write((b3 << 6) | b4); - - outLen += 3; - - i = nextI(data, i, finish); - } - - int e0 = nextI(data, i, end); - int e1 = nextI(data, e0 + 1, end); - int e2 = nextI(data, e1 + 1, end); - int e3 = nextI(data, e2 + 1, end); - - outLen += decodeLastBlock(out, (char)data[e0], (char)data[e1], (char)data[e2], (char)data[e3]); - - return outLen; - } - - private int nextI(byte[] data, int i, int finish) - { - while ((i < finish) && ignore((char)data[i])) - { - i++; - } - return i; - } - - /** - * decode the base 64 encoded String data writing it to the given output stream, - * whitespace characters will be ignored. - * - * @return the number of bytes produced. - */ - public int decode( - String data, - OutputStream out) - throws IOException - { - byte b1, b2, b3, b4; - int length = 0; - - int end = data.length(); - - while (end > 0) - { - if (!ignore(data.charAt(end - 1))) - { - break; - } - - end--; - } - - // empty data! - if (end == 0) - { - return 0; - } - - int i = 0; - int finish = end; - - while (finish > 0 && i != 4) - { - if (!ignore(data.charAt(finish - 1))) - { - i++; - } - - finish--; - } - - i = nextI(data, 0, finish); - - while (i < finish) - { - b1 = decodingTable[data.charAt(i++)]; - - i = nextI(data, i, finish); - - b2 = decodingTable[data.charAt(i++)]; - - i = nextI(data, i, finish); - - b3 = decodingTable[data.charAt(i++)]; - - i = nextI(data, i, finish); - - b4 = decodingTable[data.charAt(i++)]; - - if ((b1 | b2 | b3 | b4) < 0) - { - throw new IOException("invalid characters encountered in base64 data"); - } - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - out.write((b3 << 6) | b4); - - length += 3; - - i = nextI(data, i, finish); - } - - int e0 = nextI(data, i, end); - int e1 = nextI(data, e0 + 1, end); - int e2 = nextI(data, e1 + 1, end); - int e3 = nextI(data, e2 + 1, end); - - length += decodeLastBlock(out, data.charAt(e0), data.charAt(e1), data.charAt(e2), data.charAt(e3)); - - return length; - } - - private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) - throws IOException - { - byte b1, b2, b3, b4; - - if (c3 == padding) - { - if (c4 != padding) - { - throw new IOException("invalid characters encountered at end of base64 data"); - } - - b1 = decodingTable[c1]; - b2 = decodingTable[c2]; - - if ((b1 | b2) < 0) - { - throw new IOException("invalid characters encountered at end of base64 data"); - } - - out.write((b1 << 2) | (b2 >> 4)); - - return 1; - } - else if (c4 == padding) - { - b1 = decodingTable[c1]; - b2 = decodingTable[c2]; - b3 = decodingTable[c3]; - - if ((b1 | b2 | b3) < 0) - { - throw new IOException("invalid characters encountered at end of base64 data"); - } - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - - return 2; - } - else - { - b1 = decodingTable[c1]; - b2 = decodingTable[c2]; - b3 = decodingTable[c3]; - b4 = decodingTable[c4]; - - if ((b1 | b2 | b3 | b4) < 0) - { - throw new IOException("invalid characters encountered at end of base64 data"); - } - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - out.write((b3 << 6) | b4); - - return 3; - } - } - - private int nextI(String data, int i, int finish) - { - while ((i < finish) && ignore(data.charAt(i))) - { - i++; - } - return i; - } -} \ No newline at end of file diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/DecoderException.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/DecoderException.java deleted file mode 100644 index 0356231..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/DecoderException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.base64; - -/** - * Exception thrown if an attempt is made to decode invalid data, or some other failure occurs. - */ -public class DecoderException - extends IllegalStateException -{ - private Throwable cause; - - DecoderException(String msg, Throwable cause) - { - super(msg); - - this.cause = cause; - } - - public Throwable getCause() - { - return cause; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Encoder.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Encoder.java deleted file mode 100644 index f6d1681..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/Encoder.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.base64; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * Encode and decode byte arrays (typically from binary to 7-bit ASCII - * encodings). - */ -public interface Encoder -{ - int encode(byte[] data, int off, int length, OutputStream out) throws IOException; - - int decode(byte[] data, int off, int length, OutputStream out) throws IOException; - - int decode(String data, OutputStream out) throws IOException; -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/EncoderException.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/EncoderException.java deleted file mode 100644 index 1a4703c..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/base64/EncoderException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.base64; - -/** - * Exception thrown if an attempt is made to encode invalid data, or some other failure occurs. - */ -public class EncoderException - extends IllegalStateException -{ - private Throwable cause; - - EncoderException(String msg, Throwable cause) - { - super(msg); - - this.cause = cause; - } - - public Throwable getCause() - { - return cause; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Arrays.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Arrays.java deleted file mode 100644 index c9330bf..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Arrays.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * General array utilities. - */ -public final class Arrays -{ - private Arrays() - { - // static class, hide constructor - } - - /** - * Make a copy of a range of bytes from the passed in data array. The range can - * extend beyond the end of the input array, in which case the return array will - * be padded with zeroes. - * - * @param data the array from which the data is to be copied. - * @param from the start index at which the copying should take place. - * @param to the final index of the range (exclusive). - * - * @return a new byte array containing the range given. - */ - public static byte[] copyOfRange(byte[] data, int from, int to) - { - int newLength = getLength(from, to); - - byte[] tmp = new byte[newLength]; - - if (data.length - from < newLength) - { - System.arraycopy(data, from, tmp, 0, data.length - from); - } - else - { - System.arraycopy(data, from, tmp, 0, newLength); - } - - return tmp; - } - - private static int getLength(int from, int to) - { - int newLength = to - from; - if (newLength < 0) - { - StringBuffer sb = new StringBuffer(from); - sb.append(" > ").append(to); - throw new IllegalArgumentException(sb.toString()); - } - return newLength; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/CipherParameters.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/CipherParameters.java deleted file mode 100644 index 3bf19e7..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/CipherParameters.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * all parameter classes implement this. - */ -public interface CipherParameters -{ -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DataLengthException.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DataLengthException.java deleted file mode 100644 index beb2d3b..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DataLengthException.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * this exception is thrown if a buffer that is meant to have output - * copied into it turns out to be too short, or if we've been given - * insufficient input. In general this exception will get thrown rather - * than an ArrayOutOfBounds exception. - */ -public class DataLengthException - extends RuntimeCryptoException -{ - /** - * base constructor. - */ - public DataLengthException() - { - } - - /** - * create a DataLengthException with the given message. - * - * @param message the message to be carried with the exception. - */ - public DataLengthException( - String message) - { - super(message); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Digest.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Digest.java deleted file mode 100644 index 237183a..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Digest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * interface that a message digest conforms to. - */ -public interface Digest -{ - /** - * return the algorithm name - * - * @return the algorithm name - */ - public String getAlgorithmName(); - - /** - * return the size, in bytes, of the digest produced by this message digest. - * - * @return the size, in bytes, of the digest produced by this message digest. - */ - public int getDigestSize(); - - /** - * update the message digest with a single byte. - * - * @param in the input byte to be entered. - */ - public void update(byte in); - - /** - * update the message digest with a block of bytes. - * - * @param in the byte array containing the data. - * @param inOff the offset into the byte array where the data starts. - * @param len the length of the data. - */ - public void update(byte[] in, int inOff, int len); - - /** - * close the digest, producing the final digest value. The doFinal - * call leaves the digest reset. - * - * @param out the array the digest is to be copied into. - * @param outOff the offset into the out array the digest is to start at. - */ - public int doFinal(byte[] out, int outOff); - - /** - * reset the digest back to it's initial state. - */ - public void reset(); -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DigestFactory.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DigestFactory.java deleted file mode 100644 index 998674d..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/DigestFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * Basic factory class for message digests. - */ -public final class DigestFactory -{ - public static Digest createSHA256() - { - return new SHA256Digest(); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/EncodableDigest.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/EncodableDigest.java deleted file mode 100644 index 71413d8..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/EncodableDigest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * Encodable digests allow you to download an encoded copy of their internal state. This is useful for the situation where - * you need to generate a signature on an external device and it allows for "sign with last round", so a copy of the - * internal state of the digest, plus the last few blocks of the message are all that needs to be sent, rather than the - * entire message. - */ -public interface EncodableDigest -{ - /** - * Return an encoded byte array for the digest's internal state - * - * @return an encoding of the digests internal state. - */ - byte[] getEncodedState(); -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/ExtendedDigest.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/ExtendedDigest.java deleted file mode 100644 index ceda3e7..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/ExtendedDigest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -public interface ExtendedDigest - extends Digest -{ - /** - * Return the size in bytes of the internal buffer the digest applies it's compression - * function to. - * - * @return byte length of the digests internal buffer. - */ - public int getByteLength(); -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/GeneralDigest.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/GeneralDigest.java deleted file mode 100644 index b0077d7..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/GeneralDigest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * base implementation of MD4 family style digest as outlined in - * "Handbook of Applied Cryptography", pages 344 - 347. - */ -public abstract class GeneralDigest - implements ExtendedDigest, Memoable -{ - private static final int BYTE_LENGTH = 64; - - private final byte[] xBuf = new byte[4]; - private int xBufOff; - - private long byteCount; - - /** - * Standard constructor - */ - protected GeneralDigest() - { - xBufOff = 0; - } - - /** - * Copy constructor. We are using copy constructors in place - * of the Object.clone() interface as this interface is not - * supported by J2ME. - */ - protected GeneralDigest(GeneralDigest t) - { - copyIn(t); - } - - protected GeneralDigest(byte[] encodedState) - { - System.arraycopy(encodedState, 0, xBuf, 0, xBuf.length); - xBufOff = Pack.bigEndianToInt(encodedState, 4); - byteCount = Pack.bigEndianToLong(encodedState, 8); - } - - protected void copyIn(GeneralDigest t) - { - System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); - - xBufOff = t.xBufOff; - byteCount = t.byteCount; - } - - public void update( - byte in) - { - xBuf[xBufOff++] = in; - - if (xBufOff == xBuf.length) - { - processWord(xBuf, 0); - xBufOff = 0; - } - - byteCount++; - } - - public void update( - byte[] in, - int inOff, - int len) - { - len = Math.max(0, len); - - // - // fill the current word - // - int i = 0; - if (xBufOff != 0) - { - while (i < len) - { - xBuf[xBufOff++] = in[inOff + i++]; - if (xBufOff == 4) - { - processWord(xBuf, 0); - xBufOff = 0; - break; - } - } - } - - // - // process whole words. - // - int limit = ((len - i) & ~3) + i; - for (; i < limit; i += 4) - { - processWord(in, inOff + i); - } - - // - // load in the remainder. - // - while (i < len) - { - xBuf[xBufOff++] = in[inOff + i++]; - } - - byteCount += len; - } - - public void finish() - { - long bitLength = (byteCount << 3); - - // - // add the pad bytes. - // - update((byte)128); - - while (xBufOff != 0) - { - update((byte)0); - } - - processLength(bitLength); - - processBlock(); - } - - public void reset() - { - byteCount = 0; - - xBufOff = 0; - for (int i = 0; i < xBuf.length; i++) - { - xBuf[i] = 0; - } - } - - protected void populateState(byte[] state) - { - System.arraycopy(xBuf, 0, state, 0, xBufOff); - Pack.intToBigEndian(xBufOff, state, 4); - Pack.longToBigEndian(byteCount, state, 8); - } - - public int getByteLength() - { - return BYTE_LENGTH; - } - - protected abstract void processWord(byte[] in, int inOff); - - protected abstract void processLength(long bitLength); - - protected abstract void processBlock(); -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/HMac.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/HMac.java deleted file mode 100644 index b9c1f12..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/HMac.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -import java.util.Hashtable; - -/** - * HMAC implementation based on RFC2104 - * - * H(K XOR opad, H(K XOR ipad, text)) - */ -public class HMac - implements Mac -{ - private final static byte IPAD = (byte)0x36; - private final static byte OPAD = (byte)0x5C; - - private Digest digest; - private int digestSize; - private int blockLength; - private Memoable ipadState; - private Memoable opadState; - - private byte[] inputPad; - private byte[] outputBuf; - - private static Hashtable blockLengths; - - static - { - blockLengths = new Hashtable<>(); - - blockLengths.put("GOST3411", Integers.valueOf(32)); - - blockLengths.put("MD2", Integers.valueOf(16)); - blockLengths.put("MD4", Integers.valueOf(64)); - blockLengths.put("MD5", Integers.valueOf(64)); - - blockLengths.put("RIPEMD128", Integers.valueOf(64)); - blockLengths.put("RIPEMD160", Integers.valueOf(64)); - - blockLengths.put("SHA-1", Integers.valueOf(64)); - blockLengths.put("SHA-224", Integers.valueOf(64)); - blockLengths.put("SHA-256", Integers.valueOf(64)); - blockLengths.put("SHA-384", Integers.valueOf(128)); - blockLengths.put("SHA-512", Integers.valueOf(128)); - - blockLengths.put("Tiger", Integers.valueOf(64)); - blockLengths.put("Whirlpool", Integers.valueOf(64)); - } - - private static int getByteLength( - Digest digest) - { - if (digest instanceof ExtendedDigest) - { - return ((ExtendedDigest)digest).getByteLength(); - } - - Integer b = (Integer)blockLengths.get(digest.getAlgorithmName()); - - if (b == null) - { - throw new IllegalArgumentException("unknown digest passed: " + digest.getAlgorithmName()); - } - - return b.intValue(); - } - - /** - * Base constructor for one of the standard digest algorithms that the - * byteLength of the algorithm is know for. - * - * @param digest the digest. - */ - public HMac( - Digest digest) - { - this(digest, getByteLength(digest)); - } - - private HMac( - Digest digest, - int byteLength) - { - this.digest = digest; - this.digestSize = digest.getDigestSize(); - this.blockLength = byteLength; - this.inputPad = new byte[blockLength]; - this.outputBuf = new byte[blockLength + digestSize]; - } - - public String getAlgorithmName() - { - return digest.getAlgorithmName() + "/HMAC"; - } - - public Digest getUnderlyingDigest() - { - return digest; - } - - public void init( - CipherParameters params) - { - digest.reset(); - - byte[] key = ((KeyParameter)params).getKey(); - int keyLength = key.length; - - if (keyLength > blockLength) - { - digest.update(key, 0, keyLength); - digest.doFinal(inputPad, 0); - - keyLength = digestSize; - } - else - { - System.arraycopy(key, 0, inputPad, 0, keyLength); - } - - for (int i = keyLength; i < inputPad.length; i++) - { - inputPad[i] = 0; - } - - System.arraycopy(inputPad, 0, outputBuf, 0, blockLength); - - xorPad(inputPad, blockLength, IPAD); - xorPad(outputBuf, blockLength, OPAD); - - if (digest instanceof Memoable) - { - opadState = ((Memoable)digest).copy(); - - ((Digest)opadState).update(outputBuf, 0, blockLength); - } - - digest.update(inputPad, 0, inputPad.length); - - if (digest instanceof Memoable) - { - ipadState = ((Memoable)digest).copy(); - } - } - - public int getMacSize() - { - return digestSize; - } - - public void update( - byte in) - { - digest.update(in); - } - - public void update( - byte[] in, - int inOff, - int len) - { - digest.update(in, inOff, len); - } - - public int doFinal( - byte[] out, - int outOff) - { - digest.doFinal(outputBuf, blockLength); - - if (opadState != null) - { - ((Memoable)digest).reset(opadState); - digest.update(outputBuf, blockLength, digest.getDigestSize()); - } - else - { - digest.update(outputBuf, 0, outputBuf.length); - } - - int len = digest.doFinal(out, outOff); - - for (int i = blockLength; i < outputBuf.length; i++) - { - outputBuf[i] = 0; - } - - if (ipadState != null) - { - ((Memoable)digest).reset(ipadState); - } - else - { - digest.update(inputPad, 0, inputPad.length); - } - - return len; - } - - /** - * Reset the mac generator. - */ - public void reset() - { - /* - * reset the underlying digest. - */ - digest.reset(); - - /* - * reinitialize the digest. - */ - digest.update(inputPad, 0, inputPad.length); - } - - private static void xorPad(byte[] pad, int len, byte n) - { - for (int i = 0; i < len; ++i) - { - pad[i] ^= n; - } - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Integers.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Integers.java deleted file mode 100644 index 71605a1..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Integers.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * Utility methods for ints. - */ -public class Integers -{ - public static Integer valueOf(int value) - { - return Integer.valueOf(value); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/KeyParameter.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/KeyParameter.java deleted file mode 100644 index f3f1f2b..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/KeyParameter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -public class KeyParameter - implements CipherParameters -{ - private byte[] key; - - public KeyParameter( - byte[] key) - { - this(key, 0, key.length); - } - - public KeyParameter( - byte[] key, - int keyOff, - int keyLen) - { - this.key = new byte[keyLen]; - - System.arraycopy(key, keyOff, this.key, 0, keyLen); - } - - public byte[] getKey() - { - return key; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Mac.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Mac.java deleted file mode 100644 index 01f932f..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Mac.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - - -/** - * The base interface for implementations of message authentication codes (MACs). - */ -public interface Mac -{ - /** - * Initialise the MAC. - * - * @param params the key and other data required by the MAC. - * @exception IllegalArgumentException if the params argument is - * inappropriate. - */ - public void init(CipherParameters params) - throws IllegalArgumentException; - - /** - * Return the name of the algorithm the MAC implements. - * - * @return the name of the algorithm the MAC implements. - */ - public String getAlgorithmName(); - - /** - * Return the block size for this MAC (in bytes). - * - * @return the block size for this MAC in bytes. - */ - public int getMacSize(); - - /** - * add a single byte to the mac for processing. - * - * @param in the byte to be processed. - * @exception IllegalStateException if the MAC is not initialised. - */ - public void update(byte in) - throws IllegalStateException; - - /** - * @param in the array containing the input. - * @param inOff the index in the array the data begins at. - * @param len the length of the input starting at inOff. - * @exception IllegalStateException if the MAC is not initialised. - * @exception DataLengthException if there isn't enough data in in. - */ - public void update(byte[] in, int inOff, int len) - throws DataLengthException, IllegalStateException; - - /** - * Compute the final stage of the MAC writing the output to the out - * parameter. - *

- * doFinal leaves the MAC in the same state it was after the last init. - * - * @param out the array the MAC is to be output to. - * @param outOff the offset into the out buffer the output is to start at. - * @exception DataLengthException if there isn't enough space in out. - * @exception IllegalStateException if the MAC is not initialised. - */ - public int doFinal(byte[] out, int outOff) - throws DataLengthException, IllegalStateException; - - /** - * Reset the MAC. At the end of resetting the MAC should be in the - * in the same state it was after the last init (if there was one). - */ - public void reset(); -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Memoable.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Memoable.java deleted file mode 100644 index 4d435d9..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Memoable.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state - * via the copy() method and then reseting the object back to that state later using the reset() method. - */ -public interface Memoable -{ - /** - * Produce a copy of this object with its configuration and in its current state. - *

- * The returned object may be used simply to store the state, or may be used as a similar object - * starting from the copied state. - */ - Memoable copy(); - - /** - * Restore a copied object state into this object. - *

- * Implementations of this method should try to avoid or minimise memory allocation to perform the reset. - * - * @param other an object originally {@link #copy() copied} from an object of the same type as this instance. - * @throws ClassCastException if the provided object is not of the correct type. - */ - void reset(Memoable other); -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PBEParametersGenerator.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PBEParametersGenerator.java deleted file mode 100644 index 6b78a23..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PBEParametersGenerator.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * super class for all Password Based Encryption (PBE) parameter generator classes. - */ -public abstract class PBEParametersGenerator -{ - protected byte[] password; - protected byte[] salt; - protected int iterationCount; - - /** - * base constructor. - */ - protected PBEParametersGenerator() - { - } - - /** - * initialise the PBE generator. - * - * @param password the password converted into bytes (see below). - * @param salt the salt to be mixed with the password. - * @param iterationCount the number of iterations the "mixing" function - * is to be applied for. - */ - public void init( - byte[] password, - byte[] salt, - int iterationCount) - { - this.password = password; - this.salt = salt; - this.iterationCount = iterationCount; - } - - /** - * return the password byte array. - * - * @return the password byte array. - */ - public byte[] getPassword() - { - return password; - } - - /** - * return the salt byte array. - * - * @return the salt byte array. - */ - public byte[] getSalt() - { - return salt; - } - - /** - * return the iteration count. - * - * @return the iteration count. - */ - public int getIterationCount() - { - return iterationCount; - } - - /** - * generate derived parameters for a key of length keySize. - * - * @param keySize the length, in bits, of the key required. - * @return a parameters object representing a key. - */ - public abstract CipherParameters generateDerivedParameters(int keySize); - - /** - * converts a password to a byte array according to the scheme in - * PKCS5 (UTF-8, no padding) - * - * @param password a character array representing the password. - * @return a byte array representing the password. - */ - public static byte[] PKCS5PasswordToUTF8Bytes( - char[] password) - { - if (password != null) - { - return Strings.toUTF8ByteArray(password); - } - else - { - return new byte[0]; - } - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PKCS5S2ParametersGenerator.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PKCS5S2ParametersGenerator.java deleted file mode 100644 index 2129f4c..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/PKCS5S2ParametersGenerator.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * Generator for PBE derived keys and ivs as defined by PKCS 5 V2.0 Scheme 2. - * This generator uses a SHA-1 HMac as the calculation function. - *

- * The document this implementation is based on can be found at - * - * RSA's PKCS5 Page - */ -public class PKCS5S2ParametersGenerator - extends PBEParametersGenerator -{ - private Mac hMac; - private byte[] state; - - public PKCS5S2ParametersGenerator(Digest digest) - { - hMac = new HMac(digest); - state = new byte[hMac.getMacSize()]; - } - - private void F( - byte[] S, - int c, - byte[] iBuf, - byte[] out, - int outOff) - { - if (c == 0) - { - throw new IllegalArgumentException("iteration count must be at least 1."); - } - - if (S != null) - { - hMac.update(S, 0, S.length); - } - - hMac.update(iBuf, 0, iBuf.length); - hMac.doFinal(state, 0); - - System.arraycopy(state, 0, out, outOff, state.length); - - for (int count = 1; count < c; count++) - { - hMac.update(state, 0, state.length); - hMac.doFinal(state, 0); - - for (int j = 0; j != state.length; j++) - { - out[outOff + j] ^= state[j]; - } - } - } - - private byte[] generateDerivedKey( - int dkLen) - { - int hLen = hMac.getMacSize(); - int l = (dkLen + hLen - 1) / hLen; - byte[] iBuf = new byte[4]; - byte[] outBytes = new byte[l * hLen]; - int outPos = 0; - - CipherParameters param = new KeyParameter(password); - - hMac.init(param); - - for (int i = 1; i <= l; i++) - { - // Increment the value in 'iBuf' - int pos = 3; - while (++iBuf[pos] == 0) - { - --pos; - } - - F(salt, iterationCount, iBuf, outBytes, outPos); - outPos += hLen; - } - - return outBytes; - } - - /** - * Generate a key parameter derived from the password, salt, and iteration - * count we are currently initialised with. - * - * @param keySize the size of the key we want (in bits) - * @return a KeyParameter object. - */ - public CipherParameters generateDerivedParameters( - int keySize) - { - keySize = keySize / 8; - - byte[] dKey = Arrays.copyOfRange(generateDerivedKey(keySize), 0, keySize); - - return new KeyParameter(dKey, 0, keySize); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Pack.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Pack.java deleted file mode 100644 index 5c3a5a3..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Pack.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * Utility methods for converting byte arrays into ints and longs, and back again. - */ -public abstract class Pack -{ - public static int bigEndianToInt(byte[] bs, int off) - { - int n = bs[ off] << 24; - n |= (bs[++off] & 0xff) << 16; - n |= (bs[++off] & 0xff) << 8; - n |= (bs[++off] & 0xff); - return n; - } - - public static long bigEndianToLong(byte[] bs, int off) - { - int hi = bigEndianToInt(bs, off); - int lo = bigEndianToInt(bs, off + 4); - return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); - } - - public static void longToBigEndian(long n, byte[] bs, int off) - { - intToBigEndian((int)(n >>> 32), bs, off); - intToBigEndian((int)(n & 0xffffffffL), bs, off + 4); - } - - public static byte[] longToBigEndian(long[] ns) - { - byte[] bs = new byte[8 * ns.length]; - longToBigEndian(ns, bs, 0); - return bs; - } - - public static void longToBigEndian(long[] ns, byte[] bs, int off) - { - for (int i = 0; i < ns.length; ++i) - { - longToBigEndian(ns[i], bs, off); - off += 8; - } - } - - public static short littleEndianToShort(byte[] bs, int off) - { - int n = bs[ off] & 0xff; - n |= (bs[++off] & 0xff) << 8; - return (short)n; - } - - public static void intToBigEndian(int n, byte[] bs, int off) - { - bs[ off] = (byte)(n >>> 24); - bs[++off] = (byte)(n >>> 16); - bs[++off] = (byte)(n >>> 8); - bs[++off] = (byte)(n ); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/RuntimeCryptoException.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/RuntimeCryptoException.java deleted file mode 100644 index 60e2b6c..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/RuntimeCryptoException.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * the foundation class for the exceptions thrown by the crypto packages. - */ -public class RuntimeCryptoException - extends RuntimeException -{ - /** - * base constructor. - */ - public RuntimeCryptoException() - { - } - - /** - * create a RuntimeCryptoException with the given message. - * - * @param message the message to be carried with the exception. - */ - public RuntimeCryptoException( - String message) - { - super(message); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/SHA256Digest.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/SHA256Digest.java deleted file mode 100644 index b68ee09..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/SHA256Digest.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -/** - * FIPS 180-2 implementation of SHA-256. - * - *

- *         block  word  digest
- * SHA-1   512    32    160
- * SHA-256 512    32    256
- * SHA-384 1024   64    384
- * SHA-512 1024   64    512
- * 
- */ -public class SHA256Digest - extends GeneralDigest - implements EncodableDigest -{ - private static final int DIGEST_LENGTH = 32; - - private int H1, H2, H3, H4, H5, H6, H7, H8; - - private int[] X = new int[64]; - private int xOff; - - /** - * Standard constructor - */ - public SHA256Digest() - { - reset(); - } - - /** - * Copy constructor. This will copy the state of the provided - * message digest. - */ - public SHA256Digest(SHA256Digest t) - { - super(t); - - copyIn(t); - } - - private void copyIn(SHA256Digest t) - { - super.copyIn(t); - - H1 = t.H1; - H2 = t.H2; - H3 = t.H3; - H4 = t.H4; - H5 = t.H5; - H6 = t.H6; - H7 = t.H7; - H8 = t.H8; - - System.arraycopy(t.X, 0, X, 0, t.X.length); - xOff = t.xOff; - } - - /** - * State constructor - create a digest initialised with the state of a previous one. - * - * @param encodedState the encoded state from the originating digest. - */ - public SHA256Digest(byte[] encodedState) - { - super(encodedState); - - H1 = Pack.bigEndianToInt(encodedState, 16); - H2 = Pack.bigEndianToInt(encodedState, 20); - H3 = Pack.bigEndianToInt(encodedState, 24); - H4 = Pack.bigEndianToInt(encodedState, 28); - H5 = Pack.bigEndianToInt(encodedState, 32); - H6 = Pack.bigEndianToInt(encodedState, 36); - H7 = Pack.bigEndianToInt(encodedState, 40); - H8 = Pack.bigEndianToInt(encodedState, 44); - - xOff = Pack.bigEndianToInt(encodedState, 48); - for (int i = 0; i != xOff; i++) - { - X[i] = Pack.bigEndianToInt(encodedState, 52 + (i * 4)); - } - } - - - public String getAlgorithmName() - { - return "SHA-256"; - } - - public int getDigestSize() - { - return DIGEST_LENGTH; - } - - protected void processWord( - byte[] in, - int inOff) - { - // Note: Inlined for performance -// X[xOff] = Pack.bigEndianToInt(in, inOff); - int n = in[inOff] << 24; - n |= (in[++inOff] & 0xff) << 16; - n |= (in[++inOff] & 0xff) << 8; - n |= (in[++inOff] & 0xff); - X[xOff] = n; - - if (++xOff == 16) - { - processBlock(); - } - } - - protected void processLength( - long bitLength) - { - if (xOff > 14) - { - processBlock(); - } - - X[14] = (int)(bitLength >>> 32); - X[15] = (int)(bitLength & 0xffffffff); - } - - public int doFinal( - byte[] out, - int outOff) - { - finish(); - - Pack.intToBigEndian(H1, out, outOff); - Pack.intToBigEndian(H2, out, outOff + 4); - Pack.intToBigEndian(H3, out, outOff + 8); - Pack.intToBigEndian(H4, out, outOff + 12); - Pack.intToBigEndian(H5, out, outOff + 16); - Pack.intToBigEndian(H6, out, outOff + 20); - Pack.intToBigEndian(H7, out, outOff + 24); - Pack.intToBigEndian(H8, out, outOff + 28); - - reset(); - - return DIGEST_LENGTH; - } - - /** - * reset the chaining variables - */ - public void reset() - { - super.reset(); - - /* SHA-256 initial hash value - * The first 32 bits of the fractional parts of the square roots - * of the first eight prime numbers - */ - - H1 = 0x6a09e667; - H2 = 0xbb67ae85; - H3 = 0x3c6ef372; - H4 = 0xa54ff53a; - H5 = 0x510e527f; - H6 = 0x9b05688c; - H7 = 0x1f83d9ab; - H8 = 0x5be0cd19; - - xOff = 0; - for (int i = 0; i != X.length; i++) - { - X[i] = 0; - } - } - - protected void processBlock() - { - // - // expand 16 word block into 64 word blocks. - // - for (int t = 16; t <= 63; t++) - { - X[t] = Theta1(X[t - 2]) + X[t - 7] + Theta0(X[t - 15]) + X[t - 16]; - } - - // - // set up working variables. - // - int a = H1; - int b = H2; - int c = H3; - int d = H4; - int e = H5; - int f = H6; - int g = H7; - int h = H8; - - int t = 0; - for(int i = 0; i < 8; i ++) - { - // t = 8 * i - h += Sum1(e) + Ch(e, f, g) + K[t] + X[t]; - d += h; - h += Sum0(a) + Maj(a, b, c); - ++t; - - // t = 8 * i + 1 - g += Sum1(d) + Ch(d, e, f) + K[t] + X[t]; - c += g; - g += Sum0(h) + Maj(h, a, b); - ++t; - - // t = 8 * i + 2 - f += Sum1(c) + Ch(c, d, e) + K[t] + X[t]; - b += f; - f += Sum0(g) + Maj(g, h, a); - ++t; - - // t = 8 * i + 3 - e += Sum1(b) + Ch(b, c, d) + K[t] + X[t]; - a += e; - e += Sum0(f) + Maj(f, g, h); - ++t; - - // t = 8 * i + 4 - d += Sum1(a) + Ch(a, b, c) + K[t] + X[t]; - h += d; - d += Sum0(e) + Maj(e, f, g); - ++t; - - // t = 8 * i + 5 - c += Sum1(h) + Ch(h, a, b) + K[t] + X[t]; - g += c; - c += Sum0(d) + Maj(d, e, f); - ++t; - - // t = 8 * i + 6 - b += Sum1(g) + Ch(g, h, a) + K[t] + X[t]; - f += b; - b += Sum0(c) + Maj(c, d, e); - ++t; - - // t = 8 * i + 7 - a += Sum1(f) + Ch(f, g, h) + K[t] + X[t]; - e += a; - a += Sum0(b) + Maj(b, c, d); - ++t; - } - - H1 += a; - H2 += b; - H3 += c; - H4 += d; - H5 += e; - H6 += f; - H7 += g; - H8 += h; - - // - // reset the offset and clean out the word buffer. - // - xOff = 0; - for (int i = 0; i < 16; i++) - { - X[i] = 0; - } - } - - /* SHA-256 functions */ - private int Ch( - int x, - int y, - int z) - { - return (x & y) ^ ((~x) & z); - } - - private int Maj( - int x, - int y, - int z) - { - return (x & y) ^ (x & z) ^ (y & z); - } - - private int Sum0( - int x) - { - return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)); - } - - private int Sum1( - int x) - { - return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)); - } - - private int Theta0( - int x) - { - return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3); - } - - private int Theta1( - int x) - { - return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10); - } - - /* SHA-256 Constants - * (represent the first 32 bits of the fractional parts of the - * cube roots of the first sixty-four prime numbers) - */ - static final int K[] = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - }; - - public Memoable copy() - { - return new SHA256Digest(this); - } - - public void reset(Memoable other) - { - SHA256Digest d = (SHA256Digest)other; - - copyIn(d); - } - - public byte[] getEncodedState() - { - byte[] state = new byte[52 + xOff * 4]; - - super.populateState(state); - - Pack.intToBigEndian(H1, state, 16); - Pack.intToBigEndian(H2, state, 20); - Pack.intToBigEndian(H3, state, 24); - Pack.intToBigEndian(H4, state, 28); - Pack.intToBigEndian(H5, state, 32); - Pack.intToBigEndian(H6, state, 36); - Pack.intToBigEndian(H7, state, 40); - Pack.intToBigEndian(H8, state, 44); - Pack.intToBigEndian(xOff, state, 48); - - for (int i = 0; i != xOff; i++) - { - Pack.intToBigEndian(X[i], state, 52 + (i * 4)); - } - - return state; - } -} - diff --git a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Strings.java b/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Strings.java deleted file mode 100644 index 19a24c5..0000000 --- a/common/src/main/java/com/ongres/scram/common/bouncycastle/pbkdf2/Strings.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.bouncycastle.pbkdf2; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * String utilities. - */ -public final class Strings -{ - - public static byte[] toUTF8ByteArray(char[] string) - { - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - - try - { - toUTF8ByteArray(string, bOut); - } - catch (IOException e) - { - throw new IllegalStateException("cannot encode string to byte array!"); - } - - return bOut.toByteArray(); - } - - public static void toUTF8ByteArray(char[] string, OutputStream sOut) - throws IOException - { - char[] c = string; - int i = 0; - - while (i < c.length) - { - char ch = c[i]; - - if (ch < 0x0080) - { - sOut.write(ch); - } - else if (ch < 0x0800) - { - sOut.write(0xc0 | (ch >> 6)); - sOut.write(0x80 | (ch & 0x3f)); - } - // surrogate pair - else if (ch >= 0xD800 && ch <= 0xDFFF) - { - // in error - can only happen, if the Java String class has a - // bug. - if (i + 1 >= c.length) - { - throw new IllegalStateException("invalid UTF-16 codepoint"); - } - char W1 = ch; - ch = c[++i]; - char W2 = ch; - // in error - can only happen, if the Java String class has a - // bug. - if (W1 > 0xDBFF) - { - throw new IllegalStateException("invalid UTF-16 codepoint"); - } - int codePoint = (((W1 & 0x03FF) << 10) | (W2 & 0x03FF)) + 0x10000; - sOut.write(0xf0 | (codePoint >> 18)); - sOut.write(0x80 | ((codePoint >> 12) & 0x3F)); - sOut.write(0x80 | ((codePoint >> 6) & 0x3F)); - sOut.write(0x80 | (codePoint & 0x3F)); - } - else - { - sOut.write(0xe0 | (ch >> 12)); - sOut.write(0x80 | ((ch >> 6) & 0x3F)); - sOut.write(0x80 | (ch & 0x3F)); - } - - i++; - } - } - - /** - * Convert an array of 8 bit characters into a string. - * - * @param bytes 8 bit characters. - * @return resulting String. - */ - public static String fromByteArray(byte[] bytes) - { - return new String(asCharArray(bytes)); - } - - /** - * Do a simple conversion of an array of 8 bit characters into a string. - * - * @param bytes 8 bit characters. - * @return resulting String. - */ - public static char[] asCharArray(byte[] bytes) - { - char[] chars = new char[bytes.length]; - - for (int i = 0; i != chars.length; i++) - { - chars[i] = (char)(bytes[i] & 0xff); - } - - return chars; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/exception/ScramException.java b/common/src/main/java/com/ongres/scram/common/exception/ScramException.java deleted file mode 100644 index 430dbdc..0000000 --- a/common/src/main/java/com/ongres/scram/common/exception/ScramException.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.exception; - - -import javax.security.sasl.SaslException; - - -/** - * This class represents an error when using SCRAM, which is a SASL method. - * - * {@link SaslException} - */ -public class ScramException extends SaslException { - /** - * Constructs a new instance of ScramException with a detailed message. - * @param detail A String containing details about the exception - */ - public ScramException(String detail) { - super(detail); - } - - /** - * Constructs a new instance of ScramException with a detailed message and a root cause. - * @param detail A String containing details about the exception - * @param ex The root exception - */ - public ScramException(String detail, Throwable ex) { - super(detail, ex); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java b/common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java deleted file mode 100644 index d0712fc..0000000 --- a/common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.exception; - - -/** - * This class represents an error when parsing SCRAM messages - */ -public class ScramInvalidServerSignatureException extends ScramException { - /** - * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message. - * @param detail A String containing details about the exception - */ - public ScramInvalidServerSignatureException(String detail) { - super(detail); - } - - /** - * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message and a root cause. - * @param detail A String containing details about the exception - * @param ex The root exception - */ - public ScramInvalidServerSignatureException(String detail, Throwable ex) { - super(detail, ex); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java b/common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java deleted file mode 100644 index b9ba1ce..0000000 --- a/common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.exception; - - -/** - * This class represents an error when parsing SCRAM messages - */ -public class ScramParseException extends ScramException { - /** - * Constructs a new instance of ScramParseException with a detailed message. - * @param detail A String containing details about the exception - */ - public ScramParseException(String detail) { - super(detail); - } - - /** - * Constructs a new instance of ScramParseException with a detailed message and a root cause. - * @param detail A String containing details about the exception - * @param ex The root exception - */ - public ScramParseException(String detail, Throwable ex) { - super(detail, ex); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java b/common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java deleted file mode 100644 index dcf0872..0000000 --- a/common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.exception; - - -import com.ongres.scram.common.message.ServerFinalMessage; - - -/** - * This class represents an error when parsing SCRAM messages - */ -public class ScramServerErrorException extends ScramException { - private final ServerFinalMessage.Error error; - - private static String toString(ServerFinalMessage.Error error) { - return "Server-final-message is an error message. Error: " + error.getErrorMessage(); - } - - /** - * Constructs a new instance of ScramServerErrorException with a detailed message. - * @param error The SCRAM error in the message - */ - public ScramServerErrorException(ServerFinalMessage.Error error) { - super(toString(error)); - this.error = error; - } - - /** - * Constructs a new instance of ScramServerErrorException with a detailed message and a root cause. - * @param error The SCRAM error in the message - * @param ex The root exception - */ - public ScramServerErrorException(ServerFinalMessage.Error error, Throwable ex) { - super(toString(error), ex); - this.error = error; - } - - public ServerFinalMessage.Error getError() { - return error; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2AttributeValue.java b/common/src/main/java/com/ongres/scram/common/gssapi/Gs2AttributeValue.java deleted file mode 100644 index 3869738..0000000 --- a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2AttributeValue.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.gssapi; - - -import com.ongres.scram.common.util.AbstractCharAttributeValue; - -/** - * Parse and write GS2 Attribute-Value pairs. - */ -public class Gs2AttributeValue extends AbstractCharAttributeValue { - public Gs2AttributeValue(Gs2Attributes attribute, String value) { - super(attribute, value); - } - - public static StringBuffer writeTo(StringBuffer sb, Gs2Attributes attribute, String value) { - return new Gs2AttributeValue(attribute, value).writeTo(sb); - } - - /** - * Parses a potential Gs2AttributeValue String. - * @param value The string that contains the Attribute-Value pair (where value is optional). - * @return The parsed class, or null if the String was null. - * @throws IllegalArgumentException If the String is an invalid Gs2AttributeValue - */ - public static Gs2AttributeValue parse(String value) throws IllegalArgumentException { - if(null == value) { - return null; - } - - if(value.length() < 1 || value.length() == 2 || (value.length() > 2 && value.charAt(1) != '=')) { - throw new IllegalArgumentException("Invalid Gs2AttributeValue"); - } - - return new Gs2AttributeValue( - Gs2Attributes.byChar(value.charAt(0)), - value.length() > 2 ? value.substring(2) : null - ); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Attributes.java b/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Attributes.java deleted file mode 100644 index d101e73..0000000 --- a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Attributes.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.gssapi; - - -import com.ongres.scram.common.ScramAttributes; -import com.ongres.scram.common.util.CharAttribute; - - -/** - * Possible values of a GS2 Attribute. - * - * @see [RFC5802] Formal Syntax - */ -public enum Gs2Attributes implements CharAttribute { - /** - * Channel binding attribute. Client doesn't support channel binding. - */ - CLIENT_NOT(Gs2CbindFlag.CLIENT_NOT.getChar()), - - /** - * Channel binding attribute. Client does support channel binding but thinks the server does not. - */ - CLIENT_YES_SERVER_NOT(Gs2CbindFlag.CLIENT_YES_SERVER_NOT.getChar()), - - /** - * Channel binding attribute. Client requires channel binding. The selected channel binding follows "p=". - */ - CHANNEL_BINDING_REQUIRED(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED.getChar()), - - /** - * SCRAM attribute. This attribute specifies an authorization identity. - */ - AUTHZID(ScramAttributes.AUTHZID.getChar()) - ; - - private final char flag; - - Gs2Attributes(char flag) { - this.flag = flag; - } - - @Override - public char getChar() { - return flag; - } - - public static Gs2Attributes byChar(char c) { - switch(c) { - case 'n': return CLIENT_NOT; - case 'y': return CLIENT_YES_SERVER_NOT; - case 'p': return CHANNEL_BINDING_REQUIRED; - case 'a': return AUTHZID; - } - - throw new IllegalArgumentException("Invalid GS2Attribute character '" + c + "'"); - } - - public static Gs2Attributes byGS2CbindFlag(Gs2CbindFlag cbindFlag) { - return byChar(cbindFlag.getChar()); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2CbindFlag.java b/common/src/main/java/com/ongres/scram/common/gssapi/Gs2CbindFlag.java deleted file mode 100644 index e2025fa..0000000 --- a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2CbindFlag.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.gssapi; - - -import com.ongres.scram.common.util.CharAttribute; - - -/** - * Possible values of a GS2 Cbind Flag (channel binding; part of GS2 header). - * These values are sent by the client, and so are interpreted from this perspective. - * - * @see [RFC5802] Formal Syntax - */ -public enum Gs2CbindFlag implements CharAttribute { - /** - * Client doesn't support channel binding. - */ - CLIENT_NOT('n'), - - /** - * Client does support channel binding but thinks the server does not. - */ - CLIENT_YES_SERVER_NOT('y'), - - /** - * Client requires channel binding. The selected channel binding follows "p=". - */ - CHANNEL_BINDING_REQUIRED('p') - ; - - private final char flag; - - Gs2CbindFlag(char flag) { - this.flag = flag; - } - - @Override - public char getChar() { - return flag; - } - - public static Gs2CbindFlag byChar(char c) { - switch(c) { - case 'n': return CLIENT_NOT; - case 'y': return CLIENT_YES_SERVER_NOT; - case 'p': return CHANNEL_BINDING_REQUIRED; - } - - throw new IllegalArgumentException("Invalid Gs2CbindFlag character '" + c + "'"); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Header.java b/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Header.java deleted file mode 100644 index b794967..0000000 --- a/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Header.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.gssapi; - - -import com.ongres.scram.common.util.StringWritableCsv; -import com.ongres.scram.common.ScramStringFormatting; -import com.ongres.scram.common.util.AbstractStringWritable; - -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * GSS Header. Format: - * - * {@code - * gs2-header = gs2-cbind-flag "," [ authzid ] "," - * gs2-cbind-flag = ("p=" cb-name) / "n" / "y" - * authzid = "a=" saslname - * } - * - * Current implementation does not support channel binding. - * If p is used as the cbind flag, the cb-name value is not validated. - * - * @see [RFC5802] Formal Syntax - */ -public class Gs2Header extends AbstractStringWritable { - private final Gs2AttributeValue cbind; - private final Gs2AttributeValue authzid; - - /** - * Construct and validates a Gs2Header. - * Only provide the channel binding name if the channel binding flag is set to required. - * @param cbindFlag The channel binding flag - * @param cbName The channel-binding name. Should be not null iif channel binding is required - * @param authzid The optional SASL authorization identity - * @throws IllegalArgumentException If the channel binding flag and argument are invalid - */ - public Gs2Header(Gs2CbindFlag cbindFlag, String cbName, String authzid) throws IllegalArgumentException { - checkNotNull(cbindFlag, "cbindFlag"); - if(cbindFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED ^ cbName != null) { - throw new IllegalArgumentException("Specify channel binding flag and value together, or none"); - } - // TODO: cbName is not being properly validated - cbind = new Gs2AttributeValue(Gs2Attributes.byGS2CbindFlag(cbindFlag), cbName); - - this.authzid = authzid == null ? - null : new Gs2AttributeValue(Gs2Attributes.AUTHZID, ScramStringFormatting.toSaslName(authzid)) - ; - } - - /** - * Construct and validates a Gs2Header with no authzid. - * Only provide the channel binding name if the channel binding flag is set to required. - * @param cbindFlag The channel binding flag - * @param cbName The channel-binding name. Should be not null iif channel binding is required - * @throws IllegalArgumentException If the channel binding flag and argument are invalid - */ - public Gs2Header(Gs2CbindFlag cbindFlag, String cbName) throws IllegalArgumentException { - this(cbindFlag, cbName, null); - } - - /** - * Construct and validates a Gs2Header with no authzid nor channel binding. - * @param cbindFlag The channel binding flag - * @throws IllegalArgumentException If the channel binding is supported (no cbname can be provided here) - */ - public Gs2Header(Gs2CbindFlag cbindFlag) { - this(cbindFlag, null, null); - } - - public Gs2CbindFlag getChannelBindingFlag() { - return Gs2CbindFlag.byChar(cbind.getChar()); - } - - public String getChannelBindingName() { - return cbind.getValue(); - } - - public String getAuthzid() { - return authzid != null ? authzid.getValue() : null; - } - - @Override - public StringBuffer writeTo(StringBuffer sb) { - return StringWritableCsv.writeTo(sb, cbind, authzid); - } - - /** - * Read a Gs2Header from a String. String may contain trailing fields that will be ignored. - * @param message The String containing the Gs2Header - * @return The parsed Gs2Header object - * @throws IllegalArgumentException If the format/values of the String do not conform to a Gs2Header - */ - public static Gs2Header parseFrom(String message) throws IllegalArgumentException { - checkNotNull(message, "Null message"); - - String[] gs2HeaderSplit = StringWritableCsv.parseFrom(message, 2); - if(gs2HeaderSplit.length == 0) { - throw new IllegalArgumentException("Invalid number of fields for the GS2 Header"); - } - - Gs2AttributeValue gs2cbind = Gs2AttributeValue.parse(gs2HeaderSplit[0]); - return new Gs2Header( - Gs2CbindFlag.byChar(gs2cbind.getChar()), - gs2cbind.getValue(), - gs2HeaderSplit[1] == null || gs2HeaderSplit[1].isEmpty() ? - null : Gs2AttributeValue.parse(gs2HeaderSplit[1]).getValue() - ); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/message/ClientFinalMessage.java b/common/src/main/java/com/ongres/scram/common/message/ClientFinalMessage.java deleted file mode 100644 index a47ebe4..0000000 --- a/common/src/main/java/com/ongres/scram/common/message/ClientFinalMessage.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import com.ongres.scram.common.ScramAttributeValue; -import com.ongres.scram.common.ScramAttributes; -import com.ongres.scram.common.ScramStringFormatting; -import com.ongres.scram.common.gssapi.Gs2Header; -import com.ongres.scram.common.util.StringWritable; -import com.ongres.scram.common.util.StringWritableCsv; - -import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * Constructs and parses client-final-messages. Formal syntax is: - * - * {@code - * client-final-message-without-proof = channel-binding "," nonce ["," extensions] - * client-final-message = client-final-message-without-proof "," proof - * } - * - * Note that extensions are not supported. - * - * @see [RFC5802] Section 7 - */ -public class ClientFinalMessage implements StringWritable { - private final String cbind; - private final String nonce; - private final byte[] proof; - - private static String generateCBind(Gs2Header gs2Header, byte[] cbindData) { - StringBuffer sb = new StringBuffer(); - gs2Header.writeTo(sb) - .append(','); - - if(null != cbindData) { - new ScramAttributeValue( - ScramAttributes.CHANNEL_BINDING, - ScramStringFormatting.base64Encode(cbindData) - ).writeTo(sb); - } - - return sb.toString(); - } - - /** - * Constructus a client-final-message with the provided gs2Header (the same one used in the client-first-message), - * optionally the channel binding data, and the nonce. - * This method is intended to be used by SCRAM clients, and not to be constructed directly. - * @param gs2Header The GSS-API header - * @param cbindData If using channel binding, the channel binding data - * @param nonce The nonce - * @param proof The bytes representing the computed client proof - */ - public ClientFinalMessage(Gs2Header gs2Header, byte[] cbindData, String nonce, byte[] proof) { - this.cbind = generateCBind( - checkNotNull(gs2Header, "gs2Header"), - cbindData - ); - this.nonce = checkNotEmpty(nonce, "nonce"); - this.proof = checkNotNull(proof, "proof"); - } - - private static StringBuffer writeToWithoutProof(StringBuffer sb, String cbind, String nonce) { - return StringWritableCsv.writeTo( - sb, - new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, ScramStringFormatting.base64Encode(cbind)), - new ScramAttributeValue(ScramAttributes.NONCE, nonce) - ); - } - - private static StringBuffer writeToWithoutProof( - StringBuffer sb, Gs2Header gs2Header, byte[] cbindData, String nonce - ) { - return writeToWithoutProof( - sb, - generateCBind( - checkNotNull(gs2Header, "gs2Header"), - cbindData - ), - nonce - ); - } - - /** - * Returns a StringBuffer filled in with the formatted output of a client-first-message without the proof value. - * This is useful for computing the auth-message, used in turn to compute the proof. - * @param gs2Header The GSS-API header - * @param cbindData The optional channel binding data - * @param nonce The nonce - * @return The String representation of the part of the message that excludes the proof - */ - public static StringBuffer writeToWithoutProof(Gs2Header gs2Header, byte[] cbindData, String nonce) { - return writeToWithoutProof(new StringBuffer(), gs2Header, cbindData, nonce); - } - - @Override - public StringBuffer writeTo(StringBuffer sb) { - writeToWithoutProof(sb, cbind, nonce); - - return StringWritableCsv.writeTo( - sb, - null, // This marks the position of writeToWithoutProof, required for the "," - new ScramAttributeValue(ScramAttributes.CLIENT_PROOF, ScramStringFormatting.base64Encode(proof)) - ); - } - - @Override - public String toString() { - return writeTo(new StringBuffer()).toString(); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/message/ClientFirstMessage.java b/common/src/main/java/com/ongres/scram/common/message/ClientFirstMessage.java deleted file mode 100644 index b2e9291..0000000 --- a/common/src/main/java/com/ongres/scram/common/message/ClientFirstMessage.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import com.ongres.scram.common.ScramAttributeValue; -import com.ongres.scram.common.ScramAttributes; -import com.ongres.scram.common.ScramStringFormatting; -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.gssapi.Gs2CbindFlag; -import com.ongres.scram.common.gssapi.Gs2Header; -import com.ongres.scram.common.util.StringWritable; -import com.ongres.scram.common.util.StringWritableCsv; - -import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * Constructs and parses client-first-messages. - * Message contains a {@link Gs2Header}, a username and a nonce. Formal syntax is: - * - * {@code - * client-first-message-bare = [reserved-mext ","] username "," nonce ["," extensions] - client-first-message = gs2-header client-first-message-bare - * } - * - * Note that extensions are not supported. - * - * @see [RFC5802] Section 7 - */ -public class ClientFirstMessage implements StringWritable { - private final Gs2Header gs2Header; - private final String user; - private final String nonce; - - /** - * Constructs a client-first-message for the given user, nonce and gs2Header. - * This constructor is intended to be instantiated by a scram client, and not directly. - * The client should be providing the header, and nonce (and probably the user too). - * @param gs2Header The GSS-API header - * @param user The SCRAM user - * @param nonce The nonce for this session - * @throws IllegalArgumentException If any of the arguments is null or empty - */ - public ClientFirstMessage(Gs2Header gs2Header, String user, String nonce) throws IllegalArgumentException { - this.gs2Header = checkNotNull(gs2Header, "gs2Header"); - this.user = checkNotEmpty(user, "user"); - this.nonce = checkNotEmpty(nonce, "nonce"); - } - - private static Gs2Header gs2Header(Gs2CbindFlag gs2CbindFlag, String authzid, String cbindName) { - checkNotNull(gs2CbindFlag, "gs2CbindFlag"); - if(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED == gs2CbindFlag && null == cbindName) { - throw new IllegalArgumentException("Channel binding name is required if channel binding is specified"); - } - - return new Gs2Header(gs2CbindFlag, cbindName, authzid); - } - - /** - * Constructs a client-first-message for the given parameters. - * Under normal operation, this constructor is intended to be instantiated by a scram client, and not directly. - * However, this constructor is more user- or test-friendly, as the arguments are easier to provide without - * building other indirect object parameters. - * @param gs2CbindFlag The channel-binding flag - * @param authzid The optional authzid - * @param cbindName The optional channel binding name - * @param user The SCRAM user - * @param nonce The nonce for this session - * @throws IllegalArgumentException If the flag, user or nonce are null or empty - */ - public ClientFirstMessage(Gs2CbindFlag gs2CbindFlag, String authzid, String cbindName, String user, String nonce) { - this(gs2Header(gs2CbindFlag, authzid, cbindName), user, nonce); - } - - /** - * Constructs a client-first-message for the given parameters, with no channel binding nor authzid. - * Under normal operation, this constructor is intended to be instantiated by a scram client, and not directly. - * However, this constructor is more user- or test-friendly, as the arguments are easier to provide without - * building other indirect object parameters. - * @param user The SCRAM user - * @param nonce The nonce for this session - * @throws IllegalArgumentException If the user or nonce are null or empty - */ - public ClientFirstMessage(String user, String nonce) { - this(gs2Header(Gs2CbindFlag.CLIENT_NOT, null, null), user, nonce); - } - - public Gs2CbindFlag getChannelBindingFlag() { - return gs2Header.getChannelBindingFlag(); - } - - public boolean isChannelBinding() { - return gs2Header.getChannelBindingFlag() == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED; - } - - public String getChannelBindingName() { - return gs2Header.getChannelBindingName(); - } - - public String getAuthzid() { - return gs2Header.getAuthzid(); - } - - public Gs2Header getGs2Header() { - return gs2Header; - } - - public String getUser() { - return user; - } - - public String getNonce() { - return nonce; - } - - /** - * Limited version of the {@link StringWritableCsv#toString()} method, that doesn't write the GS2 header. - * This method is useful to construct the auth message used as part of the SCRAM algorithm. - * @param sb A StringBuffer where to write the data to. - * @return The same StringBuffer - */ - public StringBuffer writeToWithoutGs2Header(StringBuffer sb) { - return StringWritableCsv.writeTo( - sb, - new ScramAttributeValue(ScramAttributes.USERNAME, ScramStringFormatting.toSaslName(user)), - new ScramAttributeValue(ScramAttributes.NONCE, nonce) - ); - } - - @Override - public StringBuffer writeTo(StringBuffer sb) { - StringWritableCsv.writeTo( - sb, - gs2Header, - null // This marks the position of the rest of the elements, required for the "," - ); - - return writeToWithoutGs2Header(sb); - } - - /** - * Construct a {@link ClientFirstMessage} instance from a message (String) - * @param clientFirstMessage The String representing the client-first-message - * @return The instance - * @throws ScramParseException If the message is not a valid client-first-message - * @throws IllegalArgumentException If the message is null or empty - */ - public static ClientFirstMessage parseFrom(String clientFirstMessage) - throws ScramParseException, IllegalArgumentException { - checkNotEmpty(clientFirstMessage, "clientFirstMessage"); - - Gs2Header gs2Header = Gs2Header.parseFrom(clientFirstMessage); // Takes first two fields - String[] userNonceString; - try { - userNonceString = StringWritableCsv.parseFrom(clientFirstMessage, 2, 2); - } catch (IllegalArgumentException e) { - throw new ScramParseException("Illegal series of attributes in client-first-message", e); - } - - ScramAttributeValue user = ScramAttributeValue.parse(userNonceString[0]); - if(ScramAttributes.USERNAME.getChar() != user.getChar()) { - throw new ScramParseException("user must be the 3rd element of the client-first-message"); - } - - ScramAttributeValue nonce = ScramAttributeValue.parse(userNonceString[1]); - if(ScramAttributes.NONCE.getChar() != nonce.getChar()) { - throw new ScramParseException("nonce must be the 4th element of the client-first-message"); - } - - return new ClientFirstMessage(gs2Header, user.getValue(), nonce.getValue()); - } - - @Override - public String toString() { - return writeTo(new StringBuffer()).toString(); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/message/ServerFinalMessage.java b/common/src/main/java/com/ongres/scram/common/message/ServerFinalMessage.java deleted file mode 100644 index 5961d00..0000000 --- a/common/src/main/java/com/ongres/scram/common/message/ServerFinalMessage.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - -import java.util.HashMap; -import java.util.Map; - -import com.ongres.scram.common.ScramAttributeValue; -import com.ongres.scram.common.ScramAttributes; -import com.ongres.scram.common.ScramStringFormatting; -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.util.StringWritable; -import com.ongres.scram.common.util.StringWritableCsv; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - - -/** - * Constructs and parses server-final-messages. Formal syntax is: - * - * {@code - * server-error = "e=" server-error-value - * - * server-error-value = "invalid-encoding" / - * "extensions-not-supported" / ; unrecognized 'm' value - * "invalid-proof" / - * "channel-bindings-dont-match" / - * "server-does-support-channel-binding" / - * ; server does not support channel binding - * "channel-binding-not-supported" / - * "unsupported-channel-binding-type" / - * "unknown-user" / - * "invalid-username-encoding" / - * ; invalid username encoding (invalid UTF-8 or - * ; SASLprep failed) - * "no-resources" / - * "other-error" / - * server-error-value-ext - * ; Unrecognized errors should be treated as "other-error". - * ; In order to prevent information disclosure, the server - * ; may substitute the real reason with "other-error". - * - * server-error-value-ext = value - * ; Additional error reasons added by extensions - * ; to this document. - * - * verifier = "v=" base64 - * ;; base-64 encoded ServerSignature. - * - * server-final-errorMessage = (server-error / verifier) - * ["," extensions] - * } - * - * Note that extensions are not supported (and, consequently, error message extensions). - * - * @see [RFC5802] Section 7 - */ -public class ServerFinalMessage implements StringWritable { - - /** - * Possible error messages sent on a server-final-message. - */ - public enum Error { - INVALID_ENCODING("invalid-encoding"), - EXTENSIONS_NOT_SUPPORTED("extensions-not-supported"), - INVALID_PROOF("invalid-proof"), - CHANNEL_BINDINGS_DONT_MATCH("channel-bindings-dont-match"), - SERVER_DOES_SUPPORT_CHANNEL_BINDING("server-does-support-channel-binding"), - CHANNEL_BINDING_NOT_SUPPORTED("channel-binding-not-supported"), - UNSUPPORTED_CHANNEL_BINDING_TYPE("unsupported-channel-binding-type"), - UNKNOWN_USER("unknown-user"), - INVALID_USERNAME_ENCODING("invalid-username-encoding"), - NO_RESOURCES("no-resources"), - OTHER_ERROR("other-error") - ; - - private static final Map BY_NAME_MAPPING = valuesAsMap(); - - private final String errorMessage; - - Error(String errorMessage) { - this.errorMessage = errorMessage; - } - - public String getErrorMessage() { - return errorMessage; - } - - public static Error getByErrorMessage(String errorMessage) throws IllegalArgumentException { - checkNotEmpty(errorMessage, "errorMessage"); - - if(! BY_NAME_MAPPING.containsKey(errorMessage)) { - throw new IllegalArgumentException("Invalid error message '" + errorMessage + "'"); - } - - return BY_NAME_MAPPING.get(errorMessage); - } - - private static Map valuesAsMap() { - Map map = new HashMap<>(values().length); - for (Error error : values()) { - map.put(error.errorMessage, error); - } - return map; - } - - } - - private final byte[] verifier; - private final Error error; - - /** - * Constructs a server-final-message with no errors, and the provided server verifier - * @param verifier The bytes of the computed signature - * @throws IllegalArgumentException If the verifier is null - */ - public ServerFinalMessage(byte[] verifier) throws IllegalArgumentException { - this.verifier = checkNotNull(verifier, "verifier"); - this.error = null; - } - - /** - * Constructs a server-final-message which represents a SCRAM error. - * @param error The error - * @throws IllegalArgumentException If the error is null - */ - public ServerFinalMessage(Error error) throws IllegalArgumentException { - this.error = checkNotNull(error, "error"); - this.verifier = null; - } - - /** - * Whether this server-final-message contains an error - * @return True if it contains an error, false if it contains a verifier - */ - public boolean isError() { - return null != error; - } - - @SuppressFBWarnings("EI_EXPOSE_REP") - public byte[] getVerifier() { - return verifier; - } - - public Error getError() { - return error; - } - - @Override - public StringBuffer writeTo(StringBuffer sb) { - return StringWritableCsv.writeTo( - sb, - isError() ? - new ScramAttributeValue(ScramAttributes.ERROR, error.errorMessage) - : new ScramAttributeValue( - ScramAttributes.SERVER_SIGNATURE, ScramStringFormatting.base64Encode(verifier) - ) - ); - } - - /** - * Parses a server-final-message from a String. - * @param serverFinalMessage The message - * @return A constructed server-final-message instance - * @throws ScramParseException If the argument is not a valid server-final-message - * @throws IllegalArgumentException If the message is null or empty - */ - public static ServerFinalMessage parseFrom(String serverFinalMessage) - throws ScramParseException, IllegalArgumentException { - checkNotEmpty(serverFinalMessage, "serverFinalMessage"); - - String[] attributeValues = StringWritableCsv.parseFrom(serverFinalMessage, 1, 0); - if(attributeValues == null || attributeValues.length != 1) { - throw new ScramParseException("Invalid server-final-message"); - } - - ScramAttributeValue attributeValue = ScramAttributeValue.parse(attributeValues[0]); - if(ScramAttributes.SERVER_SIGNATURE.getChar() == attributeValue.getChar()) { - byte[] verifier = ScramStringFormatting.base64Decode(attributeValue.getValue()); - return new ServerFinalMessage(verifier); - } else if(ScramAttributes.ERROR.getChar() == attributeValue.getChar()) { - return new ServerFinalMessage(Error.getByErrorMessage(attributeValue.getValue())); - } else { - throw new ScramParseException( - "Invalid server-final-message: it must contain either a verifier or an error attribute" - ); - } - } - - @Override - public String toString() { - return writeTo(new StringBuffer()).toString(); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/message/ServerFirstMessage.java b/common/src/main/java/com/ongres/scram/common/message/ServerFirstMessage.java deleted file mode 100644 index 08ba9dd..0000000 --- a/common/src/main/java/com/ongres/scram/common/message/ServerFirstMessage.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import com.ongres.scram.common.ScramAttributeValue; -import com.ongres.scram.common.ScramAttributes; -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.util.StringWritable; -import com.ongres.scram.common.util.StringWritableCsv; - -import static com.ongres.scram.common.util.Preconditions.checkArgument; -import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; - - -/** - * Constructs and parses server-first-messages. Formal syntax is: - * - * {@code - * server-first-message = [reserved-mext ","] nonce "," salt "," - * iteration-count ["," extensions] - * } - * - * Note that extensions are not supported. - * - * @see [RFC5802] Section 7 - */ -public class ServerFirstMessage implements StringWritable { - /** - * Minimum allowed value for the iteration, as per the RFC. - */ - public static final int ITERATION_MIN_VALUE = 4096; - - private final String clientNonce; - private final String serverNonce; - private final String salt; - private final int iteration; - - /** - * Constructs a server-first-message from a client-first-message and the additional required data. - * @param clientNonce String representing the client-first-message - * @param serverNonce Server serverNonce - * @param salt The salt - * @param iteration The iteration count (must be <= 4096) - * @throws IllegalArgumentException If clientFirstMessage, serverNonce or salt are null or empty, - * or iteration < 4096 - */ - public ServerFirstMessage( - String clientNonce, String serverNonce, String salt, int iteration - ) throws IllegalArgumentException { - this.clientNonce = checkNotEmpty(clientNonce, "clientNonce"); - this.serverNonce = checkNotEmpty(serverNonce, "serverNonce"); - this.salt = checkNotEmpty(salt, "salt"); - checkArgument(iteration >= ITERATION_MIN_VALUE, "iteration must be >= " + ITERATION_MIN_VALUE); - this.iteration = iteration; - } - - public String getClientNonce() { - return clientNonce; - } - - public String getServerNonce() { - return serverNonce; - } - - public String getNonce() { - return clientNonce + serverNonce; - } - - public String getSalt() { - return salt; - } - - public int getIteration() { - return iteration; - } - - @Override - public StringBuffer writeTo(StringBuffer sb) { - return StringWritableCsv.writeTo( - sb, - new ScramAttributeValue(ScramAttributes.NONCE, getNonce()), - new ScramAttributeValue(ScramAttributes.SALT, salt), - new ScramAttributeValue(ScramAttributes.ITERATION, iteration + "") - ); - } - - /** - * Parses a server-first-message from a String. - * @param serverFirstMessage The string representing the server-first-message - * @param clientNonce The serverNonce that is present in the client-first-message - * @return The parsed instance - * @throws ScramParseException If the argument is not a valid server-first-message - * @throws IllegalArgumentException If either argument is empty or serverFirstMessage is not a valid message - */ - public static ServerFirstMessage parseFrom(String serverFirstMessage, String clientNonce) - throws ScramParseException, IllegalArgumentException { - checkNotEmpty(serverFirstMessage, "serverFirstMessage"); - checkNotEmpty(clientNonce, "clientNonce"); - - String[] attributeValues = StringWritableCsv.parseFrom(serverFirstMessage, 3, 0); - if(attributeValues.length != 3) { - throw new ScramParseException("Invalid server-first-message"); - } - - ScramAttributeValue nonce = ScramAttributeValue.parse(attributeValues[0]); - if(ScramAttributes.NONCE.getChar() != nonce.getChar()) { - throw new ScramParseException("serverNonce must be the 1st element of the server-first-message"); - } - if(! nonce.getValue().startsWith(clientNonce)) { - throw new ScramParseException("parsed serverNonce does not start with client serverNonce"); - } - - ScramAttributeValue salt = ScramAttributeValue.parse(attributeValues[1]); - if(ScramAttributes.SALT.getChar() != salt.getChar()) { - throw new ScramParseException("salt must be the 2nd element of the server-first-message"); - } - - ScramAttributeValue iteration = ScramAttributeValue.parse(attributeValues[2]); - if(ScramAttributes.ITERATION.getChar() != iteration.getChar()) { - throw new ScramParseException("iteration must be the 3rd element of the server-first-message"); - } - - int iterationInt; - try { - iterationInt = Integer.parseInt(iteration.getValue()); - } catch (NumberFormatException e) { - throw new ScramParseException("invalid iteration"); - } - - return new ServerFirstMessage( - clientNonce, nonce.getValue().substring(clientNonce.length()), salt.getValue(), iterationInt - ); - } - - @Override - public String toString() { - return writeTo(new StringBuffer()).toString(); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparation.java b/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparation.java deleted file mode 100644 index 941a0ee..0000000 --- a/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparation.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.stringprep; - - -/** - * Interface for all possible String Preparations mechanisms. - */ -public interface StringPreparation { - /** - * Normalize a UTF-8 String according to this String Preparation rules. - * @param value The String to prepare - * @return The prepared String - * @throws IllegalArgumentException If the String to prepare is not valid. - */ - String normalize(String value) throws IllegalArgumentException; -} diff --git a/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparations.java b/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparations.java deleted file mode 100644 index 7270bf0..0000000 --- a/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparations.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.stringprep; - - -import com.ongres.saslprep.SaslPrep; -import com.ongres.scram.common.util.UsAsciiUtils; - -import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; - -public enum StringPreparations implements StringPreparation { - /** - * Implementation of StringPreparation that performs no preparation. - * Non US-ASCII characters will produce an exception. - * Even though the [RFC5802] is not very clear about it, - * this implementation will normalize non-printable US-ASCII characters similarly to what SaslPrep does - * (i.e., removing them). - */ - NO_PREPARATION { - @Override - protected String doNormalize(String value) throws IllegalArgumentException { - return UsAsciiUtils.toPrintable(value); - } - }, - /** - * Implementation of StringPreparation that performs preparation. - * Non US-ASCII characters will produce an exception. - * Even though the [RFC5802] is not very clear about it, - * this implementation will normalize as SaslPrep does. - */ - SASL_PREPARATION { - @Override - protected String doNormalize(String value) throws IllegalArgumentException { - return SaslPrep.saslPrep(value, true); - } - } - ; - - protected abstract String doNormalize(String value) throws IllegalArgumentException; - - public String normalize(String value) throws IllegalArgumentException { - checkNotEmpty(value, "value"); - - String normalized = doNormalize(value); - - if(null == normalized || normalized.isEmpty()) { - throw new IllegalArgumentException("null or empty value after normalization"); - } - - return normalized; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/util/AbstractCharAttributeValue.java b/common/src/main/java/com/ongres/scram/common/util/AbstractCharAttributeValue.java deleted file mode 100644 index b554208..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/AbstractCharAttributeValue.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * Construct and write generic CharAttribute-Value pairs. - * - * Concrete sub-classes should also provide a static parse(String) creation method. - */ -public class AbstractCharAttributeValue extends AbstractStringWritable implements CharAttributeValue { - private final CharAttribute charAttribute; - private final String value; - - public AbstractCharAttributeValue(CharAttribute charAttribute, String value) throws IllegalArgumentException { - this.charAttribute = checkNotNull(charAttribute, "attribute"); - if(null != value && value.isEmpty()) { - throw new IllegalArgumentException("Value should be either null or non-empty"); - } - this.value = value; - } - - @Override - public char getChar() { - return charAttribute.getChar(); - } - - @Override - public String getValue() { - return value; - } - - @Override - public StringBuffer writeTo(StringBuffer sb) { - sb.append(charAttribute.getChar()); - - if(null != value) { - sb.append('=').append(value); - } - - return sb; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/util/AbstractStringWritable.java b/common/src/main/java/com/ongres/scram/common/util/AbstractStringWritable.java deleted file mode 100644 index ec53ef4..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/AbstractStringWritable.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -/** - * Basic implementation of the StringWritable interface, that overrides the toString() method. - */ -public abstract class AbstractStringWritable implements StringWritable { - public String toString() { - return writeTo(new StringBuffer()).toString(); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/util/CharAttribute.java b/common/src/main/java/com/ongres/scram/common/util/CharAttribute.java deleted file mode 100644 index e1ccce8..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/CharAttribute.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -/** - * Represents an attribute (a key name) that is represented by a single char. - */ -public interface CharAttribute { - /** - * Return the char used to represent this attribute - * @return The character of the attribute - */ - char getChar(); -} diff --git a/common/src/main/java/com/ongres/scram/common/util/CharAttributeValue.java b/common/src/main/java/com/ongres/scram/common/util/CharAttributeValue.java deleted file mode 100644 index 4320353..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/CharAttributeValue.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -/** - * Augments a {@link CharAttribute} with a String value and the method(s) to write its data to a StringBuffer. - */ -public interface CharAttributeValue extends CharAttribute, StringWritable { - /** - * Returns the value associated with the {@link CharAttribute} - * @return The String value or null if no value is associated - */ - String getValue(); -} diff --git a/common/src/main/java/com/ongres/scram/common/util/CryptoUtil.java b/common/src/main/java/com/ongres/scram/common/util/CryptoUtil.java deleted file mode 100644 index 009364b..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/CryptoUtil.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; -import java.security.InvalidKeyException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; - -import static com.ongres.scram.common.util.Preconditions.checkArgument; -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * Utility static methods for cryptography related tasks. - */ -public class CryptoUtil { - private static final int MIN_ASCII_PRINTABLE_RANGE = 0x21; - private static final int MAX_ASCII_PRINTABLE_RANGE = 0x7e; - private static final int EXCLUDED_CHAR = (int) ','; // 0x2c - - private static class SecureRandomHolder { - private static final SecureRandom INSTANCE = new SecureRandom(); - } - - /** - * Generates a random string (called a 'nonce'), composed of ASCII printable characters, except comma (','). - * @param size The length of the nonce, in characters/bytes - * @param random The SecureRandom to use - * @return The String representing the nonce - */ - public static String nonce(int size, SecureRandom random) { - if(size <= 0) { - throw new IllegalArgumentException("Size must be positive"); - } - - char[] chars = new char[size]; - int r; - for(int i = 0; i < size;) { - r = random.nextInt(MAX_ASCII_PRINTABLE_RANGE - MIN_ASCII_PRINTABLE_RANGE + 1) + MIN_ASCII_PRINTABLE_RANGE; - if(r != EXCLUDED_CHAR) { - chars[i++] = (char) r; - } - } - - return new String(chars); - } - - /** - * Generates a random string (called a 'nonce'), composed of ASCII printable characters, except comma (','). - * It uses a default SecureRandom instance. - * @param size The length of the nonce, in characters/bytes - * @return The String representing the nonce - */ - public static String nonce(int size) { - return nonce(size, SecureRandomHolder.INSTANCE); - } - - /** - * Compute the "Hi" function for SCRAM. - * - * {@code - * Hi(str, salt, i): - * - * U1 := HMAC(str, salt + INT(1)) - * U2 := HMAC(str, U1) - * ... - * Ui-1 := HMAC(str, Ui-2) - * Ui := HMAC(str, Ui-1) - * - * Hi := U1 XOR U2 XOR ... XOR Ui - * - * where "i" is the iteration count, "+" is the string concatenation - * operator, and INT(g) is a 4-octet encoding of the integer g, most - * significant octet first. - * - * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the - * pseudorandom function (PRF) and with dkLen == output length of - * HMAC() == output length of H(). - * } - * - * @param secretKeyFactory The SecretKeyFactory to generate the SecretKey - * @param keyLength The length of the key (in bits) - * @param value The char array to compute the Hi function - * @param salt The salt - * @param iterations The number of iterations - * @return The bytes of the computed Hi value - */ - public static byte[] hi( - SecretKeyFactory secretKeyFactory, int keyLength, char[] value, byte[] salt, int iterations - ) { - try { - PBEKeySpec spec = new PBEKeySpec(value, salt, iterations, keyLength); - SecretKey key = secretKeyFactory.generateSecret(spec); - return key.getEncoded(); - } catch(InvalidKeySpecException e) { - throw new RuntimeException("Platform error: unsupported PBEKeySpec"); - } - } - - /** - * Computes the HMAC of a given message. - * - * {@code - * HMAC(key, str): Apply the HMAC keyed hash algorithm (defined in - * [RFC2104]) using the octet string represented by "key" as the key - * and the octet string "str" as the input string. The size of the - * result is the hash result size for the hash function in use. For - * example, it is 20 octets for SHA-1 (see [RFC3174]). - * } - * - * @param secretKeySpec A key of the given algorithm - * @param mac A MAC instance of the given algorithm - * @param message The message to compute the HMAC - * @return The bytes of the computed HMAC value - */ - public static byte[] hmac(SecretKeySpec secretKeySpec, Mac mac, byte[] message) { - try { - mac.init(secretKeySpec); - } catch (InvalidKeyException e) { - throw new RuntimeException("Platform error: unsupported key for HMAC algorithm"); - } - - return mac.doFinal(message); - } - - /** - * Computes a byte-by-byte xor operation. - * - * {@code - * XOR: Apply the exclusive-or operation to combine the octet string - * on the left of this operator with the octet string on the right of - * this operator. The length of the output and each of the two - * inputs will be the same for this use. - * } - * - * @param value1 - * @param value2 - * @return - * @throws IllegalArgumentException - */ - public static byte[] xor(byte[] value1, byte[] value2) throws IllegalArgumentException { - checkNotNull(value1, "value1"); - checkNotNull(value2, "value2"); - checkArgument(value1.length == value2.length, "Both values must have the same length"); - - byte[] result = new byte[value1.length]; - for(int i = 0; i < value1.length; i++) { - result[i] = (byte) (value1[i] ^ value2[i]); - } - - return result; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/util/Preconditions.java b/common/src/main/java/com/ongres/scram/common/util/Preconditions.java deleted file mode 100644 index 9cd4e46..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/Preconditions.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -/** - * Simple methods similar to Precondition class. Avoid importing full library. - */ -public class Preconditions { - /** - * Checks that the argument is not null. - * @param value The value to be checked - * @param valueName The name of the value that is checked in the method - * @param The type of the value - * @return The same value passed as argument - * @throws IllegalArgumentException If value is null - */ - public static T checkNotNull(T value, String valueName) throws IllegalArgumentException { - if(null == value) { - throw new IllegalArgumentException("Null value for '" + valueName + "'"); - } - - return value; - } - - /** - * Checks that the String is not null and not empty - * @param value The String to check - * @param valueName The name of the value that is checked in the method - * @return The same String passed as argument - * @throws IllegalArgumentException If value is null or empty - */ - public static String checkNotEmpty(String value, String valueName) throws IllegalArgumentException { - if(checkNotNull(value, valueName).isEmpty()) { - throw new IllegalArgumentException("Empty string '" + valueName + "'"); - } - - return value; - } - - /** - * Checks that the argument is valid, based in a check boolean condition. - * @param check The boolean check - * @param valueName The name of the value that is checked in the method - * @throws IllegalArgumentException - */ - public static void checkArgument(boolean check, String valueName) throws IllegalArgumentException { - if(! check) { - throw new IllegalArgumentException("Argument '" + valueName + "' is not valid"); - } - } - - /** - * Checks that the integer argument is positive. - * @param value The value to be checked - * @param valueName The name of the value that is checked in the method - * @return The same value passed as argument - * @throws IllegalArgumentException If value is null - */ - public static int gt0(int value, String valueName) throws IllegalArgumentException { - if(value <= 0) { - throw new IllegalArgumentException("'" + valueName + "' must be positive"); - } - - return value; - } -} diff --git a/common/src/main/java/com/ongres/scram/common/util/StringWritable.java b/common/src/main/java/com/ongres/scram/common/util/StringWritable.java deleted file mode 100644 index 25d9428..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/StringWritable.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -/** - * Interface to denote classes which can write to a StringBuffer. - */ -public interface StringWritable { - /** - * Write the class information to the given StringBuffer. - * @param sb Where to write the data. - * @return The same StringBuffer. - */ - StringBuffer writeTo(StringBuffer sb); -} diff --git a/common/src/main/java/com/ongres/scram/common/util/StringWritableCsv.java b/common/src/main/java/com/ongres/scram/common/util/StringWritableCsv.java deleted file mode 100644 index d47aa01..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/StringWritableCsv.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import java.util.Arrays; - -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -/** - * Helper class to generate Comma Separated Values of {@link StringWritable}s - */ -public class StringWritableCsv { - private static void writeStringWritableToStringBuffer(StringWritable value, StringBuffer sb) { - if(null != value) { - value.writeTo(sb); - } - } - - /** - * Write a sequence of {@link StringWritableCsv}s to a StringBuffer. - * Null {@link StringWritable}s are not printed, but separator is still used. - * Separator is a comma (',') - * @param sb The sb to write to - * @param values Zero or more attribute-value pairs to write - * @return The same sb, with data filled in (if any) - * @throws IllegalArgumentException If sb is null - */ - public static StringBuffer writeTo(StringBuffer sb, StringWritable... values) throws IllegalArgumentException { - checkNotNull(sb, "sb"); - if(null == values || values.length == 0) { - return sb; - } - - writeStringWritableToStringBuffer(values[0], sb); - int i = 1; - while (i < values.length) { - sb.append(','); - writeStringWritableToStringBuffer(values[i], sb); - i++; - } - - return sb; - } - - /** - * Parse a String with a {@link StringWritableCsv} into its composing Strings - * represented as Strings. No validation is performed on the individual attribute-values returned. - * @param value The String with the set of attribute-values - * @param n Number of entries to return (entries will be null of there were not enough). 0 means unlimited - * @param offset How many entries to skip before start returning - * @return An array of Strings which represent the individual attribute-values - * @throws IllegalArgumentException If value is null or either n or offset are negative - */ - public static String[] parseFrom(String value, int n, int offset) throws IllegalArgumentException { - checkNotNull(value, "value"); - if(n < 0 || offset < 0) { - throw new IllegalArgumentException("Limit and offset have to be >= 0"); - } - - if(value.isEmpty()) { - return new String[0]; - } - - String[] split = value.split(","); - if(split.length < offset) { - throw new IllegalArgumentException("Not enough items for the given offset"); - } - - return Arrays.copyOfRange( - split, - offset, - (n == 0 ? split.length : n) + offset - ); - } - - /** - * Parse a String with a {@link StringWritableCsv} into its composing Strings - * represented as Strings. No validation is performed on the individual attribute-values returned. - * Elements are returned starting from the first available attribute-value. - * @param value The String with the set of attribute-values - * @param n Number of entries to return (entries will be null of there were not enough). 0 means unlimited - * @return An array of Strings which represent the individual attribute-values - * @throws IllegalArgumentException If value is null or n is negative - */ - public static String[] parseFrom(String value, int n) throws IllegalArgumentException { - return parseFrom(value, n, 0); - } - - /** - * Parse a String with a {@link StringWritableCsv} into its composing Strings - * represented as Strings. No validation is performed on the individual attribute-values returned. - * All the available attribute-values will be returned. - * @param value The String with the set of attribute-values - * @return An array of Strings which represent the individual attribute-values - * @throws IllegalArgumentException If value is null - */ - public static String[] parseFrom(String value) throws IllegalArgumentException{ - return parseFrom(value, 0, 0); - } -} diff --git a/common/src/main/java/com/ongres/scram/common/util/UsAsciiUtils.java b/common/src/main/java/com/ongres/scram/common/util/UsAsciiUtils.java deleted file mode 100644 index 43fc42c..0000000 --- a/common/src/main/java/com/ongres/scram/common/util/UsAsciiUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import static com.ongres.scram.common.util.Preconditions.checkNotNull; - - -public class UsAsciiUtils { - /** - * Removes non-printable characters from the US-ASCII String. - * @param value The original String - * @return The possibly modified String, without non-printable US-ASCII characters. - * @throws IllegalArgumentException If the String is null or contains non US-ASCII characters. - */ - public static String toPrintable(String value) throws IllegalArgumentException { - checkNotNull(value, "value"); - - char[] printable = new char[value.length()]; - int i = 0; - for(char chr : value.toCharArray()) { - int c = (int) chr; - if (c < 0 || c >= 127) { - throw new IllegalArgumentException("value contains character '" + chr + "' which is non US-ASCII"); - } else if (c > 32) { - printable[i++] = chr; - } - } - - return i == value.length() ? value : new String(printable, 0, i); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java b/common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java deleted file mode 100644 index c0c3f97..0000000 --- a/common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -public class RfcExampleSha1 { - public static final String USER = "user"; - public static final String PASSWORD = "pencil"; - public static final String CLIENT_NONCE = "fyko+d2lbbFgONRv9qkxdawL"; - public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = "n=" + USER + ",r=" + CLIENT_NONCE; - public static final String CLIENT_FIRST_MESSAGE = "n," + "," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; - public static final String SERVER_SALT = "QSXCR+Q6sek8bf92"; - public static final int SERVER_ITERATIONS = 4096; - public static final String SERVER_NONCE = "3rfcNHYJY1ZVvWVs7j"; - public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; - public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT - + ",i=" + SERVER_ITERATIONS; - public static final String GS2_HEADER_BASE64 = "biws"; - public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 - + ",r=" + FULL_NONCE; - public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," - + SERVER_FIRST_MESSAGE + "," - + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; - public static final String CLIENT_FINAL_MESSAGE_PROOF = "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="; - public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF - + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; - public static final String SERVER_FINAL_MESSAGE = "v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; -} diff --git a/common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java b/common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java deleted file mode 100644 index be05cd3..0000000 --- a/common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -public class RfcExampleSha256 { - public static final String USER = "user"; - public static final String PASSWORD = "pencil"; - public static final String CLIENT_NONCE = "rOprNGfwEbeRWgbNEkqO"; - public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = "n=" + USER + ",r=" + CLIENT_NONCE; - public static final String CLIENT_FIRST_MESSAGE = "n," + "," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; - public static final String SERVER_SALT = "W22ZaJ0SNY7soEsUEjb6gQ=="; - public static final int SERVER_ITERATIONS = 4096; - public static final String SERVER_NONCE = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0"; - public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; - public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT - + ",i=" + SERVER_ITERATIONS; - public static final String GS2_HEADER_BASE64 = "biws"; - public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 - + ",r=" + FULL_NONCE; - public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," - + SERVER_FIRST_MESSAGE + "," - + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; - public static final String CLIENT_FINAL_MESSAGE_PROOF = "dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ="; - public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF - + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; - public static final String SERVER_FINAL_MESSAGE = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4="; -} diff --git a/common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java b/common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java deleted file mode 100644 index 1046396..0000000 --- a/common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.message.ServerFinalMessage; -import org.junit.Test; - -import static com.ongres.scram.common.RfcExampleSha1.*; -import static com.ongres.scram.common.ScramAttributes.CLIENT_PROOF; -import static com.ongres.scram.common.ScramAttributes.USERNAME; -import static org.junit.Assert.*; - - -public class ScramAttributeValueTest { - @Test - public void constructorDoesNotAllowNullValue() { - try { - assertNotNull(new ScramAttributeValue(USERNAME, null)); - } catch(IllegalArgumentException e) { - return; - } - - fail("A null value must throw an IllegalArgumentException"); - } - - @Test - public void parseIllegalValuesStructure() { - String[] values = new String[] { - null, "", "asdf", "asdf=a", CLIENT_PROOF.getChar() + "=", CLIENT_PROOF.getChar() + ",a" - }; - int n = 0; - for(String value : values) { - try { - assertNotNull(ScramAttributeValue.parse(value)); - } catch(ScramParseException e) { - n++; - } - } - - assertEquals("Not every illegal value thrown ScramParseException", values.length, n); - } - - @Test - public void parseIllegalValuesInvalidSCRAMAttibute() { - // SCRAM allows for extensions. If a new attribute is supported and its value has been used below, - // test will fail and will need to be fixed - String[] values = new String[] { "z=asdfasdf", "!=value" }; - - int n = 0; - for(String value : values) { - try { - assertNotNull(ScramAttributeValue.parse(value)); - } catch(ScramParseException e) { - n++; - } - } - - assertEquals("Not every illegal value thrown ScramParseException", values.length, n); - } - - @Test - public void parseLegalValues() throws ScramParseException { - String[] legalValues = new String[] { - CLIENT_PROOF.getChar() + "=" + "proof", - USERNAME.getChar() + "=" + "username", - "n=" + USER, - "r=" + CLIENT_NONCE, - "r=" + FULL_NONCE, - "s=" + SERVER_SALT, - "i=" + SERVER_ITERATIONS, - "c=" + GS2_HEADER_BASE64, - "p=" + CLIENT_FINAL_MESSAGE_PROOF, - SERVER_FINAL_MESSAGE, - }; - for(String value : legalValues) { - assertNotNull(ScramAttributeValue.parse(value)); - } - - // Test all possible error messages - for(ServerFinalMessage.Error e : ServerFinalMessage.Error.values()) { - assertNotNull(ScramAttributeValue.parse("e=" + e.getErrorMessage())); - } - } -} diff --git a/common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java b/common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java deleted file mode 100644 index 275f00a..0000000 --- a/common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import com.ongres.scram.common.bouncycastle.base64.Base64; -import com.ongres.scram.common.stringprep.StringPreparations; -import org.junit.Test; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - - -public class ScramFunctionsTest { - private void assertBytesEqualsBase64(String expected, byte[] actual) { - assertArrayEquals(Base64.decode(expected), actual); - } - - @Test - public void hmac() throws UnsupportedEncodingException { - String message = "The quick brown fox jumps over the lazy dog"; - byte[] key = "key".getBytes(StandardCharsets.UTF_8); - - assertBytesEqualsBase64( - "3nybhbi3iqa8ino29wqQcBydtNk=", - ScramFunctions.hmac(ScramMechanisms.SCRAM_SHA_1, message.getBytes(StandardCharsets.US_ASCII), key) - ); - assertBytesEqualsBase64( - "97yD9DBThCSxMpjmqm+xQ+9NWaFJRhdZl0edvC0aPNg=", - ScramFunctions.hmac(ScramMechanisms.SCRAM_SHA_256, message.getBytes(StandardCharsets.US_ASCII), key) - ); - } - - private byte[] generateSaltedPassword() { - return ScramFunctions.saltedPassword( - ScramMechanisms.SCRAM_SHA_1, StringPreparations.NO_PREPARATION, "pencil", - Base64.decode("QSXCR+Q6sek8bf92"), 4096 - ); - } - - private byte[] generateSaltedPasswordSha256() { - return ScramFunctions.saltedPassword( - ScramMechanisms.SCRAM_SHA_256, StringPreparations.NO_PREPARATION, "pencil", - Base64.decode("W22ZaJ0SNY7soEsUEjb6gQ=="), 4096 - ); - } - - @Test - public void saltedPassword() { - assertBytesEqualsBase64("HZbuOlKbWl+eR8AfIposuKbhX30=", generateSaltedPassword()); - } - - @Test - public void saltedPasswordWithSaslPrep() { - assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( - ScramMechanisms.SCRAM_SHA_1, StringPreparations.SASL_PREPARATION, "\u2168\u3000a\u0300", - Base64.decode("0BojBCBE6P2/N4bQ"), 6400 - )); - assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( - ScramMechanisms.SCRAM_SHA_1, StringPreparations.SASL_PREPARATION, "\u00ADIX \u00E0", - Base64.decode("0BojBCBE6P2/N4bQ"), 6400 - )); - assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( - ScramMechanisms.SCRAM_SHA_1, StringPreparations.SASL_PREPARATION, "IX \u00E0", - Base64.decode("0BojBCBE6P2/N4bQ"), 6400 - )); - assertBytesEqualsBase64("HZbuOlKbWl+eR8AfIposuKbhX30=", ScramFunctions.saltedPassword( - ScramMechanisms.SCRAM_SHA_1, StringPreparations.SASL_PREPARATION, "\u0070enc\u1806il", - Base64.decode("QSXCR+Q6sek8bf92"), 4096 - )); - try { - ScramFunctions.saltedPassword( - ScramMechanisms.SCRAM_SHA_1, StringPreparations.SASL_PREPARATION, "\u2168\u3000a\u0300\u0007", - Base64.decode("QSXCR+Q6sek8bf92"), 6400); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Prohibited character \u0007", e.getMessage()); - } - } - - @Test - public void saltedPasswordSha256() { - assertBytesEqualsBase64("xKSVEDI6tPlSysH6mUQZOeeOp01r6B3fcJbodRPcYV0=", generateSaltedPasswordSha256()); - } - - private byte[] generateClientKey() { - return ScramFunctions.clientKey(ScramMechanisms.SCRAM_SHA_1, generateSaltedPassword()); - } - - private byte[] generateClientKeySha256() { - return ScramFunctions.clientKey(ScramMechanisms.SCRAM_SHA_256, generateSaltedPasswordSha256()); - } - - @Test - public void clientKey() { - assertBytesEqualsBase64("4jTEe/bDZpbdbYUrmaqiuiZVVyg=", generateClientKey()); - } - - @Test - public void clientKeySha256() { - assertBytesEqualsBase64("pg/JI9Z+hkSpLRa5btpe9GVrDHJcSEN0viVTVXaZbos=", generateClientKeySha256()); - } - - private byte[] generateStoredKey() { - return ScramFunctions.storedKey(ScramMechanisms.SCRAM_SHA_1, generateClientKey()); - } - - private byte[] generateStoredKeySha256() { - return ScramFunctions.storedKey(ScramMechanisms.SCRAM_SHA_256, generateClientKeySha256()); - } - - @Test - public void storedKey() { - assertBytesEqualsBase64("6dlGYMOdZcOPutkcNY8U2g7vK9Y=", generateStoredKey()); - } - - @Test - public void storedKeySha256() { - assertBytesEqualsBase64("WG5d8oPm3OtcPnkdi4Uo7BkeZkBFzpcXkuLmtbsT4qY=", generateStoredKeySha256()); - } - - private byte[] generateServerKey() { - return ScramFunctions.serverKey(ScramMechanisms.SCRAM_SHA_1, generateSaltedPassword()); - } - - private byte[] generateServerKeySha256() { - return ScramFunctions.serverKey(ScramMechanisms.SCRAM_SHA_256, generateSaltedPasswordSha256()); - } - - @Test - public void serverKey() { - assertBytesEqualsBase64("D+CSWLOshSulAsxiupA+qs2/fTE=", generateServerKey()); - } - - @Test - public void serverKeySha256() { - assertBytesEqualsBase64("wfPLwcE6nTWhTAmQ7tl2KeoiWGPlZqQxSrmfPwDl2dU=", generateServerKeySha256()); - } - - private byte[] generateClientSignature() { - return ScramFunctions.clientSignature(ScramMechanisms.SCRAM_SHA_1, generateStoredKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE); - } - - private byte[] generateClientSignatureSha256() { - return ScramFunctions.clientSignature(ScramMechanisms.SCRAM_SHA_256, generateStoredKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE); - } - - @Test - public void clientSignature() { - assertBytesEqualsBase64("XXE4xIawv6vfSePi2ovW5cedthM=", generateClientSignature()); - } - - @Test - public void clientSignatureSha256() { - assertBytesEqualsBase64("0nMSRnwopAqKfwXHPA3jPrPL+0qDeDtYFEzxmsa+G98=", generateClientSignatureSha256()); - } - - private byte[] generateClientProof() { - return ScramFunctions.clientProof(generateClientKey(), generateClientSignature()); - } - - private byte[] generateClientProofSha256() { - return ScramFunctions.clientProof(generateClientKeySha256(), generateClientSignatureSha256()); - } - - @Test - public void clientProof() { - assertBytesEqualsBase64("v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", generateClientProof()); - } - - @Test - public void clientProofSha256() { - assertBytesEqualsBase64("dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=", generateClientProofSha256()); - } - - private byte[] generateServerSignature() { - return ScramFunctions.serverSignature(ScramMechanisms.SCRAM_SHA_1, generateServerKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE); - } - - private byte[] generateServerSignatureSha256() { - return ScramFunctions.serverSignature(ScramMechanisms.SCRAM_SHA_256, generateServerKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE); - } - - @Test - public void serverSignature() { - assertBytesEqualsBase64("rmF9pqV8S7suAoZWja4dJRkFsKQ=", generateServerSignature()); - } - - @Test - public void serverSignatureSha256() { - assertBytesEqualsBase64("6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=", generateServerSignatureSha256()); - } - - @Test - public void verifyClientProof() { - assertTrue( - ScramFunctions.verifyClientProof( - ScramMechanisms.SCRAM_SHA_1, generateClientProof(), generateStoredKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE - ) - ); - } - - @Test - public void verifyClientProofSha256() { - assertTrue( - ScramFunctions.verifyClientProof( - ScramMechanisms.SCRAM_SHA_256, generateClientProofSha256(), generateStoredKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE - ) - ); - } - - @Test - public void verifyServerSignature() { - assertTrue( - ScramFunctions.verifyServerSignature( - ScramMechanisms.SCRAM_SHA_1, generateServerKey(), com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE, generateServerSignature() - ) - ); - } - - @Test - public void verifyServerSignatureSha256() { - assertTrue( - ScramFunctions.verifyServerSignature( - ScramMechanisms.SCRAM_SHA_256, generateServerKeySha256(), com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE, generateServerSignatureSha256() - ) - ); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/ScramMechanismsTest.java b/common/src/test/java/com/ongres/scram/common/ScramMechanismsTest.java deleted file mode 100644 index 8271604..0000000 --- a/common/src/test/java/com/ongres/scram/common/ScramMechanismsTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import org.junit.Test; - - -public class ScramMechanismsTest { - @Test - public void TestHashSupportedByJVM() { - byte[] digest; - for(ScramMechanisms scramMechanism : ScramMechanisms.values()) { - digest = scramMechanism.digest(new byte[0]); - assertNotNull("got a null digest", digest); - } - } - - @Test - public void TestHMACSupportedByJVM() { - byte[] hmac; - for(ScramMechanisms scramMechanism : ScramMechanisms.values()) { - hmac = scramMechanism.hmac(new byte[] { 0 }, new byte[0]); - assertNotNull("got a null HMAC", hmac); - } - } - - private interface Predicate { - boolean test(T t); - } - - - private void testNames(String[] names, Predicate predicate) { - int count = 0; - for (String name : names) { - if (predicate.test(ScramMechanisms.byName(name))) { - count++; - } - } - assertEquals( - names.length, - count - ); - } - - @Test - public void byNameValid() { - testNames( - new String[] { "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" }, - new Predicate() { - @Override - public boolean test(ScramMechanisms scramMechanisms) { - return scramMechanisms != null; - } - } - ); - } - - @Test - public void byNameInvalid() { - testNames( - new String[] { "SCRAM-SHA", "SHA-1-PLUS", "SCRAM-SHA-256-", "SCRAM-SHA-256-PLUS!" }, - new Predicate() { - @Override - public boolean test(ScramMechanisms scramMechanisms) { - return scramMechanisms == null; - } - } - ); - } - - private void selectMatchingMechanismTest(ScramMechanisms scramMechanisms, boolean channelBinding, String... names) { - assertEquals( - scramMechanisms, ScramMechanisms.selectMatchingMechanism(channelBinding, names) - ); - } - - @Test - public void selectMatchingMechanism() { - selectMatchingMechanismTest( - ScramMechanisms.SCRAM_SHA_1, false, - "SCRAM-SHA-1" - ); - selectMatchingMechanismTest( - ScramMechanisms.SCRAM_SHA_256_PLUS, true, - "SCRAM-SHA-256-PLUS" - ); - selectMatchingMechanismTest( - ScramMechanisms.SCRAM_SHA_256, false, - "SCRAM-SHA-1", "SCRAM-SHA-256" - ); - selectMatchingMechanismTest( - ScramMechanisms.SCRAM_SHA_256, false, - "SCRAM-SHA-1", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" - ); - selectMatchingMechanismTest( - ScramMechanisms.SCRAM_SHA_1_PLUS, true, - "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256" - ); - selectMatchingMechanismTest( - ScramMechanisms.SCRAM_SHA_256_PLUS, true, - "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" - ); - selectMatchingMechanismTest( - null, true, - "SCRAM-SHA-1", "SCRAM-SHA-256" - ); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java b/common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java deleted file mode 100644 index 9b15d3b..0000000 --- a/common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common; - - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - - -public class ScramStringFormattingTest { - private static final String[] VALUES_NO_CHARS_TO_BE_ESCAPED = new String[] { "asdf", "''--%%21", " ttt???" }; - private static final String[] VALUES_TO_BE_ESCAPED = new String[] { - ",", "=", "a,b", "===", "a=", ",=,", "=2C", "=3D" - }; - private static final String[] ESCAPED_VALUES = new String[] { - "=2C", "=3D", "a=2Cb", "=3D=3D=3D", "a=3D", "=2C=3D=2C", "=3D2C", "=3D3D" - }; - private static final String[] INVALID_SASL_NAMES = new String[] { "=", "as,df", "a=b", " ttt???=2D" }; - - @Test - public void toSaslNameNoCharactersToBeEscaped() { - for(String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { - assertEquals(s, ScramStringFormatting.toSaslName(s)); - } - } - - @Test - public void toSaslNameWithCharactersToBeEscaped() { - for(int i = 0; i < VALUES_TO_BE_ESCAPED.length; i++) { - assertEquals(ESCAPED_VALUES[i], ScramStringFormatting.toSaslName(VALUES_TO_BE_ESCAPED[i])); - } - } - - @Test - public void fromSaslNameNoCharactersToBeEscaped() { - for(String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { - assertEquals(s, ScramStringFormatting.fromSaslName(s)); - } - } - - @Test - public void fromSaslNameWithCharactersToBeUnescaped() { - for(int i = 0; i < ESCAPED_VALUES.length; i++) { - assertEquals(VALUES_TO_BE_ESCAPED[i], ScramStringFormatting.fromSaslName(ESCAPED_VALUES[i])); - } - } - - @Test - public void fromSaslNameWithInvalidCharacters() { - int n = 0; - for(String s : INVALID_SASL_NAMES) { - try { - assertEquals(s, ScramStringFormatting.fromSaslName(s)); - } catch (IllegalArgumentException e) { - n++; - } - } - - assertTrue("Not all values produced IllegalArgumentException", n == INVALID_SASL_NAMES.length); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/gssapi/Gs2AttributeValueTest.java b/common/src/test/java/com/ongres/scram/common/gssapi/Gs2AttributeValueTest.java deleted file mode 100644 index 2bd6ecf..0000000 --- a/common/src/test/java/com/ongres/scram/common/gssapi/Gs2AttributeValueTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.gssapi; - - -import org.junit.Test; - -import static org.junit.Assert.*; - - -public class Gs2AttributeValueTest { - @Test - public void constructorAllowsNullValue() { - try { - assertNotNull(new Gs2AttributeValue(Gs2Attributes.CHANNEL_BINDING_REQUIRED, null)); - } catch(IllegalArgumentException e) { - fail("A null value is valid and cannot throw an IllegalArgumentException"); - } - } - - @Test - public void parseNullValue() { - assertNull(Gs2AttributeValue.parse(null)); - } - - @Test - public void parseIllegalValuesStructure() { - String[] values = new String[] { "", "as", "asdfjkl", Gs2Attributes.CHANNEL_BINDING_REQUIRED.getChar() + "=" }; - int n = 0; - for(String value : values) { - try { - assertNotNull(Gs2AttributeValue.parse(value)); - } catch(IllegalArgumentException e) { - n++; - } - } - - assertEquals("Not every illegal value thrown IllegalArgumentException", values.length, n); - } - - @Test - public void parseIllegalValuesInvalidGS2Attibute() { - String[] values = new String[] { "z=asdfasdf", "i=value" }; - - int n = 0; - for(String value : values) { - try { - assertNotNull(Gs2AttributeValue.parse(value)); - } catch(IllegalArgumentException e) { - n++; - } - } - - assertEquals("Not every illegal value thrown IllegalArgumentException", values.length, n); - } - - @Test - public void parseLegalValues() { - String[] values = new String[] { "n", "y", "p=value", "a=authzid" }; - for(String value : values) { - assertNotNull(Gs2AttributeValue.parse(value)); - } - } -} diff --git a/common/src/test/java/com/ongres/scram/common/gssapi/Gs2HeaderTest.java b/common/src/test/java/com/ongres/scram/common/gssapi/Gs2HeaderTest.java deleted file mode 100644 index a195ea8..0000000 --- a/common/src/test/java/com/ongres/scram/common/gssapi/Gs2HeaderTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.gssapi; - - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - - -public class Gs2HeaderTest { - private static final String[] VALID_GS2HEADER_STRINGS = new String[] { - "n,", "y,", "n,a=blah", "p=cb,", "p=cb,a=b" - }; - private static final Gs2Header[] VALID_GS_2_HEADERS = new Gs2Header[] { - new Gs2Header(Gs2CbindFlag.CLIENT_NOT), - new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT), - new Gs2Header(Gs2CbindFlag.CLIENT_NOT, null, "blah"), - new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "cb"), - new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "cb", "b") - }; - - private void assertGS2Header(String expected, Gs2Header gs2Header) { - assertEquals(expected, gs2Header.writeTo(new StringBuffer()).toString()); - } - - @Test - public void constructorValid() { - for(int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { - assertGS2Header(VALID_GS2HEADER_STRINGS[i], VALID_GS_2_HEADERS[i]); - } - } - - @Test(expected = IllegalArgumentException.class) - public void constructorInvalid1() { - new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorInvalid2() { - new Gs2Header(Gs2CbindFlag.CLIENT_NOT, "blah"); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorInvalid3() { - new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, "blah"); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorInvalid4() { - new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, "b"); - } - - @Test - public void parseFromInvalid() { - String[] invalids = new String[] { "Z,", "n,Z=blah", "p,", "n=a," }; - int n = 0; - for(String invalid : invalids) { - try { - Gs2Header.parseFrom(invalid); - System.out.println(invalid); - } catch (IllegalArgumentException e) { - n++; - } - } - - assertEquals(invalids.length, n); - } - - @Test - public void parseFromValid() { - for(int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { - assertGS2Header( - VALID_GS_2_HEADERS[i].writeTo(new StringBuffer()).toString(), - Gs2Header.parseFrom(VALID_GS2HEADER_STRINGS[i]) - ); - } - } -} diff --git a/common/src/test/java/com/ongres/scram/common/message/ClientFinalMessageTest.java b/common/src/test/java/com/ongres/scram/common/message/ClientFinalMessageTest.java deleted file mode 100644 index 10271ec..0000000 --- a/common/src/test/java/com/ongres/scram/common/message/ClientFinalMessageTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import com.ongres.scram.common.RfcExampleSha1; -import com.ongres.scram.common.gssapi.Gs2CbindFlag; -import com.ongres.scram.common.gssapi.Gs2Header; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - - -public class ClientFinalMessageTest { - @Test - public void writeToWithoutProofValid() { - StringBuffer sb = ClientFinalMessage.writeToWithoutProof( - new Gs2Header(Gs2CbindFlag.CLIENT_NOT), null, RfcExampleSha1.FULL_NONCE - ); - - assertEquals(RfcExampleSha1.CLIENT_FINAL_MESSAGE_WITHOUT_PROOF, sb.toString()); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/message/ClientFirstMessageTest.java b/common/src/test/java/com/ongres/scram/common/message/ClientFirstMessageTest.java deleted file mode 100644 index 7df7c01..0000000 --- a/common/src/test/java/com/ongres/scram/common/message/ClientFirstMessageTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.gssapi.Gs2CbindFlag; -import org.junit.Test; - -import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; -import static org.junit.Assert.*; - - -public class ClientFirstMessageTest { - - @Test(expected = IllegalArgumentException.class) - public void constructorTestInvalid1() { - assertNotNull(new ClientFirstMessage(null, "a", CLIENT_NONCE)); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorTestInvalid2() { - assertNotNull( - new ClientFirstMessage(Gs2CbindFlag.CLIENT_NOT, null, "cbind", "a", CLIENT_NONCE) - ); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorTestInvalid3() { - assertNotNull( - new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, "cbind", "a", CLIENT_NONCE) - ); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorTestInvalid4() { - assertNotNull(new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, null, "a", CLIENT_NONCE)); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorTestInvalid5() { - assertNotNull(new ClientFirstMessage(Gs2CbindFlag.CLIENT_NOT, "authzid", null, null, CLIENT_NONCE)); - } - - private void assertClientFirstMessage(String expected, ClientFirstMessage clientFirstMessage) { - assertEquals(expected, clientFirstMessage.writeTo(new StringBuffer()).toString()); - } - - @Test - public void writeToValidValues() { - assertClientFirstMessage( - "n,,n=user,r=" + CLIENT_NONCE, - new ClientFirstMessage("user", CLIENT_NONCE) - ); - assertClientFirstMessage( - "y,,n=user,r=" + CLIENT_NONCE, - new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, null, "user", CLIENT_NONCE) - ); - assertClientFirstMessage( - "p=blah,,n=user,r=" + CLIENT_NONCE, - new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, "blah", "user", CLIENT_NONCE) - ); - assertClientFirstMessage( - "p=blah,a=authzid,n=user,r=" + CLIENT_NONCE, - new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "authzid", "blah", "user", CLIENT_NONCE) - ); - } - - @Test - public void parseFromValidValues() throws ScramParseException { - ClientFirstMessage m1 = ClientFirstMessage.parseFrom("n,,n=user,r=" + CLIENT_NONCE); - assertTrue( - ! m1.isChannelBinding() && m1.getChannelBindingFlag() == Gs2CbindFlag.CLIENT_NOT - && null == m1.getAuthzid() && "user".equals(m1.getUser()) && CLIENT_NONCE.equals(m1.getNonce()) - ); - - ClientFirstMessage m2 = ClientFirstMessage.parseFrom("y,,n=user,r=" + CLIENT_NONCE); - assertTrue( - ! m2.isChannelBinding() && m2.getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT - && null == m2.getAuthzid() && "user".equals(m2.getUser()) && CLIENT_NONCE.equals(m2.getNonce()) - ); - - ClientFirstMessage m3 = ClientFirstMessage.parseFrom("y,a=user2,n=user,r=" + CLIENT_NONCE); - assertTrue( - ! m3.isChannelBinding() && m3.getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT - && null != m3.getAuthzid() && "user2".equals(m3.getAuthzid()) - && "user".equals(m3.getUser()) && CLIENT_NONCE.equals(m3.getNonce()) - ); - - ClientFirstMessage m4 = ClientFirstMessage.parseFrom("p=channel,a=user2,n=user,r=" + CLIENT_NONCE); - assertTrue( - m4.isChannelBinding() && m4.getChannelBindingFlag() == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED - && null != m4.getChannelBindingName() && "channel".equals(m4.getChannelBindingName()) - && null != m4.getAuthzid() && "user2".equals(m4.getAuthzid()) - && "user".equals(m4.getUser()) && CLIENT_NONCE.equals(m4.getNonce()) - ); - } - - @Test - public void parseFromInvalidValues() { - String[] invalidValues = new String[] { - "n,,r=user,r=" + CLIENT_NONCE, "n,,z=user,r=" + CLIENT_NONCE, "n,,n=user", "n,", "n,,", "n,,n=user,r", "n,,n=user,r=" - }; - - int n = 0; - for(String s : invalidValues) { - try { - assertNotNull(ClientFirstMessage.parseFrom(s)); - } catch (ScramParseException e) { - n++; - } - } - - assertEquals(invalidValues.length, n); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/message/ServerFinalMessageTest.java b/common/src/test/java/com/ongres/scram/common/message/ServerFinalMessageTest.java deleted file mode 100644 index a476b4a..0000000 --- a/common/src/test/java/com/ongres/scram/common/message/ServerFinalMessageTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import com.ongres.scram.common.ScramAttributes; -import com.ongres.scram.common.ScramFunctions; -import com.ongres.scram.common.ScramMechanisms; -import com.ongres.scram.common.bouncycastle.base64.Base64; -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.stringprep.StringPreparations; -import org.junit.Test; - -import static com.ongres.scram.common.RfcExampleSha1.*; -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - - -public class ServerFinalMessageTest { - @Test - public void validConstructor() { - byte[] serverKey = ScramFunctions.serverKey( - ScramMechanisms.SCRAM_SHA_1, - StringPreparations.NO_PREPARATION, - PASSWORD, - Base64.decode(SERVER_SALT), - SERVER_ITERATIONS - ); - ServerFinalMessage serverFinalMessage1 = new ServerFinalMessage( - ScramFunctions.serverSignature(ScramMechanisms.SCRAM_SHA_1, serverKey, AUTH_MESSAGE) - ); - assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); - assertFalse(serverFinalMessage1.isError()); - - ServerFinalMessage serverFinalMessage2 = new ServerFinalMessage(ServerFinalMessage.Error.UNKNOWN_USER); - assertEquals(ScramAttributes.ERROR.getChar() + "=" + "unknown-user", serverFinalMessage2.toString()); - assertTrue(serverFinalMessage2.isError()); - } - - @Test - public void validParseFrom() throws ScramParseException { - ServerFinalMessage serverFinalMessage1 = ServerFinalMessage.parseFrom(SERVER_FINAL_MESSAGE); - assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); - assertFalse(serverFinalMessage1.isError()); - - ServerFinalMessage serverFinalMessage2 = ServerFinalMessage.parseFrom("e=channel-binding-not-supported"); - assertEquals("e=channel-binding-not-supported", serverFinalMessage2.toString()); - assertTrue(serverFinalMessage2.isError()); - assertTrue(serverFinalMessage2.getError() == ServerFinalMessage.Error.CHANNEL_BINDING_NOT_SUPPORTED); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/message/ServerFirstMessageTest.java b/common/src/test/java/com/ongres/scram/common/message/ServerFirstMessageTest.java deleted file mode 100644 index 1967777..0000000 --- a/common/src/test/java/com/ongres/scram/common/message/ServerFirstMessageTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.message; - - -import com.ongres.scram.common.exception.ScramParseException; -import org.junit.Test; - -import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; -import static com.ongres.scram.common.RfcExampleSha1.SERVER_FIRST_MESSAGE; -import static org.junit.Assert.assertEquals; - - -public class ServerFirstMessageTest { - @Test - public void validConstructor() { - ServerFirstMessage serverFirstMessage = new ServerFirstMessage( - CLIENT_NONCE, - "3rfcNHYJY1ZVvWVs7j", - "QSXCR+Q6sek8bf92", - 4096 - ); - - assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); - } - - @Test - public void validParseFrom() throws ScramParseException { - ServerFirstMessage serverFirstMessage = ServerFirstMessage.parseFrom(SERVER_FIRST_MESSAGE, CLIENT_NONCE); - - assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/stringprep/SaslPrepTest.java b/common/src/test/java/com/ongres/scram/common/stringprep/SaslPrepTest.java deleted file mode 100644 index 4d32f86..0000000 --- a/common/src/test/java/com/ongres/scram/common/stringprep/SaslPrepTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.stringprep; - -import com.ongres.saslprep.SaslPrep; -import com.ongres.stringprep.StringPrep; -import java.io.IOException; - -import org.junit.Assert; -import org.junit.Test; - -public class SaslPrepTest { - - @Test - public void rfc4013Examples() throws IOException { - // Taken from https://tools.ietf.org/html/rfc4013#section-3 - Assert.assertEquals("IX", SaslPrep.saslPrep("I\u00ADX", true)); - Assert.assertEquals("user", SaslPrep.saslPrep("user", true)); - Assert.assertEquals("USER", SaslPrep.saslPrep("USER", true)); - Assert.assertEquals("a", SaslPrep.saslPrep("\u00AA", true)); - Assert.assertEquals("IX", SaslPrep.saslPrep("\u2168", true)); - try { - SaslPrep.saslPrep("\u0007", true); - Assert.fail("Should throw IllegalArgumentException"); - } catch (IllegalArgumentException e) { - Assert.assertEquals("Prohibited character ", e.getMessage()); - } - try { - SaslPrep.saslPrep("\u0627\u0031", true); - Assert.fail("Should thow IllegalArgumentException"); - } catch (IllegalArgumentException e) { - Assert.assertEquals("The string contains any RandALCat character but a RandALCat character " - + "is not the first and the last characters", e.getMessage()); - } - } - - @Test - public void unassigned() throws IOException { - int unassignedCodepoint; - for (unassignedCodepoint = Character.MAX_CODE_POINT; - unassignedCodepoint >= Character.MIN_CODE_POINT; - unassignedCodepoint--) { - if (!Character.isDefined(unassignedCodepoint) && - !StringPrep.prohibitionAsciiControl(unassignedCodepoint) && - !StringPrep.prohibitionAsciiSpace(unassignedCodepoint) && - !StringPrep.prohibitionChangeDisplayProperties(unassignedCodepoint) && - !StringPrep.prohibitionInappropriateCanonicalRepresentation(unassignedCodepoint) && - !StringPrep.prohibitionInappropriatePlainText(unassignedCodepoint) && - !StringPrep.prohibitionNonAsciiControl(unassignedCodepoint) && - !StringPrep.prohibitionNonAsciiSpace(unassignedCodepoint) && - !StringPrep.prohibitionNonCharacterCodePoints(unassignedCodepoint) && - !StringPrep.prohibitionPrivateUse(unassignedCodepoint) && - !StringPrep.prohibitionSurrogateCodes(unassignedCodepoint) && - !StringPrep.prohibitionTaggingCharacters(unassignedCodepoint)) { - break; - } - } - String withUnassignedChar = "abc"+new String(Character.toChars(unassignedCodepoint)); - //Assert.assertEquals(withUnassignedChar, saslPrepQuery(withUnassignedChar)); - try { - SaslPrep.saslPrep(withUnassignedChar, true); - Assert.fail("Should thow IllegalArgumentException"); - } catch (IllegalArgumentException e) { - Assert.assertEquals("Prohibited character 󯿽", e.getMessage()); - } - } -} \ No newline at end of file diff --git a/common/src/test/java/com/ongres/scram/common/stringprep/StringPreparationTest.java b/common/src/test/java/com/ongres/scram/common/stringprep/StringPreparationTest.java deleted file mode 100644 index ed4c432..0000000 --- a/common/src/test/java/com/ongres/scram/common/stringprep/StringPreparationTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.stringprep; - - -import org.junit.Test; - -import java.util.Random; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - - -public class StringPreparationTest { - private static final String[] ONLY_NON_PRINTABLE_STRINGS = new String[] { " ", (char) 13 + "", (char) 13 + " " }; - - @Test - public void doNormalizeNullEmpty() { - String[] nullEmpty = new String[] { null, "" }; - int n = 0; - for(StringPreparation stringPreparation : StringPreparations.values()) { - for(String s : nullEmpty) { - try { - stringPreparation.normalize(s); - } catch (IllegalArgumentException e) { - n++; - } - } - } - - assertTrue( - "IllegalArgumentException not thrown for either null or empty input", - n == nullEmpty.length * StringPreparations.values().length - ); - } - - @Test - public void doNormalizeValidAsciiCases() { - // 200 usernames from http://jimpix.co.uk/words/random-username-list.asp - String[] validAsciiUsernames = new String[] { - "toastingxenotime", "infecttolerant", "cobblerjack", "zekedigital", "freshscarisdale", "lamwaylon", - "lagopodousmonkeys", "fanfarecheesy", "willowfinnegan", "canoeamoeba", "stinkeroddball", "terracecomet", - "cakebrazos", "headersidesaddle", "cloudultracrepidarian", "grimegastropub", "stallchilli", - "shawnapentagon", "chapeltarp", "rydbergninja", "differencegym", "europiummuscle", "swilledonce", - "defensivesyntaxis", "desktopredundant", "stakingsky", "goofywaiting", "boundsemm", "pipermonstrous", - "faintfrog", "riskinsist", "constantjunkie", "rejectbroth", "ceilbeau", "ponyjaialai", "burnishselfies", - "unamusedglenmore", "parmesanporcupine", "suteconcerto", "ribstony", "sassytwelve", "coursesnasturtium", - "singlecinders", "kinkben", "chiefpussface", "unknownivery", "robterra", "wearycubes", "bearcontent", - "aquifertrip", "insulinlick", "batterypeace", "rubigloo", "fixessnizort", "coalorecheesy", "logodarthvader", - "equipmentbizarre", "charitycolne", "gradecomputer", "incrediblegases", "ingotflyingfish", "abaftmounting", - "kissingfluke", "chesterdinky", "anthropicdip", "portalcairo", "purebredhighjump", "jamaicansteeping", - "skaterscoins", "chondrulelocust", "modespretty", "otisnadrid", "lagoonone", "arrivepayday", "lawfulpatsy", - "customersdeleted", "superiorarod", "abackwarped", "footballcyclic", "sawtshortstop", "waskerleysanidine", - "polythenehead", "carpacciosierra", "gnashgabcheviot", "plunkarnisdale", "surfacebased", "wickedpark", - "capitalistivan", "kinglassmuse", "adultsceiriog", "medrones", "climaxshops", "archeangolfer", "tomfront", - "kobeshift", "nettleaugustus", "bitesizedlion", "crickedbunting", "englishrichard", "dangerousdelmonico", - "sparklemicrosoft", "kneepadsfold", "enunciatesunglasses", "parchmentsteak", "meigpiton", "puttingcitrusy", - "eyehash", "newtonatomiser", "witchesburberry", "positionwobbly", "clipboardamber", "ricolobster", - "calendarpetal", "shinywound", "dealemral", "moonrakerfinnish", "banditliberated", "whippedfanatical", - "jargongreasy", "yumlayla", "dwarfismtransition", "doleriteduce", "sikickball", - "columngymnastics", "draybowmont", "jupitersnorkling", "siderealmolding", "dowdyrosary", "novaskeeter", - "whickerpulley", "rutlandsliders", "categoryflossed", "coiltiedogfish", "brandwaren", "altairlatigo", - "acruxyouthscape", "harmonicdash", "jasperserver", "slicedaggie", "gravityfern", "bitsstorm", - "readymadehobby", "surfeitgrape", "pantheonslabs", "ammandecent", "skicrackers", "speyfashions", - "languagedeeno", "pettyconfit", "minutesshimmering", "thinhopeangellist", "sleevelesscadmium", "controlarc", - "robinvolvox", "postboxskylark", "tortepleasing", "lutzdillinger", "amnioteperl", "burntmaximize", - "gamblingearn", "bumsouch", "coronagraphdown", "bodgeelearning", "hackingscraper", "hartterbium", - "mindyurgonian", "leidlebalki", "labelthumbs", "lincolncrisps", "pearhamster", "termsfiona", - "tickingsomber", "hatellynfi", "northumberlandgrotesque", "harpistcaramel", "gentryswiss", "illusionnooks", - "easilyrows", "highgluten", "backedallegiance", "laelsitesearch", "methodfix", "teethminstral", - "chemicalchildish", "likablepace", "alikealeph", "nalasincere", "investbaroque", "conditionenvelope", - "splintsmccue", "carnonprompt", "resultharvey", "acceptsheba", "redditmonsoon", "multiplepostbox", - "invitationchurch", "drinksgaliath", "ordersvivid", "mugsgit", "clumpingfreak" - }; - - for(StringPreparation stringPreparation : StringPreparations.values()) { - for(String s : validAsciiUsernames) { - assertEquals(s, stringPreparation.normalize(s)); - } - } - } - - /* - * Some simple random testing won't hurt. If a test would fail, create new test with the generated word. - */ - @Test - public void doNormalizeValidAsciiRandom() { - int n = 10 * 1000; - int maxLenght = 64; - Random random = new Random(); - String[] values = new String[n]; - for(int i = 0; i < n; i++) { - char[] charValue = new char[random.nextInt(maxLenght) + 1]; - for(int j = 0; j < charValue.length; j++) { - charValue[j] = (char) (random.nextInt(127 - 33) + 33); - } - values[i] = new String(charValue); - } - - for(StringPreparation stringPreparation : StringPreparations.values()) { - for(String s : values) { - assertEquals( - "'" + s + "' is a printable ASCII string, should not be changed by normalize()", - s, - stringPreparation.normalize(s) - ); - } - } - } - - @Test - public void doNormalizeNoPreparationEmptyAfterNormalization() { - int n = 0; - for(String s : ONLY_NON_PRINTABLE_STRINGS) { - try { - StringPreparations.NO_PREPARATION.normalize(s); - } catch (IllegalArgumentException e) { - n++; - } - } - - assertTrue( - "IllegalArgumentException not thrown for either null or empty output after normalization", - n == ONLY_NON_PRINTABLE_STRINGS.length - ); - } - - @Test - public void doNormalizeNoPreparationNonEmptyAfterNormalization() { - // No exception should be thrown - for(String s : ONLY_NON_PRINTABLE_STRINGS) { - StringPreparations.NO_PREPARATION.normalize(s + "a"); - } - } -} diff --git a/common/src/test/java/com/ongres/scram/common/util/AbstractCharAttributeValueTest.java b/common/src/test/java/com/ongres/scram/common/util/AbstractCharAttributeValueTest.java deleted file mode 100644 index b5b6204..0000000 --- a/common/src/test/java/com/ongres/scram/common/util/AbstractCharAttributeValueTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import org.junit.Test; - -import static org.junit.Assert.*; - - -public class AbstractCharAttributeValueTest { - private class MockCharAttribute implements CharAttribute { - private final char c; - - public MockCharAttribute(char c) { - this.c = c; - } - - @Override - public char getChar() { - return c; - } - } - - @Test - public void constructorNullAttribute() { - try { - assertNotNull(new AbstractCharAttributeValue((CharAttribute) null, "value")); - } catch(IllegalArgumentException e) { - return; - } - - fail("IllegalArgumentException must be thrown if the CharAttribute is null"); - } - - @Test - public void constructorEmptyValue() { - try { - assertNotNull(new AbstractCharAttributeValue(new MockCharAttribute('c'), "")); - } catch(IllegalArgumentException e) { - return; - } - - fail("IllegalArgumentException must be thrown if the value is empty"); - } - - @Test - public void writeToNonNullValues() { - String[] legalValues = new String[] { "a", "----", "value" }; - char c = 'c'; - for(String s : legalValues) { - assertEquals( - "" + c + '=' + s, - new AbstractCharAttributeValue(new MockCharAttribute(c), s).toString() - ); - } - } - - @Test - public void writeToNullValue() { - char c = 'd'; - assertEquals( - "" + c, - new AbstractCharAttributeValue(new MockCharAttribute(c), null).toString() - ); - } - - @Test - public void writeToEscapedValues() { - char c = 'a'; - MockCharAttribute mockCharAttribute = new MockCharAttribute(c); - String[] values = new String[] { "a=b", "c,a", ",", "=,", "=,,=" }; - for(int i = 0; i < values.length; i++) { - assertEquals( - "" + c + '=' + values[i], - new AbstractCharAttributeValue(mockCharAttribute, values[i]).toString() - ); - } - } -} diff --git a/common/src/test/java/com/ongres/scram/common/util/Base64Test.java b/common/src/test/java/com/ongres/scram/common/util/Base64Test.java deleted file mode 100644 index 3431061..0000000 --- a/common/src/test/java/com/ongres/scram/common/util/Base64Test.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2019, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package com.ongres.scram.common.util; - -import java.nio.charset.StandardCharsets; - -import org.junit.Assert; -import org.junit.Test; - -import com.ongres.scram.common.bouncycastle.base64.Base64; - -public class Base64Test { - - @Test - public void rfcTest() { - Assert.assertEquals("", new String(Base64.decode(""), StandardCharsets.UTF_8)); - Assert.assertEquals("f", new String(Base64.decode("Zg=="), StandardCharsets.UTF_8)); - Assert.assertEquals("fo", new String(Base64.decode("Zm8="), StandardCharsets.UTF_8)); - Assert.assertEquals("foo", new String(Base64.decode("Zm9v"), StandardCharsets.UTF_8)); - Assert.assertEquals("foob", new String(Base64.decode("Zm9vYg=="), StandardCharsets.UTF_8)); - Assert.assertEquals("fooba", new String(Base64.decode("Zm9vYmE="), StandardCharsets.UTF_8)); - Assert.assertEquals("foobar", new String(Base64.decode("Zm9vYmFy"), StandardCharsets.UTF_8)); - Assert.assertEquals("", Base64.toBase64String("".getBytes(StandardCharsets.UTF_8))); - Assert.assertEquals("Zg==", Base64.toBase64String("f".getBytes(StandardCharsets.UTF_8))); - Assert.assertEquals("Zm8=", Base64.toBase64String("fo".getBytes(StandardCharsets.UTF_8))); - Assert.assertEquals("Zm9v", Base64.toBase64String("foo".getBytes(StandardCharsets.UTF_8))); - Assert.assertEquals("Zm9vYg==", Base64.toBase64String("foob".getBytes(StandardCharsets.UTF_8))); - Assert.assertEquals("Zm9vYmE=", Base64.toBase64String("fooba".getBytes(StandardCharsets.UTF_8))); - Assert.assertEquals("Zm9vYmFy", Base64.toBase64String("foobar".getBytes(StandardCharsets.UTF_8))); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/util/CryptoUtilTest.java b/common/src/test/java/com/ongres/scram/common/util/CryptoUtilTest.java deleted file mode 100644 index b21b0d0..0000000 --- a/common/src/test/java/com/ongres/scram/common/util/CryptoUtilTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import org.junit.Test; - -import java.security.SecureRandom; -import java.util.Random; - -import static org.junit.Assert.fail; - - -public class CryptoUtilTest { - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - - @Test(expected = IllegalArgumentException.class) - public void nonceInvalidSize1() { - CryptoUtil.nonce(0, SECURE_RANDOM); - } - - @Test(expected = IllegalArgumentException.class) - public void nonceInvalidSize2() { - CryptoUtil.nonce(-1, SECURE_RANDOM); - } - - @Test - public void nonceValid() { - int nNonces = 1000; - int nonceMaxSize = 100; - Random random = new Random(); - - // Some more random testing - for(int i = 0; i < nNonces; i++) { - for(char c : CryptoUtil.nonce(random.nextInt(nonceMaxSize) + 1, SECURE_RANDOM).toCharArray()) { - if(c == ',' || c < (char) 33 || c > (char) 126) { - fail("Character c='" + c + "' is not allowed on a nonce"); - } - } - } - } -} diff --git a/common/src/test/java/com/ongres/scram/common/util/StringWritableCsvTest.java b/common/src/test/java/com/ongres/scram/common/util/StringWritableCsvTest.java deleted file mode 100644 index ed5c051..0000000 --- a/common/src/test/java/com/ongres/scram/common/util/StringWritableCsvTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import com.ongres.scram.common.ScramAttributes; -import com.ongres.scram.common.ScramAttributeValue; -import com.ongres.scram.common.gssapi.Gs2AttributeValue; -import com.ongres.scram.common.gssapi.Gs2Attributes; -import org.junit.Test; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - - -public class StringWritableCsvTest { - private static final String[] ONE_ARG_VALUES = new String[] { "c=channel", "i=4096", "a=authzid", "n" }; - private static final String SEVERAL_VALUES_STRING = "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL"; - - @Test - public void writeToNullOrEmpty() { - assertTrue(StringWritableCsv.writeTo(new StringBuffer()).length() == 0); - assertTrue(StringWritableCsv.writeTo(new StringBuffer(), new CharAttributeValue[]{}).length() == 0); - } - - @Test - public void writeToOneArg() { - CharAttributeValue[] pairs = new CharAttributeValue[] { - new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, "channel"), - new ScramAttributeValue(ScramAttributes.ITERATION, "" + 4096), - new Gs2AttributeValue(Gs2Attributes.AUTHZID, "authzid"), - new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null) - }; - - for(int i = 0; i < pairs.length; i++) { - assertEquals(ONE_ARG_VALUES[i], StringWritableCsv.writeTo(new StringBuffer(), pairs[i]).toString()); - } - } - - @Test - public void writeToSeveralArgs() { - assertEquals( - SEVERAL_VALUES_STRING, - StringWritableCsv.writeTo( - new StringBuffer(), - new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null), - null, - new ScramAttributeValue(ScramAttributes.USERNAME, "user"), - new ScramAttributeValue(ScramAttributes.NONCE, "fyko+d2lbbFgONRv9qkxdawL") - - ).toString() - ); - } - - @Test - public void parseFromEmpty() { - assertArrayEquals(new String[]{}, StringWritableCsv.parseFrom("")); - } - - @Test - public void parseFromOneArgWithLimitsOffsets() { - for(String s : ONE_ARG_VALUES) { - assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s)); - } - - int[] numberEntries = new int[] { 0, 1 }; - for(int n : numberEntries) { - for(String s : ONE_ARG_VALUES) { - assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n)); - } - } - for(String s : ONE_ARG_VALUES) { - assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3)); - } - - for(int n : numberEntries) { - for(String s : ONE_ARG_VALUES) { - assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n, 0)); - } - } - for(String s : ONE_ARG_VALUES) { - assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3, 0)); - } - - for(int n : numberEntries) { - for(String s : ONE_ARG_VALUES) { - assertArrayEquals(new String[] { null }, StringWritableCsv.parseFrom(s, n, 1)); - } - } - } - - @Test - public void parseFromSeveralArgsWithLimitsOffsets() { - assertArrayEquals( - new String[] { "n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL" }, - StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING) - ); - - assertArrayEquals( - new String[] { "n", "" }, - StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2) - ); - - assertArrayEquals( - new String[] { "", "n=user" }, - StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 1) - ); - - assertArrayEquals( - new String[] { "r=fyko+d2lbbFgONRv9qkxdawL", null }, - StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 3) - ); - - assertArrayEquals( - new String[] { null, null }, - StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 4) - ); - - assertArrayEquals( - new String[] { "n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL", null }, - StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 5) - ); - } -} diff --git a/common/src/test/java/com/ongres/scram/common/util/UsAsciiUtilsTest.java b/common/src/test/java/com/ongres/scram/common/util/UsAsciiUtilsTest.java deleted file mode 100644 index 1c7b6a9..0000000 --- a/common/src/test/java/com/ongres/scram/common/util/UsAsciiUtilsTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2017, OnGres. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - - -package com.ongres.scram.common.util; - - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.*; - - -public class UsAsciiUtilsTest { - @Test - public void toPrintableNull() { - try { - UsAsciiUtils.toPrintable(null); - } catch(IllegalArgumentException ex) { - return; - } - - fail("Calling with null value must throw IllegalArgumentException"); - } - - @Test - public void toPrintableNonASCII() { - String[] nonASCIIStrings = new String[] { "abcdé", "ñ", "€", "Наташа", (char) 127 + "" }; - int n = 0; - for(String s : nonASCIIStrings) { - try { - UsAsciiUtils.toPrintable(s); - } catch(IllegalArgumentException ex) { - n++; - } - } - - assertTrue( - "String(s) with non-ASCII characters not throwing IllegalArgumentException", - n == nonASCIIStrings.length - ); - } - - @Test - public void toPrintableNonPrintable() { - String[] original = new String[] { " u ", "a" + (char) 12, (char) 0 + "ttt" + (char) 31 }; - String[] expected = new String[] { "u", "a", "ttt" }; - - for(int i = 0; i < original.length; i++) { - assertEquals("", expected[i], UsAsciiUtils.toPrintable(original[i])); - } - } - - @Test - public void toPrintableAllPrintable() { - List values = new ArrayList(); - values.addAll(Arrays.asList( - new String[] { (char) 33 + "", "user", "!", "-,.=?", (char) 126 + "" }) - ); - for(int c = 33; c < 127; c++) { - values.add("---" + (char) c + "---"); - } - - for(String s : values) { - assertEquals( - "All printable String '" + s + "' not returning the same value", - s, - UsAsciiUtils.toPrintable(s) - ); - } - } -} diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml new file mode 100644 index 0000000..44bc204 --- /dev/null +++ b/coverage-report/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + com.ongres.scram + scram-parent + 3.2 + ../scram-parent/pom.xml + + + coverage-report + pom + + JaCoCo Coverage Report + + + + com.ongres.scram + scram-common + + + com.ongres.scram + scram-client + + + + + + + org.jacoco + jacoco-maven-plugin + + + META-INF/versions/** + + + + + report-aggregate + + report-aggregate + + verify + + + + + + diff --git a/debian/changelog b/debian/changelog index 701dbd8..93fb008 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,27 @@ +libscram-java (3.2-1) unstable; urgency=medium + + * New upstream version 3.2. + * Fixes timing attacks. (Closes: #1116054, CVE-2025-59432) + + -- Christoph Berg Tue, 23 Sep 2025 18:13:50 +0200 + +libscram-java (3.1-1) unstable; urgency=medium + + * Team upload. + + [ Debian Janitor ] + * Remove unnecessary get-orig-source-target. + + [ Emmanuel Bourg ] + * New upstream release + - Updated the Maven build + - Depend on libstringprep-java (>= 2.1) + - libscram-java 3.1 breaks libpostgresql-jdbc-java (<< 42.7.5) + * Enabled the unit tests + * Standards-Version updated to 4.7.0 + + -- Emmanuel Bourg Mon, 10 Feb 2025 09:43:11 +0100 + libscram-java (2.1-1) unstable; urgency=medium * New upstream version 2.1. diff --git a/debian/control b/debian/control index 552740c..045377c 100644 --- a/debian/control +++ b/debian/control @@ -9,14 +9,11 @@ Build-Depends: default-jdk, maven-debian-helper (>= 2.1), Build-Depends-Indep: - default-jdk-doc, - junit4 (>= 4.12), - libfindbugs-annotations-java, + junit5, + libjetbrains-annotations-java, libmaven-dependency-plugin-java, - libmaven-javadoc-plugin-java, - libmaven-source-plugin-java, - libstringprep-java (<< 2.0), -Standards-Version: 4.5.0 + libstringprep-java (>= 2.1), +Standards-Version: 4.7.0 Vcs-Git: https://salsa.debian.org/java-team/libscram-java.git Vcs-Browser: https://salsa.debian.org/java-team/libscram-java Homepage: https://github.com/ongres/scram @@ -28,8 +25,8 @@ Depends: ${maven:Depends}, ${misc:Depends}, Suggests: - libscram-java-doc, ${maven:OptionalDepends}, +Breaks: libpostgresql-jdbc-java (<< 42.7.5) Description: Salted Challenge Response Authentication Mechanism SCRAM (Salted Challenge Response Authentication Mechanism) is part of the family of Simple Authentication and Security Layer (SASL, RFC 4422) diff --git a/debian/libscram-java.poms b/debian/libscram-java.poms index 9e925ba..4ad8762 100644 --- a/debian/libscram-java.poms +++ b/debian/libscram-java.poms @@ -26,5 +26,6 @@ # Empty by default. [mh_install] # pom.xml --has-package-version -common/pom.xml --has-package-version --usj-name=scram-common -client/pom.xml --has-package-version --usj-name=scram-client +scram-common/pom.xml --has-package-version --usj-name=scram-common +scram-client/pom.xml --has-package-version --usj-name=scram-client +scram-parent/pom.xml --has-package-version diff --git a/debian/maven.ignoreRules b/debian/maven.ignoreRules index c7244e5..787dfdb 100644 --- a/debian/maven.ignoreRules +++ b/debian/maven.ignoreRules @@ -1,2 +1,3 @@ +org.apache.maven.plugins maven-enforcer-plugin org.sonatype.plugins nexus-staging-maven-plugin * * * * * * test-jar * * * diff --git a/debian/maven.properties b/debian/maven.properties index 3b73c63..b0342cd 100644 --- a/debian/maven.properties +++ b/debian/maven.properties @@ -3,5 +3,3 @@ # maven.test.skip=true # project.build.sourceEncoding=UTF-8 -maven.test.skip=true -maven.javadoc.skip=true diff --git a/debian/patches/01-multi-release-jar.patch b/debian/patches/01-multi-release-jar.patch new file mode 100644 index 0000000..5bd3133 --- /dev/null +++ b/debian/patches/01-multi-release-jar.patch @@ -0,0 +1,18 @@ +Description: Adds the missing Multi-Release attribute in the manifest. + Without this attribute the module-info.class of scram-common is not seen + when compiling scram-client and the build fails. Why is the attribute + missing when building with maven-debian-helper??? +Author: Emmanuel Bourg +Forwarded: not-needed +--- a/scram-parent/pom.xml ++++ b/scram-parent/pom.xml +@@ -205,6 +205,9 @@ + + true + ++ ++ true ++ + + + diff --git a/debian/patches/02-reactor-dependencies.patch b/debian/patches/02-reactor-dependencies.patch new file mode 100644 index 0000000..f83d8ca --- /dev/null +++ b/debian/patches/02-reactor-dependencies.patch @@ -0,0 +1,13 @@ +Description: Set the version of the reactor dependencies (otherwise the build fails) +Author: Emmanuel Bourg +Forwarded: not-needed +--- a/scram-client/pom.xml ++++ b/scram-client/pom.xml +@@ -17,6 +17,7 @@ + + com.ongres.scram + scram-common ++ ${project.version} + + + diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..09ad3e5 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,2 @@ +01-multi-release-jar.patch +02-reactor-dependencies.patch diff --git a/debian/rules b/debian/rules index 5985cac..2d33f6a 100755 --- a/debian/rules +++ b/debian/rules @@ -2,6 +2,3 @@ %: dh $@ - -get-orig-source: - uscan --download-current-version --force-download --no-symlink diff --git a/mvnw b/mvnw index 1dc59e8..1ddd97b 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Required ENV vars: # ------------------ @@ -27,285 +27,312 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then +if [ -z "$MAVEN_SKIP_RC" ]; then - if [ -f /etc/mavenrc ] ; then + if [ -f /usr/local/etc/mavenrc ]; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ]; then . /etc/mavenrc fi - if [ -f "$HOME/.mavenrc" ] ; then + if [ -f "$HOME/.mavenrc" ]; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; +cygwin=false +darwin=false mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)" + export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home" + export JAVA_HOME fi - ;; + fi + ;; esac -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +if $mingw; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ + && JAVA_HOME="$( + cd "$JAVA_HOME" || ( + echo "cannot cd into $JAVA_HOME." >&2 + exit 1 + ) + pwd + )" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin; then + javaHome="$(dirname "$javaExecutable")" + javaExecutable="$(cd "$javaHome" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "$javaExecutable")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "$javaExecutable")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`which java`" + JAVACMD="$( + \unset -f command 2>/dev/null + \command -v java + )" fi fi -if [ ! -x "$JAVACMD" ] ; then +if [ ! -x "$JAVACMD" ]; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." >&2 fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" >&2 return 1 fi basedir="$1" wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + wdir=$( + cd "$wdir/.." || exit 1 + pwd + ) fi # end of workaround done - echo "${basedir}" + printf '%s' "$( + cd "$basedir" || exit 1 + pwd + )" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' <"$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then - exit 1; + exit 1 fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + fi + while IFS="=" read -r key value; do + case "$key" in wrapperUrl) + wrapperUrl=$(trim "${value-}") + break + ;; + esac + done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" + elif command -v curl >/dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" "-Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi fi + fi fi ########################################################################################## # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in wrapperSha256Sum) + wrapperSha256Sum=$(trim "${value-}") + break + ;; + esac +done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] \ + && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ - "-Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2" \ $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index b26ab24..8366e21 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -18,15 +18,14 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -46,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal @@ -60,22 +59,22 @@ set ERROR_CODE=0 @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome -echo. +echo. >&2 echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 -echo. +echo. >&2 goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init -echo. +echo. >&2 echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 -echo. +echo. >&2 goto error @REM ==== END VALIDATION ==== @@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% + echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ @@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% ( "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% @@ -154,11 +153,36 @@ if exist %WRAPPER_JAR% ( ) @REM End of extension +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end @@ -168,15 +192,15 @@ set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause +if "%MAVEN_BATCH_PAUSE%"=="on" pause -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% -exit /B %ERROR_CODE% +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 5ea1fce..71e17ee 100644 --- a/pom.xml +++ b/pom.xml @@ -1,244 +1,64 @@ - - - 4.0.0 + + 4.0.0 + com.ongres.scram - parent - 2.1 - pom - - - common - client - - - SCRAM - Java Implementation of the Salted Challenge Response Authentication Mechanism (SCRAM) - https://gitlab.com/ongresinc/scram - 2019 - - - - com.ongres.aht - Álvaro Hernández Tortosa - aht@ongres.com - - - com.ongres.matteom - Matteo Melli - matteom@ongres.com - - - com.ongres.begona - Begoña Pérez Martín - begonapm@ongres.com - - - - - - The 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause - repo - - - - - scm:git:https://gitlab.com/ongresinc/scram.git - scm:git:git@gitlab.com:ongresinc/scram.git - git@gitlab.com:ongresinc/scram.git - - - - GitLab - https://gitlab.com/ongresinc/scram/issues - - - - - ossrh-release - OSSRH Release repository - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - - ossrh-snapshot - OSSRH Snapshot repository - https://oss.sonatype.org/content/repositories/snapshots - - + scram-parent + 3.2 + scram-parent/pom.xml + - - UTF-8 - 1.7 - common/spotbugs-exclude.xml - + scram-aggregator + pom - - - - junit - junit - 4.12 - - - + SCRAM - Aggregator + Java Implementation of the Salted Challenge Response Authentication Mechanism (SCRAM) + https://github.com/ongres/scram + 2017 + + OnGres, Inc + https://www.ongres.com + + + + BSD 2-Clause "Simplified" License + https://spdx.org/licenses/BSD-2-Clause + repo + + - - - com.google.code.findbugs - annotations - 3.0.1 - provided - - - com.google.code.findbugs - jsr305 - 3.0.1 - provided - - + + + com.ongres.aht + Álvaro Hernández Tortosa + aht@ongres.com + + + com.ongres.matteom + Matteo Melli + matteom@ongres.com + + + com.ongres.jorsol + Jorge Solórzano + jorsol@ongres.com + + - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.1 - - ${java.version} - ${java.version} - -Xlint:all - -Xlint:-options - true - true - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - - attach-javadocs - - jar - - - 7 - - - - - - + + scram-parent + scram-common + scram-client + - - - safer - - - - org.codehaus.mojo - findbugs-maven-plugin - 3.0.4 - - Max - Low - true - ${spotbugs.excluded.location} - ${project.build.directory}/findbugs - - - - analyze-compile - - check - - - - - - - - - master-branch - - - - org.apache.maven.plugins - maven-enforcer-plugin - 1.4.1 - - - enforce-release - - enforce - - - - - Artifacts on master branch must not be snapshot! - - - - - - - - - - - release - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - software@ongres.com - - - - - - - - + + + coverage + + coverage-report + + + diff --git a/scram-client/pom.xml b/scram-client/pom.xml new file mode 100644 index 0000000..c40dc50 --- /dev/null +++ b/scram-client/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + com.ongres.scram + scram-parent + 3.2 + ../scram-parent/pom.xml + + + scram-client + + SCRAM - Client + + + + com.ongres.scram + scram-common + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + + run-its + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-invoker-plugin + + + + + + + diff --git a/scram-client/src/it/jpms-scram-client/invoker.properties b/scram-client/src/it/jpms-scram-client/invoker.properties new file mode 100644 index 0000000..93b7001 --- /dev/null +++ b/scram-client/src/it/jpms-scram-client/invoker.properties @@ -0,0 +1,3 @@ +# build project if JRE version is 11 or higher +invoker.java.version = 11+ +invoker.goals = test diff --git a/scram-client/src/it/jpms-scram-client/pom.xml b/scram-client/src/it/jpms-scram-client/pom.xml new file mode 100644 index 0000000..c4a30c1 --- /dev/null +++ b/scram-client/src/it/jpms-scram-client/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.ongres.scram.it + jpms-scram-client + JPMS Scram Client + 3.2 + + + UTF-8 + ${java.specification.version} + ${java.specification.version} + ${java.specification.version} + + + + + com.ongres.scram + scram-client + ${project.version} + + + org.junit.jupiter + junit-jupiter + 5.13.4 + test + + + + + + + maven-compiler-plugin + 3.14.0 + + + maven-jar-plugin + 3.4.2 + + + maven-surefire-plugin + 3.5.4 + + + + diff --git a/scram-client/src/it/jpms-scram-client/src/test/java/module-info.java b/scram-client/src/it/jpms-scram-client/src/test/java/module-info.java new file mode 100644 index 0000000..e4c72d9 --- /dev/null +++ b/scram-client/src/it/jpms-scram-client/src/test/java/module-info.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +open module test.scram { + requires com.ongres.scram.client; + + requires transitive org.junit.jupiter.engine; + requires transitive org.junit.jupiter.api; + requires transitive org.junit.jupiter.params; +} \ No newline at end of file diff --git a/scram-client/src/it/jpms-scram-client/src/test/java/test/scram/ScramClientTest.java b/scram-client/src/it/jpms-scram-client/src/test/java/test/scram/ScramClientTest.java new file mode 100644 index 0000000..0fc3374 --- /dev/null +++ b/scram-client/src/it/jpms-scram-client/src/test/java/test/scram/ScramClientTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package test.scram; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.util.Arrays; + +import com.ongres.scram.client.ScramClient; +import com.ongres.scram.common.ScramFunctions; +import org.junit.jupiter.api.Test; + +class ScramClientTest { + + @Test + void accessPublic() { + assertEquals("com.ongres.scram.client", ScramClient.class.getModule().getName()); + assertEquals("com.ongres.scram.common", ScramFunctions.class.getModule().getName()); + } + + @Test + void testBuildClient() { + ScramClient scramSession = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) + .username("user") + .password("pencil".toCharArray()) + .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") + .build(); + assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); + assertEquals("n,,n=user,r=rOprNGfwEbeRWgbNEkqO", + scramSession.clientFirstMessage().toString()); + assertDoesNotThrow( + () -> scramSession.serverFirstMessage( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," + + "s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096")); + assertEquals( + "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," + + "p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=", + scramSession.clientFinalMessage().toString()); + assertDoesNotThrow( + () -> scramSession.serverFinalMessage("v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=")); + } + +} diff --git a/scram-client/src/it/settings.xml b/scram-client/src/it/settings.xml new file mode 100644 index 0000000..8890c85 --- /dev/null +++ b/scram-client/src/it/settings.xml @@ -0,0 +1,35 @@ + + + + + it-repo + + true + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + diff --git a/scram-client/src/main/java/com/ongres/scram/client/ClientFinalProcessor.java b/scram-client/src/main/java/com/ongres/scram/client/ClientFinalProcessor.java new file mode 100644 index 0000000..dd083a2 --- /dev/null +++ b/scram-client/src/main/java/com/ongres/scram/client/ClientFinalProcessor.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.client; + +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import com.ongres.scram.common.ClientFinalMessage; +import com.ongres.scram.common.ClientFirstMessage; +import com.ongres.scram.common.ScramFunctions; +import com.ongres.scram.common.ScramMechanism; +import com.ongres.scram.common.ServerFinalMessage; +import com.ongres.scram.common.ServerFirstMessage; +import com.ongres.scram.common.StringPreparation; +import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; +import com.ongres.scram.common.exception.ScramParseException; +import com.ongres.scram.common.exception.ScramServerErrorException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Processor that allows to generate the client-final-message, as well as process the + * server-final-message and verify server's signature. Generate the processor by calling either + * {@code ServerFirstProcessor#clientFinalProcessor(char[])} or + * {@code ServerFirstProcessor#clientFinalProcessor(byte[], byte[])}. + */ +final class ClientFinalProcessor { + private final byte[] clientKey; + private final byte[] storedKey; + private final byte[] serverKey; + + private final ScramMechanism scramMechanism; + + private final ClientFirstMessage clientFirstMessage; + + private final ServerFirstMessage serverFirstMessage; + private String authMessage; + + private ClientFinalProcessor(ScramMechanism scramMechanism, byte[] clientKey, + byte[] storedKey, byte[] serverKey, ClientFirstMessage clientFirstMessage, + ServerFirstMessage serverFirstMessage) { + this.scramMechanism = scramMechanism; + this.clientKey = checkNotNull(clientKey, "clientKey"); + this.storedKey = checkNotNull(storedKey, "storedKey"); + this.serverKey = checkNotNull(serverKey, "serverKey"); + this.clientFirstMessage = clientFirstMessage; + this.serverFirstMessage = serverFirstMessage; + } + + ClientFinalProcessor(ScramMechanism scramMechanism, byte[] clientKey, byte[] serverKey, + ClientFirstMessage clientFirstMessage, ServerFirstMessage serverFirstMessage) { + this(scramMechanism, clientKey, ScramFunctions.storedKey(scramMechanism, clientKey), + serverKey, clientFirstMessage, serverFirstMessage); + } + + ClientFinalProcessor(ScramMechanism scramMechanism, byte[] saltedPassword, + ClientFirstMessage clientFirstMessage, ServerFirstMessage serverFirstMessage) { + this( + scramMechanism, + ScramFunctions.clientKey(scramMechanism, saltedPassword), + ScramFunctions.serverKey(scramMechanism, saltedPassword), + clientFirstMessage, serverFirstMessage); + } + + ClientFinalProcessor(ScramMechanism scramMechanism, StringPreparation stringPreparation, + char[] password, byte[] salt, ClientFirstMessage clientFirstMessage, + ServerFirstMessage serverFirstMessage) { + this(scramMechanism, + ScramFunctions.saltedPassword(scramMechanism, stringPreparation, password, salt, + serverFirstMessage.getIterationCount()), + clientFirstMessage, serverFirstMessage); + } + + private void generateAndCacheAuthMessage(byte[] cbindData) { + if (null == this.authMessage) { + this.authMessage = ScramFunctions.authMessage(clientFirstMessage, serverFirstMessage, cbindData); + } + } + + /** + * Generates the SCRAM representation of the client-final-message, including the given + * channel-binding data. + * + * @param cbindData The bytes of the channel-binding data + * @return The message + */ + @NotNull + ClientFinalMessage clientFinalMessage(byte @Nullable [] cbindData) { + generateAndCacheAuthMessage(cbindData); + + return new ClientFinalMessage( + clientFirstMessage.getGs2Header(), + cbindData, + serverFirstMessage.getNonce(), + ScramFunctions.clientProof( + clientKey, + ScramFunctions.clientSignature(scramMechanism, storedKey, authMessage))); + } + + /** + * Receive and process the server-final-message. Server SCRAM signatures is verified. + * + * @param serverFinalMessage The received server-final-message + * @throws ScramParseException If the message is not a valid server-final-message + * @throws ScramServerErrorException If the server-final-message contained an error + * @throws ScramInvalidServerSignatureException If the server signature is invalid + * @throws IllegalArgumentException If the message is null or empty + */ + @NotNull + ServerFinalMessage receiveServerFinalMessage(@NotNull String serverFinalMessage) + throws ScramParseException, ScramServerErrorException, ScramInvalidServerSignatureException { + checkNotEmpty(serverFinalMessage, "serverFinalMessage"); + + ServerFinalMessage message = ServerFinalMessage.parseFrom(serverFinalMessage); + if (message.isError()) { + throw new ScramServerErrorException(message.getServerError()); + } + if (!ScramFunctions.verifyServerSignature( + scramMechanism, serverKey, authMessage, message.getVerifier())) { + throw new ScramInvalidServerSignatureException("Invalid SCRAM server signature"); + } + return message; + } +} diff --git a/scram-client/src/main/java/com/ongres/scram/client/MessageFlow.java b/scram-client/src/main/java/com/ongres/scram/client/MessageFlow.java new file mode 100644 index 0000000..64766d2 --- /dev/null +++ b/scram-client/src/main/java/com/ongres/scram/client/MessageFlow.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.client; + +import com.ongres.scram.common.ClientFinalMessage; +import com.ongres.scram.common.ClientFirstMessage; +import com.ongres.scram.common.ServerFinalMessage; +import com.ongres.scram.common.ServerFirstMessage; +import com.ongres.scram.common.exception.ScramException; +import org.jetbrains.annotations.NotNull; + +interface MessageFlow { + @NotNull + ClientFirstMessage clientFirstMessage(); + + @NotNull + ServerFirstMessage serverFirstMessage(@NotNull String serverFirstMessage) throws ScramException; + + @NotNull + ClientFinalMessage clientFinalMessage(); + + @NotNull + ServerFinalMessage serverFinalMessage(@NotNull String serverFinalMessage) throws ScramException; + + enum Stage { + NONE, + CLIENT_FIRST, + SERVER_FIRST, + CLIENT_FINAL, + SERVER_FINAL; + } +} diff --git a/scram-client/src/main/java/com/ongres/scram/client/ScramClient.java b/scram-client/src/main/java/com/ongres/scram/client/ScramClient.java new file mode 100644 index 0000000..f48a1cf --- /dev/null +++ b/scram-client/src/main/java/com/ongres/scram/client/ScramClient.java @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.client; + +import static com.ongres.scram.common.util.Preconditions.checkArgument; +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; +import static com.ongres.scram.common.util.Preconditions.gt0; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Supplier; + +import com.ongres.scram.common.ClientFinalMessage; +import com.ongres.scram.common.ClientFirstMessage; +import com.ongres.scram.common.Gs2CbindFlag; +import com.ongres.scram.common.ScramFunctions; +import com.ongres.scram.common.ScramMechanism; +import com.ongres.scram.common.ServerFinalMessage; +import com.ongres.scram.common.ServerFirstMessage; +import com.ongres.scram.common.StringPreparation; +import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; +import com.ongres.scram.common.exception.ScramParseException; +import com.ongres.scram.common.exception.ScramServerErrorException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A class that represents a SCRAM client. Use this class to perform a SCRAM negotiation with a + * SCRAM server. This class performs an authentication execution for a given user, and has state + * related to it. Thus, it cannot be shared across users or authentication executions. + * + *

Example of usage: + * + *

{@code
+ * ScramClient scramClient = ScramClient.builder()
+ *     .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS"))
+ *     .username("user")
+ *     .password("pencil".toCharArray())
+ *     .channelBinding("tls-server-end-point", channelBindingData) // client supports channel binding
+ *     .build();
+ *
+ *   // The build() call negotiates the SCRAM mechanism to be used. In this example,
+ *   // since the server advertise support for the SCRAM-SHA-256-PLUS mechanism,
+ *   // and the builder is set with the channel binding type and data, the constructed
+ *   // scramClient will use the "SCRAM-SHA-256-PLUS" mechanism for authentication.
+ *
+ * // Send the client-first-message ("p=...,,n=...,r=...")
+ * ClientFirstMessage clientFirstMsg = scramClient.clientFirstMessage();
+ * ...
+ * // Receive the server-first-message
+ * ServerFirstMessage serverFirstMsg = scramClient.serverFirstMessage("r=...,s=...,i=...");
+ * ...
+ * // Send the client-final-message ("c=...,r=...,p=...")
+ * ClientFinalMessage clientFinalMsg = scramClient.clientFinalMessage();
+ * ...
+ * // Receive the server-final-message, throw an ScramException on error
+ * ServerFinalMessage serverFinalMsg = scramClient.serverFinalMessage("v=...");
+ * }
+ * + *

Commonly, a protocol will specify that the server advertises supported and available + * mechanisms to the client via some facility provided by the protocol, and the client will then + * select the "best" mechanism from this list that it supports and finds suitable. + * + *

When building the ScramClient, it provides mechanism negotiation based on parameters, if + * channel binding is missing the client will use {@code "n"} as gs2-cbind-flag, if the channel + * binding is set, but the mechanisms send by the server do not advertise the {@code -PLUS} + * version, it will use {@code "y"} as gs2-cbind-flag, when both client and server support channel + * binding, it will use {@code "p=" cb-name} as gs2-cbind-flag. + * + * @see RFC-5802: Salted Challenge Response + * Authentication Mechanism (SCRAM) SASL and GSS-API Mechanisms + * @see RFC-7677: SCRAM-SHA-256 and + * SCRAM-SHA-256-PLUS Simple Authentication and Security Layer (SASL) Mechanisms + */ +public final class ScramClient implements MessageFlow { + + private final ScramMechanism scramMechanism; + private final Gs2CbindFlag channelBinding; + private final StringPreparation stringPreparation; + private final String username; + private final char[] password; + private final byte[] saltedPassword; + private final byte[] clientKey; + private final byte[] serverKey; + private final String cbindType; + private final byte[] cbindData; + private final String authzid; + private final String nonce; + + private Stage currentState = Stage.NONE; + private ClientFirstMessage clientFirstMessage; + + private ServerFirstProcessor serverFirstProcessor; + + private ClientFinalProcessor clientFinalProcessor; + + /** + * Constructs a SCRAM client, to perform an authentication for a given user. This class can not be + * instantiated directly, use a {@link #builder()} is used instead. + * + * @param builder The Builder used to initialize this client + */ + private ScramClient(@NotNull Builder builder) { + this.channelBinding = builder.channelBinding; + this.scramMechanism = builder.selectedScramMechanism; + this.stringPreparation = builder.stringPreparation; + this.username = builder.username; + this.password = builder.password != null ? builder.password.clone() : null; + this.saltedPassword = builder.saltedPassword; + this.clientKey = builder.clientKey; + this.serverKey = builder.serverKey; + this.nonce = builder.nonce; + this.cbindType = builder.cbindType; + this.cbindData = builder.cbindData; + this.authzid = builder.authzid; + } + + /** + * Returns the scram mechanism negotiated by this SASL client. + * + * @return the SCRAM mechanims selected during the negotiation + */ + public ScramMechanism getScramMechanism() { + return scramMechanism; + } + + /** + * Returns the text representation of a SCRAM {@code client-first-message}. + * + * @apiNote should be the initial call and can be called only once + * @return The {@code client-first-message} + */ + @Override + public ClientFirstMessage clientFirstMessage() { + if (currentState != Stage.NONE) { + throw new IllegalStateException("Invalid state for processing client first message"); + } + this.clientFirstMessage = + new ClientFirstMessage(channelBinding, cbindType, authzid, username, nonce); + this.currentState = Stage.CLIENT_FIRST; + return clientFirstMessage; + } + + /** + * Process the {@code server-first-message}, from its String representation. + * + * @apiNote should be called after {@link #clientFirstMessage()} and can be called only once + * @param serverFirstMessage The {@code server-first-message} + * @throws ScramParseException If the message is not a valid server-first-message + * @throws IllegalArgumentException If the message is null or empty + */ + @Override + public ServerFirstMessage serverFirstMessage(String serverFirstMessage) + throws ScramParseException { + if (currentState != Stage.CLIENT_FIRST) { + throw new IllegalStateException("Invalid state for processing server first message"); + } + checkNotEmpty(serverFirstMessage, "serverFirstMessage"); + this.serverFirstProcessor = + new ServerFirstProcessor(scramMechanism, stringPreparation, serverFirstMessage, nonce, + clientFirstMessage); + this.currentState = Stage.SERVER_FIRST; + return serverFirstProcessor.getServerFirstMessage(); + } + + /** + * Returns the text representation of a SCRAM {@code client-final-message}. + * + * @apiNote should be called after {@link #serverFirstMessage(String)} and can be called only once + * @return The {@code client-final-message} + */ + @Override + public ClientFinalMessage clientFinalMessage() { + if (currentState != Stage.SERVER_FIRST || serverFirstProcessor == null) { + throw new IllegalStateException("Invalid state for processing client final message"); + } + if (password != null) { + this.clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(password); + Arrays.fill(password, (char) 0); // clear password after use + } else if (saltedPassword != null) { + this.clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(saltedPassword); + } else if (clientKey != null && serverKey != null) { + this.clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(clientKey, serverKey); + } + ClientFinalMessage clientFinalMessage = clientFinalProcessor.clientFinalMessage(cbindData); + this.currentState = Stage.CLIENT_FINAL; + return clientFinalMessage; + } + + /** + * Process and verify the {@code server-final-message}, from its String representation. + * + * @apiNote should be called after {@link #clientFinalMessage()} and can be called only once + * @param serverFinalMessage The {@code server-final-message} + * @throws ScramParseException If the message is not a valid + * @throws ScramServerErrorException If the message is an error + * @throws ScramInvalidServerSignatureException If the verification fails + * @throws IllegalArgumentException If the message is null or empty + */ + @Override + public ServerFinalMessage serverFinalMessage(String serverFinalMessage) + throws ScramParseException, ScramServerErrorException, ScramInvalidServerSignatureException { + if (currentState != Stage.CLIENT_FINAL || clientFinalProcessor == null) { + throw new IllegalStateException("Invalid state for processing server final message"); + } + ServerFinalMessage receiveServerFinalMessage = + clientFinalProcessor.receiveServerFinalMessage(serverFinalMessage); + this.currentState = Stage.SERVER_FINAL; + return receiveServerFinalMessage; + } + + /** + * Creates a builder for {@link ScramClient ScramClient} instances. + * + * @return Builder instance to contruct a {@link ScramClient ScramClient} + */ + public static MechanismsBuildStage builder() { + return new Builder(); + } + + /** + * Builder stage for the advertised mechanisms. + */ + public interface MechanismsBuildStage { + + /** + * List of the advertised mechanisms that will be negotiated between the server and the client. + * + * @param scramMechanisms list with the IANA-registered mechanism name of this SASL client + * @return {@code this} builder for use in a chained invocation + */ + UsernameBuildStage advertisedMechanisms(@NotNull Collection<@NotNull String> scramMechanisms); + } + + /** + * Builder stage for the required username. + */ + public interface UsernameBuildStage { + + /** + * Sets the username. + * + * @param username the required username + * @return {@code this} builder for use in a chained invocation + */ + PasswordBuildStage username(@NotNull String username); + } + + /** + * Builder stage for the password (or a ClientKey/ServerKey, or SaltedPassword). + */ + public interface PasswordBuildStage { + + /** + * Sets the password. + * + * @param password the required password + * @return {@code this} builder for use in a chained invocation + */ + FinalBuildStage password(char @NotNull [] password); + + /** + * Sets the SaltedPassword. + * + * @param saltedPassword the required SaltedPassword + * @return {@code this} builder for use in a chained invocation + */ + FinalBuildStage saltedPassword(byte @NotNull [] saltedPassword); + + /** + * Sets the ClientKey/ServerKey. + * + * @param clientKey the required ClientKey + * @param serverKey the required ServerKey + * @return {@code this} builder for use in a chained invocation + */ + FinalBuildStage clientAndServerKey(byte @NotNull [] clientKey, byte @NotNull [] serverKey); + } + + /** + * Builder stage for the optional atributes and the final build() call. + */ + public interface FinalBuildStage { + + /** + * If the client supports channel binding negotiation, this method sets the type and data used + * for channel binding. + * + * @apiNote If {@code cbindType} or {@code cbindData} are null, sets the gs2-cbind-flag to 'n' + * and does not use channel binding. + * + * @param cbindType channel bynding type name + * @param cbindData channel binding data + * @return {@code this} builder for use in a chained invocation + */ + FinalBuildStage channelBinding(@Nullable String cbindType, byte @Nullable [] cbindData); + + /** + * Sets the StringPreparation, is recommended to leave the default SASL_PREPARATION. + * + * @param stringPreparation type of string preparation normalization + * @return {@code this} builder for use in a chained invocation + */ + FinalBuildStage stringPreparation(@NotNull StringPreparation stringPreparation); + + /** + * Sets the authzid. + * + * @param authzid the optional authorization id + * @return {@code this} builder for use in a chained invocation + */ + FinalBuildStage authzid(@NotNull String authzid); + + /** + * Sets a non-default length for the nonce generation. + * + *

The default value is 24. This call overwrites the length used for the client nonce. + * + * @param length The length of the nonce. Must be positive and greater than 0 + * @return {@code this} builder for use in a chained invocation + * @throws IllegalArgumentException If length is less than 1 + */ + FinalBuildStage nonceLength(int length); + + /** + * The client will use a default nonce generator, unless an external one is provided by this + * method. + * + * @apiNote you should rely on the default randomly generated nonce instead of this, this call + * exists mostly for testing with a predefined nonce + * @param nonceSupplier A supplier of valid nonce Strings. Please note that according to the SCRAM RFC only ASCII + * printable characters (except the comma, ',') are permitted on a nonce. Length is not + * limited. + * @return {@code this} builder for use in a chained invocation + * @throws IllegalArgumentException If nonceSupplier is null + */ + FinalBuildStage nonceSupplier(@NotNull Supplier<@NotNull String> nonceSupplier); + + /** + * Selects a non-default SecureRandom instance, based on the given algorithm and optionally + * provider. This SecureRandom instance will be used to generate secure random values, like the + * ones required to generate the nonce. Algorithm and provider names are those supported by the + * {@link SecureRandom} class. + * + * @param algorithm The name of the algorithm to use + * @param provider The name of the provider of SecureRandom. Might be null + * @return {@code this} builder for use in a chained invocation + * @throws IllegalArgumentException If algorithm is null, or either the algorithm or provider + * are not supported + */ + FinalBuildStage secureRandomAlgorithmProvider(@NotNull String algorithm, + @Nullable String provider); + + /** + * Returns the fully contructed ScramClient ready to start the message flow with the server. + * + * @return ScramClient specific for the set of parameters + * @throws IllegalArgumentException if any parameter set is invalid + */ + ScramClient build(); + } + + /** + * Builds instances of type {@link ScramClient ScramClient}. Initialize attributes and then invoke + * the {@link #build()} method to create an instance. + * + * @apiNote {@code Builder} is not thread-safe and generally should not be stored in a field or + * collection, but instead used immediately to create instances. + */ + static final class Builder + implements MechanismsBuildStage, UsernameBuildStage, PasswordBuildStage, FinalBuildStage { + + ScramMechanism selectedScramMechanism; + Collection scramMechanisms; + Gs2CbindFlag channelBinding = Gs2CbindFlag.CLIENT_NOT; + StringPreparation stringPreparation = StringPreparation.SASL_PREPARATION; + int nonceLength = 24; + String nonce; + SecureRandom secureRandom; + String username; + char[] password; + byte[] saltedPassword; + byte[] clientKey; + byte[] serverKey; + String cbindType; + byte[] cbindData; + String authzid; + Supplier nonceSupplier; + + private Builder() { + // called from ScramClient.builder() + } + + @Override + public FinalBuildStage stringPreparation(@NotNull StringPreparation stringPreparation) { + this.stringPreparation = checkNotNull(stringPreparation, "stringPreparation"); + return this; + } + + @Override + public FinalBuildStage channelBinding(@Nullable String cbindType, byte @Nullable [] cbindData) { + this.cbindType = cbindType; + this.cbindData = cbindData != null ? cbindData.clone() : null; + this.channelBinding = cbindType != null && cbindData != null + && !cbindType.isEmpty() && cbindData.length > 0 + ? Gs2CbindFlag.CLIENT_YES_SERVER_NOT + : Gs2CbindFlag.CLIENT_NOT; + return this; + } + + @Override + public FinalBuildStage authzid(@NotNull String authzid) { + this.authzid = checkNotEmpty(authzid, "authzid"); + return this; + } + + @Override + public PasswordBuildStage username(@NotNull String username) { + this.username = checkNotEmpty(username, "username"); + return this; + } + + @Override + public FinalBuildStage password(char @NotNull [] password) { + this.password = checkNotEmpty(password, "password"); + return this; + } + + @Override + public FinalBuildStage saltedPassword(byte @NotNull [] saltedPassword) { + this.saltedPassword = checkNotNull(saltedPassword, "saltedPassword"); + return this; + } + + @Override + public FinalBuildStage clientAndServerKey(byte @NotNull [] clientKey, + byte @NotNull [] serverKey) { + this.clientKey = checkNotNull(clientKey, "clientKey"); + this.serverKey = checkNotNull(serverKey, "serverKey"); + return this; + } + + @Override + public UsernameBuildStage advertisedMechanisms( + @NotNull Collection<@NotNull String> scramMechanisms) { + checkNotNull(scramMechanisms, "scramMechanisms"); + checkArgument(!scramMechanisms.isEmpty(), "scramMechanisms"); + this.scramMechanisms = scramMechanisms; + return this; + } + + @Override + public FinalBuildStage nonceLength(int length) { + this.nonceLength = gt0(length, "length"); + return this; + } + + @Override + public FinalBuildStage nonceSupplier(@NotNull Supplier<@NotNull String> nonceSupplier) { + this.nonceSupplier = checkNotNull(nonceSupplier, "nonceSupplier"); + return this; + } + + @Override + public FinalBuildStage secureRandomAlgorithmProvider(@NotNull String algorithm, + @Nullable String provider) { + try { + this.secureRandom = null == provider + ? SecureRandom.getInstance(algorithm) + : SecureRandom.getInstance(algorithm, provider); + } catch (NoSuchAlgorithmException | NoSuchProviderException ex) { + throw new IllegalArgumentException("Invalid algorithm or provider", ex); + } + return this; + } + + @Override + public ScramClient build() { + final SecureRandom random = secureRandom != null ? secureRandom : new SecureRandom(); + this.nonce = nonceSupplier != null + ? nonceSupplier.get() + : ScramFunctions.nonce(nonceLength, random); + this.selectedScramMechanism = mechanismNegotiation(); + return new ScramClient(this); + } + + private ScramMechanism mechanismNegotiation() { + final ScramMechanism cbind = selectMechanism(scramMechanisms, true); + final ScramMechanism noncbind = selectMechanism(scramMechanisms, false); + ScramMechanism mechanismNegotiaion = cbind != null ? cbind : noncbind; + if (mechanismNegotiaion == null) { + throw new IllegalArgumentException("Either a bare or plus mechanism must be present"); + } + + if (channelBinding == Gs2CbindFlag.CLIENT_YES_SERVER_NOT + && mechanismNegotiaion.isPlus()) { + // Client and server supports channel binding + this.channelBinding = Gs2CbindFlag.CHANNEL_BINDING_REQUIRED; + } else { + // Client or server does not support channel binding + if (noncbind == null) { + throw new IllegalArgumentException("A non-PLUS mechanism was not advertised"); + } + this.cbindType = null; + this.cbindData = null; + mechanismNegotiaion = noncbind; + } + if (channelBinding == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED + && (cbindType == null || cbindData == null)) { + throw new IllegalArgumentException("Channel Binding type and data are required"); + } + + return mechanismNegotiaion; + } + + /** + * This method classifies SCRAM mechanisms by two properties: whether they support channel + * binding; and a priority, which is higher for safer algorithms (like SHA-256 vs SHA-1). + * + * @param channelBinding True to select {@code -PLUS} mechanisms. + * @param scramMechanisms The mechanisms supported by the other peer + * @return The selected mechanism, or null if no mechanism matched + */ + private static @Nullable ScramMechanism selectMechanism( + @NotNull Collection<@NotNull String> scramMechanisms, + boolean channelBinding) { + ScramMechanism selectedMechanism = null; + for (String mechanism : scramMechanisms) { + ScramMechanism candidateMechanism = ScramMechanism.byName(mechanism); + if (candidateMechanism != null && candidateMechanism.isPlus() == channelBinding + && (selectedMechanism == null + || candidateMechanism.ordinal() > selectedMechanism.ordinal())) { + selectedMechanism = candidateMechanism; + } + } + return selectedMechanism; + } + + } + +} diff --git a/scram-client/src/main/java/com/ongres/scram/client/ServerFirstProcessor.java b/scram-client/src/main/java/com/ongres/scram/client/ServerFirstProcessor.java new file mode 100644 index 0000000..b0fc70d --- /dev/null +++ b/scram-client/src/main/java/com/ongres/scram/client/ServerFirstProcessor.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.client; + +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.Base64; + +import com.ongres.scram.common.ClientFirstMessage; +import com.ongres.scram.common.ScramFunctions; +import com.ongres.scram.common.ScramMechanism; +import com.ongres.scram.common.ServerFirstMessage; +import com.ongres.scram.common.StringPreparation; +import com.ongres.scram.common.exception.ScramParseException; +import org.jetbrains.annotations.NotNull; + +/** + * Process a received server-first-message. Generate by calling + * {@link ScramClient#receiveServerFirstMessage(String)}. + */ +final class ServerFirstProcessor { + private final ScramMechanism scramMechanism; + private final StringPreparation stringPreparation; + private final ClientFirstMessage clientFirstMessage; + private final ServerFirstMessage serverFirstMessage; + + ServerFirstProcessor(ScramMechanism scramMechanism, StringPreparation stringPreparation, + @NotNull String receivedServerFirstMessage, @NotNull String nonce, + @NotNull ClientFirstMessage clientFirstMessage) throws ScramParseException { + this.scramMechanism = scramMechanism; + this.stringPreparation = stringPreparation; + this.serverFirstMessage = ServerFirstMessage.parseFrom(receivedServerFirstMessage, nonce); + this.clientFirstMessage = clientFirstMessage; + } + + @NotNull + ServerFirstMessage getServerFirstMessage() { + return serverFirstMessage; + } + + /** + * Generates a {@code ClientFinalProcessor}, that allows to generate the client-final-message and + * also receive and parse the server-first-message. It is based on the user's password. + * + * @param password The user's password + * @return The handler + * @throws IllegalArgumentException If the message is null or empty + */ + ClientFinalProcessor clientFinalProcessor(char[] password) { + return new ClientFinalProcessor( + scramMechanism, + stringPreparation, + checkNotEmpty(password, "password"), + Base64.getDecoder().decode(serverFirstMessage.getSalt().getBytes(UTF_8)), + clientFirstMessage, + serverFirstMessage); + } + + /** + * Generates a {@code ClientFinalProcessor}, that allows to generate the client-final-message and + * also receive and parse the server-first-message. It is based on the clientKey and serverKey, + * which, if available, provide an optimized path versus providing the original user's password. + * + * @param clientKey The client key, as per the SCRAM algorithm. It can be generated with: + * {@link ScramFunctions#clientKey(ScramMechanism, byte[])} + * @param serverKey The server key, as per the SCRAM algorithm. It can be generated with: + * {@link ScramFunctions#serverKey(ScramMechanism, byte[])} + * @return The handler + * @throws IllegalArgumentException If the clientKey/serverKey is null + */ + ClientFinalProcessor clientFinalProcessor(byte[] clientKey, byte[] serverKey) { + return new ClientFinalProcessor( + scramMechanism, + checkNotNull(clientKey, "clientKey"), + checkNotNull(serverKey, "serverKey"), + clientFirstMessage, + serverFirstMessage); + } + + /** + * Generates a {@code ClientFinalProcessor}, that allows to generate the client-final-message and + * also receive and parse the server-first-message. It is based on the saltedPassword, + * which, if available, provide an optimized path versus providing the original user's password. + * + * @param saltedPassword The salted password, as per the SCRAM algorithm. It can be generated + * with: + * {@link ScramFunctions#saltedPassword(ScramMechanism, StringPreparation, char[], byte[], int)} + * @return The handler + * @throws IllegalArgumentException If the saltedPassword is null + */ + ClientFinalProcessor clientFinalProcessor(byte[] saltedPassword) { + return new ClientFinalProcessor( + scramMechanism, + checkNotNull(saltedPassword, "saltedPassword"), + clientFirstMessage, + serverFirstMessage); + } +} diff --git a/scram-client/src/main/java/com/ongres/scram/client/package-info.java b/scram-client/src/main/java/com/ongres/scram/client/package-info.java new file mode 100644 index 0000000..22a47b8 --- /dev/null +++ b/scram-client/src/main/java/com/ongres/scram/client/package-info.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/** + * This module expose the client implementation of Salted Challenge Response + * Authentication Mechanism (SCRAM). It provides a high level and easy to use API to negotiate the + * mechanism and the message flow used for authentication. + */ + +package com.ongres.scram.client; diff --git a/scram-client/src/main/java9/module-info.java b/scram-client/src/main/java9/module-info.java new file mode 100644 index 0000000..493c54f --- /dev/null +++ b/scram-client/src/main/java9/module-info.java @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +module com.ongres.scram.client { + requires transitive com.ongres.scram.common; + exports com.ongres.scram.client; +} \ No newline at end of file diff --git a/scram-client/src/test/java/com/example/ChannelBindingNegotiationTest.java b/scram-client/src/test/java/com/example/ChannelBindingNegotiationTest.java new file mode 100644 index 0000000..381bd34 --- /dev/null +++ b/scram-client/src/test/java/com/example/ChannelBindingNegotiationTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.example; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Base64; + +import com.ongres.scram.client.ScramClient; +import com.ongres.scram.common.ClientFirstMessage; +import com.ongres.scram.common.Gs2CbindFlag; +import com.ongres.scram.common.exception.ScramParseException; +import com.ongres.scram.common.util.TlsServerEndpoint; +import org.junit.jupiter.api.Test; + +/** + * Servers that support the use of channel binding SHOULD advertise + * both the non-PLUS (SCRAM-{@code hash-function}) and PLUS-variant (SCRAM- + * {@code hash-function}-PLUS) mechanism name. If the server cannot + * support channel binding, it SHOULD advertise only the non-PLUS- + * variant. If the server would never succeed in the authentication + * of the non-PLUS-variant due to policy reasons, it MUST advertise + * only the PLUS-variant. + */ +class ChannelBindingNegotiationTest { + + private static final byte[] CBIND_DATA = Base64.getDecoder().decode("Dv4abLuK1TiHcq3tJXrHODILGF" + + "QuC1M4kfP4w7dyRvjadaqGq8D/Po1XeJZpzUqal+mAKXNGytneo5KPOsJnYA=="); + + /** + * If the client supports channel binding and the server does not + * appear to (i.e., the client did not see the -PLUS name advertised + * by the server), then the client MUST NOT use an "n" gs2-cbind- + * flag. + */ + @Test + void clientYesServerNo() throws ScramParseException { + ScramClient scramSession = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256")) + .username("user") + .password("pencil".toCharArray()) + .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, CBIND_DATA) + .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") + .build(); + assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); + ClientFirstMessage msg = scramSession.clientFirstMessage(); + assertEquals(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, msg.getGs2Header().getChannelBindingFlag()); + } + + /** + * Clients that support mechanism negotiation and channel binding + * MUST use a "p" gs2-cbind-flag when the server offers the PLUS- + * variant of the desired GS2 mechanism. + */ + @Test + void channelBindingRequired() throws ScramParseException { + ScramClient scramSession = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) + .username("user") + .password("pencil".toCharArray()) + .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, CBIND_DATA) + .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") + .build(); + assertEquals("SCRAM-SHA-256-PLUS", scramSession.getScramMechanism().getName()); + ClientFirstMessage msg = scramSession.clientFirstMessage(); + assertEquals(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, msg.getGs2Header().getChannelBindingFlag()); + } + + /** + * If the client does not support channel binding, then it MUST use + * an "n" gs2-cbind-flag. Conversely, if the client requires the use + * of channel binding then it MUST use a "p" gs2-cbind-flag. Clients + * that do not support mechanism negotiation never use a "y" gs2- + * cbind-flag, they use either "p" or "n" according to whether they + * require and support the use of channel binding or whether they do + * not, respectively. + */ + @Test + void clientNo() throws ScramParseException { + ScramClient scramSession = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) + .username("user") + .password("pencil".toCharArray()) + .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") + .build(); + assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); + ClientFirstMessage msg = scramSession.clientFirstMessage(); + assertEquals(Gs2CbindFlag.CLIENT_NOT, msg.getGs2Header().getChannelBindingFlag()); + } + +} diff --git a/scram-client/src/test/java/com/example/ScramClientTest.java b/scram-client/src/test/java/com/example/ScramClientTest.java new file mode 100644 index 0000000..9f58548 --- /dev/null +++ b/scram-client/src/test/java/com/example/ScramClientTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.example; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import com.ongres.scram.client.ScramClient; +import com.ongres.scram.common.ClientFinalMessage; +import com.ongres.scram.common.util.TlsServerEndpoint; +import org.junit.jupiter.api.Test; + +class ScramClientTest { + + @Test + void completeTest() + throws CertificateException, IOException { + final X509Certificate cert = getCert(); + final byte[] channelBindingData = TlsServerEndpoint.getChannelBindingData(cert); + + ScramClient scramSession = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256", "SCRAM-SHA-256-PLUS")) + .username("user") + .password("pencil".toCharArray()) + .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, channelBindingData) + .nonceSupplier(() -> "rOprNGfwEbeRWgbNEkqO") + .build(); + assertEquals("SCRAM-SHA-256-PLUS", scramSession.getScramMechanism().getName()); + assertEquals("p=tls-server-end-point,,n=user,r=rOprNGfwEbeRWgbNEkqO", + scramSession.clientFirstMessage().toString()); + + assertDoesNotThrow( + () -> scramSession.serverFirstMessage( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," + + "s=W22ZaJ0SNY7soEsUEjb6gQ==," + + "i=4096")); + + ClientFinalMessage clientFinalMessage = scramSession.clientFinalMessage(); + assertEquals( + "c=cD10bHMtc2VydmVyLWVuZC1wb2ludCwsDv4abLuK1TiHcq3tJ" + + "XrHODILGFQuC1M4kfP4w7dyRvjadaqGq8D/Po1XeJZpzUqal+mAKXNGytneo5KPOsJnYA==" + + ",r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0" + + ",p=WIBtRXGH4I4R2CU1/tHa2YREwrJjLFa3/pKJQH/0Ofo=", + clientFinalMessage.toString()); + + assertDoesNotThrow( + () -> scramSession.serverFinalMessage("v=9k31qsYXd74d6BnbFf9jE+r9un6a8ou85FYeNxDAdqc=")); + } + + private X509Certificate getCert() throws CertificateException, IOException { + String pemFilePath = "/SHA512withRSA.pem"; + // Create a CertificateFactory object for X.509 + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + final X509Certificate cert; + try (InputStream inputStream = getClass().getResourceAsStream(pemFilePath)) { + // Check if the file exists + assertNotNull(inputStream, "Certificate file not found"); + // Load the PEM file as an X.509 certificate + cert = (X509Certificate) certFactory.generateCertificate(inputStream); + // Perform your assertions or further tests here + assertNotNull(cert); + assertEquals("SHA512withRSA", cert.getSigAlgName()); + } + return cert; + } + + @Test + void iterationTest() + throws CertificateException, IOException { + final X509Certificate cert = getCert(); + final byte[] channelBindingData = TlsServerEndpoint.getChannelBindingData(cert); + + ScramClient scramSession = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256")) + .username("postgres") + .password("pencil".toCharArray()) + .channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, channelBindingData) + .nonceSupplier(() -> "1q^MGrWUi{etW+H7(#k431kB") + .build(); + assertEquals("SCRAM-SHA-256", scramSession.getScramMechanism().getName()); + assertEquals("y,,n=postgres,r=1q^MGrWUi{etW+H7(#k431kB", + scramSession.clientFirstMessage().toString()); + + assertDoesNotThrow( + () -> scramSession.serverFirstMessage( + "r=1q^MGrWUi{etW+H7(#k431kBdAr3CWX7B6houDP4f7Z2XEpZ," + + "s=Fgh8JU2AlRjBHUsIU/GgtQ==," + + "i=1000000")); + + ClientFinalMessage clientFinalMessage = scramSession.clientFinalMessage(); + assertEquals( + "c=eSws," + + "r=1q^MGrWUi{etW+H7(#k431kBdAr3CWX7B6houDP4f7Z2XEpZ," + + "p=vQ3IyYl3LvjWOlK2c0IP5QAi6XB7Dm0Axo0V51DcHZA=", + clientFinalMessage.toString()); + + assertDoesNotThrow( + () -> scramSession.serverFinalMessage("v=sz/isCwVSUn/TBWeYABz6WaoZIcfsui9NPaJCoxxAjY=")); + } +} diff --git a/scram-client/src/test/java/com/ongres/scram/JarFileCheckIT.java b/scram-client/src/test/java/com/ongres/scram/JarFileCheckIT.java new file mode 100644 index 0000000..d6c7aec --- /dev/null +++ b/scram-client/src/test/java/com/ongres/scram/JarFileCheckIT.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.module.ModuleDescriptor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class JarFileCheckIT { + + private static JarFile jarFile; + private static Path buildJarPath; + + @BeforeAll + static void beforeAll() throws IOException { + buildJarPath = Paths.get(System.getProperty("buildJar")); + assertTrue(Files.exists(buildJarPath)); + jarFile = new JarFile(buildJarPath.toFile(), true); + } + + @AfterAll + static void afterAll() throws IOException { + jarFile.close(); + } + + @Test + void checkLicense() throws IOException { + JarEntry jarLicense = jarFile.getJarEntry("META-INF/LICENSE"); + assertNotNull(jarLicense, "LICENSE file should be present in the final JAR file"); + try (InputStream is = jarFile.getInputStream(jarLicense); + BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) { + String line = reader.readLine(); + assertEquals("Copyright (c) 2017 OnGres, Inc.", line); + } + } + + @Test + void checkMultiReleaseManifest() throws IOException { + Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); + String multiReleaseValue = mainAttributes.getValue(new Attributes.Name("Multi-Release")); + assertNotNull(multiReleaseValue); + assertEquals("true", multiReleaseValue); + } + + @Test + void checkModuleInfoPresent() throws IOException { + JarEntry jarModuleInfo = jarFile.getJarEntry("META-INF/versions/9/module-info.class"); + ModuleDescriptor moduleDescriptor = ModuleDescriptor.read(jarFile.getInputStream(jarModuleInfo)); + assertNotNull(moduleDescriptor); + assertEquals("com.ongres.scram.client", moduleDescriptor.name()); + } +} diff --git a/scram-client/src/test/java/com/ongres/scram/client/RfcExampleSha1.java b/scram-client/src/test/java/com/ongres/scram/client/RfcExampleSha1.java new file mode 100644 index 0000000..d2aabcf --- /dev/null +++ b/scram-client/src/test/java/com/ongres/scram/client/RfcExampleSha1.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.client; + +/** + * Constants for examples of the RFC for SHA-1 tests. + */ +class RfcExampleSha1 { + public static final String USER = "user"; + public static final String PASSWORD = "pencil"; + public static final String CLIENT_NONCE = "fyko+d2lbbFgONRv9qkxdawL"; + public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = + "n=" + USER + ",r=" + CLIENT_NONCE; + public static final String CLIENT_FIRST_MESSAGE = + "n," + "," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; + public static final String SERVER_SALT = "QSXCR+Q6sek8bf92"; + public static final int SERVER_ITERATIONS = 4096; + public static final String SERVER_NONCE = "3rfcNHYJY1ZVvWVs7j"; + public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; + public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT + + ",i=" + SERVER_ITERATIONS; + public static final String GS2_HEADER_BASE64 = "biws"; + public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 + + ",r=" + FULL_NONCE; + public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," + + SERVER_FIRST_MESSAGE + "," + + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; + public static final String CLIENT_FINAL_MESSAGE_PROOF = "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="; + public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF + + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; + public static final String SERVER_FINAL_MESSAGE = "v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; +} diff --git a/scram-client/src/test/java/com/ongres/scram/client/ScramBuilderTest.java b/scram-client/src/test/java/com/ongres/scram/client/ScramBuilderTest.java new file mode 100644 index 0000000..0855156 --- /dev/null +++ b/scram-client/src/test/java/com/ongres/scram/client/ScramBuilderTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.client; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Arrays; +import java.util.Base64; + +import com.ongres.scram.common.ClientFinalMessage; +import com.ongres.scram.common.ScramFunctions; +import com.ongres.scram.common.ScramMechanism; +import com.ongres.scram.common.StringPreparation; +import com.ongres.scram.common.exception.ScramParseException; +import org.junit.jupiter.api.Test; + +class ScramBuilderTest { + @Test + void getValid() throws ScramParseException { + ScramClient client1 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1")) + .username("*") + .password("*".toCharArray()) + .stringPreparation(StringPreparation.POSTGRESQL_PREPARATION) + .build(); + assertNotNull(client1); + assertNotNull(client1.clientFirstMessage()); + ScramClient client2 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS")) + .username("*") + .password("*".toCharArray()) + .stringPreparation(StringPreparation.SASL_PREPARATION) + .nonceLength(12) + .channelBinding("tls-server-end-point", new byte[0]) + .build(); + assertNotNull(client2); + assertNotNull(client2.clientFirstMessage()); + ScramClient client3 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS")) + .username("*") + .password("*".toCharArray()) + .stringPreparation(StringPreparation.NO_PREPARATION) + .nonceLength(36) + .build(); + assertNotNull(client3); + assertNotNull(client3.clientFirstMessage()); + ScramClient client4 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS")) + .username("*") + .password("*".toCharArray()) + .stringPreparation(StringPreparation.NO_PREPARATION) + .nonceLength(64) + .build(); + assertNotNull(client4); + assertNotNull(client4.clientFirstMessage()); + ScramClient client5 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS")) + .username("*") + .password("*".toCharArray()) + .stringPreparation(StringPreparation.NO_PREPARATION) + // .secureRandomAlgorithmProvider("PKCS11", null) + .nonceLength(64) + .build(); + assertNotNull(client5); + assertNotNull(client5.clientFirstMessage()); + ScramClient client6 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1")) + .username("*") + .password("*".toCharArray()) + .build(); + assertNotNull(client6); + assertNotNull(client6.clientFirstMessage()); + ScramClient client7 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-256-PLUS")) + .username("*") + .password("*".toCharArray()) + .channelBinding("tls-server-end-point", new byte[10]) + .stringPreparation(StringPreparation.NO_PREPARATION) + .build(); + assertNotNull(client7); + assertNotNull(client7.clientFirstMessage()); + + byte[] saltedPassword = ScramFunctions.saltedPassword(ScramMechanism.SCRAM_SHA_1, + StringPreparation.SASL_PREPARATION, + RfcExampleSha1.PASSWORD.toCharArray(), + Base64.getDecoder().decode(RfcExampleSha1.SERVER_SALT), 4096); + ScramClient client8 = ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS")) + .username(RfcExampleSha1.USER) + .saltedPassword(saltedPassword) + .nonceSupplier(() -> RfcExampleSha1.CLIENT_NONCE) + .build(); + assertNotNull(client8); + assertEquals("SCRAM-SHA-1", client8.getScramMechanism().getName()); + assertNotNull(client8.clientFirstMessage()); + assertThrows(IllegalStateException.class, () -> client8.clientFinalMessage()); + assertDoesNotThrow(() -> client8.serverFirstMessage(RfcExampleSha1.SERVER_FIRST_MESSAGE)); + assertThrows(IllegalStateException.class, () -> client8.clientFirstMessage()); + assertThrows(IllegalStateException.class, + () -> client8.serverFinalMessage(RfcExampleSha1.SERVER_FINAL_MESSAGE)); + ClientFinalMessage clientFinalMessage = client8.clientFinalMessage(); + assertEquals(RfcExampleSha1.CLIENT_FINAL_MESSAGE, clientFinalMessage.toString()); + assertThrows(IllegalStateException.class, + () -> client8.serverFirstMessage(RfcExampleSha1.SERVER_FIRST_MESSAGE)); + assertDoesNotThrow( + () -> client8.serverFinalMessage(RfcExampleSha1.SERVER_FINAL_MESSAGE)); + } + + @Test + void getInvalid() { + assertThrows(IllegalArgumentException.class, () -> ScramClient.builder() + .advertisedMechanisms(Arrays.asList("SCRAM-SHA-1-PLUS")) + .username("*") + .password("*".toCharArray()) + .stringPreparation(StringPreparation.NO_PREPARATION) + .build()); + } + +} diff --git a/scram-client/src/test/resources/SHA3-512withECDSA.pem b/scram-client/src/test/resources/SHA3-512withECDSA.pem new file mode 100644 index 0000000..bec2e17 --- /dev/null +++ b/scram-client/src/test/resources/SHA3-512withECDSA.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICbTCCAc2gAwIBAgIUF/r9sHKRKU0yh9d1vq6wCNdF2NswCwYJYIZIAWUDBAMM +MEcxCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxEzARBgNVBAcMCkFsY29i +ZW5kYXMxEjAQBgNVBAoMCU9uR3JlcyBTTDAeFw0yNDAzMjAxNTIzNDdaFw0zNDAz +MTgxNTIzNDdaMEcxCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxEzARBgNV +BAcMCkFsY29iZW5kYXMxEjAQBgNVBAoMCU9uR3JlcyBTTDCBmzAQBgcqhkjOPQIB +BgUrgQQAIwOBhgAEAbdhZ97Wg6jLy+vwojByzoRwQOdPbY1Ye6CPY2bmDJ+At7j1 +dw+a/lB3MkTMhIlCPgKO+hJty8dZBIdg/hCWNtqtAb6tSsv6RQw3nAv1pY5d5qWF +nVQzYiAWLsZ5MzTWKMcy3xJTAW+YEBwCzT3Ov6mO9wfz20I5SNLjXLaixMjh4Mup +o1MwUTAdBgNVHQ4EFgQUlhN7FIdCf4bBZS1NDmCLpJHOJWwwHwYDVR0jBBgwFoAU +lhN7FIdCf4bBZS1NDmCLpJHOJWwwDwYDVR0TAQH/BAUwAwEB/zALBglghkgBZQME +AwwDgYwAMIGIAkIByQyFB6lnc/Hp/eTjibbydSrGp/3ijwHFWkf44Bb9Xxsko5g4 +97DtrgL/sRAD9HPnF6P4jvGQmKT4r40e4Z4xg5oCQgGxVvM9YeGFgAHWesH/Uz8A +rEySKFQU6HWOg3VGk/i+h8Mf3Q3qOMw0Mq8NTHp0t4yNwwltmZDRpzZvdF3dINMa +Cg== +-----END CERTIFICATE----- diff --git a/scram-client/src/test/resources/SHA512withRSA.pem b/scram-client/src/test/resources/SHA512withRSA.pem new file mode 100644 index 0000000..c58740c --- /dev/null +++ b/scram-client/src/test/resources/SHA512withRSA.pem @@ -0,0 +1,54 @@ +-----BEGIN CERTIFICATE----- +MIIJpzCCBZugAwIBAgIUWzmIIuqdxXzf3oQiJ/UhLS9r2kcwDQYJKoZIhvcNAQEN +BQAwbzELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDETMBEGA1UEBwwKQWxj +b2JlbmRhczEUMBIGA1UECgwLT25HcmVzIEluYy4xDjAMBgNVBAsMBVNDUkFNMRQw +EgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yNDAzMTMxNzQxNTVaFw0zNDAzMTExNzQx +NTVaMG8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxEzARBgNVBAcMCkFs +Y29iZW5kYXMxFDASBgNVBAoMC09uR3JlcyBJbmMuMQ4wDAYDVQQLDAVTQ1JBTTEU +MBIGA1UEAwwLZXhhbXBsZS5jb20wggQWMA0GCSqGSIb3DQEBAQUAA4IEAwAwggP+ +AoID9QCtTBX88QlbjYb3FsXDDWPZ6P3qrNjXhdecKdpxv81K2I3W/iBz3tEpj+FT +4pfWu5jYAf85yrnkg2/eFD2zGrWq+qfQnsFE+6N0M/JW6t5hTVz3rAh2C58I0cMo +WIWJt4808b7VLeIwMJwE6hcGSIgyuVOgEhEqrnSmXD3PIoAkrAvpiLpqOCAdG7xN +QUXySOeq8yIC3uCVwTqMonkB488RcaTMlDO4+3GCuiIHhiqAjtVGlmn0xTaLZdA7 +3hQPDs3qkqWHM7a2pLNrdPoJgF5BdsTq5Rbw5TzNe8lm9NjyNbUilOPloci0VNg5 +qgvKuRRqd1285wzCMEb/FQzu6eTv57pzmOzWyzd5D0K53GZv6Cb+yaz7xx6sUTDF +q2qQClllXMGu+P0kpkdH2qBIRuxrOiPvcZ2djKk+xa4fDbfLI0Hk1STpqZq1KjOR +1+eFYGCawo/JFjMsQfyq/CVQDqy5djnKKOrbCKl3ApEdSvTBcoBbpIroKMqMIssB +owu0b8bqEOl/tVamGm8oELh6iDQMD5OKlg5ruihp0VTvGeGbDU6LTksx0dygvIen +dtIUoiEfaP/7LfTUWQ6X2dc00FD8lRRSygKHqg/Rq7HJ5Jmrfz7e4ozOnAqemxv2 +P0kNqT9qhgBdbNaNlwQsVf5ikbLeRQCSVvgKhcmB0rM57E+7cf3HtMZ5ECfbgF91 +cK67bVfFnnlR6apEN1rbBxES/OHJu7X6yr8CJc+FRHyg48/hLIH1p6aylnqlZxOX +kTSOxfM6sgUsR6Krt7FWD6Dah7WAWpzokQpxm+Kdr17WBJxXcr9HptBApap5CWP2 +0dtg3Uz//ParEu2e9PWeU+Dt6oJPGg7FuTke+70w4jzE6IrBlGR2+uJBazH4ibC7 +k2uiaTB6OSu00G5K7PBok2C+rFacM+3BA7xuk84zhz/VHBi1g3yVtiaRaZ/lXHNU +M0M6Xmtzqe10XRHo22habv4sLa6KnC27gMNQ2U8D9G3JIs1cvOqf1rFY6nmU2V7f +3Lmi9jmxoSFHYHWYF6hgAiufVhEmbOAeA34usH2DKqLJdbVBfI9eTcfxjQZiHxKp +2dAyqGP6fRhlijLpCyDk2yGb8r5oMpOvf234VgVAflPnaeRUvTpX8jAgy6N2WYXc +uYuSMsNz762NlwE2iCD88jAgKlOBj4Jl2/E6UDGBocdpETF+yW9MRyNYL+msL1Yx +UunYHk2CQhAt2sQBjveJpPUE+lwZUJ7s2ItHalb7oiffGCaIMQkCuzoNgb7iu1JE +/Nzl/OjUJVYnavVY5Wb/VqJvUAeWWb4u8a617ip72h/1xyOm9mw7w+VFe38aqjk8 +89hH8J+3vzXjAgMBAAGjUzBRMB0GA1UdDgQWBBQoMJjtzt7sWYytC3HeG63r8/Z/ +gzAfBgNVHSMEGDAWgBQoMJjtzt7sWYytC3HeG63r8/Z/gzAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBDQUAA4ID9QCLpHpEX7LAwTevNucEwnw1N5leRCRleK0B +zTdCQiLHNJlZx/I9JnKTymc87FStFXE/JODDWu3Ggo6XXYjDeeKKCMfM9YYaOvAX +2Rmyjzn0wU9dgSIcY7LuepIsfy+Ko9olyT3dTQfqdfl44qiMFwtRhI5Xv6XG7QWa +icUScrG1zBfsQtplpHTM67clnCqytVaHSQBxkBl9G/+nTz1DjYz0avOWQ4A0Q7S1 +N9duk5pMGW7CcP7UP646MMYLSKO8q8AHRhKCLgq2f4FB1jGu80l3VBc8kMCX0Osb +mjF1dFtYcWgzZlAgbPOEhr22d7TdYFfpuyl9Td60VdlzFWKe8mK1w8cu0DxVYML5 +4NP+QeL0SduPmzRNgfF+0kHiav7wwg7RXh6aILyt9w9nCubhYhbEyai+MRX//iVg +J+DhBHmB7FgWlDx+wzIjDjRJI2BJB9EK5gIM97Cew6cELWiDvKY836vrAh+TZg2G +A5GwFxr3kjbYs1G2sOMaSH+Fq8+VpIBr9cQ7v1WDbrsYzStnjYaFx+uqN+SjsPh8 +EgT+8+SiaKioS2yOrctovcVXLiB5HJEQdkaTWApZ+KVluJQLF0XFqf1gp2mBJVjy +IN2HpRV+JUX/CHGBXE8KeH5aOJPjWNq3k2vJy+3USjH7YvNysLOhekOvRuh5uNxU +1yVWmPWsv5ww0TJZT5L0t+KHTvoSZE2I12PnonDz1t4sGQFXexkB402tkbV2nxJB +6fnks5RMDhXjr5b9/Oca02PPWSuu35ypo36Sh5cBtQ/lRT30cRcW9v1Pkk1N5I+4 +FXFEjr5kPantmd2zM5m1z+EfWapVINMv4T9B4bwR8jskM2Vm3a+1Gk1NY5cRxHdS +T+EQY+Z40mWxieRNHe4ss5k7uefBq3GXw1FGUQQfHFctV/yKMhjgLXleQcyr82rh +t+vsfiU6Z3fIvb8MZzzEfNkLcnUTUkbeHxFYDvEter/uVFVpWyzYg9o8pliuLMf7 +ggA5RgA7y6MH5XxcgHEVNx572eHdpFx6q8signasHW8p9fps60rtJGVvF0x98vK1 +VDr2CIbKgnqprIc31Xkoata3U7Xevubt6Pm/kyBOs9SnASIHdZvlLQqILOKxjaMf +ZD2rJ6rNY+8V14NHXTaEAgefMrF42RXZ4RxGHTNDrxkjieyUDMc9dpnOdCxT50Ai +8NKdtfHT/K0yBNvDQtg4OoHDxy9ikFYyRKJrnVCCA2hfSbuMxiAg1fewrUBPVKy4 +T98gvWxCQ9fdT/wjPgn9oMv1lEKtM0JKdM0SshK7EA3PWaQfsOfvd4v00dZaDxRN +W4XrAMUHzJ4YICoubSNVl/gXCgqF8kcEAiJY +-----END CERTIFICATE----- diff --git a/scram-common/pom.xml b/scram-common/pom.xml new file mode 100644 index 0000000..d828b41 --- /dev/null +++ b/scram-common/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + com.ongres.scram + scram-parent + 3.2 + ../scram-parent/pom.xml + + + scram-common + + SCRAM - Common + + + + com.ongres.stringprep + saslprep + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + + run-its + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-invoker-plugin + + + + + + + diff --git a/scram-common/src/main/java/com/ongres/scram/common/AbstractCharAttributeValue.java b/scram-common/src/main/java/com/ongres/scram/common/AbstractCharAttributeValue.java new file mode 100644 index 0000000..ef0ea8b --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/AbstractCharAttributeValue.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Construct and write generic CharAttribute-Value pairs. + * + *

Concrete sub-classes should also provide a static parse(String) creation method. + */ +abstract class AbstractCharAttributeValue extends StringWritable { + + private final char charAttribute; + private final @Nullable String value; + + protected AbstractCharAttributeValue(@NotNull T charAttribute, @Nullable String value) { + if (null != value && value.isEmpty()) { + throw new IllegalArgumentException("Value should be either null or non-empty"); + } + this.charAttribute = checkNotNull(charAttribute, "attribute").getChar(); + this.value = value; + } + + public final char getChar() { + return charAttribute; + } + + public @Nullable String getValue() { + return value; + } + + @Override + StringBuilder writeTo(StringBuilder sb) { + sb.append(charAttribute); + if (null != value) { + sb.append('=').append(value); + } + return sb; + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/AbstractScramMessage.java b/scram-common/src/main/java/com/ongres/scram/common/AbstractScramMessage.java new file mode 100644 index 0000000..c8b3f6a --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/AbstractScramMessage.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import org.jetbrains.annotations.NotNull; + +/** + * Basic implementation of the StringWritable interface, that overrides the toString() method. + */ +abstract class AbstractScramMessage extends StringWritable { + + /** + * String representation of the SCRAM message. + */ + @Override + public final @NotNull String toString() { + return writeTo(new StringBuilder(48)).toString(); + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/CharSupplier.java b/scram-common/src/main/java/com/ongres/scram/common/CharSupplier.java new file mode 100644 index 0000000..7ef4be2 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/CharSupplier.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +/** + * Represents an attribute (a key name) that is represented by a single char. + */ +interface CharSupplier { + + /** + * Return the char used to represent this attribute. + * + * @return The character of the attribute + */ + char getChar(); + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ClientFinalMessage.java b/scram-common/src/main/java/com/ongres/scram/common/ClientFinalMessage.java new file mode 100644 index 0000000..f025cf8 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ClientFinalMessage.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import java.nio.charset.StandardCharsets; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Constructs and parses client-final-messages. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Formal Syntax:
cbind-inputgs2-header [ cbind-data ]
+ * ;; cbind-data MUST be present for
+ * ;; gs2-cbind-flag of "p" and MUST be absent
+ * ;; for "y" or "n".
channel-binding"c=" base64
+ * ;; base64 encoding of cbind-input.
client-final-message-without-proofchannel-binding "," nonce ["," extensions]
client-final-messageclient-final-message-without-proof "," proof
+ * + * @implNote {@code extensions} are not supported. + * @see [RFC5802] Section 7 + */ +public final class ClientFinalMessage extends AbstractScramMessage { + + /** + * channel-binding = "c=" base64 encoding of cbind-input. + */ + private final String cbindInput; + + /** + * nonce = "r=" c-nonce [s-nonce]. Second part provided by server. + */ + private final String nonce; + + /** + * proof = "p=" base64. + */ + private final byte[] proof; + + /** + * Constructus a client-final-message with the provided gs2Header (the same one used in the + * client-first-message), optionally the channel binding data, and the nonce. This method is + * intended to be used by SCRAM clients, and not to be constructed directly. + * + * @param gs2Header The GSS-API header + * @param cbindData If using channel binding, the channel binding data + * @param nonce The nonce + * @param proof The bytes representing the computed client proof + */ + public ClientFinalMessage(Gs2Header gs2Header, byte[] cbindData, String nonce, byte[] proof) { + this.cbindInput = generateCBindInput(gs2Header, cbindData); + this.nonce = checkNotEmpty(nonce, "nonce"); + this.proof = checkNotNull(proof, "proof").clone(); + } + + /** + * Return the channel-binding "c=" base64 encoding of cbind-input. + * + * @return the {@code channel-binding} + */ + public String getCbindInput() { + return cbindInput; + } + + /** + * Return the nonce. + * + * @return the {@code nonce} + */ + public String getNonce() { + return nonce; + } + + /** + * Return the proof. + * + * @return the {@code proof} + */ + public byte[] getProof() { + return proof.clone(); + } + + private static void checkChannelBinding(Gs2Header gs2Header, byte[] cbindData) { + final Gs2CbindFlag channelBindingFlag = gs2Header.getChannelBindingFlag(); + if (channelBindingFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED + && null == cbindData) { + throw new IllegalArgumentException("Channel binding data is required"); + } + if (channelBindingFlag != Gs2CbindFlag.CHANNEL_BINDING_REQUIRED + && null != cbindData) { + throw new IllegalArgumentException("Channel binding data should not be present"); + } + } + + private static @NotNull String generateCBindInput(@NotNull Gs2Header gs2Header, + byte @Nullable [] cbindData) { + checkNotNull(gs2Header, "gs2Header"); + checkChannelBinding(gs2Header, cbindData); + + byte[] cbindInput = gs2Header.writeTo(new StringBuilder(32)) + .append(',').toString().getBytes(StandardCharsets.UTF_8); + + if (null != cbindData && cbindData.length != 0) { + byte[] cbindInputNew = new byte[cbindInput.length + cbindData.length]; + System.arraycopy(cbindInput, 0, cbindInputNew, 0, cbindInput.length); + System.arraycopy(cbindData, 0, cbindInputNew, cbindInput.length, cbindData.length); + cbindInput = cbindInputNew; + } + + return ScramStringFormatting.base64Encode(cbindInput); + } + + private StringBuilder writeToWithoutProof(@NotNull StringBuilder sb) { + return StringWritableCsv.writeTo(sb, + new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, cbindInput), + new ScramAttributeValue(ScramAttributes.NONCE, nonce)); + } + + static StringBuilder withoutProof(StringBuilder sb, Gs2Header gs2Header, byte[] cbindData, + String nonce) { + return StringWritableCsv.writeTo(sb, + new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, + generateCBindInput(gs2Header, cbindData)), + new ScramAttributeValue(ScramAttributes.NONCE, nonce)); + } + + @Override + StringBuilder writeTo(StringBuilder sb) { + writeToWithoutProof(sb); + + return StringWritableCsv.writeTo( + sb, + null, // This marks the position of writeToWithoutProof, required for the "," + new ScramAttributeValue(ScramAttributes.CLIENT_PROOF, + ScramStringFormatting.base64Encode(proof))); + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ClientFirstMessage.java b/scram-common/src/main/java/com/ongres/scram/common/ClientFirstMessage.java new file mode 100644 index 0000000..5913149 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ClientFirstMessage.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import com.ongres.scram.common.exception.ScramParseException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Constructs and parses client-first-messages. Message contains a {@code gs2-header}, a username + * and a + * nonce. + * + * + * + * + * + * + * + * + * + * + * + *
Formal Syntax:
client-first-message-bare[reserved-mext ","] username "," nonce ["," extensions]
client-first-messagegs2-header client-first-message-bare
+ * + * @implNote {@code extensions} are not supported. + * @see [RFC5802] Section 7 + */ +public final class ClientFirstMessage extends AbstractScramMessage { + + /** + * gs2-header = gs2-cbind-flag "," [ authzid ] ",". + */ + private final @NotNull Gs2Header gs2Header; + + /** + * username = "n=" saslname. + */ + private final @NotNull String username; + + /** + * nonce= "r=" c-nonce [s-nonce]. + */ + private final @NotNull String clientNonce; + + /** + * Constructs a client-first-message for the given user, nonce and gs2Header. This constructor is + * intended to be instantiated by a scram client, and not directly. The client should be providing + * the header, and nonce (and probably the user too). + * + * @param gs2Header The GSS-API header + * @param username The SCRAM username + * @param clientNonce The nonce for this session + * @throws IllegalArgumentException If any of the arguments is null or empty + */ + public ClientFirstMessage(@NotNull Gs2Header gs2Header, @NotNull String username, + @NotNull String clientNonce) { + this.gs2Header = checkNotNull(gs2Header, "gs2Header"); + this.username = ScramStringFormatting.toSaslName(checkNotEmpty(username, "username")); + this.clientNonce = checkNotEmpty(clientNonce, "clientNonce"); + } + + /** + * Constructs a client-first-message for the given parameters. Under normal operation, this + * constructor is intended to be instantiated by a scram client, and not directly. However, this + * constructor is more user- or test-friendly, as the arguments are easier to provide without + * building other indirect object parameters. + * + * @param gs2CbindFlag The channel-binding flag + * @param authzid The optional authzid + * @param cbindName The optional channel binding name + * @param username The SCRAM user + * @param clientNonce The nonce for this session + * @throws IllegalArgumentException If the flag, user or nonce are null or empty + */ + public ClientFirstMessage(@NotNull Gs2CbindFlag gs2CbindFlag, @Nullable String cbindName, + @Nullable String authzid, @NotNull String username, @NotNull String clientNonce) { + this(new Gs2Header(gs2CbindFlag, cbindName, authzid), username, clientNonce); + } + + /** + * Constructs a client-first-message for the given parameters, with no channel binding nor + * authzid. Under normal operation, this constructor is intended to be instantiated by a scram + * client, and not directly. However, this constructor is more user- or test-friendly, as the + * arguments are easier to provide without building other indirect object parameters. + * + * @param username The SCRAM user + * @param clientNonce The nonce for this session + * @throws IllegalArgumentException If the user or nonce are null or empty + */ + public ClientFirstMessage(@NotNull String username, @NotNull String clientNonce) { + this(new Gs2Header(Gs2CbindFlag.CLIENT_NOT), username, clientNonce); + } + + /** + * Check to probe if gs2-cbind-flag is set to "p=". + * + * @return true if the message requires channel binding + */ + public boolean isChannelBindingRequired() { + return gs2Header.getChannelBindingFlag() == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED; + } + + /** + * Return the Gs2Header. + * + * @return the {@code gs2-header} + */ + public @NotNull Gs2Header getGs2Header() { + return gs2Header; + } + + /** + * Return the username. + * + * @return the {@code "n=" saslname} + */ + public @NotNull String getUsername() { + return username; + } + + /** + * Return the client nonce. + * + * @return the {@code c-nonce} + */ + public @NotNull String getClientNonce() { + return clientNonce; + } + + /** + * Limited version of the StringWritableCsv method, that doesn't write the GS2 header. This method + * is useful to construct the auth message used as part of the SCRAM algorithm. + * + * @param sb A StringBuffer where to write the data to. + * @return The same StringBuffer + */ + @NotNull + StringBuilder clientFirstMessageBare(@NotNull StringBuilder sb) { + return StringWritableCsv.writeTo( + sb, + new ScramAttributeValue(ScramAttributes.USERNAME, username), + new ScramAttributeValue(ScramAttributes.NONCE, clientNonce)); + } + + /** + * Construct a {@link ClientFirstMessage} instance from a message (String). + * + * @param clientFirstMessage The String representing the client-first-message + * @return The instance + * @throws ScramParseException If the message is not a valid client-first-message + * @throws IllegalArgumentException If the message is null or empty + */ + @NotNull + public static ClientFirstMessage parseFrom(@NotNull String clientFirstMessage) + throws ScramParseException { + checkNotEmpty(clientFirstMessage, "clientFirstMessage"); + + @NotNull + String @NotNull [] userNonceString; + try { + userNonceString = StringWritableCsv.parseFrom(clientFirstMessage, 2, 2); + } catch (IllegalArgumentException e) { + throw new ScramParseException("Illegal series of attributes in client-first-message", e); + } + + ScramAttributeValue user = ScramAttributeValue.parse(userNonceString[0]); + if (ScramAttributes.USERNAME.getChar() != user.getChar()) { + throw new ScramParseException("user must be the 3rd element of the client-first-message"); + } + + ScramAttributeValue nonce = ScramAttributeValue.parse(userNonceString[1]); + if (ScramAttributes.NONCE.getChar() != nonce.getChar()) { + throw new ScramParseException("nonce must be the 4th element of the client-first-message"); + } + + Gs2Header gs2Header = Gs2Header.parseFrom(clientFirstMessage); // Takes first two fields + return new ClientFirstMessage(gs2Header, user.getValue(), nonce.getValue()); + } + + @Override + StringBuilder writeTo(StringBuilder sb) { + StringWritableCsv.writeTo( + sb, + gs2Header, + null // This marks the position of the rest of the elements, required for the "," + ); + + return clientFirstMessageBare(sb); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/CryptoUtil.java b/scram-common/src/main/java/com/ongres/scram/common/CryptoUtil.java new file mode 100644 index 0000000..aac2c20 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/CryptoUtil.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkArgument; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; +import static com.ongres.scram.common.util.Preconditions.gt0; + +import java.security.InvalidKeyException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.util.Locale; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import com.ongres.scram.common.exception.ScramRuntimeException; +import org.jetbrains.annotations.NotNull; + +/** + * Utility static methods for cryptography related tasks. + */ +final class CryptoUtil { + + private CryptoUtil() { + throw new IllegalStateException("Utility class"); + } + + /** + * Compute the "Hi" function for SCRAM. + * + * {@code + * Hi(str, salt, i): + * + * U1 := HMAC(str, salt + INT(1)) + * U2 := HMAC(str, U1) + * ... + * Ui-1 := HMAC(str, Ui-2) + * Ui := HMAC(str, Ui-1) + * + * Hi := U1 XOR U2 XOR ... XOR Ui + * + * where "i" is the iteration count, "+" is the string concatenation + * operator, and INT(g) is a 4-octet encoding of the integer g, most + * significant octet first. + * + * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the + * pseudorandom function (PRF) and with dkLen == output length of + * HMAC() == output length of H(). + * } + * + * @param secretKeyFactory The SecretKeyFactory to generate the SecretKey + * @param keyLength The length of the key (in bits) + * @param password The char array to compute the Hi function + * @param salt The salt + * @param iterationCount The number of iterations + * @return The bytes of the computed Hi value + * @throws ScramRuntimeException if unsupported PBEKeySpec + */ + static byte[] hi(SecretKeyFactory secretKeyFactory, int keyLength, char[] password, byte[] salt, + int iterationCount) { + try { + PBEKeySpec spec = new PBEKeySpec(password, salt, iterationCount, keyLength); + SecretKey key = secretKeyFactory.generateSecret(spec); + spec.clearPassword(); + return key.getEncoded(); + } catch (InvalidKeySpecException ex) { + throw new ScramRuntimeException( + String.format(Locale.ROOT, "Platform error: unsupported PBEKeySpec for %s algorithm", + secretKeyFactory.getAlgorithm()), + ex); + } + } + + /** + * Computes the HMAC of a given message. + * + * {@code + * HMAC(key, str): Apply the HMAC keyed hash algorithm (defined in + * [RFC2104]) using the octet string represented by "key" as the key + * and the octet string "str" as the input string. The size of the + * result is the hash result size for the hash function in use. For + * example, it is 20 octets for SHA-1 (see [RFC3174]). + * } + * + * @param secretKeySpec A key of the given algorithm + * @param mac A MAC instance of the given algorithm + * @param message The message to compute the HMAC + * @return The bytes of the computed HMAC value + * @throws ScramRuntimeException unsupported key for HMAC algorithm + */ + static byte[] hmac(SecretKeySpec secretKeySpec, Mac mac, byte[] message) { + try { + mac.init(secretKeySpec); + } catch (InvalidKeyException ex) { + throw new ScramRuntimeException( + String.format(Locale.ROOT, "Platform error: unsupported key for %s algorithm", + mac.getAlgorithm()), + ex); + } + return mac.doFinal(message); + } + + /** + * Computes a byte-by-byte xor operation. + * {@code + * XOR: Apply the exclusive-or operation to combine the octet string + * on the left of this operator with the octet string on the right of + * this operator. The length of the output and each of the two + * inputs will be the same for this use. + * } + * + * @param value1 first value to apply xor + * @param value2 second value to apply xor + * @return xor operation + */ + static byte @NotNull [] xor(byte @NotNull [] value1, byte @NotNull [] value2) { + checkNotNull(value1, "value1"); + checkNotNull(value2, "value2"); + checkArgument(value1.length == value2.length, "Both values must have the same length"); + + byte[] result = new byte[value1.length]; + for (int i = 0; i < value1.length; i++) { + result[i] = (byte) (value1[i] ^ value2[i]); + } + + return result; + } + + /** + * Generates a random salt. Normally the output is encoded to Base64. + * + * @param saltSize The length of the salt, in bytes + * @param random The SecureRandom to use + * @return The bye[] representing the salt + * @throws IllegalArgumentException if the saltSize is not positive, or if random is null + */ + static byte @NotNull [] salt(int saltSize, @NotNull SecureRandom random) { + gt0(saltSize, "saltSize"); + checkNotNull(random, "random"); + byte[] randomSalt = new byte[saltSize]; + random.nextBytes(randomSalt); + return randomSalt; + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/Gs2AttributeValue.java b/scram-common/src/main/java/com/ongres/scram/common/Gs2AttributeValue.java new file mode 100644 index 0000000..e230ddc --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/Gs2AttributeValue.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Parse and write GS2 Attribute-Value pairs. + */ +final class Gs2AttributeValue extends AbstractCharAttributeValue { + + Gs2AttributeValue(@NotNull Gs2Attributes attribute, @Nullable String value) { + super(attribute, value); + if (attribute.isRequiredValue()) { + checkNotNull(value, "value"); + } + } + + /** + * Parses a potential Gs2AttributeValue String. + * + * @param value The string that contains the Attribute-Value pair (where value is optional). + * @return The parsed class, or null if the String was null. + * @throws IllegalArgumentException If the String is an invalid Gs2AttributeValue + */ + @NotNull + static Gs2AttributeValue parse(@NotNull String value) { + if (value.isEmpty() || value.length() == 2 || value.length() > 2 && value.charAt(1) != '=') { + throw new IllegalArgumentException("Invalid Gs2AttributeValue"); + } + + Gs2Attributes byChar = Gs2Attributes.byChar(value.charAt(0)); + String val = value.length() > 2 ? value.substring(2) : null; + return new Gs2AttributeValue(byChar, val); + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/Gs2Attributes.java b/scram-common/src/main/java/com/ongres/scram/common/Gs2Attributes.java new file mode 100644 index 0000000..537ac59 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/Gs2Attributes.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import org.jetbrains.annotations.NotNull; + +/** + * Possible values of a GS2 Attribute. + * + * @see [RFC5802] Formal Syntax + */ +enum Gs2Attributes implements CharSupplier { + + /** + * Channel binding attribute. Client doesn't support channel binding. + */ + CLIENT_NOT(Gs2CbindFlag.CLIENT_NOT.getChar(), false), + + /** + * Channel binding attribute. Client does support channel binding but thinks the server does not. + */ + CLIENT_YES_SERVER_NOT(Gs2CbindFlag.CLIENT_YES_SERVER_NOT.getChar(), false), + + /** + * Channel binding attribute. Client requires channel binding. The selected channel binding + * follows "p=". + */ + CHANNEL_BINDING_REQUIRED(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED.getChar(), true), + + /** + * SCRAM attribute. This attribute specifies an authorization identity. + */ + AUTHZID(ScramAttributes.AUTHZID.getChar(), true); + + private final char flag; + private final boolean requiredValue; + + Gs2Attributes(char flag, boolean requiredValue) { + this.flag = flag; + this.requiredValue = requiredValue; + } + + @Override + public char getChar() { + return flag; + } + + boolean isRequiredValue() { + return requiredValue; + } + + @NotNull + static Gs2Attributes byChar(char c) { + switch (c) { + case 'n': + return CLIENT_NOT; + case 'y': + return CLIENT_YES_SERVER_NOT; + case 'p': + return CHANNEL_BINDING_REQUIRED; + case 'a': + return AUTHZID; + default: + throw new IllegalArgumentException("Invalid GS2Attribute character '" + c + "'"); + } + } + + @NotNull + static Gs2Attributes byGs2CbindFlag(Gs2CbindFlag cbindFlag) { + return byChar(cbindFlag.getChar()); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/Gs2CbindFlag.java b/scram-common/src/main/java/com/ongres/scram/common/Gs2CbindFlag.java new file mode 100644 index 0000000..1dd1216 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/Gs2CbindFlag.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import org.jetbrains.annotations.NotNull; + +/** + * Possible values of a GS2 Cbind Flag (channel binding; part of GS2 header). These values are sent + * by the client, and so are interpreted from this perspective. + * + * @see [RFC5802] Formal Syntax + */ +public enum Gs2CbindFlag implements CharSupplier { + + /** + * Client doesn't support channel binding. + */ + CLIENT_NOT('n'), + + /** + * Client does support channel binding but thinks the server does not. + */ + CLIENT_YES_SERVER_NOT('y'), + + /** + * Client requires channel binding. The selected channel binding follows "p=". + */ + CHANNEL_BINDING_REQUIRED('p'); + + private final char flag; + + Gs2CbindFlag(char flag) { + this.flag = flag; + } + + @Override + public char getChar() { + return flag; + } + + @NotNull + static Gs2CbindFlag byChar(char c) { + switch (c) { + case 'n': + return CLIENT_NOT; + case 'y': + return CLIENT_YES_SERVER_NOT; + case 'p': + return CHANNEL_BINDING_REQUIRED; + default: + throw new IllegalArgumentException("Invalid Gs2CbindFlag character '" + c + "'"); + } + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/Gs2Header.java b/scram-common/src/main/java/com/ongres/scram/common/Gs2Header.java new file mode 100644 index 0000000..9c5bdf6 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/Gs2Header.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.castNonNull; +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import com.ongres.scram.common.util.Preconditions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * GS2 header for SCRAM. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Formal Syntax:
gs2-cbind-flag("p=" cb-name) / "n" / "y"
+ * ;; "n" -> client doesn't support channel binding.
+ * ;; "y" -> client does support channel binding
+ * ;; but thinks the server does not.
+ * ;; "p" -> client requires channel binding.
+ * ;; The selected channel binding follows "p=".
gs2-headergs2-cbind-flag "," [ authzid ] ","
+ * ;; GS2 header for SCRAM
+ * ;; (the actual GS2 header includes an optional
+ * ;; flag to indicate that the GSS mechanism is not
+ * ;; "standard", but since SCRAM is "standard", we
+ * ;; don't include that flag).
authzid"a=" saslname
+ * + * @see [RFC5802] Formal Syntax + */ +public final class Gs2Header extends StringWritable { + + private final @NotNull Gs2AttributeValue gs2CbindFlag; + private final @Nullable Gs2AttributeValue authzid; + + /** + * Construct and validates a Gs2Header. Only provide the channel binding name if the channel + * binding flag is set to required. + * + * @param cbindFlag The channel binding flag + * @param cbName The channel-binding name. Should be not null if channel binding is required + * @param authzid The optional SASL authorization identity + * @throws IllegalArgumentException If the channel binding flag and argument are invalid + */ + public Gs2Header(@NotNull Gs2CbindFlag cbindFlag, @Nullable String cbName, + @Nullable String authzid) { + checkChannelBinding(cbindFlag, cbName); + + this.gs2CbindFlag = new Gs2AttributeValue(Gs2Attributes.byGs2CbindFlag(cbindFlag), cbName); + this.authzid = authzid == null ? null + : new Gs2AttributeValue(Gs2Attributes.AUTHZID, ScramStringFormatting.toSaslName(authzid)); + } + + /** + * Construct and validates a Gs2Header with no authzid. Only provide the channel binding name if + * the channel binding flag is set to required. + * + * @param cbindFlag The channel binding flag + * @param cbName The channel-binding name. Should be not null iif channel binding is required + * @throws IllegalArgumentException If the channel binding flag and argument are invalid + */ + public Gs2Header(@NotNull Gs2CbindFlag cbindFlag, @Nullable String cbName) { + this(cbindFlag, cbName, null); + } + + /** + * Construct and validates a Gs2Header with no authzid nor channel binding. + * + * @param cbindFlag The channel binding flag + * @throws IllegalArgumentException If the channel binding is supported (no cbname can be provided + * here) + */ + public Gs2Header(@NotNull Gs2CbindFlag cbindFlag) { + this(cbindFlag, null, null); + } + + /** + * Return the channel binding flag. + * + * @return the {@code gs2-cbind-flag} + */ + public @NotNull Gs2CbindFlag getChannelBindingFlag() { + return Gs2CbindFlag.byChar(gs2CbindFlag.getChar()); + } + + /** + * Return the channel binding type. + * + * @return the {@code cb-name} + */ + public @Nullable String getChannelBindingName() { + return gs2CbindFlag.getValue(); + } + + /** + * Return the authzid. + * + * @return the {@code "a=" saslname} + */ + public @Nullable String getAuthzid() { + return authzid != null ? castNonNull(authzid).getValue() : null; + } + + @Override + StringBuilder writeTo(StringBuilder sb) { + return StringWritableCsv.writeTo(sb, gs2CbindFlag, authzid); + } + + /** + * Read a Gs2Header from a String. String may contain trailing fields that will be ignored. + * + * @param message The String containing the Gs2Header + * @return The parsed Gs2Header object + * @throws IllegalArgumentException If the format/values of the String do not conform to a + * Gs2Header + */ + public static @NotNull Gs2Header parseFrom(@NotNull String message) { + checkNotNull(message, "Null message"); + + @NotNull + String[] gs2HeaderSplit = StringWritableCsv.parseFrom(message, 2); + if (gs2HeaderSplit.length == 0) { + throw new IllegalArgumentException("Invalid number of fields for the GS2 Header"); + } + + Gs2AttributeValue gs2cbind = Gs2AttributeValue.parse(castNonNull(gs2HeaderSplit[0])); + String authzId = Preconditions.isNullOrEmpty(gs2HeaderSplit[1]) + ? null + : castNonNull(Gs2AttributeValue.parse(gs2HeaderSplit[1])).getValue(); + + return new Gs2Header(Gs2CbindFlag.byChar(gs2cbind.getChar()), gs2cbind.getValue(), authzId); + } + + private static void checkChannelBinding(@NotNull Gs2CbindFlag cbindFlag, + @Nullable String cbName) { + checkNotNull(cbindFlag, "cbindFlag"); + if (cbindFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED ^ cbName != null) { + throw new IllegalArgumentException( + "Specify required channel binding flag and type together, or none"); + } + if (cbindFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED) { + validateChannelBindingType(castNonNull(cbName)); + } + } + + /** + * Checks that the channel binding name is valid. + * + *

{@code
+   * cb-name = 1*(ALPHA / DIGIT / "." / "-")
+   *           ;; See RFC 5056, Section 7.
+   * }
+ * + * @see IANA + * Channel-Binding Types + * @param cbname Channel Binding Name + * @throws IllegalArgumentException If the name is not a valid channel binding type. + */ + private static void validateChannelBindingType(@NotNull String cbname) { + checkNotEmpty(cbname, "cbname"); + switch (cbname) { + // IANA Registered Types + case "tls-server-end-point": + case "tls-unique": + case "tls-exporter": + break; + default: + // https://datatracker.ietf.org/doc/html/rfc5056#section-7 + for (int i = 0; i < cbname.length(); i++) { + char ch = cbname.charAt(i); + if (!(ch >= 'A' && ch <= 'Z') && !(ch >= 'a' && ch <= 'z') + && !(ch >= '0' && ch <= '9') && !(ch >= '-' && ch <= '.')) { + throw new IllegalArgumentException( + "Invalid Channel Binding Type name '" + cbname + "'"); + } + } + break; + } + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java b/scram-common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java new file mode 100644 index 0000000..f581bfb --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.castNonNull; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import com.ongres.scram.common.exception.ScramParseException; +import org.jetbrains.annotations.NotNull; + +/** + * Parse and write SCRAM Attribute-Value pairs. + */ +class ScramAttributeValue extends AbstractCharAttributeValue { + + public ScramAttributeValue(@NotNull ScramAttributes attribute, @NotNull String value) { + super(attribute, checkNotNull(value, "value")); + } + + @Override + public final @NotNull String getValue() { + return castNonNull(super.getValue()); + } + + /** + * Parses a potential ScramAttributeValue String. + * + * @param value The string that contains the Attribute-Value pair. + * @return The parsed class + * @throws ScramParseException If the argument is empty or an invalid Attribute-Value + */ + public static @NotNull ScramAttributeValue parse(@NotNull String value) throws ScramParseException { + if (value == null || value.length() < 3 || value.charAt(1) != '=') { + throw new ScramParseException("Invalid ScramAttributeValue '" + value + "'"); + } + return new ScramAttributeValue(ScramAttributes.byChar(value.charAt(0)), value.substring(2)); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ScramAttributes.java b/scram-common/src/main/java/com/ongres/scram/common/ScramAttributes.java new file mode 100644 index 0000000..7c6fffb --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ScramAttributes.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import com.ongres.scram.common.exception.ScramParseException; + +/** + * SCRAM Attributes as defined in Section + * 5.1 of the RFC. + * + *

Not all the available attributes may be available in this implementation. + */ +enum ScramAttributes implements CharSupplier { + + /** + * This attribute specifies the name of the user whose password is used for authentication (a.k.a. + * "authentication identity" [RFC4422]). If the + * "a" attribute is not specified (which would normally be the case), this username is also the + * identity that will be associated with the connection subsequent to authentication and + * authorization. + * + *

The client SHOULD prepare the username using the "SASLprep" profile [RFC4013] of the "stringprep" algorithm [RFC3454] treating it as a query string (i.e., + * unassigned Unicode code points are allowed). + * + *

The characters ',' or '=' in usernames are sent as '=2C' and '=3D' respectively. + */ + USERNAME('n'), + + /** + * This is an optional attribute, and is part of the GS2 [RFC5801] bridge between the GSS-API and SASL. + * This attribute specifies an authorization identity. A client may include it in its first + * message to the server if it wants to authenticate as one user, but subsequently act as a + * different user. This is typically used by an administrator to perform some management task on + * behalf of another user, or by a proxy in some situations. + * + *

If this attribute is omitted (as it normally would be), the authorization identity is + * assumed to be derived from the username specified with the (required) "n" attribute. + * + *

The server always authenticates the user specified by the "n" attribute. If the "a" + * attribute specifies a different user, the server associates that identity with the connection + * after successful authentication and authorization checks. + * + *

The syntax of this field is the same as that of the "n" field with respect to quoting of '=' + * and ','. + */ + AUTHZID('a'), + + /** + * This attribute specifies a sequence of random printable ASCII characters excluding ',' (which + * forms the nonce used as input to the hash function). No quoting is applied to this string. + */ + NONCE('r'), + + /** + * This REQUIRED attribute specifies the base64-encoded GS2 header and channel binding data. The + * attribute data consist of:

  • the GS2 header from the client's first message (recall + * that the GS2 header contains a channel binding flag and an optional authzid). This header is + * going to include channel binding type prefix (see [RFC5056]), if and only if the client is using + * channel binding;
  • followed by the external channel's channel binding data, if and + * only if the client is using channel binding.
+ */ + CHANNEL_BINDING('c'), + + /** + * This attribute specifies the base64-encoded salt used by the server for this user. + */ + SALT('s'), + + /** + * This attribute specifies an iteration count for the selected hash function and user. + */ + ITERATION('i'), + + /** + * This attribute specifies a base64-encoded ClientProof. + */ + CLIENT_PROOF('p'), + + /** + * This attribute specifies a base64-encoded ServerSignature. + */ + SERVER_SIGNATURE('v'), + + /** + * This attribute specifies an error that occurred during authentication exchange. Can help + * diagnose the reason for the authentication exchange failure. + */ + ERROR('e'); + + private final char attributeChar; + + ScramAttributes(char attributeChar) { + this.attributeChar = checkNotNull(attributeChar, "attributeChar"); + } + + @Override + public char getChar() { + return attributeChar; + } + + /** + * Find a SCRAMAttribute by its character. + * + * @param c The character. + * @return The SCRAMAttribute that has that character. + * @throws ScramParseException If no SCRAMAttribute has this character. + */ + public static ScramAttributes byChar(char c) throws ScramParseException { + switch (c) { + case 'n': + return USERNAME; + case 'a': + return AUTHZID; + case 'r': + return NONCE; + case 'c': + return CHANNEL_BINDING; + case 's': + return SALT; + case 'i': + return ITERATION; + case 'p': + return CLIENT_PROOF; + case 'v': + return SERVER_SIGNATURE; + case 'e': + return ERROR; + default: + throw new ScramParseException("Attribute with char '" + c + "' does not exist"); + } + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ScramFunctions.java b/scram-common/src/main/java/com/ongres/scram/common/ScramFunctions.java new file mode 100644 index 0000000..a129e55 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ScramFunctions.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.security.MessageDigest; +import java.security.SecureRandom; + +import com.ongres.scram.common.util.Preconditions; +import org.jetbrains.annotations.NotNull; + +/** + * Utility functions (mostly crypto) for SCRAM. + */ +public final class ScramFunctions { + + private static final byte @NotNull [] CLIENT_KEY_HMAC_MESSAGE = "Client Key".getBytes(UTF_8); + private static final byte @NotNull [] SERVER_KEY_HMAC_MESSAGE = "Server Key".getBytes(UTF_8); + + private ScramFunctions() { + throw new IllegalStateException("Utility class"); + } + + /** + * Compute the salted password, based on the given SCRAM mechanism, the String preparation + * algorithm, the provided salt and the number of iterations. + * + *
{@code
+   *      SaltedPassword  := Hi(Normalize(password), salt, i)
+   *  }
+ * + * @param scramMechanism The SCRAM mechanism + * @param stringPreparation The String preparation + * @param password The non-salted password + * @param salt The bytes representing the salt + * @param iterationCount The number of iterations + * @return The salted password + */ + public static byte @NotNull [] saltedPassword(@NotNull ScramMechanism scramMechanism, + @NotNull StringPreparation stringPreparation, char @NotNull [] password, + byte @NotNull [] salt, int iterationCount) { + return scramMechanism.saltedPassword(stringPreparation, password, salt, iterationCount); + } + + /** + * Computes the HMAC of the message and key, using the given SCRAM mechanism. + * + *
{@code
+   *     HMAC(key, str)
+   * }
+ * + * @param scramMechanism The SCRAM mechanism + * @param message The message to compute the HMAC + * @param key The key used to initialize the MAC + * @return The computed HMAC + */ + public static byte @NotNull [] hmac(@NotNull ScramMechanism scramMechanism, byte @NotNull [] key, + byte @NotNull [] message) { + return scramMechanism.hmac(key, message); + } + + /** + * Generates a client key, from the salted password. + * + *
{@code
+   *      ClientKey := HMAC(SaltedPassword, "Client Key")
+   *  }
+ * + * @param scramMechanism The SCRAM mechanism + * @param saltedPassword The salted password + * @return The client key + */ + public static byte[] clientKey(@NotNull ScramMechanism scramMechanism, + byte @NotNull [] saltedPassword) { + return hmac(scramMechanism, saltedPassword, CLIENT_KEY_HMAC_MESSAGE); + } + + /** + * Generates a server key, from the salted password. + * + *
{@code
+   *      ServerKey := HMAC(SaltedPassword, "Server Key")
+   * }
+ * + * @param scramMechanism The SCRAM mechanism + * @param saltedPassword The salted password + * @return The server key + */ + public static byte[] serverKey(@NotNull ScramMechanism scramMechanism, + byte @NotNull [] saltedPassword) { + return hmac(scramMechanism, saltedPassword, SERVER_KEY_HMAC_MESSAGE); + } + + /** + * Computes the hash function of a given value, based on the SCRAM mechanism hash function. + * + *
{@code
+   *     H(str)
+   * }
+ * + * @param scramMechanism The SCRAM mechanism + * @param message The message to hash + * @return The hashed value + */ + public static byte[] hash(@NotNull ScramMechanism scramMechanism, byte @NotNull [] message) { + return scramMechanism.digest(message); + } + + /** + * Generates a stored key, from the salted password. + * + *
{@code
+   *      StoredKey := H(ClientKey)
+   * }
+ * + * @param scramMechanism The SCRAM mechanism + * @param clientKey The client key + * @return The stored key + */ + public static byte[] storedKey(@NotNull ScramMechanism scramMechanism, + byte @NotNull [] clientKey) { + return hash(scramMechanism, clientKey); + } + + /** + * Computes the SCRAM client signature. + * + *
{@code
+   *      ClientSignature := HMAC(StoredKey, AuthMessage)
+   * }
+ * + * @param scramMechanism The SCRAM mechanism + * @param storedKey The stored key + * @param authMessage The auth message + * @return The client signature + */ + public static byte @NotNull [] clientSignature(@NotNull ScramMechanism scramMechanism, + byte @NotNull [] storedKey, @NotNull String authMessage) { + return hmac(scramMechanism, storedKey, authMessage.getBytes(UTF_8)); + } + + /** + * Computes the SCRAM client proof to be sent to the server on the client-final-message. + * + *
{@code
+   *      ClientProof := ClientKey XOR ClientSignature
+   * }
+ * + * @param clientKey The client key + * @param clientSignature The client signature + * @return The client proof + */ + public static byte[] clientProof(byte @NotNull [] clientKey, byte @NotNull [] clientSignature) { + return CryptoUtil.xor(clientKey, clientSignature); + } + + /** + * Compute the SCRAM server signature. + * + *
{@code
+   *      ServerSignature := HMAC(ServerKey, AuthMessage)
+   * }
+ * + * @param scramMechanism The SCRAM mechanism + * @param serverKey The server key + * @param authMessage The auth message + * @return The server signature + */ + public static byte @NotNull [] serverSignature(@NotNull ScramMechanism scramMechanism, + byte @NotNull [] serverKey, @NotNull String authMessage) { + return hmac(scramMechanism, serverKey, authMessage.getBytes(UTF_8)); + } + + /** + * Verifies that a provided client proof is correct. + * + * @param scramMechanism The SCRAM mechanism + * @param clientProof The provided client proof + * @param storedKey The stored key + * @param authMessage The auth message + * @return True if the client proof is correct + */ + public static boolean verifyClientProof( + @NotNull ScramMechanism scramMechanism, byte @NotNull [] clientProof, + byte @NotNull [] storedKey, @NotNull String authMessage) { + byte[] clientSignature = clientSignature(scramMechanism, storedKey, authMessage); + byte[] clientKey = CryptoUtil.xor(clientSignature, clientProof); + byte[] computedStoredKey = hash(scramMechanism, clientKey); + return MessageDigest.isEqual(storedKey, computedStoredKey); + } + + /** + * Verifies that a provided server proof is correct. + * + * @param scramMechanism The SCRAM mechanism + * @param serverKey The server key + * @param authMessage The auth message + * @param serverSignature The provided server signature + * @return True if the server signature is correct + */ + public static boolean verifyServerSignature( + ScramMechanism scramMechanism, byte[] serverKey, String authMessage, byte[] serverSignature) { + byte[] computedServerSignature = serverSignature(scramMechanism, serverKey, authMessage); + return MessageDigest.isEqual(serverSignature, computedServerSignature); + } + + /** + * Generates a random string (called a 'nonce'), composed of ASCII printable characters, except + * comma (','). + * + * @param nonceSize The length of the nonce, in characters/bytes + * @param random The SecureRandom to use + * @return The String representing the nonce + * @throws IllegalArgumentException if the nonceSize is not positive, or if random is null + */ + public static String nonce(int nonceSize, SecureRandom random) { + Preconditions.gt0(nonceSize, "nonceSize"); + Preconditions.checkNotNull(random, "random"); + final StringBuilder nonceBuilder = new StringBuilder(nonceSize); + while (nonceBuilder.length() < nonceSize) { + int codePoint = random.nextInt(0x7E - 0x21 + 1) + 0x21; + if (codePoint != ',') { + nonceBuilder.append((char) codePoint); + } + } + return nonceBuilder.toString(); + } + + /** + * Generates a random salt that can be used to generate a salted password. + * + * @param saltSize The length of the salt, in bytes + * @param random The SecureRandom to use + * @return The bye[] representing the salt + * @throws IllegalArgumentException if the saltSize is not positive, or if random is null + */ + public static byte @NotNull [] salt(int saltSize, @NotNull SecureRandom random) { + return CryptoUtil.salt(saltSize, random); + } + + /** + * The AuthMessage is computed by concatenating messages from the authentication exchange. + * + *
{@code
+   *      AuthMessage := client-first-message-bare + "," +
+   *                                    server-first-message + "," +
+   *                                    client-final-message-without-proof
+   * }
+ * + * @param clientFirstMessage the {@link ClientFirstMessage ClientFirstMessage} + * @param serverFirstMessage the {@link ServerFirstMessage ServerFirstMessage} + * @param cbindData the channel binding data, or null + * @return the AuthMessage + */ + public static String authMessage(ClientFirstMessage clientFirstMessage, + ServerFirstMessage serverFirstMessage, byte[] cbindData) { + StringBuilder sb = clientFirstMessage.clientFirstMessageBare(new StringBuilder(96)) + .append(',').append(serverFirstMessage).append(','); + ClientFinalMessage.withoutProof(sb, clientFirstMessage.getGs2Header(), + cbindData, serverFirstMessage.getNonce()); + return sb.toString(); + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ScramMechanism.java b/scram-common/src/main/java/com/ongres/scram/common/ScramMechanism.java new file mode 100644 index 0000000..144bfc5 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ScramMechanism.java @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotNull; +import static com.ongres.scram.common.util.Preconditions.gt0; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.SecretKeySpec; + +import com.ongres.scram.common.exception.ScramRuntimeException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +/** + * SCRAM Mechanisms supported by this library. At least, {@code SCRAM-SHA-1} and + * {@code SCRAM-SHA-256} are provided, since both the hash and the HMAC implementations are provided + * by the Java JDK version 8 or greater. + * + *

{@link java.security.MessageDigest}: "Every implementation of the Java platform is required to + * support the following standard MessageDigest algorithms: {@code SHA-1}, {@code SHA-256}". + * + *

{@link javax.crypto.Mac}: "Every implementation of the Java platform is required to support + * the following standard Mac algorithms: {@code HmacSHA1}, {@code HmacSHA256}". + * + * @see SASL + * SCRAM Family Mechanisms + */ +public enum ScramMechanism { + + /** + * SCRAM-SHA-1 mechanism, defined in RFC-5802. + */ + SCRAM_SHA_1("SCRAM-SHA-1", "SHA-1", 160, "HmacSHA1", 4096), + /** + * SCRAM-SHA-1-PLUS mechanism, defined in RFC-5802. + */ + SCRAM_SHA_1_PLUS("SCRAM-SHA-1-PLUS", "SHA-1", 160, "HmacSHA1", 4096), + /** + * SCRAM-SHA-224 mechanism, not defined in an RFC. + */ + SCRAM_SHA_224("SCRAM-SHA-224", "SHA-224", 224, "HmacSHA224", 4096), + /** + * SCRAM-SHA-224-PLUS mechanism, not defined in an RFC. + */ + SCRAM_SHA_224_PLUS("SCRAM-SHA-224-PLUS", "SHA-224", 224, "HmacSHA224", 4096), + /** + * SCRAM-SHA-256 mechanism, defined in RFC-7677. + */ + SCRAM_SHA_256("SCRAM-SHA-256", "SHA-256", 256, "HmacSHA256", 4096), + /** + * SCRAM-SHA-256-PLUS mechanism, defined in RFC-7677. + */ + SCRAM_SHA_256_PLUS("SCRAM-SHA-256-PLUS", "SHA-256", 256, "HmacSHA256", 4096), + /** + * SCRAM-SHA-384 mechanism, not defined in an RFC. + */ + SCRAM_SHA_384("SCRAM-SHA-384", "SHA-384", 384, "HmacSHA384", 4096), + /** + * SCRAM-SHA-384-PLUS mechanism, not defined in an RFC. + */ + SCRAM_SHA_384_PLUS("SCRAM-SHA-384-PLUS", "SHA-384", 384, "HmacSHA384", 4096), + /** + * SCRAM-SHA-512 mechanism. + */ + SCRAM_SHA_512("SCRAM-SHA-512", "SHA-512", 512, "HmacSHA512", 10000), + /** + * SCRAM-SHA-512-PLUS mechanism. + */ + SCRAM_SHA_512_PLUS("SCRAM-SHA-512-PLUS", "SHA-512", 512, "HmacSHA512", 10000); + + private static final @Unmodifiable Map BY_NAME_MAPPING = + Arrays.stream(values()) + .filter(ScramMechanism::isAlgorithmSupported) + .collect(Collectors.collectingAndThen( + Collectors.toMap(ScramMechanism::getName, Function.identity()), + Collections::unmodifiableMap)); + private static final @Unmodifiable List SUPPORTED_MECHANISMS = BY_NAME_MAPPING.keySet() + .stream() + .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + + private final @NotNull String mechanismName; + private final @NotNull String hashAlgorithmName; + private final int keyLength; + private final @NotNull String hmacAlgorithmName; + private final @NotNull String keyFactoryAlgorithmName; + private final boolean channelBinding; + private final int iterationCount; + + ScramMechanism(String name, String hashAlgorithmName, int keyLength, String hmacAlgorithmName, + int iterationCount) { + this.mechanismName = checkNotNull(name, "name"); + this.hashAlgorithmName = checkNotNull(hashAlgorithmName, "hashAlgorithmName"); + this.keyLength = gt0(keyLength, "keyLength"); + this.hmacAlgorithmName = checkNotNull(hmacAlgorithmName, "hmacAlgorithmName"); + this.keyFactoryAlgorithmName = "PBKDF2With" + hmacAlgorithmName; + this.channelBinding = name.endsWith("-PLUS"); + this.iterationCount = gt0(iterationCount, "iterationCount"); + } + + /** + * Method that returns the name of the hash algorithm. It is protected since should be of no + * interest for direct users. The instance is supposed to provide abstractions over the algorithm + * names, and are not meant to be directly exposed. + * + * @return The name of the hash algorithm + */ + @NotNull + String getHashAlgorithmName() { + return hashAlgorithmName; + } + + /** + * Method that returns the name of the HMAC algorithm. It is protected since should be of no + * interest for direct users. The instance is supposed to provide abstractions over the algorithm + * names, and are not meant to be directly exposed. + * + * @return The name of the HMAC algorithm + */ + @NotNull + String getHmacAlgorithmName() { + return hmacAlgorithmName; + } + + /** + * The name of the mechanism. + * + *

Must be a value registered under IANA: SASL SCRAM + * Family Mechanisms + * + * @return The mechanism name + */ + @NotNull + public String getName() { + return mechanismName; + } + + /** + * The mechanism {@code -PLUS} require channel binding. + * + * @return true if the mechanism requires channel binding + */ + public boolean isPlus() { + return channelBinding; + } + + /** + * Returns the length of the key length of the algorithm. + * + * @return The length (in bits) + */ + int getKeyLength() { + return keyLength; + } + + int getIterationCount() { + return iterationCount; + } + + /** + * Calculate a message digest, according to the algorithm of the SCRAM mechanism. + * + * @param message the message + * @return The calculated message digest + * @throws ScramRuntimeException If the algorithm is not provided by current JVM or any included + * implementations + */ + byte @NotNull [] digest(byte @NotNull [] message) { + try { + return MessageDigest.getInstance(hashAlgorithmName).digest(message); + } catch (NoSuchAlgorithmException e) { + throw new ScramRuntimeException( + "Hash algorithm " + hashAlgorithmName + " not present in current JVM", e); + } + } + + /** + * Calculate the hmac of a key and a message, according to the algorithm of the SCRAM mechanism. + * + * @param key the key + * @param message the message + * @return The calculated message hmac instance + * @throws ScramRuntimeException If the algorithm is not provided by current JVM or any included + * implementations + */ + byte @NotNull [] hmac(byte @NotNull [] key, byte @NotNull [] message) { + try { + return CryptoUtil.hmac(new SecretKeySpec(key, hmacAlgorithmName), + Mac.getInstance(hmacAlgorithmName), message); + } catch (NoSuchAlgorithmException e) { + throw new ScramRuntimeException( + "HMAC algorithm " + hmacAlgorithmName + " not present in current JVM", e); + } + } + + /** + * Compute the salted password. + * + * @param stringPreparation Type of preparation to perform in the string + * @param password Password used + * @param salt Salt used + * @param iterationCount Number of iterations + * @return The salted password + * @throws ScramRuntimeException If the algorithm is not provided by current JVM or any included + * implementations + */ + byte @NotNull [] saltedPassword(@NotNull StringPreparation stringPreparation, + char @NotNull [] password, byte @NotNull [] salt, int iterationCount) { + final char[] normalizedPassword = stringPreparation.normalize(password); + try { + return CryptoUtil.hi( + SecretKeyFactory.getInstance(keyFactoryAlgorithmName), + keyLength, + normalizedPassword, + salt, + iterationCount); + } catch (NoSuchAlgorithmException ex) { + throw new ScramRuntimeException( + "Unsupported " + keyFactoryAlgorithmName + " for " + mechanismName, ex); + } + } + + /** + * Gets a SCRAM mechanism given its standard IANA name, supported by the Java security provider. + * + * @apiNote This will get only the mechanims supported by the Java security provider, if the + * configured security provider lacks the algorithm this method will return {@code null}. + * + * @param name The standard IANA full name of the mechanism. + * @return An instance that contains the ScramMechanism if it was found, or null otherwise. + */ + public static @Nullable ScramMechanism byName(@NotNull String name) { + return BY_NAME_MAPPING.get(checkNotNull(name, "name")); + } + + /** + * List all the supported SCRAM mechanisms by this client implementation. + * + * @return A unmodifiable list of the IANA-registered, SCRAM supported mechanisms + */ + @Unmodifiable + public static @NotNull List<@NotNull String> supportedMechanisms() { + return Collections.unmodifiableList(SUPPORTED_MECHANISMS); + } + + private static boolean isAlgorithmSupported(@NotNull ScramMechanism mechanism) { + try { + MessageDigest.getInstance(mechanism.hashAlgorithmName); + Mac.getInstance(mechanism.hmacAlgorithmName); + SecretKeyFactory.getInstance(mechanism.keyFactoryAlgorithmName); + return true; + } catch (NoSuchAlgorithmException e) { + return false; + } + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java b/scram-common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java new file mode 100644 index 0000000..188e322 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.Base64; + +import com.ongres.saslprep.SASLprep; +import com.ongres.stringprep.Profile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Class with static methods that provide support for converting to/from salNames. + * + * @see [RFC5802] Section 7: Formal + * Syntax + */ +final class ScramStringFormatting { + + static final Profile SASL_PREP = new SASLprep(); + + private ScramStringFormatting() { + throw new IllegalStateException("Utility class"); + } + + /** + * Given a value-safe-char (normalized UTF-8 String), return one where characters ',' and '=' are + * represented by '=2C' or '=3D', respectively. + * + * @param value The value to convert so saslName + * @return The saslName, with caracter escaped (if any) + */ + @NotNull + static String toSaslName(@NotNull final String value) { + if (value.isEmpty()) { + return value; + } + + final char[] originalChars = SASL_PREP.prepareQuery(value.toCharArray()); + + int comma = 0; + int equal = 0; + // Fast path + for (char c : originalChars) { + if (',' == c) { + comma++; + } else if ('=' == c) { + equal++; + } + } + if (comma == 0 && equal == 0) { + return new String(originalChars); + } + + // Replace chars + char[] saslChars = new char[originalChars.length + comma * 2 + equal * 2]; + int i = 0; + for (char c : originalChars) { + if (',' == c) { + saslChars[i++] = '='; + saslChars[i++] = '2'; + saslChars[i++] = 'C'; + } else if ('=' == c) { + saslChars[i++] = '='; + saslChars[i++] = '3'; + saslChars[i++] = 'D'; + } else { + saslChars[i++] = c; + } + } + + return new String(saslChars); + } + + /** + * Given a saslName, return a non-escaped String. + * + * @param value The saslName + * @return The saslName, unescaped + * @throws IllegalArgumentException If a ',' character is present, or a '=' not followed by either + * '2C' or '3D' + */ + @Nullable + static String fromSaslName(@Nullable String value) { + if (null == value || value.isEmpty()) { + return value; + } + + int equal = 0; + char[] orig = value.toCharArray(); + + // Fast path + for (int i = 0; i < orig.length; i++) { + if (orig[i] == ',') { + throw new IllegalArgumentException("Invalid ',' character present in saslName"); + } + if (orig[i] == '=') { + equal++; + if (i + 2 > orig.length - 1) { + throw new IllegalArgumentException("Invalid '=' character present in saslName"); + } + if (!(orig[i + 1] == '2' && orig[i + 2] == 'C' + || orig[i + 1] == '3' && orig[i + 2] == 'D')) { + throw new IllegalArgumentException( + "Invalid char '=" + orig[i + 1] + orig[i + 2] + "' found in saslName"); + } + } + } + if (equal == 0) { + return value; + } + + // Replace characters + char[] replaced = new char[orig.length - equal * 2]; + + for (int r = 0, o = 0; r < replaced.length; r++) { + if ('=' == orig[o]) { + if (orig[o + 1] == '2' && orig[o + 2] == 'C') { + replaced[r] = ','; + } else if (orig[o + 1] == '3' && orig[o + 2] == 'D') { + replaced[r] = '='; + } + o += 3; + } else { + replaced[r] = orig[o]; + o += 1; + } + } + + return new String(replaced); + } + + static @NotNull String base64Encode(byte @NotNull [] value) { + checkNotNull(value, "value"); + return new String(Base64.getEncoder().encode(value), UTF_8); + } + + static byte @NotNull [] base64Decode(@NotNull String value) { + checkNotEmpty(value, "value"); + return Base64.getDecoder().decode(value.getBytes(UTF_8)); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ServerFinalMessage.java b/scram-common/src/main/java/com/ongres/scram/common/ServerFinalMessage.java new file mode 100644 index 0000000..a7696a5 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ServerFinalMessage.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.castNonNull; +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import com.ongres.scram.common.exception.ScramParseException; +import com.ongres.scram.common.exception.ServerErrorValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Constructs and parses {@code server-final-messages}. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Formal Syntax:
server-error"e=" server-error-value
server-error-value"invalid-encoding" /
+ * "extensions-not-supported" / ; unrecognized 'm' value
+ * "invalid-proof" /
+ * "channel-bindings-dont-match" /
+ * "server-does-support-channel-binding" /
+ *     ; server does not support channel binding
+ * "channel-binding-not-supported" /
+ * "unsupported-channel-binding-type" /
+ * "unknown-user" /
+ * "invalid-username-encoding" /
+ *     ; invalid username encoding (invalid UTF-8 or
+ *     ; SASLprep failed)
+ * "no-resources" /
+ * "other-error"

+ * ; Unrecognized errors should be treated as "other-error".
+ * ; In order to prevent information disclosure, the server
+ * ; may substitute the real reason with "other-error".
verifier"v=" base64
+ * ;; base-64 encoded ServerSignature.
server-final-message(server-error / verifier)
+ * ["," extensions]
+ * + * @implNote {@code extensions} are not supported. + * @see [RFC5802] Section 7 + */ +public final class ServerFinalMessage extends AbstractScramMessage { + + private final byte @Nullable [] verifier; + private final @Nullable String serverError; + + /** + * Constructs a server-final-message with no errors, and the provided server verifier. + * + * @param verifier The bytes of the computed signature + * @throws IllegalArgumentException If the verifier is null + */ + public ServerFinalMessage(byte @NotNull [] verifier) { + this.verifier = checkNotNull(verifier, "verifier"); + this.serverError = null; + } + + /** + * Constructs a server-final-message which represents a SCRAM error. + * + * @param serverError The error message + * @throws IllegalArgumentException If the error is null + */ + public ServerFinalMessage(@NotNull String serverError) { + this.serverError = validateServerErrorType(serverError); + this.verifier = null; + } + + /** + * Whether this server-final-message contains an error. + * + * @return True if it contains an error, false if it contains a verifier + */ + public boolean isError() { + return null != serverError; + } + + /** + * Get the verifier value from the "v=" server-final-message. + * + * @return the {@code verifier} + */ + public byte @Nullable [] getVerifier() { + return verifier != null ? checkNotNull(verifier, "verifier").clone() : null; + } + + /** + * Get the server-error-value from the "e=" server-final-message. + * + * @return the {@code server-error-value} + */ + public @Nullable String getServerError() { + return serverError; + } + + /** + * Parses a server-final-message from a String. + * + * @param serverFinalMessage The message + * @return A constructed server-final-message instance + * @throws ScramParseException If the argument is not a valid server-final-message + * @throws IllegalArgumentException If the message is null or empty + */ + public static @NotNull ServerFinalMessage parseFrom(@NotNull String serverFinalMessage) + throws ScramParseException { + checkNotEmpty(serverFinalMessage, "serverFinalMessage"); + + @NotNull + String @NotNull [] attributeValues = StringWritableCsv.parseFrom(serverFinalMessage, 1, 0); + if (attributeValues.length != 1) { + throw new ScramParseException("Invalid server-final-message"); + } + + ScramAttributeValue attributeValue = ScramAttributeValue.parse(attributeValues[0]); + if (ScramAttributes.SERVER_SIGNATURE.getChar() == attributeValue.getChar()) { + byte[] verifier = ScramStringFormatting.base64Decode(attributeValue.getValue()); + return new ServerFinalMessage(verifier); + } else if (ScramAttributes.ERROR.getChar() == attributeValue.getChar()) { + return new ServerFinalMessage(attributeValue.getValue()); + } else { + throw new ScramParseException( + "Invalid server-final-message: it must contain either a verifier or an error attribute"); + } + } + + @Override + StringBuilder writeTo(StringBuilder sb) { + return StringWritableCsv.writeTo( + sb, + isError() + ? new ScramAttributeValue(ScramAttributes.ERROR, castNonNull(serverError)) + : new ScramAttributeValue(ScramAttributes.SERVER_SIGNATURE, + ScramStringFormatting.base64Encode(castNonNull(verifier)))); + } + + private static String validateServerErrorType(@NotNull String serverError) { + checkNotNull(serverError, "serverError"); + if (ServerErrorValue.getErrorMessage(serverError) == null) { + throw new IllegalArgumentException( + "Invalid server-error-value '" + serverError + "'"); + } + return serverError; + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/ServerFirstMessage.java b/scram-common/src/main/java/com/ongres/scram/common/ServerFirstMessage.java new file mode 100644 index 0000000..53c8f56 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/ServerFirstMessage.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.castNonNull; +import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; +import static com.ongres.scram.common.util.Preconditions.checkNotNull; +import static com.ongres.scram.common.util.Preconditions.gt0; + +import com.ongres.scram.common.exception.ScramParseException; +import org.jetbrains.annotations.NotNull; + +/** + * Constructs and parses {@code server-first-messages}. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Formal Syntax:
nonce"r=" c-nonce [s-nonce]
+ * ;; Second part provided by server.
salt"s=" base64
server-first-message[reserved-mext ","] nonce "," salt ",
+ * "iteration-count ["," extensions]
+ * + * @implNote {@code extensions} are not supported. + * @see [RFC5802] Section 7 + */ +public final class ServerFirstMessage extends AbstractScramMessage { + + private final @NotNull String clientNonce; + private final @NotNull String serverNonce; + private final @NotNull String salt; + private final int iterationCount; + + /** + * Constructs a server-first-message from a client-first-message and the additional required data. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Formal Syntax:
server-error"e=" server-error-value
verifier"v=" base64
+ * ;; base-64 encoded ServerSignature.
server-final-message(server-error / verifier)
+ * ["," extensions]
+ * + * @param clientNonce The c-nonce used in client-first-message + * @param serverNonce The s-nonce returned by the server + * @param salt The salt + * @param iterationCount The iteration count (must be positive) + * @throws IllegalArgumentException If clientFirstMessage, serverNonce or salt are null or empty, + * or iteration < 1 + */ + public ServerFirstMessage(@NotNull String clientNonce, @NotNull String serverNonce, + @NotNull String salt, int iterationCount) { + this.clientNonce = checkNotEmpty(clientNonce, "clientNonce"); + this.serverNonce = checkNotEmpty(serverNonce, "serverNonce"); + this.salt = checkNotNull(salt, "salt"); + this.iterationCount = gt0(iterationCount, "iterationCount"); + } + + /** + * The client nonce. + * + * @return The client nonce + */ + public @NotNull String getClientNonce() { + return clientNonce; + } + + /** + * The server nonce. + * + * @return The server nonce + */ + public @NotNull String getServerNonce() { + return serverNonce; + } + + /** + * The concatenation of the client nonce and the server nonce: {@code c-nonce [s-nonce]}. + * + * @return The nonce + */ + public @NotNull String getNonce() { + return clientNonce + serverNonce; + } + + /** + * The salt in base64. + * + * @return The salt in base64. + */ + public String getSalt() { + return salt; + } + + /** + * The number of iterations. + * + * @return The number of iterations. + */ + public int getIterationCount() { + return iterationCount; + } + + /** + * Parses a server-first-message from a String. + * + * @param serverFirstMessage The string representing the server-first-message + * @param clientNonce The clientNonce that is present in the client-first-message + * @return The parsed instance + * @throws ScramParseException If the argument is not a valid server-first-message + * @throws IllegalArgumentException If either argument is empty or serverFirstMessage is not a + * valid message + */ + public static @NotNull ServerFirstMessage parseFrom(@NotNull String serverFirstMessage, + @NotNull String clientNonce) throws ScramParseException { + checkNotEmpty(serverFirstMessage, "serverFirstMessage"); + checkNotEmpty(clientNonce, "clientNonce"); + + String[] attributeValues = StringWritableCsv.parseFrom(serverFirstMessage, 3, 0); + if (attributeValues.length != 3) { + throw new ScramParseException("Invalid server-first-message"); + } + + ScramAttributeValue nonce = ScramAttributeValue.parse(castNonNull(attributeValues[0])); + if (ScramAttributes.NONCE.getChar() != nonce.getChar()) { + throw new ScramParseException( + "nonce must be the 1st element of the server-first-message"); + } + if (!nonce.getValue().startsWith(clientNonce)) { + throw new ScramParseException("parsed nonce does not start with client nonce"); + } + + ScramAttributeValue salt = ScramAttributeValue.parse(castNonNull(attributeValues[1])); + if (ScramAttributes.SALT.getChar() != salt.getChar()) { + throw new ScramParseException("salt must be the 2nd element of the server-first-message"); + } + + ScramAttributeValue iteration = ScramAttributeValue.parse(castNonNull(attributeValues[2])); + if (ScramAttributes.ITERATION.getChar() != iteration.getChar()) { + throw new ScramParseException( + "iteration must be the 3rd element of the server-first-message"); + } + + int iterationInt; + try { + iterationInt = Integer.parseInt(iteration.getValue()); + } catch (NumberFormatException ex) { + throw new ScramParseException("invalid iteration", ex); + } + + return new ServerFirstMessage(clientNonce, nonce.getValue().substring(clientNonce.length()), + salt.getValue(), iterationInt); + } + + @Override + StringBuilder writeTo(StringBuilder sb) { + return StringWritableCsv.writeTo( + sb, + new ScramAttributeValue(ScramAttributes.NONCE, getNonce()), + new ScramAttributeValue(ScramAttributes.SALT, salt), + new ScramAttributeValue(ScramAttributes.ITERATION, Integer.toString(iterationCount))); + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/StringPreparation.java b/scram-common/src/main/java/com/ongres/scram/common/StringPreparation.java new file mode 100644 index 0000000..82d45f3 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/StringPreparation.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +/** + * StringPreparations enumerations to use in SCRAM. + */ +public enum StringPreparation { + + /** + * Implementation of StringPreparation that performs no preparation. Non US-ASCII characters will + * produce an exception. + * + *

Even though the [RFC5802] is not very + * clear about it, this implementation will normalize non-printable US-ASCII characters similarly + * to what SASLprep does (i.e., removing them). + */ + NO_PREPARATION { + @Override + char[] doNormalize(char[] value) { + return UsAsciiUtils.toPrintable(value); + } + }, + /** + * Implementation of StringPreparation that performs {@code SASLprep} preparation. UTF-8 byte + * sequences that are prohibited by the SASLprep algorithm will produce an exception. + */ + SASL_PREPARATION { + @Override + char[] doNormalize(char[] value) { + return ScramStringFormatting.SASL_PREP.prepareStored(value); + } + }, + /** + * Implementation of StringPreparation that performs {@code SASLprep} preparation for PostgreSQL. + * + *

The SCRAM specification dictates that the password is also in UTF-8, and is + * processed with the {@code SASLprep} algorithm. PostgreSQL, however, does not require UTF-8 to + * be used for the password. When a user's password is set, it is processed with SASLprep as if it + * was in UTF-8, regardless of the actual encoding used. However, if it is not a legal UTF-8 byte + * sequence, or it contains UTF-8 byte sequences that are prohibited by the SASLprep algorithm, + * the raw password will be used without SASLprep processing, instead of throwing an error. This + * allows the password to be normalized when it is in UTF-8, but still allows a non-UTF-8 password + * to be used, and doesn't require the system to know which encoding the password is + * in.
+ * + * @see PostgreSQL + * SCRAM-SHA-256 Authentication + */ + POSTGRESQL_PREPARATION { + @Override + char[] doNormalize(char[] value) { + try { + return ScramStringFormatting.SASL_PREP.prepareStored(value); + } catch (IllegalArgumentException ex) { + // the raw password will be used without SASLprep processing + return value; + } + } + }; + + abstract char[] doNormalize(char[] value); + + /** + * Normalize acording the selected preparation. + * + * @param value array of chars to normalize + * @return the normalized array of chars + * @throws IllegalArgumentException if the string is null or empty + */ + public char[] normalize(char[] value) { + if (null == value || value.length == 0) { + throw new IllegalArgumentException("Empty string for value"); + } + + char[] normalized = doNormalize(value); + + if (null == normalized || normalized.length == 0) { + throw new IllegalArgumentException("null or empty value after normalization"); + } + + return normalized; + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/StringWritable.java b/scram-common/src/main/java/com/ongres/scram/common/StringWritable.java new file mode 100644 index 0000000..176730f --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/StringWritable.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import org.jetbrains.annotations.NotNull; + +/** + * Abstract class to denote classes which can write to a StringBuffer. + */ +abstract class StringWritable { + + /** + * Write the class information to the given StringBuffer. + * + * @param sb Where to write the data. + * @return The same StringBuffer. + */ + abstract @NotNull StringBuilder writeTo(@NotNull StringBuilder sb); + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/StringWritableCsv.java b/scram-common/src/main/java/com/ongres/scram/common/StringWritableCsv.java new file mode 100644 index 0000000..a6497c1 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/StringWritableCsv.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import java.util.Arrays; + +import com.ongres.scram.common.util.Preconditions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Helper class to generate Comma Separated Values of StringWritables. + */ +final class StringWritableCsv { + + private StringWritableCsv() { + throw new IllegalStateException("Utility class"); + } + + /** + * Write a sequence of StringWritableCsv to a StringBuffer. Null StringWritables are not printed, + * but separator is still used. Separator is a comma (',') + * + * @param sb The sb to write to + * @param values Zero or more attribute-value pairs to write + * @return The same sb, with data filled in (if any) + * @throws IllegalArgumentException If sb is null + */ + static @NotNull StringBuilder writeTo(@NotNull StringBuilder sb, + @Nullable StringWritable... values) { + checkNotNull(sb, "sb"); + if (null == values || values.length == 0) { + return sb; + } + + if (null != values[0]) { + Preconditions.castNonNull(values[0]).writeTo(sb); + } + int i = 1; + while (i < values.length) { + sb.append(','); + if (null != values[i]) { + Preconditions.castNonNull(values[i]).writeTo(sb); + } + i++; + } + + return sb; + } + + /** + * Parse a String with a StringWritableCsv into its composing Strings represented as Strings. No + * validation is performed on the individual attribute-values returned. + * + * @param value The String with the set of attribute-values + * @param n Number of entries to return (entries will be null of there were not enough). 0 means + * unlimited + * @param offset How many entries to skip before start returning + * @return An array of Strings which represent the individual attribute-values + * @throws IllegalArgumentException If value is null or either n or offset are negative + */ + static @NotNull String @NotNull [] parseFrom(@NotNull String value, int n, int offset) { + checkNotNull(value, "value"); + if (n < 0 || offset < 0) { + throw new IllegalArgumentException("Limit and offset have to be >= 0"); + } + + if (value.isEmpty()) { + return new String[0]; + } + + String[] split = value.split(",", -1); + if (split.length < offset) { + throw new IllegalArgumentException("Not enough items for the given offset"); + } + + return Arrays.copyOfRange( + split, + offset, + (n == 0 ? split.length : n) + offset); + } + + /** + * Parse a String with a StringWritableCsv into its composing Strings represented as Strings. No + * validation is performed on the individual attribute-values returned. Elements are returned + * starting from the first available attribute-value. + * + * @param value The String with the set of attribute-values + * @param n Number of entries to return (entries will be null of there were not enough). 0 means + * unlimited + * @return An array of Strings which represent the individual attribute-values + * @throws IllegalArgumentException If value is null or n is negative + */ + static @NotNull String @NotNull [] parseFrom(@NotNull String value, int n) { + return parseFrom(value, n, 0); + } + + /** + * Parse a String with a StringWritableCsv into its composing Strings represented as Strings. No + * validation is performed on the individual attribute-values returned. All the available + * attribute-values will be returned. + * + * @param value The String with the set of attribute-values + * @return An array of Strings which represent the individual attribute-values + * @throws IllegalArgumentException If value is null + */ + static @NotNull String @NotNull [] parseFrom(@NotNull String value) { + return parseFrom(value, 0, 0); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/UsAsciiUtils.java b/scram-common/src/main/java/com/ongres/scram/common/UsAsciiUtils.java new file mode 100644 index 0000000..ed8390b --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/UsAsciiUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.util.Preconditions.checkNotNull; + +import java.nio.CharBuffer; + +/** + * Utility to remove non-printable characters from the US-ASCII String. + */ +final class UsAsciiUtils { + + private UsAsciiUtils() { + throw new IllegalStateException("Utility class"); + } + + /** + * Removes non-printable characters from the US-ASCII String. + * + * @param value The original String + * @return The possibly modified String, without non-printable US-ASCII characters. + * @throws IllegalArgumentException If the String is null or contains non US-ASCII characters. + */ + static char[] toPrintable(final char[] value) { + checkNotNull(value, "value"); + CharBuffer charBuffer = CharBuffer.allocate(value.length); + for (char ch : value) { + if (ch >= 127) { + throw new IllegalArgumentException( + "value contains character '" + ch + "' which is non US-ASCII"); + } else if (ch >= 32) { + charBuffer.put(ch); + } + } + // Flip the buffer to prepare for reading + charBuffer.flip(); + char[] charArray = new char[charBuffer.remaining()]; + charBuffer.get(charArray); + return charArray; + } + + /** + * Removes non-printable characters from the US-ASCII String. + * + * @param value The original String + * @return The possibly modified String, without non-printable US-ASCII characters. + * @throws IllegalArgumentException If the String is null or contains non US-ASCII characters. + */ + static String toPrintable(final String value) { + char[] charArray = checkNotNull(value, "value").toCharArray(); + return new String(toPrintable(charArray)); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/exception/ScramException.java b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramException.java new file mode 100644 index 0000000..fb7f570 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.exception; + +/** + * This class represents an generic error when using SCRAM, which is a SASL method. + */ +public class ScramException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new instance of ScramException with a detailed message. + * + * @param detail A String containing details about the exception + */ + public ScramException(String detail) { + super(detail); + } + + /** + * Constructs a new instance of ScramException with a detailed message and a root cause. + * + * @param detail A String containing details about the exception + * @param ex The root exception + */ + public ScramException(String detail, Throwable ex) { + super(detail, ex); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java new file mode 100644 index 0000000..d3517da --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramInvalidServerSignatureException.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.exception; + +/** + * This class represents an error when verifying the a base64-encoded ServerSignature in a + * {@code server-final-message}. + * + *

Is used by the client to verify that the server has access to the user's authentication + * information. + */ +public class ScramInvalidServerSignatureException extends ScramException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message. + * + * @param detail A String containing details about the exception + */ + public ScramInvalidServerSignatureException(String detail) { + super(detail); + } + + /** + * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message and a + * root cause. + * + * @param detail A String containing details about the exception + * @param ex The root exception + */ + public ScramInvalidServerSignatureException(String detail, Throwable ex) { + super(detail, ex); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java new file mode 100644 index 0000000..64e19e6 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.exception; + +/** + * This class represents an error when parsing SCRAM messages. + */ +public class ScramParseException extends ScramException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new instance of ScramParseException with a detailed message. + * + * @param detail A String containing details about the exception + */ + public ScramParseException(String detail) { + super(detail); + } + + /** + * Constructs a new instance of ScramParseException with a detailed message and a root cause. + * + * @param detail A String containing details about the exception + * @param ex The root exception + */ + public ScramParseException(String detail, Throwable ex) { + super(detail, ex); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/exception/ScramRuntimeException.java b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramRuntimeException.java new file mode 100644 index 0000000..42a77c8 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramRuntimeException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.exception; + +/** + * This class represents an error when using SCRAM, which is a SASL method. + */ +public class ScramRuntimeException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new instance of ScramRuntimeException with a detailed message. + * + * @param detail A String containing details about the exception + */ + public ScramRuntimeException(String detail) { + super(detail); + } + + /** + * Constructs a new instance of ScramRuntimeException with a detailed message and a root cause. + * + * @param detail A String containing details about the exception + * @param ex The root exception + */ + public ScramRuntimeException(String detail, Throwable ex) { + super(detail, ex); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java new file mode 100644 index 0000000..1d055ca --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/exception/ScramServerErrorException.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.exception; + +/** + * This class specifies an error that occurred during authentication exchange in a + * {@code server-final-message}. + * + *

It is sent by the server in its {@code server-final-message} and can help diagnose the reason + * for the authentication exchange failure. + */ +public class ScramServerErrorException extends ScramException { + + private static final long serialVersionUID = 1L; + + /** + * {@code server-error-value}. + */ + private final String serverError; + + /** + * Constructs a new instance of ScramServerErrorException with a detailed message. + * + * @param serverError The SCRAM error in the message + */ + public ScramServerErrorException(String serverError) { + super(ServerErrorValue.getErrorMessage(serverError)); + this.serverError = serverError; + } + + /** + * Constructs a new instance of ScramServerErrorException with a detailed message and a root + * cause. + * + * @param serverError The SCRAM error in the message + * @param ex The root exception + */ + public ScramServerErrorException(String serverError, Throwable ex) { + super(ServerErrorValue.getErrorMessage(serverError), ex); + this.serverError = serverError; + } + + /** + * Return the "e=" server-error-value from the server-final-message. + * + * @return the error type returned in the server-final-message + */ + public String getServerError() { + return serverError; + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/exception/ServerErrorValue.java b/scram-common/src/main/java/com/ongres/scram/common/exception/ServerErrorValue.java new file mode 100644 index 0000000..fc7c8f6 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/exception/ServerErrorValue.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.exception; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * This attribute specifies an error that occurred during authentication exchange. It is sent by the + * server in its final message and can help diagnose the reason for the authentication exchange + * failure. + */ +public final class ServerErrorValue { + + private static final ConcurrentMap ERROR_MESSAGE = initServerErrorValue(); + + private ServerErrorValue() { + throw new IllegalStateException(); + } + + private static ConcurrentMap initServerErrorValue() { + ConcurrentMap map = new ConcurrentHashMap<>(); + map.put("invalid-encoding", "The message format or encoding is incorrect"); + map.put("extensions-not-supported", "Requested extensions are not recognized by the server"); + map.put("invalid-proof", "The client-provided proof is invalid"); + map.put("channel-bindings-dont-match", + "Channel bindings sent by the client don't match those expected by the server."); + map.put("server-does-support-channel-binding", + "Server doesn't support channel binding at all."); + map.put("channel-binding-not-supported", "Channel binding is not supported for this user"); + map.put("unsupported-channel-binding-type", + "The requested channel binding type is not supported."); + map.put("unknown-user", "The specified username is not recognized"); + map.put("invalid-username-encoding", + "The username encoding is invalid (either invalid UTF-8 or SASLprep failure)"); + map.put("no-resources", "The server lacks resources to process the request"); + map.put("other-error", "A generic error occurred that doesn't fit into other categories"); + return map; + } + + /** + * This get the error message used in a {@link ScramServerErrorException}. + * + * @param errorValue the {@code server-error-value} send by the server + * @return String with a user friendly message about the error + */ + public static String getErrorMessage(String errorValue) { + return ERROR_MESSAGE.get(errorValue); + } +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/exception/package-info.java b/scram-common/src/main/java/com/ongres/scram/common/exception/package-info.java new file mode 100644 index 0000000..7b547f5 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/exception/package-info.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/** + * This package expose the exceptions that can be throw by the client/server + * implementations of SCRAM. + */ + +package com.ongres.scram.common.exception; diff --git a/scram-common/src/main/java/com/ongres/scram/common/package-info.java b/scram-common/src/main/java/com/ongres/scram/common/package-info.java new file mode 100644 index 0000000..659994b --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/package-info.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/** + * This package expose the messages used to implement a client/server of Salted Challenge Response + * Authentication Mechanism (SCRAM). + * + * @implNote {@code extensions} are not supported. + */ + +package com.ongres.scram.common; diff --git a/scram-common/src/main/java/com/ongres/scram/common/util/Preconditions.java b/scram-common/src/main/java/com/ongres/scram/common/util/Preconditions.java new file mode 100644 index 0000000..465cbc2 --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/util/Preconditions.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Generic utility methods used to validate data. + * + * @apiNote This is not part of the public API of the SCRAM library, it's provided as a helper + * utility and could be renamed or removed at any time. + */ +public final class Preconditions { + + private Preconditions() { + throw new IllegalStateException("Utility class"); + } + + /** + * Checks that the argument is not null. + * + * @param value The value to be checked + * @param valueName The name of the value that is checked in the method + * @param The type of the value + * @return The same value passed as argument + * @throws IllegalArgumentException If value is null. + */ + public static @NotNull T checkNotNull(@Nullable T value, @NotNull String valueName) { + if (null == value) { + throw new IllegalArgumentException("Null value for '" + valueName + "'"); + } + return value; + } + + @SuppressWarnings("null") + public static @NotNull T castNonNull(@Nullable T ref) { + assert ref != null : "Misuse of castNonNull: called with a null argument"; + return (@NotNull T) ref; + } + + /** + * Checks that the String is not null and not empty. + * + * @param value The String to check + * @param valueName The name of the value that is checked in the method + * @return The same String passed as argument + * @throws IllegalArgumentException If value is null or empty + */ + public static @NotNull String checkNotEmpty(@NotNull String value, @NotNull String valueName) { + if (checkNotNull(value, valueName).isEmpty()) { + throw new IllegalArgumentException("The value for '" + valueName + "' must not be empty"); + } + return value; + } + + /** + * Checks that the char[] is not null and not empty. + * + * @param value The String to check + * @param valueName The name of the value that is checked in the method + * @return The same String passed as argument + * @throws IllegalArgumentException If value is null or empty + */ + public static char @NotNull [] checkNotEmpty(char @NotNull [] value, @NotNull String valueName) { + if (checkNotNull(value, valueName).length == 0) { + throw new IllegalArgumentException("The value for '" + valueName + "' must not be empty"); + } + return value; + } + + /** + * Checks that the argument is valid, based in a check boolean condition. + * + * @param check The boolean check + * @param valueName The name of the value that is checked in the method + * @throws IllegalArgumentException if check is not valid + */ + public static void checkArgument(boolean check, @NotNull String valueName) { + if (!check) { + throw new IllegalArgumentException("Argument '" + valueName + "' is not valid"); + } + } + + /** + * Checks that the argument is valid, based in a check boolean condition. + * + * @param check The boolean check + * @param valueName The name of the value that is checked in the method + * @param errMsg Detail of the error message + * @throws IllegalArgumentException if check is not valid + */ + public static void checkArgument(boolean check, @NotNull String valueName, + @NotNull String errMsg) { + if (!check) { + throw new IllegalArgumentException("Argument '" + valueName + "' is not valid, " + errMsg); + } + } + + /** + * Checks that the integer argument is positive. + * + * @param value The value to be checked + * @param valueName The name of the value that is checked in the method + * @return The same value passed as argument + * @throws IllegalArgumentException If value is equal or less than 0 + */ + public static int gt0(int value, @NotNull String valueName) { + if (value <= 0) { + throw new IllegalArgumentException("'" + valueName + "' must be positive, was: " + value); + } + return value; + } + + /** + * Returns {@code true} if the given string is null or is the empty string. + * + * @param string a String reference to check + * @return {@code true} if the string is null or the string is empty + */ + public static boolean isNullOrEmpty(@Nullable String string) { + return string == null || string.isEmpty(); + } + +} diff --git a/scram-common/src/main/java/com/ongres/scram/common/util/TlsServerEndpoint.java b/scram-common/src/main/java/com/ongres/scram/common/util/TlsServerEndpoint.java new file mode 100644 index 0000000..01f8d9f --- /dev/null +++ b/scram-common/src/main/java/com/ongres/scram/common/util/TlsServerEndpoint.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.jetbrains.annotations.NotNull; + +/** + * Utilitiy for extracting the {@code "tls-server-end-point"} channel binding data. + * + * @apiNote This is not part of the public API of the SCRAM library, it's provided as a helper to + * extract the channel-binding data and could be renamed or removed at any time. + */ +public final class TlsServerEndpoint { + + /** + * The "tls-server-end-point" Channel Binding Type. + */ + public static final String TLS_SERVER_END_POINT = "tls-server-end-point"; + + private TlsServerEndpoint() { + throw new IllegalStateException("Utility class"); + } + + /** + * Get the digest algorithm that would be used for a given signature algorithm name. + * + *

The TLS server's certificate bytes need to be hashed with SHA-256 if its signature algorithm + * is MD5 or SHA-1 as per RFC 5929 (https://tools.ietf.org/html/rfc5929#section-4.1). If something + * else is used, the same hash as the signature algorithm is used. + * + * @param signatureAlgorithm the signature algorithm name for the certificate signature algorithm + * @return the MessageDigest algorithm, or {@code null} if the name is not recognized + * @see The tls-server-end-point + * Channel Binding Type + */ + private static MessageDigest getDigestAlgorithm(final String signatureAlgorithm) { + int index = signatureAlgorithm.indexOf("with"); + String algorithm = index > 0 ? signatureAlgorithm.substring(0, index) : "SHA-256"; + // if the certificate's signatureAlgorithm uses a single hash + // function and that hash function neither MD5 nor SHA-1, then use + // the hash function associated with the certificate's signatureAlgorithm. + if (!algorithm.startsWith("SHA3-")) { + algorithm = algorithm.replace("SHA", "SHA-"); + } + // if the certificate's signatureAlgorithm uses a single hash + // function, and that hash function is either MD5 [RFC1321] or SHA-1 + // [RFC3174], then use SHA-256 [FIPS-180-3] + if ("MD5".equals(algorithm) || "SHA-1".equals(algorithm)) { + algorithm = "SHA-256"; + } + + try { + return MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + + /** + * The hash of the TLS server's certificate [RFC5280] as it appears, octet for octet, in the + * server's Certificate message. Note that the Certificate message contains a certificate_list, in + * which the first element is the server's certificate. + * + *

The TLS server's certificate bytes need to be hashed with SHA-256 if its signature algorithm + * is MD5 or SHA-1 as per RFC 5929 (https://tools.ietf.org/html/rfc5929#section-4.1). If something + * else is used, the same hash as the signature algorithm is used. + * + * @param serverCert the TLS server's peer certificate + * @return the hash of the TLS server's peer certificate + * @throws CertificateEncodingException if an encoding error occurs. + */ + public static byte @NotNull [] getChannelBindingData(final @NotNull X509Certificate serverCert) + throws CertificateEncodingException { + MessageDigest digestAlgorithm = getDigestAlgorithm(serverCert.getSigAlgName()); + if (digestAlgorithm == null) { + return new byte[0]; + } + return digestAlgorithm.digest(serverCert.getEncoded()); + } + +} diff --git a/scram-common/src/main/java9/module-info.java b/scram-common/src/main/java9/module-info.java new file mode 100644 index 0000000..f114de7 --- /dev/null +++ b/scram-common/src/main/java9/module-info.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +module com.ongres.scram.common { + requires transitive com.ongres.saslprep; + exports com.ongres.scram.common; + exports com.ongres.scram.common.exception; + exports com.ongres.scram.common.util; +} \ No newline at end of file diff --git a/scram-common/src/test/java/com/ongres/scram/JarFileCheckIT.java b/scram-common/src/test/java/com/ongres/scram/JarFileCheckIT.java new file mode 100644 index 0000000..813c84f --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/JarFileCheckIT.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.module.ModuleDescriptor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class JarFileCheckIT { + + private static JarFile jarFile; + private static Path buildJarPath; + + @BeforeAll + static void beforeAll() throws IOException { + buildJarPath = Paths.get(System.getProperty("buildJar")); + assertTrue(Files.exists(buildJarPath)); + jarFile = new JarFile(buildJarPath.toFile(), true); + } + + @AfterAll + static void afterAll() throws IOException { + jarFile.close(); + } + + @Test + void checkLicense() throws IOException { + JarEntry jarLicense = jarFile.getJarEntry("META-INF/LICENSE"); + assertNotNull(jarLicense, "LICENSE file should be present in the final JAR file"); + try (InputStream is = jarFile.getInputStream(jarLicense); + BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) { + String line = reader.readLine(); + assertEquals("Copyright (c) 2017 OnGres, Inc.", line); + } + } + + @Test + void checkMultiReleaseManifest() throws IOException { + Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); + String multiReleaseValue = mainAttributes.getValue(new Attributes.Name("Multi-Release")); + assertNotNull(multiReleaseValue); + assertEquals("true", multiReleaseValue); + } + + @Test + void checkModuleInfoPresent() throws IOException { + JarEntry jarModuleInfo = jarFile.getJarEntry("META-INF/versions/9/module-info.class"); + ModuleDescriptor moduleDescriptor = ModuleDescriptor.read(jarFile.getInputStream(jarModuleInfo)); + assertNotNull(moduleDescriptor); + assertEquals("com.ongres.scram.common", moduleDescriptor.name()); + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ClientFinalMessageTest.java b/scram-common/src/test/java/com/ongres/scram/common/ClientFinalMessageTest.java new file mode 100644 index 0000000..324ba73 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ClientFinalMessageTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class ClientFinalMessageTest { + + @Test + void writeToWithoutProofValid() { + StringBuilder sb = ClientFinalMessage.withoutProof(new StringBuilder(), + new Gs2Header(Gs2CbindFlag.CLIENT_NOT), null, RfcExampleSha1.FULL_NONCE); + + assertEquals(RfcExampleSha1.CLIENT_FINAL_MESSAGE_WITHOUT_PROOF, sb.toString()); + } + +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ClientFirstMessageTest.java b/scram-common/src/test/java/com/ongres/scram/common/ClientFirstMessageTest.java new file mode 100644 index 0000000..524fba1 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ClientFirstMessageTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.ongres.scram.common.exception.ScramParseException; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ClientFirstMessageTest { + + @ParameterizedTest + @CsvSource(value = {"CLIENT_NOT,cbind,,user,nonce", + "CHANNEL_BINDING_REQUIRED,,,user,nonce", + "CHANNEL_BINDING_REQUIRED,tls-*,,user,nonce", + "CLIENT_YES_SERVER_NOT,,,,nonce", + "CLIENT_YES_SERVER_NOT,,,'',nonce", + "CHANNEL_BINDING_REQUIRED,tls-export,,u,", + "CHANNEL_BINDING_REQUIRED,tls-export,,u,''"}) + void constructorTestInvalid(@NotNull Gs2CbindFlag flag, String cbName, String authzid, + @NotNull String user, @NotNull String nonce) { + assertThrows(IllegalArgumentException.class, + () -> new ClientFirstMessage(flag, cbName, authzid, user, nonce)); + } + + @Test + void writeToValidValues() { + assertEquals("n,,n=user,r=fyko", + new ClientFirstMessage("user", "fyko").toString()); + assertEquals("y,,n=user,r=fyko", + new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, null, "user", "fyko") + .toString()); + assertEquals("p=tls-server-end-point,,n=user,r=fyko", + new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", null, + "user", "fyko").toString()); + assertEquals("p=tls-server-end-point,a=authzid,n=user,r=fyko", + new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", + "authzid", "user", "fyko").toString()); + } + + @Test + void parseToValidValues() throws ScramParseException { + assertEquals(ClientFirstMessage.parseFrom("n,,n=user,r=fyko").toString(), + new ClientFirstMessage("user", "fyko").toString()); + assertEquals(ClientFirstMessage.parseFrom("y,,n=user,r=fyko").toString(), + new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, null, "user", "fyko") + .toString()); + assertEquals(ClientFirstMessage.parseFrom("p=tls-server-end-point,,n=user,r=fyko").toString(), + new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", null, + "user", "fyko").toString()); + assertEquals( + ClientFirstMessage.parseFrom("p=tls-server-end-point,a=authzid,n=user,r=fyko").toString(), + new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", + "authzid", "user", "fyko").toString()); + } + + @Test + void parseFromValidValues() throws ScramParseException { + ClientFirstMessage m1 = ClientFirstMessage.parseFrom("n,,n=user,r=" + CLIENT_NONCE); + assertFalse(m1.isChannelBindingRequired()); + assertSame(Gs2CbindFlag.CLIENT_NOT, m1.getGs2Header().getChannelBindingFlag()); + assertNull(m1.getGs2Header().getChannelBindingName()); + assertNull(m1.getGs2Header().getAuthzid()); + assertEquals("user", m1.getUsername()); + assertEquals(CLIENT_NONCE, m1.getClientNonce()); + + ClientFirstMessage m2 = ClientFirstMessage.parseFrom("y,,n=user,r=" + CLIENT_NONCE); + assertTrue( + !m2.isChannelBindingRequired() + && m2.getGs2Header().getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT + && null == m2.getGs2Header().getAuthzid() && "user".equals(m2.getUsername()) + && CLIENT_NONCE.equals(m2.getClientNonce())); + + ClientFirstMessage m3 = ClientFirstMessage.parseFrom("y,a=user2,n=user,r=" + CLIENT_NONCE); + assertTrue( + !m3.isChannelBindingRequired() + && m3.getGs2Header().getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT + && null != m3.getGs2Header().getAuthzid() + && "user2".equals(m3.getGs2Header().getAuthzid()) + && "user".equals(m3.getUsername()) && CLIENT_NONCE.equals(m3.getClientNonce())); + + ClientFirstMessage m4 = + ClientFirstMessage.parseFrom("p=tls-unique,a=user2,n=user,r=" + CLIENT_NONCE); + assertTrue(m4.isChannelBindingRequired()); + assertSame(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, m4.getGs2Header().getChannelBindingFlag()); + assertNotNull(m4.getGs2Header().getChannelBindingName()); + assertEquals("tls-unique", m4.getGs2Header().getChannelBindingName()); + assertNotNull(m4.getGs2Header().getAuthzid()); + assertEquals("user2", m4.getGs2Header().getAuthzid()); + assertEquals("user", m4.getUsername()); + assertEquals(CLIENT_NONCE, m4.getClientNonce()); + } + + @Test + void parseFromInvalidValues() { + String[] invalidValues = new String[] { + "n,,r=user,r=" + CLIENT_NONCE, "n,,z=user,r=" + CLIENT_NONCE, "n,,n=user", "n,", "n,,", + "n,,n=user,r", "n,,n=user,r=" + }; + + int n = 0; + for (String s : invalidValues) { + try { + assertNotNull(ClientFirstMessage.parseFrom(s)); + } catch (ScramParseException e) { + n++; + } + } + + assertEquals(invalidValues.length, n); + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/Gs2AttributeValueTest.java b/scram-common/src/test/java/com/ongres/scram/common/Gs2AttributeValueTest.java new file mode 100644 index 0000000..72d30ef --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/Gs2AttributeValueTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.ongres.scram.common.Gs2AttributeValue; +import com.ongres.scram.common.Gs2Attributes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.NullSource; + +class Gs2AttributeValueTest { + + @ParameterizedTest + @NullSource + @EnumSource(value = Gs2Attributes.class, names = {"CHANNEL_BINDING_REQUIRED", "AUTHZID"}) + void constructorNotAllowsNullValuesForCbAuthzId(Gs2Attributes attrib) { + assertThrows(IllegalArgumentException.class, + () -> new Gs2AttributeValue(attrib, null)); + } + + @ParameterizedTest + @EnumSource(value = Gs2Attributes.class, names = {"CLIENT_NOT", "CLIENT_YES_SERVER_NOT"}) + void constructorAllowsNullValuesForClientnClienty(Gs2Attributes attrib) { + Gs2AttributeValue gs2 = assertDoesNotThrow(() -> new Gs2AttributeValue(attrib, null)); + assertNotNull(gs2); + } + + @Test + void parseIllegalValuesStructure() { + String[] values = + new String[] {"", "as", "asdfjkl", Gs2Attributes.CHANNEL_BINDING_REQUIRED.getChar() + "="}; + int n = 0; + for (String value : values) { + try { + assertNotNull(Gs2AttributeValue.parse(value)); + } catch (IllegalArgumentException e) { + n++; + } + } + + assertEquals(values.length, n, "Not every illegal value thrown IllegalArgumentException"); + } + + @Test + void parseIllegalValuesInvalidGS2Attibute() { + String[] values = new String[] {"z=asdfasdf", "i=value"}; + + int n = 0; + for (String value : values) { + try { + assertNotNull(Gs2AttributeValue.parse(value)); + } catch (IllegalArgumentException e) { + n++; + } + } + + assertEquals(values.length, n, "Not every illegal value thrown IllegalArgumentException"); + } + + @Test + void parseLegalValues() { + String[] values = new String[] {"n", "y", "p=value", "a=authzid"}; + for (String value : values) { + assertNotNull(Gs2AttributeValue.parse(value)); + } + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/Gs2HeaderTest.java b/scram-common/src/test/java/com/ongres/scram/common/Gs2HeaderTest.java new file mode 100644 index 0000000..6e0574e --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/Gs2HeaderTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.ongres.scram.common.Gs2CbindFlag; +import com.ongres.scram.common.Gs2Header; +import org.junit.jupiter.api.Test; + +class Gs2HeaderTest { + private static final String[] VALID_GS2HEADER_STRINGS = new String[] { + "n,", "y,", "n,a=blah", "p=tls-server-end-point,", "p=tls-server-end-point,a=b" + }; + private static final Gs2Header[] VALID_GS_2_HEADERS = new Gs2Header[] { + new Gs2Header(Gs2CbindFlag.CLIENT_NOT), + new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT), + new Gs2Header(Gs2CbindFlag.CLIENT_NOT, null, "blah"), + new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point"), + new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "tls-server-end-point", "b") + }; + + private void assertGS2Header(String expected, Gs2Header gs2Header) { + assertEquals(expected, gs2Header.writeTo(new StringBuilder()).toString()); + } + + @Test + void constructorValid() { + for (int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { + assertGS2Header(VALID_GS2HEADER_STRINGS[i], VALID_GS_2_HEADERS[i]); + } + } + + @Test + void constructorInvalid1() { + assertThrows(IllegalArgumentException.class, + () -> new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null)); + } + + @Test + void constructorInvalid2() { + assertThrows(IllegalArgumentException.class, + () -> new Gs2Header(Gs2CbindFlag.CLIENT_NOT, "blah")); + } + + @Test + void constructorInvalid3() { + assertThrows(IllegalArgumentException.class, + () -> new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, "blah")); + } + + @Test + void constructorInvalid4() { + assertThrows(IllegalArgumentException.class, + () -> new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, "b")); + } + + @Test + void parseFromInvalid() { + String[] invalids = new String[] {"Z,", "n,Z=blah", "p,", "n=a,"}; + int n = 0; + for (String invalid : invalids) { + try { + Gs2Header.parseFrom(invalid); + } catch (IllegalArgumentException e) { + n++; + } + } + + assertEquals(invalids.length, n); + } + + @Test + void parseFromValid() { + for (int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { + assertGS2Header( + VALID_GS_2_HEADERS[i].writeTo(new StringBuilder()).toString(), + Gs2Header.parseFrom(VALID_GS2HEADER_STRINGS[i])); + } + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java b/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java new file mode 100644 index 0000000..2e7aff0 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha1.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +/** + * Constants for examples of the RFC for SHA-1 tests. + */ +public class RfcExampleSha1 { + public static final String USER = "user"; + public static final String PASSWORD = "pencil"; + public static final String CLIENT_NONCE = "fyko+d2lbbFgONRv9qkxdawL"; + public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = + "n=" + USER + ",r=" + CLIENT_NONCE; + public static final String CLIENT_FIRST_MESSAGE = + "n,," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; + public static final String SERVER_SALT = "QSXCR+Q6sek8bf92"; + public static final int SERVER_ITERATIONS = 4096; + public static final String SERVER_NONCE = "3rfcNHYJY1ZVvWVs7j"; + public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; + public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT + + ",i=" + SERVER_ITERATIONS; + public static final String GS2_HEADER_BASE64 = "biws"; + public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 + + ",r=" + FULL_NONCE; + public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," + + SERVER_FIRST_MESSAGE + "," + + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; + public static final String CLIENT_FINAL_MESSAGE_PROOF = "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="; + public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF + + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; + public static final String SERVER_FINAL_MESSAGE = "v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java b/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java new file mode 100644 index 0000000..4036d4d --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/RfcExampleSha256.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +/** + * Constants for examples of the RFC for SHA-256 tests. + */ +public class RfcExampleSha256 { + public static final String USER = "user"; + public static final String PASSWORD = "pencil"; + public static final String CLIENT_NONCE = "rOprNGfwEbeRWgbNEkqO"; + public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = + "n=" + USER + ",r=" + CLIENT_NONCE; + public static final String CLIENT_FIRST_MESSAGE = + "n," + "," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; + public static final String SERVER_SALT = "W22ZaJ0SNY7soEsUEjb6gQ=="; + public static final int SERVER_ITERATIONS = 4096; + public static final String SERVER_NONCE = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0"; + public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; + public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT + + ",i=" + SERVER_ITERATIONS; + public static final String GS2_HEADER_BASE64 = "biws"; + public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 + + ",r=" + FULL_NONCE; + public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," + + SERVER_FIRST_MESSAGE + "," + + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; + public static final String CLIENT_FINAL_MESSAGE_PROOF = + "dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ="; + public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF + + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; + public static final String SERVER_FINAL_MESSAGE = + "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4="; +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/SaslPrepTest.java b/scram-common/src/test/java/com/ongres/scram/common/SaslPrepTest.java new file mode 100644 index 0000000..0540df2 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/SaslPrepTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import com.ongres.stringprep.Profile; +import com.ongres.stringprep.Stringprep; +import org.junit.jupiter.api.Test; + +class SaslPrepTest { + + private static final Profile saslPrep = Stringprep.getProvider("SASLprep"); + + @Test + void rfc4013Examples() throws IOException { + // Taken from https://tools.ietf.org/html/rfc4013#section-3 + assertEquals("IX", saslPrep.prepareStored("I\u00ADX")); + assertEquals("user", saslPrep.prepareStored("user")); + assertEquals("USER", saslPrep.prepareStored("USER")); + assertEquals("a", saslPrep.prepareStored("\u00AA")); + assertEquals("IX", saslPrep.prepareStored("\u2168")); + try { + saslPrep.prepareStored("\u0007"); + fail("Should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Prohibited ASCII control \"0x0007\"", e.getMessage()); + } + try { + saslPrep.prepareStored("\u0627\u0031"); + fail("Should thow IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("RandALCat character is not the first and the last character", e.getMessage()); + } + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java b/scram-common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java new file mode 100644 index 0000000..a378a86 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.RfcExampleSha1.CLIENT_FINAL_MESSAGE_PROOF; +import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; +import static com.ongres.scram.common.RfcExampleSha1.FULL_NONCE; +import static com.ongres.scram.common.RfcExampleSha1.GS2_HEADER_BASE64; +import static com.ongres.scram.common.RfcExampleSha1.SERVER_FINAL_MESSAGE; +import static com.ongres.scram.common.RfcExampleSha1.SERVER_ITERATIONS; +import static com.ongres.scram.common.RfcExampleSha1.SERVER_SALT; +import static com.ongres.scram.common.RfcExampleSha1.USER; +import static com.ongres.scram.common.ScramAttributes.CLIENT_PROOF; +import static com.ongres.scram.common.ScramAttributes.USERNAME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.ongres.scram.common.exception.ScramParseException; +import org.junit.jupiter.api.Test; + +class ScramAttributeValueTest { + + private static final Object NULL = null; // Skip validation for null analisys + + @Test + void constructorDoesNotAllowNullValue() { + assertThrows(IllegalArgumentException.class, + () -> new ScramAttributeValue(USERNAME, (String) NULL), + "A null value must throw an IllegalArgumentException"); + } + + @Test + void parseIllegalValuesStructure() { + String[] values = new String[] { + null, "", "asdf", "asdf=a", CLIENT_PROOF.getChar() + "=", CLIENT_PROOF.getChar() + ",a" + }; + int n = 0; + for (String value : values) { + try { + assertNotNull(ScramAttributeValue.parse(value)); + } catch (ScramParseException e) { + n++; + } + } + + assertEquals(values.length, n, "Not every illegal value thrown ScramParseException"); + } + + @Test + void parseIllegalValuesInvalidSCRAMAttibute() { + // SCRAM allows for extensions. If a new attribute is supported and its value has been used + // below, + // test will fail and will need to be fixed + String[] values = new String[] {"z=asdfasdf", "!=value"}; + + for (String value : values) { + assertThrows(ScramParseException.class, () -> ScramAttributeValue.parse(value)); + } + } + + @Test + void parseLegalValues() throws ScramParseException { + String[] legalValues = new String[] { + CLIENT_PROOF.getChar() + "=" + "proof", + USERNAME.getChar() + "=" + "username", + "n=" + USER, + "r=" + CLIENT_NONCE, + "r=" + FULL_NONCE, + "s=" + SERVER_SALT, + "i=" + SERVER_ITERATIONS, + "c=" + GS2_HEADER_BASE64, + "p=" + CLIENT_FINAL_MESSAGE_PROOF, + SERVER_FINAL_MESSAGE, + }; + for (String value : legalValues) { + assertNotNull(ScramAttributeValue.parse(value)); + } + + assertNotNull(ScramAttributeValue.parse("e=unsupported-channel-binding-type")); + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java b/scram-common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java new file mode 100644 index 0000000..33419b6 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Locale; + +import org.junit.jupiter.api.Test; + +class ScramFunctionsTest { + private void assertBytesEqualsBase64(String expected, byte[] actual) { + assertArrayEquals(ScramStringFormatting.base64Decode(expected), actual); + } + + @Test + void hmac() { + String message = "The quick brown fox jumps over the lazy dog"; + byte[] key = generateSaltedPasswordSha256(); + assertBytesEqualsBase64( + "1zw4SuJ+BRmn6corI3Y1+eGQwGQ=", + ScramFunctions.hmac(ScramMechanism.SCRAM_SHA_1, + key, message.getBytes(StandardCharsets.US_ASCII))); + assertBytesEqualsBase64( + "+Q4a/8FjMG6MoLE/8y8LWyEBJmjpVLbuXtU8rnLd/5E=", + ScramFunctions.hmac(ScramMechanism.SCRAM_SHA_256, + key, message.getBytes(StandardCharsets.US_ASCII))); + assertBytesEqualsBase64( + "uu/n7DW8kGuWWANfaHT/rwEU/EBufylMLTWOCtLmvxp" + + "2Zmx01UpZO4nauZfkaSWob8jt7no0+xWIVXv/d7LvkQ==", + ScramFunctions.hmac(ScramMechanism.SCRAM_SHA_512, + key, message.getBytes(StandardCharsets.US_ASCII))); + } + + private byte[] generateSaltedPassword() { + return ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "pencil".toCharArray(), + Base64.getDecoder().decode("QSXCR+Q6sek8bf92".getBytes(StandardCharsets.UTF_8)), 4096); + } + + private byte[] generateSaltedPasswordSha256() { + byte[] salt = Base64.getDecoder().decode("Fgh8JU2AlRjBHUsIU/GgtQ=="); + byte[] saltedPassword = ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_256, StringPreparation.SASL_PREPARATION, "test".toCharArray(), + salt, 4096); + byte[] clientKey = ScramFunctions.clientKey(ScramMechanism.SCRAM_SHA_256, saltedPassword); + byte[] storedKey = ScramFunctions.storedKey(ScramMechanism.SCRAM_SHA_256, clientKey); + byte[] serverKey = ScramFunctions.serverKey(ScramMechanism.SCRAM_SHA_256, saltedPassword); + String encodeToStringSalt = Base64.getEncoder().encodeToString(salt); + String encodeToStringClient = Base64.getEncoder().encodeToString(storedKey); + String encodeToStringServer = Base64.getEncoder().encodeToString(serverKey); + assertEquals("XiT346dvVvPmnmTWeW0djrcMYBGuiQDh8QYbBJaBm/I=", encodeToStringClient); + assertEquals("CY9vUvDF8v6FIR8Zwircvd82YV58J5AwWiMWwfssuwg=", encodeToStringServer); + String pw = + String.format(Locale.ROOT, "%S$%d:%s$%s:%s", ScramMechanism.SCRAM_SHA_256.getName(), 4096, + encodeToStringSalt, encodeToStringClient, encodeToStringServer); + + assertEquals( + "SCRAM-SHA-256$4096:Fgh8JU2AlRjBHUsIU/GgtQ==$XiT346dvVvPmnmTWeW0djrcMYBGuiQDh8QYbBJaBm/I=:" + + "CY9vUvDF8v6FIR8Zwircvd82YV58J5AwWiMWwfssuwg=", + pw); + + return ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_256, StringPreparation.SASL_PREPARATION, "pencil".toCharArray(), + Base64.getDecoder().decode("W22ZaJ0SNY7soEsUEjb6gQ=="), 4096); + } + + @Test + void saltedPassword() { + assertBytesEqualsBase64("HZbuOlKbWl+eR8AfIposuKbhX30=", generateSaltedPassword()); + } + + @Test + void saltedPasswordWithSaslPrep() { + assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, + "\u2168\u3000a\u0300".toCharArray(), + Base64.getDecoder().decode("0BojBCBE6P2/N4bQ"), 6400)); + assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, + "\u00ADIX \u00E0".toCharArray(), + Base64.getDecoder().decode("0BojBCBE6P2/N4bQ"), 6400)); + assertBytesEqualsBase64("YniLes+b8WFMvBhtSACZyyvxeCc=", ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, "IX \u00E0".toCharArray(), + Base64.getDecoder().decode("0BojBCBE6P2/N4bQ"), 6400)); + assertBytesEqualsBase64("HZbuOlKbWl+eR8AfIposuKbhX30=", ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, + "\u0070enc\u1806il".toCharArray(), + Base64.getDecoder().decode("QSXCR+Q6sek8bf92"), 4096)); + + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_1, StringPreparation.SASL_PREPARATION, + "\u2168\u3000a\u0300\u0007".toCharArray(), + Base64.getDecoder().decode("QSXCR+Q6sek8bf92"), 6400)); + assertEquals("Prohibited ASCII control \"0x0007\"", e.getMessage()); + + assertBytesEqualsBase64("MFB9tXSMpUK2frvCND2TWRGdiVY=", + ScramFunctions.saltedPassword( + ScramMechanism.SCRAM_SHA_1_PLUS, StringPreparation.SASL_PREPARATION, + "\u0070enc\u1806il \u00B4\u00BD".toCharArray(), + Base64.getDecoder().decode("QSXCR+Q6sek8bf92"), 4096)); + } + + @Test + void saltedPasswordSha256() { + assertBytesEqualsBase64("xKSVEDI6tPlSysH6mUQZOeeOp01r6B3fcJbodRPcYV0=", + generateSaltedPasswordSha256()); + } + + private byte[] generateClientKey() { + return ScramFunctions.clientKey(ScramMechanism.SCRAM_SHA_1, generateSaltedPassword()); + } + + private byte[] generateClientKeySha256() { + return ScramFunctions.clientKey(ScramMechanism.SCRAM_SHA_256, generateSaltedPasswordSha256()); + } + + @Test + void clientKey() { + assertBytesEqualsBase64("4jTEe/bDZpbdbYUrmaqiuiZVVyg=", generateClientKey()); + } + + @Test + void clientKeySha256() { + assertBytesEqualsBase64("pg/JI9Z+hkSpLRa5btpe9GVrDHJcSEN0viVTVXaZbos=", + generateClientKeySha256()); + } + + private byte[] generateStoredKey() { + return ScramFunctions.storedKey(ScramMechanism.SCRAM_SHA_1, generateClientKey()); + } + + private byte[] generateStoredKeySha256() { + return ScramFunctions.storedKey(ScramMechanism.SCRAM_SHA_256, generateClientKeySha256()); + } + + @Test + void storedKey() { + assertBytesEqualsBase64("6dlGYMOdZcOPutkcNY8U2g7vK9Y=", generateStoredKey()); + } + + @Test + void storedKeySha256() { + assertBytesEqualsBase64("WG5d8oPm3OtcPnkdi4Uo7BkeZkBFzpcXkuLmtbsT4qY=", + generateStoredKeySha256()); + } + + private byte[] generateServerKey() { + return ScramFunctions.serverKey(ScramMechanism.SCRAM_SHA_1, generateSaltedPassword()); + } + + private byte[] generateServerKeySha256() { + return ScramFunctions.serverKey(ScramMechanism.SCRAM_SHA_256, generateSaltedPasswordSha256()); + } + + @Test + void serverKey() { + assertBytesEqualsBase64("D+CSWLOshSulAsxiupA+qs2/fTE=", generateServerKey()); + } + + @Test + void serverKeySha256() { + assertBytesEqualsBase64("wfPLwcE6nTWhTAmQ7tl2KeoiWGPlZqQxSrmfPwDl2dU=", + generateServerKeySha256()); + } + + private byte[] generateClientSignature() { + return ScramFunctions.clientSignature(ScramMechanism.SCRAM_SHA_1, generateStoredKey(), + com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE); + } + + private byte[] generateClientSignatureSha256() { + return ScramFunctions.clientSignature(ScramMechanism.SCRAM_SHA_256, generateStoredKeySha256(), + com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE); + } + + @Test + void clientSignature() { + assertBytesEqualsBase64("XXE4xIawv6vfSePi2ovW5cedthM=", generateClientSignature()); + } + + @Test + void clientSignatureSha256() { + assertBytesEqualsBase64("0nMSRnwopAqKfwXHPA3jPrPL+0qDeDtYFEzxmsa+G98=", + generateClientSignatureSha256()); + } + + private byte[] generateClientProof() { + return ScramFunctions.clientProof(generateClientKey(), generateClientSignature()); + } + + private byte[] generateClientProofSha256() { + return ScramFunctions.clientProof(generateClientKeySha256(), generateClientSignatureSha256()); + } + + @Test + void clientProof() { + assertBytesEqualsBase64("v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", generateClientProof()); + } + + @Test + void clientProofSha256() { + assertBytesEqualsBase64("dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=", + generateClientProofSha256()); + } + + private byte[] generateServerSignature() { + return ScramFunctions.serverSignature(ScramMechanism.SCRAM_SHA_1, generateServerKey(), + com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE); + } + + private byte[] generateServerSignatureSha256() { + return ScramFunctions.serverSignature(ScramMechanism.SCRAM_SHA_256, generateServerKeySha256(), + com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE); + } + + @Test + void serverSignature() { + assertBytesEqualsBase64("rmF9pqV8S7suAoZWja4dJRkFsKQ=", generateServerSignature()); + } + + @Test + void serverSignatureSha256() { + assertBytesEqualsBase64("6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=", + generateServerSignatureSha256()); + } + + @Test + void verifyClientProof() { + assertTrue( + ScramFunctions.verifyClientProof( + ScramMechanism.SCRAM_SHA_1, generateClientProof(), generateStoredKey(), + com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE)); + } + + @Test + void verifyClientProofSha256() { + assertTrue( + ScramFunctions.verifyClientProof( + ScramMechanism.SCRAM_SHA_256, generateClientProofSha256(), generateStoredKeySha256(), + com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE)); + } + + @Test + void verifyServerSignature() { + assertTrue( + ScramFunctions.verifyServerSignature( + ScramMechanism.SCRAM_SHA_1, generateServerKey(), + com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE, generateServerSignature())); + } + + @Test + void verifyServerSignatureSha256() { + assertTrue( + ScramFunctions.verifyServerSignature( + ScramMechanism.SCRAM_SHA_256, generateServerKeySha256(), + com.ongres.scram.common.RfcExampleSha256.AUTH_MESSAGE, + generateServerSignatureSha256())); + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ScramMechanismTest.java b/scram-common/src/test/java/com/ongres/scram/common/ScramMechanismTest.java new file mode 100644 index 0000000..c527b78 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ScramMechanismTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class ScramMechanismTest { + + private static final byte[] EMPTY_KEY = new byte[32]; + + @ParameterizedTest + @ValueSource(strings = {"SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS"}) + void testIanaScramMechanisms(@NotNull String name) { + assertNotNull(ScramMechanism.byName(name)); + } + + @ParameterizedTest + @EnumSource(ScramMechanism.class) + void testNameConvention(ScramMechanism scramMechanism) { + // Note that SASL mechanism names are limited to 20 octets, which means that only + // hash function names with lengths shorter or equal to 9 octets + // (20-length("SCRAM-")-length("-PLUS") can be used. + String name = scramMechanism.getName(); + assertTrue(name.startsWith("SCRAM-"), "name should start with SCRAM-"); + if (scramMechanism.isPlus()) { + assertTrue(name.endsWith("-PLUS"), "name should end with -PLUS"); + } else { + assertFalse(name.endsWith("-PLUS"), "name should not end with -PLUS"); + } + + String hashName = name.replace("SCRAM-", "").replace("-PLUS", ""); + assertEquals(scramMechanism.getHashAlgorithmName(), hashName); + assertTrue(hashName.length() <= 9); + } + + @ParameterizedTest + @ValueSource(strings = {"SCRAM-SHA", "SHA-1-PLUS", "SCRAM-SHA-256-", "SCRAM-SHA-256-PLUS!"}) + @EmptySource + void byNameInvalid(@NotNull String name) { + assertNull(ScramMechanism.byName(name)); + } + + @ParameterizedTest + @NullSource + void byNullName(@NotNull String name) { + assertThrows(IllegalArgumentException.class, () -> ScramMechanism.byName(name)); + } + + @ParameterizedTest + @EnumSource(ScramMechanism.class) + void testHashSupportedByJvm(ScramMechanism scramMechanism) { + byte[] digest = scramMechanism.digest(new byte[0]); + assertNotNull(digest, "got a null digest"); + assertEquals(scramMechanism.getKeyLength() / 8, digest.length); + } + + @ParameterizedTest + @MethodSource("provideSupportedMechanisms") + void testHmacSupportedByJvm(@NotNull String mechanism) { + ScramMechanism scramMechanism = ScramMechanism.byName(mechanism); + assertNotNull(scramMechanism); + byte[] hmac = scramMechanism.hmac(EMPTY_KEY, new byte[0]); + assertNotNull(hmac, "got a null HMAC"); + assertEquals(scramMechanism.getKeyLength() / 8, hmac.length); + } + + private static @NotNull List<@NotNull String> provideSupportedMechanisms() { + return ScramMechanism.supportedMechanisms(); + } + +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java b/scram-common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java new file mode 100644 index 0000000..3e2cbf6 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ScramStringFormattingTest { + private static final String[] VALUES_NO_CHARS_TO_BE_ESCAPED = + new String[] {"asdf", "''--%%21", " ttt???"}; + private static final String[] VALUES_TO_BE_ESCAPED = new String[] { + ",", "=", "a,b", "===", "a=", ",=,", "=2C", "=3D" + }; + private static final String[] ESCAPED_VALUES = new String[] { + "=2C", "=3D", "a=2Cb", "=3D=3D=3D", "a=3D", "=2C=3D=2C", "=3D2C", "=3D3D" + }; + private static final String[] INVALID_SASL_NAMES = + new String[] {"=", "as,df", "a=b", " ttt???=2D"}; + + @Test + void toSaslNameNoCharactersToBeEscaped() { + for (String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { + assertEquals(s, ScramStringFormatting.toSaslName(s)); + } + } + + @Test + void toSaslNameWithCharactersToBeEscaped() { + for (int i = 0; i < VALUES_TO_BE_ESCAPED.length; i++) { + assertEquals(ESCAPED_VALUES[i], ScramStringFormatting.toSaslName(VALUES_TO_BE_ESCAPED[i])); + } + } + + @Test + void fromSaslNameNoCharactersToBeEscaped() { + for (String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { + assertEquals(s, ScramStringFormatting.fromSaslName(s)); + } + } + + @Test + void fromSaslNameWithCharactersToBeUnescaped() { + for (int i = 0; i < ESCAPED_VALUES.length; i++) { + assertEquals(VALUES_TO_BE_ESCAPED[i], ScramStringFormatting.fromSaslName(ESCAPED_VALUES[i])); + } + } + + @Test + void fromSaslNameWithInvalidCharacters() { + int n = 0; + for (String s : INVALID_SASL_NAMES) { + try { + assertEquals(s, ScramStringFormatting.fromSaslName(s)); + } catch (IllegalArgumentException e) { + n++; + } + } + + assertTrue(n == INVALID_SASL_NAMES.length, "Not all values produced IllegalArgumentException"); + } + +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ServerFinalMessageTest.java b/scram-common/src/test/java/com/ongres/scram/common/ServerFinalMessageTest.java new file mode 100644 index 0000000..214c967 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ServerFinalMessageTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.RfcExampleSha1.AUTH_MESSAGE; +import static com.ongres.scram.common.RfcExampleSha1.PASSWORD; +import static com.ongres.scram.common.RfcExampleSha1.SERVER_FINAL_MESSAGE; +import static com.ongres.scram.common.RfcExampleSha1.SERVER_ITERATIONS; +import static com.ongres.scram.common.RfcExampleSha1.SERVER_SALT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Base64; + +import com.ongres.scram.common.exception.ScramParseException; +import org.junit.jupiter.api.Test; + +class ServerFinalMessageTest { + @Test + void validConstructor() { + byte[] serverKey = ScramFunctions.serverKey( + ScramMechanism.SCRAM_SHA_1, + ScramFunctions.saltedPassword(ScramMechanism.SCRAM_SHA_1, StringPreparation.NO_PREPARATION, + PASSWORD.toCharArray(), + Base64.getDecoder().decode(SERVER_SALT), + SERVER_ITERATIONS)); + ServerFinalMessage serverFinalMessage1 = new ServerFinalMessage( + ScramFunctions.serverSignature(ScramMechanism.SCRAM_SHA_1, serverKey, AUTH_MESSAGE)); + assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); + assertFalse(serverFinalMessage1.isError()); + + ServerFinalMessage serverFinalMessage2 = + new ServerFinalMessage("unknown-user"); + assertEquals(ScramAttributes.ERROR.getChar() + "=unknown-user", + serverFinalMessage2.toString()); + assertTrue(serverFinalMessage2.isError()); + } + + @Test + void validParseFrom() throws ScramParseException { + ServerFinalMessage serverFinalMessage1 = ServerFinalMessage.parseFrom(SERVER_FINAL_MESSAGE); + assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); + assertFalse(serverFinalMessage1.isError()); + + ServerFinalMessage serverFinalMessage2 = + ServerFinalMessage.parseFrom("e=channel-binding-not-supported"); + assertEquals("e=channel-binding-not-supported", serverFinalMessage2.toString()); + assertTrue(serverFinalMessage2.isError()); + assertEquals("channel-binding-not-supported", + serverFinalMessage2.getServerError()); + } + + @Test + void invalidServerError() throws ScramParseException { + assertThrows(IllegalArgumentException.class, + () -> ServerFinalMessage.parseFrom("e=binding-et-supported")); + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/ServerFirstMessageTest.java b/scram-common/src/test/java/com/ongres/scram/common/ServerFirstMessageTest.java new file mode 100644 index 0000000..142c54a --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/ServerFirstMessageTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static com.ongres.scram.common.RfcExampleSha1.CLIENT_NONCE; +import static com.ongres.scram.common.RfcExampleSha1.SERVER_FIRST_MESSAGE; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.ongres.scram.common.exception.ScramParseException; +import org.junit.jupiter.api.Test; + +class ServerFirstMessageTest { + @Test + void validConstructor() { + ServerFirstMessage serverFirstMessage = new ServerFirstMessage( + CLIENT_NONCE, + "3rfcNHYJY1ZVvWVs7j", + "QSXCR+Q6sek8bf92", + 4096); + + assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); + } + + @Test + void validParseFrom() throws ScramParseException { + ServerFirstMessage serverFirstMessage = + ServerFirstMessage.parseFrom(SERVER_FIRST_MESSAGE, CLIENT_NONCE); + + assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/StringPreparationTest.java b/scram-common/src/test/java/com/ongres/scram/common/StringPreparationTest.java new file mode 100644 index 0000000..257bf71 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/StringPreparationTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.security.SecureRandom; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class StringPreparationTest { + private static final String[] ONLY_NON_PRINTABLE_STRINGS = + new String[] {(char) 13 + "", (char) 13 + "\n\n"}; + + @ParameterizedTest + @NullAndEmptySource + void doNormalizeNullEmpty(char[] value) { + for (StringPreparation stringPreparation : StringPreparation.values()) { + assertThrows(IllegalArgumentException.class, () -> stringPreparation.normalize(value)); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "toastingxenotime", "infecttolerant", "cobblerjack", "zekedigital", "freshscarisdale", + "lamwaylon", + "lagopodousmonkeys", "fanfarecheesy", "willowfinnegan", "canoeamoeba", "stinkeroddball", + "terracecomet", + "cakebrazos", "headersidesaddle", "cloudultracrepidarian", "grimegastropub", "stallchilli", + "shawnapentagon", "chapeltarp", "rydbergninja", "differencegym", "europiummuscle", + "swilledonce", + "defensivesyntaxis", "desktopredundant", "stakingsky", "goofywaiting", "boundsemm", + "pipermonstrous", + "faintfrog", "riskinsist", "constantjunkie", "rejectbroth", "ceilbeau", "ponyjaialai", + "burnishselfies", + "unamusedglenmore", "parmesanporcupine", "suteconcerto", "ribstony", "sassytwelve", + "coursesnasturtium", + "singlecinders", "kinkben", "chiefpussface", "unknownivery", "robterra", "wearycubes", + "bearcontent", + "aquifertrip", "insulinlick", "batterypeace", "rubigloo", "fixessnizort", "coalorecheesy", + "logodarthvader", + "equipmentbizarre", "charitycolne", "gradecomputer", "incrediblegases", "ingotflyingfish", + "abaftmounting", + "kissingfluke", "chesterdinky", "anthropicdip", "portalcairo", "purebredhighjump", + "jamaicansteeping", + "skaterscoins", "chondrulelocust", "modespretty", "otisnadrid", "lagoonone", "arrivepayday", + "lawfulpatsy", + "customersdeleted", "superiorarod", "abackwarped", "footballcyclic", "sawtshortstop", + "waskerleysanidine", + "polythenehead", "carpacciosierra", "gnashgabcheviot", "plunkarnisdale", "surfacebased", + "wickedpark", + "capitalistivan", "kinglassmuse", "adultsceiriog", "medrones", "climaxshops", + "archeangolfer", "tomfront", + "kobeshift", "nettleaugustus", "bitesizedlion", "crickedbunting", "englishrichard", + "dangerousdelmonico", + "sparklemicrosoft", "kneepadsfold", "enunciatesunglasses", "parchmentsteak", "meigpiton", + "puttingcitrusy", + "eyehash", "newtonatomiser", "witchesburberry", "positionwobbly", "clipboardamber", + "ricolobster", + "calendarpetal", "shinywound", "dealemral", "moonrakerfinnish", "banditliberated", + "whippedfanatical", + "jargongreasy", "yumlayla", "dwarfismtransition", "doleriteduce", "sikickball", + "columngymnastics", "draybowmont", "jupitersnorkling", "siderealmolding", "dowdyrosary", + "novaskeeter", + "whickerpulley", "rutlandsliders", "categoryflossed", "coiltiedogfish", "brandwaren", + "altairlatigo", + "acruxyouthscape", "harmonicdash", "jasperserver", "slicedaggie", "gravityfern", + "bitsstorm", + "readymadehobby", "surfeitgrape", "pantheonslabs", "ammandecent", "skicrackers", + "speyfashions", + "languagedeeno", "pettyconfit", "minutesshimmering", "thinhopeangellist", + "sleevelesscadmium", "controlarc", + "robinvolvox", "postboxskylark", "tortepleasing", "lutzdillinger", "amnioteperl", + "burntmaximize", + "gamblingearn", "bumsouch", "coronagraphdown", "bodgeelearning", "hackingscraper", + "hartterbium", + "mindyurgonian", "leidlebalki", "labelthumbs", "lincolncrisps", "pearhamster", "termsfiona", + "tickingsomber", "hatellynfi", "northumberlandgrotesque", "harpistcaramel", "gentryswiss", + "illusionnooks", + "easilyrows", "highgluten", "backedallegiance", "laelsitesearch", "methodfix", + "teethminstral", + "chemicalchildish", "likablepace", "alikealeph", "nalasincere", "investbaroque", + "conditionenvelope", + "splintsmccue", "carnonprompt", "resultharvey", "acceptsheba", "redditmonsoon", + "multiplepostbox", + "invitationchurch", "drinksgaliath", "ordersvivid", "mugsgit", "clumpingfreak" + }) + void doNormalizeValidAsciiCases(String username) { + char[] validAsciiUsername = username.toCharArray(); + for (StringPreparation stringPreparation : StringPreparation.values()) { + assertArrayEquals(validAsciiUsername, stringPreparation.normalize(validAsciiUsername)); + } + } + + private static Stream provideRandomStrings() { + final SecureRandom srand = new SecureRandom(); + return IntStream.iterate(0, i -> i) + .limit(1000) + .mapToObj(c -> srand.ints(32, 127) + .filter(i -> (i >= 32) && (i <= 127)) + .limit(128) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString()); + } + + /* + * Some simple random testing won't hurt. If a test would fail, create new test with the generated + * word. + */ + @ParameterizedTest + @MethodSource("provideRandomStrings") + void doNormalizeValidAsciiRandom(String random) { + for (StringPreparation stringPreparation : StringPreparation.values()) { + assertArrayEquals(random.toCharArray(), stringPreparation.normalize(random.toCharArray()), + "'" + random + "' is a printable ASCII string, should not be changed by normalize()"); + } + } + + @Test + void doNormalizeNoPreparationEmptyAfterNormalization() { + for (String s : ONLY_NON_PRINTABLE_STRINGS) { + char[] charArray = s.toCharArray(); + assertThrows(IllegalArgumentException.class, + () -> StringPreparation.NO_PREPARATION.normalize(charArray)); + } + } + + @Test + void doNormalizeNoPreparationNonEmptyAfterNormalization() { + // No exception should be thrown + for (String s : ONLY_NON_PRINTABLE_STRINGS) { + StringPreparation.NO_PREPARATION.normalize((s + "a").toCharArray()); + } + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/StringWritableCsvTest.java b/scram-common/src/test/java/com/ongres/scram/common/StringWritableCsvTest.java new file mode 100644 index 0000000..c8f175a --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/StringWritableCsvTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class StringWritableCsvTest { + private static final String[] ONE_ARG_VALUES = + new String[] {"c=channel", "i=4096", "a=authzid", "n"}; + private static final String SEVERAL_VALUES_STRING = "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL"; + + @Test + void writeToNullOrEmpty() { + assertEquals(0, StringWritableCsv.writeTo(new StringBuilder()).length()); + assertEquals(0, + StringWritableCsv.writeTo(new StringBuilder(), new StringWritable[] {}).length()); + } + + @Test + void writeToOneArg() { + StringWritable[] pairs = new StringWritable[] { + new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, "channel"), + new ScramAttributeValue(ScramAttributes.ITERATION, "" + 4096), + new Gs2AttributeValue(Gs2Attributes.AUTHZID, "authzid"), + new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null) + }; + + for (int i = 0; i < pairs.length; i++) { + assertEquals(ONE_ARG_VALUES[i], + StringWritableCsv.writeTo(new StringBuilder(), pairs[i]).toString()); + } + } + + @Test + void writeToSeveralArgs() { + assertEquals( + SEVERAL_VALUES_STRING, + StringWritableCsv.writeTo( + new StringBuilder(), + new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null), + null, + new ScramAttributeValue(ScramAttributes.USERNAME, "user"), + new ScramAttributeValue(ScramAttributes.NONCE, "fyko+d2lbbFgONRv9qkxdawL") + + ).toString()); + } + + @Test + void parseFromEmpty() { + assertArrayEquals(new String[] {}, StringWritableCsv.parseFrom("")); + } + + @Test + void parseFromOneArgWithLimitsOffsets() { + for (String s : ONE_ARG_VALUES) { + assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s)); + } + + int[] numberEntries = new int[] {0, 1}; + for (int n : numberEntries) { + for (String s : ONE_ARG_VALUES) { + assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n)); + } + } + for (String s : ONE_ARG_VALUES) { + assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3)); + } + + for (int n : numberEntries) { + for (String s : ONE_ARG_VALUES) { + assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n, 0)); + } + } + for (String s : ONE_ARG_VALUES) { + assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3, 0)); + } + + for (int n : numberEntries) { + for (String s : ONE_ARG_VALUES) { + assertArrayEquals(new String[] {null}, StringWritableCsv.parseFrom(s, n, 1)); + } + } + } + + @Test + void parseFromSeveralArgsWithLimitsOffsets() { + assertArrayEquals( + new String[] {"n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL"}, + StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING)); + + assertArrayEquals( + new String[] {"n", ""}, + StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2)); + + assertArrayEquals( + new String[] {"", "n=user"}, + StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 1)); + + assertArrayEquals( + new String[] {"r=fyko+d2lbbFgONRv9qkxdawL", null}, + StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 3)); + + assertArrayEquals( + new String[] {null, null}, + StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 4)); + + assertArrayEquals( + new String[] {"n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL", null}, + StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 5)); + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/UsAsciiUtilsTest.java b/scram-common/src/test/java/com/ongres/scram/common/UsAsciiUtilsTest.java new file mode 100644 index 0000000..8edebba --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/UsAsciiUtilsTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.ongres.scram.common.UsAsciiUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class UsAsciiUtilsTest { + + @ParameterizedTest + @NullSource + void toPrintableNull(String value) { + assertThrows(IllegalArgumentException.class, () -> UsAsciiUtils.toPrintable(value), + () -> "Calling with null value must throw IllegalArgumentException"); + } + + @ParameterizedTest + @ValueSource(strings = {"abcdé", "ñ", "€", "Наташа", (char) 127 + ""}) + void toPrintableNonASCII(String value) { + assertThrows(IllegalArgumentException.class, () -> UsAsciiUtils.toPrintable(value), + () -> "String(s) with non-ASCII characters not throwing IllegalArgumentException"); + } + + @ParameterizedTest + @CsvSource(value = {" u , u ", "a" + (char) 12 + ",a", (char) 0 + "ttt" + (char) 31 + ",ttt"}, + ignoreLeadingAndTrailingWhitespace = false) + void toPrintableNonPrintable(String original, String expected) { + assertEquals(expected, UsAsciiUtils.toPrintable(original)); + } + + @Test + void toPrintableAllPrintable() { + List values = new ArrayList(); + values.addAll(Arrays.asList( + new String[] {(char) 33 + "", "user", "!", "-,.=?", (char) 126 + ""})); + for (int c = 33; c < 127; c++) { + values.add("---" + (char) c + "---"); + } + + for (String s : values) { + assertEquals(s, UsAsciiUtils.toPrintable(s), + "All printable String '" + s + "' not returning the same value"); + } + } +} diff --git a/scram-common/src/test/java/com/ongres/scram/common/util/NonceTest.java b/scram-common/src/test/java/com/ongres/scram/common/util/NonceTest.java new file mode 100644 index 0000000..3ca7ca6 --- /dev/null +++ b/scram-common/src/test/java/com/ongres/scram/common/util/NonceTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017 OnGres, Inc. + * SPDX-License-Identifier: BSD-2-Clause + */ + +package com.ongres.scram.common.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import com.ongres.scram.common.ScramFunctions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class NonceTest { + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + @ParameterizedTest + @ValueSource(ints = {0, -1, Integer.MIN_VALUE}) + void nonceInvalidSize(int size) { + assertThrows(IllegalArgumentException.class, () -> ScramFunctions.nonce(size, SECURE_RANDOM)); + } + + @Test + void nonceValid() throws NoSuchAlgorithmException { + int nonces = 1000; + int nonceMaxSize = 100; + SecureRandom random = new SecureRandom(); + + // Some more random testing + for (int i = 0; i < nonces; i++) { + final int size = SECURE_RANDOM.nextInt(nonceMaxSize) + 1; + final String nonce = ScramFunctions.nonce(size, random); + for (int j = 0; j < nonce.length(); j++) { + char c = nonce.charAt(j); + if (c == ',' || c < (char) 33 || c > (char) 126) { + fail("Character c='" + c + "' is not allowed on a nonce"); + } + } + assertEquals(size, nonce.length()); + } + } +} diff --git a/scram-parent/javadoc/style.css b/scram-parent/javadoc/style.css new file mode 100644 index 0000000..9054c59 --- /dev/null +++ b/scram-parent/javadoc/style.css @@ -0,0 +1,40 @@ +table { + border-collapse: collapse; + font-size: 100%; + margin: 1em 1em; +} + +caption { + font-weight: bold; + text-align: left; + font-style: italic; + padding-bottom: 0.5em; +} + +td { + font-family: 'Courier New', Courier, monospace; + font-size: small; + border: 1px solid #EEEEEE; + padding: 0.5em 1em; + vertical-align: top; +} + +blockquote { + margin-left: 10px; + padding: 10px 20px; + background-color: #f5f5f5; + border-left: 5px solid #ddd; + font-style: italic; + font-size: 1.1em; +} + +blockquote::before { + content: open-quote; + color: #ccc; + margin-left: -0.5em; +} + +blockquote::after { + content: close-quote; + color: #ccc; +} \ No newline at end of file diff --git a/scram-parent/pom.xml b/scram-parent/pom.xml new file mode 100644 index 0000000..e81b380 --- /dev/null +++ b/scram-parent/pom.xml @@ -0,0 +1,728 @@ + + + 4.0.0 + + com.ongres.scram + scram-parent + 3.2 + pom + + SCRAM - Parent + Java Implementation of the Salted Challenge Response Authentication Mechanism (SCRAM) + https://github.com/ongres/scram + 2017 + + OnGres, Inc + https://www.ongres.com + + + + BSD 2-Clause "Simplified" License + https://spdx.org/licenses/BSD-2-Clause + repo + + + + + + com.ongres.aht + Álvaro Hernández Tortosa + aht@ongres.com + + + com.ongres.matteom + Matteo Melli + matteom@ongres.com + + + com.ongres.jorsol + Jorge Solórzano + jorsol@ongres.com + + + + + scm:git:https://github.com/ongres/scram.git + scm:git:git@github.com:ongres/scram.git + 3.2 + https://github.com/ongres/scram + + + + GitHub + https://github.com/ongres/scram/issues + + + + UTF-8 + UTF-8 + 8 + ${base.java.version} + ${base.java.version} + 2025-09-16T20:00:00Z + + 26.0.2-1 + 5.13.4 + 2.2 + + 3.14.0 + 3.4.2 + 3.3.1 + 3.11.3 + 3.5.0 + 3.3.1 + 3.5.4 + 3.5.4 + 3.6.1 + 3.1.4 + 0.8.0 + 3.9.1 + 3.2.8 + 0.8.13 + 1.7.2 + 3.1.2 + 4.0.0 + 2.9.1 + + 11.0.1 + 3.6.0 + 2.41.0 + 4.9.5 + 4.9.5.0 + 1.14.0 + 7.17.0 + 3.27.0 + 3.9 + ${rootDirectory}/checks + ${checks.location}/checkstyle.xml + ${checks.location}/checkstyle-suppressions.xml + ${checks.location}/checkstyle-header.txt + ${checks.location}/spotbugs-exclude.xml + ${checks.location}/pmd-ruleset.xml + + + + + + com.ongres.scram + scram-common + ${project.version} + + + com.ongres.scram + scram-client + ${project.version} + + + com.ongres.stringprep + saslprep + ${saslprep.version} + + + org.junit + junit-bom + ${junit5.version} + pom + import + + + + + + + org.junit.jupiter + junit-jupiter + test + + + org.jetbrains + annotations + ${jetbrains-annotations.version} + provided + true + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + true + true + true + + + + java9-module + + compile + + + none + + 9 + + ${project.basedir}/src/main/java9 + + true + + + + default-testCompile + + testCompile + + + 11 + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${jar-plugin.version} + + true + + + true + + + + + + org.apache.maven.plugins + maven-source-plugin + ${source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${javadoc-plugin.version} + + en_US + true + false + ${rootDirectory}/scram-parent/javadoc + true + + ${project.groupId}:* + + true + false + com.ongres.scram.common.util + + style.css + + + + apiNote + a + API Note: + + + implSpec + a + Implementation Requirements: + + + implNote + a + Implementation Note: + + + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${resources-plugin.version} + + + add-license + + copy-resources + + generate-resources + + ${project.build.outputDirectory}/META-INF + + + ${rootDirectory} + + LICENSE + + false + + + + + + + + org.apache.maven.plugins + maven-invoker-plugin + ${invoker-plugin.version} + + false + ${project.build.directory}/it + + */pom.xml + + + ${project.build.directory}/local-repo + src/it/settings.xml + + + + integration-test + + install + integration-test + verify + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire-plugin.version} + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${failsafe-plugin.version} + + + **/*Test.java + **/*IT.java + + + ${project.build.directory}/${project.build.finalName}.jar + + + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-install-plugin + ${install-plugin.version} + + + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} + true + + central + + + + org.apache.maven.plugins + maven-clean-plugin + ${clean-plugin.version} + + + org.apache.maven.plugins + maven-enforcer-plugin + ${enforcer-plugin.version} + + + enforce-versions + + enforce + + + + + [3.9.9,) + + + [21,) + + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-plugin.verson} + + + + prepare-agent + + prepare-agent + + process-test-classes + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + ossrh + all + + + + + flatten + + flatten + + process-resources + + + + flatten-clean + + clean + + clean + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${gpg-plugin.version} + + + sign-artifacts + + sign + + verify + + + + + org.cyclonedx + cyclonedx-maven-plugin + ${cyclonedx-plugin.version} + + true + false + false + false + false + + + + + makeAggregateBom + + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + + + + + checks + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + true + true + + -Xlint:all + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -XepAllErrorsAsWarnings -XepDisableWarningsInGeneratedCode + + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + + + + de.thetaphi + forbiddenapis + ${forbiddenapis.version} + + + + jdk-unsafe + jdk-deprecated + jdk-internal + + jdk-non-portable + + jdk-reflection + + jdk-system-out + + + ${checks.location}/forbiddenapis.txt + + + + + + check + testCheck + + + + + + org.apache.maven.plugins + maven-jdeps-plugin + ${jdeps-plugin.version} + + 9 + + + + + jdkinternals + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + + style + + check + + verify + + error + true + true + true + false + true + false + + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs-plugin.version} + + Max + Low + true + true + true + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs.version} + + + + + + com.github.spotbugs + spotbugs + ${spotbugs.version} + + + + + scan + + check + + verify + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${pmd-plugin.version} + + 5 + true + true + false + + ${pmd.ruleset} + + + + + net.sourceforge.pmd + pmd-core + ${pmd.version} + + + net.sourceforge.pmd + pmd-java + ${pmd.version} + + + + + pmd-scan + + check + + verify + + + + + com.github.ekryd.sortpom + sortpom-maven-plugin + ${sortpom-plugin.version} + + + + sort + + verify + + + + + + + + + release + + + + org.codehaus.mojo + flatten-maven-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + + + org.cyclonedx + cyclonedx-maven-plugin + + + org.sonatype.central + central-publishing-maven-plugin + + + + + + + compile-java9 + + [9,) + + + ${base.java.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + java9-module + compile + + + + + + + + + + diff --git a/thirdparty/bouncycastle-LICENSE b/thirdparty/bouncycastle-LICENSE deleted file mode 100644 index acd3318..0000000 --- a/thirdparty/bouncycastle-LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/thirdparty/passlib_lambda-LICENSE b/thirdparty/passlib_lambda-LICENSE deleted file mode 100644 index c8a2e47..0000000 --- a/thirdparty/passlib_lambda-LICENSE +++ /dev/null @@ -1,22 +0,0 @@ - -The MIT License - -Copyright (c) 2010-2016 Google, Inc. http://angularjs.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/thirdparty/saslprep-LICENSE b/thirdparty/saslprep-LICENSE deleted file mode 100644 index 84b3fe0..0000000 --- a/thirdparty/saslprep-LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright 2019, OnGres. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.