VYPR
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.

PackageAffected versionsPatched versions
github.com/smallstep/certificatesGo
< 0.30.00.30.0

Affected products

7
  • Smallstep/Step Ca7 versions
    cpe: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

1
e6da031d5125

Add scep integration tests

https://github.com/smallstep/certificatesMariano CanoMar 18, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.