apereo/cas · critical · BeanCreationException
Public and private keys do not match
Error message
Public and private keys do not match
What it means
BasicX509CredentialFactoryBean builds a SAML X509Credential from a configured certificate and optional private key. When both are configured, it verifies they form a matching key pair via KeySupport.matchKeyPair before creating the bean; if the certificate's public key does not correspond to the private key, the Spring bean cannot be created and this BeanCreationException is thrown. This fails fast at application startup rather than producing a credential that would sign/assert with mismatched material.
Solutions
- Regenerate or re-export the certificate from the private key so the public key matches (e.g. openssl x509 -pubkey to compare with the key's public part).
- Verify you are pointing entityCertificate and privateKeyResource at files from the SAME keypair: compare fingerprints/subject of the cert against the key.
- If the certificate was renewed, replace the private key file with the new one generated together with the certificate.
- If only a certificate is needed (e.g. for signature validation only), remove the privateKeyResource so BasicX509Credential(cert) is built without the pair check.
Example fix
# before (mismatched files) cas.authn.saml.idp.credential.entity-certificate=file:/etc/cas/old-idp.crt cas.authn.saml.idp.credential.private-key=file:/etc/cas/renewed-idp.key # after (cert and key from the same keypair) cas.authn.saml.idp.credential.entity-certificate=file:/etc/cas/renewed-idp.crt cas.authn.saml.idp.credential.private-key=file:/etc/cas/renewed-idp.key
Defensive patterns
Strategy: validation
Validate before calling
import org.opensaml.security.x509.KeySupport;
// before creating the credential factory bean, pre-check the pair
try (var is = privateKeyResource.getInputStream()) {
var key = KeySupport.decodePrivateKey(is, password);
var certs = X509Support.decodeCertificates(entityResource.getInputStream());
if (certs.size() != 1 || !KeySupport.matchKeyPair(certs.get(0).getPublicKey(), key)) {
throw new IllegalStateException("entityCertificate and privateKey are not a matching pair");
}
} Try / catch
// optional: fail with a clearer message at startup
try {
context.getBean(BasicX509Credential.class);
} catch (BeanCreationException e) {
if (e.getMessage().contains("Public and private keys do not match")) {
throw new IllegalStateException("Check that entityCertificate and privateKeyResource belong to the same keypair", e);
}
throw e;
} Prevention
- Always generate the certificate and key together (one keytool/openssl invocation) and store them as a matched pair.
- After any cert renewal, re-verify the pair: compare `openssl x509 -pubkey` output with `openssl pkey -pubout` of the private key.
- Keep per-environment directories (dev/, prod/) so a cert from one env cannot be mixed with a key from another.
- Let the bean fail at startup (it does) and add a smoke test that builds the credential in CI with the real files.
When it happens
Trigger: Both entityCertificate (or certificateResources) and privateKeyResource are configured, and KeySupport.matchKeyPair(entityCertificate.getPublicKey(), privateKey) returns false during getObject() — i.e. the decoded public key does not pair with the decoded private key.
Common situations: Copying a certificate meant for a different service alongside the real private key; renewing the certificate but leaving the old private key (or vice versa); PEM files with multiple blocks where the wrong block is picked; a typo'd path pointing at a stale key; reusing one keypair config across environments (dev cert, prod key).
Related errors
- Configuration element indicated an entityCertificate, but…
- Could not decode provided Entity Certificate file
- Could not decode provided CertificateFile:
- Could not decode provided KeyFile
- No Certificates provided
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/9256fdc15637fa37.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-saml-core-api/src/main/java/org/apereo/cas/support/saml/util/credential/BasicX509CredentialFactoryBean.java:86
val certificates = getCertificates();
if (certificates.isEmpty()) {
throw new BeanCreationException("No Certificates provided");
}
var entityCertificate = getEntityCertificate();
if (null == entityCertificate) {
entityCertificate = certificates.getFirst();
}
val privateKey = getPrivateKey();
var credential = (BasicX509Credential) null;
if (null == privateKey) {
credential = new BasicX509Credential(entityCertificate);
} else {
credential = new BasicX509Credential(entityCertificate, privateKey);
if (!KeySupport.matchKeyPair(entityCertificate.getPublicKey(), privateKey)) {
throw new BeanCreationException("Public and private keys do not match");
}
}
credential.setEntityCertificateChain(certificates);
val crls = getCRLs();
if (null != crls && !crls.isEmpty()) {
credential.setCRLs(crls);
}
if (null != getUsageType()) {
credential.setUsageType(UsageType.valueOf(getUsageType()));
}
if (null != getEntityID()) {
credential.setEntityId(getEntityID());
}
View on GitHub (pinned to e7288fc434)