Steps to Reproduce
- Configure a SCEP provisioner with a custom decrypter certificate/key so that decryption uses a dedicated RSA key pair instead of the CA's default signer key:
{
"type": "SCEP",
"name": "est-core-scep",
"decrypterCertificate": "<base64 PEM>",
"decrypterKeyPEM": "<base64 PEM>",
"encryptionAlgorithmIdentifier": 2
}
- Start step-ca with this configuration and confirm the SCEP endpoint is reachable:
curl -i "http://localhost/scep/est-core-scep?operation=GetCACaps"
-
Submit a SCEP PKIOperation request (PKCSReq) from a client whose CMS/PKCS#7 implementation runs in a FIPS-approved-only mode (for example, Bouncy Castle FIPS with org.bouncycastle.fips.approved_only=true), where the client:
- Encrypts the request to the configured decrypterCertificate.
- Verifies the signature on the returned
CertRep using the CA certificate chain obtained from GetCACert.
-
Observe step-ca's log for the request — it shows status=200 and a successfully issued certificate:
method=POST path="/scep/est-core-scep/pkiclient.exe?operation=PKIOperation"
status=200 subject=Aruba-Gateway-01 issuer="EstScepCA Intermediate CA"
- Observe the SCEP client attempting to verify the
CertRep signature.
Your Environment
-
step-ca version: custom build from master (commit-equivalent to the current scep.go implementation using DecrypterKeyPEM/DecrypterKeyURI for both decryption and signing)
-
OS: Ubuntu Linux (host running step-ca as a native binary)
-
CA intermediate: RSA 3072-bit
-
SCEP decrypter: separate self-signed RSA 3072-bit certificate/key (CN=est-core-scep-decrypter), configured via decrypterCertificate + decrypterKeyPEM
-
SCEP client: Java 17 SCEP implementation backed by Bouncy Castle FIPS (BC-FIPS), running with -Dorg.bouncycastle.fips.approved_only=true
-
Client CMS verification library: Bouncy Castle CMSSignedData / SignerInformation.verify(...)
Expected Behavior
The SCEP CertRep response should be signed with the CA's normal signing key (the intermediate/CA signer), independent of whichever key is configured as the SCEP decrypter. The client should be able to verify the CertRep signature using the CA certificate chain returned by GetCACert, and successfully retrieve the issued certificate.
Actual Behavior
The client fails to verify the SCEP response signature with a FIPS key-usage-separation error, because step-ca signed the CertRep using the same RSA key pair configured as the SCEP decrypter:
org.bouncycastle.crypto.IllegalKeyException: Attempt to sign/verify with RSA modulus already used for encrypt/decrypt.
at org.bouncycastle.crypto.fips.FipsRSA$SignatureOperatorFactory.createVerifier(Unknown Source)
at org.bouncycastle.jcajce.provider.BaseSignature.initVerify(Unknown Source)
at org.bouncycastle.cms.SignerInformationVerifier.getContentVerifier(Unknown Source)
at org.bouncycastle.cms.SignerInformation.doVerify(Unknown Source)
at org.bouncycastle.cms.SignerInformation.verify(Unknown Source)
...
Inspecting the step-ca source confirms the cause: when DecrypterKeyPEM/DecrypterKeyURI is configured, SCEP.Init also creates a signer from that same key material and sets signerCertificate = decrypterCertificate, so GetSigner() returns the decrypter's key/certificate for the response signature instead of the CA's default signer:
if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKeyPEM: s.DecrypterKeyPEM, // TODO(hs): support distinct signer key in the future?
...
})
...
// the decrypter certificate is also the signer certificate
s.signerCertificate = s.decrypterCertificate
Note that the SCEP transaction itself succeeds end-to-end on the CA side (status=200, valid issued certificate) — the defect is specifically that the response is signed with a key that has already been used for decryption, which a FIPS-approved-only client correctly refuses to trust for verification.
Additional Context
-
This is a distinct failure mode from pkcs7: no enveloped recipient for provided certificate (client/CA decrypter-certificate mismatch) — in this case decryption succeeds and only the signing side is affected.
-
A local workaround was validated: removing the CreateSigner(...) calls tied to DecrypterKeyPEM/DecrypterKeyURI, and removing s.signerCertificate = s.decrypterCertificate, so that GetSigner() falls through to the authority's default signer (a.defaultSigner/a.signerCertificate) via the existing selectSigner fallback in authority.go. This was confirmed to resolve the issue: the CA then signs CertRep with the intermediate key while still decrypting requests with the dedicated decrypter key, and the FIPS client verifies successfully.
-
Relevant existing code paths:
-
scep.go — GetSigner(), GetDecrypter()
-
authority.go — selectSigner, selectDecrypter
-
Related but distinct upstream discussion: #1723 — Get a certificate via SCEP fails with: scep post request failed: crypto/rsa: verification error, which covers decrypter/recipient-certificate mismatches rather than this signer/decrypter key-reuse issue.
Contributing
Vote on this issue by adding a 👍 reaction.
To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).
Steps to Reproduce
Submit a SCEP
PKIOperationrequest (PKCSReq) from a client whose CMS/PKCS#7 implementation runs in a FIPS-approved-only mode (for example, Bouncy Castle FIPS withorg.bouncycastle.fips.approved_only=true), where the client:CertRepusing the CA certificate chain obtained fromGetCACert.Observe step-ca's log for the request — it shows
status=200and a successfully issued certificate:CertRepsignature.Your Environment
step-ca version: custom build from
master(commit-equivalent to the current scep.go implementation using DecrypterKeyPEM/DecrypterKeyURI for both decryption and signing)OS: Ubuntu Linux (host running step-ca as a native binary)
CA intermediate: RSA 3072-bit
SCEP decrypter: separate self-signed RSA 3072-bit certificate/key (
CN=est-core-scep-decrypter), configured via decrypterCertificate +decrypterKeyPEMSCEP client: Java 17 SCEP implementation backed by Bouncy Castle FIPS (
BC-FIPS), running with-Dorg.bouncycastle.fips.approved_only=trueClient CMS verification library: Bouncy Castle CMSSignedData / SignerInformation.verify(...)
Expected Behavior
The SCEP
CertRepresponse should be signed with the CA's normal signing key (the intermediate/CA signer), independent of whichever key is configured as the SCEP decrypter. The client should be able to verify theCertRepsignature using the CA certificate chain returned byGetCACert, and successfully retrieve the issued certificate.Actual Behavior
The client fails to verify the SCEP response signature with a FIPS key-usage-separation error, because step-ca signed the
CertRepusing the same RSA key pair configured as the SCEP decrypter:Inspecting the step-ca source confirms the cause: when
DecrypterKeyPEM/DecrypterKeyURIis configured, SCEP.Init also creates a signer from that same key material and setssignerCertificate = decrypterCertificate, soGetSigner()returns the decrypter's key/certificate for the response signature instead of the CA's default signer:Note that the SCEP transaction itself succeeds end-to-end on the CA side (
status=200, valid issued certificate) — the defect is specifically that the response is signed with a key that has already been used for decryption, which a FIPS-approved-only client correctly refuses to trust for verification.Additional Context
This is a distinct failure mode from
pkcs7: no enveloped recipient for provided certificate(client/CA decrypter-certificate mismatch) — in this case decryption succeeds and only the signing side is affected.A local workaround was validated: removing the
CreateSigner(...)calls tied toDecrypterKeyPEM/DecrypterKeyURI, and removings.signerCertificate = s.decrypterCertificate, so thatGetSigner()falls through to the authority's default signer (a.defaultSigner/a.signerCertificate) via the existingselectSignerfallback inauthority.go. This was confirmed to resolve the issue: the CA then signsCertRepwith the intermediate key while still decrypting requests with the dedicated decrypter key, and the FIPS client verifies successfully.Relevant existing code paths:
scep.go — GetSigner(), GetDecrypter()
authority.go — selectSigner, selectDecrypter
Related but distinct upstream discussion: #1723 — Get a certificate via SCEP fails with: scep post request failed: crypto/rsa: verification error, which covers decrypter/recipient-certificate mismatches rather than this signer/decrypter key-reuse issue.
Contributing
Vote on this issue by adding a 👍 reaction.
To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).