From 8dbfc208b91ac4fc5a4d8b0e95d4e19a8c7c32c5 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 29 Mar 2022 18:20:28 +0200 Subject: [PATCH] Fix golangci-lint warnings --- .golangci.yml | 4 +- cmd/cacertocsp/main.go | 1 + pkg/ocsp/ocsp.go | 163 +++++++++++++++++++-------- pkg/ocsp/responder.go | 101 ++++++++--------- pkg/ocsp/responder_test.go | 125 +++----------------- pkg/ocspsource/opensslcertdb_test.go | 4 +- 6 files changed, 186 insertions(+), 212 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 7dbf626..aa10e6d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -25,10 +25,12 @@ linters-settings: gomnd: ignore-functions: - 'strconv.*' + ignored-files: + - "pkg/ocsp/ocsp.go" goimports: local-prefixes: git.cacert.org/cacert-goocsp misspell: - locale: en-AU + locale: "US" ignore-words: - CAcert diff --git a/cmd/cacertocsp/main.go b/cmd/cacertocsp/main.go index e069e45..145456c 100644 --- a/cmd/cacertocsp/main.go +++ b/cmd/cacertocsp/main.go @@ -140,6 +140,7 @@ func configureIssuers(ctx context.Context, issuerConfigs []*koanf.Koanf, opts [] continue } + issuer := ocspsource.NewIssuer( caCertificate, responderCertificate, diff --git a/pkg/ocsp/ocsp.go b/pkg/ocsp/ocsp.go index fa65599..a2ba199 100644 --- a/pkg/ocsp/ocsp.go +++ b/pkg/ocsp/ocsp.go @@ -30,7 +30,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" - _ "crypto/sha1" + _ "crypto/sha1" //nolint:gosec _ "crypto/sha256" _ "crypto/sha512" "crypto/x509" @@ -45,19 +45,19 @@ import ( var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1}) -// ResponseStatus contains the result of an OCSP request. See -// https://tools.ietf.org/html/rfc6960#section-2.3 +// ResponseStatus contains the result of an OCSP request. See https://tools.ietf.org/html/rfc6960#section-2.3 type ResponseStatus int const ( - Success ResponseStatus = 0 - Malformed ResponseStatus = 1 - InternalError ResponseStatus = 2 - TryLater ResponseStatus = 3 - // Status code four is unused in OCSP. See - // https://tools.ietf.org/html/rfc6960#section-4.2.1 - SignatureRequired ResponseStatus = 5 - Unauthorized ResponseStatus = 6 + Success ResponseStatus = 0 // response has valid confirmations + Malformed ResponseStatus = 1 // illegal confirmation request + InternalError ResponseStatus = 2 // internal error in issuer + TryLater ResponseStatus = 3 // try again later + + // Status code four is unused in OCSP. See https://tools.ietf.org/html/rfc6960#section-4.2.1 + + SignatureRequired ResponseStatus = 5 // must sign the request + Unauthorized ResponseStatus = 6 // request unauthorized ) func (r ResponseStatus) String() string { @@ -115,7 +115,7 @@ type request struct { Cert certID } -type responseASN1 struct { +type ocspResponseASN1 struct { Status asn1.Enumerated Response responseBytes `asn1:"explicit,tag:0,optional"` } @@ -200,8 +200,17 @@ var signatureAlgorithmDetails = []struct { } // TODO(rlb): This is also from crypto/x509, so same comment as AGL's below -func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) { - var pubType x509.PublicKeyAlgorithm +//nolint:cyclop +func signingParamsForPublicKey( + pub interface{}, + requestedSigAlgo x509.SignatureAlgorithm, +) (crypto.Hash, pkix.AlgorithmIdentifier, error) { + var ( + pubType x509.PublicKeyAlgorithm + hashFunc crypto.Hash + sigAlgo pkix.AlgorithmIdentifier + err error + ) switch pub := pub.(type) { case *rsa.PublicKey: @@ -234,35 +243,41 @@ func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureA } if err != nil { - return + return 0, pkix.AlgorithmIdentifier{}, err } if requestedSigAlgo == 0 { - return + return hashFunc, sigAlgo, nil } found := false + for _, details := range signatureAlgorithmDetails { if details.algo == requestedSigAlgo { if details.pubKeyAlgo != pubType { - err = errors.New("x509: requested SignatureAlgorithm does not match private key type") - return + return 0, pkix.AlgorithmIdentifier{}, errors.New( + "x509: requested SignatureAlgorithm does not match private key type", + ) } + sigAlgo.Algorithm, hashFunc = details.oid, details.hash if hashFunc == 0 { - err = errors.New("x509: cannot sign with hash function requested") - return + return 0, pkix.AlgorithmIdentifier{}, errors.New( + "x509: cannot sign with hash function requested", + ) } + found = true + break } } if !found { - err = errors.New("x509: unknown SignatureAlgorithm") + return 0, pkix.AlgorithmIdentifier{}, errors.New("x509: unknown SignatureAlgorithm") } - return + return hashFunc, sigAlgo, nil } // TODO(agl): this is taken from crypto/x509 and so should probably be exported @@ -273,6 +288,7 @@ func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgor return details.algo } } + return x509.UnknownSignatureAlgorithm } @@ -283,6 +299,7 @@ func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash { return hash } } + return crypto.Hash(0) } @@ -292,6 +309,7 @@ func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier { return oid } } + return nil } @@ -308,7 +326,7 @@ const ( // ServerFailed is unused and was never used (see // https://go-review.googlesource.com/#/c/18944). ParseResponse will // return a ResponseError when an error response is parsed. - ServerFailed + // ServerFailed ) // The enumerated reasons for revoking a certificate. See RFC 5280. @@ -340,7 +358,8 @@ func (req *Request) Marshal() ([]byte, error) { if hashAlg == nil { return nil, errors.New("Unknown hash algorithm") } - return asn1.Marshal(ocspRequest{ + + request, err := asn1.Marshal(ocspRequest{ tbsRequest{ Version: 0, RequestList: []request{ @@ -358,6 +377,11 @@ func (req *Request) Marshal() ([]byte, error) { }, }, }) + if err != nil { + return nil, fmt.Errorf("could not marshal OCSP request ASN.1: %w", err) + } + + return request, nil } // Response represents an OCSP response containing a single SingleResponse. See @@ -408,13 +432,13 @@ type Response struct { // defined by OCSP. The Unauthorized code in particular can be used by an OCSP // responder that supports only pre-signed responses as a response to requests // for certificates with unknown status. See RFC 5019. -var ( - MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01} - InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02} - TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03} - SigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05} - UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06} -) +// var ( +// MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01} +// InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02} +// TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03} +// SigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05} +// UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06} +// ) // CheckSignatureFrom checks that the signature in resp is a valid signature // from issuer. This should only be used if resp.Certificate is nil. Otherwise, @@ -422,7 +446,11 @@ var ( // signature. That signature is checked by ParseResponse and only // resp.Certificate remains to be validated. func (resp *Response) CheckSignatureFrom(issuer *x509.Certificate) error { - return issuer.CheckSignature(resp.SignatureAlgorithm, resp.TBSResponseData, resp.Signature) + if err := issuer.CheckSignature(resp.SignatureAlgorithm, resp.TBSResponseData, resp.Signature); err != nil { + return fmt.Errorf("could not check signature: %w", err) + } + + return nil } // ParseError results from an invalid OCSP response. @@ -437,10 +465,12 @@ func (p ParseError) Error() string { // If a request includes a signature, it will result in a ParseError. func ParseRequest(bytes []byte) (*Request, error) { var req ocspRequest + rest, err := asn1.Unmarshal(bytes, &req) if err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal OCSP request ASN.1: %w", err) } + if len(rest) > 0 { return nil, ParseError("trailing data in OCSP request") } @@ -448,6 +478,7 @@ func ParseRequest(bytes []byte) (*Request, error) { if len(req.TBSRequest.RequestList) == 0 { return nil, ParseError("OCSP request contains no request body") } + innerRequest := req.TBSRequest.RequestList[0] hashFunc := getHashAlgorithmFromOID(innerRequest.Cert.HashAlgorithm.Algorithm) @@ -487,12 +518,17 @@ func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) { // multiple statuses and cert is not nil, then ParseResponseForCert will return // the first status which contains a matching serial, otherwise it will return an // error. If cert is nil, then the first status in the response will be returned. +// +//nolint:gocognit, cyclop func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Response, error) { - var resp responseASN1 + var resp ocspResponseASN1 + rest, err := asn1.Unmarshal(bytes, &resp) + if err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal OCSP response ASN.1: %w", err) } + if len(rest) > 0 { return nil, ParseError("trailing data in OCSP response") } @@ -506,10 +542,12 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon } var basicResp basicResponse + rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp) if err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal basic response ASN.1: %w", err) } + if len(rest) > 0 { return nil, ParseError("trailing data in OCSP response") } @@ -519,6 +557,7 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon } var singleResp singleResponse + if cert == nil { singleResp = basicResp.TBSResponseData.Responses[0] } else { @@ -527,6 +566,7 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon if cert.SerialNumber.Cmp(resp.CertID.SerialNumber) == 0 { singleResp = resp match = true + break } } @@ -553,9 +593,11 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon switch rawResponderID.Tag { case 1: // Name var rdn pkix.RDNSequence + if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &rdn); err != nil || len(rest) != 0 { return nil, ParseError("invalid responder name") } + ret.RawResponderName = rawResponderID.Bytes case 2: // KeyHash if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &ret.ResponderKeyHash); err != nil || len(rest) != 0 { @@ -565,7 +607,7 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon return nil, ParseError("invalid responder id tag") } - if len(basicResp.Certificates) > 0 { + if len(basicResp.Certificates) > 0 { //nolint:nestif // Responders should only send a single certificate (if they // send any) that connects the responder's certificate to the // original issuer. We accept responses with multiple @@ -575,7 +617,7 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon // [1] https://github.com/golang/go/issues/21527 ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes) if err != nil { - return nil, err + return nil, fmt.Errorf("could not parse signer certificate: %w", err) } if err := ret.CheckSignatureFrom(ret.Certificate); err != nil { @@ -583,7 +625,11 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon } if issuer != nil { - if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil { + if err := issuer.CheckSignature( + ret.Certificate.SignatureAlgorithm, + ret.Certificate.RawTBSCertificate, + ret.Certificate.Signature, + ); err != nil { return nil, ParseError("bad OCSP signature: " + err.Error()) } } @@ -602,9 +648,11 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon for h, oid := range hashOIDs { if singleResp.CertID.HashAlgorithm.Algorithm.Equal(oid) { ret.IssuerHash = h + break } } + if ret.IssuerHash == 0 { return nil, ParseError("unsupported issuer hash algorithm") } @@ -635,6 +683,7 @@ func (opts *RequestOptions) hash() crypto.Hash { // SHA-1 is nearly universally used in OCSP. return crypto.SHA1 } + return opts.Hash } @@ -654,14 +703,16 @@ func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte if !hashFunc.Available() { return nil, x509.ErrUnsupportedAlgorithm } + h := opts.hash().New() var publicKeyInfo struct { Algorithm pkix.AlgorithmIdentifier PublicKey asn1.BitString } + if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal public key info ASN.1: %w", err) } h.Write(publicKeyInfo.PublicKey.RightAlign()) @@ -677,6 +728,7 @@ func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte IssuerKeyHash: issuerKeyHash, SerialNumber: cert.SerialNumber, } + return req.Marshal() } @@ -694,18 +746,27 @@ func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte // If template.IssuerHash is not set, SHA1 will be used. // // The ProducedAt date is automatically set to the current date, to the nearest minute. -func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer, extensions []pkix.Extension) ([]byte, error) { +// +//nolint:cyclop +func CreateResponse( + issuer, responderCert *x509.Certificate, + template Response, + priv crypto.Signer, + extensions []pkix.Extension, +) ([]byte, error) { var publicKeyInfo struct { Algorithm pkix.AlgorithmIdentifier PublicKey asn1.BitString } + if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal public key info ASN.1: %w", err) } if template.IssuerHash == 0 { template.IssuerHash = crypto.SHA1 } + hashOID := getOIDFromHashAlgorithm(template.IssuerHash) if hashOID == nil { return nil, errors.New("unsupported issuer hash algorithm") @@ -714,12 +775,15 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response, if !template.IssuerHash.Available() { return nil, fmt.Errorf("issuer hash algorithm %v not linked into binary", template.IssuerHash) } + h := template.IssuerHash.New() + h.Write(publicKeyInfo.PublicKey.RightAlign()) issuerKeyHash := h.Sum(nil) h.Reset() h.Write(issuer.RawSubject) + issuerNameHash := h.Sum(nil) innerResponse := singleResponse{ @@ -765,7 +829,7 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response, tbsResponseDataDER, err := asn1.Marshal(tbsResponseData) if err != nil { - return nil, err + return nil, fmt.Errorf("could not marshal response data: %w", err) } hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm) @@ -775,9 +839,10 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response, responseHash := hashFunc.New() responseHash.Write(tbsResponseDataDER) + signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc) if err != nil { - return nil, err + return nil, fmt.Errorf("signing failed: %w", err) } response := basicResponse{ @@ -793,16 +858,22 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response, {FullBytes: template.Certificate.Raw}, } } + responseDER, err := asn1.Marshal(response) if err != nil { - return nil, err + return nil, fmt.Errorf("could not marshal OCSP data ASN.1: %w", err) } - return asn1.Marshal(responseASN1{ + responseStructureDER, err := asn1.Marshal(ocspResponseASN1{ Status: asn1.Enumerated(Success), Response: responseBytes{ ResponseType: idPKIXOCSPBasic, Response: responseDER, }, }) + if err != nil { + return nil, fmt.Errorf("could not marshal OSCP response ASN.1: %w", err) + } + + return responseStructureDER, nil } diff --git a/pkg/ocsp/responder.go b/pkg/ocsp/responder.go index 0c3be10..cfc81ee 100644 --- a/pkg/ocsp/responder.go +++ b/pkg/ocsp/responder.go @@ -34,7 +34,6 @@ import ( "io/ioutil" "net/http" "net/url" - "regexp" "time" "github.com/jmhodges/clock" @@ -44,9 +43,9 @@ import ( var ( malformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01} internalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02} - tryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03} - sigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05} - unauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06} + // tryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03} + // sigRequiredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05} + unauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06} // ErrNotFound indicates the request OCSP response was not found. It is used to // indicate that the responder should reply with unauthorizedErrorResponse. @@ -79,46 +78,10 @@ func (src InMemorySource) Response(request *Request) ([]byte, http.Header, error if !present { return nil, nil, ErrNotFound } + return response, nil, nil } -// NewSourceFromFile reads the named file into an InMemorySource. -// The file read by this function must contain whitespace-separated OCSP -// responses. Each OCSP response must be in base64-encoded DER form (i.e., -// PEM without headers or whitespace). Invalid responses are ignored. -// This function pulls the entire file into an InMemorySource. -func NewSourceFromFile(responseFile string) (Source, error) { - fileContents, err := ioutil.ReadFile(responseFile) - if err != nil { - return nil, err - } - - responsesB64 := regexp.MustCompile("\\s").Split(string(fileContents), -1) - src := InMemorySource{} - for _, b64 := range responsesB64 { - // if the line/space is empty just skip - if b64 == "" { - continue - } - der, tmpErr := base64.StdEncoding.DecodeString(b64) - if tmpErr != nil { - logrus.Errorf("Base64 decode error %s on: %s", tmpErr, b64) - continue - } - - response, tmpErr := ParseResponse(der, nil) - if tmpErr != nil { - logrus.Errorf("OCSP decode error %s on: %s", tmpErr, b64) - continue - } - - src[response.SerialNumber.String()] = der - } - - logrus.Infof("Read %d OCSP responses", len(src)) - return src, nil -} - // Stats is a basic interface that allows users to record information // about returned responses type Stats interface { @@ -190,6 +153,8 @@ var hashToString = map[crypto.Hash]string{ // default handler will try to canonicalize path components by changing any // strings of repeated '/' into a single '/', which will break the base64 // encoding. +// +//nolint:gocognit, cyclop func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) { le := logEvent{ IP: request.RemoteAddr, @@ -201,13 +166,16 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques defer func() { le.Headers = response.Header() le.Took = time.Since(le.Received) + jb, err := json.Marshal(le) if err != nil { // we log this error at the debug level as if we aren't at that level anyway - // we shouldn't really care about marshalling the log event object + // we shouldn't really care about marshaling the log event object logrus.Debugf("failed to marshal log event object: %s", err) + return } + logrus.Debugf("Received request: %s", string(jb)) }() // By default we set a 'max-age=0, no-cache' Cache-Control header, this @@ -215,15 +183,20 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques // is not found or an error is returned. If a response if found the header // will be altered to contain the proper max-age and modifiers. response.Header().Add("Cache-Control", "max-age=0, no-cache") - // Read response from request - var requestBody []byte - var err error + + var ( + // Read response from request + requestBody []byte + err error + ) + switch request.Method { case "GET": base64Request, err := url.QueryUnescape(request.URL.Path) if err != nil { logrus.Debugf("Error decoding URL: %s", request.URL.Path) response.WriteHeader(http.StatusBadRequest) + return } // url.QueryUnescape not only unescapes %2B escaping, but it additionally @@ -243,10 +216,12 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques if len(base64RequestBytes) > 0 && base64RequestBytes[0] == '/' { base64RequestBytes = base64RequestBytes[1:] } + requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes)) if err != nil { logrus.Debugf("Error decoding base64 from URL: %s", string(base64RequestBytes)) response.WriteHeader(http.StatusBadRequest) + return } case "POST": @@ -254,14 +229,19 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques if err != nil { logrus.Errorf("Problem reading body of POST: %s", err) response.WriteHeader(http.StatusBadRequest) + return } default: response.WriteHeader(http.StatusMethodNotAllowed) + return } + b64Body := base64.StdEncoding.EncodeToString(requestBody) + logrus.Debugf("Received OCSP request: %s", b64Body) + if request.Method == http.MethodPost { le.Body = b64Body } @@ -279,12 +259,15 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques if err != nil { logrus.Debugf("Error decoding request body: %s", b64Body) response.WriteHeader(http.StatusBadRequest) - response.Write(malformedRequestErrorResponse) + _, _ = response.Write(malformedRequestErrorResponse) + if rs.stats != nil { rs.stats.ResponseStatus(Malformed) } + return } + le.Serial = fmt.Sprintf("%x", ocspRequest.SerialNumber.Bytes()) le.IssuerKeyHash = fmt.Sprintf("%x", ocspRequest.IssuerKeyHash) le.IssuerNameHash = fmt.Sprintf("%x", ocspRequest.IssuerNameHash) @@ -293,22 +276,28 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques // Look up OCSP response from source ocspResponse, headers, err := rs.Source.Response(ocspRequest) if err != nil { - if err == ErrNotFound { + if errors.Is(err, ErrNotFound) { logrus.Infof("No response found for request: serial %x, request body %s", ocspRequest.SerialNumber, b64Body) - response.Write(unauthorizedErrorResponse) + + _, _ = response.Write(unauthorizedErrorResponse) + if rs.stats != nil { rs.stats.ResponseStatus(Unauthorized) } + return } + logrus.Infof("Error retrieving response for request: serial %x, request body %s, error: %s", ocspRequest.SerialNumber, b64Body, err) response.WriteHeader(http.StatusInternalServerError) - response.Write(internalErrorErrorResponse) + _, _ = response.Write(internalErrorErrorResponse) + if rs.stats != nil { rs.stats.ResponseStatus(InternalError) } + return } @@ -316,18 +305,23 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques if err != nil { logrus.Errorf("Error parsing response for serial %x: %s", ocspRequest.SerialNumber, err) - response.Write(internalErrorErrorResponse) + + _, _ = response.Write(internalErrorErrorResponse) + if rs.stats != nil { rs.stats.ResponseStatus(InternalError) } + return } // Write OCSP response to response response.Header().Add("Last-Modified", parsedResponse.ThisUpdate.Format(time.RFC1123)) response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123)) + now := rs.clk.Now() maxAge := 0 + if now.Before(parsedResponse.NextUpdate) { maxAge = int(parsedResponse.NextUpdate.Sub(now) / time.Second) } else { @@ -335,6 +329,7 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques // (despite being stale) and 5019 forbids attaching no-cache maxAge = 0 } + response.Header().Set( "Cache-Control", fmt.Sprintf( @@ -342,6 +337,7 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques maxAge, ), ) + responseHash := sha256.Sum256(ocspResponse) response.Header().Add("ETag", fmt.Sprintf("\"%X\"", responseHash)) @@ -355,11 +351,14 @@ func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Reques if etag := request.Header.Get("If-None-Match"); etag != "" { if etag == fmt.Sprintf("\"%X\"", responseHash) { response.WriteHeader(http.StatusNotModified) + return } } + response.WriteHeader(http.StatusOK) - response.Write(ocspResponse) + _, _ = response.Write(ocspResponse) + if rs.stats != nil { rs.stats.ResponseStatus(Success) } diff --git a/pkg/ocsp/responder_test.go b/pkg/ocsp/responder_test.go index bd1fd9e..e6d970f 100644 --- a/pkg/ocsp/responder_test.go +++ b/pkg/ocsp/responder_test.go @@ -24,21 +24,13 @@ import ( "net/url" "reflect" "testing" - "time" "github.com/jmhodges/clock" ) -const ( - responseFile = "testdata/resp64.pem" - binResponseFile = "testdata/response.der" - brokenResponseFile = "testdata/response_broken.pem" - mixResponseFile = "testdata/response_mix.pem" -) - type testSource struct{} -func (ts testSource) Response(r *Request) ([]byte, http.Header, error) { +func (ts testSource) Response(_ *Request) ([]byte, http.Header, error) { return []byte("hi"), nil, nil } @@ -52,22 +44,22 @@ func TestOCSP(t *testing.T) { {"OPTIONS", "/", http.StatusMethodNotAllowed}, {"GET", "/", http.StatusBadRequest}, // Bad URL encoding - // nolint:lll + //nolint:lll {"GET", "%ZZFQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D", http.StatusBadRequest}, // Bad URL encoding - // nolint:lll + //nolint:lll {"GET", "%%FQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D", http.StatusBadRequest}, // Bad base64 encoding - // nolint:lll + //nolint:lll {"GET", "==MFQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D", http.StatusBadRequest}, // Bad OCSP DER encoding - // nolint:lll + //nolint:lll {"GET", "AAAMFQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D", http.StatusBadRequest}, // Good encoding all around, including a double slash - // nolint:lll + //nolint:lll {"GET", "MFQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D", http.StatusOK}, // Good request, leading slash - // nolint:lll + //nolint:lll {"GET", "/MFQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D", http.StatusOK}, } @@ -85,21 +77,23 @@ func TestOCSP(t *testing.T) { Path: tc.path, }, }) + if rw.Code != tc.expected { t.Errorf("Incorrect response code: got %d, wanted %d", rw.Code, tc.expected) } } } -// nolint:lll +//nolint:lll var testResp = `308204f90a0100a08204f2308204ee06092b0601050507300101048204df308204db3081a7a003020100a121301f311d301b06035504030c146861707079206861636b65722066616b65204341180f32303135303932333231303630305a306c306a3042300906052b0e03021a0500041439e45eb0e3a861c7fa3a3973876be61f7b7d98860414fb784f12f96015832c9f177f3419b32e36ea41890209009cf1912ea8d509088000180f32303135303932333030303030305aa011180f32303330303832363030303030305a300d06092a864886f70d01010b05000382010100c17ed5f12c408d214092c86cb2d6ba9881637a9d5cafb8ddc05aed85806a554c37abdd83c2e00a4bb25b2d0dda1e1c0be65144377471bca53f14616f379ee0c0b436c697b400b7eba9513c5be6d92fbc817586d568156293cfa0099d64585146def907dee36eb650c424a00207b01813aa7ae90e65045339482eeef12b6fa8656315da8f8bb1375caa29ac3858f891adb85066c35b5176e154726ae746016e42e0d6016668ff10a8aa9637417d29be387a1bdba9268b13558034ab5f3e498a47fb096f2e1b39236b22956545884fbbed1884f1bc9686b834d8def4802bac8f79924a36867af87412f808977abaf6457f3cda9e7eccbd0731bcd04865b899ee41a08203193082031530820311308201f9a0030201020209009cf1912ea8d50908300d06092a864886f70d01010b0500301f311d301b06035504030c146861707079206861636b65722066616b65204341301e170d3135303430373233353033385a170d3235303430343233353033385a301f311d301b06035504030c146861707079206861636b65722066616b6520434130820122300d06092a864886f70d01010105000382010f003082010a0282010100c20a47799a05c512b27717633413d770f936bf99de62f130c8774d476deac0029aa6c9d1bb519605df32d34b336394d48e9adc9bbeb48652767dafdb5241c2fc54ce9650e33cb672298888c403642407270cc2f46667f07696d3dd62cfd1f41a8dc0ed60d7c18366b1d2cd462d34a35e148e8695a9a3ec62b656bd129a211a9a534847992d005b0412bcdffdde23085eeca2c32c2693029b5a79f1090fe0b1cb4a154b5c36bc04c7d5a08fa2a58700d3c88d5059205bc5560dc9480f1732b1ad29b030ed3235f7fb868f904fdc79f98ffb5c4e7d4b831ce195f171729ec3f81294df54e66bd3f83d81843b640aea5d7ec64d0905a9dbb03e6ff0e6ac523d36ab0203010001a350304e301d0603551d0e04160414fb784f12f96015832c9f177f3419b32e36ea4189301f0603551d23041830168014fb784f12f96015832c9f177f3419b32e36ea4189300c0603551d13040530030101ff300d06092a864886f70d01010b050003820101001df436be66ff938ccbfb353026962aa758763a777531119377845109e7c2105476c165565d5bbce1464b41bd1d392b079a7341c978af754ca9b3bd7976d485cbbe1d2070d2d4feec1e0f79e8fec9df741e0ea05a26a658d3866825cc1aa2a96a0a04942b2c203cc39501f917a899161dfc461717fe9301fce6ea1afffd7b7998f8941cf76f62def994c028bd1c4b49b17c4d243a6fb058c484968cf80501234da89347108b56b2640cb408e3c336fd72cd355c7f690a15405a7f4ba1e30a6be4a51d262b586f77f8472b207fdd194efab8d3a2683cc148abda7a11b9de1db9307b8ed5a9cd20226f668bd6ac5a3852fd449e42899b7bc915ee747891a110a971` type testHeaderSource struct { headers http.Header } -func (ts testHeaderSource) Response(r *Request) ([]byte, http.Header, error) { +func (ts testHeaderSource) Response(_ *Request) ([]byte, http.Header, error) { resp, _ := hex.DecodeString(testResp) + return resp, ts.headers, nil } @@ -120,104 +114,11 @@ func TestOverrideHeaders(t *testing.T) { rw := httptest.NewRecorder() responder.ServeHTTP(rw, &http.Request{ Method: "GET", - URL: &url.URL{Path: "MFQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D"}, + //nolint:lll + URL: &url.URL{Path: "MFQwUjBQME4wTDAJBgUrDgMCGgUABBQ55F6w46hhx%2Fo6OXOHa%2BYfe32YhgQU%2B3hPEvlgFYMsnxd%2FNBmzLjbqQYkCEwD6Wh0MaVKu9gJ3By9DI%2F%2Fxsd4%3D"}, }) if !reflect.DeepEqual(rw.Header(), headers) { t.Fatalf("Unexpected Headers returned: wanted %s, got %s", headers, rw.Header()) } } - -func TestCacheHeaders(t *testing.T) { - source, err := NewSourceFromFile(responseFile) - if err != nil { - t.Fatalf("Error constructing source: %s", err) - } - - fc := clock.NewFake() - fc.Set(time.Date(2015, 11, 12, 0, 0, 0, 0, time.UTC)) - responder := Responder{ - Source: source, - clk: fc, - } - - rw := httptest.NewRecorder() - responder.ServeHTTP(rw, &http.Request{ - Method: "GET", - URL: &url.URL{ - Path: "MEMwQTA/MD0wOzAJBgUrDgMCGgUABBSwLsMRhyg1dJUwnXWk++D57lvgagQU6aQ/7p6l5vLV13lgPJOmLiSOl6oCAhJN", - }, - }) - if rw.Code != http.StatusOK { - t.Errorf("Unexpected HTTP status code %d", rw.Code) - } - testCases := []struct { - header string - value string - }{ - {"Last-Modified", "Tue, 20 Oct 2015 00:00:00 UTC"}, - {"Expires", "Sun, 20 Oct 2030 00:00:00 UTC"}, - {"Cache-Control", "max-age=471398400, public, no-transform, must-revalidate"}, - {"Etag", "\"8169FB0843B081A76E9F6F13FD70C8411597BEACF8B182136FFDD19FBD26140A\""}, - } - for _, tc := range testCases { - headers, ok := rw.Header()[tc.header] - if !ok { - t.Errorf("Header %s missing from HTTP response", tc.header) - continue - } - if len(headers) != 1 { - t.Errorf("Wrong number of headers in HTTP response. Wanted 1, got %d", len(headers)) - continue - } - actual := headers[0] - if actual != tc.value { - t.Errorf("Got header %s: %s. Expected %s", tc.header, actual, tc.value) - } - } - - rw = httptest.NewRecorder() - headers := http.Header{} - headers.Add("If-None-Match", "\"8169FB0843B081A76E9F6F13FD70C8411597BEACF8B182136FFDD19FBD26140A\"") - responder.ServeHTTP(rw, &http.Request{ - Method: "GET", - URL: &url.URL{ - Path: "MEMwQTA/MD0wOzAJBgUrDgMCGgUABBSwLsMRhyg1dJUwnXWk++D57lvgagQU6aQ/7p6l5vLV13lgPJOmLiSOl6oCAhJN", - }, - Header: headers, - }) - if rw.Code != http.StatusNotModified { - t.Fatalf("Got wrong status code: expected %d, got %d", http.StatusNotModified, rw.Code) - } -} - -func TestNewSourceFromFile(t *testing.T) { - _, err := NewSourceFromFile("") - if err == nil { - t.Fatal("Didn't fail on non-file input") - } - - // expected case - _, err = NewSourceFromFile(responseFile) - if err != nil { - t.Fatal(err) - } - - // binary-formatted file - _, err = NewSourceFromFile(binResponseFile) - if err != nil { - t.Fatal(err) - } - - // the response file from before, with stuff deleted - _, err = NewSourceFromFile(brokenResponseFile) - if err != nil { - t.Fatal(err) - } - - // mix of a correct and malformed responses - _, err = NewSourceFromFile(mixResponseFile) - if err != nil { - t.Fatal(err) - } -} diff --git a/pkg/ocspsource/opensslcertdb_test.go b/pkg/ocspsource/opensslcertdb_test.go index 28dcd47..d40b555 100644 --- a/pkg/ocspsource/opensslcertdb_test.go +++ b/pkg/ocspsource/opensslcertdb_test.go @@ -288,7 +288,7 @@ func (suite *OcspSourceTestSuite) newRandomSerial() *big.Int { func (suite *OcspSourceTestSuite) deserializeKey(index int) crypto.Signer { var ( - // nolint:lll + //nolint:lll pkcs8Keys = []string{ `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`, `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`, @@ -394,7 +394,7 @@ func (suite *OcspSourceTestSuite) TestCertificateInIndexTxt() { suite.FailNow("could not retrieve response: %v", err) } - defer response.Body.Close() + defer func() { _ = response.Body.Close() }() assert.Equal(suite.T(), 200, response.StatusCode)