Skip to content

Commit

Permalink
Add signed failure responses
Browse files Browse the repository at this point in the history
  • Loading branch information
hslatman committed Mar 10, 2021
1 parent 2536a08 commit 9902dc1
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 13 deletions.
44 changes: 31 additions & 13 deletions scep/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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:
Expand All @@ -354,7 +372,7 @@ func contentHeader(r SCEPResponse) string {
}
return leafHeader
case opnPKIOperation:
return pkiOpHeader
return pkiOperationHeader
default:
return "text/plain"
}
Expand Down
73 changes: 73 additions & 0 deletions scep/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
},
},
}

Expand Down Expand Up @@ -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) {

Expand Down
13 changes: 13 additions & 0 deletions scep/scep.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand Down

0 comments on commit 9902dc1

Please sign in to comment.