From f0050e5ca98be94f3c2c614d08fd83f498ee6812 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 10 Mar 2021 21:13:05 +0100 Subject: [PATCH] Add signed failure responses --- scep/api/api.go | 44 +++++++++++++++++++--------- scep/authority.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++ scep/scep.go | 13 +++++++++ 3 files changed, 117 insertions(+), 13 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index f6294f06..ced6fa05 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -34,9 +34,9 @@ const maxPayloadSize = 2 << 20 type nextHTTP = func(http.ResponseWriter, *http.Request) const ( - certChainHeader = "application/x-x509-ca-ra-cert" - leafHeader = "application/x-x509-ca-cert" - pkiOpHeader = "application/x-pki-message" + certChainHeader = "application/x-x509-ca-ra-cert" + leafHeader = "application/x-x509-ca-cert" + pkiOperationHeader = "application/x-pki-message" ) // SCEPRequest is a SCEP server request. @@ -125,10 +125,6 @@ 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) } @@ -262,9 +258,13 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { + // return the error, because we can't use the msg for creating a CertRep return SCEPResponse{}, err } + // this is essentially doing the same as microscep.ParsePKIMessage, but + // gives us access to the p7 itself in scep.PKIMessage. Essentially a small + // wrapper for the microscep implementation. p7, err := pkcs7.Parse(microMsg.Raw) if err != nil { return SCEPResponse{}, err @@ -283,23 +283,25 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe return SCEPResponse{}, err } + // NOTE: at this point we have sufficient information for returning nicely signed CertReps + csr := msg.CSRReqMessage.CSR + if msg.MessageType == microscep.PKCSReq { challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) if err != nil { - return SCEPResponse{}, err + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when checking password") } if !challengeMatches { - return SCEPResponse{}, errors.New("wrong password provided") + // TODO: can this be returned safely to the client? In the end, if the password was correct, that gains a bit of info too. + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "wrong password provided") } } - csr := msg.CSRReqMessage.CSR - certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { - return SCEPResponse{}, err + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when signing new certificate") } // //cert := certRep.CertRepMessage.Certificate @@ -330,6 +332,11 @@ func formatCapabilities(caps []string) []byte { // writeSCEPResponse writes a SCEP response back to the SCEP client. func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { + + if response.Certificate != nil { + api.LogCertificate(w, response.Certificate) + } + w.Header().Set("Content-Type", contentHeader(response)) _, err := w.Write(response.Data) if err != nil { @@ -346,6 +353,17 @@ func writeError(w http.ResponseWriter, err error) { api.WriteError(w, scepError) } +func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, infoText string) (SCEPResponse, error) { + certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), infoText) + if err != nil { + return SCEPResponse{}, err + } + return SCEPResponse{ + Operation: opnPKIOperation, + Data: certRepMsg.Raw, + }, nil +} + func contentHeader(r SCEPResponse) string { switch r.Operation { case opnGetCACert: @@ -354,7 +372,7 @@ func contentHeader(r SCEPResponse) string { } return leafHeader case opnPKIOperation: - return pkiOpHeader + return pkiOperationHeader default: return "text/plain" } diff --git a/scep/authority.go b/scep/authority.go index f8e5a76f..e277a5af 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) + CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error) MatchChallengePassword(ctx context.Context, password string) (bool, error) GetCACaps(ctx context.Context) []string @@ -376,6 +377,10 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m Type: oidSCEPrecipientNonce, Value: msg.SenderNonce, }, + pkcs7.Attribute{ + Type: oidSCEPsenderNonce, + Value: msg.SenderNonce, + }, }, } @@ -419,6 +424,74 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } +// CreateFailureResponse creates an appropriately signed reply for PKI operations +func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error) { + + config := pkcs7.SignerInfoConfig{ + ExtraSignedAttributes: []pkcs7.Attribute{ + pkcs7.Attribute{ + Type: oidSCEPtransactionID, + Value: msg.TransactionID, + }, + pkcs7.Attribute{ + Type: oidSCEPpkiStatus, + Value: microscep.FAILURE, + }, + pkcs7.Attribute{ + Type: oidSCEPfailInfo, + Value: info, + }, + pkcs7.Attribute{ + Type: oidSCEPfailInfoText, + Value: infoText, + }, + pkcs7.Attribute{ + Type: oidSCEPmessageType, + Value: microscep.CertRep, + }, + pkcs7.Attribute{ + Type: oidSCEPsenderNonce, + Value: msg.SenderNonce, + }, + pkcs7.Attribute{ + Type: oidSCEPrecipientNonce, + Value: msg.SenderNonce, + }, + }, + } + + signedData, err := pkcs7.NewSignedData(nil) + if err != nil { + return nil, err + } + + // sign the attributes + if err := signedData.AddSigner(a.intermediateCertificate, a.service.Signer, config); err != nil { + return nil, err + } + + certRepBytes, err := signedData.Finish() + if err != nil { + return nil, err + } + + cr := &CertRepMessage{ + PKIStatus: microscep.FAILURE, + FailInfo: microscep.BadRequest, + RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), + } + + // create a CertRep message from the original + crepMsg := &PKIMessage{ + Raw: certRepBytes, + TransactionID: msg.TransactionID, + MessageType: microscep.CertRep, + CertRepMessage: cr, + } + + return crepMsg, nil +} + // MatchChallengePassword verifies a SCEP challenge password func (a *Authority) MatchChallengePassword(ctx context.Context, password string) (bool, error) { diff --git a/scep/scep.go b/scep/scep.go index 7fc4c261..0c25ec4c 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -11,6 +11,18 @@ import ( "go.mozilla.org/pkcs7" ) +// FailInfoName models the name/value of failInfo +type FailInfoName microscep.FailInfo + +// FailInfo models a failInfo object consisting of a +// name/identifier and a failInfoText, the latter of +// which can be more descriptive and is intended to be +// read by humans. +type FailInfo struct { + Name FailInfoName + Text string +} + // SCEP OIDs var ( oidSCEPmessageType = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 2} @@ -20,6 +32,7 @@ var ( oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} + oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} ) // PKIMessage defines the possible SCEP message types