{"record":{"id":"9256fdc15637fa37","repo":"apereo/cas","slug":"public-and-private-keys-do-not-match","errorCode":null,"errorMessage":"Public and private keys do not match","messagePattern":"Public and private keys do not match","errorType":"exception","errorClass":"BeanCreationException","httpStatus":null,"severity":"critical","filePath":"support/cas-server-support-saml-core-api/src/main/java/org/apereo/cas/support/saml/util/credential/BasicX509CredentialFactoryBean.java","lineNumber":86,"sourceCode":"        val certificates = getCertificates();\n        if (certificates.isEmpty()) {\n            throw new BeanCreationException(\"No Certificates provided\");\n        }\n\n        var entityCertificate = getEntityCertificate();\n        if (null == entityCertificate) {\n            entityCertificate = certificates.getFirst();\n        }\n\n        val privateKey = getPrivateKey();\n        var credential = (BasicX509Credential) null;\n        if (null == privateKey) {\n            credential = new BasicX509Credential(entityCertificate);\n        } else {\n            credential = new BasicX509Credential(entityCertificate, privateKey);\n\n            if (!KeySupport.matchKeyPair(entityCertificate.getPublicKey(), privateKey)) {\n                throw new BeanCreationException(\"Public and private keys do not match\");\n            }\n        }\n\n        credential.setEntityCertificateChain(certificates);\n\n        val crls = getCRLs();\n        if (null != crls && !crls.isEmpty()) {\n            credential.setCRLs(crls);\n        }\n\n        if (null != getUsageType()) {\n            credential.setUsageType(UsageType.valueOf(getUsageType()));\n        }\n\n        if (null != getEntityID()) {\n            credential.setEntityId(getEntityID());\n        }\n","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-saml-core-api/src/main/java/org/apereo/cas/support/saml/util/credential/BasicX509CredentialFactoryBean.java#L68-L104","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"# before (mismatched files)\ncas.authn.saml.idp.credential.entity-certificate=file:/etc/cas/old-idp.crt\ncas.authn.saml.idp.credential.private-key=file:/etc/cas/renewed-idp.key\n# after (cert and key from the same keypair)\ncas.authn.saml.idp.credential.entity-certificate=file:/etc/cas/renewed-idp.crt\ncas.authn.saml.idp.credential.private-key=file:/etc/cas/renewed-idp.key","handlingStrategy":"validation","validationCode":"import org.opensaml.security.x509.KeySupport;\n// before creating the credential factory bean, pre-check the pair\ntry (var is = privateKeyResource.getInputStream()) {\n    var key = KeySupport.decodePrivateKey(is, password);\n    var certs = X509Support.decodeCertificates(entityResource.getInputStream());\n    if (certs.size() != 1 || !KeySupport.matchKeyPair(certs.get(0).getPublicKey(), key)) {\n        throw new IllegalStateException(\"entityCertificate and privateKey are not a matching pair\");\n    }\n}","typeGuard":null,"tryCatchPattern":"// optional: fail with a clearer message at startup\ntry {\n    context.getBean(BasicX509Credential.class);\n} catch (BeanCreationException e) {\n    if (e.getMessage().contains(\"Public and private keys do not match\")) {\n        throw new IllegalStateException(\"Check that entityCertificate and privateKeyResource belong to the same keypair\", e);\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["spring","saml","keystore","x509","startup-failure"],"backgroundTag":"key-pair-mismatch","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}