Skip to content

[Bug]: SCEP response signing reuses the configured decrypter key, causing signature verification failures with FIPS-approved-only clients #2800

Description

@tarokon

Steps to Reproduce

  1. 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
}
  1. Start step-ca with this configuration and confirm the SCEP endpoint is reachable:
curl -i "http://localhost/scep/est-core-scep?operation=GetCACaps"
  1. 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.
  2. 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"
  1. 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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

needs triageWaiting for discussion / prioritization by team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions