keycloak/keycloak · critical · VerificationException

Invalid jws signature

Error message

Invalid jws signature

What it means

Thrown by JwsToken.verifySignature() (line 93) when verifier.verify(encodedSignatureInput, signature) returns false: the signature does not validate against the signing input using the verifier's public key. The token was tampered with, signed by a different key, or corrupted. This is a security-critical rejection.

Source

Thrown at core/src/main/java/org/keycloak/sdjwt/JwsToken.java:93

        }
        return jws;
    }

    public void verifySignature(SignatureVerifierContext verifier) throws VerificationException {
        Objects.requireNonNull(verifier, "verifier must not be null");
        String headerAlgorithm = jwsHeader == null || jwsHeader.getAlgorithm() == null
                ? null
                : jwsHeader.getRawAlgorithm();
        String verifierAlgorithm = verifier.getAlgorithm();
        if (headerAlgorithm == null || verifierAlgorithm == null || !headerAlgorithm.equals(verifierAlgorithm)) {
            throw new VerificationException(String.format(
                    "JWS header algorithm '%s' does not match verifier algorithm '%s'",
                    headerAlgorithm, verifierAlgorithm));
        }
        try {
            if (!verifier.verify(jwsInput.getEncodedSignatureInput().getBytes(StandardCharsets.UTF_8),
                                 jwsInput.getSignature())) {
                throw new VerificationException("Invalid jws signature");
            }
        } catch (Exception e) {
            throw new VerificationException(e);
        }
    }

    public Optional<String> getSdHashAlgorithm() {
        return Optional.ofNullable(payload.get(OID4VCConstants.CLAIM_NAME_SD_HASH_ALGORITHM))
                       .map(JsonNode::textValue);
    }

    public String getJws() {
        return jws;
    }

    public void setJws(String jws) {
        this.jws = jws;
    }

View on GitHub (pinned to 66c7e15a37)

Solutions

  1. Confirm the verifier uses the issuer's current public key (refresh the JWKS).
  2. Check the JWS string is not truncated or altered by whitespace/encoding.
  3. For key binding, ensure the holder key matches the cnf.jwk in the SD-JWT VC.
  4. Verify the signing input is the UTF-8 bytes of the encoded signature input (header.payload).
Defensive patterns

Strategy: try-catch

Validate before calling

// No meaningful pre-check: signature validity is the verifier's job. Ensure the right key first.
public static SignatureVerifierContext loadCurrentIssuerVerifier(JwkParsingSource source) {
    // refresh JWKS so the key is current, then build the verifier
    return source.refreshAndBuild();
}

Try / catch

// Signature failure is security-critical; never retry with relaxed checks.
try {
    token.verifySignature(verifier);
} catch (VerificationException e) {
    // treat as invalid credential — do not fall back to a different key to 'make it pass'
    throw e;
}

Prevention

When it happens

Trigger: The token's signature segment was modified; the verifier holds a stale or wrong issuer key; a token signed by one issuer is verified against another's key; the JWS string is truncated; for key binding, the holder signed with a key different from cnf.jwk.

Common situations: Wrong issuer JWKS configured; key rotation without JWKS refresh; man-in-the-middle tampering; copy-paste truncation of the JWS; holder key binding JWT signed by a different holder key than the cnf.jwk in the credential.

Related errors


AI-assisted analysis of keycloak/keycloak@66c7e15a37 (2026-08-14). Data as JSON: /api/errors/9e098e5ba58a5485. Report an issue: GitHub.