mirror of https://github.com/jwtk/jjwt.git
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:
parent
5819aa2f4b
commit
f1cd8b99d7
|
@ -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<SecretKey, SecretKey> aeswrap(String fqcn, int keyLength) {
|
||||
return Classes.newInstance(fqcn, AESWRAP_ARGS, keyLength);
|
||||
private static <T> T lookup(String methodName) {
|
||||
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 EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = aeswrap(AESWRAP, 128);
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = aeswrap(AESWRAP, 192);
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = aeswrap(AESWRAP, 256);
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128GCMKW = aeswrap(AESGCMWRAP, 128);
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192GCMKW = aeswrap(AESGCMWRAP, 192);
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256GCMKW = aeswrap(AESGCMWRAP, 256);
|
||||
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = lookup("direct");
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = lookup("a128kw");
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = lookup("a192kw");
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = lookup("a256kw");
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128GCMKW = lookup("a128gcmkw");
|
||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192GCMKW = lookup("a192gcmkw");
|
||||
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) {
|
||||
Map<String, KeyAlgorithm<?,?>> m = new LinkedHashMap<>();
|
||||
|
@ -43,7 +46,7 @@ public final class KeyAlgorithms {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
|
|
@ -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<JweBuilder> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<byte[]> result = encAlg.decrypt(decryptRequest);
|
||||
bytes = result.getPayload();
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
|
|||
}
|
||||
|
||||
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.computeX509Sha256Thumbprint = b.computeX509Sha256Thumbprint;
|
||||
this.applyX509KeyUse = b.applyX509KeyUse;
|
||||
|
@ -154,7 +154,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
|
|||
extends AbstractAsymmetricJwkBuilder<K, J, T>
|
||||
implements PublicJwkBuilder<K, L, J, M, P, T> {
|
||||
|
||||
public DefaultPublicJwkBuilder(JwkContext<K> ctx) {
|
||||
DefaultPublicJwkBuilder(JwkContext<K> ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
|
@ -194,8 +194,8 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
|
|||
extends DefaultPublicJwkBuilder<ECPublicKey, ECPrivateKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder, EcPublicJwkBuilder>
|
||||
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<K extends Key, J extends AsymmetricJ
|
|||
implements RsaPublicJwkBuilder {
|
||||
|
||||
DefaultRsaPublicJwkBuilder(JwkContext<?> 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<K extends Key, J extends AsymmetricJ
|
|||
implements EcPrivateJwkBuilder {
|
||||
|
||||
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) {
|
||||
|
@ -236,7 +236,7 @@ abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJ
|
|||
implements RsaPrivateJwkBuilder {
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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>
|
||||
implements SecretJwkBuilder {
|
||||
public DefaultSecretJwkBuilder(JwkContext<?> ctx, SecretKey key) {
|
||||
super(new DefaultJwkContext<>(ctx, key, DefaultSecretJwk.PRIVATE_NAMES));
|
||||
super(new DefaultJwkContext<>(DefaultSecretJwk.PRIVATE_NAMES, ctx, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<K extends Key> implements JwkContext<K> {
|
|||
private static final Converter<URI, Object> URI_CONVERTER =
|
||||
Converters.forEncoded(URI.class, new UriStringConverter());
|
||||
|
||||
private static final Set<String> DEFAULT_PRIVATE_NAMES;
|
||||
private static final Map<String, Canonicalizer<?>> SETTERS;
|
||||
|
||||
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")
|
||||
List<Canonicalizer<?>> fns = Collections.<Canonicalizer<?>>of(
|
||||
Canonicalizer.forKey(AbstractJwk.ALGORITHM, "Algorithm"),
|
||||
|
@ -55,7 +63,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
for (Canonicalizer<?> fn : fns) {
|
||||
s.put(fn.getId(), fn);
|
||||
}
|
||||
SETTERS = s;
|
||||
SETTERS = java.util.Collections.unmodifiableMap(s);
|
||||
}
|
||||
|
||||
private final Map<String, Object> values;
|
||||
|
@ -67,46 +75,51 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
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<String> 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<String> privateMemberNames, K key) {
|
||||
this(privateMemberNames);
|
||||
this.key = Assert.notNull(key, "Key cannot be null.");
|
||||
}
|
||||
|
||||
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.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<String> 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<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@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.");
|
||||
for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Object remove(String key) {
|
||||
|
@ -204,8 +218,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAlgorithm(String algorithm) {
|
||||
public JwkContext<K> setAlgorithm(String algorithm) {
|
||||
put(AbstractJwk.ALGORITHM, algorithm);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -214,8 +229,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
public JwkContext<K> setId(String id) {
|
||||
put(AbstractJwk.ID, id);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -225,8 +241,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setOperations(Set<String> ops) {
|
||||
public JwkContext<K> setOperations(Set<String> ops) {
|
||||
put(AbstractJwk.OPERATIONS, ops);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -235,8 +252,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setType(String type) {
|
||||
public JwkContext<K> setType(String type) {
|
||||
put(AbstractJwk.TYPE, type);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -245,8 +263,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKeyUse(String use) {
|
||||
public JwkContext<K> setPublicKeyUse(String use) {
|
||||
put(AbstractAsymmetricJwk.PUBLIC_KEY_USE, use);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -256,8 +275,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setX509CertificateChain(List<X509Certificate> x5c) {
|
||||
public JwkContext<K> setX509CertificateChain(List<X509Certificate> x5c) {
|
||||
put(AbstractAsymmetricJwk.X509_CERT_CHAIN, x5c);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -266,8 +286,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setX509CertificateSha1Thumbprint(byte[] x5t) {
|
||||
public JwkContext<K> setX509CertificateSha1Thumbprint(byte[] x5t) {
|
||||
put(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT, x5t);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -276,8 +297,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setX509CertificateSha256Thumbprint(byte[] x5ts256) {
|
||||
public JwkContext<K> setX509CertificateSha256Thumbprint(byte[] x5ts256) {
|
||||
put(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT, x5ts256);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -286,8 +308,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setX509Url(URI url) {
|
||||
public JwkContext<K> setX509Url(URI url) {
|
||||
put(AbstractAsymmetricJwk.X509_URL, url);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -307,8 +330,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKey(PublicKey publicKey) {
|
||||
public JwkContext<K> setPublicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -317,8 +341,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setProvider(Provider provider) {
|
||||
public JwkContext<K> setProvider(Provider provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<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 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<EK extends RSAKey & PublicKey, DK extends RSAKey
|
|||
byte[] ciphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
||||
@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<EK extends RSAKey & PublicKey, DK extends RSAKey
|
|||
return execute(request, Cipher.class, new InstanceCallback<Cipher, SecretKey>() {
|
||||
@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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,7 +28,6 @@ public class DefaultRsaSignatureAlgorithm<SK extends RSAKey & PrivateKey, VK ext
|
|||
|
||||
private static AlgorithmParameterSpec pssParamFromSaltBitLength(int saltBitLength) {
|
||||
MGF1ParameterSpec ps = new MGF1ParameterSpec("SHA-" + saltBitLength);
|
||||
//MGF1ParameterSpec ps = MGF1ParameterSpec.SHA256;
|
||||
int saltByteLength = saltBitLength / Byte.SIZE;
|
||||
return new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, saltByteLength, 1);
|
||||
}
|
||||
|
|
|
@ -41,8 +41,7 @@ class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJw
|
|||
|
||||
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
|
||||
// requires public values to be present in private JWKs, so add them:
|
||||
JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>();
|
||||
pubCtx.setKey(ecPublicKey);
|
||||
JwkContext<ECPublicKey> 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<ECPrivateKey, EcPrivateJw
|
|||
// 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)
|
||||
// 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);
|
||||
EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx);
|
||||
JwkContext<ECPublicKey> 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);
|
||||
|
|
|
@ -34,41 +34,41 @@ public interface JwkContext<K extends Key> extends Identifiable {
|
|||
|
||||
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();
|
||||
|
||||
void setType(String type);
|
||||
JwkContext<K> setType(String type);
|
||||
|
||||
Set<String> getOperations();
|
||||
|
||||
void setOperations(Set<String> operations);
|
||||
JwkContext<K> setOperations(Set<String> operations);
|
||||
|
||||
String getAlgorithm();
|
||||
|
||||
void setAlgorithm(String algorithm);
|
||||
JwkContext<K> setAlgorithm(String algorithm);
|
||||
|
||||
String getPublicKeyUse();
|
||||
|
||||
void setPublicKeyUse(String use);
|
||||
JwkContext<K> setPublicKeyUse(String use);
|
||||
|
||||
URI getX509Url();
|
||||
|
||||
void setX509Url(URI url);
|
||||
JwkContext<K> setX509Url(URI url);
|
||||
|
||||
List<X509Certificate> getX509CertificateChain();
|
||||
|
||||
void setX509CertificateChain(List<X509Certificate> x5c);
|
||||
JwkContext<K> setX509CertificateChain(List<X509Certificate> x5c);
|
||||
|
||||
byte[] getX509CertificateSha1Thumbprint();
|
||||
|
||||
void setX509CertificateSha1Thumbprint(byte[] x5t);
|
||||
JwkContext<K> setX509CertificateSha1Thumbprint(byte[] x5t);
|
||||
|
||||
byte[] getX509CertificateSha256Thumbprint();
|
||||
|
||||
void setX509CertificateSha256Thumbprint(byte[] x5ts256);
|
||||
JwkContext<K> setX509CertificateSha256Thumbprint(byte[] x5ts256);
|
||||
|
||||
K getKey();
|
||||
|
||||
|
@ -76,11 +76,11 @@ public interface JwkContext<K extends Key> extends Identifiable {
|
|||
|
||||
PublicKey getPublicKey();
|
||||
|
||||
void setPublicKey(PublicKey publicKey);
|
||||
JwkContext<K> setPublicKey(PublicKey publicKey);
|
||||
|
||||
Set<String> getPrivateMemberNames();
|
||||
|
||||
Provider getProvider();
|
||||
|
||||
void setProvider(Provider provider);
|
||||
JwkContext<K> setProvider(Provider provider);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<RSAPrivateKey, RsaPr
|
|||
|
||||
// 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:
|
||||
JwkContext<RSAPublicKey> pubCtx = new DefaultJwkContext<>();
|
||||
pubCtx.setKey(rsaPublicKey);
|
||||
RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx);
|
||||
JwkContext<RSAPublicKey> 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<RSAPrivateKey, RsaPr
|
|||
|
||||
//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:
|
||||
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);
|
||||
RSAPublicKey pubKey = pubJwk.toKey();
|
||||
final BigInteger modulus = pubKey.getModulus();
|
||||
|
@ -156,8 +154,6 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
|
|||
|
||||
KeySpec spec;
|
||||
|
||||
|
||||
|
||||
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 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.");
|
||||
}
|
||||
|
||||
// Need to do 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:
|
||||
JwkContext<?> 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());
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
* <a href="https://tools.ietf.org/html/rfc7518#appendix-B.1">RFC 7518, Appendix B.1</a>
|
||||
*
|
||||
* @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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
* <a href="https://tools.ietf.org/html/rfc7518#appendix-B.2">RFC 7518, Appendix B.2</a>
|
||||
*
|
||||
* @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,
|
|
@ -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
|
||||
* <a href="https://tools.ietf.org/html/rfc7518#appendix-B.3">RFC 7518, Appendix B.3</a>
|
||||
*
|
||||
* @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,
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue