diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea759511f..096b090f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
## Release Notes
+### 0.12.6
+
+This patch release:
+
+* Ensures that after successful JWS signature verification, an application-configured Base64Url `Decoder` output is
+ used to construct a `Jws` instance (instead of JJWT's default decoder). See
+ [Issue 947](https://github.com/jwtk/jjwt/issues/947).
+* Fixes a decompression memory leak in concurrent/multi-threaded environments introduced in 0.12.0 when decompressing JWTs with a `zip` header of `GZIP`. See [Issue 949](https://github.com/jwtk/jjwt/issues/949).
+* Upgrades BouncyCastle to 1.78 via [PR 941](https://github.com/jwtk/jjwt/pull/941).
+
### 0.12.5
This patch release:
diff --git a/README.adoc b/README.adoc
index 36070003f..546796c3c 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,6 +1,6 @@
:doctype: book
= Java JWT: JSON Web Token for Java and Android
-:project-version: 0.12.5
+:project-version: 0.12.6
:toc:
:toc-title:
:toc-placement!:
diff --git a/SECURITY.md b/SECURITY.md
index ae1236f3b..96eff597f 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -11,8 +11,8 @@ We ask that all users or security researchers upgrade to the latest stable relea
| Version | Supported |
| -------- | ------------------ |
-| 0.11.x | :white_check_mark: |
-| < 0.11.0 | :x: |
+| 0.12.x | :white_check_mark: |
+| < 0.12.0 | :x: |
## Reporting Security Issues
diff --git a/api/pom.xml b/api/pom.xml
index 2d106b7c8..fac58aa6e 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
../pom.xml
diff --git a/extensions/gson/pom.xml b/extensions/gson/pom.xml
index 84972064a..35e458e27 100644
--- a/extensions/gson/pom.xml
+++ b/extensions/gson/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
../../pom.xml
diff --git a/extensions/jackson/pom.xml b/extensions/jackson/pom.xml
index 9107c069b..4e39f20a2 100644
--- a/extensions/jackson/pom.xml
+++ b/extensions/jackson/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
../../pom.xml
diff --git a/extensions/orgjson/pom.xml b/extensions/orgjson/pom.xml
index a2411941c..5d9da307f 100644
--- a/extensions/orgjson/pom.xml
+++ b/extensions/orgjson/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
../../pom.xml
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 972050bf7..a302e93b2 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
../pom.xml
diff --git a/impl/pom.xml b/impl/pom.xml
index 0682240ef..4d10f5581 100644
--- a/impl/pom.xml
+++ b/impl/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
../pom.xml
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJws.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
index 0bf0fdf8b..dadaa51d3 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
@@ -18,7 +18,6 @@
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtVisitor;
-import io.jsonwebtoken.io.Decoders;
public class DefaultJws
extends DefaultProtectedJwt implements Jws {
@@ -26,9 +25,9 @@ public class DefaultJws
extends DefaultProtectedJwt implements
private final String signature;
- public DefaultJws(JwsHeader header, P payload, String signature) {
- super(header, payload, Decoders.BASE64URL.decode(signature), DIGEST_NAME);
- this.signature = signature;
+ public DefaultJws(JwsHeader header, P payload, byte[] signature, String b64UrlSig) {
+ super(header, payload, signature, DIGEST_NAME);
+ this.signature = b64UrlSig;
}
@Override
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
index a4f678488..ec20cbd80 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
@@ -617,14 +617,8 @@ private String sign(final Payload payload, final Key key, final Provider provide
// Next, b64 extension requires the raw (non-encoded) payload to be included directly in the signing input,
// so we ensure we have an input stream for that:
- if (payload.isClaims() || payload.isCompressed()) {
- ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192);
- writeAndClose("JWS Unencoded Payload", payload, claimsOut);
- payloadStream = Streams.of(claimsOut.toByteArray());
- } else {
- // No claims and not compressed, so just get the direct InputStream:
- payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
- }
+ payloadStream = toInputStream("JWS Unencoded Payload", payload);
+
if (!payload.isClaims()) {
payloadStream = new CountingInputStream(payloadStream); // we'll need to assert if it's empty later
}
@@ -712,14 +706,7 @@ private String encrypt(final Payload content, final Key key, final Provider keyP
Assert.stateNotNull(keyAlgFunction, "KeyAlgorithm function cannot be null.");
assertPayloadEncoding("JWE");
- InputStream plaintext;
- if (content.isClaims()) {
- ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
- writeAndClose("JWE Claims", content, out);
- plaintext = Streams.of(out.toByteArray());
- } else {
- plaintext = content.toInputStream();
- }
+ InputStream plaintext = toInputStream("JWE Payload", content);
//only expose (mutable) JweHeader functionality to KeyAlgorithm instances, not the full headerBuilder
// (which exposes this JwtBuilder and shouldn't be referenced by KeyAlgorithms):
@@ -839,4 +826,15 @@ private void encodeAndWrite(String name, byte[] data, OutputStream out) {
Streams.writeAndClose(out, data, "Unable to write bytes");
}
+ private InputStream toInputStream(final String name, Payload payload) {
+ if (payload.isClaims() || payload.isCompressed()) {
+ ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192);
+ writeAndClose(name, payload, claimsOut);
+ return Streams.of(claimsOut.toByteArray());
+ } else {
+ // No claims and not compressed, so just get the direct InputStream:
+ return Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
+ }
+ }
+
}
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 86b4c65a2..ce5059181 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -266,8 +266,8 @@ private static boolean hasContentType(Header header) {
return header != null && Strings.hasText(header.getContentType());
}
- private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHeader, final String alg,
- @SuppressWarnings("deprecation") SigningKeyResolver resolver, Claims claims, Payload payload) {
+ private byte[] verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHeader, final String alg,
+ @SuppressWarnings("deprecation") SigningKeyResolver resolver, Claims claims, Payload payload) {
Assert.notNull(resolver, "SigningKeyResolver instance cannot be null.");
@@ -355,6 +355,8 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
} finally {
Streams.reset(payloadStream);
}
+
+ return signature;
}
@Override
@@ -486,7 +488,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
}
byte[] iv = null;
- byte[] tag = null;
+ byte[] digest = null; // either JWE AEAD tag or JWS signature after Base64Url-decoding
if (tokenized instanceof TokenizedJwe) {
TokenizedJwe tokenizedJwe = (TokenizedJwe) tokenized;
@@ -522,8 +524,8 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
base64Url = base64UrlDigest;
//guaranteed to be non-empty via the `alg` + digest check above:
Assert.hasText(base64Url, "JWE AAD Authentication Tag cannot be null or empty.");
- tag = decode(base64Url, "JWE AAD Authentication Tag");
- if (Bytes.isEmpty(tag)) {
+ digest = decode(base64Url, "JWE AAD Authentication Tag");
+ if (Bytes.isEmpty(digest)) {
String msg = "Compact JWE strings must always contain an AAD Authentication Tag.";
throw new MalformedJwtException(msg);
}
@@ -565,7 +567,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
// TODO: add encProvider(Provider) builder method that applies to this request only?
InputStream ciphertext = payload.toInputStream();
ByteArrayOutputStream plaintext = new ByteArrayOutputStream(8192);
- DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, tag);
+ DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, digest);
encAlg.decrypt(dreq, plaintext);
payload = new Payload(plaintext.toByteArray(), header.getContentType());
@@ -575,7 +577,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
// not using a signing key resolver, so we can verify the signature before reading the payload, which is
// always safer:
JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");
- verifySignature(tokenized, jwsHeader, alg, new LocatingKeyResolver(this.keyLocator), null, payload);
+ digest = verifySignature(tokenized, jwsHeader, alg, new LocatingKeyResolver(this.keyLocator), null, payload);
integrityVerified = true; // no exception means signature verified
}
@@ -596,66 +598,73 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
Claims claims = null;
byte[] payloadBytes = payload.getBytes();
if (payload.isConsumable()) {
-
- InputStream in = payload.toInputStream();
-
- if (!hasContentType(header)) { // If there is a content type set, then the application using JJWT is expected
- // to convert the byte payload themselves based on this content type
- // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 :
- //
- // "This parameter is ignored by JWS implementations; any processing of this
- // parameter is performed by the JWS application."
- //
- Map claimsMap = null;
- try {
- // if deserialization fails, we'll need to rewind to convert to a byte array. So if
- // mark/reset isn't possible, we'll need to buffer:
- if (!in.markSupported()) {
- in = new BufferedInputStream(in);
- in.mark(0);
- }
- claimsMap = deserialize(new UncloseableInputStream(in) /* Don't close in case we need to rewind */, "claims");
- } catch (DeserializationException | MalformedJwtException ignored) { // not JSON, treat it as a byte[]
+ InputStream in = null;
+ try {
+ in = payload.toInputStream();
+
+ if (!hasContentType(header)) { // If there is a content type set, then the application using JJWT is expected
+ // to convert the byte payload themselves based on this content type
+ // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 :
+ //
+ // "This parameter is ignored by JWS implementations; any processing of this
+ // parameter is performed by the JWS application."
+ //
+ Map claimsMap = null;
+ try {
+ // if deserialization fails, we'll need to rewind to convert to a byte array. So if
+ // mark/reset isn't possible, we'll need to buffer:
+ if (!in.markSupported()) {
+ in = new BufferedInputStream(in);
+ in.mark(0);
+ }
+ claimsMap = deserialize(new UncloseableInputStream(in) /* Don't close in case we need to rewind */, "claims");
+ } catch (DeserializationException |
+ MalformedJwtException ignored) { // not JSON, treat it as a byte[]
// String msg = "Invalid claims: " + e.getMessage();
// throw new MalformedJwtException(msg, e);
- } finally {
- Streams.reset(in);
- }
- if (claimsMap != null) {
- try {
- claims = new DefaultClaims(claimsMap);
- } catch (Throwable t) {
- String msg = "Invalid claims: " + t.getMessage();
- throw new MalformedJwtException(msg);
+ } finally {
+ Streams.reset(in);
+ }
+ if (claimsMap != null) {
+ try {
+ claims = new DefaultClaims(claimsMap);
+ } catch (Throwable t) {
+ String msg = "Invalid claims: " + t.getMessage();
+ throw new MalformedJwtException(msg);
+ }
}
}
- }
- if (claims == null) {
- // consumable, but not claims, so convert to byte array:
- payloadBytes = Streams.bytes(in, "Unable to convert payload to byte array.");
+ if (claims == null) {
+ // consumable, but not claims, so convert to byte array:
+ payloadBytes = Streams.bytes(in, "Unable to convert payload to byte array.");
+ }
+ } finally { // always ensure closed per https://github.com/jwtk/jjwt/issues/949
+ Objects.nullSafeClose(in);
}
}
+ // =============== Post-SKR Signature Check =================
+ if (hasDigest && signingKeyResolver != null) { // TODO: remove for 1.0
+ // A SigningKeyResolver has been configured, and due to it's API, we have to verify the signature after
+ // parsing the body. This can be a security risk, so it needs to be removed before 1.0
+ JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");
+ digest = verifySignature(tokenized, jwsHeader, alg, this.signingKeyResolver, claims, payload);
+ //noinspection UnusedAssignment
+ integrityVerified = true; // no exception means verified successfully
+ }
+
Jwt, ?> jwt;
Object body = claims != null ? claims : payloadBytes;
if (header instanceof JweHeader) {
- jwt = new DefaultJwe<>((JweHeader) header, body, iv, tag);
+ jwt = new DefaultJwe<>((JweHeader) header, body, iv, digest);
} else if (hasDigest) {
JwsHeader jwsHeader = Assert.isInstanceOf(JwsHeader.class, header, "JwsHeader required.");
- jwt = new DefaultJws<>(jwsHeader, body, base64UrlDigest.toString());
+ jwt = new DefaultJws<>(jwsHeader, body, digest, base64UrlDigest.toString());
} else {
//noinspection rawtypes
jwt = new DefaultJwt(header, body);
}
- // =============== Signature =================
- if (hasDigest && signingKeyResolver != null) { // TODO: remove for 1.0
- // A SigningKeyResolver has been configured, and due to it's API, we have to verify the signature after
- // parsing the body. This can be a security risk, so it needs to be removed before 1.0
- JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");
- verifySignature(tokenized, jwsHeader, alg, this.signingKeyResolver, claims, payload);
- }
-
final boolean allowSkew = this.allowedClockSkewMillis > 0;
//since 0.3:
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java
index 28ecdfb86..e92277aed 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java
@@ -135,12 +135,12 @@ private void assertAlgorithmName(SecretKey key, boolean signing) {
throw new InvalidKeyException(msg);
}
- // We can ignore PKCS11 key name assertions because HSM module key algorithm names don't always align with
- // JCA standard algorithm names:
- boolean pkcs11Key = KeysBridge.isSunPkcs11GenericSecret(key);
+ // We can ignore key name assertions for generic secrets, because HSM module key algorithm names
+ // don't always align with JCA standard algorithm names
+ boolean generic = KeysBridge.isGenericSecret(key);
//assert key's jca name is valid if it's a JWA standard algorithm:
- if (!pkcs11Key && isJwaStandard() && !isJwaStandardJcaName(name)) {
+ if (!generic && isJwaStandard() && !isJwaStandardJcaName(name)) {
throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + name +
"' does not equal a valid HmacSHA* algorithm name or PKCS12 OID and cannot be used with " +
getId() + ".");
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java b/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java
index a3602619a..869fab940 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java
@@ -34,8 +34,9 @@
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
public final class KeysBridge {
- private static final String SUNPKCS11_GENERIC_SECRET_CLASSNAME = "sun.security.pkcs11.P11Key$P11SecretKey";
- private static final String SUNPKCS11_GENERIC_SECRET_ALGNAME = "Generic Secret"; // https://github.com/openjdk/jdk/blob/4f90abaf17716493bad740dcef76d49f16d69379/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyStore.java#L1292
+ // Some HSMs use generic secrets. This prefix matches the generic secret algorithm name
+ // used by SUN PKCS#11 provider, AWS CloudHSM JCE provider and possibly other HSMs
+ private static final String GENERIC_SECRET_ALG_PREFIX = "Generic";
// prevent instantiation
private KeysBridge() {
@@ -95,10 +96,13 @@ public static byte[] findEncoded(Key key) {
return encoded;
}
- public static boolean isSunPkcs11GenericSecret(Key key) {
- return key instanceof SecretKey &&
- key.getClass().getName().equals(SUNPKCS11_GENERIC_SECRET_CLASSNAME) &&
- SUNPKCS11_GENERIC_SECRET_ALGNAME.equals(key.getAlgorithm());
+ public static boolean isGenericSecret(Key key) {
+ if (!(key instanceof SecretKey)) {
+ return false;
+ }
+
+ String algName = Assert.hasText(key.getAlgorithm(), "Key algorithm cannot be null or empty.");
+ return algName.startsWith(GENERIC_SECRET_ALG_PREFIX);
}
/**
diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
index ee8737060..4837b3882 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
@@ -22,6 +22,7 @@ import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.lang.Services
import io.jsonwebtoken.impl.security.*
+import io.jsonwebtoken.io.CompressionAlgorithm
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.io.Deserializer
import io.jsonwebtoken.io.Encoders
@@ -1391,6 +1392,97 @@ class JwtsTest {
}
}
+ @Test
+ void testJweCompressionWithArbitraryContentString() {
+ def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP]
+
+ for (CompressionAlgorithm zip : codecs) {
+
+ for (AeadAlgorithm enc : Jwts.ENC.get().values()) {
+
+ SecretKey key = enc.key().build()
+
+ String payload = 'hello, world!'
+
+ // encrypt and compress:
+ String jwe = Jwts.builder()
+ .content(payload)
+ .compressWith(zip)
+ .encryptWith(key, enc)
+ .compact()
+
+ //decompress and decrypt:
+ def jwt = Jwts.parser()
+ .decryptWith(key)
+ .build()
+ .parseEncryptedContent(jwe)
+ assertEquals payload, new String(jwt.getPayload(), StandardCharsets.UTF_8)
+ }
+ }
+ }
+
+ @Test
+ void testJweCompressionWithArbitraryContentByteArray() {
+ def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP]
+
+ for (CompressionAlgorithm zip : codecs) {
+
+ for (AeadAlgorithm enc : Jwts.ENC.get().values()) {
+
+ SecretKey key = enc.key().build()
+
+ byte[] payload = new byte[14];
+ Randoms.secureRandom().nextBytes(payload)
+
+ // encrypt and compress:
+ String jwe = Jwts.builder()
+ .content(payload)
+ .compressWith(zip)
+ .encryptWith(key, enc)
+ .compact()
+
+ //decompress and decrypt:
+ def jwt = Jwts.parser()
+ .decryptWith(key)
+ .build()
+ .parseEncryptedContent(jwe)
+ assertArrayEquals payload, jwt.getPayload()
+ }
+ }
+ }
+
+ @Test
+ void testJweCompressionWithArbitraryContentInputStream() {
+ def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP]
+
+ for (CompressionAlgorithm zip : codecs) {
+
+ for (AeadAlgorithm enc : Jwts.ENC.get().values()) {
+
+ SecretKey key = enc.key().build()
+
+ byte[] payloadBytes = new byte[14];
+ Randoms.secureRandom().nextBytes(payloadBytes)
+
+ ByteArrayInputStream payload = new ByteArrayInputStream(payloadBytes)
+
+ // encrypt and compress:
+ String jwe = Jwts.builder()
+ .content(payload)
+ .compressWith(zip)
+ .encryptWith(key, enc)
+ .compact()
+
+ //decompress and decrypt:
+ def jwt = Jwts.parser()
+ .decryptWith(key)
+ .build()
+ .parseEncryptedContent(jwe)
+ assertArrayEquals payloadBytes, jwt.getPayload()
+ }
+ }
+ }
+
@Test
void testPasswordJwes() {
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
index a55e550e0..1511eb103 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
@@ -17,8 +17,12 @@ package io.jsonwebtoken.impl
import io.jsonwebtoken.JwsHeader
import io.jsonwebtoken.Jwts
+import io.jsonwebtoken.impl.lang.Bytes
+import io.jsonwebtoken.io.Encoders
import org.junit.Test
+import java.security.MessageDigest
+
import static org.junit.Assert.*
class DefaultJwsTest {
@@ -26,10 +30,13 @@ class DefaultJwsTest {
@Test
void testConstructor() {
JwsHeader header = new DefaultJwsHeader([:])
- def jws = new DefaultJws(header, 'foo', 'sig')
+ byte[] sig = Bytes.random(32)
+ String b64u = Encoders.BASE64URL.encode(sig)
+ def jws = new DefaultJws(header, 'foo', sig, b64u)
assertSame jws.getHeader(), header
assertEquals jws.getPayload(), 'foo'
- assertEquals jws.getSignature(), 'sig'
+ assertTrue MessageDigest.isEqual(sig, jws.getDigest())
+ assertEquals b64u, jws.getSignature()
}
@Test
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultMacAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultMacAlgorithmTest.groovy
index 184f502eb..6df8d95b7 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultMacAlgorithmTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultMacAlgorithmTest.groovy
@@ -19,6 +19,7 @@ import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.security.*
import org.junit.Test
+import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.StandardCharsets
import java.security.Key
@@ -232,4 +233,29 @@ class DefaultMacAlgorithmTest {
assertSame mac, DefaultMacAlgorithm.findByKey(oidKey)
}
}
+
+ /**
+ * Asserts that generic secrets are accepted
+ */
+ @Test
+ void testValidateKeyAcceptsGenericSecret() {
+ def genericSecret = new SecretKey() {
+ @Override
+ String getAlgorithm() {
+ return 'GenericSecret'
+ }
+
+ @Override
+ String getFormat() {
+ return "RAW"
+ }
+
+ @Override
+ byte[] getEncoded() {
+ return Randoms.secureRandom().nextBytes(new byte[32])
+ }
+ }
+
+ newAlg().validateKey(genericSecret, true)
+ }
}
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/KeysBridgeTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/KeysBridgeTest.groovy
index 89d74beaa..9e62ff2c1 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/KeysBridgeTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/KeysBridgeTest.groovy
@@ -17,9 +17,13 @@ package io.jsonwebtoken.impl.security
import org.junit.Test
+import javax.crypto.SecretKey
import java.security.Key
+import java.security.PrivateKey
import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertTrue
class KeysBridgeTest {
@@ -56,4 +60,50 @@ class KeysBridgeTest {
void testToStringPassword() {
testFormattedOutput(new PasswordSpec("foo".toCharArray()))
}
+
+ @Test
+ void testIsGenericSecret() {
+ def secretKeyWithAlg = { alg ->
+ new SecretKey() {
+ @Override
+ String getAlgorithm() {
+ return alg
+ }
+
+ @Override
+ String getFormat() {
+ return 'RAW'
+ }
+
+ @Override
+ byte[] getEncoded() {
+ return new byte[0]
+ }
+ }
+ }
+
+ PrivateKey genericPrivateKey = new PrivateKey() {
+ @Override
+ String getAlgorithm() {
+ return "Generic"
+ }
+
+ @Override
+ String getFormat() {
+ return "RAW"
+ }
+
+ @Override
+ byte[] getEncoded() {
+ return new byte[0]
+ }
+ }
+
+ assertTrue KeysBridge.isGenericSecret(secretKeyWithAlg("GenericSecret"))
+ assertTrue KeysBridge.isGenericSecret(secretKeyWithAlg("Generic Secret"))
+ assertFalse KeysBridge.isGenericSecret(secretKeyWithAlg(" Generic"))
+ assertFalse KeysBridge.isGenericSecret(TestKeys.HS256)
+ assertFalse KeysBridge.isGenericSecret(TestKeys.A256GCM)
+ assertFalse KeysBridge.isGenericSecret(genericPrivateKey)
+ }
}
diff --git a/pom.xml b/pom.xml
index d25be5d34..aec615aaf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
JJWT
JSON Web Token support for the JVM and Android
pom
@@ -117,7 +117,7 @@
- 1.76
+ 1.78
bcprov-jdk18on
bcpkix-jdk18on
diff --git a/tdjar/pom.xml b/tdjar/pom.xml
index 9ab645801..be4f4e3b6 100644
--- a/tdjar/pom.xml
+++ b/tdjar/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 1.0.0-SNAPSHOT
+ 0.12.7-SNAPSHOT
../pom.xml