apereo/cas · error · IllegalArgumentException

Proof JWT signature validation failed

Error message

Proof JWT signature validation failed

What it means

Thrown by OidcVerifiableCredentialJwtProofValidator.verifySignature when the holder's proof JWT cannot be verified: no verifier could be created because the holderJwk is neither an RSAKey nor an ECKey, or SignedJWT.verify(verifier) returns false. This proves control check ensures the credential requester actually possesses the private key of the bound holder key.

Solutions

  1. Sign the proof JWT with the private key corresponding exactly to the holder JWK / kid bound to the credential request.
  2. Use an RSA or EC JWK as the holder key; other key types are not accepted by this validator.
  3. Verify the kid in the JWT header resolves to the intended JWK and that the JWK is well-formed.
  4. Re-check the signing algorithm and library settings to ensure the signature covers the exact JWT content.

Example fix

// before
SignedJWT signed = new SignedJWT(header, claims); // never signed
// after
SignedJWT signed = new SignedJWT(header, claims);
signed.sign(new RSASSASigner(rsaKeyPair.getPrivate()));
Defensive patterns

Strategy: try-catch

Validate before calling

if (!(holderJwk instanceof RSAKey) && !(holderJwk instanceof ECKey)) {
    throw new IllegalArgumentException("Holder key must be RSA or EC");
}

Type guard

boolean isSupportedHolderKey(JWK jwk) { return jwk instanceof RSAKey || jwk instanceof ECKey; }

Try / catch

try {
    validator.validate(signedJwt, holderJwk);
} catch (IllegalArgumentException e) {
    log.warn("Proof signature validation failed: re-sign with the holder private key");
}

Prevention

When it happens

Trigger: Proof JWT signed with a key that does not match the key (kid/JWK) presented or bound to the request; proof signed with the wrong private key; JWK of an unsupported type (e.g. oct/symmetric or OKP Ed25519 key) passed to the validator; corrupted or tampered JWT.

Common situations: Client signs the proof with a test key but references a different key in cnf/jkt; unsupported key types (symmetric, Edwards-curve) supplied as holder JWKs; kid lookup returning the wrong JWK; JWT payload modified after signing.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/4542188e01d95176. 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:73

    protected @Nullable String verifyNonce(final SignedJWT signedJwt) throws Exception {
        val claims = signedJwt.getJWTClaimsSet();
        val nonce = claims.getStringClaim("nonce");
        if (nonce == null || !oidcVerifiableCredentialNonceService.exists(nonce)) {
            throw new IllegalArgumentException("Proof nonce %s is invalid or missing".formatted(nonce));
        }
        return nonce;
    }

    protected void verifySignature(final SignedJWT signedJwt, final JWK holderJwk) throws Exception {
        JWSVerifier verifier = null;
        if (holderJwk instanceof final RSAKey rsaKey) {
            verifier = new RSASSAVerifier(rsaKey);
        } else if (holderJwk instanceof final ECKey ecKey) {
            verifier = new ECDSAVerifier(ecKey);
        }
        if (verifier == null || !signedJwt.verify(verifier)) {
            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");

View on GitHub (pinned to e7288fc434)