Critical severity10.0NVD Advisory· Published Mar 19, 2026· Updated Apr 27, 2026
CVE-2026-30836
CVE-2026-30836
Description
Step CA is an online certificate authority for secure, automated certificate management for DevOps. Versions 0.30.0-rc6 and below do not safeguard against unauthenticated certificate issuance through the SCEP UpdateReq. This issue has been fixed in version 0.30.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/smallstep/certificatesGo | < 0.30.0 | 0.30.0 |
Affected products
7cpe:2.3:a:smallstep:step-ca:0.30.0:rc1:*:*:*:go:*:*+ 6 more
- cpe:2.3:a:smallstep:step-ca:0.30.0:rc1:*:*:*:go:*:*
- cpe:2.3:a:smallstep:step-ca:0.30.0:rc2:*:*:*:go:*:*
- cpe:2.3:a:smallstep:step-ca:0.30.0:rc3:*:*:*:go:*:*
- cpe:2.3:a:smallstep:step-ca:0.30.0:rc4:*:*:*:go:*:*
- cpe:2.3:a:smallstep:step-ca:0.30.0:rc5:*:*:*:go:*:*
- cpe:2.3:a:smallstep:step-ca:0.30.0:rc6:*:*:*:go:*:*
- cpe:2.3:a:smallstep:step-ca:*:*:*:*:*:go:*:*range: <0.30.0
Patches
1e6da031d5125Add scep integration tests
9 files changed · +196 −133
scep/api/api.go+3 −0 modified@@ -395,6 +395,9 @@ func PKIOperation(ctx context.Context, req request) (Response, error) { return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, scepErr.Error(), scepErr) } signCSROpts = append(signCSROpts, challengeOptions...) + } else { + scepErr := fmt.Errorf("unexpected message type: (%s)", string(msg.MessageType)) + return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, scepErr.Error(), scepErr) } // TODO: authorize renewal: we can authorize renewals with the challenge password (if reusable secrets are used).
scep/authority.go+3 −4 modified@@ -213,7 +213,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err } msg.CertRepMessage.Certificate = certs[0] return nil - case smallscep.PKCSReq, smallscep.UpdateReq, smallscep.RenewalReq: + case smallscep.PKCSReq, smallscep.RenewalReq: csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) if err != nil { return fmt.Errorf("parse CSR from pkiEnvelope: %w", err) @@ -232,11 +232,10 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err ChallengePassword: cp, } return nil - case smallscep.GetCRL, smallscep.GetCert, smallscep.CertPoll: + default: + // this is for e.g. GetCRL, GetCert and CertPoll return errors.New("not implemented") } - - return nil } // SignCSR creates an x509.Certificate based on a CSR template and Cert Authority credentials
test/integration/scep/common_test.go+109 −119 modified@@ -28,6 +28,7 @@ import ( "github.com/smallstep/pkcs7" "github.com/smallstep/scep" + scepx509util "github.com/smallstep/scep/x509util" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/pemutil" @@ -130,7 +131,7 @@ func newTestCA(t *testing.T, name string) *testCA { Name: "scep", Type: "SCEP", ForceCN: false, - ChallengePassword: "", + ChallengePassword: "the-challenge", EncryptionAlgorithmIdentifier: 2, MinimumPublicKeyLength: 2048, Claims: &config.GlobalProvisionerClaims, @@ -235,52 +236,132 @@ func (c *client) getCACert(t *testing.T) error { return nil } -func (c *client) requestCertificate(t *testing.T, commonName string, sans []string) (*x509.Certificate, error) { +type certificateParserFunc = func(der []byte) (*x509.Certificate, error) + +type option func(o *options) + +type options struct { + commonName string + sans []string + challenge string + template *x509.Certificate + signer crypto.Signer + messageType scep.MessageType + certificateParser certificateParserFunc +} + +func withChallenge(challenge string) option { + return func(o *options) { + o.challenge = challenge + } +} + +func withTemplate(tmpl *x509.Certificate) option { + return func(o *options) { + o.template = tmpl + } +} + +func withSigner(signer crypto.Signer) option { + return func(o *options) { + o.signer = signer + } +} + +func withMessageType(messageType scep.MessageType) option { + return func(o *options) { + o.messageType = messageType + } +} + +func withCertificateParser(certificateParser certificateParserFunc) option { + return func(o *options) { + o.certificateParser = certificateParser + } +} + +func (c *client) requestCertificate(t *testing.T, opts ...option) (*x509.Certificate, error) { + o := &options{ + commonName: "test.localhost", + sans: []string{"test.localhost"}, + challenge: "the-challenge", + messageType: scep.PKCSReq, + certificateParser: x509.ParseCertificate, + } + for _, applyTo := range opts { + applyTo(o) + } + if err := c.getCACert(t); err != nil { return nil, fmt.Errorf("failed getting CA certificate: %w", err) } - signer, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, fmt.Errorf("failed creating SCEP private key: %w", err) + var ( + signer = o.signer + tmpl = o.template + err error + ) + if signer == nil { + signer, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, fmt.Errorf("failed creating SCEP private key: %w", err) + } } - csr, err := x509util.CreateCertificateRequest(commonName, sans, signer) + csr, err := x509util.CreateCertificateRequest(o.commonName, o.sans, signer) if err != nil { return nil, fmt.Errorf("failed creating CSR: %w", err) } - tmpl := &x509.Certificate{ - Subject: csr.Subject, - PublicKey: signer.Public(), - SerialNumber: big.NewInt(1), - NotBefore: time.Now().Add(-1 * time.Hour), - NotAfter: time.Now().Add(1 * time.Hour), - DNSNames: csr.DNSNames, - IPAddresses: csr.IPAddresses, - EmailAddresses: csr.EmailAddresses, - URIs: csr.URIs, + if tmpl == nil { + tmpl = &x509.Certificate{ + Subject: csr.Subject, + PublicKey: signer.Public(), + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(1 * time.Hour), + DNSNames: csr.DNSNames, + IPAddresses: csr.IPAddresses, + EmailAddresses: csr.EmailAddresses, + URIs: csr.URIs, + } + } + + crTmpl := &scepx509util.CertificateRequest{ + CertificateRequest: *csr, + ChallengePassword: o.challenge, + } + + newCSR, err := scepx509util.CreateCertificateRequest(rand.Reader, crTmpl, signer) + if err != nil { + return nil, fmt.Errorf("failed creating csr: %w", err) + } + + cr, err := x509.ParseCertificateRequest(newCSR) + if err != nil { + return nil, fmt.Errorf("failed parsing certificate request: %w", err) } selfSigned, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, signer.Public(), signer) if err != nil { return nil, fmt.Errorf("failed creating self signed certificate: %w", err) } - selfSignedCertificate, err := x509.ParseCertificate(selfSigned) + + selfSignedCertificate, err := o.certificateParser(selfSigned) if err != nil { return nil, fmt.Errorf("failed parsing self signed certificate: %w", err) } msgTmpl := &scep.PKIMessage{ TransactionID: "test-1", - MessageType: scep.PKCSReq, + MessageType: o.messageType, SenderNonce: []byte("test-nonce-1"), Recipients: []*x509.Certificate{c.caCert}, SignerCert: selfSignedCertificate, SignerKey: signer, } - msg, err := scep.NewCSRRequest(csr, msgTmpl) + msg, err := scep.NewCSRRequest(cr, msgTmpl) if err != nil { return nil, fmt.Errorf("failed creating SCEP PKCSReq message: %w", err) } @@ -351,29 +432,14 @@ type pkcs1PublicKey struct { E int } -type parseFunc = func(der []byte) (*x509.Certificate, error) - -func (c *client) requestCertificateEmulatingWindowsClient(t *testing.T, commonName string, sans []string, parseCertificate parseFunc) (*x509.Certificate, error) { - if err := c.getCACert(t); err != nil { - return nil, fmt.Errorf("failed getting CA certificate: %w", err) - } - - signer, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, fmt.Errorf("failed creating SCEP private key: %w", err) - } - - csr, err := x509util.CreateCertificateRequest(commonName, sans, signer) - if err != nil { - return nil, fmt.Errorf("failed creating CSR: %w", err) - } +func createWindowsTemplate(t *testing.T, signer *rsa.PrivateKey) *x509.Certificate { + t.Helper() // on Windows the self-signed certificate contains an authority key identifier // extension that is marked critical value, err := asn1.Marshal(authorityKeyID{[]byte("bla")}) // fake value - if err != nil { - return nil, fmt.Errorf("failed marshaling authority key ID") - } + require.NoError(t, err) + authorityKeyIDExtension := pkix.Extension{ Id: oidExtensionAuthorityKeyID, Critical: true, @@ -385,24 +451,21 @@ func (c *client) requestCertificateEmulatingWindowsClient(t *testing.T, commonNa N: signer.N, E: signer.E, }) - if err != nil { - return nil, fmt.Errorf("failed marshaling RSA public key: %w", err) - } + require.NoError(t, err) h := sha1.Sum(publicKeyBytes) subjectKeyID := h[:] // create subject key ID extension value, err = asn1.Marshal(subjectKeyID) - if err != nil { - return nil, fmt.Errorf("failed marshaling subject key ID: %w", err) - } + require.NoError(t, err) + subjectKeyIDExtension := pkix.Extension{ Id: oidExtensionSubjectKeyID, Value: value, } - tmpl := &x509.Certificate{ + return &x509.Certificate{ Subject: pkix.Name{CommonName: "SCEP Protocol Certificate"}, SignatureAlgorithm: x509.SHA1WithRSA, PublicKey: signer.Public(), @@ -411,80 +474,6 @@ func (c *client) requestCertificateEmulatingWindowsClient(t *testing.T, commonNa NotAfter: time.Now().Add(365 * 24 * time.Hour), ExtraExtensions: []pkix.Extension{authorityKeyIDExtension, subjectKeyIDExtension}, } - - selfSignedDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, signer.Public(), signer) - if err != nil { - return nil, fmt.Errorf("failed creating self signed certificate: %w", err) - } - selfSignedCertificate, err := parseCertificate(selfSignedDER) - if err != nil { - return nil, fmt.Errorf("failed parsing self signed certificate: %w", err) - } - - msgTmpl := &scep.PKIMessage{ - TransactionID: "test-1", - MessageType: scep.PKCSReq, - SenderNonce: []byte("test-nonce-1"), - Recipients: []*x509.Certificate{c.caCert}, - SignerCert: selfSignedCertificate, - SignerKey: signer, - } - - msg, err := scep.NewCSRRequest(csr, msgTmpl) - if err != nil { - return nil, fmt.Errorf("failed creating SCEP PKCSReq message: %w", err) - } - - t.Log(string(msg.Raw)) - - u, err := url.Parse(c.caURL) - if err != nil { - return nil, fmt.Errorf("failed parsing CA URL: %w", err) - } - - opURL := u.ResolveReference(&url.URL{RawQuery: fmt.Sprintf("operation=PKIOperation&message=%s", url.QueryEscape(base64.StdEncoding.EncodeToString(msg.Raw)))}) - resp, err := c.httpClient.Get(opURL.String()) - if err != nil { - return nil, fmt.Errorf("failed get request: %w", err) - } - defer resp.Body.Close() - - if ct := resp.Header.Get("Content-Type"); ct != "application/x-pki-message" { - return nil, fmt.Errorf("received unexpected content type %q", ct) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed reading response body: %w", err) - } - - t.Log(string(body)) - - signedData, err := pkcs7.Parse(body) - if err != nil { - return nil, fmt.Errorf("failed parsing response body: %w", err) - } - - // TODO: verify the signature? - - p7, err := pkcs7.Parse(signedData.Content) - if err != nil { - return nil, fmt.Errorf("failed decrypting inner p7: %w", err) - } - - content, err := p7.Decrypt(selfSignedCertificate, signer) - if err != nil { - return nil, fmt.Errorf("failed decrypting response: %w", err) - } - - p7, err = pkcs7.Parse(content) - if err != nil { - return nil, fmt.Errorf("failed parsing p7 content: %w", err) - } - - cert := p7.Certificates[0] - - return cert, nil } type testCAS struct { @@ -502,6 +491,7 @@ func (c *testCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 CertificateChain: []*x509.Certificate{cert, c.ca.Intermediate}, }, nil } + func (c *testCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { return nil, errors.New("not implemented") }
test/integration/scep/decrypter_cas_test.go+2 −2 modified@@ -86,7 +86,7 @@ func TestIssuesCertificateUsingSCEPWithDecrypterAndUpstreamCAS(t *testing.T) { Name: "scep", Type: "SCEP", ForceCN: false, - ChallengePassword: "", + ChallengePassword: "the-challenge", EncryptionAlgorithmIdentifier: 2, MinimumPublicKeyLength: 2048, Claims: &config.GlobalProvisionerClaims, @@ -136,7 +136,7 @@ func TestIssuesCertificateUsingSCEPWithDecrypterAndUpstreamCAS(t *testing.T) { requireHealthyCA(t, caClient) scepClient := createSCEPClient(t, fmt.Sprintf("https://localhost:%s/scep/scep", port), m.Root) - cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + cert, err := scepClient.requestCertificate(t) assert.NoError(t, err) require.NotNil(t, cert)
test/integration/scep/decrypter_test.go+2 −2 modified@@ -84,7 +84,7 @@ func TestIssuesCertificateUsingSCEPWithDecrypter(t *testing.T) { Name: "scep", Type: "SCEP", ForceCN: false, - ChallengePassword: "", + ChallengePassword: "the-challenge", EncryptionAlgorithmIdentifier: 2, MinimumPublicKeyLength: 2048, Claims: &config.GlobalProvisionerClaims, @@ -126,7 +126,7 @@ func TestIssuesCertificateUsingSCEPWithDecrypter(t *testing.T) { requireHealthyCA(t, caClient) scepClient := createSCEPClient(t, fmt.Sprintf("https://localhost:%s/scep/scep", port), m.Root) - cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + cert, err := scepClient.requestCertificate(t) assert.NoError(t, err) require.NotNil(t, cert)
test/integration/scep/regular_cas_test.go+2 −2 modified@@ -57,7 +57,7 @@ func TestFailsIssuingCertificateUsingRegularSCEPWithUpstreamCAS(t *testing.T) { Name: "scep", Type: "SCEP", ForceCN: false, - ChallengePassword: "", + ChallengePassword: "the-challenge", EncryptionAlgorithmIdentifier: 2, MinimumPublicKeyLength: 2048, Claims: &config.GlobalProvisionerClaims, @@ -106,7 +106,7 @@ func TestFailsIssuingCertificateUsingRegularSCEPWithUpstreamCAS(t *testing.T) { // issuance is expected to fail when an upstream CAS is configured, as the current // CAS interfaces do not support providing a decrypter. scepClient := createSCEPClient(t, fmt.Sprintf("https://localhost:%s/scep/scep", port), m.Root) - cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + cert, err := scepClient.requestCertificate(t) assert.Error(t, err) assert.Nil(t, cert)
test/integration/scep/regular_test.go+59 −1 modified@@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/smallstep/scep" ) func TestIssuesCertificateUsingRegularSCEPConfiguration(t *testing.T) { @@ -26,7 +28,7 @@ func TestIssuesCertificateUsingRegularSCEPConfiguration(t *testing.T) { requireHealthyCA(t, caClient) scepClient := createSCEPClient(t, c.caURL, c.root) - cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + cert, err := scepClient.requestCertificate(t) require.NoError(t, err) require.NotNil(t, cert) @@ -39,3 +41,59 @@ func TestIssuesCertificateUsingRegularSCEPConfiguration(t *testing.T) { wg.Wait() } + +func TestBlocksCertificateRequestUsingInvalidChallenge(t *testing.T) { + c := newTestCA(t, "Step E2E | SCEP Regular w/ invalid challenge") + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + err := c.run() + require.ErrorIs(t, err, http.ErrServerClosed) + }() + + // instantiate a client for the CA running at the random address + caClient := newCAClient(t, c.caURL, c.rootFilepath) + requireHealthyCA(t, caClient) + + scepClient := createSCEPClient(t, c.caURL, c.root) + cert, err := scepClient.requestCertificate(t, withChallenge("an-invalid-challenge")) + require.Error(t, err) + require.Nil(t, cert) + + // done testing; stop and wait for the server to quit + err = c.stop() + require.NoError(t, err) + + wg.Wait() +} + +func TestBlocksUnsupportedMessageType(t *testing.T) { + c := newTestCA(t, "Step E2E | SCEP Regular w/ unsupported message type") + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + err := c.run() + require.ErrorIs(t, err, http.ErrServerClosed) + }() + + // instantiate a client for the CA running at the random address + caClient := newCAClient(t, c.caURL, c.rootFilepath) + requireHealthyCA(t, caClient) + + scepClient := createSCEPClient(t, c.caURL, c.root) + cert, err := scepClient.requestCertificate(t, withMessageType(scep.CertPoll)) + require.Error(t, err) + require.Nil(t, cert) + + // done testing; stop and wait for the server to quit + err = c.stop() + require.NoError(t, err) + + wg.Wait() +}
test/integration/scep/windows_go1.23_test.go+8 −1 modified@@ -3,6 +3,8 @@ package sceptest import ( + "crypto/rand" + "crypto/rsa" "crypto/x509" "fmt" "net/http" @@ -41,7 +43,12 @@ func TestIssuesCertificateToEmulatedWindowsClientGo123(t *testing.T) { requireHealthyCA(t, caClient) scepClient := createSCEPClient(t, c.caURL, c.root) - cert, err := scepClient.requestCertificateEmulatingWindowsClient(t, "test.localhost", []string{"test.localhost"}, legacyCertificateParser) + + signer, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + tmpl := createWindowsTemplate(t, signer) + cert, err := scepClient.requestCertificate(t, withTemplate(tmpl), withSigner(signer), withCertificateParser(legacyCertificateParser)) require.NoError(t, err) require.NotNil(t, cert)
test/integration/scep/windows_test.go+8 −2 modified@@ -3,7 +3,8 @@ package sceptest import ( - "crypto/x509" + "crypto/rand" + "crypto/rsa" "net/http" "sync" "testing" @@ -28,7 +29,12 @@ func TestIssuesCertificateToEmulatedWindowsClient(t *testing.T) { requireHealthyCA(t, caClient) scepClient := createSCEPClient(t, c.caURL, c.root) - cert, err := scepClient.requestCertificateEmulatingWindowsClient(t, "test.localhost", []string{"test.localhost"}, x509.ParseCertificate) + + signer, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + tmpl := createWindowsTemplate(t, signer) + cert, err := scepClient.requestCertificate(t, withTemplate(tmpl), withSigner(signer)) require.NoError(t, err) require.NotNil(t, cert)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/smallstep/certificates/commit/e6da031d5125cfd99fe9a26f74bb41e4dacca4efnvdPatchWEB
- github.com/advisories/GHSA-q4r8-xm5f-56gwghsaADVISORY
- github.com/smallstep/certificates/security/advisories/GHSA-q4r8-xm5f-56gwnvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-30836ghsaADVISORY
- github.com/smallstep/certificates/releases/tag/v0.30.0-rc7nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.