diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 7213285c3..0bdaf3e99 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -15,6 +15,7 @@ import ( "go.step.sm/crypto/kms" kmsapi "go.step.sm/crypto/kms/apiv1" + "go.step.sm/crypto/x509util" "go.step.sm/linkedca" "github.com/smallstep/certificates/webhook" @@ -145,25 +146,33 @@ var ( // that case, the other webhooks will be skipped. If none of // the webhooks indicates the value of the challenge was accepted, // an error is returned. -func (c *challengeValidationController) Validate(ctx context.Context, csr *x509.CertificateRequest, provisionerName, challenge, transactionID string) error { +func (c *challengeValidationController) Validate(ctx context.Context, csr *x509.CertificateRequest, provisionerName, challenge, transactionID string) ([]SignCSROption, error) { + var opts []SignCSROption + for _, wh := range c.webhooks { req, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr)) if err != nil { - return fmt.Errorf("failed creating new webhook request: %w", err) + return nil, fmt.Errorf("failed creating new webhook request: %w", err) } req.ProvisionerName = provisionerName req.SCEPChallenge = challenge req.SCEPTransactionID = transactionID resp, err := wh.DoWithContext(ctx, c.client, req, nil) // TODO(hs): support templated URL? Requires some refactoring if err != nil { - return fmt.Errorf("failed executing webhook request: %w", err) + return nil, fmt.Errorf("failed executing webhook request: %w", err) } if resp.Allow { - return nil // return early when response is positive + opts = append(opts, TemplateDataModifierFunc(func(data x509util.TemplateData) { + data.SetWebhook(wh.Name, resp.Data) + })) } } - return ErrSCEPChallengeInvalid + if len(opts) == 0 { + return nil, ErrSCEPChallengeInvalid + } + + return opts, nil } type notificationController struct { @@ -440,18 +449,18 @@ func (s *SCEP) GetContentEncryptionAlgorithm() int { // ValidateChallenge validates the provided challenge. It starts by // selecting the validation method to use, then performs validation // according to that method. -func (s *SCEP) ValidateChallenge(ctx context.Context, csr *x509.CertificateRequest, challenge, transactionID string) error { +func (s *SCEP) ValidateChallenge(ctx context.Context, csr *x509.CertificateRequest, challenge, transactionID string) ([]SignCSROption, error) { if s.challengeValidationController == nil { - return fmt.Errorf("provisioner %q wasn't initialized", s.Name) + return nil, fmt.Errorf("provisioner %q wasn't initialized", s.Name) } switch s.selectValidationMethod() { case validationMethodWebhook: return s.challengeValidationController.Validate(ctx, csr, s.Name, challenge, transactionID) default: if subtle.ConstantTimeCompare([]byte(s.ChallengePassword), []byte(challenge)) == 0 { - return errors.New("invalid challenge password provided") + return nil, errors.New("invalid challenge password provided") } - return nil + return []SignCSROption{}, nil } } diff --git a/authority/provisioner/scep_test.go b/authority/provisioner/scep_test.go index 4e2081ee3..dbed83d5a 100644 --- a/authority/provisioner/scep_test.go +++ b/authority/provisioner/scep_test.go @@ -22,6 +22,7 @@ import ( "go.step.sm/crypto/kms/softkms" "go.step.sm/crypto/minica" "go.step.sm/crypto/pemutil" + "go.step.sm/crypto/x509util" "go.step.sm/linkedca" ) @@ -37,6 +38,7 @@ func Test_challengeValidationController_Validate(t *testing.T) { } type response struct { Allow bool `json:"allow"` + Data any `json:"data"` } nokServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { req := &request{} @@ -60,11 +62,22 @@ func Test_challengeValidationController_Validate(t *testing.T) { if assert.NotNil(t, req.Request) { assert.Equal(t, []byte{1}, req.Request.Raw) } - b, err := json.Marshal(response{Allow: true}) + resp := response{Allow: true} + if r.Header.Get("X-Smallstep-Webhook-Id") == "webhook-id-2" { + resp.Data = map[string]any{ + "ID": "2adcbfec-5e4a-4b93-8913-640e24faf101", + "Email": "admin@example.com", + } + } + b, err := json.Marshal(resp) require.NoError(t, err) w.WriteHeader(200) w.Write(b) })) + t.Cleanup(func() { + nokServer.Close() + okServer.Close() + }) type fields struct { client *http.Client webhooks []*Webhook @@ -78,7 +91,7 @@ func Test_challengeValidationController_Validate(t *testing.T) { name string fields fields args args - server *httptest.Server + want x509util.TemplateData expErr error }{ { @@ -134,7 +147,6 @@ func Test_challengeValidationController_Validate(t *testing.T) { challenge: "not-allowed", transactionID: "transaction-1", }, - server: nokServer, expErr: errors.New("webhook server did not allow request"), }, { @@ -154,26 +166,58 @@ func Test_challengeValidationController_Validate(t *testing.T) { challenge: "challenge", transactionID: "transaction-1", }, - server: okServer, + want: x509util.TemplateData{ + x509util.WebhooksKey: map[string]any{ + "webhook-name-1": nil, + }, + }, + }, + { + name: "ok with data", + fields: fields{http.DefaultClient, []*Webhook{ + { + ID: "webhook-id-2", + Name: "webhook-name-2", + Secret: "MTIzNAo=", + Kind: linkedca.Webhook_SCEPCHALLENGE.String(), + CertType: linkedca.Webhook_X509.String(), + URL: okServer.URL, + }, + }}, + args: args{ + provisionerName: "my-scep-provisioner", + challenge: "challenge", + transactionID: "transaction-1", + }, + want: x509util.TemplateData{ + x509util.WebhooksKey: map[string]any{ + "webhook-name-2": map[string]any{ + "ID": "2adcbfec-5e4a-4b93-8913-640e24faf101", + "Email": "admin@example.com", + }, + }, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := newChallengeValidationController(tt.fields.client, tt.fields.webhooks) - - if tt.server != nil { - defer tt.server.Close() - } - ctx := context.Background() - err := c.Validate(ctx, dummyCSR, tt.args.provisionerName, tt.args.challenge, tt.args.transactionID) - + got, err := c.Validate(ctx, dummyCSR, tt.args.provisionerName, tt.args.challenge, tt.args.transactionID) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } - assert.NoError(t, err) + data := x509util.TemplateData{} + for _, o := range got { + if m, ok := o.(TemplateDataModifier); ok { + m.Modify(data) + } else { + t.Errorf("Validate() got = %T, want TemplateDataModifier", o) + } + } + assert.Equal(t, tt.want, data) }) } } @@ -257,6 +301,7 @@ func TestSCEP_ValidateChallenge(t *testing.T) { } type response struct { Allow bool `json:"allow"` + Data any `json:"data"` } okServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { req := &request{} @@ -268,11 +313,19 @@ func TestSCEP_ValidateChallenge(t *testing.T) { if assert.NotNil(t, req.Request) { assert.Equal(t, []byte{1}, req.Request.Raw) } - b, err := json.Marshal(response{Allow: true}) + resp := response{Allow: true} + if r.Header.Get("X-Smallstep-Webhook-Id") == "webhook-id-2" { + resp.Data = map[string]any{ + "ID": "2adcbfec-5e4a-4b93-8913-640e24faf101", + "Email": "admin@example.com", + } + } + b, err := json.Marshal(resp) require.NoError(t, err) w.WriteHeader(200) w.Write(b) })) + t.Cleanup(okServer.Close) type args struct { challenge string transactionID string @@ -282,6 +335,7 @@ func TestSCEP_ValidateChallenge(t *testing.T) { p *SCEP server *httptest.Server args args + want x509util.TemplateData expErr error }{ {"ok/webhooks", &SCEP{ @@ -299,9 +353,43 @@ func TestSCEP_ValidateChallenge(t *testing.T) { }, }, }, - }, okServer, args{"webhook-challenge", "webhook-transaction-1"}, - nil, - }, + }, okServer, args{"webhook-challenge", "webhook-transaction-1"}, x509util.TemplateData{ + x509util.WebhooksKey: map[string]any{ + "webhook-name-1": nil, + }, + }, nil}, + {"ok/with-data", &SCEP{ + Name: "SCEP", + Type: "SCEP", + Options: &Options{ + Webhooks: []*Webhook{ + { + ID: "webhook-id-1", + Name: "webhook-name-1", + Secret: "MTIzNAo=", + Kind: linkedca.Webhook_SCEPCHALLENGE.String(), + CertType: linkedca.Webhook_X509.String(), + URL: okServer.URL, + }, + { + ID: "webhook-id-2", + Name: "webhook-name-2", + Secret: "MTIzNAo=", + Kind: linkedca.Webhook_SCEPCHALLENGE.String(), + CertType: linkedca.Webhook_X509.String(), + URL: okServer.URL, + }, + }, + }, + }, okServer, args{"webhook-challenge", "webhook-transaction-1"}, x509util.TemplateData{ + x509util.WebhooksKey: map[string]any{ + "webhook-name-1": nil, + "webhook-name-2": map[string]any{ + "ID": "2adcbfec-5e4a-4b93-8913-640e24faf101", + "Email": "admin@example.com", + }, + }, + }, nil}, {"fail/webhooks-secret-configuration", &SCEP{ Name: "SCEP", Type: "SCEP", @@ -317,60 +405,53 @@ func TestSCEP_ValidateChallenge(t *testing.T) { }, }, }, - }, nil, args{"webhook-challenge", "webhook-transaction-1"}, - errors.New("failed executing webhook request: illegal base64 data at input byte 0"), - }, + }, nil, args{"webhook-challenge", "webhook-transaction-1"}, nil, errors.New("failed executing webhook request: illegal base64 data at input byte 0")}, {"ok/static-challenge", &SCEP{ Name: "SCEP", Type: "SCEP", Options: &Options{}, ChallengePassword: "secret-static-challenge", - }, nil, args{"secret-static-challenge", "static-transaction-1"}, - nil, - }, + }, nil, args{"secret-static-challenge", "static-transaction-1"}, x509util.TemplateData{}, nil}, {"fail/wrong-static-challenge", &SCEP{ Name: "SCEP", Type: "SCEP", Options: &Options{}, ChallengePassword: "secret-static-challenge", - }, nil, args{"the-wrong-challenge-secret", "static-transaction-1"}, - errors.New("invalid challenge password provided"), - }, + }, nil, args{"the-wrong-challenge-secret", "static-transaction-1"}, nil, errors.New("invalid challenge password provided")}, {"ok/no-challenge", &SCEP{ Name: "SCEP", Type: "SCEP", Options: &Options{}, ChallengePassword: "", - }, nil, args{"", "static-transaction-1"}, - nil, - }, + }, nil, args{"", "static-transaction-1"}, x509util.TemplateData{}, nil}, {"fail/no-challenge-but-provided", &SCEP{ Name: "SCEP", Type: "SCEP", Options: &Options{}, ChallengePassword: "", - }, nil, args{"a-challenge-value", "static-transaction-1"}, - errors.New("invalid challenge password provided"), - }, + }, nil, args{"a-challenge-value", "static-transaction-1"}, nil, errors.New("invalid challenge password provided")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - if tt.server != nil { - defer tt.server.Close() - } - err := tt.p.Init(Config{Claims: globalProvisionerClaims, WebhookClient: http.DefaultClient}) require.NoError(t, err) ctx := context.Background() - err = tt.p.ValidateChallenge(ctx, dummyCSR, tt.args.challenge, tt.args.transactionID) + got, err := tt.p.ValidateChallenge(ctx, dummyCSR, tt.args.challenge, tt.args.transactionID) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } - assert.NoError(t, err) + data := x509util.TemplateData{} + for _, o := range got { + if m, ok := o.(TemplateDataModifier); ok { + m.Modify(data) + } else { + t.Errorf("Validate() got = %T, want TemplateDataModifier", o) + } + } + assert.Equal(t, tt.want, data) }) } } diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 8367d8d09..fc842c43a 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -545,3 +545,28 @@ func (s csrFingerprintValidator) Valid(cr *x509.CertificateRequest) error { } return nil } + +// SignCSROption is the interface used to collect extra options in the SignCSR +// method of the SCEP authority. +type SignCSROption any + +// TemplateDataModifier is an interface that allows to modify template data. +type TemplateDataModifier interface { + Modify(data x509util.TemplateData) +} + +type templateDataModifier struct { + fn func(x509util.TemplateData) +} + +func (t *templateDataModifier) Modify(data x509util.TemplateData) { + t.fn(data) +} + +// TemplateDataModifierFunc returns a TemplateDataModifier with the given +// function. +func TemplateDataModifierFunc(fn func(data x509util.TemplateData)) TemplateDataModifier { + return &templateDataModifier{ + fn: fn, + } +} diff --git a/scep/api/api.go b/scep/api/api.go index 687099c9b..a5e24055a 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -384,14 +384,17 @@ func PKIOperation(ctx context.Context, req request) (Response, error) { // even if using the renewal flow as described in the README.md. MicroMDM SCEP client also only does PKCSreq by default, unless // a certificate exists; then it will use RenewalReq. Adding the challenge check here may be a small breaking change for clients. // We'll have to see how it works out. + var signCSROpts []provisioner.SignCSROption if msg.MessageType == smallscep.PKCSReq || msg.MessageType == smallscep.RenewalReq { - if err := auth.ValidateChallenge(ctx, csr, challengePassword, transactionID); err != nil { + challengeOptions, err := auth.ValidateChallenge(ctx, csr, challengePassword, transactionID) + if err != nil { if errors.Is(err, provisioner.ErrSCEPChallengeInvalid) { return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, err.Error(), err) } scepErr := errors.New("failed validating challenge password") return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, scepErr.Error(), scepErr) } + signCSROpts = append(signCSROpts, challengeOptions...) } // TODO: authorize renewal: we can authorize renewals with the challenge password (if reusable secrets are used). @@ -402,7 +405,7 @@ func PKIOperation(ctx context.Context, req request) (Response, error) { // Authentication by the (self-signed) certificate with an optional challenge is required; supporting renewals incl. verification // of the client cert is not. - certRep, err := auth.SignCSR(ctx, csr, msg) + certRep, err := auth.SignCSR(ctx, csr, msg, signCSROpts...) if err != nil { if notifyErr := auth.NotifyFailure(ctx, csr, transactionID, 0, err.Error()); notifyErr != nil { // TODO(hs): ignore this error case? It's not critical if the notification fails; but logging it might be good diff --git a/scep/authority.go b/scep/authority.go index 00c58d8db..256c540d0 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -241,7 +241,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err // SignCSR creates an x509.Certificate based on a CSR template and Cert Authority credentials // returns a new PKIMessage with CertRep data -func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) { +func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, signCSROpts ...provisioner.SignCSROption) (*PKIMessage, error) { // TODO: intermediate storage of the request? In SCEP it's possible to request a csr/certificate // to be signed, which can be performed asynchronously / out-of-band. In that case a client can // poll for the status. It seems to be similar as what can happen in ACME, so might want to model @@ -284,6 +284,13 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m CommonName: csr.Subject.CommonName, }) + // Apply CSR options. Currently only one option is defined. + for _, o := range signCSROpts { + if m, ok := o.(provisioner.TemplateDataModifier); ok { + m.Modify(data) + } + } + // Get authorizations from the SCEP provisioner. ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) signOps, err := p.AuthorizeSign(ctx, "") @@ -506,7 +513,7 @@ func (a *Authority) GetCACaps(ctx context.Context) []string { return caps } -func (a *Authority) ValidateChallenge(ctx context.Context, csr *x509.CertificateRequest, challenge, transactionID string) error { +func (a *Authority) ValidateChallenge(ctx context.Context, csr *x509.CertificateRequest, challenge, transactionID string) ([]provisioner.SignCSROption, error) { p := provisionerFromContext(ctx) return p.ValidateChallenge(ctx, csr, challenge, transactionID) } diff --git a/scep/authority_test.go b/scep/authority_test.go index e7e786ae1..326aa35cd 100644 --- a/scep/authority_test.go +++ b/scep/authority_test.go @@ -1,16 +1,27 @@ package scep import ( + "context" + "crypto" + "crypto/rand" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" + "encoding/pem" + "net/url" "testing" + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/pkcs7" + "github.com/smallstep/scep" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/randutil" + "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" ) func generateContent(t *testing.T, size int) []byte { @@ -71,3 +82,168 @@ func TestAuthority_encrypt(t *testing.T) { }) } } + +type signAuthority struct { + ca *minica.CA + webhooks []*provisioner.Webhook + template string +} + +func (s *signAuthority) SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + var certOptions []x509util.Option + for _, so := range signOpts { + if co, ok := so.(provisioner.CertificateOptions); ok { + certOptions = append(certOptions, co.Options(opts)...) + } + } + c, err := x509util.NewCertificate(cr, certOptions...) + if err != nil { + return nil, err + } + crt, err := s.ca.Sign(c.GetCertificate()) + if err != nil { + return nil, err + } + return []*x509.Certificate{crt, s.ca.Intermediate}, nil +} + +func (s *signAuthority) LoadProvisionerByName(string) (provisioner.Interface, error) { + p := &provisioner.SCEP{ + Name: "scep", + Type: "SCEP", + ChallengePassword: "password", + DecrypterCertificate: pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: s.ca.Intermediate.Raw, + }), + DecrypterKeyPEM: pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(s.ca.Signer.(*rsa.PrivateKey)), + }), + Options: &provisioner.Options{ + Webhooks: s.webhooks, + X509: &provisioner.X509Options{ + Template: s.template, + }, + }, + } + if err := p.Init(provisioner.Config{ + Claims: config.GlobalProvisionerClaims, + }); err != nil { + return nil, err + } + return p, nil +} + +func TestAuthority_SignCSR(t *testing.T) { + ca, err := minica.New(minica.WithGetSignerFunc(func() (crypto.Signer, error) { + return rsa.GenerateKey(rand.Reader, 2048) + })) + require.NoError(t, err) + + sa := &signAuthority{ + ca: ca, + webhooks: []*provisioner.Webhook{{ + ID: "1f81b7ed-62c4-4dd5-b63a-348e92b2e25d", + Name: "ScepChallenge", + Kind: linkedca.Webhook_SCEPCHALLENGE.String(), + CertType: linkedca.Webhook_X509.String(), + URL: "https://not.used", + Secret: "MTIzNAo=", + }}, + template: `{ +{{- with .Webhooks.ScepChallenge.CommonName }} + "subject": {"commonName" : {{ . | toJson }}}, +{{- else }} + "subject": {{ toJson .Subject }}, +{{- end }} +{{- with .Webhooks.ScepChallenge.Email }} + "emailAddresses" : [ {{ . | toJson }} ], +{{- else }} + "sans": {{ toJson .SANs }}, +{{- end }} +{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} + "keyUsage": ["keyEncipherment", "digitalSignature"], +{{- else }} + "keyUsage": ["digitalSignature"], +{{- end }} + "extKeyUsage": ["serverAuth", "clientAuth"] +}`, + } + + a1, err := New(sa, Options{ + Roots: []*x509.Certificate{ca.Root}, + Intermediates: []*x509.Certificate{ca.Intermediate}, + SignerCert: ca.Intermediate, + Signer: ca.Signer, + Decrypter: ca.Signer.(*rsa.PrivateKey), + DecrypterCert: ca.Intermediate, + SCEPProvisionerNames: []string{"scep"}, + }) + require.NoError(t, err) + + p1, err := a1.LoadProvisionerByName("scep") + require.NoError(t, err) + + ctx := NewProvisionerContext(context.Background(), p1.(*provisioner.SCEP)) + + signer, err := keyutil.GenerateDefaultSigner() + require.NoError(t, err) + csr, err := x509util.CreateCertificateRequest("jane@example.com", []string{"urn:uuid:81d19787-cfe8-4a04-82b2-8827f3727235"}, signer) + require.NoError(t, err) + + type args struct { + ctx context.Context + csr *x509.CertificateRequest + msg *PKIMessage + signCSROpts []provisioner.SignCSROption + } + tests := []struct { + name string + authority *Authority + args args + validate func(*testing.T, *PKIMessage) + assertion assert.ErrorAssertionFunc + }{ + {"ok", a1, args{ctx, csr, &PKIMessage{ + CSRReqMessage: &scep.CSRReqMessage{CSR: csr}, + P7: &pkcs7.PKCS7{ + Certificates: []*x509.Certificate{ca.Intermediate}, + }, + }, []provisioner.SignCSROption{ + provisioner.TemplateDataModifierFunc(func(data x509util.TemplateData) { + data.SetWebhook("ScepChallenge", map[string]any{ + "CommonName": "Jane C.", + "Email": "jane@example.com", + }) + }), + }}, func(t *testing.T, p *PKIMessage) { + require.NotNil(t, p.CertRepMessage) + cert, err := x509.ParseCertificate(p.Certificate.Raw) + require.NoError(t, err) + assert.Equal(t, "Jane C.", cert.Subject.CommonName) + assert.Equal(t, []string{"jane@example.com"}, cert.EmailAddresses) + assert.Nil(t, cert.URIs) + }, assert.NoError}, + {"ok no sign options", a1, args{ctx, csr, &PKIMessage{ + CSRReqMessage: &scep.CSRReqMessage{CSR: csr}, + P7: &pkcs7.PKCS7{ + Certificates: []*x509.Certificate{ca.Intermediate}, + }, + }, []provisioner.SignCSROption{}}, func(t *testing.T, p *PKIMessage) { + require.NotNil(t, p.CertRepMessage) + cert, err := x509.ParseCertificate(p.Certificate.Raw) + require.NoError(t, err) + assert.Equal(t, "jane@example.com", cert.Subject.CommonName) + assert.Nil(t, cert.EmailAddresses) + assert.Equal(t, []*url.URL{{Scheme: "urn", Opaque: "uuid:81d19787-cfe8-4a04-82b2-8827f3727235"}}, cert.URIs) + }, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.authority.SignCSR(tt.args.ctx, tt.args.csr, tt.args.msg, tt.args.signCSROpts...) + tt.assertion(t, err) + tt.validate(t, got) + }) + } +} diff --git a/scep/provisioner.go b/scep/provisioner.go index 3df4b367b..35821d8cc 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -20,7 +20,7 @@ type Provisioner interface { GetDecrypter() (*x509.Certificate, crypto.Decrypter) GetSigner() (*x509.Certificate, crypto.Signer) GetContentEncryptionAlgorithm() int - ValidateChallenge(ctx context.Context, csr *x509.CertificateRequest, challenge, transactionID string) error + ValidateChallenge(ctx context.Context, csr *x509.CertificateRequest, challenge, transactionID string) ([]provisioner.SignCSROption, error) NotifySuccess(ctx context.Context, csr *x509.CertificateRequest, cert *x509.Certificate, transactionID string) error NotifyFailure(ctx context.Context, csr *x509.CertificateRequest, transactionID string, errorCode int, errorDescription string) error }