From 226953485683329ea50e9ce3787f9280073b0269 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Mon, 9 Apr 2018 19:34:42 -0500 Subject: [PATCH 01/13] 56: deserializing header will throw invalidJwt --- .gitignore | 1 + .../jwt/serialization/HeaderDeserializer.java | 3 ++- ...rdesTest.java => JweDirectSerializerTest.java} | 2 +- .../jwe/serialization/rsa/JweRsaSerdesTest.java | 7 +++++++ .../jwt/serialization/HeaderDeserializerTest.java | 15 +++++++++++++++ 5 files changed, 26 insertions(+), 2 deletions(-) rename src/test/java/org/rootservices/jwt/jwe/serialization/direct/{JweDirectSerdesTest.java => JweDirectSerializerTest.java} (98%) diff --git a/.gitignore b/.gitignore index e2de616..4aab4ef 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ !gradle-wrapper.jar .gradle /build/ +/out/ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* diff --git a/src/main/java/org/rootservices/jwt/serialization/HeaderDeserializer.java b/src/main/java/org/rootservices/jwt/serialization/HeaderDeserializer.java index c42f223..d0d5358 100644 --- a/src/main/java/org/rootservices/jwt/serialization/HeaderDeserializer.java +++ b/src/main/java/org/rootservices/jwt/serialization/HeaderDeserializer.java @@ -9,6 +9,7 @@ public class HeaderDeserializer { public static final String JWT_SPLITTER = "\\."; + public final int JWT_LENGTH = 2; public static final String INVALID_HEADER = "JOSE Header is invalid"; public static final String JWT_IS_NOT_SPLITTABLE = "JWT is not splittable by '.'"; private Base64.Decoder decoder; @@ -22,7 +23,7 @@ public HeaderDeserializer(Base64.Decoder decoder, Serdes serdes) { public Header toHeader(String encodedJwt) throws JsonToJwtException, InvalidJWT { String[] jwtParts = encodedJwt.split(JWT_SPLITTER); - if (jwtParts.length == 0) { + if (jwtParts.length < JWT_LENGTH) { throw new InvalidJWT(JWT_IS_NOT_SPLITTABLE); } diff --git a/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectSerdesTest.java b/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectSerializerTest.java similarity index 98% rename from src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectSerdesTest.java rename to src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectSerializerTest.java index 9c5034e..b04c728 100644 --- a/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectSerdesTest.java +++ b/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectSerializerTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.notNull; -public class JweDirectSerdesTest { +public class JweDirectSerializerTest { private static JwtAppFactory jwtAppFactory = new JwtAppFactory(); @Test diff --git a/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerdesTest.java b/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerdesTest.java index 4b02144..5b9e1cb 100644 --- a/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerdesTest.java +++ b/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerdesTest.java @@ -1,6 +1,8 @@ package org.rootservices.jwt.jwe.serialization.rsa; import helper.entity.Factory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.junit.Test; import org.rootservices.jwt.config.JwtAppFactory; import org.rootservices.jwt.entity.jwe.EncryptionAlgorithm; @@ -22,6 +24,7 @@ import static org.junit.Assert.*; public class JweRsaSerdesTest { + private static final Logger LOGGER = LogManager.getLogger(JweRsaSerdesTest.class); @Test public void extractCipherText() throws Exception { @@ -97,6 +100,10 @@ public void JWEToCompact() throws Exception { ByteArrayOutputStream actual = subject.JWEToCompact(jwe); + for(String member: actual.toString().split("\\.")) { + LOGGER.info(member); + } + // make sure it can be read. RSAKeyPair jwk = Factory.makeRSAKeyPairForJWE(); JweRsaDeserializer JweRsaDeserializer = jwtAppFactory.jweRsaDeserializer(); diff --git a/src/test/java/org/rootservices/jwt/serialization/HeaderDeserializerTest.java b/src/test/java/org/rootservices/jwt/serialization/HeaderDeserializerTest.java index 5105c6f..cf6b291 100644 --- a/src/test/java/org/rootservices/jwt/serialization/HeaderDeserializerTest.java +++ b/src/test/java/org/rootservices/jwt/serialization/HeaderDeserializerTest.java @@ -8,6 +8,7 @@ import org.rootservices.jwt.entity.jwt.header.Algorithm; import org.rootservices.jwt.entity.jwt.header.AlgorithmFor; import org.rootservices.jwt.entity.jwt.header.Header; +import org.rootservices.jwt.exception.InvalidJWT; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -37,4 +38,18 @@ public void toHeader() throws Exception { assertThat(actual.getEncryptionAlgorithm().isPresent(), is(true)); assertThat(actual.getEncryptionAlgorithm().get(), is(EncryptionAlgorithm.AES_GCM_256)); } + + @Test + public void toHeaderShouldThrowInvalidJwt() throws Exception { + String compactJWE = ""; + + InvalidJWT actual = null; + try { + subject.toHeader(compactJWE); + } catch (InvalidJWT e) { + actual = e; + } + + assertThat(actual, is(notNullValue())); + } } \ No newline at end of file From 5dba7c53507a87222fe2aa9a01593cf157cd85bc Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Mon, 9 Apr 2018 19:36:28 -0500 Subject: [PATCH 02/13] 56: version bump --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7e2f21d..35f93aa 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'signing' apply plugin: 'maven' group = "org.rootservices" -version = "1.3-SNAPSHOT" +version = "1.3.1-SNAPSHOT" archivesBaseName="jwt" description = """JSON Web Tokens""" From da74c15c1337ed55556c1888c2858d502632b1c7 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Mon, 9 Apr 2018 19:39:38 -0500 Subject: [PATCH 03/13] 56: correcting file name. --- ...eRsaSerdesTest.java => JweRsaSerializerTest.java} | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) rename src/test/java/org/rootservices/jwt/jwe/serialization/rsa/{JweRsaSerdesTest.java => JweRsaSerializerTest.java} (92%) diff --git a/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerdesTest.java b/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerializerTest.java similarity index 92% rename from src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerdesTest.java rename to src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerializerTest.java index 5b9e1cb..178e83f 100644 --- a/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerdesTest.java +++ b/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerializerTest.java @@ -8,14 +8,12 @@ import org.rootservices.jwt.entity.jwe.EncryptionAlgorithm; import org.rootservices.jwt.entity.jwk.RSAKeyPair; import org.rootservices.jwt.entity.jwk.RSAPublicKey; -import org.rootservices.jwt.entity.jwk.SymmetricKey; import org.rootservices.jwt.entity.jwt.header.Algorithm; import org.rootservices.jwt.entity.jwt.header.Header; import org.rootservices.jwt.jwe.entity.JWE; import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Optional; @@ -23,8 +21,7 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.*; -public class JweRsaSerdesTest { - private static final Logger LOGGER = LogManager.getLogger(JweRsaSerdesTest.class); +public class JweRsaSerializerTest { @Test public void extractCipherText() throws Exception { @@ -38,8 +35,7 @@ public void extractCipherText() throws Exception { byte[] initVector = decoder.decode(jweParts[2]); byte[] cipherText = decoder.decode(jweParts[3]); byte[] authenticationTag = decoder.decode(jweParts[4]); - - RSAKeyPair jwk = Factory.makeRSAKeyPairForJWE(); + JweRsaDeserializer JweRsaDeserializer = jwtAppFactory.jweRsaDeserializer(); RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); @@ -100,10 +96,6 @@ public void JWEToCompact() throws Exception { ByteArrayOutputStream actual = subject.JWEToCompact(jwe); - for(String member: actual.toString().split("\\.")) { - LOGGER.info(member); - } - // make sure it can be read. RSAKeyPair jwk = Factory.makeRSAKeyPairForJWE(); JweRsaDeserializer JweRsaDeserializer = jwtAppFactory.jweRsaDeserializer(); From 06a05d48557ce65b8cc0ae4418526132cc8d4436 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Mon, 9 Apr 2018 19:42:24 -0500 Subject: [PATCH 04/13] 56: removed unneeded imports for log4j --- .../jwt/jwe/serialization/rsa/JweRsaSerializerTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerializerTest.java b/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerializerTest.java index 178e83f..f0e9041 100644 --- a/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerializerTest.java +++ b/src/test/java/org/rootservices/jwt/jwe/serialization/rsa/JweRsaSerializerTest.java @@ -1,8 +1,6 @@ package org.rootservices.jwt.jwe.serialization.rsa; import helper.entity.Factory; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.junit.Test; import org.rootservices.jwt.config.JwtAppFactory; import org.rootservices.jwt.entity.jwe.EncryptionAlgorithm; @@ -35,7 +33,7 @@ public void extractCipherText() throws Exception { byte[] initVector = decoder.decode(jweParts[2]); byte[] cipherText = decoder.decode(jweParts[3]); byte[] authenticationTag = decoder.decode(jweParts[4]); - + JweRsaDeserializer JweRsaDeserializer = jwtAppFactory.jweRsaDeserializer(); RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); From dd093acdd0f1675d0af0b9d92238fa4fd2600aa8 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Thu, 9 Aug 2018 10:25:02 -0500 Subject: [PATCH 05/13] compact-builder: builders to produce compact jwt --- .../compact/EncryptedCompactBuilder.java | 56 +++++++++ .../builder/compact/SecureCompactBuilder.java | 57 +++++++++ .../compact/UnsecureCompactBuilder.java | 22 ++++ .../builder/exception/CompactException.java | 7 ++ .../direct/JweDirectDesializer.java | 12 +- .../AsymmetricSignedJsonWebToken.java | 24 ++-- .../examples/SymmetricSignedJsonWebToken.java | 23 ++-- .../UnsecuredJsonWebTokenSerializer.java | 11 +- .../compact/EncryptedCompactBuilderTest.java | 119 ++++++++++++++++++ .../compact/SecureCompactBuilderTest.java | 56 +++++++++ .../compact/UnsecureCompactBuilderTest.java | 30 +++++ .../direct/JweDirectDesializerTest.java | 19 +++ 12 files changed, 406 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java create mode 100644 src/main/java/org/rootservices/jwt/builder/compact/SecureCompactBuilder.java create mode 100644 src/main/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilder.java create mode 100644 src/main/java/org/rootservices/jwt/builder/exception/CompactException.java create mode 100644 src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java create mode 100644 src/test/java/org/rootservices/jwt/builder/compact/SecureCompactBuilderTest.java create mode 100644 src/test/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilderTest.java diff --git a/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java b/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java new file mode 100644 index 0000000..2354b91 --- /dev/null +++ b/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java @@ -0,0 +1,56 @@ +package org.rootservices.jwt.builder.compact; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.rootservices.jwt.builder.exception.CompactException; +import org.rootservices.jwt.config.JwtAppFactory; +import org.rootservices.jwt.entity.jwk.RSAPublicKey; +import org.rootservices.jwt.jwe.entity.JWE; +import org.rootservices.jwt.jwe.factory.exception.CipherException; +import org.rootservices.jwt.jwe.serialization.JweSerializer; +import org.rootservices.jwt.jws.signer.factory.rsa.exception.PublicKeyException; +import org.rootservices.jwt.serialization.exception.EncryptException; +import org.rootservices.jwt.serialization.exception.JsonToJwtException; + +import java.io.ByteArrayOutputStream; + +public class EncryptedCompactBuilder { + private static final Logger LOGGER = LogManager.getLogger(EncryptedCompactBuilder.class); + public static final String UNABLE_TO_BUILD_COMPACT_JWE = "Unable to build compact jwe"; + private static JwtAppFactory jwtAppFactory = new JwtAppFactory(); + + private JWE jwe; + private RSAPublicKey publicKey; + + public EncryptedCompactBuilder jwe(JWE jwe) { + this.jwe = jwe; + return this; + } + + public EncryptedCompactBuilder key(RSAPublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + public ByteArrayOutputStream build() throws CompactException { + JweSerializer jweSerializer; + if (publicKey != null) { + try { + jweSerializer = jwtAppFactory.jweRsaSerializer(publicKey); + } catch (PublicKeyException | CipherException e) { + LOGGER.error(e.getMessage(), e); + throw new CompactException(UNABLE_TO_BUILD_COMPACT_JWE, e); + } + } else { + jweSerializer = jwtAppFactory.jweDirectSerializer(); + } + + try { + return jweSerializer.JWEToCompact(jwe); + } catch (JsonToJwtException | CipherException | EncryptException e) { + LOGGER.error(e.getMessage(), e); + throw new CompactException(UNABLE_TO_BUILD_COMPACT_JWE, e); + } + } + +} diff --git a/src/main/java/org/rootservices/jwt/builder/compact/SecureCompactBuilder.java b/src/main/java/org/rootservices/jwt/builder/compact/SecureCompactBuilder.java new file mode 100644 index 0000000..9510b0b --- /dev/null +++ b/src/main/java/org/rootservices/jwt/builder/compact/SecureCompactBuilder.java @@ -0,0 +1,57 @@ +package org.rootservices.jwt.builder.compact; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.rootservices.jwt.builder.exception.CompactException; +import org.rootservices.jwt.config.JwtAppFactory; +import org.rootservices.jwt.entity.jwk.Key; +import org.rootservices.jwt.entity.jwt.Claims; +import org.rootservices.jwt.entity.jwt.header.Algorithm; +import org.rootservices.jwt.exception.SignatureException; +import org.rootservices.jwt.jws.serialization.SecureJwtSerializer; +import org.rootservices.jwt.serialization.exception.JwtToJsonException; + +import java.io.ByteArrayOutputStream; + +public class SecureCompactBuilder { + private static final Logger LOGGER = LogManager.getLogger(SecureCompactBuilder.class); + public static final String UNABLE_TO_BUILD_COMPACT_JWT = "Unable to build compact jwt"; + private static JwtAppFactory jwtAppFactory = new JwtAppFactory(); + + private Claims claims; + private Key key; + private Algorithm alg; + + public SecureCompactBuilder claims(Claims claims) { + this.claims = claims; + return this; + } + + public SecureCompactBuilder key(Key key) { + this.key = key; + return this; + } + + public SecureCompactBuilder alg(Algorithm alg) { + this.alg = alg; + return this; + } + + + public ByteArrayOutputStream build() throws CompactException { + SecureJwtSerializer secureJwtSerializer; + try { + secureJwtSerializer = jwtAppFactory.secureJwtSerializer(alg, key); + } catch (SignatureException e) { + LOGGER.error(e.getMessage(), e); + throw new CompactException(UNABLE_TO_BUILD_COMPACT_JWT, e); + } + + try { + return secureJwtSerializer.compactJwt(claims); + } catch (JwtToJsonException e) { + LOGGER.error(e.getMessage(), e); + throw new CompactException(UNABLE_TO_BUILD_COMPACT_JWT, e); + } + } +} diff --git a/src/main/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilder.java b/src/main/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilder.java new file mode 100644 index 0000000..f38073b --- /dev/null +++ b/src/main/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilder.java @@ -0,0 +1,22 @@ +package org.rootservices.jwt.builder.compact; + +import org.rootservices.jwt.config.JwtAppFactory; +import org.rootservices.jwt.entity.jwt.Claims; +import org.rootservices.jwt.serialization.UnSecureJwtSerializer; + +import java.io.ByteArrayOutputStream; + +public class UnsecureCompactBuilder { + private static JwtAppFactory jwtAppFactory = new JwtAppFactory(); + private Claims claims; + + public UnsecureCompactBuilder claims(Claims claims) { + this.claims = claims; + return this; + } + + public ByteArrayOutputStream build() { + UnSecureJwtSerializer unSecureJwtSerializer = jwtAppFactory.unSecureJwtSerializer(); + return unSecureJwtSerializer.compactJwt(claims); + } +} diff --git a/src/main/java/org/rootservices/jwt/builder/exception/CompactException.java b/src/main/java/org/rootservices/jwt/builder/exception/CompactException.java new file mode 100644 index 0000000..3508d99 --- /dev/null +++ b/src/main/java/org/rootservices/jwt/builder/exception/CompactException.java @@ -0,0 +1,7 @@ +package org.rootservices.jwt.builder.exception; + +public class CompactException extends Exception { + public CompactException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializer.java b/src/main/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializer.java index e8c96b9..53a0bb8 100644 --- a/src/main/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializer.java +++ b/src/main/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializer.java @@ -8,6 +8,7 @@ import org.rootservices.jwt.jwe.factory.CipherSymmetricFactory; import org.rootservices.jwt.jwe.factory.exception.CipherException; import org.rootservices.jwt.jwe.serialization.JweDeserializer; +import org.rootservices.jwt.jwe.serialization.exception.KeyException; import org.rootservices.jwt.serialization.Serdes; import org.rootservices.jwt.serialization.exception.DecryptException; import org.rootservices.jwt.serialization.exception.JsonException; @@ -21,6 +22,7 @@ public class JweDirectDesializer implements JweDeserializer { + public static final String KEY_CANNOT_BE_DECODED = "Key cannot be decoded."; private Serdes serdes; private Base64.Decoder decoder; private CipherSymmetricFactory cipherSymmetricFactory; @@ -32,7 +34,7 @@ public JweDirectDesializer(Serdes serdes, Base64.Decoder decoder, CipherSymmetri } @Override - public JWE stringToJWE(String compactJWE, Key key) throws JsonToJwtException, DecryptException, CipherException { + public JWE stringToJWE(String compactJWE, Key key) throws JsonToJwtException, DecryptException, CipherException, KeyException { String[] jweParts = compactJWE.split(JWT_SPLITTER); byte[] protectedHeader = decoder.decode(jweParts[0]); byte[] initVector = decoder.decode(jweParts[2]); @@ -48,7 +50,13 @@ public JWE stringToJWE(String compactJWE, Key key) throws JsonToJwtException, De byte[] aad = jweParts[0].getBytes(StandardCharsets.US_ASCII); - byte[] cek = decoder.decode(((SymmetricKey) key).getKey().getBytes()); + byte[] cek; + try { + cek = decoder.decode(((SymmetricKey) key).getKey().getBytes()); + } catch (IllegalArgumentException e) { + throw new KeyException(KEY_CANNOT_BE_DECODED, e); + } + Cipher symmetricCipher; try { symmetricCipher = cipherSymmetricFactory.forDecrypt(Transformation.AES_GCM_NO_PADDING, cek, initVector, aad); diff --git a/src/test/java/examples/AsymmetricSignedJsonWebToken.java b/src/test/java/examples/AsymmetricSignedJsonWebToken.java index c271620..c4b542a 100644 --- a/src/test/java/examples/AsymmetricSignedJsonWebToken.java +++ b/src/test/java/examples/AsymmetricSignedJsonWebToken.java @@ -1,6 +1,8 @@ package examples; import helper.entity.Claim; +import org.rootservices.jwt.builder.compact.SecureCompactBuilder; +import org.rootservices.jwt.builder.exception.CompactException; import org.rootservices.jwt.jws.serialization.SecureJwtSerializer; import org.rootservices.jwt.config.JwtAppFactory; import org.rootservices.jwt.entity.jwk.KeyType; @@ -15,6 +17,7 @@ import org.rootservices.jwt.serialization.exception.JwtToJsonException; import org.rootservices.jwt.jws.verifier.VerifySignature; +import java.io.ByteArrayOutputStream; import java.math.BigInteger; import java.util.Optional; @@ -25,6 +28,8 @@ public class AsymmetricSignedJsonWebToken { public String toCompactJwt() { + SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); + RSAKeyPair keyPair = new RSAKeyPair( Optional.of("test-key-id"), KeyType.RSA, @@ -42,22 +47,17 @@ public String toCompactJwt() { Claim claim = new Claim(); claim.setUriIsRoot(true); - JwtAppFactory appFactory = new JwtAppFactory(); - SecureJwtSerializer secureJwtSerializer = null; - try { - secureJwtSerializer = appFactory.secureJwtSerializer(Algorithm.RS256, keyPair); - } catch (SignatureException e) { - e.printStackTrace(); - } - - String encodedJwt = null; + ByteArrayOutputStream encodedJwt = null; try { - encodedJwt = secureJwtSerializer.compactJwtToString(claim); - } catch (JwtToJsonException e) { + encodedJwt = compactBuilder.claims(claim) + .key(keyPair) + .alg(Algorithm.RS256) + .build(); + } catch (CompactException e) { e.printStackTrace(); } - return encodedJwt; + return encodedJwt.toString(); } public Boolean verifySignature() throws Exception { diff --git a/src/test/java/examples/SymmetricSignedJsonWebToken.java b/src/test/java/examples/SymmetricSignedJsonWebToken.java index 6666b47..391c131 100644 --- a/src/test/java/examples/SymmetricSignedJsonWebToken.java +++ b/src/test/java/examples/SymmetricSignedJsonWebToken.java @@ -1,6 +1,8 @@ package examples; import helper.entity.Claim; +import org.rootservices.jwt.builder.compact.SecureCompactBuilder; +import org.rootservices.jwt.builder.exception.CompactException; import org.rootservices.jwt.jws.serialization.SecureJwtSerializer; import org.rootservices.jwt.config.JwtAppFactory; import org.rootservices.jwt.entity.jwk.SymmetricKey; @@ -13,6 +15,7 @@ import org.rootservices.jwt.serialization.exception.JwtToJsonException; import org.rootservices.jwt.jws.verifier.VerifySignature; +import java.io.ByteArrayOutputStream; import java.util.Optional; /** @@ -22,7 +25,7 @@ public class SymmetricSignedJsonWebToken { public String tocCompactJwt() { - JwtAppFactory appFactory = new JwtAppFactory(); + SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); SymmetricKey key = new SymmetricKey( Optional.of("test-key-id"), @@ -33,21 +36,17 @@ public String tocCompactJwt() { Claim claim = new Claim(); claim.setUriIsRoot(true); - SecureJwtSerializer secureJwtSerializer = null; - try { - secureJwtSerializer = appFactory.secureJwtSerializer(Algorithm.HS256, key); - } catch (SignatureException e) { - e.printStackTrace(); - } - - String encodedJwt = null; + ByteArrayOutputStream encodedJwt = null; try { - encodedJwt = secureJwtSerializer.compactJwtToString(claim); - } catch (JwtToJsonException e) { + encodedJwt = compactBuilder.claims(claim) + .key(key) + .alg(Algorithm.HS256) + .build(); + } catch (CompactException e) { e.printStackTrace(); } - return encodedJwt; + return encodedJwt.toString(); } public Boolean verifySignature() throws Exception { diff --git a/src/test/java/examples/UnsecuredJsonWebTokenSerializer.java b/src/test/java/examples/UnsecuredJsonWebTokenSerializer.java index 8448576..3100f06 100644 --- a/src/test/java/examples/UnsecuredJsonWebTokenSerializer.java +++ b/src/test/java/examples/UnsecuredJsonWebTokenSerializer.java @@ -1,21 +1,24 @@ package examples; import helper.entity.Claim; +import org.rootservices.jwt.builder.compact.UnsecureCompactBuilder; import org.rootservices.jwt.serialization.UnSecureJwtSerializer; import org.rootservices.jwt.config.JwtAppFactory; +import java.io.ByteArrayOutputStream; + public class UnsecuredJsonWebTokenSerializer { public String toEncodedJwt() { - JwtAppFactory appFactory = new JwtAppFactory(); - UnSecureJwtSerializer unSecureJwtSerializer = appFactory.unSecureJwtSerializer(); + + UnsecureCompactBuilder compactBuilder = new UnsecureCompactBuilder(); Claim claim = new Claim(); claim.setUriIsRoot(true); - String encodedJwt = unSecureJwtSerializer.compactJwtToString(claim); + ByteArrayOutputStream encodedJwt = compactBuilder.claims(claim).build(); - return encodedJwt; + return encodedJwt.toString(); } } diff --git a/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java b/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java new file mode 100644 index 0000000..c7ef1e9 --- /dev/null +++ b/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java @@ -0,0 +1,119 @@ +package org.rootservices.jwt.builder.compact; + +import helper.entity.Factory; +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Test; +import org.rootservices.jwt.config.JwtAppFactory; +import org.rootservices.jwt.entity.jwe.EncryptionAlgorithm; +import org.rootservices.jwt.entity.jwk.RSAKeyPair; +import org.rootservices.jwt.entity.jwk.RSAPublicKey; +import org.rootservices.jwt.entity.jwk.SymmetricKey; +import org.rootservices.jwt.entity.jwt.header.Algorithm; +import org.rootservices.jwt.entity.jwt.header.Header; +import org.rootservices.jwt.jwe.entity.JWE; +import org.rootservices.jwt.jwe.serialization.JweDeserializer; +import org.rootservices.jwt.jwe.serialization.JweSerializer; +import org.rootservices.jwt.jwe.serialization.rsa.JweRsaDeserializer; +import org.rootservices.jwt.jwe.serialization.rsa.JweRsaSerializer; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.*; + +public class EncryptedCompactBuilderTest { + private static JwtAppFactory jwtAppFactory = new JwtAppFactory(); + private EncryptedCompactBuilder subject; + + @Before + public void setUp() { + subject = new EncryptedCompactBuilder(); + } + + @Test + public void buildWithDirect() throws Exception { + + SymmetricKey key = Factory.makeSymmetricKeyForJWE(); + + Base64.Decoder decoder = jwtAppFactory.urlDecoder(); + + Header header = new Header(); + header.setEncryptionAlgorithm(Optional.of(EncryptionAlgorithm.AES_GCM_256)); + header.setAlgorithm(Algorithm.DIRECT); + + JWE jwe = new JWE(); + jwe.setHeader(header); + jwe.setCek(decoder.decode(key.getKey())); + jwe.setPayload("Help me, Obi-Wan Kenobi. You're my only hope.".getBytes()); + + ByteArrayOutputStream actual = subject.jwe(jwe).build(); + + assertThat(actual, is(notNullValue())); + + String compactJWE = actual.toString(); + + String[] jweParts = compactJWE.split("\\."); + String protectedHeader = new String(decoder.decode(jweParts[0]), StandardCharsets.UTF_8); + String encryptedKey = new String(decoder.decode(jweParts[1]), StandardCharsets.UTF_8); + String initVector = new String(decoder.decode(jweParts[2]), StandardCharsets.UTF_8); + String cipherText = new String(decoder.decode(jweParts[3]), StandardCharsets.UTF_8); + String authenticationTag = new String(decoder.decode(jweParts[4]), StandardCharsets.UTF_8); + + assertThat(protectedHeader, is(notNullValue())); + + assertThat(encryptedKey, is(notNullValue())); + assertThat(encryptedKey, is("")); + + assertThat(initVector, is(notNullValue())); + assertThat(cipherText, is(notNullValue())); + assertThat(authenticationTag, is(notNullValue())); + + // should be able to deserialize it. + JweDeserializer jweDeserializer = jwtAppFactory.jweDirectDesializer(); + + JWE leia = jweDeserializer.stringToJWE(compactJWE, key); + + assertThat(leia, CoreMatchers.is(CoreMatchers.notNullValue())); + String payload = new String(leia.getPayload()); + assertThat(payload, CoreMatchers.is("Help me, Obi-Wan Kenobi. You're my only hope.")); + } + + @Test + public void buildWithRSA() throws Exception { + JwtAppFactory jwtAppFactory = new JwtAppFactory(); + + RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); + publicKey.setKeyId(Optional.of(UUID.randomUUID().toString())); + + Header header = new Header(); + header.setEncryptionAlgorithm(Optional.of(EncryptionAlgorithm.AES_GCM_256)); + header.setAlgorithm(Algorithm.RSAES_OAEP); + header.setKeyId(publicKey.getKeyId()); + + JWE jwe = new JWE(); + jwe.setHeader(header); + jwe.setPayload("Help me, Obi-Wan Kenobi. You're my only hope.".getBytes()); + + ByteArrayOutputStream actual = subject.jwe(jwe).key(publicKey).build(); + + // make sure it can be read. + RSAKeyPair jwk = Factory.makeRSAKeyPairForJWE(); + JweRsaDeserializer JweRsaDeserializer = jwtAppFactory.jweRsaDeserializer(); + + String compactJWE = actual.toString(); + + JWE leia = JweRsaDeserializer.stringToJWE(compactJWE, jwk); + + assertThat(leia, CoreMatchers.is(CoreMatchers.notNullValue())); + String payload = new String(leia.getPayload()); + assertThat(payload, CoreMatchers.is("Help me, Obi-Wan Kenobi. You're my only hope.")); + + } + +} \ No newline at end of file diff --git a/src/test/java/org/rootservices/jwt/builder/compact/SecureCompactBuilderTest.java b/src/test/java/org/rootservices/jwt/builder/compact/SecureCompactBuilderTest.java new file mode 100644 index 0000000..f6e68ba --- /dev/null +++ b/src/test/java/org/rootservices/jwt/builder/compact/SecureCompactBuilderTest.java @@ -0,0 +1,56 @@ +package org.rootservices.jwt.builder.compact; + +import helper.entity.Claim; +import helper.entity.Factory; +import org.junit.Before; +import org.junit.Test; +import org.rootservices.jwt.entity.jwk.RSAKeyPair; +import org.rootservices.jwt.entity.jwk.SymmetricKey; +import org.rootservices.jwt.entity.jwt.header.Algorithm; +import org.rootservices.jwt.jws.serialization.SecureJwtSerializer; + +import java.io.ByteArrayOutputStream; +import java.util.Optional; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; + +public class SecureCompactBuilderTest { + private SecureCompactBuilder subject; + + @Before + public void setUp() { + subject = new SecureCompactBuilder(); + } + + + @Test + public void buildWithSymmetricKeyShouldEncode() throws Exception { + SymmetricKey key = Factory.makeSymmetricKey(); + key.setKeyId(Optional.of("test-key-id")); + + Claim claim = Factory.makeClaim(); + + ByteArrayOutputStream actual = subject.alg(Algorithm.HS256) + .key(key) + .claims(claim) + .build(); + + assertThat(actual.toString(), is("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIn0.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.YiFm03WWrDAbFn7omROmU2GHACkaGI30xdbWFzyoCNQ")); + } + + @Test + public void buildWithAsymmetricKeyShouldEncode() throws Exception { + RSAKeyPair key = Factory.makeRSAKeyPair(); + key.setKeyId(Optional.of("test-key-id")); + + Claim claim = Factory.makeClaim(); + + ByteArrayOutputStream actual = subject.alg(Algorithm.RS256) + .key(key) + .claims(claim) + .build(); + + assertThat(actual.toString(), is("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIn0.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.JmMm15IjKsNB18oMqIIaIPHQ8TuqEXNdbmGyEya5Yuoo2Kj132PhiCt2gbPL2i75IH1Zmjvdc7Fm2eb2Db6P6NXezZEHgZG3WVTWJwl11lQnDnj6hTrTbHnL0XUgcFw0vIwthQF6NNjAy2lTMSG0KTH5y_3D-5pt6FM2cyfvK5RwhCom9v2MDWA7fTqR1u5L-_dfcgRlN5rjQ-QYBsk3oaNTMU9MtXtEuG7erun_-VXQJjXGwDRO_kPmzN-wILyoaOr670xpaHVmFLrTakjvfhCkLrB1YwdQV-B6ZFLqTpQpGr7ydEWMyuoiV0Xg71-mJhNHeml_jFMUwm-Lu-d2Og")); + } +} \ No newline at end of file diff --git a/src/test/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilderTest.java b/src/test/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilderTest.java new file mode 100644 index 0000000..11f0e4e --- /dev/null +++ b/src/test/java/org/rootservices/jwt/builder/compact/UnsecureCompactBuilderTest.java @@ -0,0 +1,30 @@ +package org.rootservices.jwt.builder.compact; + +import helper.entity.Factory; +import org.junit.Before; +import org.junit.Test; +import org.rootservices.jwt.entity.jwt.Claims; +import org.rootservices.jwt.serialization.UnSecureJwtSerializer; + +import java.io.ByteArrayOutputStream; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; + +public class UnsecureCompactBuilderTest { + private UnsecureCompactBuilder subject; + + @Before + public void setUp() { + subject = new UnsecureCompactBuilder(); + } + + @Test + public void buildShouldBeOk() { + + Claims claims = Factory.makeClaim(); + + ByteArrayOutputStream actual = subject.claims(claims).build(); + assertThat(actual.toString(), is("eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.")); + } +} \ No newline at end of file diff --git a/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializerTest.java b/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializerTest.java index 289a26a..7658ca2 100644 --- a/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializerTest.java +++ b/src/test/java/org/rootservices/jwt/jwe/serialization/direct/JweDirectDesializerTest.java @@ -10,6 +10,7 @@ import org.rootservices.jwt.entity.jwt.header.AlgorithmFor; import org.rootservices.jwt.jwe.entity.JWE; import org.rootservices.jwt.jwe.serialization.JweDeserializer; +import org.rootservices.jwt.jwe.serialization.exception.KeyException; import java.nio.charset.StandardCharsets; @@ -45,4 +46,22 @@ public void stringToJWE() throws Exception { String payload = new String(actual.getPayload(), StandardCharsets.UTF_8); assertThat(payload, CoreMatchers.is("Help me, Obi-Wan Kenobi. You're my only hope.")); } + + @Test + public void stringToJWEWhenKeyIsNotBase64ShouldThrowKeyException() throws Exception { + JweDirectDesializer subject = jwtAppFactory.jweDirectDesializer(); + + SymmetricKey key = Factory.makeSymmetricKeyForJWE(); + key.setKey("=bad-key="); + String compactJwe = Factory.symmetricCompactJWE(); + + KeyException actual = null; + try { + subject.stringToJWE(compactJwe, key); + } catch (KeyException e) { + actual = e; + } + + assertThat(actual, is(notNullValue())); + } } \ No newline at end of file From d35e933d00f6390f5839df68d730c5e168d6e8b5 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Thu, 9 Aug 2018 11:41:37 -0500 Subject: [PATCH 06/13] compact-builder: no longer pass in jwe --- .../compact/EncryptedCompactBuilder.java | 94 ++++++++++++++++--- .../compact/EncryptedCompactBuilderTest.java | 44 ++++----- 2 files changed, 100 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java b/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java index 2354b91..59b98d2 100644 --- a/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java +++ b/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java @@ -4,7 +4,10 @@ import org.apache.logging.log4j.Logger; import org.rootservices.jwt.builder.exception.CompactException; import org.rootservices.jwt.config.JwtAppFactory; +import org.rootservices.jwt.entity.jwe.EncryptionAlgorithm; import org.rootservices.jwt.entity.jwk.RSAPublicKey; +import org.rootservices.jwt.entity.jwt.header.Algorithm; +import org.rootservices.jwt.entity.jwt.header.Header; import org.rootservices.jwt.jwe.entity.JWE; import org.rootservices.jwt.jwe.factory.exception.CipherException; import org.rootservices.jwt.jwe.serialization.JweSerializer; @@ -13,44 +16,111 @@ import org.rootservices.jwt.serialization.exception.JsonToJwtException; import java.io.ByteArrayOutputStream; +import java.util.Optional; public class EncryptedCompactBuilder { private static final Logger LOGGER = LogManager.getLogger(EncryptedCompactBuilder.class); public static final String UNABLE_TO_BUILD_COMPACT_JWE = "Unable to build compact jwe"; private static JwtAppFactory jwtAppFactory = new JwtAppFactory(); - private JWE jwe; + private byte[] payload; + private byte[] cek; + private Algorithm alg; + private EncryptionAlgorithm encAlg; + private RSAPublicKey publicKey; - public EncryptedCompactBuilder jwe(JWE jwe) { - this.jwe = jwe; + public EncryptedCompactBuilder payload(byte[] payload) { + this.payload = payload; return this; } - public EncryptedCompactBuilder key(RSAPublicKey publicKey) { + public EncryptedCompactBuilder rsa(RSAPublicKey publicKey) { this.publicKey = publicKey; return this; } + public EncryptedCompactBuilder cek(byte[] cek) { + this.cek = cek; + return this; + } + + public EncryptedCompactBuilder alg(Algorithm alg) { + this.alg = alg; + return this; + } + + public EncryptedCompactBuilder encAlg(EncryptionAlgorithm encAlg) { + this.encAlg = encAlg; + return this; + } + public ByteArrayOutputStream build() throws CompactException { + JWE jwe = jwe(); + JweSerializer jweSerializer = jweSerializer(); + + try { + return jweSerializer.JWEToCompact(jwe); + } catch (JsonToJwtException | CipherException | EncryptException e) { + LOGGER.error(e.getMessage(), e); + throw new CompactException(UNABLE_TO_BUILD_COMPACT_JWE, e); + } + } + + // a few factory methods to help the build method. + + protected JweSerializer jweSerializer() throws CompactException { JweSerializer jweSerializer; if (publicKey != null) { - try { - jweSerializer = jwtAppFactory.jweRsaSerializer(publicKey); - } catch (PublicKeyException | CipherException e) { - LOGGER.error(e.getMessage(), e); - throw new CompactException(UNABLE_TO_BUILD_COMPACT_JWE, e); - } + jweSerializer = jweRsaSerializer(); } else { jweSerializer = jwtAppFactory.jweDirectSerializer(); } + return jweSerializer; + } + protected JweSerializer jweRsaSerializer() throws CompactException { try { - return jweSerializer.JWEToCompact(jwe); - } catch (JsonToJwtException | CipherException | EncryptException e) { + return jwtAppFactory.jweRsaSerializer(publicKey); + } catch (PublicKeyException | CipherException e) { LOGGER.error(e.getMessage(), e); throw new CompactException(UNABLE_TO_BUILD_COMPACT_JWE, e); } } + protected JWE jwe() { + if (publicKey != null) { + return jweForRsa(); + } else { + return jweForDirect(); + } + } + + protected JWE jweForDirect() { + JWE jwe = new JWE(); + Header header = new Header(); + + header.setEncryptionAlgorithm(Optional.of(this.encAlg)); + header.setAlgorithm(this.alg); + + jwe.setHeader(header); + jwe.setPayload(payload); + jwe.setCek(cek); + + return jwe; + } + + protected JWE jweForRsa() { + JWE jwe = new JWE(); + Header header = new Header(); + + header.setKeyId(publicKey.getKeyId()); + header.setEncryptionAlgorithm(Optional.of(this.encAlg)); + header.setAlgorithm(this.alg); + + jwe.setHeader(header); + jwe.setPayload(payload); + + return jwe; + } } diff --git a/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java b/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java index c7ef1e9..902e804 100644 --- a/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java +++ b/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java @@ -10,12 +10,9 @@ import org.rootservices.jwt.entity.jwk.RSAPublicKey; import org.rootservices.jwt.entity.jwk.SymmetricKey; import org.rootservices.jwt.entity.jwt.header.Algorithm; -import org.rootservices.jwt.entity.jwt.header.Header; import org.rootservices.jwt.jwe.entity.JWE; import org.rootservices.jwt.jwe.serialization.JweDeserializer; -import org.rootservices.jwt.jwe.serialization.JweSerializer; import org.rootservices.jwt.jwe.serialization.rsa.JweRsaDeserializer; -import org.rootservices.jwt.jwe.serialization.rsa.JweRsaSerializer; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; @@ -43,16 +40,14 @@ public void buildWithDirect() throws Exception { Base64.Decoder decoder = jwtAppFactory.urlDecoder(); - Header header = new Header(); - header.setEncryptionAlgorithm(Optional.of(EncryptionAlgorithm.AES_GCM_256)); - header.setAlgorithm(Algorithm.DIRECT); + byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); - JWE jwe = new JWE(); - jwe.setHeader(header); - jwe.setCek(decoder.decode(key.getKey())); - jwe.setPayload("Help me, Obi-Wan Kenobi. You're my only hope.".getBytes()); - - ByteArrayOutputStream actual = subject.jwe(jwe).build(); + ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) + .alg(Algorithm.DIRECT) + .encAlg(EncryptionAlgorithm.AES_GCM_256) + .payload(payload) + .cek(decoder.decode(key.getKey())) + .build(); assertThat(actual, is(notNullValue())); @@ -80,27 +75,24 @@ public void buildWithDirect() throws Exception { JWE leia = jweDeserializer.stringToJWE(compactJWE, key); assertThat(leia, CoreMatchers.is(CoreMatchers.notNullValue())); - String payload = new String(leia.getPayload()); - assertThat(payload, CoreMatchers.is("Help me, Obi-Wan Kenobi. You're my only hope.")); + String decryptedPayload = new String(leia.getPayload()); + assertThat(decryptedPayload, CoreMatchers.is("Help me, Obi-Wan Kenobi. You're my only hope.")); } @Test public void buildWithRSA() throws Exception { JwtAppFactory jwtAppFactory = new JwtAppFactory(); + byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); + RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); publicKey.setKeyId(Optional.of(UUID.randomUUID().toString())); - Header header = new Header(); - header.setEncryptionAlgorithm(Optional.of(EncryptionAlgorithm.AES_GCM_256)); - header.setAlgorithm(Algorithm.RSAES_OAEP); - header.setKeyId(publicKey.getKeyId()); - - JWE jwe = new JWE(); - jwe.setHeader(header); - jwe.setPayload("Help me, Obi-Wan Kenobi. You're my only hope.".getBytes()); - - ByteArrayOutputStream actual = subject.jwe(jwe).key(publicKey).build(); + ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) + .alg(Algorithm.RSAES_OAEP) + .payload(payload) + .rsa(publicKey) + .build(); // make sure it can be read. RSAKeyPair jwk = Factory.makeRSAKeyPairForJWE(); @@ -111,8 +103,8 @@ public void buildWithRSA() throws Exception { JWE leia = JweRsaDeserializer.stringToJWE(compactJWE, jwk); assertThat(leia, CoreMatchers.is(CoreMatchers.notNullValue())); - String payload = new String(leia.getPayload()); - assertThat(payload, CoreMatchers.is("Help me, Obi-Wan Kenobi. You're my only hope.")); + String decryptedPayload = new String(leia.getPayload()); + assertThat(decryptedPayload, CoreMatchers.is("Help me, Obi-Wan Kenobi. You're my only hope.")); } From cddd9513a6c1078114aaced6119bae253aec2c8f Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Thu, 9 Aug 2018 12:49:48 -0500 Subject: [PATCH 07/13] builder-docs: doc builder in readme. --- README.md | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dbf44bf..e401b1e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,111 @@ [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) --------------------------------------------------------------------------------------------------------------------- +[![Build Status](https://travis-ci.org/RootServices/jwt.svg?branch=development)](https://travis-ci.org/RootServices/jwt) + Documentation ------------ - Documentation is written in [github pages](http://rootservices.github.io/jwt/). Which is located in the branch, [gh-pages](https://github.com/RootServices/jwt/tree/gh-pages) + More documentation is written in [github pages](http://rootservices.github.io/jwt/). Which is located in the branch, [gh-pages](https://github.com/RootServices/jwt/tree/gh-pages) + +Quick Start +----------- + +## unsecure compact jwt +```java +UnsecureCompactBuilder compactBuilder = new UnsecureCompactBuilder(); + +Claim claim = new Claim(); +claim.setUriIsRoot(true); + +ByteArrayOutputStream encodedJwt = compactBuilder.claims(claim) + .build(); +``` + +## secure compact jwt with asymmetric key +```java +SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); + +RSAKeyPair keyPair = new RSAKeyPair( + Optional.of("test-key-id"), + KeyType.RSA, + Use.SIGNATURE, + new BigInteger("20446702916744654562596343388758805860065209639960173505037453331270270518732245089773723012043203236097095623402044690115755377345254696448759605707788965848889501746836211206270643833663949992536246985362693736387185145424787922241585721992924045675229348655595626434390043002821512765630397723028023792577935108185822753692574221566930937805031155820097146819964920270008811327036286786392793593121762425048860211859763441770446703722015857250621107855398693133264081150697423188751482418465308470313958250757758547155699749157985955379381294962058862159085915015369381046959790476428631998204940879604226680285601"), + new BigInteger("65537"), + new BigInteger("2358310989939619510179986262349936882924652023566213765118606431955566700506538911356936879137503597382515919515633242482643314423192704128296593672966061810149316320617894021822784026407461403384065351821972350784300967610143459484324068427674639688405917977442472804943075439192026107319532117557545079086537982987982522396626690057355718157403493216553255260857777965627529169195827622139772389760130571754834678679842181142252489617665030109445573978012707793010592737640499220015083392425914877847840457278246402760955883376999951199827706285383471150643561410605789710883438795588594095047409018233862167884701"), + new BigInteger("157377055902447438395586165028960291914931973278777532798470200156035267537359239071829408411909323208574959800537247728959718236884809685233284537349207654661530801859889389455120932077199406250387226339056140578989122526711937239401762061949364440402067108084155200696015505170135950332209194782224750221639"), + new BigInteger("129921752567406358990993347540064445018230073402482260994179328573323861908379211274626956543471664997237185298964648133324343327052852264060322088122401124781249085873464824282666514908127141915943024862618996371026577302203267804867959037802770797169483022132210859867700312376409633383772189122488119155159"), + new BigInteger("4922760648183732070600601771661330216909692924935440167185924139339187000497222028735680413269055839870281941362914961691371628021024152077883230870590287807744299308982303864732867094286567630701646611762288298013758658158894538059014178662376933683632720014228880806671525788467258162275185762295508460173"), + new BigInteger("95501022448116849078110281587424883261489167280943290677534750975651978631525094586933777934986404467352856624385049043666431411835209194330560695076194390729082708437497817187133629692908081460223069101908960299950170666285307890683263785145958472051553704139602453015343925544671829327400421727981791235189"), + new BigInteger("23545019917990284444784037831882732213707743418529123971725460465297450415859883707284136179135646366158633580054594447195052813412945775933274620822213099556720089770059982091144435545976515508108465724188242241967967709555336331874325396876783846248039429242763646988988076187339075374375350105207330456437") +); + +Claim claim = new Claim(); +claim.setUriIsRoot(true); + +ByteArrayOutputStream encodedJwt = null; +try { + encodedJwt = compactBuilder.claims(claim) + .key(keyPair) + .alg(Algorithm.RS256) + .build(); +} catch (CompactException e) { + e.printStackTrace(); +} +``` + +## secure compact jwt with symmetric key +```java +SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); + +SymmetricKey key = new SymmetricKey( + Optional.of("test-key-id"), + "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", + Use.SIGNATURE +); + +Claim claim = new Claim(); +claim.setUriIsRoot(true); + +ByteArrayOutputStream encodedJwt = null; +try { + encodedJwt = compactBuilder.claims(claim) + .key(key) + .alg(Algorithm.HS256) + .build(); +} catch (CompactException e) { + e.printStackTrace(); +} +``` + +encrypted compact jwt with symmetric key +```java +SymmetricKey key = Factory.makeSymmetricKeyForJWE(); + +Base64.Decoder decoder = jwtAppFactory.urlDecoder(); + +byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); + +ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) + .alg(Algorithm.DIRECT) + .encAlg(EncryptionAlgorithm.AES_GCM_256) + .payload(payload) + .cek(decoder.decode(key.getKey())) + .build(); +``` + +encrypted compact jwt with asymmetric key +```java +JwtAppFactory jwtAppFactory = new JwtAppFactory(); + +byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); + +RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); +publicKey.setKeyId(Optional.of(UUID.randomUUID().toString())); -Travis Results -------------------------------------- - - development branch [![Build Status](https://travis-ci.org/RootServices/jwt.svg?branch=development)](https://travis-ci.org/RootServices/jwt) +ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) + .alg(Algorithm.RSAES_OAEP) + .payload(payload) + .rsa(publicKey) + .build(); +``` From 4d1ffd265c074ef8f44ba715e21961b6582fa483 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Thu, 9 Aug 2018 12:54:09 -0500 Subject: [PATCH 08/13] builder-docs: formatting --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e401b1e..99299a8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ ByteArrayOutputStream encodedJwt = compactBuilder.claims(claim) .build(); ``` -## secure compact jwt with asymmetric key +## secure + +### compact jwt signed with asymmetric key ```java SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); @@ -54,7 +56,7 @@ try { } ``` -## secure compact jwt with symmetric key +### compact jwt signed with symmetric key ```java SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); @@ -78,7 +80,8 @@ try { } ``` -encrypted compact jwt with symmetric key +## Encrypted +### encrypted compact jwt with symmetric key ```java SymmetricKey key = Factory.makeSymmetricKeyForJWE(); @@ -94,7 +97,7 @@ ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) .build(); ``` -encrypted compact jwt with asymmetric key +### encrypted compact jwt with asymmetric key ```java JwtAppFactory jwtAppFactory = new JwtAppFactory(); From ebd99fe4449a2fb89bc2437149f436790477f604 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Thu, 9 Aug 2018 13:39:06 -0500 Subject: [PATCH 09/13] builder-docs: making things more descriptive --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 99299a8..aced1da 100644 --- a/README.md +++ b/README.md @@ -6,25 +6,25 @@ Documentation ------------ - More documentation is written in [github pages](http://rootservices.github.io/jwt/). Which is located in the branch, [gh-pages](https://github.com/RootServices/jwt/tree/gh-pages) + More documentation is available [here](http://rootservices.github.io/jwt/). Quick Start ----------- - -## unsecure compact jwt +This is a Java implementation of JWT, JWS, and JWE. + +## Unsecured JWT ```java UnsecureCompactBuilder compactBuilder = new UnsecureCompactBuilder(); Claim claim = new Claim(); claim.setUriIsRoot(true); -ByteArrayOutputStream encodedJwt = compactBuilder.claims(claim) - .build(); +ByteArrayOutputStream encodedJwt = compactBuilder.claims(claim).build(); ``` -## secure +## JWS Compact Serialization -### compact jwt signed with asymmetric key +### Asymmetric key ```java SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); @@ -56,7 +56,7 @@ try { } ``` -### compact jwt signed with symmetric key +### Symmetric key ```java SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); @@ -80,8 +80,8 @@ try { } ``` -## Encrypted -### encrypted compact jwt with symmetric key +## JWE Compact Serialization +### Symmetric key ```java SymmetricKey key = Factory.makeSymmetricKeyForJWE(); @@ -97,7 +97,7 @@ ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) .build(); ``` -### encrypted compact jwt with asymmetric key +### Asymmetric key ```java JwtAppFactory jwtAppFactory = new JwtAppFactory(); From 59a343c37c908e23fab7eb04c6bdd3987b7341a9 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Thu, 9 Aug 2018 14:11:03 -0500 Subject: [PATCH 10/13] key-id: include key id in header for jwe --- README.md | 54 +++++-------------- .../compact/EncryptedCompactBuilder.java | 11 ++-- .../compact/EncryptedCompactBuilderTest.java | 6 +-- 3 files changed, 25 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index aced1da..95a7d57 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Documentation Quick Start ----------- -This is a Java implementation of JWT, JWS, and JWE. +This is a Java implementation of JWT, JWS, and JWE. Below are examples that were sourced from the test suite. ## Unsecured JWT ```java @@ -28,56 +28,32 @@ ByteArrayOutputStream encodedJwt = compactBuilder.claims(claim).build(); ```java SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); -RSAKeyPair keyPair = new RSAKeyPair( - Optional.of("test-key-id"), - KeyType.RSA, - Use.SIGNATURE, - new BigInteger("20446702916744654562596343388758805860065209639960173505037453331270270518732245089773723012043203236097095623402044690115755377345254696448759605707788965848889501746836211206270643833663949992536246985362693736387185145424787922241585721992924045675229348655595626434390043002821512765630397723028023792577935108185822753692574221566930937805031155820097146819964920270008811327036286786392793593121762425048860211859763441770446703722015857250621107855398693133264081150697423188751482418465308470313958250757758547155699749157985955379381294962058862159085915015369381046959790476428631998204940879604226680285601"), - new BigInteger("65537"), - new BigInteger("2358310989939619510179986262349936882924652023566213765118606431955566700506538911356936879137503597382515919515633242482643314423192704128296593672966061810149316320617894021822784026407461403384065351821972350784300967610143459484324068427674639688405917977442472804943075439192026107319532117557545079086537982987982522396626690057355718157403493216553255260857777965627529169195827622139772389760130571754834678679842181142252489617665030109445573978012707793010592737640499220015083392425914877847840457278246402760955883376999951199827706285383471150643561410605789710883438795588594095047409018233862167884701"), - new BigInteger("157377055902447438395586165028960291914931973278777532798470200156035267537359239071829408411909323208574959800537247728959718236884809685233284537349207654661530801859889389455120932077199406250387226339056140578989122526711937239401762061949364440402067108084155200696015505170135950332209194782224750221639"), - new BigInteger("129921752567406358990993347540064445018230073402482260994179328573323861908379211274626956543471664997237185298964648133324343327052852264060322088122401124781249085873464824282666514908127141915943024862618996371026577302203267804867959037802770797169483022132210859867700312376409633383772189122488119155159"), - new BigInteger("4922760648183732070600601771661330216909692924935440167185924139339187000497222028735680413269055839870281941362914961691371628021024152077883230870590287807744299308982303864732867094286567630701646611762288298013758658158894538059014178662376933683632720014228880806671525788467258162275185762295508460173"), - new BigInteger("95501022448116849078110281587424883261489167280943290677534750975651978631525094586933777934986404467352856624385049043666431411835209194330560695076194390729082708437497817187133629692908081460223069101908960299950170666285307890683263785145958472051553704139602453015343925544671829327400421727981791235189"), - new BigInteger("23545019917990284444784037831882732213707743418529123971725460465297450415859883707284136179135646366158633580054594447195052813412945775933274620822213099556720089770059982091144435545976515508108465724188242241967967709555336331874325396876783846248039429242763646988988076187339075374375350105207330456437") -); +RSAKeyPair key = Factory.makeRSAKeyPair(); +key.setKeyId(Optional.of("test-key-id")); Claim claim = new Claim(); claim.setUriIsRoot(true); -ByteArrayOutputStream encodedJwt = null; -try { - encodedJwt = compactBuilder.claims(claim) - .key(keyPair) - .alg(Algorithm.RS256) - .build(); -} catch (CompactException e) { - e.printStackTrace(); -} +ByteArrayOutputStream actual = subject.alg(Algorithm.RS256) + .key(key) + .claims(claim) + .build(); ``` ### Symmetric key ```java SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); -SymmetricKey key = new SymmetricKey( - Optional.of("test-key-id"), - "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", - Use.SIGNATURE -); +SymmetricKey key = Factory.makeSymmetricKey(); +key.setKeyId(Optional.of("test-key-id")); Claim claim = new Claim(); claim.setUriIsRoot(true); -ByteArrayOutputStream encodedJwt = null; -try { - encodedJwt = compactBuilder.claims(claim) - .key(key) - .alg(Algorithm.HS256) - .build(); -} catch (CompactException e) { - e.printStackTrace(); -} +ByteArrayOutputStream actual = subject.alg(Algorithm.HS256) + .key(key) + .claims(claim) + .build(); ``` ## JWE Compact Serialization @@ -85,15 +61,13 @@ try { ```java SymmetricKey key = Factory.makeSymmetricKeyForJWE(); -Base64.Decoder decoder = jwtAppFactory.urlDecoder(); - byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) .alg(Algorithm.DIRECT) .encAlg(EncryptionAlgorithm.AES_GCM_256) .payload(payload) - .cek(decoder.decode(key.getKey())) + .cek(key) .build(); ``` diff --git a/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java b/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java index 59b98d2..732f847 100644 --- a/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java +++ b/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java @@ -6,6 +6,7 @@ import org.rootservices.jwt.config.JwtAppFactory; import org.rootservices.jwt.entity.jwe.EncryptionAlgorithm; import org.rootservices.jwt.entity.jwk.RSAPublicKey; +import org.rootservices.jwt.entity.jwk.SymmetricKey; import org.rootservices.jwt.entity.jwt.header.Algorithm; import org.rootservices.jwt.entity.jwt.header.Header; import org.rootservices.jwt.jwe.entity.JWE; @@ -16,6 +17,7 @@ import org.rootservices.jwt.serialization.exception.JsonToJwtException; import java.io.ByteArrayOutputStream; +import java.util.Base64; import java.util.Optional; public class EncryptedCompactBuilder { @@ -24,7 +26,7 @@ public class EncryptedCompactBuilder { private static JwtAppFactory jwtAppFactory = new JwtAppFactory(); private byte[] payload; - private byte[] cek; + private SymmetricKey cek; private Algorithm alg; private EncryptionAlgorithm encAlg; @@ -40,7 +42,7 @@ public EncryptedCompactBuilder rsa(RSAPublicKey publicKey) { return this; } - public EncryptedCompactBuilder cek(byte[] cek) { + public EncryptedCompactBuilder cek(SymmetricKey cek) { this.cek = cek; return this; } @@ -97,15 +99,18 @@ protected JWE jwe() { } protected JWE jweForDirect() { + Base64.Decoder decoder = jwtAppFactory.urlDecoder(); + JWE jwe = new JWE(); Header header = new Header(); header.setEncryptionAlgorithm(Optional.of(this.encAlg)); header.setAlgorithm(this.alg); + header.setKeyId(cek.getKeyId()); jwe.setHeader(header); jwe.setPayload(payload); - jwe.setCek(cek); + jwe.setCek(decoder.decode(cek.getKey())); return jwe; } diff --git a/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java b/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java index 902e804..25fcb6f 100644 --- a/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java +++ b/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java @@ -38,21 +38,21 @@ public void buildWithDirect() throws Exception { SymmetricKey key = Factory.makeSymmetricKeyForJWE(); - Base64.Decoder decoder = jwtAppFactory.urlDecoder(); - byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) .alg(Algorithm.DIRECT) .encAlg(EncryptionAlgorithm.AES_GCM_256) .payload(payload) - .cek(decoder.decode(key.getKey())) + .cek(key) .build(); assertThat(actual, is(notNullValue())); String compactJWE = actual.toString(); + Base64.Decoder decoder = jwtAppFactory.urlDecoder(); + String[] jweParts = compactJWE.split("\\."); String protectedHeader = new String(decoder.decode(jweParts[0]), StandardCharsets.UTF_8); String encryptedKey = new String(decoder.decode(jweParts[1]), StandardCharsets.UTF_8); From 660ddfb296bc8fec55f38f4aadf990b8ef62f7d3 Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Thu, 9 Aug 2018 17:41:56 -0500 Subject: [PATCH 11/13] key-id: docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95a7d57..da2647e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Documentation Quick Start ----------- -This is a Java implementation of JWT, JWS, and JWE. Below are examples that were sourced from the test suite. +This is a Java implementation of JWT, JWS, and JWE. ## Unsecured JWT ```java From 159cba375104ea89a2836946688d4582833e8e6a Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Fri, 10 Aug 2018 07:33:10 -0500 Subject: [PATCH 12/13] docs: change variable names in docs --- README.md | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index da2647e..9a2a40d 100644 --- a/README.md +++ b/README.md @@ -50,39 +50,42 @@ key.setKeyId(Optional.of("test-key-id")); Claim claim = new Claim(); claim.setUriIsRoot(true); -ByteArrayOutputStream actual = subject.alg(Algorithm.HS256) +ByteArrayOutputStream actual = compactBuilder.alg(Algorithm.HS256) .key(key) .claims(claim) .build(); ``` ## JWE Compact Serialization -### Symmetric key + +### Asymmetric key ```java -SymmetricKey key = Factory.makeSymmetricKeyForJWE(); +EncryptedCompactBuilder compactBuilder = new EncryptedCompactBuilder(); byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); -ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) - .alg(Algorithm.DIRECT) - .encAlg(EncryptionAlgorithm.AES_GCM_256) +RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); +publicKey.setKeyId(Optional.of(UUID.randomUUID().toString())); + +ByteArrayOutputStream actual = compactBuilder.encAlg(EncryptionAlgorithm.AES_GCM_256) + .alg(Algorithm.RSAES_OAEP) .payload(payload) - .cek(key) + .rsa(publicKey) .build(); ``` -### Asymmetric key +### Symmetric key ```java -JwtAppFactory jwtAppFactory = new JwtAppFactory(); +EncryptedCompactBuilder compactBuilder = new EncryptedCompactBuilder(); -byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); +SymmetricKey key = Factory.makeSymmetricKeyForJWE(); -RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); -publicKey.setKeyId(Optional.of(UUID.randomUUID().toString())); +byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); -ByteArrayOutputStream actual = subject.encAlg(EncryptionAlgorithm.AES_GCM_256) - .alg(Algorithm.RSAES_OAEP) +ByteArrayOutputStream actual = compactBuilder.encAlg(EncryptionAlgorithm.AES_GCM_256) + .alg(Algorithm.DIRECT) + .encAlg(EncryptionAlgorithm.AES_GCM_256) .payload(payload) - .rsa(publicKey) + .cek(key) .build(); ``` From 996994f7fae726caa6d579d95e08b877b1dd906a Mon Sep 17 00:00:00 2001 From: Tom MacKenzie Date: Fri, 10 Aug 2018 07:35:48 -0500 Subject: [PATCH 13/13] release-1.3.1: preparing release 1.3.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 35f93aa..615b7a6 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'signing' apply plugin: 'maven' group = "org.rootservices" -version = "1.3.1-SNAPSHOT" +version = "1.3.1" archivesBaseName="jwt" description = """JSON Web Tokens"""