apereo/cas · error · IllegalArgumentException

Proof JWT algorithm does not match RSA holder key

Error message

Proof JWT algorithm does not match RSA holder key

What it means

Thrown by OidcVerifiableCredentialJwtProofValidator.verifyAlgorithm when the holder JWK is an RSAKey but the proof JWT's alg header is not in the JWSAlgorithm.RSA family (e.g. an ES256-signed JWT presented against an RSA holder key). The algorithm must be cryptographically consistent with the presented holder key type.

Solutions

  1. Sign the proof with an RSA-family algorithm (RS256/RS384/RS512, PS*) when the holder key is RSA.
  2. Align the signing key with the holder JWK type bound to the request.
  3. Make the proof builder choose the algorithm from the key type instead of hardcoding it.

Example fix

// before
signed.sign(new ECDSASigner(ecKeyPair.getPrivate())); // EC alg, RSA holder key
// after
signed.sign(new RSASSASigner(rsaKeyPair.getPrivate()));
Defensive patterns

Strategy: validation

Validate before calling

if (holderJwk instanceof RSAKey && !JWSAlgorithm.Family.RSA.contains(alg)) {
    throw new IllegalArgumentException("Use an RSA-family algorithm for RSA holder keys");
}

Prevention

When it happens

Trigger: Proof JWT signed with an EC algorithm while the bound holder JWK is RSA; kid resolving to an RSA JWK but the client signing with an EC key; generic proof-builder hardcoding ES256 regardless of key type.

Common situations: Copy-pasted proof code using ES256 defaults while the key material bound to the credential request is RSA; key rotation replacing an EC key with RSA (or vice versa) without updating signing code.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/7e4bc8fa8737590c. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/proof/OidcVerifiableCredentialJwtProofValidator.java:91

            throw new IllegalArgumentException("Proof JWT signature validation failed");
        }
    }

    protected void verifyAudience(final SignedJWT signedJwt) throws ParseException {
        val audiences = signedJwt.getJWTClaimsSet().getAudience();
        val credentialIssuer = casProperties.getAuthn().getOidc().getCore().getIssuer();
        if (audiences == null || !audiences.contains(credentialIssuer)) {
            throw new IllegalArgumentException("Proof audience does not match credential issuer");
        }
    }

    protected void verifyAlgorithm(final SignedJWT signedJwt, final JWK holderJwk) {
        val alg = signedJwt.getHeader().getAlgorithm();
        if (alg == null || Algorithm.NONE.equals(alg)) {
            throw new IllegalArgumentException("Proof JWT algorithm is invalid");
        }
        if (holderJwk instanceof RSAKey && !JWSAlgorithm.Family.RSA.contains(alg)) {
            throw new IllegalArgumentException("Proof JWT algorithm does not match RSA holder key");
        }
        if (holderJwk instanceof ECKey && !JWSAlgorithm.Family.EC.contains(alg)) {
            throw new IllegalArgumentException("Proof JWT algorithm does not match EC holder key");
        }
    }

    protected void verifyFreshness(final SignedJWT signedJwt) throws ParseException {
        val claims = signedJwt.getJWTClaimsSet();
        val issuedAt = claims.getIssueTime();
        if (issuedAt == null) {
            throw new IllegalArgumentException("Proof JWT is missing iat");
        }
        val now = Instant.now(Clock.systemUTC());
        val iat = issuedAt.toInstant();
        if (iat.isAfter(now.plusSeconds(SECONDS_IN_FUTURE))) {
            throw new IllegalArgumentException("Proof iat is in the future");
        }
        if (iat.isBefore(now.minus(Duration.ofMinutes(MINUTES_IN_PAST)))) {

View on GitHub (pinned to e7288fc434)