From f1cd8b99d75e432ebad354d9aa96b463ee87cdd7 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Mon, 13 Sep 2021 22:45:43 -0700 Subject: [PATCH] JWE work cont'd. Lots of cleanup. Have full roundtrip encrypt/decrypt working now that obtaining AAD data has been fixed to work correctly (was previously using the serialized header bytes directly instead of the ASCII bytes of the encoded header) --- .../jsonwebtoken/security/KeyAlgorithms.java | 29 +-- .../jsonwebtoken/impl/DefaultJweBuilder.java | 25 +-- .../jsonwebtoken/impl/DefaultJwtParser.java | 6 +- .../AbstractAsymmetricJwkBuilder.java | 14 +- .../impl/security/AbstractJwkBuilder.java | 2 +- .../impl/security/DefaultJwkContext.java | 99 +++++++---- ...rithm.java => DefaultRsaKeyAlgorithm.java} | 31 ++-- .../DefaultRsaSignatureAlgorithm.java | 1 - .../impl/security/EcPrivateJwkFactory.java | 7 +- .../impl/security/JwkContext.java | 24 +-- .../impl/security/KeyAlgorithms.java | 71 ++++++++ .../impl/security/RsaPrivateJwkFactory.java | 16 +- .../security/RFC7516AppendixA1Test.groovy | 165 ++++++++++++++++++ .../security/RFC7516AppendixA2Test.groovy | 159 +++++++++++++++++ ...st.groovy => RFC7518AppendixB1Test.groovy} | 29 ++- ...st.groovy => RFC7518AppendixB2Test.groovy} | 6 +- ...st.groovy => RFC7518AppendixB3Test.groovy} | 6 +- ...RsaesPkcsv15Aes128CbcHmacSha256Test.groovy | 78 --------- 18 files changed, 573 insertions(+), 195 deletions(-) rename impl/src/main/java/io/jsonwebtoken/impl/security/{Rsa15KeyAlgorithm.java => DefaultRsaKeyAlgorithm.java} (66%) create mode 100644 impl/src/main/java/io/jsonwebtoken/impl/security/KeyAlgorithms.java create mode 100644 impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA1Test.groovy create mode 100644 impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA2Test.groovy rename impl/src/test/groovy/io/jsonwebtoken/impl/security/{Aes128CbcHmacSha256Test.groovy => RFC7518AppendixB1Test.groovy} (74%) rename impl/src/test/groovy/io/jsonwebtoken/impl/security/{Aes192CbcHmacSha384Test.groovy => RFC7518AppendixB2Test.groovy} (95%) rename impl/src/test/groovy/io/jsonwebtoken/impl/security/{Aes256CbcHmacSha512Test.groovy => RFC7518AppendixB3Test.groovy} (95%) delete mode 100644 impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaesPkcsv15Aes128CbcHmacSha256Test.groovy diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithms.java b/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithms.java index f9331e13..1e1bbdee 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithms.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithms.java @@ -4,6 +4,8 @@ import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Classes; import javax.crypto.SecretKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; @@ -18,21 +20,22 @@ public final class KeyAlgorithms { private KeyAlgorithms() { } - private static final String AESWRAP = "io.jsonwebtoken.impl.security.AesWrapKeyAlgorithm"; - private static final String AESGCMWRAP = "io.jsonwebtoken.impl.security.AesGcmKeyAlgorithm"; - private static final Class[] AESWRAP_ARGS = new Class[]{int.class}; + private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeyAlgorithms"; - private static EncryptedKeyAlgorithm aeswrap(String fqcn, int keyLength) { - return Classes.newInstance(fqcn, AESWRAP_ARGS, keyLength); + private static T lookup(String methodName) { + return Classes.invokeStatic(BRIDGE_CLASSNAME, methodName, null, (Object[]) null); } - public static final KeyAlgorithm DIRECT = Classes.newInstance("io.jsonwebtoken.impl.security.DirectKeyAlgorithm"); - public static final EncryptedKeyAlgorithm A128KW = aeswrap(AESWRAP, 128); - public static final EncryptedKeyAlgorithm A192KW = aeswrap(AESWRAP, 192); - public static final EncryptedKeyAlgorithm A256KW = aeswrap(AESWRAP, 256); - public static final EncryptedKeyAlgorithm A128GCMKW = aeswrap(AESGCMWRAP, 128); - public static final EncryptedKeyAlgorithm A192GCMKW = aeswrap(AESGCMWRAP, 192); - public static final EncryptedKeyAlgorithm A256GCMKW = aeswrap(AESGCMWRAP, 256); + public static final KeyAlgorithm DIRECT = lookup("direct"); + public static final EncryptedKeyAlgorithm A128KW = lookup("a128kw"); + public static final EncryptedKeyAlgorithm A192KW = lookup("a192kw"); + public static final EncryptedKeyAlgorithm A256KW = lookup("a256kw"); + public static final EncryptedKeyAlgorithm A128GCMKW = lookup("a128gcmkw"); + public static final EncryptedKeyAlgorithm A192GCMKW = lookup("a192gcmkw"); + public static final EncryptedKeyAlgorithm A256GCMKW = lookup("a256gcmkw"); + public static final EncryptedKeyAlgorithm RSA1_5 = lookup("rsa1_5"); + public static final EncryptedKeyAlgorithm RSA_OAEP = lookup("rsaOaep"); + public static final EncryptedKeyAlgorithm RSA_OAEP_256 = lookup("rsaOaep256"); private static Map> toMap(KeyAlgorithm... algs) { Map> m = new LinkedHashMap<>(); @@ -43,7 +46,7 @@ public final class KeyAlgorithms { } private static final Map> STANDARD_ALGORITHMS = toMap( - DIRECT, A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW + DIRECT, A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, RSA1_5, RSA_OAEP, RSA_OAEP_256 ); public static Collection> values() { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJweBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJweBuilder.java index 9d87ee72..7b305b5d 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJweBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJweBuilder.java @@ -9,19 +9,19 @@ import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.impl.security.DefaultKeyRequest; import io.jsonwebtoken.impl.security.DefaultSymmetricAeadRequest; import io.jsonwebtoken.io.SerializationException; -import io.jsonwebtoken.security.EncryptedKeyAlgorithm; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; -import io.jsonwebtoken.security.KeyRequest; -import io.jsonwebtoken.security.SecurityException; -import io.jsonwebtoken.security.SymmetricAeadAlgorithm; +import io.jsonwebtoken.security.AeadResult; +import io.jsonwebtoken.security.EncryptedKeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithms; +import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; -import io.jsonwebtoken.security.AeadResult; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.SymmetricAeadAlgorithm; import io.jsonwebtoken.security.SymmetricAeadRequest; import javax.crypto.SecretKey; @@ -149,29 +149,30 @@ public class DefaultJweBuilder extends DefaultJwtBuilder implements byte[] encryptedCek = Assert.notNull(keyResult.getPayload(), "KeyResult must return an encrypted key byte array, even if empty."); jweHeader.putAll(keyResult.getHeaderParams()); - jweHeader.setEncryptionAlgorithm(enc.getId()); jweHeader.setAlgorithm(alg.getId()); + jweHeader.setEncryptionAlgorithm(enc.getId()); byte[] headerBytes = this.headerSerializer.apply(jweHeader); + final String base64UrlEncodedHeader = base64UrlEncoder.encode(headerBytes); + byte[] aad = base64UrlEncodedHeader.getBytes(StandardCharsets.US_ASCII); - SymmetricAeadRequest encRequest = new DefaultSymmetricAeadRequest(provider, secureRandom, plaintext, cek, headerBytes); + SymmetricAeadRequest encRequest = new DefaultSymmetricAeadRequest(provider, secureRandom, plaintext, cek, aad); AeadResult encResult = encFunction.apply(encRequest); byte[] iv = Assert.notEmpty(encResult.getInitializationVector(), "Encryption result must have a non-empty initialization vector."); byte[] ciphertext = Assert.notEmpty(encResult.getPayload(), "Encryption result must have non-empty ciphertext (result.getData())."); byte[] tag = Assert.notEmpty(encResult.getAuthenticationTag(), "Encryption result must have a non-empty authentication tag."); - String base64UrlEncodedHeader = base64UrlEncoder.encode(headerBytes); - String base64UrlEncodedEncryptedKey = base64UrlEncoder.encode(encryptedCek); + String base64UrlEncodedEncryptedCek = base64UrlEncoder.encode(encryptedCek); String base64UrlEncodedIv = base64UrlEncoder.encode(iv); String base64UrlEncodedCiphertext = base64UrlEncoder.encode(ciphertext); - String base64UrlEncodedAad = base64UrlEncoder.encode(tag); + String base64UrlEncodedTag = base64UrlEncoder.encode(tag); return base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + - base64UrlEncodedEncryptedKey + JwtParser.SEPARATOR_CHAR + + base64UrlEncodedEncryptedCek + JwtParser.SEPARATOR_CHAR + base64UrlEncodedIv + JwtParser.SEPARATOR_CHAR + base64UrlEncodedCiphertext + JwtParser.SEPARATOR_CHAR + - base64UrlEncodedAad; + base64UrlEncodedTag; } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 42751174..a8d468e2 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -40,8 +40,8 @@ import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver; import io.jsonwebtoken.impl.lang.LegacyServices; -import io.jsonwebtoken.impl.security.DefaultKeyRequest; import io.jsonwebtoken.impl.security.DefaultAeadResult; +import io.jsonwebtoken.impl.security.DefaultKeyRequest; import io.jsonwebtoken.impl.security.DefaultVerifySignatureRequest; import io.jsonwebtoken.impl.security.DelegatingSigningKeyResolver; import io.jsonwebtoken.impl.security.StaticKeyResolver; @@ -346,6 +346,8 @@ public class DefaultJwtParser implements JwtParser { throw new MalformedJwtException(msg); } + final byte[] aad = base64UrlHeader.getBytes(StandardCharsets.US_ASCII); + base64Url = tokenizedJwe.getDigest(); if (Strings.hasText(base64Url)) { tag = base64UrlDecode(base64Url, "JWE AAD Authentication Tag"); @@ -373,7 +375,7 @@ public class DefaultJwtParser implements JwtParser { final SecretKey cek = keyAlg.getDecryptionKey(request); SymmetricAeadDecryptionRequest decryptRequest = - new DefaultAeadResult(this.provider, null, bytes, cek, headerBytes, tag, iv); + new DefaultAeadResult(this.provider, null, bytes, cek, aad, tag, iv); PayloadSupplier result = encAlg.decrypt(decryptRequest); bytes = result.getPayload(); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java index 5ea3c39c..dde00650 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java @@ -51,7 +51,7 @@ abstract class AbstractAsymmetricJwkBuilder b, K key, Set privateNames) { - super(new DefaultJwkContext<>(b.jwkContext, key, privateNames)); + super(new DefaultJwkContext<>(privateNames, b.jwkContext, key)); this.computeX509Sha1Thumbprint = b.computeX509Sha1Thumbprint; this.computeX509Sha256Thumbprint = b.computeX509Sha256Thumbprint; this.applyX509KeyUse = b.applyX509KeyUse; @@ -154,7 +154,7 @@ abstract class AbstractAsymmetricJwkBuilder implements PublicJwkBuilder { - public DefaultPublicJwkBuilder(JwkContext ctx) { + DefaultPublicJwkBuilder(JwkContext ctx) { super(ctx); } @@ -194,8 +194,8 @@ abstract class AbstractAsymmetricJwkBuilder implements EcPublicJwkBuilder { - public DefaultEcPublicJwkBuilder(JwkContext src, ECPublicKey key) { - super(new DefaultJwkContext<>(src, key, DefaultEcPrivateJwk.PRIVATE_NAMES)); + DefaultEcPublicJwkBuilder(JwkContext src, ECPublicKey key) { + super(new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, src, key)); } @Override @@ -209,7 +209,7 @@ abstract class AbstractAsymmetricJwkBuilder ctx, RSAPublicKey key) { - super(new DefaultJwkContext<>(ctx, key, DefaultRsaPrivateJwk.PRIVATE_NAMES)); + super(new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, ctx, key)); } @Override @@ -223,7 +223,7 @@ abstract class AbstractAsymmetricJwkBuilder src, ECPrivateKey key) { - super(new DefaultJwkContext<>(src, key, DefaultEcPrivateJwk.PRIVATE_NAMES)); + super(new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, src, key)); } DefaultEcPrivateJwkBuilder(DefaultEcPublicJwkBuilder b, ECPrivateKey key) { @@ -236,7 +236,7 @@ abstract class AbstractAsymmetricJwkBuilder src, RSAPrivateKey key) { - super(new DefaultJwkContext<>(src, key, DefaultRsaPrivateJwk.PRIVATE_NAMES)); + super(new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, src, key)); } DefaultRsaPrivateJwkBuilder(DefaultRsaPublicJwkBuilder b, RSAPrivateKey key) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java index bdc76dcb..532cf683 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java @@ -90,7 +90,7 @@ abstract class AbstractJwkBuilder, T extends Jwk static class DefaultSecretJwkBuilder extends AbstractJwkBuilder implements SecretJwkBuilder { public DefaultSecretJwkBuilder(JwkContext ctx, SecretKey key) { - super(new DefaultJwkContext<>(ctx, key, DefaultSecretJwk.PRIVATE_NAMES)); + super(new DefaultJwkContext<>(DefaultSecretJwk.PRIVATE_NAMES, ctx, key)); } } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkContext.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkContext.java index d23bbf46..5ca13a79 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkContext.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkContext.java @@ -21,6 +21,7 @@ import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -36,9 +37,16 @@ public class DefaultJwkContext implements JwkContext { private static final Converter URI_CONVERTER = Converters.forEncoded(URI.class, new UriStringConverter()); + private static final Set DEFAULT_PRIVATE_NAMES; private static final Map> SETTERS; static { + Set set = new LinkedHashSet<>(); + set.addAll(DefaultRsaPrivateJwk.PRIVATE_NAMES); + set.addAll(DefaultEcPrivateJwk.PRIVATE_NAMES); + set.addAll(DefaultSecretJwk.PRIVATE_NAMES); + DEFAULT_PRIVATE_NAMES = java.util.Collections.unmodifiableSet(set); + @SuppressWarnings("RedundantTypeArguments") List> fns = Collections.>of( Canonicalizer.forKey(AbstractJwk.ALGORITHM, "Algorithm"), @@ -55,7 +63,7 @@ public class DefaultJwkContext implements JwkContext { for (Canonicalizer fn : fns) { s.put(fn.getId(), fn); } - SETTERS = s; + SETTERS = java.util.Collections.unmodifiableMap(s); } private final Map values; @@ -67,46 +75,51 @@ public class DefaultJwkContext implements JwkContext { private Provider provider; public DefaultJwkContext() { + // For the default constructor case, we don't know how it will be used or what values will be populated, + // so we can't know ahead of time what the sensitive data is. As such, for security reasons, we assume all + // the known private names for all supported algorithms in case it is used for any of them: + this(DEFAULT_PRIVATE_NAMES); + } + + public DefaultJwkContext(Set privateMemberNames) { + this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty."); this.values = new LinkedHashMap<>(); this.canonicalValues = new LinkedHashMap<>(); this.redactedValues = new LinkedHashMap<>(); - this.privateMemberNames = Collections.emptySet(); } -// public DefaultJwkContext(JwkContext other) { -// //noinspection unchecked -// this(other, -// (Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance.")).privateMemberNames); -// } + public DefaultJwkContext(Set privateMemberNames, K key) { + this(privateMemberNames); + this.key = Assert.notNull(key, "Key cannot be null."); + } - public DefaultJwkContext(JwkContext other, Set privateMemberNames) { + public DefaultJwkContext(Set privateMemberNames, JwkContext other) { + this(privateMemberNames, other, true); + } + + public DefaultJwkContext(Set privateMemberNames, JwkContext other, K key) { + //if the key is null or a PublicKey, we don't want to redact - we want to fully remove the items that are + //private names (public JWKs should never contain any private key fields, even if redacted): + this(privateMemberNames, other, (key == null || key instanceof PublicKey)); + this.key = Assert.notNull(key, "Key cannot be null."); + } + + private DefaultJwkContext(Set privateMemberNames, JwkContext other, boolean removePrivate) { + this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty."); Assert.notNull(other, "JwkContext cannot be null."); Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance."); DefaultJwkContext src = (DefaultJwkContext) other; - this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty."); this.provider = other.getProvider(); this.values = new LinkedHashMap<>(src.values); - this.canonicalValues = new LinkedHashMap<>(src.values); - this.redactedValues = new LinkedHashMap<>(this.values); - - //if the key is a PublicKey, we don't even want to redact - we want to fully remove the items that are - //private names (public JWKs should never contain any private key fields, even if redacted): - final Key key = other.getKey(); - final boolean remove = (key == null || key instanceof PublicKey); - for (String name : this.privateMemberNames) { - if (remove) { + this.canonicalValues = new LinkedHashMap<>(src.canonicalValues); + this.redactedValues = new LinkedHashMap<>(src.redactedValues); + if (removePrivate) { + for (String name : this.privateMemberNames) { remove(name); - } else if (this.redactedValues.containsKey(name)) { //otherwise ensure redacted for toString calls: - this.redactedValues.put(name, AbstractJwk.REDACTED_VALUE); } } } - public DefaultJwkContext(JwkContext other, K key, Set privateMemberNames) { - this(other, privateMemberNames); - this.key = Assert.notNull(key, "Key cannot be null."); - } - protected Object nullSafePut(String name, Object value) { if (JwtMap.isReduceableToNull(value)) { return remove(name); @@ -140,11 +153,12 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void putAll(Map m) { + public JwkContext putAll(Map m) { Assert.notEmpty(m, "JWK values cannot be null or empty."); for (Map.Entry entry : m.entrySet()) { put(entry.getKey(), entry.getValue()); } + return this; } private Object remove(String key) { @@ -204,8 +218,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setAlgorithm(String algorithm) { + public JwkContext setAlgorithm(String algorithm) { put(AbstractJwk.ALGORITHM, algorithm); + return this; } @Override @@ -214,8 +229,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setId(String id) { + public JwkContext setId(String id) { put(AbstractJwk.ID, id); + return this; } @Override @@ -225,8 +241,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setOperations(Set ops) { + public JwkContext setOperations(Set ops) { put(AbstractJwk.OPERATIONS, ops); + return this; } @Override @@ -235,8 +252,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setType(String type) { + public JwkContext setType(String type) { put(AbstractJwk.TYPE, type); + return this; } @Override @@ -245,8 +263,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setPublicKeyUse(String use) { + public JwkContext setPublicKeyUse(String use) { put(AbstractAsymmetricJwk.PUBLIC_KEY_USE, use); + return this; } @Override @@ -256,8 +275,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setX509CertificateChain(List x5c) { + public JwkContext setX509CertificateChain(List x5c) { put(AbstractAsymmetricJwk.X509_CERT_CHAIN, x5c); + return this; } @Override @@ -266,8 +286,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setX509CertificateSha1Thumbprint(byte[] x5t) { + public JwkContext setX509CertificateSha1Thumbprint(byte[] x5t) { put(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT, x5t); + return this; } @Override @@ -276,8 +297,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setX509CertificateSha256Thumbprint(byte[] x5ts256) { + public JwkContext setX509CertificateSha256Thumbprint(byte[] x5ts256) { put(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT, x5ts256); + return this; } @Override @@ -286,8 +308,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setX509Url(URI url) { + public JwkContext setX509Url(URI url) { put(AbstractAsymmetricJwk.X509_URL, url); + return this; } @Override @@ -307,8 +330,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setPublicKey(PublicKey publicKey) { + public JwkContext setPublicKey(PublicKey publicKey) { this.publicKey = publicKey; + return this; } @Override @@ -317,8 +341,9 @@ public class DefaultJwkContext implements JwkContext { } @Override - public void setProvider(Provider provider) { + public JwkContext setProvider(Provider provider) { this.provider = provider; + return this; } @Override diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/Rsa15KeyAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java similarity index 66% rename from impl/src/main/java/io/jsonwebtoken/impl/security/Rsa15KeyAlgorithm.java rename to impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java index 6ddf3fe7..7f168dd2 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/Rsa15KeyAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java @@ -12,19 +12,20 @@ import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAKey; +import java.security.spec.AlgorithmParameterSpec; -public class Rsa15KeyAlgorithm extends CryptoAlgorithm implements EncryptedKeyAlgorithm { +public class DefaultRsaKeyAlgorithm extends CryptoAlgorithm + implements EncryptedKeyAlgorithm { - private static final String ID = "RSA1_5"; - private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding"; + private final AlgorithmParameterSpec SPEC; //can be null - public Rsa15KeyAlgorithm() { - super(ID, TRANSFORMATION); + public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString) { + this(id, jcaTransformationString, null); } - @Override - public String getId() { - return ID; + public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString, AlgorithmParameterSpec spec) { + super(id, jcaTransformationString); + this.SPEC = spec; //can be null } @Override @@ -36,7 +37,11 @@ public class Rsa15KeyAlgorithm() { @Override public byte[] doWithInstance(Cipher cipher) throws Exception { - cipher.init(Cipher.ENCRYPT_MODE, kek, ensureSecureRandom(request)); + if (SPEC == null) { + cipher.init(Cipher.WRAP_MODE, kek, ensureSecureRandom(request)); + } else { + cipher.init(Cipher.WRAP_MODE, kek, SPEC, ensureSecureRandom(request)); + } return cipher.wrap(cek); } }); @@ -53,11 +58,15 @@ public class Rsa15KeyAlgorithm() { @Override public SecretKey doWithInstance(Cipher cipher) throws Exception { - cipher.init(Cipher.UNWRAP_MODE, kek); + if (SPEC == null) { + cipher.init(Cipher.UNWRAP_MODE, kek); + } else { + cipher.init(Cipher.UNWRAP_MODE, kek, SPEC); + } Key key = cipher.unwrap(cekBytes, "AES", Cipher.SECRET_KEY); Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance."); return (SecretKey) key; } }); } -} +} \ No newline at end of file diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaSignatureAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaSignatureAlgorithm.java index 7eb935c4..a09ee038 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaSignatureAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaSignatureAlgorithm.java @@ -28,7 +28,6 @@ public class DefaultRsaSignatureAlgorithm pubCtx = new DefaultJwkContext<>(); - pubCtx.setKey(ecPublicKey); + JwkContext pubCtx = new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, ecPublicKey); EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx); ctx.putAll(pubJwk); // add public values to private key context @@ -62,8 +61,8 @@ class EcPrivateJwkFactory extends AbstractEcJwkFactory pubCtx = new DefaultJwkContext<>(ctx, DefaultEcPrivateJwk.PRIVATE_NAMES); - EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx); + JwkContext pubCtx = new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, ctx); + EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwk(pubCtx); ECParameterSpec spec = getCurveByJwaId(curveId); final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, spec); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkContext.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkContext.java index 46ff1089..c4020ba1 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkContext.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkContext.java @@ -34,41 +34,41 @@ public interface JwkContext extends Identifiable { Object put(String name, Object value); - void putAll(Map m); + JwkContext putAll(Map m); - void setId(String id); + JwkContext setId(String id); String getType(); - void setType(String type); + JwkContext setType(String type); Set getOperations(); - void setOperations(Set operations); + JwkContext setOperations(Set operations); String getAlgorithm(); - void setAlgorithm(String algorithm); + JwkContext setAlgorithm(String algorithm); String getPublicKeyUse(); - void setPublicKeyUse(String use); + JwkContext setPublicKeyUse(String use); URI getX509Url(); - void setX509Url(URI url); + JwkContext setX509Url(URI url); List getX509CertificateChain(); - void setX509CertificateChain(List x5c); + JwkContext setX509CertificateChain(List x5c); byte[] getX509CertificateSha1Thumbprint(); - void setX509CertificateSha1Thumbprint(byte[] x5t); + JwkContext setX509CertificateSha1Thumbprint(byte[] x5t); byte[] getX509CertificateSha256Thumbprint(); - void setX509CertificateSha256Thumbprint(byte[] x5ts256); + JwkContext setX509CertificateSha256Thumbprint(byte[] x5ts256); K getKey(); @@ -76,11 +76,11 @@ public interface JwkContext extends Identifiable { PublicKey getPublicKey(); - void setPublicKey(PublicKey publicKey); + JwkContext setPublicKey(PublicKey publicKey); Set getPrivateMemberNames(); Provider getProvider(); - void setProvider(Provider provider); + JwkContext setProvider(Provider provider); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/KeyAlgorithms.java b/impl/src/main/java/io/jsonwebtoken/impl/security/KeyAlgorithms.java new file mode 100644 index 00000000..74bf5810 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/KeyAlgorithms.java @@ -0,0 +1,71 @@ +package io.jsonwebtoken.impl.security; + +import io.jsonwebtoken.security.EncryptedKeyAlgorithm; +import io.jsonwebtoken.security.KeyAlgorithm; + +import javax.crypto.SecretKey; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; + +//bridge class for the API so it doesn't need to know implementation/constructor argument details +public final class KeyAlgorithms { + + private static final String RSA1_5_ID = "RSA1_5"; + private static final String RSA1_5_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; + private static final String RSA_OAEP_ID = "RSA-OAEP"; + private static final String RSA_OAEP_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; + private static final String RSA_OAEP_256_ID = "RSA-OAEP-256"; + private static final String RSA_OAEP_256_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; + private static final AlgorithmParameterSpec RSA_OAEP_256_SPEC = + new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); + + // prevent instantiation + private KeyAlgorithms() { + } + + public static KeyAlgorithm direct() { + return new DirectKeyAlgorithm(); + } + + public static EncryptedKeyAlgorithm a128kw() { + return new AesWrapKeyAlgorithm(128); + } + + public static EncryptedKeyAlgorithm a192kw() { + return new AesWrapKeyAlgorithm(192); + } + + public static EncryptedKeyAlgorithm a256kw() { + return new AesWrapKeyAlgorithm(256); + } + + public static EncryptedKeyAlgorithm a128gcmkw() { + return new AesGcmKeyAlgorithm(128); + } + + public static EncryptedKeyAlgorithm a192gcmkw() { + return new AesGcmKeyAlgorithm(192); + } + + public static EncryptedKeyAlgorithm a256gcmkw() { + return new AesGcmKeyAlgorithm(256); + } + + public static EncryptedKeyAlgorithm rsa1_5() { + return new DefaultRsaKeyAlgorithm<>(RSA1_5_ID, RSA1_5_TRANSFORMATION); + } + + public static EncryptedKeyAlgorithm rsaOaep() { + return new DefaultRsaKeyAlgorithm<>(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION); + } + + public static EncryptedKeyAlgorithm rsaOaep256() { + return new DefaultRsaKeyAlgorithm<>(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC); + } + +} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java b/impl/src/main/java/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java index 4f17f817..97441ee2 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java @@ -12,7 +12,6 @@ import io.jsonwebtoken.security.RsaPublicJwk; import io.jsonwebtoken.security.UnsupportedKeyException; import java.math.BigInteger; -import java.security.Key; import java.security.KeyFactory; import java.security.PublicKey; import java.security.interfaces.RSAMultiPrimePrivateCrtKey; @@ -95,9 +94,8 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory pubCtx = new DefaultJwkContext<>(); - pubCtx.setKey(rsaPublicKey); - RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx); + JwkContext pubCtx = new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, rsaPublicKey); + RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwk(pubCtx); ctx.putAll(pubJwk); // add public values to private key context ctx.put(DefaultRsaPrivateJwk.PRIVATE_EXPONENT, encode(key.getPrivateExponent())); @@ -133,7 +131,7 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory pubCtx = new DefaultJwkContext<>(ctx, DefaultRsaPrivateJwk.PRIVATE_NAMES); + JwkContext pubCtx = new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, ctx); RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx); RSAPublicKey pubKey = pubJwk.toKey(); final BigInteger modulus = pubKey.getModulus(); @@ -156,8 +154,6 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory ctx = new DefaultJwkContext<>(); + // Need to add the values to a Context instance to satisfy the API contract of the getRequired* methods + // called below. It's less than ideal, but it works: + JwkContext ctx = new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES); for (Map.Entry entry : m.entrySet()) { String name = String.valueOf(entry.getKey()); ctx.put(name, entry.getValue()); diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA1Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA1Test.groovy new file mode 100644 index 00000000..94e79581 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA1Test.groovy @@ -0,0 +1,165 @@ +package io.jsonwebtoken.impl.security + +import io.jsonwebtoken.Jwe +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.io.Decoders +import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.security.Jwks +import io.jsonwebtoken.security.RsaPrivateJwk +import org.junit.Test + +import javax.crypto.SecretKey +import javax.crypto.spec.SecretKeySpec +import java.nio.charset.StandardCharsets +import java.security.interfaces.RSAPrivateKey + +import static org.junit.Assert.assertArrayEquals +import static org.junit.Assert.assertEquals + +/** + * Tests successful parsing/decryption of a 'JWE using RSAES-OAEP and AES GCM' as defined in + * RFC 7516, Appendix A.1 + * + * @since JJWT_RELEASE_VERSION + */ +class RFC7516AppendixA1Test { + + static String encode(byte[] b) { + return Encoders.BASE64URL.encode(b) + } + + static byte[] decode(String val) { + return Decoders.BASE64URL.decode(val) + } + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1 : + final static String PLAINTEXT = 'The true sign of intelligence is not knowledge but imagination.' as String + final static byte[] PLAINTEXT_BYTES = [84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32, + 111, 102, 32, 105, 110, 116, 101, 108, 108, 105, 103, 101, 110, 99, + 101, 32, 105, 115, 32, 110, 111, 116, 32, 107, 110, 111, 119, 108, + 101, 100, 103, 101, 32, 98, 117, 116, 32, 105, 109, 97, 103, 105, + 110, 97, 116, 105, 111, 110, 46] as byte[] + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1.1 : + final static String PROT_HEADER_STRING = '{"alg":"RSA-OAEP","enc":"A256GCM"}' as String + final static String encodedHeader = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ' as String + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1.2 + final static byte[] CEK_BYTES = [177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, + 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, + 234, 64, 252] as byte[] + final static SecretKey CEK = new SecretKeySpec(CEK_BYTES, "AES") + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1.3 + final static Map KEK_VALUES = [ + "kty": "RSA", + "n" : "oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" + + "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" + + "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" + + "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" + + "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" + + "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", + "e" : "AQAB", + "d" : "kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" + + "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" + + "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" + + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + + "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", + "p" : "1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-" + + "SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf" + + "fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", + "q" : "wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm" + + "UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX" + + "IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", + "dp" : "ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL" + + "hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827" + + "rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", + "dq" : "Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj" + + "ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB" + + "UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", + "qi" : "VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7" + + "AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3" + + "eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" + ] + + final static byte[] ENCRYPTED_CEK_BYTES = [56, 163, 154, 192, 58, 53, 222, 4, 105, 218, 136, 218, 29, 94, 203, + 22, 150, 92, 129, 94, 211, 232, 53, 89, 41, 60, 138, 56, 196, 216, + 82, 98, 168, 76, 37, 73, 70, 7, 36, 8, 191, 100, 136, 196, 244, 220, + 145, 158, 138, 155, 4, 117, 141, 230, 199, 247, 173, 45, 182, 214, + 74, 177, 107, 211, 153, 11, 205, 196, 171, 226, 162, 128, 171, 182, + 13, 237, 239, 99, 193, 4, 91, 219, 121, 223, 107, 167, 61, 119, 228, + 173, 156, 137, 134, 200, 80, 219, 74, 253, 56, 185, 91, 177, 34, 158, + 89, 154, 205, 96, 55, 18, 138, 43, 96, 218, 215, 128, 124, 75, 138, + 243, 85, 25, 109, 117, 140, 26, 155, 249, 67, 167, 149, 231, 100, 6, + 41, 65, 214, 251, 232, 87, 72, 40, 182, 149, 154, 168, 31, 193, 126, + 215, 89, 28, 111, 219, 125, 182, 139, 235, 195, 197, 23, 234, 55, 58, + 63, 180, 68, 202, 206, 149, 75, 205, 248, 176, 67, 39, 178, 60, 98, + 193, 32, 238, 122, 96, 158, 222, 57, 183, 111, 210, 55, 188, 215, + 206, 180, 166, 150, 166, 106, 250, 55, 229, 72, 40, 69, 214, 216, + 104, 23, 40, 135, 212, 28, 127, 41, 80, 175, 174, 168, 115, 171, 197, + 89, 116, 92, 103, 246, 83, 216, 182, 176, 84, 37, 147, 35, 45, 219, + 172, 99, 226, 233, 73, 37, 124, 42, 72, 49, 242, 35, 127, 184, 134, + 117, 114, 135, 206] as byte[] + + final static String encodedEncryptedCek = 'OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe' + + 'ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb' + + 'Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV' + + 'mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8' + + '1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi' + + '6UklfCpIMfIjf7iGdXKHzg' as String + + // https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1.4 + final static byte[] IV = [227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219] as byte[] + final static String encodedIv = '48V1_ALb6US04U3b' as String + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1.5 + final static byte[] AAD_BYTES = [101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, + 116, 84, 48, 70, 70, 85, 67, 73, 115, 73, 109, 86, 117, 89, 121, 73, + 54, 73, 107, 69, 121, 78, 84, 90, 72, 81, 48, 48, 105, 102, 81] as byte[] + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1.6 + final static byte[] CIPHERTEXT = [229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122, + 233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111, + 104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32, + 123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205, + 160, 109, 64, 63, 192] as byte[] + final static String encodedCiphertext = + '5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A' as String + + final static byte[] TAG = [92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145] as byte[] + final static String encodedTag = 'XFBoMYUZodetZdvTiFvSkQ' + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1.7 + final static String COMPLETE_JWE = '' + + 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.' + + 'OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe' + + 'ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb' + + 'Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV' + + 'mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8' + + '1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi' + + '6UklfCpIMfIjf7iGdXKHzg.' + + '48V1_ALb6US04U3b.' + + '5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji' + + 'SdiwkIr3ajwQzaBtQD_A.' + + 'XFBoMYUZodetZdvTiFvSkQ' as String + + @Test + void test() { + //ensure our test constants are correctly copied and match the RFC values: + assertEquals PLAINTEXT, new String(PLAINTEXT_BYTES, StandardCharsets.UTF_8) + assertEquals PROT_HEADER_STRING, new String(decode(encodedHeader), StandardCharsets.UTF_8) + assertEquals encodedEncryptedCek, encode(ENCRYPTED_CEK_BYTES) + assertEquals encodedIv, encode(IV) + assertArrayEquals AAD_BYTES, encodedHeader.getBytes(StandardCharsets.US_ASCII) + assertArrayEquals CIPHERTEXT, decode(encodedCiphertext) + assertArrayEquals TAG, decode(encodedTag) + + //read the RFC Test JWK to get the private key for decrypting + RsaPrivateJwk jwk = Jwks.builder().putAll(KEK_VALUES).build() as RsaPrivateJwk + RSAPrivateKey privKey = jwk.toKey() + + Jwe jwe = Jwts.parserBuilder().decryptWith(privKey).build().parsePlaintextJwe(COMPLETE_JWE) + assertEquals PLAINTEXT, jwe.getBody() + } +} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA2Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA2Test.groovy new file mode 100644 index 00000000..aef01812 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA2Test.groovy @@ -0,0 +1,159 @@ +package io.jsonwebtoken.impl.security + +import io.jsonwebtoken.Jwe +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.io.Decoders +import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.security.Jwks +import io.jsonwebtoken.security.RsaPrivateJwk +import org.junit.Test + +import javax.crypto.SecretKey +import javax.crypto.spec.SecretKeySpec +import java.nio.charset.StandardCharsets +import java.security.interfaces.RSAPrivateKey + +import static org.junit.Assert.assertArrayEquals +import static org.junit.Assert.assertEquals + +/** + * Tests successful parsing/decryption of a 'JWE using RSAES-PKCS1-v1_5 and AES_128_CBC_HMAC_SHA_256' as defined in + * RFC 7516, Appendix A.2 + * + * @since JJWT_RELEASE_VERSION + */ +class RFC7516AppendixA2Test { + + static String encode(byte[] b) { + return Encoders.BASE64URL.encode(b) + } + + static byte[] decode(String val) { + return Decoders.BASE64URL.decode(val) + } + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2 : + final static String PLAINTEXT = 'Live long and prosper.' as String + final static byte[] PLAINTEXT_BYTES = [76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, + 112, 114, 111, 115, 112, 101, 114, 46] as byte[] + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.1 : + final static String PROT_HEADER_STRING = '{"alg":"RSA1_5","enc":"A128CBC-HS256"}' as String + final static String encodedHeader = 'eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0' as String + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.2 + final static byte[] CEK_BYTES = [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, + 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, + 44, 207] as byte[] + final static SecretKey CEK = new SecretKeySpec(CEK_BYTES, "AES") + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.3 + final static Map KEK_VALUES = [ + "kty": "RSA", + "n" : "sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1Wl" + + "UzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDpre" + + "cbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_" + + "7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBI" + + "Y2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU" + + "7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw", + "e" : "AQAB", + "d" : "VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq" + + "1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-ry" + + "nq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_" + + "0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj" + + "-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-Kyvj" + + "T1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ", + "p" : "9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68" + + "ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEP" + + "krdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM", + "q" : "uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-y" + + "BhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN" + + "-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0", + "dp" : "w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuv" + + "ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra" + + "Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs", + "dq" : "o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff" + + "7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_" + + "odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU", + "qi" : "eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC" + + "tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ" + + "B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo" + ] + + final static byte[] ENCRYPTED_CEK_BYTES = [80, 104, 72, 58, 11, 130, 236, 139, 132, 189, 255, 205, 61, 86, 151, + 176, 99, 40, 44, 233, 176, 189, 205, 70, 202, 169, 72, 40, 226, 181, + 156, 223, 120, 156, 115, 232, 150, 209, 145, 133, 104, 112, 237, 156, + 116, 250, 65, 102, 212, 210, 103, 240, 177, 61, 93, 40, 71, 231, 223, + 226, 240, 157, 15, 31, 150, 89, 200, 215, 198, 203, 108, 70, 117, 66, + 212, 238, 193, 205, 23, 161, 169, 218, 243, 203, 128, 214, 127, 253, + 215, 139, 43, 17, 135, 103, 179, 220, 28, 2, 212, 206, 131, 158, 128, + 66, 62, 240, 78, 186, 141, 125, 132, 227, 60, 137, 43, 31, 152, 199, + 54, 72, 34, 212, 115, 11, 152, 101, 70, 42, 219, 233, 142, 66, 151, + 250, 126, 146, 141, 216, 190, 73, 50, 177, 146, 5, 52, 247, 28, 197, + 21, 59, 170, 247, 181, 89, 131, 241, 169, 182, 246, 99, 15, 36, 102, + 166, 182, 172, 197, 136, 230, 120, 60, 58, 219, 243, 149, 94, 222, + 150, 154, 194, 110, 227, 225, 112, 39, 89, 233, 112, 207, 211, 241, + 124, 174, 69, 221, 179, 107, 196, 225, 127, 167, 112, 226, 12, 242, + 16, 24, 28, 120, 182, 244, 213, 244, 153, 194, 162, 69, 160, 244, + 248, 63, 165, 141, 4, 207, 249, 193, 79, 131, 0, 169, 233, 127, 167, + 101, 151, 125, 56, 112, 111, 248, 29, 232, 90, 29, 147, 110, 169, + 146, 114, 165, 204, 71, 136, 41, 252] as byte[] + + final static String encodedEncryptedCek = 'UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm' + + '1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc' + + 'HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF' + + 'NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8' + + 'rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv' + + '-B3oWh2TbqmScqXMR4gp_A' as String + + // https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.4 + final static byte[] IV = [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101] as byte[] + final static String encodedIv = 'AxY8DCtDaGlsbGljb3RoZQ' as String + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.5 + final static byte[] AAD_BYTES = [101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, + 120, 88, 122, 85, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, + 74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, + 50, 73, 110, 48] as byte[] + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.6 + final static byte[] CIPHERTEXT = [40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, + 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, + 112, 56, 102] as byte[] + final static String encodedCiphertext = 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY' as String + + final static byte[] TAG = [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191] as byte[] + final static String encodedTag = '9hH0vgRfYgPnAHOd8stkvw' + + // defined in https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2.7 + final static String COMPLETE_JWE = + "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm" + + "1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc" + + "HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF" + + "NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8" + + "rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv" + + "-B3oWh2TbqmScqXMR4gp_A." + + "AxY8DCtDaGlsbGljb3RoZQ." + + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + + "9hH0vgRfYgPnAHOd8stkvw" as String + + @Test + void test() { + //ensure our test constants are correctly copied and match the RFC values: + assertEquals PLAINTEXT, new String(PLAINTEXT_BYTES, StandardCharsets.UTF_8) + assertEquals PROT_HEADER_STRING, new String(decode(encodedHeader), StandardCharsets.UTF_8) + assertEquals encodedEncryptedCek, encode(ENCRYPTED_CEK_BYTES) + assertEquals encodedIv, encode(IV) + assertArrayEquals AAD_BYTES, encodedHeader.getBytes(StandardCharsets.US_ASCII) + assertArrayEquals CIPHERTEXT, decode(encodedCiphertext) + assertArrayEquals TAG, decode(encodedTag) + + //read the RFC Test JWK to get the private key for decrypting + RsaPrivateJwk jwk = Jwks.builder().putAll(KEK_VALUES).build() as RsaPrivateJwk + RSAPrivateKey privKey = jwk.toKey() + + Jwe jwe = Jwts.parserBuilder().decryptWith(privKey).build().parsePlaintextJwe(COMPLETE_JWE) + assertEquals PLAINTEXT, jwe.getBody() + } +} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes128CbcHmacSha256Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy similarity index 74% rename from impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes128CbcHmacSha256Test.groovy rename to impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy index 83988ebf..8fcb347e 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes128CbcHmacSha256Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy @@ -1,6 +1,6 @@ package io.jsonwebtoken.impl.security - +import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.security.EncryptionAlgorithms import org.junit.Test @@ -10,10 +10,12 @@ import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.assertArrayEquals /** - * Test case defined in https://tools.ietf.org/html/rfc7518#appendix-B.1 + * Tests successful encryption and decryption using 'AES_128_CBC_HMAC_SHA_256' as defined in + * RFC 7518, Appendix B.1 + * * @since JJWT_RELEASE_VERSION */ -class Aes128CbcHmacSha256Test { +class RFC7518AppendixB1Test { final byte[] K = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -88,6 +90,27 @@ class Aes128CbcHmacSha256Test { byte[] decryptionResult = alg.decrypt(dreq).getPayload() assertArrayEquals(P, decryptionResult) + + String encryptedCek = 'UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm' + + '1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc' + + 'HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF' + + 'NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8' + + 'rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv' + + '-B3oWh2TbqmScqXMR4gp_A' as String + + ciphertext = Decoders.BASE64URL.decode('KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY') + byte[] aad = Decoders.BASE64URL.decode('eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0') + tag = Decoders.BASE64URL.decode('9hH0vgRfYgPnAHOd8stkvw') + iv = Decoders.BASE64URL.decode('AxY8DCtDaGlsbGljb3RoZQ') + SecretKey key = new SecretKeySpec([4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, + 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, + 44, 207] as byte[], "AES") + + dreq = new DefaultAeadResult(null, null, ciphertext, key, aad, tag, iv) + + decryptionResult = alg.decrypt(dreq).getPayload() + + println decryptionResult } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes192CbcHmacSha384Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy similarity index 95% rename from impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes192CbcHmacSha384Test.groovy rename to impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy index 01fc79e9..ddd51e3d 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes192CbcHmacSha384Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy @@ -11,10 +11,12 @@ import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.assertArrayEquals /** - * Test case defined in https://tools.ietf.org/html/rfc7518#appendix-B.2 + * Tests successful encryption and decryption using 'AES_192_CBC_HMAC_SHA_384' as defined in + * RFC 7518, Appendix B.2 + * * @since JJWT_RELEASE_VERSION */ -class Aes192CbcHmacSha384Test { +class RFC7518AppendixB2Test { final byte[] K = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes256CbcHmacSha512Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy similarity index 95% rename from impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes256CbcHmacSha512Test.groovy rename to impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy index 67e6448c..bb98e907 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Aes256CbcHmacSha512Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy @@ -11,10 +11,12 @@ import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.assertArrayEquals /** - * Test case defined in https://tools.ietf.org/html/rfc7518#appendix-B.3 + * Tests successful encryption and decryption using 'AES_256_CBC_HMAC_SHA_512' as defined in + * RFC 7518, Appendix B.3 + * * @since JJWT_RELEASE_VERSION */ -class Aes256CbcHmacSha512Test { +class RFC7518AppendixB3Test { final byte[] K = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaesPkcsv15Aes128CbcHmacSha256Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaesPkcsv15Aes128CbcHmacSha256Test.groovy deleted file mode 100644 index be0ab6c0..00000000 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaesPkcsv15Aes128CbcHmacSha256Test.groovy +++ /dev/null @@ -1,78 +0,0 @@ -package io.jsonwebtoken.impl.security - -import io.jsonwebtoken.Jwts -import io.jsonwebtoken.lang.Maps -import io.jsonwebtoken.security.Jwk -import io.jsonwebtoken.security.Jwks -import io.jsonwebtoken.security.SignatureAlgorithms -import org.junit.Test - -import java.nio.charset.StandardCharsets -import java.security.Key -import java.security.interfaces.RSAPrivateKey - -/** - * Test case defined in RFC 7516, Appendix A.2 - * @since JJWT_RELEASE_VERSION - */ -class RsaesPkcsv15Aes128CbcHmacSha256Test { - - final static byte[] P = [76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, - 111, 115, 112, 101, 114, 46] as byte[] - - final static byte[] K = [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, - 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, - 44, 207] as byte[] - - final static Map kek = [ - "kty": "RSA", - "n" : "sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1Wl" + - "UzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDpre" + - "cbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_" + - "7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBI" + - "Y2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU" + - "7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw", - "e" : "AQAB", - "d" : "VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq" + - "1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-ry" + - "nq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_" + - "0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj" + - "-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-Kyvj" + - "T1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ", - "p" : "9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68" + - "ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEP" + - "krdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM", - "q" : "uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-y" + - "BhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN" + - "-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0", - "dp" : "w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuv" + - "ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra" + - "Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs", - "dq" : "o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff" + - "7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_" + - "odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU", - "qi" : "eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC" + - "tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ" + - "B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo" - ] - - @Test - void test() { - - def jwk = Jwks.builder().putAll(kek).build(); - - println "JWK: " + jwk.toString() - - final Map claims = Collections.unmodifiableMap(Maps.of('my-claim', 'my-map').build()); - final String subject = "admin"; - String jws = Jwts.builder() - .setSubject(subject) - .setClaims(claims) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + 30000L)) - .signWith(SignatureAlgorithms.HS512.generateKey()) - .compact(); - - println new String(P, StandardCharsets.UTF_8) - } -}