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/README.md b/README.md index dbf44bf..9a2a40d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,91 @@ [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 available [here](http://rootservices.github.io/jwt/). + +Quick Start +----------- +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(); +``` + +## JWS Compact Serialization + +### Asymmetric key +```java +SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); + +RSAKeyPair key = Factory.makeRSAKeyPair(); +key.setKeyId(Optional.of("test-key-id")); + +Claim claim = new Claim(); +claim.setUriIsRoot(true); + +ByteArrayOutputStream actual = subject.alg(Algorithm.RS256) + .key(key) + .claims(claim) + .build(); +``` + +### Symmetric key +```java +SecureCompactBuilder compactBuilder = new SecureCompactBuilder(); + +SymmetricKey key = Factory.makeSymmetricKey(); +key.setKeyId(Optional.of("test-key-id")); + +Claim claim = new Claim(); +claim.setUriIsRoot(true); + +ByteArrayOutputStream actual = compactBuilder.alg(Algorithm.HS256) + .key(key) + .claims(claim) + .build(); +``` + +## JWE Compact Serialization + +### Asymmetric key +```java +EncryptedCompactBuilder compactBuilder = new EncryptedCompactBuilder(); + +byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); + +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) + .rsa(publicKey) + .build(); +``` + +### Symmetric key +```java +EncryptedCompactBuilder compactBuilder = new EncryptedCompactBuilder(); + +SymmetricKey key = Factory.makeSymmetricKeyForJWE(); + +byte[] payload = "Help me, Obi-Wan Kenobi. You're my only hope.".getBytes(); -Travis Results -------------------------------------- - - development branch [![Build Status](https://travis-ci.org/RootServices/jwt.svg?branch=development)](https://travis-ci.org/RootServices/jwt) +ByteArrayOutputStream actual = compactBuilder.encAlg(EncryptionAlgorithm.AES_GCM_256) + .alg(Algorithm.DIRECT) + .encAlg(EncryptionAlgorithm.AES_GCM_256) + .payload(payload) + .cek(key) + .build(); +``` diff --git a/build.gradle b/build.gradle index fd795ca..615b7a6 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'signing' apply plugin: 'maven' group = "org.rootservices" -version = "1.3" +version = "1.3.1" archivesBaseName="jwt" description = """JSON Web Tokens""" 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..732f847 --- /dev/null +++ b/src/main/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilder.java @@ -0,0 +1,131 @@ +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.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; +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; +import java.util.Base64; +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 byte[] payload; + private SymmetricKey cek; + private Algorithm alg; + private EncryptionAlgorithm encAlg; + + private RSAPublicKey publicKey; + + public EncryptedCompactBuilder payload(byte[] payload) { + this.payload = payload; + return this; + } + + public EncryptedCompactBuilder rsa(RSAPublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + public EncryptedCompactBuilder cek(SymmetricKey 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) { + jweSerializer = jweRsaSerializer(); + } else { + jweSerializer = jwtAppFactory.jweDirectSerializer(); + } + return jweSerializer; + } + + protected JweSerializer jweRsaSerializer() throws CompactException { + try { + 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() { + 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(decoder.decode(cek.getKey())); + + 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/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/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/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..25fcb6f --- /dev/null +++ b/src/test/java/org/rootservices/jwt/builder/compact/EncryptedCompactBuilderTest.java @@ -0,0 +1,111 @@ +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.jwe.entity.JWE; +import org.rootservices.jwt.jwe.serialization.JweDeserializer; +import org.rootservices.jwt.jwe.serialization.rsa.JweRsaDeserializer; + +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(); + + 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(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); + 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 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())); + + 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(); + JweRsaDeserializer JweRsaDeserializer = jwtAppFactory.jweRsaDeserializer(); + + String compactJWE = actual.toString(); + + JWE leia = JweRsaDeserializer.stringToJWE(compactJWE, jwk); + + assertThat(leia, CoreMatchers.is(CoreMatchers.notNullValue())); + String decryptedPayload = new String(leia.getPayload()); + assertThat(decryptedPayload, 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 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/JweRsaSerializerTest.java similarity index 95% 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 4b02144..f0e9041 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 @@ -6,14 +6,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; @@ -21,7 +19,7 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.*; -public class JweRsaSerdesTest { +public class JweRsaSerializerTest { @Test public void extractCipherText() throws Exception { @@ -36,7 +34,6 @@ public void extractCipherText() throws Exception { byte[] cipherText = decoder.decode(jweParts[3]); byte[] authenticationTag = decoder.decode(jweParts[4]); - RSAKeyPair jwk = Factory.makeRSAKeyPairForJWE(); JweRsaDeserializer JweRsaDeserializer = jwtAppFactory.jweRsaDeserializer(); RSAPublicKey publicKey = Factory.makeRSAPublicKeyForJWE(); 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