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 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() {

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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) {

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>
implements SecretJwkBuilder {
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.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

View File

@ -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,7 +58,11 @@ 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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

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 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());

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
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
}
}

View File

@ -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,

View File

@ -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,

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)
}
}