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)

This commit is contained in:
Les Hazlewood 2021-09-13 22:45:43 -07:00
parent 5819aa2f4b
commit f1cd8b99d7
18 changed files with 573 additions and 195 deletions

View File

@ -4,6 +4,8 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Classes;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -18,21 +20,22 @@ public final class KeyAlgorithms {
private KeyAlgorithms() { private KeyAlgorithms() {
} }
private static final String AESWRAP = "io.jsonwebtoken.impl.security.AesWrapKeyAlgorithm"; private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeyAlgorithms";
private static final String AESGCMWRAP = "io.jsonwebtoken.impl.security.AesGcmKeyAlgorithm";
private static final Class<?>[] AESWRAP_ARGS = new Class[]{int.class};
private static EncryptedKeyAlgorithm<SecretKey, SecretKey> aeswrap(String fqcn, int keyLength) { private static <T> T lookup(String methodName) {
return Classes.newInstance(fqcn, AESWRAP_ARGS, keyLength); return Classes.invokeStatic(BRIDGE_CLASSNAME, methodName, null, (Object[]) null);
} }
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = Classes.newInstance("io.jsonwebtoken.impl.security.DirectKeyAlgorithm"); public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = lookup("direct");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = aeswrap(AESWRAP, 128); public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = lookup("a128kw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = aeswrap(AESWRAP, 192); public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = lookup("a192kw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = aeswrap(AESWRAP, 256); public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = lookup("a256kw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128GCMKW = aeswrap(AESGCMWRAP, 128); public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128GCMKW = lookup("a128gcmkw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192GCMKW = aeswrap(AESGCMWRAP, 192); public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192GCMKW = lookup("a192gcmkw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256GCMKW = aeswrap(AESGCMWRAP, 256); public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256GCMKW = lookup("a256gcmkw");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA1_5 = lookup("rsa1_5");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP = lookup("rsaOaep");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP_256 = lookup("rsaOaep256");
private static Map<String,KeyAlgorithm<?,?>> toMap(KeyAlgorithm<?,?>... algs) { private static Map<String,KeyAlgorithm<?,?>> toMap(KeyAlgorithm<?,?>... algs) {
Map<String, KeyAlgorithm<?,?>> m = new LinkedHashMap<>(); Map<String, KeyAlgorithm<?,?>> m = new LinkedHashMap<>();
@ -43,7 +46,7 @@ public final class KeyAlgorithms {
} }
private static final Map<String,KeyAlgorithm<?,?>> STANDARD_ALGORITHMS = toMap( private static final Map<String,KeyAlgorithm<?,?>> 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<? extends KeyAlgorithm<?,?>> values() { public static Collection<? extends KeyAlgorithm<?,?>> values() {

View File

@ -9,19 +9,19 @@ import io.jsonwebtoken.impl.lang.Services;
import io.jsonwebtoken.impl.security.DefaultKeyRequest; import io.jsonwebtoken.impl.security.DefaultKeyRequest;
import io.jsonwebtoken.impl.security.DefaultSymmetricAeadRequest; import io.jsonwebtoken.impl.security.DefaultSymmetricAeadRequest;
import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.SerializationException;
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.AeadResult;
import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithms; import io.jsonwebtoken.security.KeyAlgorithms;
import io.jsonwebtoken.security.KeyRequest;
import io.jsonwebtoken.security.KeyResult; 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 io.jsonwebtoken.security.SymmetricAeadRequest;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -149,29 +149,30 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
byte[] encryptedCek = Assert.notNull(keyResult.getPayload(), "KeyResult must return an encrypted key byte array, even if empty."); byte[] encryptedCek = Assert.notNull(keyResult.getPayload(), "KeyResult must return an encrypted key byte array, even if empty.");
jweHeader.putAll(keyResult.getHeaderParams()); jweHeader.putAll(keyResult.getHeaderParams());
jweHeader.setEncryptionAlgorithm(enc.getId());
jweHeader.setAlgorithm(alg.getId()); jweHeader.setAlgorithm(alg.getId());
jweHeader.setEncryptionAlgorithm(enc.getId());
byte[] headerBytes = this.headerSerializer.apply(jweHeader); 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); AeadResult encResult = encFunction.apply(encRequest);
byte[] iv = Assert.notEmpty(encResult.getInitializationVector(), "Encryption result must have a non-empty initialization vector."); 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[] 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."); byte[] tag = Assert.notEmpty(encResult.getAuthenticationTag(), "Encryption result must have a non-empty authentication tag.");
String base64UrlEncodedHeader = base64UrlEncoder.encode(headerBytes); String base64UrlEncodedEncryptedCek = base64UrlEncoder.encode(encryptedCek);
String base64UrlEncodedEncryptedKey = base64UrlEncoder.encode(encryptedCek);
String base64UrlEncodedIv = base64UrlEncoder.encode(iv); String base64UrlEncodedIv = base64UrlEncoder.encode(iv);
String base64UrlEncodedCiphertext = base64UrlEncoder.encode(ciphertext); String base64UrlEncodedCiphertext = base64UrlEncoder.encode(ciphertext);
String base64UrlEncodedAad = base64UrlEncoder.encode(tag); String base64UrlEncodedTag = base64UrlEncoder.encode(tag);
return return
base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR +
base64UrlEncodedEncryptedKey + JwtParser.SEPARATOR_CHAR + base64UrlEncodedEncryptedCek + JwtParser.SEPARATOR_CHAR +
base64UrlEncodedIv + JwtParser.SEPARATOR_CHAR + base64UrlEncodedIv + JwtParser.SEPARATOR_CHAR +
base64UrlEncodedCiphertext + JwtParser.SEPARATOR_CHAR + base64UrlEncodedCiphertext + JwtParser.SEPARATOR_CHAR +
base64UrlEncodedAad; base64UrlEncodedTag;
} }
} }

View File

@ -40,8 +40,8 @@ import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver; import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.lang.LegacyServices; import io.jsonwebtoken.impl.lang.LegacyServices;
import io.jsonwebtoken.impl.security.DefaultKeyRequest;
import io.jsonwebtoken.impl.security.DefaultAeadResult; import io.jsonwebtoken.impl.security.DefaultAeadResult;
import io.jsonwebtoken.impl.security.DefaultKeyRequest;
import io.jsonwebtoken.impl.security.DefaultVerifySignatureRequest; import io.jsonwebtoken.impl.security.DefaultVerifySignatureRequest;
import io.jsonwebtoken.impl.security.DelegatingSigningKeyResolver; import io.jsonwebtoken.impl.security.DelegatingSigningKeyResolver;
import io.jsonwebtoken.impl.security.StaticKeyResolver; import io.jsonwebtoken.impl.security.StaticKeyResolver;
@ -346,6 +346,8 @@ public class DefaultJwtParser implements JwtParser {
throw new MalformedJwtException(msg); throw new MalformedJwtException(msg);
} }
final byte[] aad = base64UrlHeader.getBytes(StandardCharsets.US_ASCII);
base64Url = tokenizedJwe.getDigest(); base64Url = tokenizedJwe.getDigest();
if (Strings.hasText(base64Url)) { if (Strings.hasText(base64Url)) {
tag = base64UrlDecode(base64Url, "JWE AAD Authentication Tag"); tag = base64UrlDecode(base64Url, "JWE AAD Authentication Tag");
@ -373,7 +375,7 @@ public class DefaultJwtParser implements JwtParser {
final SecretKey cek = keyAlg.getDecryptionKey(request); final SecretKey cek = keyAlg.getDecryptionKey(request);
SymmetricAeadDecryptionRequest decryptRequest = SymmetricAeadDecryptionRequest decryptRequest =
new DefaultAeadResult(this.provider, null, bytes, cek, headerBytes, tag, iv); new DefaultAeadResult(this.provider, null, bytes, cek, aad, tag, iv);
PayloadSupplier<byte[]> result = encAlg.decrypt(decryptRequest); PayloadSupplier<byte[]> result = encAlg.decrypt(decryptRequest);
bytes = result.getPayload(); bytes = result.getPayload();
} }

View File

@ -51,7 +51,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
} }
AbstractAsymmetricJwkBuilder(AbstractAsymmetricJwkBuilder<?, ?, ?> b, K key, Set<String> privateNames) { AbstractAsymmetricJwkBuilder(AbstractAsymmetricJwkBuilder<?, ?, ?> b, K key, Set<String> privateNames) {
super(new DefaultJwkContext<>(b.jwkContext, key, privateNames)); super(new DefaultJwkContext<>(privateNames, b.jwkContext, key));
this.computeX509Sha1Thumbprint = b.computeX509Sha1Thumbprint; this.computeX509Sha1Thumbprint = b.computeX509Sha1Thumbprint;
this.computeX509Sha256Thumbprint = b.computeX509Sha256Thumbprint; this.computeX509Sha256Thumbprint = b.computeX509Sha256Thumbprint;
this.applyX509KeyUse = b.applyX509KeyUse; this.applyX509KeyUse = b.applyX509KeyUse;
@ -154,7 +154,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
extends AbstractAsymmetricJwkBuilder<K, J, T> extends AbstractAsymmetricJwkBuilder<K, J, T>
implements PublicJwkBuilder<K, L, J, M, P, T> { implements PublicJwkBuilder<K, L, J, M, P, T> {
public DefaultPublicJwkBuilder(JwkContext<K> ctx) { DefaultPublicJwkBuilder(JwkContext<K> ctx) {
super(ctx); super(ctx);
} }
@ -194,8 +194,8 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
extends DefaultPublicJwkBuilder<ECPublicKey, ECPrivateKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder, EcPublicJwkBuilder> extends DefaultPublicJwkBuilder<ECPublicKey, ECPrivateKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder, EcPublicJwkBuilder>
implements EcPublicJwkBuilder { implements EcPublicJwkBuilder {
public DefaultEcPublicJwkBuilder(JwkContext<?> src, ECPublicKey key) { DefaultEcPublicJwkBuilder(JwkContext<?> src, ECPublicKey key) {
super(new DefaultJwkContext<>(src, key, DefaultEcPrivateJwk.PRIVATE_NAMES)); super(new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, src, key));
} }
@Override @Override
@ -209,7 +209,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
implements RsaPublicJwkBuilder { implements RsaPublicJwkBuilder {
DefaultRsaPublicJwkBuilder(JwkContext<?> ctx, RSAPublicKey key) { DefaultRsaPublicJwkBuilder(JwkContext<?> ctx, RSAPublicKey key) {
super(new DefaultJwkContext<>(ctx, key, DefaultRsaPrivateJwk.PRIVATE_NAMES)); super(new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, ctx, key));
} }
@Override @Override
@ -223,7 +223,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
implements EcPrivateJwkBuilder { implements EcPrivateJwkBuilder {
DefaultEcPrivateJwkBuilder(JwkContext<?> src, ECPrivateKey key) { DefaultEcPrivateJwkBuilder(JwkContext<?> src, ECPrivateKey key) {
super(new DefaultJwkContext<>(src, key, DefaultEcPrivateJwk.PRIVATE_NAMES)); super(new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, src, key));
} }
DefaultEcPrivateJwkBuilder(DefaultEcPublicJwkBuilder b, ECPrivateKey key) { DefaultEcPrivateJwkBuilder(DefaultEcPublicJwkBuilder b, ECPrivateKey key) {
@ -236,7 +236,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
implements RsaPrivateJwkBuilder { implements RsaPrivateJwkBuilder {
DefaultRsaPrivateJwkBuilder(JwkContext<?> src, RSAPrivateKey key) { DefaultRsaPrivateJwkBuilder(JwkContext<?> src, RSAPrivateKey key) {
super(new DefaultJwkContext<>(src, key, DefaultRsaPrivateJwk.PRIVATE_NAMES)); super(new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, src, key));
} }
DefaultRsaPrivateJwkBuilder(DefaultRsaPublicJwkBuilder b, RSAPrivateKey key) { DefaultRsaPrivateJwkBuilder(DefaultRsaPublicJwkBuilder b, RSAPrivateKey key) {

View File

@ -90,7 +90,7 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
static class DefaultSecretJwkBuilder extends AbstractJwkBuilder<SecretKey, SecretJwk, SecretJwkBuilder> static class DefaultSecretJwkBuilder extends AbstractJwkBuilder<SecretKey, SecretJwk, SecretJwkBuilder>
implements SecretJwkBuilder { implements SecretJwkBuilder {
public DefaultSecretJwkBuilder(JwkContext<?> ctx, SecretKey key) { public DefaultSecretJwkBuilder(JwkContext<?> ctx, SecretKey key) {
super(new DefaultJwkContext<>(ctx, key, DefaultSecretJwk.PRIVATE_NAMES)); super(new DefaultJwkContext<>(DefaultSecretJwk.PRIVATE_NAMES, ctx, key));
} }
} }
} }

View File

@ -21,6 +21,7 @@ import java.security.PublicKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -36,9 +37,16 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
private static final Converter<URI, Object> URI_CONVERTER = private static final Converter<URI, Object> URI_CONVERTER =
Converters.forEncoded(URI.class, new UriStringConverter()); Converters.forEncoded(URI.class, new UriStringConverter());
private static final Set<String> DEFAULT_PRIVATE_NAMES;
private static final Map<String, Canonicalizer<?>> SETTERS; private static final Map<String, Canonicalizer<?>> SETTERS;
static { static {
Set<String> 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") @SuppressWarnings("RedundantTypeArguments")
List<Canonicalizer<?>> fns = Collections.<Canonicalizer<?>>of( List<Canonicalizer<?>> fns = Collections.<Canonicalizer<?>>of(
Canonicalizer.forKey(AbstractJwk.ALGORITHM, "Algorithm"), Canonicalizer.forKey(AbstractJwk.ALGORITHM, "Algorithm"),
@ -55,7 +63,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
for (Canonicalizer<?> fn : fns) { for (Canonicalizer<?> fn : fns) {
s.put(fn.getId(), fn); s.put(fn.getId(), fn);
} }
SETTERS = s; SETTERS = java.util.Collections.unmodifiableMap(s);
} }
private final Map<String, Object> values; private final Map<String, Object> values;
@ -67,46 +75,51 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
private Provider provider; private Provider provider;
public DefaultJwkContext() { 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<String> privateMemberNames) {
this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty.");
this.values = new LinkedHashMap<>(); this.values = new LinkedHashMap<>();
this.canonicalValues = new LinkedHashMap<>(); this.canonicalValues = new LinkedHashMap<>();
this.redactedValues = new LinkedHashMap<>(); this.redactedValues = new LinkedHashMap<>();
this.privateMemberNames = Collections.emptySet();
} }
// public DefaultJwkContext(JwkContext<?> other) { public DefaultJwkContext(Set<String> privateMemberNames, K key) {
// //noinspection unchecked this(privateMemberNames);
// this(other, this.key = Assert.notNull(key, "Key cannot be null.");
// (Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance.")).privateMemberNames); }
// }
public DefaultJwkContext(JwkContext<?> other, Set<String> privateMemberNames) { public DefaultJwkContext(Set<String> privateMemberNames, JwkContext<?> other) {
this(privateMemberNames, other, true);
}
public DefaultJwkContext(Set<String> 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<String> privateMemberNames, JwkContext<?> other, boolean removePrivate) {
this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty.");
Assert.notNull(other, "JwkContext cannot be null."); Assert.notNull(other, "JwkContext cannot be null.");
Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance."); Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance.");
DefaultJwkContext<?> src = (DefaultJwkContext<?>) other; DefaultJwkContext<?> src = (DefaultJwkContext<?>) other;
this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty.");
this.provider = other.getProvider(); this.provider = other.getProvider();
this.values = new LinkedHashMap<>(src.values); this.values = new LinkedHashMap<>(src.values);
this.canonicalValues = new LinkedHashMap<>(src.values); this.canonicalValues = new LinkedHashMap<>(src.canonicalValues);
this.redactedValues = new LinkedHashMap<>(this.values); this.redactedValues = new LinkedHashMap<>(src.redactedValues);
if (removePrivate) {
//if the key is a PublicKey, we don't even want to redact - we want to fully remove the items that are for (String name : this.privateMemberNames) {
//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) {
remove(name); 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<String> privateMemberNames) {
this(other, privateMemberNames);
this.key = Assert.notNull(key, "Key cannot be null.");
}
protected Object nullSafePut(String name, Object value) { protected Object nullSafePut(String name, Object value) {
if (JwtMap.isReduceableToNull(value)) { if (JwtMap.isReduceableToNull(value)) {
return remove(name); return remove(name);
@ -140,11 +153,12 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void putAll(Map<? extends String, ?> m) { public JwkContext<K> putAll(Map<? extends String, ?> m) {
Assert.notEmpty(m, "JWK values cannot be null or empty."); Assert.notEmpty(m, "JWK values cannot be null or empty.");
for (Map.Entry<? extends String, ?> entry : m.entrySet()) { for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue()); put(entry.getKey(), entry.getValue());
} }
return this;
} }
private Object remove(String key) { private Object remove(String key) {
@ -204,8 +218,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setAlgorithm(String algorithm) { public JwkContext<K> setAlgorithm(String algorithm) {
put(AbstractJwk.ALGORITHM, algorithm); put(AbstractJwk.ALGORITHM, algorithm);
return this;
} }
@Override @Override
@ -214,8 +229,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setId(String id) { public JwkContext<K> setId(String id) {
put(AbstractJwk.ID, id); put(AbstractJwk.ID, id);
return this;
} }
@Override @Override
@ -225,8 +241,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setOperations(Set<String> ops) { public JwkContext<K> setOperations(Set<String> ops) {
put(AbstractJwk.OPERATIONS, ops); put(AbstractJwk.OPERATIONS, ops);
return this;
} }
@Override @Override
@ -235,8 +252,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setType(String type) { public JwkContext<K> setType(String type) {
put(AbstractJwk.TYPE, type); put(AbstractJwk.TYPE, type);
return this;
} }
@Override @Override
@ -245,8 +263,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setPublicKeyUse(String use) { public JwkContext<K> setPublicKeyUse(String use) {
put(AbstractAsymmetricJwk.PUBLIC_KEY_USE, use); put(AbstractAsymmetricJwk.PUBLIC_KEY_USE, use);
return this;
} }
@Override @Override
@ -256,8 +275,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setX509CertificateChain(List<X509Certificate> x5c) { public JwkContext<K> setX509CertificateChain(List<X509Certificate> x5c) {
put(AbstractAsymmetricJwk.X509_CERT_CHAIN, x5c); put(AbstractAsymmetricJwk.X509_CERT_CHAIN, x5c);
return this;
} }
@Override @Override
@ -266,8 +286,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setX509CertificateSha1Thumbprint(byte[] x5t) { public JwkContext<K> setX509CertificateSha1Thumbprint(byte[] x5t) {
put(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT, x5t); put(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT, x5t);
return this;
} }
@Override @Override
@ -276,8 +297,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setX509CertificateSha256Thumbprint(byte[] x5ts256) { public JwkContext<K> setX509CertificateSha256Thumbprint(byte[] x5ts256) {
put(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT, x5ts256); put(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT, x5ts256);
return this;
} }
@Override @Override
@ -286,8 +308,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setX509Url(URI url) { public JwkContext<K> setX509Url(URI url) {
put(AbstractAsymmetricJwk.X509_URL, url); put(AbstractAsymmetricJwk.X509_URL, url);
return this;
} }
@Override @Override
@ -307,8 +330,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setPublicKey(PublicKey publicKey) { public JwkContext<K> setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey; this.publicKey = publicKey;
return this;
} }
@Override @Override
@ -317,8 +341,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
} }
@Override @Override
public void setProvider(Provider provider) { public JwkContext<K> setProvider(Provider provider) {
this.provider = provider; this.provider = provider;
return this;
} }
@Override @Override

View File

@ -12,19 +12,20 @@ import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.RSAKey; import java.security.interfaces.RSAKey;
import java.security.spec.AlgorithmParameterSpec;
public class Rsa15KeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> extends CryptoAlgorithm implements EncryptedKeyAlgorithm<EK, DK> { public class DefaultRsaKeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> extends CryptoAlgorithm
implements EncryptedKeyAlgorithm<EK, DK> {
private static final String ID = "RSA1_5"; private final AlgorithmParameterSpec SPEC; //can be null
private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";
public Rsa15KeyAlgorithm() { public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString) {
super(ID, TRANSFORMATION); this(id, jcaTransformationString, null);
} }
@Override public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString, AlgorithmParameterSpec spec) {
public String getId() { super(id, jcaTransformationString);
return ID; this.SPEC = spec; //can be null
} }
@Override @Override
@ -36,7 +37,11 @@ public class Rsa15KeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RSAKey
byte[] ciphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() { byte[] ciphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
@Override @Override
public byte[] doWithInstance(Cipher cipher) throws Exception { 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); return cipher.wrap(cek);
} }
}); });
@ -53,7 +58,11 @@ public class Rsa15KeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RSAKey
return execute(request, Cipher.class, new InstanceCallback<Cipher, SecretKey>() { return execute(request, Cipher.class, new InstanceCallback<Cipher, SecretKey>() {
@Override @Override
public SecretKey doWithInstance(Cipher cipher) throws Exception { 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); Key key = cipher.unwrap(cekBytes, "AES", Cipher.SECRET_KEY);
Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance."); Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance.");
return (SecretKey) key; return (SecretKey) key;

View File

@ -28,7 +28,6 @@ public class DefaultRsaSignatureAlgorithm<SK extends RSAKey & PrivateKey, VK ext
private static AlgorithmParameterSpec pssParamFromSaltBitLength(int saltBitLength) { private static AlgorithmParameterSpec pssParamFromSaltBitLength(int saltBitLength) {
MGF1ParameterSpec ps = new MGF1ParameterSpec("SHA-" + saltBitLength); MGF1ParameterSpec ps = new MGF1ParameterSpec("SHA-" + saltBitLength);
//MGF1ParameterSpec ps = MGF1ParameterSpec.SHA256;
int saltByteLength = saltBitLength / Byte.SIZE; int saltByteLength = saltBitLength / Byte.SIZE;
return new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, saltByteLength, 1); return new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, saltByteLength, 1);
} }

View File

@ -41,8 +41,7 @@ class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJw
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2) // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
// requires public values to be present in private JWKs, so add them: // requires public values to be present in private JWKs, so add them:
JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>(); JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, ecPublicKey);
pubCtx.setKey(ecPublicKey);
EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx); EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx);
ctx.putAll(pubJwk); // add public values to private key context ctx.putAll(pubJwk); // add public values to private key context
@ -62,8 +61,8 @@ class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJw
// We don't actually need the public x,y point coordinates for JVM lookup, but the // We don't actually need the public x,y point coordinates for JVM lookup, but the
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2) // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
// requires them to be present and valid for the private key as well, so we assert that here: // requires them to be present and valid for the private key as well, so we assert that here:
JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>(ctx, DefaultEcPrivateJwk.PRIVATE_NAMES); JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>(DefaultEcPrivateJwk.PRIVATE_NAMES, ctx);
EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx); EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwk(pubCtx);
ECParameterSpec spec = getCurveByJwaId(curveId); ECParameterSpec spec = getCurveByJwaId(curveId);
final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, spec); final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, spec);

View File

@ -34,41 +34,41 @@ public interface JwkContext<K extends Key> extends Identifiable {
Object put(String name, Object value); Object put(String name, Object value);
void putAll(Map<? extends String, ?> m); JwkContext<K> putAll(Map<? extends String, ?> m);
void setId(String id); JwkContext<K> setId(String id);
String getType(); String getType();
void setType(String type); JwkContext<K> setType(String type);
Set<String> getOperations(); Set<String> getOperations();
void setOperations(Set<String> operations); JwkContext<K> setOperations(Set<String> operations);
String getAlgorithm(); String getAlgorithm();
void setAlgorithm(String algorithm); JwkContext<K> setAlgorithm(String algorithm);
String getPublicKeyUse(); String getPublicKeyUse();
void setPublicKeyUse(String use); JwkContext<K> setPublicKeyUse(String use);
URI getX509Url(); URI getX509Url();
void setX509Url(URI url); JwkContext<K> setX509Url(URI url);
List<X509Certificate> getX509CertificateChain(); List<X509Certificate> getX509CertificateChain();
void setX509CertificateChain(List<X509Certificate> x5c); JwkContext<K> setX509CertificateChain(List<X509Certificate> x5c);
byte[] getX509CertificateSha1Thumbprint(); byte[] getX509CertificateSha1Thumbprint();
void setX509CertificateSha1Thumbprint(byte[] x5t); JwkContext<K> setX509CertificateSha1Thumbprint(byte[] x5t);
byte[] getX509CertificateSha256Thumbprint(); byte[] getX509CertificateSha256Thumbprint();
void setX509CertificateSha256Thumbprint(byte[] x5ts256); JwkContext<K> setX509CertificateSha256Thumbprint(byte[] x5ts256);
K getKey(); K getKey();
@ -76,11 +76,11 @@ public interface JwkContext<K extends Key> extends Identifiable {
PublicKey getPublicKey(); PublicKey getPublicKey();
void setPublicKey(PublicKey publicKey); JwkContext<K> setPublicKey(PublicKey publicKey);
Set<String> getPrivateMemberNames(); Set<String> getPrivateMemberNames();
Provider getProvider(); Provider getProvider();
void setProvider(Provider provider); JwkContext<K> setProvider(Provider provider);
} }

View File

@ -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<SecretKey, SecretKey> direct() {
return new DirectKeyAlgorithm();
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a128kw() {
return new AesWrapKeyAlgorithm(128);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a192kw() {
return new AesWrapKeyAlgorithm(192);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a256kw() {
return new AesWrapKeyAlgorithm(256);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a128gcmkw() {
return new AesGcmKeyAlgorithm(128);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a192gcmkw() {
return new AesGcmKeyAlgorithm(192);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a256gcmkw() {
return new AesGcmKeyAlgorithm(256);
}
public static <EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> EncryptedKeyAlgorithm<EK, DK> rsa1_5() {
return new DefaultRsaKeyAlgorithm<>(RSA1_5_ID, RSA1_5_TRANSFORMATION);
}
public static <EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> EncryptedKeyAlgorithm<EK, DK> rsaOaep() {
return new DefaultRsaKeyAlgorithm<>(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION);
}
public static <EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> EncryptedKeyAlgorithm<EK, DK> rsaOaep256() {
return new DefaultRsaKeyAlgorithm<>(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC);
}
}

View File

@ -12,7 +12,6 @@ import io.jsonwebtoken.security.RsaPublicJwk;
import io.jsonwebtoken.security.UnsupportedKeyException; import io.jsonwebtoken.security.UnsupportedKeyException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.RSAMultiPrimePrivateCrtKey; import java.security.interfaces.RSAMultiPrimePrivateCrtKey;
@ -95,9 +94,8 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
// The [JWA Spec](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1) // The [JWA Spec](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1)
// requires public values to be present in private JWKs, so add them: // requires public values to be present in private JWKs, so add them:
JwkContext<RSAPublicKey> pubCtx = new DefaultJwkContext<>(); JwkContext<RSAPublicKey> pubCtx = new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, rsaPublicKey);
pubCtx.setKey(rsaPublicKey); RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwk(pubCtx);
RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx);
ctx.putAll(pubJwk); // add public values to private key context ctx.putAll(pubJwk); // add public values to private key context
ctx.put(DefaultRsaPrivateJwk.PRIVATE_EXPONENT, encode(key.getPrivateExponent())); ctx.put(DefaultRsaPrivateJwk.PRIVATE_EXPONENT, encode(key.getPrivateExponent()));
@ -133,7 +131,7 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
//The [JWA Spec, Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2) requires //The [JWA Spec, Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2) requires
//RSA Private Keys to also encode the public key values, so we assert that we can acquire it successfully: //RSA Private Keys to also encode the public key values, so we assert that we can acquire it successfully:
JwkContext<RSAPublicKey> pubCtx = new DefaultJwkContext<>(ctx, DefaultRsaPrivateJwk.PRIVATE_NAMES); JwkContext<RSAPublicKey> pubCtx = new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES, ctx);
RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx); RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx);
RSAPublicKey pubKey = pubJwk.toKey(); RSAPublicKey pubKey = pubJwk.toKey();
final BigInteger modulus = pubKey.getModulus(); final BigInteger modulus = pubKey.getModulus();
@ -156,8 +154,6 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
KeySpec spec; KeySpec spec;
if (containsOptional) { //if any one optional field exists, they are all required per JWA Section 6.3.2: if (containsOptional) { //if any one optional field exists, they are all required per JWA Section 6.3.2:
BigInteger firstPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, true); BigInteger firstPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, true);
BigInteger secondPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, true); BigInteger secondPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, true);
@ -222,9 +218,9 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
throw new MalformedKeyException("RSA JWK 'oth' Other Prime Info element map cannot be empty."); throw new MalformedKeyException("RSA JWK 'oth' Other Prime Info element map cannot be empty.");
} }
// Need to do add the values to a Context instance to satisfy the API contract of the getRequired* methods // Need to add the values to a Context instance to satisfy the API contract of the getRequired* methods
// below. It's less than ideal, but it works: // called below. It's less than ideal, but it works:
JwkContext<?> ctx = new DefaultJwkContext<>(); JwkContext<?> ctx = new DefaultJwkContext<>(DefaultRsaPrivateJwk.PRIVATE_NAMES);
for (Map.Entry<?, ?> entry : m.entrySet()) { for (Map.Entry<?, ?> entry : m.entrySet()) {
String name = String.valueOf(entry.getKey()); String name = String.valueOf(entry.getKey());
ctx.put(name, entry.getValue()); ctx.put(name, entry.getValue());

View File

@ -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
* <a href="https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.1">RFC 7516, Appendix A.1</a>
*
* @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<String, String> 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<String> jwe = Jwts.parserBuilder().decryptWith(privKey).build().parsePlaintextJwe(COMPLETE_JWE)
assertEquals PLAINTEXT, jwe.getBody()
}
}

View File

@ -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
* <a href="https://datatracker.ietf.org/doc/html/rfc7516#appendix-A.2">RFC 7516, Appendix A.2</a>
*
* @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<String, String> 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<String> jwe = Jwts.parserBuilder().decryptWith(privKey).build().parsePlaintextJwe(COMPLETE_JWE)
assertEquals PLAINTEXT, jwe.getBody()
}
}

View File

@ -1,6 +1,6 @@
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.EncryptionAlgorithms import io.jsonwebtoken.security.EncryptionAlgorithms
import org.junit.Test import org.junit.Test
@ -10,10 +10,12 @@ import javax.crypto.spec.SecretKeySpec
import static org.junit.Assert.assertArrayEquals 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
* <a href="https://tools.ietf.org/html/rfc7518#appendix-B.1">RFC 7518, Appendix B.1</a>
*
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
class Aes128CbcHmacSha256Test { class RFC7518AppendixB1Test {
final byte[] K = final byte[] K =
[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, [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() byte[] decryptionResult = alg.decrypt(dreq).getPayload()
assertArrayEquals(P, decryptionResult) 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
} }
} }

View File

@ -11,10 +11,12 @@ import javax.crypto.spec.SecretKeySpec
import static org.junit.Assert.assertArrayEquals 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
* <a href="https://tools.ietf.org/html/rfc7518#appendix-B.2">RFC 7518, Appendix B.2</a>
*
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
class Aes192CbcHmacSha384Test { class RFC7518AppendixB2Test {
final byte[] K = final byte[] K =
[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,

View File

@ -11,10 +11,12 @@ import javax.crypto.spec.SecretKeySpec
import static org.junit.Assert.assertArrayEquals 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
* <a href="https://tools.ietf.org/html/rfc7518#appendix-B.3">RFC 7518, Appendix B.3</a>
*
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
class Aes256CbcHmacSha512Test { class RFC7518AppendixB3Test {
final byte[] K = final byte[] K =
[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,

View File

@ -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 <a href="https://tools.ietf.org/html/rfc7516#appendix-A.2">RFC 7516, Appendix A.2</a>
* @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<String, String> 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<String, Object> 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)
}
}