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

  1. 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).
  2. Verify you are pointing entityCertificate and privateKeyResource at files from the SAME keypair: compare fingerprints/subject of the cert against the key.
  3. If the certificate was renewed, replace the private key file with the new one generated together with the certificate.
  4. 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

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


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)