apereo/cas · error · IllegalArgumentException

Proof JWT algorithm is invalid

Error message

Proof JWT algorithm is invalid

What it means

Thrown by OidcVerifiableCredentialJwtProofValidator.verifyAlgorithm when the proof JWT's alg header is null or equals 'none'. Key-bound proofs must be signed; unsigned JWTs are rejected outright to prevent trivially forged key-possession proofs.

Solutions

  1. Sign the proof JWT with a concrete JWSAlgorithm (e.g. RS256 or ES256) matching the holder key.
  2. Remove any code path that builds the proof with Algorithm.NONE.
  3. Ensure the JWT builder/signer is configured with a real signer before sending the credential request.

Example fix

// before
JWSHeader header = new JWSHeader(JWSAlgorithm.NONE);
// after
JWSHeader header = new JWSHeader(JWSAlgorithm.RS256);
Defensive patterns

Strategy: validation

Validate before calling

JWSAlgorithm alg = signedJwt.getHeader().getAlgorithm();
if (alg == null || Algorithm.NONE.equals(alg)) {
    throw new IllegalArgumentException("Proof must be signed");
}

Type guard

boolean isSignedProof(SignedJWT jwt) { return jwt.getHeader().getAlgorithm() != null && !Algorithm.NONE.equals(jwt.getHeader().getAlgorithm()); }

Prevention

When it happens

Trigger: Building the proof JWT with Algorithm.NONE or leaving the algorithm unset; constructing a SignedJWT without calling sign() with a real JWSAlgorithm; misconfigured JWT library defaulting to unsecured mode.

Common situations: Developers test with unsigned JWTs to inspect claims first and forget to sign; JWT-building helper libraries emitting 'none' when no signer is supplied; disabled signing in a shared proof-builder utility.

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/ef1d7027cbc52424. 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:88

            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");
        }
        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))) {

View on GitHub (pinned to e7288fc434)