Search in sources :

Example 1 with InvalidKeyException

use of io.jsonwebtoken.security.InvalidKeyException in project jjwt by jwtk.

the class DefaultJwtParser method parse.

@Override
public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {
    // remove this block in v1.0 (the equivalent is already in DefaultJwtParserBuilder)
    if (this.deserializer == null) {
        // try to find one based on the services available
        // TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0
        this.deserializeJsonWith(LegacyServices.loadFirst(Deserializer.class));
    }
    Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
    if ("..".equals(jwt)) {
        String msg = "JWT string '..' is missing a header.";
        throw new MalformedJwtException(msg);
    }
    String base64UrlEncodedHeader = null;
    String base64UrlEncodedPayload = null;
    String base64UrlEncodedDigest = null;
    int delimiterCount = 0;
    StringBuilder sb = new StringBuilder(128);
    for (char c : jwt.toCharArray()) {
        if (c == SEPARATOR_CHAR) {
            CharSequence tokenSeq = Strings.clean(sb);
            String token = tokenSeq != null ? tokenSeq.toString() : null;
            if (delimiterCount == 0) {
                base64UrlEncodedHeader = token;
            } else if (delimiterCount == 1) {
                base64UrlEncodedPayload = token;
            }
            delimiterCount++;
            sb.setLength(0);
        } else {
            sb.append(c);
        }
    }
    if (delimiterCount != 2) {
        String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
        throw new MalformedJwtException(msg);
    }
    if (sb.length() > 0) {
        base64UrlEncodedDigest = sb.toString();
    }
    // =============== Header =================
    Header header = null;
    CompressionCodec compressionCodec = null;
    if (base64UrlEncodedHeader != null) {
        byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader);
        String origValue = new String(bytes, Strings.UTF_8);
        Map<String, Object> m = (Map<String, Object>) readValue(origValue);
        if (base64UrlEncodedDigest != null) {
            header = new DefaultJwsHeader(m);
        } else {
            header = new DefaultHeader(m);
        }
        compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
    }
    // =============== Body =================
    // https://github.com/jwtk/jjwt/pull/540
    String payload = "";
    if (base64UrlEncodedPayload != null) {
        byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedPayload);
        if (compressionCodec != null) {
            bytes = compressionCodec.decompress(bytes);
        }
        payload = new String(bytes, Strings.UTF_8);
    }
    Claims claims = null;
    if (!payload.isEmpty() && payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') {
        // likely to be json, parse it:
        Map<String, Object> claimsMap = (Map<String, Object>) readValue(payload);
        claims = new DefaultClaims(claimsMap);
    }
    // =============== Signature =================
    if (base64UrlEncodedDigest != null) {
        // it is signed - validate the signature
        JwsHeader jwsHeader = (JwsHeader) header;
        SignatureAlgorithm algorithm = null;
        if (header != null) {
            String alg = jwsHeader.getAlgorithm();
            if (Strings.hasText(alg)) {
                algorithm = SignatureAlgorithm.forName(alg);
            }
        }
        if (algorithm == null || algorithm == SignatureAlgorithm.NONE) {
            // it is plaintext, but it has a signature.  This is invalid:
            String msg = "JWT string has a digest/signature, but the header does not reference a valid signature " + "algorithm.";
            throw new MalformedJwtException(msg);
        }
        if (key != null && keyBytes != null) {
            throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either.");
        } else if ((key != null || keyBytes != null) && signingKeyResolver != null) {
            String object = key != null ? "a key object" : "key bytes";
            throw new IllegalStateException("A signing key resolver and " + object + " cannot both be specified. Choose either.");
        }
        // digitally signed, let's assert the signature:
        Key key = this.key;
        if (key == null) {
            // fall back to keyBytes
            byte[] keyBytes = this.keyBytes;
            if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) {
                // use the signingKeyResolver
                if (claims != null) {
                    key = signingKeyResolver.resolveSigningKey(jwsHeader, claims);
                } else {
                    key = signingKeyResolver.resolveSigningKey(jwsHeader, payload);
                }
            }
            if (!Objects.isEmpty(keyBytes)) {
                Assert.isTrue(algorithm.isHmac(), "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");
                key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
            }
        }
        Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed.");
        // re-create the jwt part without the signature.  This is what needs to be signed for verification:
        String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR;
        if (base64UrlEncodedPayload != null) {
            jwtWithoutSignature += base64UrlEncodedPayload;
        }
        JwtSignatureValidator validator;
        try {
            // since 0.10.0: https://github.com/jwtk/jjwt/issues/334
            algorithm.assertValidVerificationKey(key);
            validator = createSignatureValidator(algorithm, key);
        } catch (WeakKeyException e) {
            throw e;
        } catch (InvalidKeyException | IllegalArgumentException e) {
            String algName = algorithm.getValue();
            String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " + "algorithm, but the specified signing key of type " + key.getClass().getName() + " may not be used to validate " + algName + " signatures.  Because the specified " + "signing key reflects a specific and expected algorithm, and the JWT does not reflect " + "this algorithm, it is likely that the JWT was not expected and therefore should not be " + "trusted.  Another possibility is that the parser was configured with the incorrect " + "signing key, but this cannot be assumed for security reasons.";
            throw new UnsupportedJwtException(msg, e);
        }
        if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) {
            String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " + "asserted and should not be trusted.";
            throw new SignatureException(msg);
        }
    }
    final boolean allowSkew = this.allowedClockSkewMillis > 0;
    // since 0.3:
    if (claims != null) {
        final Date now = this.clock.now();
        long nowTime = now.getTime();
        // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4
        // token MUST NOT be accepted on or after any specified exp time:
        Date exp = claims.getExpiration();
        if (exp != null) {
            long maxTime = nowTime - this.allowedClockSkewMillis;
            Date max = allowSkew ? new Date(maxTime) : now;
            if (max.after(exp)) {
                String expVal = DateFormats.formatIso8601(exp, false);
                String nowVal = DateFormats.formatIso8601(now, false);
                long differenceMillis = maxTime - exp.getTime();
                String msg = "JWT expired at " + expVal + ". Current time: " + nowVal + ", a difference of " + differenceMillis + " milliseconds.  Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds.";
                throw new ExpiredJwtException(header, claims, msg);
            }
        }
        // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.5
        // token MUST NOT be accepted before any specified nbf time:
        Date nbf = claims.getNotBefore();
        if (nbf != null) {
            long minTime = nowTime + this.allowedClockSkewMillis;
            Date min = allowSkew ? new Date(minTime) : now;
            if (min.before(nbf)) {
                String nbfVal = DateFormats.formatIso8601(nbf, false);
                String nowVal = DateFormats.formatIso8601(now, false);
                long differenceMillis = nbf.getTime() - minTime;
                String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal + ", a difference of " + differenceMillis + " milliseconds.  Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds.";
                throw new PrematureJwtException(header, claims, msg);
            }
        }
        validateExpectedClaims(header, claims);
    }
    Object body = claims != null ? claims : payload;
    if (base64UrlEncodedDigest != null) {
        return new DefaultJws<>((JwsHeader) header, body, base64UrlEncodedDigest);
    } else {
        return new DefaultJwt<>(header, body);
    }
}
Also used : WeakKeyException(io.jsonwebtoken.security.WeakKeyException) SignatureAlgorithm(io.jsonwebtoken.SignatureAlgorithm) SignatureException(io.jsonwebtoken.security.SignatureException) DefaultJwtSignatureValidator(io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator) JwtSignatureValidator(io.jsonwebtoken.impl.crypto.JwtSignatureValidator) SecretKeySpec(javax.crypto.spec.SecretKeySpec) CompressionCodec(io.jsonwebtoken.CompressionCodec) UnsupportedJwtException(io.jsonwebtoken.UnsupportedJwtException) Claims(io.jsonwebtoken.Claims) ExpiredJwtException(io.jsonwebtoken.ExpiredJwtException) JwsHeader(io.jsonwebtoken.JwsHeader) PrematureJwtException(io.jsonwebtoken.PrematureJwtException) InvalidKeyException(io.jsonwebtoken.security.InvalidKeyException) Date(java.util.Date) Header(io.jsonwebtoken.Header) JwsHeader(io.jsonwebtoken.JwsHeader) Deserializer(io.jsonwebtoken.io.Deserializer) MalformedJwtException(io.jsonwebtoken.MalformedJwtException) Map(java.util.Map) Key(java.security.Key)

Aggregations

Claims (io.jsonwebtoken.Claims)1 CompressionCodec (io.jsonwebtoken.CompressionCodec)1 ExpiredJwtException (io.jsonwebtoken.ExpiredJwtException)1 Header (io.jsonwebtoken.Header)1 JwsHeader (io.jsonwebtoken.JwsHeader)1 MalformedJwtException (io.jsonwebtoken.MalformedJwtException)1 PrematureJwtException (io.jsonwebtoken.PrematureJwtException)1 SignatureAlgorithm (io.jsonwebtoken.SignatureAlgorithm)1 UnsupportedJwtException (io.jsonwebtoken.UnsupportedJwtException)1 DefaultJwtSignatureValidator (io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator)1 JwtSignatureValidator (io.jsonwebtoken.impl.crypto.JwtSignatureValidator)1 Deserializer (io.jsonwebtoken.io.Deserializer)1 InvalidKeyException (io.jsonwebtoken.security.InvalidKeyException)1 SignatureException (io.jsonwebtoken.security.SignatureException)1 WeakKeyException (io.jsonwebtoken.security.WeakKeyException)1 Key (java.security.Key)1 Date (java.util.Date)1 Map (java.util.Map)1 SecretKeySpec (javax.crypto.spec.SecretKeySpec)1