Skip to content

Commit a7de554

Browse files
authored
Fixes #947 (#948)
1 parent 7543248 commit a7de554

File tree

4 files changed

+41
-23
lines changed

4 files changed

+41
-23
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
## Release Notes
22

3+
### 0.12.6
4+
5+
This patch release:
6+
7+
* Ensures that after successful JWS signature verification, an application-configured Base64Url `Decoder` output is
8+
used to construct a `Jws` instance (instead of JJWT's default decoder). See
9+
[Issue 947](https://github.com/jwtk/jjwt/issues/947).
10+
311
### 0.12.5
412

513
This patch release:

impl/src/main/java/io/jsonwebtoken/impl/DefaultJws.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,16 @@
1818
import io.jsonwebtoken.Jws;
1919
import io.jsonwebtoken.JwsHeader;
2020
import io.jsonwebtoken.JwtVisitor;
21-
import io.jsonwebtoken.io.Decoders;
2221

2322
public class DefaultJws<P> extends DefaultProtectedJwt<JwsHeader, P> implements Jws<P> {
2423

2524
private static final String DIGEST_NAME = "signature";
2625

2726
private final String signature;
2827

29-
public DefaultJws(JwsHeader header, P payload, String signature) {
30-
super(header, payload, Decoders.BASE64URL.decode(signature), DIGEST_NAME);
31-
this.signature = signature;
28+
public DefaultJws(JwsHeader header, P payload, byte[] signature, String b64UrlSig) {
29+
super(header, payload, signature, DIGEST_NAME);
30+
this.signature = b64UrlSig;
3231
}
3332

3433
@Override

impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java

+21-17
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ private static boolean hasContentType(Header header) {
265265
return header != null && Strings.hasText(header.getContentType());
266266
}
267267

268-
private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHeader, final String alg,
269-
@SuppressWarnings("deprecation") SigningKeyResolver resolver, Claims claims, Payload payload) {
268+
private byte[] verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHeader, final String alg,
269+
@SuppressWarnings("deprecation") SigningKeyResolver resolver, Claims claims, Payload payload) {
270270

271271
Assert.notNull(resolver, "SigningKeyResolver instance cannot be null.");
272272

@@ -354,6 +354,8 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
354354
} finally {
355355
Streams.reset(payloadStream);
356356
}
357+
358+
return signature;
357359
}
358360

359361
@Override
@@ -485,7 +487,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
485487
}
486488

487489
byte[] iv = null;
488-
byte[] tag = null;
490+
byte[] digest = null; // either JWE AEAD tag or JWS signature after Base64Url-decoding
489491
if (tokenized instanceof TokenizedJwe) {
490492

491493
TokenizedJwe tokenizedJwe = (TokenizedJwe) tokenized;
@@ -521,8 +523,8 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
521523
base64Url = base64UrlDigest;
522524
//guaranteed to be non-empty via the `alg` + digest check above:
523525
Assert.hasText(base64Url, "JWE AAD Authentication Tag cannot be null or empty.");
524-
tag = decode(base64Url, "JWE AAD Authentication Tag");
525-
if (Bytes.isEmpty(tag)) {
526+
digest = decode(base64Url, "JWE AAD Authentication Tag");
527+
if (Bytes.isEmpty(digest)) {
526528
String msg = "Compact JWE strings must always contain an AAD Authentication Tag.";
527529
throw new MalformedJwtException(msg);
528530
}
@@ -564,7 +566,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
564566
// TODO: add encProvider(Provider) builder method that applies to this request only?
565567
InputStream ciphertext = payload.toInputStream();
566568
ByteArrayOutputStream plaintext = new ByteArrayOutputStream(8192);
567-
DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, tag);
569+
DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, digest);
568570
encAlg.decrypt(dreq, plaintext);
569571
payload = new Payload(plaintext.toByteArray(), header.getContentType());
570572

@@ -574,7 +576,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
574576
// not using a signing key resolver, so we can verify the signature before reading the payload, which is
575577
// always safer:
576578
JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");
577-
verifySignature(tokenized, jwsHeader, alg, new LocatingKeyResolver(this.keyLocator), null, payload);
579+
digest = verifySignature(tokenized, jwsHeader, alg, new LocatingKeyResolver(this.keyLocator), null, payload);
578580
integrityVerified = true; // no exception means signature verified
579581
}
580582

@@ -635,26 +637,28 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
635637
}
636638
}
637639

640+
// =============== Post-SKR Signature Check =================
641+
if (hasDigest && signingKeyResolver != null) { // TODO: remove for 1.0
642+
// A SigningKeyResolver has been configured, and due to it's API, we have to verify the signature after
643+
// parsing the body. This can be a security risk, so it needs to be removed before 1.0
644+
JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");
645+
digest = verifySignature(tokenized, jwsHeader, alg, this.signingKeyResolver, claims, payload);
646+
//noinspection UnusedAssignment
647+
integrityVerified = true; // no exception means verified successfully
648+
}
649+
638650
Jwt<?, ?> jwt;
639651
Object body = claims != null ? claims : payloadBytes;
640652
if (header instanceof JweHeader) {
641-
jwt = new DefaultJwe<>((JweHeader) header, body, iv, tag);
653+
jwt = new DefaultJwe<>((JweHeader) header, body, iv, digest);
642654
} else if (hasDigest) {
643655
JwsHeader jwsHeader = Assert.isInstanceOf(JwsHeader.class, header, "JwsHeader required.");
644-
jwt = new DefaultJws<>(jwsHeader, body, base64UrlDigest.toString());
656+
jwt = new DefaultJws<>(jwsHeader, body, digest, base64UrlDigest.toString());
645657
} else {
646658
//noinspection rawtypes
647659
jwt = new DefaultJwt(header, body);
648660
}
649661

650-
// =============== Signature =================
651-
if (hasDigest && signingKeyResolver != null) { // TODO: remove for 1.0
652-
// A SigningKeyResolver has been configured, and due to it's API, we have to verify the signature after
653-
// parsing the body. This can be a security risk, so it needs to be removed before 1.0
654-
JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");
655-
verifySignature(tokenized, jwsHeader, alg, this.signingKeyResolver, claims, payload);
656-
}
657-
658662
final boolean allowSkew = this.allowedClockSkewMillis > 0;
659663

660664
//since 0.3:

impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy

+9-2
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,26 @@ package io.jsonwebtoken.impl
1717

1818
import io.jsonwebtoken.JwsHeader
1919
import io.jsonwebtoken.Jwts
20+
import io.jsonwebtoken.impl.lang.Bytes
21+
import io.jsonwebtoken.io.Encoders
2022
import org.junit.Test
2123

24+
import java.security.MessageDigest
25+
2226
import static org.junit.Assert.*
2327

2428
class DefaultJwsTest {
2529

2630
@Test
2731
void testConstructor() {
2832
JwsHeader header = new DefaultJwsHeader([:])
29-
def jws = new DefaultJws<String>(header, 'foo', 'sig')
33+
byte[] sig = Bytes.random(32)
34+
String b64u = Encoders.BASE64URL.encode(sig)
35+
def jws = new DefaultJws<String>(header, 'foo', sig, b64u)
3036
assertSame jws.getHeader(), header
3137
assertEquals jws.getPayload(), 'foo'
32-
assertEquals jws.getSignature(), 'sig'
38+
assertTrue MessageDigest.isEqual(sig, jws.getDigest())
39+
assertEquals b64u, jws.getSignature()
3340
}
3441

3542
@Test

0 commit comments

Comments
 (0)