Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;

/**
* Class used to perform the signature hash calculations.
Expand All @@ -15,6 +16,23 @@ class CryptoHelper {

private static final byte JWT_PART_SEPARATOR = (byte) 46;

/**
* Decode the JWT signature part, accepting only its canonical unpadded base64url form.
* The JDK decoder also accepts '=' padding and ignores non-zero trailing bits, which would
* let several different token strings carry the same valid signature.
*
* @param signature the base64url encoded JWT signature.
* @return the decoded signature bytes.
* @throws IllegalArgumentException if the signature is not canonical base64url without padding.
*/
static byte[] decodeSignature(String signature) throws IllegalArgumentException {
byte[] signatureBytes = Base64.getUrlDecoder().decode(signature);
if (!Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes).equals(signature)) {
throw new IllegalArgumentException("The signature is not a canonical base64url encoded value.");
}
return signatureBytes;
}

/**
* Verify signature for JWT header and payload.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Base64;

/**
* Subclass representing an Elliptic Curve signing algorithm
Expand Down Expand Up @@ -44,7 +43,7 @@ class ECDSAAlgorithm extends Algorithm {
@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
try {
byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature());
byte[] signatureBytes = CryptoHelper.decodeSignature(jwt.getSignature());
ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
if (publicKey == null) {
throw new IllegalStateException("The given Public Key is null.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;

/**
* Subclass representing an Hash-based MAC signing algorithm
Expand Down Expand Up @@ -50,7 +49,7 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException {
@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
try {
byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature());
byte[] signatureBytes = CryptoHelper.decodeSignature(jwt.getSignature());
boolean valid = crypto.verifySignatureFor(
getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes);
if (!valid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Base64;

class NoneAlgorithm extends Algorithm {

Expand All @@ -14,7 +13,7 @@ class NoneAlgorithm extends Algorithm {
@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
try {
byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature());
byte[] signatureBytes = CryptoHelper.decodeSignature(jwt.getSignature());

if (signatureBytes.length > 0) {
throw new SignatureVerificationException(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;

/**
* Subclass representing an RSA signing algorithm
Expand Down Expand Up @@ -54,7 +53,7 @@ class RSAAlgorithm extends Algorithm {
@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
try {
byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature());
byte[] signatureBytes = CryptoHelper.decodeSignature(jwt.getSignature());
RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
if (publicKey == null) {
throw new IllegalStateException("The given Public Key is null.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ public void shouldPassECDSA256VerificationWithJOSESignature() throws Exception {
algorithm.verify(JWT.decode(jwt));
}

@Test
public void shouldFailECDSA256VerificationWithNonCanonicalSignature() throws Exception {
exception.expect(SignatureVerificationException.class);
exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA");
// Same signature bytes as the valid token above, but the unused low bits of the last character are set
String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1h";
ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
Algorithm algorithm = Algorithm.ECDSA256(key);
algorithm.verify(JWT.decode(jwt));
}

@Test
public void shouldThrowOnECDSA256VerificationWithDERSignature() throws Exception {
exception.expect(SignatureVerificationException.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ public void shouldFailHMAC256VerificationWithInvalidSecretBytes() {
algorithm.verify(JWT.decode(jwt));
}

@Test
public void shouldFailHMAC256VerificationWithNonCanonicalSignatureTrailingBits() {
exception.expect(SignatureVerificationException.class);
exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256");
// Same signature bytes as the valid token above, but the unused low bits of the last character are set
String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1N";
Algorithm algorithm = Algorithm.HMAC256("secret");
algorithm.verify(JWT.decode(jwt));
}

@Test
public void shouldFailHMAC256VerificationWithPaddedSignature() {
exception.expect(SignatureVerificationException.class);
exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256");
String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M=";
Algorithm algorithm = Algorithm.HMAC256("secret");
algorithm.verify(JWT.decode(jwt));
}

@Test
public void shouldPassHMAC384Verification() {
String jwt = "eyJhbGciOiJIUzM4NCIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.uztpK_wUMYJhrRv8SV-1LU4aPnwl-EM1q-wJnqgyb5DHoDteP6lN_gE1xnZJH5vw";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ public void shouldPassRSA256Verification() throws Exception {
algorithm.verify(JWT.decode(jwt));
}

@Test
public void shouldFailRSA256VerificationWithNonCanonicalSignature() throws Exception {
exception.expect(SignatureVerificationException.class);
exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA");
// Same signature bytes as the valid token above, but the unused low bits of the last character are set
String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cB";
Algorithm algorithm = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"));
algorithm.verify(JWT.decode(jwt));
}

@Test
public void shouldPassRSA256VerificationWithBothKeys() throws Exception {
String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA";
Expand Down