diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 10414b5e..031241c4 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -14,10 +14,11 @@ type SCEP struct { Type string `json:"type"` Name string `json:"name"` - ForceCN bool `json:"forceCN,omitempty"` - Options *Options `json:"options,omitempty"` - Claims *Claims `json:"claims,omitempty"` - claimer *Claimer + ForceCN bool `json:"forceCN,omitempty"` + ChallengePassword string `json:"challenge,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer } // GetID returns the provisioner unique identifier. @@ -76,7 +77,7 @@ func (s *SCEP) Init(config Config) (err error) { return err } -// AuthorizeSign does not do any validation, because all validation is handled +// AuthorizeSign does not do any verification, because all verification is handled // in the SCEP protocol. This method returns a list of modifiers / constraints // on the resulting certificate. func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { @@ -91,6 +92,11 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e }, nil } +// GetChallengePassword returns the challenge password +func (s *SCEP) GetChallengePassword() string { + return s.ChallengePassword +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/api/api.go b/scep/api/api.go index 09ffddd0..3f49f7a3 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -80,7 +80,6 @@ func New(scepAuth scep.Interface) api.RouterHandler { // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { getLink := h.Auth.GetLinkExplicit - r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) } @@ -140,6 +139,8 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { return } + // TODO: fix cases in which we get here and there's no certificate (i.e. wrong password, waiting for cert, etc) + // We should generate an appropriate response and it should be signed api.LogCertificate(w, response.Certificate) writeSCEPResponse(w, response) @@ -238,8 +239,10 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { return SCEPResponse{}, errors.New("missing CA cert") } - response := SCEPResponse{Operation: opnGetCACert} - response.CACertNum = len(certs) + response := SCEPResponse{ + Operation: opnGetCACert, + CACertNum: len(certs), + } if len(certs) == 1 { response.Data = certs[0].Raw @@ -268,8 +271,6 @@ func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { // PKIOperation performs PKI operations and returns a SCEP response func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPResponse, error) { - response := SCEPResponse{Operation: opnPKIOperation} - // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { @@ -295,7 +296,15 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe } if msg.MessageType == microscep.PKCSReq { - // TODO: CSR validation, like challenge password + + challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) + if err != nil { + return SCEPResponse{}, err + } + + if !challengeMatches { + return SCEPResponse{}, errors.New("wrong password provided") + } } csr := msg.CSRReqMessage.CSR @@ -311,8 +320,11 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe // // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not // // TODO: store the new cert for CN locally; should go into the DB - response.Data = certRep.Raw - response.Certificate = certRep.Certificate + response := SCEPResponse{ + Operation: opnPKIOperation, + Data: certRep.Raw, + Certificate: certRep.Certificate, + } return response, nil } @@ -338,6 +350,7 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { } func writeError(w http.ResponseWriter, err error) { + // TODO: this probably needs to use SCEP specific errors (i.e. failInfo) scepError := &scep.Error{ Message: err.Error(), Status: http.StatusInternalServerError, // TODO: make this a param? diff --git a/scep/authority.go b/scep/authority.go index a61c093a..a2b3b8b9 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -56,6 +56,7 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) + MatchChallengePassword(ctx context.Context, password string) (bool, error) GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } @@ -401,6 +402,25 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } +// MatchChallengePassword verifies a SCEP challenge password +func (a *Authority) MatchChallengePassword(ctx context.Context, password string) (bool, error) { + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return false, err + } + + if p.GetChallengePassword() == password { + return true, nil + } + + // TODO: support dynamic challenges, i.e. a list of challenges instead of one? + // That's probably a bit harder to configure, though; likely requires some data store + // that can be interacted with more easily, via some internal API, for example. + + return false, nil +} + // degenerateCertificates creates degenerate certificates pkcs#7 type func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { var buf bytes.Buffer diff --git a/scep/provisioner.go b/scep/provisioner.go index 64a787d4..5c665a1f 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -14,4 +14,5 @@ type Provisioner interface { GetName() string DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options + GetChallengePassword() string }