From 0072107110255066fa6bc0c196f1f0895759db49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pi=C3=B1a?= Date: Thu, 6 May 2021 00:18:58 -0700 Subject: [PATCH 1/3] Vendor ocsp dep --- go.mod | 2 +- go.sum | 2 + vendor/golang.org/x/crypto/ocsp/ocsp.go | 789 ++++++++++++++++++++++++ vendor/modules.txt | 3 +- 4 files changed, 794 insertions(+), 2 deletions(-) create mode 100644 vendor/golang.org/x/crypto/ocsp/ocsp.go diff --git a/go.mod b/go.mod index 9a537ab8a7..639562e2ae 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/nats-io/nats.go v1.11.0 github.com/nats-io/nkeys v0.3.0 github.com/nats-io/nuid v1.0.1 - golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b + golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 ) diff --git a/go.sum b/go.sum index a713a0e8ca..1f0224d953 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 h1:QuAh/1Gwc0d+u9walMU1NqzhRemNegsv5esp2ALQIY4= +golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/golang.org/x/crypto/ocsp/ocsp.go b/vendor/golang.org/x/crypto/ocsp/ocsp.go new file mode 100644 index 0000000000..9d3fffa8fe --- /dev/null +++ b/vendor/golang.org/x/crypto/ocsp/ocsp.go @@ -0,0 +1,789 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses +// are signed messages attesting to the validity of a certificate for a small +// period of time. This is used to manage revocation for X.509 certificates. +package ocsp // import "golang.org/x/crypto/ocsp" + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "strconv" + "time" +) + +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 +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 +) + +func (r ResponseStatus) String() string { + switch r { + case Success: + return "success" + case Malformed: + return "malformed" + case InternalError: + return "internal error" + case TryLater: + return "try later" + case SignatureRequired: + return "signature required" + case Unauthorized: + return "unauthorized" + default: + return "unknown OCSP status: " + strconv.Itoa(int(r)) + } +} + +// ResponseError is an error that may be returned by ParseResponse to indicate +// that the response itself is an error, not just that it's indicating that a +// certificate is revoked, unknown, etc. +type ResponseError struct { + Status ResponseStatus +} + +func (r ResponseError) Error() string { + return "ocsp: error from server: " + r.Status.String() +} + +// These are internal structures that reflect the ASN.1 structure of an OCSP +// response. See RFC 2560, section 4.2. + +type certID struct { + HashAlgorithm pkix.AlgorithmIdentifier + NameHash []byte + IssuerKeyHash []byte + SerialNumber *big.Int +} + +// https://tools.ietf.org/html/rfc2560#section-4.1.1 +type ocspRequest struct { + TBSRequest tbsRequest +} + +type tbsRequest struct { + Version int `asn1:"explicit,tag:0,default:0,optional"` + RequestorName pkix.RDNSequence `asn1:"explicit,tag:1,optional"` + RequestList []request +} + +type request struct { + Cert certID +} + +type responseASN1 struct { + Status asn1.Enumerated + Response responseBytes `asn1:"explicit,tag:0,optional"` +} + +type responseBytes struct { + ResponseType asn1.ObjectIdentifier + Response []byte +} + +type basicResponse struct { + TBSResponseData responseData + SignatureAlgorithm pkix.AlgorithmIdentifier + Signature asn1.BitString + Certificates []asn1.RawValue `asn1:"explicit,tag:0,optional"` +} + +type responseData struct { + Raw asn1.RawContent + Version int `asn1:"optional,default:0,explicit,tag:0"` + RawResponderID asn1.RawValue + ProducedAt time.Time `asn1:"generalized"` + Responses []singleResponse +} + +type singleResponse struct { + CertID certID + Good asn1.Flag `asn1:"tag:0,optional"` + Revoked revokedInfo `asn1:"tag:1,optional"` + Unknown asn1.Flag `asn1:"tag:2,optional"` + ThisUpdate time.Time `asn1:"generalized"` + NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"` + SingleExtensions []pkix.Extension `asn1:"explicit,tag:1,optional"` +} + +type revokedInfo struct { + RevocationTime time.Time `asn1:"generalized"` + Reason asn1.Enumerated `asn1:"explicit,tag:0,optional"` +} + +var ( + oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} + oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} + oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} + oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} + oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} + oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} + oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} + oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} + oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} + oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} +) + +var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{ + crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}), + crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}), + crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}), + crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}), +} + +// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below +var signatureAlgorithmDetails = []struct { + algo x509.SignatureAlgorithm + oid asn1.ObjectIdentifier + pubKeyAlgo x509.PublicKeyAlgorithm + hash crypto.Hash +}{ + {x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */}, + {x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5}, + {x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, + {x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256}, + {x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384}, + {x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512}, + {x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1}, + {x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256}, + {x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1}, + {x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256}, + {x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384}, + {x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512}, +} + +// 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 + + switch pub := pub.(type) { + case *rsa.PublicKey: + pubType = x509.RSA + hashFunc = crypto.SHA256 + sigAlgo.Algorithm = oidSignatureSHA256WithRSA + sigAlgo.Parameters = asn1.RawValue{ + Tag: 5, + } + + case *ecdsa.PublicKey: + pubType = x509.ECDSA + + switch pub.Curve { + case elliptic.P224(), elliptic.P256(): + hashFunc = crypto.SHA256 + sigAlgo.Algorithm = oidSignatureECDSAWithSHA256 + case elliptic.P384(): + hashFunc = crypto.SHA384 + sigAlgo.Algorithm = oidSignatureECDSAWithSHA384 + case elliptic.P521(): + hashFunc = crypto.SHA512 + sigAlgo.Algorithm = oidSignatureECDSAWithSHA512 + default: + err = errors.New("x509: unknown elliptic curve") + } + + default: + err = errors.New("x509: only RSA and ECDSA keys supported") + } + + if err != nil { + return + } + + if requestedSigAlgo == 0 { + return + } + + 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 + } + sigAlgo.Algorithm, hashFunc = details.oid, details.hash + if hashFunc == 0 { + err = errors.New("x509: cannot sign with hash function requested") + return + } + found = true + break + } + } + + if !found { + err = errors.New("x509: unknown SignatureAlgorithm") + } + + return +} + +// TODO(agl): this is taken from crypto/x509 and so should probably be exported +// from crypto/x509 or crypto/x509/pkix. +func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgorithm { + for _, details := range signatureAlgorithmDetails { + if oid.Equal(details.oid) { + return details.algo + } + } + return x509.UnknownSignatureAlgorithm +} + +// TODO(rlb): This is not taken from crypto/x509, but it's of the same general form. +func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash { + for hash, oid := range hashOIDs { + if oid.Equal(target) { + return hash + } + } + return crypto.Hash(0) +} + +func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier { + for hash, oid := range hashOIDs { + if hash == target { + return oid + } + } + return nil +} + +// This is the exposed reflection of the internal OCSP structures. + +// The status values that can be expressed in OCSP. See RFC 6960. +const ( + // Good means that the certificate is valid. + Good = iota + // Revoked means that the certificate has been deliberately revoked. + Revoked + // Unknown means that the OCSP responder doesn't know about the certificate. + Unknown + // 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 +) + +// The enumerated reasons for revoking a certificate. See RFC 5280. +const ( + Unspecified = 0 + KeyCompromise = 1 + CACompromise = 2 + AffiliationChanged = 3 + Superseded = 4 + CessationOfOperation = 5 + CertificateHold = 6 + + RemoveFromCRL = 8 + PrivilegeWithdrawn = 9 + AACompromise = 10 +) + +// Request represents an OCSP request. See RFC 6960. +type Request struct { + HashAlgorithm crypto.Hash + IssuerNameHash []byte + IssuerKeyHash []byte + SerialNumber *big.Int +} + +// Marshal marshals the OCSP request to ASN.1 DER encoded form. +func (req *Request) Marshal() ([]byte, error) { + hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm) + if hashAlg == nil { + return nil, errors.New("Unknown hash algorithm") + } + return asn1.Marshal(ocspRequest{ + tbsRequest{ + Version: 0, + RequestList: []request{ + { + Cert: certID{ + pkix.AlgorithmIdentifier{ + Algorithm: hashAlg, + Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */}, + }, + req.IssuerNameHash, + req.IssuerKeyHash, + req.SerialNumber, + }, + }, + }, + }, + }) +} + +// Response represents an OCSP response containing a single SingleResponse. See +// RFC 6960. +type Response struct { + // Status is one of {Good, Revoked, Unknown} + Status int + SerialNumber *big.Int + ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time + RevocationReason int + Certificate *x509.Certificate + // TBSResponseData contains the raw bytes of the signed response. If + // Certificate is nil then this can be used to verify Signature. + TBSResponseData []byte + Signature []byte + SignatureAlgorithm x509.SignatureAlgorithm + + // IssuerHash is the hash used to compute the IssuerNameHash and IssuerKeyHash. + // Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384, and crypto.SHA512. + // If zero, the default is crypto.SHA1. + IssuerHash crypto.Hash + + // RawResponderName optionally contains the DER-encoded subject of the + // responder certificate. Exactly one of RawResponderName and + // ResponderKeyHash is set. + RawResponderName []byte + // ResponderKeyHash optionally contains the SHA-1 hash of the + // responder's public key. Exactly one of RawResponderName and + // ResponderKeyHash is set. + ResponderKeyHash []byte + + // Extensions contains raw X.509 extensions from the singleExtensions field + // of the OCSP response. When parsing certificates, this can be used to + // extract non-critical extensions that are not parsed by this package. When + // marshaling OCSP responses, the Extensions field is ignored, see + // ExtraExtensions. + Extensions []pkix.Extension + + // ExtraExtensions contains extensions to be copied, raw, into any marshaled + // OCSP response (in the singleExtensions field). Values override any + // extensions that would otherwise be produced based on the other fields. The + // ExtraExtensions field is not populated when parsing certificates, see + // Extensions. + ExtraExtensions []pkix.Extension +} + +// These are pre-serialized error responses for the various non-success codes +// 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} +) + +// CheckSignatureFrom checks that the signature in resp is a valid signature +// from issuer. This should only be used if resp.Certificate is nil. Otherwise, +// the OCSP response contained an intermediate certificate that created the +// 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) +} + +// ParseError results from an invalid OCSP response. +type ParseError string + +func (p ParseError) Error() string { + return string(p) +} + +// ParseRequest parses an OCSP request in DER form. It only supports +// requests for a single certificate. Signed requests are not supported. +// 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 + } + if len(rest) > 0 { + return nil, ParseError("trailing data in OCSP request") + } + + 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) + if hashFunc == crypto.Hash(0) { + return nil, ParseError("OCSP request uses unknown hash function") + } + + return &Request{ + HashAlgorithm: hashFunc, + IssuerNameHash: innerRequest.Cert.NameHash, + IssuerKeyHash: innerRequest.Cert.IssuerKeyHash, + SerialNumber: innerRequest.Cert.SerialNumber, + }, nil +} + +// ParseResponse parses an OCSP response in DER form. The response must contain +// only one certificate status. To parse the status of a specific certificate +// from a response which may contain multiple statuses, use ParseResponseForCert +// instead. +// +// If the response contains an embedded certificate, then that certificate will +// be used to verify the response signature. If the response contains an +// embedded certificate and issuer is not nil, then issuer will be used to verify +// the signature on the embedded certificate. +// +// If the response does not contain an embedded certificate and issuer is not +// nil, then issuer will be used to verify the response signature. +// +// Invalid responses and parse failures will result in a ParseError. +// Error responses will result in a ResponseError. +func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) { + return ParseResponseForCert(bytes, nil, issuer) +} + +// ParseResponseForCert acts identically to ParseResponse, except it supports +// parsing responses that contain multiple statuses. If the response contains +// 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. +func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Response, error) { + var resp responseASN1 + rest, err := asn1.Unmarshal(bytes, &resp) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, ParseError("trailing data in OCSP response") + } + + if status := ResponseStatus(resp.Status); status != Success { + return nil, ResponseError{status} + } + + if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) { + return nil, ParseError("bad OCSP response type") + } + + var basicResp basicResponse + rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, ParseError("trailing data in OCSP response") + } + + if n := len(basicResp.TBSResponseData.Responses); n == 0 || cert == nil && n > 1 { + return nil, ParseError("OCSP response contains bad number of responses") + } + + var singleResp singleResponse + if cert == nil { + singleResp = basicResp.TBSResponseData.Responses[0] + } else { + match := false + for _, resp := range basicResp.TBSResponseData.Responses { + if cert.SerialNumber.Cmp(resp.CertID.SerialNumber) == 0 { + singleResp = resp + match = true + break + } + } + if !match { + return nil, ParseError("no response matching the supplied certificate") + } + } + + ret := &Response{ + TBSResponseData: basicResp.TBSResponseData.Raw, + Signature: basicResp.Signature.RightAlign(), + SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm), + Extensions: singleResp.SingleExtensions, + SerialNumber: singleResp.CertID.SerialNumber, + ProducedAt: basicResp.TBSResponseData.ProducedAt, + ThisUpdate: singleResp.ThisUpdate, + NextUpdate: singleResp.NextUpdate, + } + + // Handle the ResponderID CHOICE tag. ResponderID can be flattened into + // TBSResponseData once https://go-review.googlesource.com/34503 has been + // released. + rawResponderID := basicResp.TBSResponseData.RawResponderID + 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 { + return nil, ParseError("invalid responder key hash") + } + default: + return nil, ParseError("invalid responder id tag") + } + + if len(basicResp.Certificates) > 0 { + // 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 + // certificates due to a number responders sending them[1], but + // ignore all but the first. + // + // [1] https://github.com/golang/go/issues/21527 + ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes) + if err != nil { + return nil, err + } + + if err := ret.CheckSignatureFrom(ret.Certificate); err != nil { + return nil, ParseError("bad signature on embedded certificate: " + err.Error()) + } + + if issuer != nil { + if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil { + return nil, ParseError("bad OCSP signature: " + err.Error()) + } + } + } else if issuer != nil { + if err := ret.CheckSignatureFrom(issuer); err != nil { + return nil, ParseError("bad OCSP signature: " + err.Error()) + } + } + + for _, ext := range singleResp.SingleExtensions { + if ext.Critical { + return nil, ParseError("unsupported critical extension") + } + } + + 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") + } + + switch { + case bool(singleResp.Good): + ret.Status = Good + case bool(singleResp.Unknown): + ret.Status = Unknown + default: + ret.Status = Revoked + ret.RevokedAt = singleResp.Revoked.RevocationTime + ret.RevocationReason = int(singleResp.Revoked.Reason) + } + + return ret, nil +} + +// RequestOptions contains options for constructing OCSP requests. +type RequestOptions struct { + // Hash contains the hash function that should be used when + // constructing the OCSP request. If zero, SHA-1 will be used. + Hash crypto.Hash +} + +func (opts *RequestOptions) hash() crypto.Hash { + if opts == nil || opts.Hash == 0 { + // SHA-1 is nearly universally used in OCSP. + return crypto.SHA1 + } + return opts.Hash +} + +// CreateRequest returns a DER-encoded, OCSP request for the status of cert. If +// opts is nil then sensible defaults are used. +func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte, error) { + hashFunc := opts.hash() + + // OCSP seems to be the only place where these raw hash identifiers are + // used. I took the following from + // http://msdn.microsoft.com/en-us/library/ff635603.aspx + _, ok := hashOIDs[hashFunc] + if !ok { + return nil, x509.ErrUnsupportedAlgorithm + } + + 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 + } + + h.Write(publicKeyInfo.PublicKey.RightAlign()) + issuerKeyHash := h.Sum(nil) + + h.Reset() + h.Write(issuer.RawSubject) + issuerNameHash := h.Sum(nil) + + req := &Request{ + HashAlgorithm: hashFunc, + IssuerNameHash: issuerNameHash, + IssuerKeyHash: issuerKeyHash, + SerialNumber: cert.SerialNumber, + } + return req.Marshal() +} + +// CreateResponse returns a DER-encoded OCSP response with the specified contents. +// The fields in the response are populated as follows: +// +// The responder cert is used to populate the responder's name field, and the +// certificate itself is provided alongside the OCSP response signature. +// +// The issuer cert is used to puplate the IssuerNameHash and IssuerKeyHash fields. +// +// The template is used to populate the SerialNumber, Status, RevokedAt, +// RevocationReason, ThisUpdate, and NextUpdate fields. +// +// 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) ([]byte, error) { + var publicKeyInfo struct { + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString + } + if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil { + return nil, err + } + + if template.IssuerHash == 0 { + template.IssuerHash = crypto.SHA1 + } + hashOID := getOIDFromHashAlgorithm(template.IssuerHash) + if hashOID == nil { + return nil, errors.New("unsupported issuer hash algorithm") + } + + 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{ + CertID: certID{ + HashAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: hashOID, + Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */}, + }, + NameHash: issuerNameHash, + IssuerKeyHash: issuerKeyHash, + SerialNumber: template.SerialNumber, + }, + ThisUpdate: template.ThisUpdate.UTC(), + NextUpdate: template.NextUpdate.UTC(), + SingleExtensions: template.ExtraExtensions, + } + + switch template.Status { + case Good: + innerResponse.Good = true + case Unknown: + innerResponse.Unknown = true + case Revoked: + innerResponse.Revoked = revokedInfo{ + RevocationTime: template.RevokedAt.UTC(), + Reason: asn1.Enumerated(template.RevocationReason), + } + } + + rawResponderID := asn1.RawValue{ + Class: 2, // context-specific + Tag: 1, // Name (explicit tag) + IsCompound: true, + Bytes: responderCert.RawSubject, + } + tbsResponseData := responseData{ + Version: 0, + RawResponderID: rawResponderID, + ProducedAt: time.Now().Truncate(time.Minute).UTC(), + Responses: []singleResponse{innerResponse}, + } + + tbsResponseDataDER, err := asn1.Marshal(tbsResponseData) + if err != nil { + return nil, err + } + + hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm) + if err != nil { + return nil, err + } + + responseHash := hashFunc.New() + responseHash.Write(tbsResponseDataDER) + signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc) + if err != nil { + return nil, err + } + + response := basicResponse{ + TBSResponseData: tbsResponseData, + SignatureAlgorithm: signatureAlgorithm, + Signature: asn1.BitString{ + Bytes: signature, + BitLength: 8 * len(signature), + }, + } + if template.Certificate != nil { + response.Certificates = []asn1.RawValue{ + {FullBytes: template.Certificate.Raw}, + } + } + responseDER, err := asn1.Marshal(response) + if err != nil { + return nil, err + } + + return asn1.Marshal(responseASN1{ + Status: asn1.Enumerated(Success), + Response: responseBytes{ + ResponseType: idPKIXOCSPBasic, + Response: responseDER, + }, + }) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 508255627e..265645381f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -20,12 +20,13 @@ github.com/nats-io/nkeys # github.com/nats-io/nuid v1.0.1 ## explicit github.com/nats-io/nuid -# golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b +# golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 ## explicit golang.org/x/crypto/bcrypt golang.org/x/crypto/blowfish golang.org/x/crypto/ed25519 golang.org/x/crypto/ed25519/internal/edwards25519 +golang.org/x/crypto/ocsp # golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 ## explicit golang.org/x/sys/cpu From b2e1ff7a7c18b59d1191b9af11f2354b588f22f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pi=C3=B1a?= Date: Thu, 6 May 2021 00:32:54 -0700 Subject: [PATCH 2/3] Add OCSP support Signed-off-by: Waldemar Quevedo --- server/ocsp.go | 454 +++++++ server/opts.go | 28 + server/opts_test.go | 1 + server/reload.go | 64 +- server/server.go | 33 + test/configs/certs/ocsp/ca-cert.pem | 34 + test/configs/certs/ocsp/ca-key.pem | 51 + test/configs/certs/ocsp/client-cert.pem | 34 + test/configs/certs/ocsp/client-key.pem | 51 + test/configs/certs/ocsp/server-cert.pem | 34 + test/configs/certs/ocsp/server-key.pem | 51 + .../certs/ocsp/server-status-request-cert.pem | 35 + .../certs/ocsp/server-status-request-key.pem | 51 + .../server-status-request-url-cert-chain.pem | 70 ++ .../ocsp/server-status-request-url-cert.pem | 36 + .../ocsp/server-status-request-url-key.pem | 51 + test/ocsp_test.go | 1039 +++++++++++++++++ 17 files changed, 2116 insertions(+), 1 deletion(-) create mode 100644 server/ocsp.go create mode 100644 test/configs/certs/ocsp/ca-cert.pem create mode 100644 test/configs/certs/ocsp/ca-key.pem create mode 100644 test/configs/certs/ocsp/client-cert.pem create mode 100644 test/configs/certs/ocsp/client-key.pem create mode 100644 test/configs/certs/ocsp/server-cert.pem create mode 100644 test/configs/certs/ocsp/server-key.pem create mode 100644 test/configs/certs/ocsp/server-status-request-cert.pem create mode 100644 test/configs/certs/ocsp/server-status-request-key.pem create mode 100644 test/configs/certs/ocsp/server-status-request-url-cert-chain.pem create mode 100644 test/configs/certs/ocsp/server-status-request-url-cert.pem create mode 100644 test/configs/certs/ocsp/server-status-request-url-key.pem create mode 100644 test/ocsp_test.go diff --git a/server/ocsp.go b/server/ocsp.go new file mode 100644 index 0000000000..6c1c909195 --- /dev/null +++ b/server/ocsp.go @@ -0,0 +1,454 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "crypto/tls" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "strings" + "sync" + "time" + + "golang.org/x/crypto/ocsp" +) + +const ( + defaultOCSPCheckInterval = 24 * time.Hour + minOCSPCheckInterval = 2 * time.Minute +) + +type OCSPMode uint8 + +const ( + // OCSPModeAuto staples a status, only if "status_request" is set in cert. + OCSPModeAuto OCSPMode = iota + + // OCSPModeAlways enforces OCSP stapling for certs and shuts down the server in + // case a server is revoked or cannot get OCSP staples. + OCSPModeAlways + + // OCSPModeNever disables OCSP stapling even if cert has Must-Staple flag. + OCSPModeNever + + // OCSPModeMust honors the Must-Staple flag from a certificate but also causing shutdown + // in case the certificate has been revoked. + OCSPModeMust +) + +// OCSPMonitor monitors the state of a staple per certificate. +type OCSPMonitor struct { + mu sync.Mutex + raw []byte + srv *Server + certFile string + resp *ocsp.Response + hc *http.Client + stopCh chan struct{} + Leaf *x509.Certificate + Issuer *x509.Certificate + + shutdownOnRevoke bool +} + +func (oc *OCSPMonitor) getNextRun() time.Duration { + oc.mu.Lock() + nextUpdate := oc.resp.NextUpdate + oc.mu.Unlock() + + now := time.Now() + if nextUpdate.IsZero() { + // If response is missing NextUpdate, we check the day after. + // Technically, if NextUpdate is missing, we can try whenever. + // https://tools.ietf.org/html/rfc6960#section-4.2.2.1 + return defaultOCSPCheckInterval + } + dur := nextUpdate.Sub(now) / 2 + + // If negative, then wait a couple of minutes before getting another staple. + if dur < 0 { + return minOCSPCheckInterval + } + + return dur +} + +func (oc *OCSPMonitor) getStatus() ([]byte, *ocsp.Response, error) { + raw, resp := oc.getCacheStatus() + if len(raw) > 0 && resp != nil { + return raw, resp, nil + } + return oc.getRemoteStatus() +} + +func (oc *OCSPMonitor) getCacheStatus() ([]byte, *ocsp.Response) { + oc.mu.Lock() + defer oc.mu.Unlock() + return oc.raw, oc.resp +} + +func (oc *OCSPMonitor) getRemoteStatus() ([]byte, *ocsp.Response, error) { + opts := oc.srv.getOpts() + var overrideURLs []string + if config := opts.OCSPConfig; config != nil { + overrideURLs = config.OverrideURLs + } + getRequestBytes := func(u string, hc *http.Client) ([]byte, error) { + resp, err := hc.Get(u) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("non-ok http status: %d", resp.StatusCode) + } + + return ioutil.ReadAll(resp.Body) + } + + // Request documentation: + // https://tools.ietf.org/html/rfc6960#appendix-A.1 + + reqDER, err := ocsp.CreateRequest(oc.Leaf, oc.Issuer, nil) + if err != nil { + return nil, nil, err + } + + reqEnc := base64.StdEncoding.EncodeToString(reqDER) + + responders := oc.Leaf.OCSPServer + if len(overrideURLs) > 0 { + responders = overrideURLs + } + if len(responders) == 0 { + return nil, nil, fmt.Errorf("no available ocsp servers") + } + + oc.mu.Lock() + hc := oc.hc + oc.mu.Unlock() + var raw []byte + for _, u := range responders { + u = strings.TrimSuffix(u, "/") + raw, err = getRequestBytes(fmt.Sprintf("%s/%s", u, reqEnc), hc) + if err == nil { + break + } + } + if err != nil { + return nil, nil, fmt.Errorf("exhausted ocsp servers: %w", err) + } + + resp, err := ocsp.ParseResponse(raw, oc.Issuer) + if err != nil { + return nil, nil, err + } + if err := validOCSPResponse(resp); err != nil { + return nil, nil, err + } + + oc.mu.Lock() + oc.raw = raw + oc.resp = resp + oc.mu.Unlock() + + return raw, resp, nil +} + +func (oc *OCSPMonitor) run() { + s := oc.srv + s.mu.Lock() + quitCh := s.quitCh + s.mu.Unlock() + + defer s.grWG.Done() + + oc.mu.Lock() + shutdownOnRevoke := oc.shutdownOnRevoke + certFile := oc.certFile + stopCh := oc.stopCh + oc.mu.Unlock() + + var nextRun time.Duration + _, resp, err := oc.getStatus() + if err == nil && resp.Status == ocsp.Good { + nextRun = oc.getNextRun() + t := resp.NextUpdate.Format(time.RFC3339Nano) + s.Noticef( + "Found existing OCSP status for certificate at '%s': good, next update %s, checking again in %s", + certFile, t, nextRun, + ) + } else if err == nil && shutdownOnRevoke { + // If resp.Status is ocsp.Revoked, ocsp.Unknown, or any other value. + s.Errorf("Found existing OCSP status for certificate at '%s': %s", certFile, ocspStatusString(resp.Status)) + s.Shutdown() + return + } + + for s.Running() { + // On reload, if the certificate changes then need to stop this monitor. + select { + case <-time.After(nextRun): + case <-stopCh: + // In case of reload and have to restart the OCSP stapling monitoring. + return + case <-quitCh: + // Server quit channel. + return + } + _, resp, err := oc.getRemoteStatus() + if err != nil { + nextRun = oc.getNextRun() + s.Errorf("Bad OCSP status update for certificate '%s': %s, trying again in %v", certFile, err, nextRun) + continue + } + + switch n := resp.Status; n { + case ocsp.Good: + nextRun = oc.getNextRun() + t := resp.NextUpdate.Format(time.RFC3339Nano) + s.Noticef( + "Received OCSP status for certificate '%s': good, next update %s, checking again in %s", + certFile, t, nextRun, + ) + continue + default: + s.Errorf("Received OCSP certificate status: %s", ocspStatusString(n)) + if shutdownOnRevoke { + s.Shutdown() + } + return + } + } +} + +func (oc *OCSPMonitor) stop() { + oc.mu.Lock() + stopCh := oc.stopCh + oc.mu.Unlock() + stopCh <- struct{}{} +} + +// NewOCSPMonitor takes a TLS configuration then wraps it with the callbacks set for OCSP verification +// along with a monitor that will periodically fetch OCSP staples. +func (srv *Server) NewOCSPMonitor(tc *tls.Config) (*tls.Config, *OCSPMonitor, error) { + opts := srv.getOpts() + oc := opts.OCSPConfig + tcOpts := opts.tlsConfigOpts + + var certFile, caFile string + if tcOpts != nil { + certFile = tcOpts.CertFile + caFile = tcOpts.CaFile + } + if opts.TLSCert != _EMPTY_ { + certFile = opts.TLSCert + } + if opts.TLSCaCert != _EMPTY_ { + caFile = opts.TLSCaCert + } + + // NOTE: Currently OCSP Stapling is enabled only for the first certificate found. + var mon *OCSPMonitor + for _, cert := range tc.Certificates { + var shutdownOnRevoke bool + mustStaple := hasOCSPStatusRequest(cert.Leaf) + if oc != nil { + switch { + case oc.Mode == OCSPModeNever: + if mustStaple { + srv.Warnf("Certificate at '%s' has MustStaple but OCSP is disabled", certFile) + } + return tc, nil, nil + case oc.Mode == OCSPModeAlways: + // Start the monitor for this cert even if it does not have + // the MustStaple flag and shutdown the server in case the + // staple ever gets revoked. + mustStaple = true + shutdownOnRevoke = true + case oc.Mode == OCSPModeMust && mustStaple: + shutdownOnRevoke = true + case oc.Mode == OCSPModeAuto && !mustStaple: + // "status_request" MustStaple flag not set in certificate. No need to do anything. + return tc, nil, nil + } + } + if !mustStaple { + // No explicit OCSP config and cert does not have MustStaple flag either. + return tc, nil, nil + } + + // TODO: Add OCSP 'responder_cert' option in case CA cert not available. + issuer, err := getOCSPIssuer(caFile, cert.Certificate) + if err != nil { + return nil, nil, err + } + + mon = &OCSPMonitor{ + srv: srv, + hc: &http.Client{Timeout: 30 * time.Second}, + shutdownOnRevoke: shutdownOnRevoke, + certFile: certFile, + stopCh: make(chan struct{}, 1), + Leaf: cert.Leaf, + Issuer: issuer, + } + + // Get the certificate status from the memory, then remote OCSP responder. + _, resp, err := mon.getStatus() + if err != nil { + return nil, nil, fmt.Errorf("bad OCSP status update for certificate at '%s': %s", certFile, err) + } + if err == nil && resp.Status != ocsp.Good && shutdownOnRevoke { + return nil, nil, fmt.Errorf("found existing OCSP status for certificate at '%s': %s", certFile, ocspStatusString(resp.Status)) + } + + // Callbacks below will be in charge of returning the certificate instead, + // so this has to be nil. + tc.Certificates = nil + + // GetCertificate returns a certificate that's presented to a + // client. + tc.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { + raw, _, err := mon.getStatus() + if err != nil { + return nil, err + } + + return &tls.Certificate{ + OCSPStaple: raw, + Certificate: cert.Certificate, + PrivateKey: cert.PrivateKey, + SupportedSignatureAlgorithms: cert.SupportedSignatureAlgorithms, + SignedCertificateTimestamps: cert.SignedCertificateTimestamps, + Leaf: cert.Leaf, + }, nil + } + + // GetClientCertificate returns a certificate that's presented to a + // server. + tc.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &cert, nil + } + } + return tc, mon, nil +} + +func hasOCSPStatusRequest(cert *x509.Certificate) bool { + // OID for id-pe-tlsfeature defined in RFC here: + // https://datatracker.ietf.org/doc/html/rfc7633 + tlsFeatures := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24} + const statusRequestExt = 5 + + // Example values: + // * [48 3 2 1 5] - seen when creating own certs locally + // * [30 3 2 1 5] - seen in the wild + // Documentation: + // https://tools.ietf.org/html/rfc6066 + + for _, ext := range cert.Extensions { + if !ext.Id.Equal(tlsFeatures) { + continue + } + + var val []int + rest, err := asn1.Unmarshal(ext.Value, &val) + if err != nil || len(rest) > 0 { + return false + } + + for _, n := range val { + if n == statusRequestExt { + return true + } + } + break + } + + return false +} + +func parseCertPEM(name string) (*x509.Certificate, error) { + data, err := ioutil.ReadFile(name) + if err != nil { + return nil, err + } + + // Ignoring left over byte slice. + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM cert %s", name) + } + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("unexpected PEM certificate type: %s", block.Type) + } + + return x509.ParseCertificate(block.Bytes) +} + +// getOCSPIssuer returns a CA cert from the given path. If the path is empty, +// then this checks a given cert chain. If both are empty, then it returns an +// error. +func getOCSPIssuer(issuerCert string, chain [][]byte) (*x509.Certificate, error) { + var issuer *x509.Certificate + var err error + switch { + case len(chain) == 1 && issuerCert == _EMPTY_: + err = fmt.Errorf("require ocsp ca in chain or configuration") + case issuerCert != _EMPTY_: + issuer, err = parseCertPEM(issuerCert) + case len(chain) > 1 && issuerCert == _EMPTY_: + issuer, err = x509.ParseCertificate(chain[1]) + default: + err = fmt.Errorf("invalid ocsp ca configuration") + } + if err != nil { + return nil, err + } else if !issuer.IsCA { + return nil, fmt.Errorf("%s invalid ca basic constraints: is not ca", issuerCert) + } + + return issuer, nil +} + +func ocspStatusString(n int) string { + switch n { + case ocsp.Good: + return "good" + case ocsp.Revoked: + return "revoked" + default: + return "unknown" + } +} + +func validOCSPResponse(r *ocsp.Response) error { + // Time validation not handled by ParseResponse. + // https://tools.ietf.org/html/rfc6960#section-4.2.2.1 + if !r.NextUpdate.IsZero() && r.NextUpdate.Before(time.Now()) { + t := r.NextUpdate.Format(time.RFC3339Nano) + return fmt.Errorf("invalid ocsp NextUpdate, is past time: %s", t) + } + if r.ThisUpdate.After(time.Now()) { + t := r.ThisUpdate.Format(time.RFC3339Nano) + return fmt.Errorf("invalid ocsp ThisUpdate, is future time: %s", t) + } + + return nil +} diff --git a/server/opts.go b/server/opts.go index dedb8be43c..e79541fc3f 100644 --- a/server/opts.go +++ b/server/opts.go @@ -268,6 +268,10 @@ type Options struct { // and used as a filter criteria for some system requests Tags jwt.TagList `json:"-"` + // OCSPConfig enables OCSP Stapling in the server. + OCSPConfig *OCSPConfig + tlsConfigOpts *TLSConfigOpts + // private fields, used to know if bool options are explicitly // defined in config and/or command line params. inConfig map[string]bool @@ -485,6 +489,15 @@ type TLSConfigOpts struct { PinnedCerts PinnedCertSet } +// OCSPConfig represents the options of OCSP stapling options. +type OCSPConfig struct { + // Mode defines the policy for OCSP stapling. + Mode OCSPMode + + // OverrideURLs is the http URL endpoint used to get OCSP staples. + OverrideURLs []string +} + var tlsUsage = ` TLS configuration is specified in the tls section of a configuration file: @@ -841,6 +854,21 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error o.TLSTimeout = tc.Timeout o.TLSMap = tc.Map o.TLSPinnedCerts = tc.PinnedCerts + + // Need to keep track of path of the original TLS config + // and certs path for OCSP Stapling monitoring. + o.tlsConfigOpts = tc + case "ocsp": + switch v.(type) { + case bool: + // Default is Auto which honors Must Staple status request + // but does not shutdown the server in case it is revoked, + // letting the client choose whether to trust or not the server. + o.OCSPConfig = &OCSPConfig{Mode: OCSPModeAuto} + default: + *errors = append(*errors, &configErr{tk, fmt.Sprintf("error parsing ocsp config: unsupported type %T", v)}) + return + } case "allow_non_tls": o.AllowNonTLS = v.(bool) case "write_deadline": diff --git a/server/opts_test.go b/server/opts_test.go index 09cbd0c641..ca4d9a91be 100644 --- a/server/opts_test.go +++ b/server/opts_test.go @@ -149,6 +149,7 @@ func TestTLSConfigFile(t *testing.T) { t.Fatal("Expected opts.TLSConfig to be non-nil") } opts.TLSConfig = nil + opts.tlsConfigOpts = nil checkOptionsEqual(t, golden, opts) // Now check TLSConfig a bit more closely diff --git a/server/reload.go b/server/reload.go index 424cb4f1bc..5704e90e58 100644 --- a/server/reload.go +++ b/server/reload.go @@ -50,6 +50,9 @@ type option interface { // IsAuthChange indicates if this option requires reloading authorization. IsAuthChange() bool + // IsTLSChange indicates if this option requires reloading TLS. + IsTLSChange() bool + // IsClusterPermsChange indicates if this option requires reloading // cluster permissions. IsClusterPermsChange() bool @@ -74,6 +77,10 @@ func (n noopOption) IsAuthChange() bool { return false } +func (n noopOption) IsTLSChange() bool { + return false +} + func (n noopOption) IsClusterPermsChange() bool { return false } @@ -202,6 +209,10 @@ func (t *tlsOption) Apply(server *Server) { server.Noticef("Reloaded: tls = %s", message) } +func (t *tlsOption) IsTLSChange() bool { + return true +} + // tlsTimeoutOption implements the option interface for the tls `timeout` // setting. type tlsTimeoutOption struct { @@ -802,7 +813,8 @@ func imposeOrder(value interface{}) error { case WebsocketOpts: sort.Strings(value.AllowedOrigins) case string, bool, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet, - *URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList: + *URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList, + *OCSPConfig: // explicitly skipped types default: // this will fail during unit tests @@ -1201,6 +1213,7 @@ func (s *Server) applyOptions(ctx *reloadContext, opts []option) { reloadClientTrcLvl = false reloadJetstream = false jsEnabled = false + reloadTLS = false ) for _, opt := range opts { opt.Apply(s) @@ -1213,6 +1226,9 @@ func (s *Server) applyOptions(ctx *reloadContext, opts []option) { if opt.IsAuthChange() { reloadAuth = true } + if opt.IsTLSChange() { + reloadTLS = true + } if opt.IsClusterPermsChange() { reloadClusterPerms = true } @@ -1256,9 +1272,55 @@ func (s *Server) applyOptions(ctx *reloadContext, opts []option) { s.updateRemoteLeafNodesTLSConfig(newOpts) } + if reloadTLS { + // Restart OCSP monitoring. + if err := s.reloadOCSP(); err != nil { + s.Warnf("Can't restart OCSP Stapling: %v", err) + } + } + s.Noticef("Reloaded server configuration") } +func (s *Server) reloadOCSP() error { + opts := s.getOpts() + + s.mu.Lock() + ocsps := s.ocsps + s.mu.Unlock() + + // Stop all OCSP Stapling monitors in case there were any running. + for _, oc := range ocsps { + oc.stop() + } + + // Restart the monitors under the new configuration. + ocspm := make([]*OCSPMonitor, 0) + if config := opts.TLSConfig; config != nil { + tc, mon, err := s.NewOCSPMonitor(config) + if err != nil { + return err + } + // Check if an OCSP stapling monitor is required for this certificate. + if mon != nil { + ocspm = append(ocspm, mon) + + // Override the TLS config with one that follows OCSP. + s.optsMu.Lock() + s.opts.TLSConfig = tc + s.optsMu.Unlock() + s.startGoRoutine(func() { mon.run() }) + } + s.Noticef("OCSP Stapling enabled for client connections") + } + // Replace stopped monitors with the new ones. + s.mu.Lock() + s.ocsps = ocspm + s.mu.Unlock() + + return nil +} + // Update all cached debug and trace settings for every client func (s *Server) reloadClientTraceLevel() { opts := s.getOpts() diff --git a/server/server.go b/server/server.go index 6fde16c083..a2ef244f0e 100644 --- a/server/server.go +++ b/server/server.go @@ -236,6 +236,9 @@ type Server struct { // MQTT structure mqtt srvMQTT + // OCSP monitoring + ocsps []*OCSPMonitor + // exporting account name the importer experienced issues with incompleteAccExporterMap sync.Map @@ -1467,6 +1470,29 @@ func (s *Server) fetchAccount(name string) (*Account, error) { return acc, nil } +func (s *Server) enableOCSP() error { + opts := s.getOpts() + + // Start OCSP Stapling for client connections. + if config := opts.TLSConfig; config != nil { + tc, mon, err := s.NewOCSPMonitor(config) + if err != nil { + return err + } + // Check if an OCSP stapling monitor is required for this certificate. + if mon != nil { + s.ocsps = append(s.ocsps, mon) + // Override the TLS config with one that follows OCSP. + opts.TLSConfig = tc + s.startGoRoutine(func() { mon.run() }) + } + s.Noticef("OCSP Stapling enabled for client connections") + } + // FIXME: Add support for leafnodes, routes, MQTT, WebSocket + + return nil +} + // Start up the server, this will block. // Start via a Go routine if needed. func (s *Server) Start() { @@ -1619,6 +1645,13 @@ func (s *Server) Start() { }) } + // Setup OCSP Stapling. This will abort server from starting if there + // are no valid staples and OCSP policy is to Always or MustStaple. + if err := s.enableOCSP(); err != nil { + s.Fatalf("Can't enable OCSP Stapling: %v", err) + return + } + // Start monitoring if needed if err := s.StartMonitoring(); err != nil { s.Fatalf("Can't start monitoring: %v", err) diff --git a/test/configs/certs/ocsp/ca-cert.pem b/test/configs/certs/ocsp/ca-cert.pem new file mode 100644 index 0000000000..28bf091fb9 --- /dev/null +++ b/test/configs/certs/ocsp/ca-cert.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/test/configs/certs/ocsp/ca-key.pem b/test/configs/certs/ocsp/ca-key.pem new file mode 100644 index 0000000000..4486a95a79 --- /dev/null +++ b/test/configs/certs/ocsp/ca-key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/configs/certs/ocsp/client-cert.pem b/test/configs/certs/ocsp/client-cert.pem new file mode 100644 index 0000000000..f2dfb70088 --- /dev/null +++ b/test/configs/certs/ocsp/client-cert.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +NejC2QUTFLwq9vfjyA== +-----END CERTIFICATE----- diff --git a/test/configs/certs/ocsp/client-key.pem b/test/configs/certs/ocsp/client-key.pem new file mode 100644 index 0000000000..a8a92abaf0 --- /dev/null +++ b/test/configs/certs/ocsp/client-key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/configs/certs/ocsp/server-cert.pem b/test/configs/certs/ocsp/server-cert.pem new file mode 100644 index 0000000000..1a70606ad2 --- /dev/null +++ b/test/configs/certs/ocsp/server-cert.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +IKBPu3vS3nsv67DyBA== +-----END CERTIFICATE----- diff --git a/test/configs/certs/ocsp/server-key.pem b/test/configs/certs/ocsp/server-key.pem new file mode 100644 index 0000000000..6da8ec10cc --- /dev/null +++ b/test/configs/certs/ocsp/server-key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/configs/certs/ocsp/server-status-request-cert.pem b/test/configs/certs/ocsp/server-status-request-cert.pem new file mode 100644 index 0000000000..9e01ea25a2 --- /dev/null +++ b/test/configs/certs/ocsp/server-status-request-cert.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGCjCCA/KgAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKDAdT +eW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMRUwEwYDVQQDDAxsb2NhbGhvc3QgY2Ex +HDAaBgkqhkiG9w0BCQEWDWRlcmVrQG5hdHMuaW8wHhcNMjEwNTExMjAxMTM1WhcN +MjkxMDE0MjAxMTM1WjCBnjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYD +VQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAdu +YXRzLmlvMSgwJgYDVQQDDB9sb2NhbGhvc3Qgc2VydmVyIHN0YXR1cyByZXF1ZXN0 +MRwwGgYJKoZIhvcNAQkBFg1kZXJla0BuYXRzLmlvMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAou+lxMBr5LpB6o6ZZ5S2najkJWYm555IEz5DlnCNxkX3 +UfDaO9StTSlJscc7ZwtmOF1aSAqwnI6KTV43Te5qSU0DolyPTPwBECjr5G4PgRJk +tvBaqCUrdc6PXcwHEUDukWw5G8XxJ73UZuIuwvCg/ZRsOO3V9XYL+Q9chdXS/W/Q +ZCY4bOb/7AJoD7VuUKQp6KY9ptnqdmDsRAlWiKjtW9HPpldzLwLG/J5s4TVHrU+2 +T1or+hywSKqzcjeJFAaPTsfKGdDiYAuW3yy7YlYdebsjlldGhi3Xo5J4FiAgyPa4 +I2RVdEPVa8VmOKoEbdfEL3KVmB57KmnmnDiqLpCYxyx51GvE1Opf5mi1QH8gQ0cw +r7mJqsms99A+wyBojgdAYxZa+tDcZcZTj4v0r/9OdKu+vTkVIPr2OYPprWE0PfGJ +efDILOfRrmY8OqbWLDe5d2WEVhaGHoXtxlNoUrTUY6SRW+dvubiBcQIYkWnk1c/e +p/c7zkgCicJmBQdjDcNEiadTT7r0gHIoJO2bMKSqb7gz0IBqVOl409hn2FhxNgh1 +mt4MlXl5FH5rCTUsNSR8va7r5IVNbdK6wUIfRtyLUBnDFYKR6qN8UwvPfr8JWsvZ +zT2jDcBWYabAj521KHqo0hwItW9JcD/CO0M43rPq18YzsYIR00IU8FJ7zVz7olEC +AwEAAaNkMGIwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwEQYIKwYBBQUHARgEBTAD +AgEFMDUGA1UdEQQuMCyCCWxvY2FsaG9zdIIfc2VydmVyLXN0YXR1cy1yZXF1ZXN0 +LmxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAgEAVGa9luluxfm2FU5Plo/53tys +dA1IurRs+294adJBKc+LJtZIZjCW74KmqNvlN+UKC857STjnMi1fjSHyJKRpNLfo +wyMax7G3SCbOYnNjsbMgub9BoGlGqUgdU98av3SS7dyfJ5jgeiNfNrt3RUBGZpYE +70+d+m671dPhFlpXDTlRKpbdmCno5KfMDne+aLmTsZq2D0WYpjjAXJLIlexcF6KI +1Pz+VgthkqfPczSE0CMTAtL/zuOD6c4RqtYX8Q8A5E3U09mNo7kybVvLZrQPPAfZ +vbFbPwv4qZMRZ6ZXZiNTGXXnPNbE66/Ce+Jm7TKZrrfShG7ObUmyKI+X8tObCssd +xHKn59Fe5AL+gu6gzy6wC0wfBIH5gPMtkHwg9thhKd/T02LbL5iOXHIUhAVfk6qB +h3+9GaY6eu4yhU0/C9Qrnoq8QAWM8eqiaJ0i2f9rhbs1o5Yp4NX/fD5T9jcok9+L +BE34BGO6l0b01TVvfssiYmH7a0YYO3xVMy6IgJg7hBTVIXLqFLEKfrj+rNai6Th3 +vv5rjkbJh4f3wzm5+uCFqM8BUBbJ/k5Pz/8qM105IbM89maW56R0DhsFZdMxOI+7 +gXee7BH3cBS9LcYhAgNIwF7lVOQX62dF3rYvIdkGEWjEouteno2SZRtKHNsSvviW +8PTvHAb9z7LxEYEjwC4= +-----END CERTIFICATE----- diff --git a/test/configs/certs/ocsp/server-status-request-key.pem b/test/configs/certs/ocsp/server-status-request-key.pem new file mode 100644 index 0000000000..c843d21d7c --- /dev/null +++ b/test/configs/certs/ocsp/server-status-request-key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/configs/certs/ocsp/server-status-request-url-cert-chain.pem b/test/configs/certs/ocsp/server-status-request-url-cert-chain.pem new file mode 100644 index 0000000000..4d4486a76b --- /dev/null +++ b/test/configs/certs/ocsp/server-status-request-url-cert-chain.pem @@ -0,0 +1,70 @@ +-----BEGIN CERTIFICATE----- +MIIGTDCCBDSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKDAdT +eW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMRUwEwYDVQQDDAxsb2NhbGhvc3QgY2Ex +HDAaBgkqhkiG9w0BCQEWDWRlcmVrQG5hdHMuaW8wHhcNMjEwNTExMjAxMTM1WhcN +MjkxMDE0MjAxMTM1WjCBpzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYD +VQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAdu +YXRzLmlvMTEwLwYDVQQDDChsb2NhbGhvc3Qgc2VydmVyIHN0YXR1cyByZXF1ZXN0 +IHdpdGggdXJsMRwwGgYJKoZIhvcNAQkBFg1kZXJla0BuYXRzLmlvMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4IEf3ITJxeRTEYSXJ13LS8rvoOyRrQTo +mGSSIWzkvs9ELJN/C7AtWy98m+0+lpdtaQLLZaY/hdh6Wu7tqJ0LS8axz9vWXkjr +GoPCV2MedhQG4+P6zDXQaJyEBfW1kchZY+O8Lm/MHKITp3soo6To4J+sIbzg9XQt +Yxl38HaPEwDP2xn0FkY/0HhaSYuoAo+K/bQ1tVj7vnSgvZtUWDvu5NhkyEx7qJxL +/cNfUPGadyetB3rKZfQGgYMfxu7dl5bB5yJwYVOx7uzAN4bjejUtSZ+E8xarliSH +0hxPbekIeSayMfyWp/XYtQ+hENSAnhsSUgezlQ++yl1Al9XLiwELRDTaehbBGjAB +zhnqkpOTxG5Eg220MKD8xyqLWuZTDSHBukOXVHGsr3ct1PRjJ1CH7WIfyzUuq9IH +eVCfGcNaO7BjqJKChGwJGLFVdqfoJqi7vc2475SKvkVa/jbNMyXw/ic0POV9frdg +Nkwxm6jXGeNJ7jaJND02eSinY/glf6Ed1aRK7FEuyh8oM73JmAlJ6up0Axr349HG +Uu6XaUjKCJy/eTwkEaDyeyQRHgsYj2fpl8uGui8CSrBK3+Ha9ai5Ie2LNzI81khq +2ZLH6wbg/Y3UJsTeoXrWhZOcin3z17KJkiOGbsUBntac4ClfMwKNlF1NcUOp6EQh +0N7tXhnimpMCAwEAAaOBnDCBmTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAxBggr +BgEFBQcBAQQlMCMwIQYIKwYBBQUHMAGGFWh0dHA6Ly8xMjcuMC4wLjE6ODg4ODAR +BggrBgEFBQcBGAQFMAMCAQUwOQYDVR0RBDIwMIIJbG9jYWxob3N0giNzZXJ2ZXIt +c3RhdHVzLXJlcXVlc3QtdXJsLmxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAgEA +Vbc6tBhSLLS2/1sYzpjDn293nvBruNeConbj3GmjdFqkp3iD2jsVilLYZbgQfjJY +KEzWXYEXWTtpcpTxVFZFZwdc1C5oMmhUsUOkm7eEcVn5+oGR8k/kQ+pbSQQd3Ii8 +uqZ3Z8vmD3kItM/8MWWGUdpNjqLwronTTO4XfqPLYtNIeFd6pjLmY3xMopOzl5G/ +PY8010e8ZMU5xCzYCJ73vc8bhQMevoMuJFfNEzWWQiKwy74lAiJObIQ8wHkIgaa5 +mrBl7oU7dvuz22eaItCY04DO06A5e3QJKOBsW0NOjcKRw6sHXbVtU1hp0CUsxcrz +HCttM22Pmi/8u+0blmWPHK5cPQoPQsks6QOXD8Ecp74oxB9sbJNUIJ3KJSkUZqUW +T/sbQhqHe/6DGEbl4WWpPsvBp2HRHOhaLlE3tPVLDSQE55mMpaSfeC6FjHvW38MD +aG6rGRI6Y9ibJBHEcy12kPo6APjSPtIGrAdCKBGBgUothKiiWAx/6ul+sKhzSL4N +iFjNODYdwwUxlINyX3jqSyHLjq5QFNH+aF1NOou1D1qbrDhzWJ09u1FrpNRTtf0F +2sAfEPihQu5DxnODqbwH5YOSNfGNXQXbY6JxZE7a5VCfocHa6u/uhN6jJg+aNllM +rTV3nI+hWq4RtvQnqAyuhMkX2q/T+QaRugRtJ1ywUtE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF+TCCA+GgAwIBAgIUOZOobSCn5WL7fGjMTas5i191p8kwDQYJKoZIhvcNAQEL +BQAwgYsxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZy +YW5jaXNjbzEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEVMBMG +A1UEAwwMbG9jYWxob3N0IGNhMRwwGgYJKoZIhvcNAQkBFg1kZXJla0BuYXRzLmlv +MB4XDTIxMDUxMTIwMTEzM1oXDTIxMDYxMDIwMTEzM1owgYsxCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEQMA4GA1UECgwH +U3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEVMBMGA1UEAwwMbG9jYWxob3N0IGNh +MRwwGgYJKoZIhvcNAQkBFg1kZXJla0BuYXRzLmlvMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAs1szTYUBy7GPQnDaH5863bEVldI2xxNa5xZalaNywI+H +z058zxuvsDeR2gmcgfZM4kuLW7eDL0IjPHmJfklS8GhoN5ZuzFSY5EHGqBKTfX0Z +vvl1aNZo2GGaPgnR+EbC9NjxdsVcEkfkW9pWC2NXqdCISHiv3uh5IjyfAjxDS/t7 +sngSyrYF+L4HCJ7rbOnfCpzfYD48Rw5l76wGzN05dAjElU8Y/0mbX6H8GPKQ7mZZ +K8z5OIHwioQmHf9KxKl870pZQKm+u3pDIhBNweH8TNr6AuI8n2YbCfC+HyjjV041 +Xprh26Y145xqQvlIoPHZtEPK94mF0w92sE79jX8mZ1LbQlDcBzpvqXaC/YwaRYgf +82/51UNY/xSYxg2vkmie6l7dDMIVMmmFUs2ZOOFxuwo3ikls8Tx0FlnIUkBjobuA +cc1MN9Opva1G9j977UocmTe0T23hEOcqYdoLJ5WM/l6b29h8+74xhws2SFkQh2Lb +CLLFfelK3wpIyequ4DzHwLfyLhqasRWE4ac9Q04G5NR+T3QKMuVCxcTEREsLk/5S +hwqZoFc8zyIT/9cVRuUtTzM/EOQq4c7CKKcE4NGJb0a+hR6drvWmYi9Zsgt1sRC3 +bSsxXUaAPQE8/tVdEBW97TNA3yK9THfHPV1MUQNCbw3FIW+qIYBQISjKatvGJD8C +AwEAAaNTMFEwHQYDVR0OBBYEFCPCNWQ7F7gS6gt1zIprz5405GmQMB8GA1UdIwQY +MBaAFCPCNWQ7F7gS6gt1zIprz5405GmQMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBAEI3tPoBKh026z1V2zNLNP9L+ZNcrbA1Yw74DOIOVs6InnuM +Grxjy9zYdNXQs5UOtDsPvH8e2u1mpS/es24GnTXA0LLkS0gCcKaaDdzTnEr4zCOn +khogeOZphY7UCX1SAi7qVrwcRXOWdSxSrAaQY17gl7Ln+W4QTWjIyNpweZIKAMu4 +60kIJYteNCnXgE5WppW7qICYdHapJz8/sJzqrtlPTR2PnmW1+aiWF86KeEFnXAai +60PNHojVbd7/U2425x4oZQRGoZ1/5azs8w2X/bidshwtz/kYtHPBP4Xzt3c6M92v +HDwpa9wIrvRoQxmUGWjPcZkVvm53ZIU/YYxQ+ueJkuS3Eloh4bP1kNKDDXKZWzmT +Oo8q3K0lnPlTAjeX5nVgkDuKq1WG+DdCp5VVAsVsOnl/fRN6N7230PfNTudazgSR +UjOLE6HsZDGYemdZJaydmQNvCWjBwRAlqd7IdmCtBSZVtxTZ88VLT6R85QzOWn9G +eCxNaJLeBBanR8HG/dFtMf1kzJ0csTwWnituG4inhE+90J2UmaBSrLbUJdbwrG66 +j1njHQH6WpEswg48XtLB8OuYh8YJs7+yCX7S1l8q/NaPUGZadg1nPR5mokcf4VvW +rrEcTc9ma9VUszgm518FE+Ibb5zQeK6YFhBU29JjcdATUK4RzglWt7mtrfxr +-----END CERTIFICATE----- diff --git a/test/configs/certs/ocsp/server-status-request-url-cert.pem b/test/configs/certs/ocsp/server-status-request-url-cert.pem new file mode 100644 index 0000000000..c421dbae18 --- /dev/null +++ b/test/configs/certs/ocsp/server-status-request-url-cert.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGTDCCBDSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKDAdT +eW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMRUwEwYDVQQDDAxsb2NhbGhvc3QgY2Ex +HDAaBgkqhkiG9w0BCQEWDWRlcmVrQG5hdHMuaW8wHhcNMjEwNTExMjAxMTM1WhcN +MjkxMDE0MjAxMTM1WjCBpzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYD +VQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAdu +YXRzLmlvMTEwLwYDVQQDDChsb2NhbGhvc3Qgc2VydmVyIHN0YXR1cyByZXF1ZXN0 +IHdpdGggdXJsMRwwGgYJKoZIhvcNAQkBFg1kZXJla0BuYXRzLmlvMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4IEf3ITJxeRTEYSXJ13LS8rvoOyRrQTo +mGSSIWzkvs9ELJN/C7AtWy98m+0+lpdtaQLLZaY/hdh6Wu7tqJ0LS8axz9vWXkjr +GoPCV2MedhQG4+P6zDXQaJyEBfW1kchZY+O8Lm/MHKITp3soo6To4J+sIbzg9XQt +Yxl38HaPEwDP2xn0FkY/0HhaSYuoAo+K/bQ1tVj7vnSgvZtUWDvu5NhkyEx7qJxL +/cNfUPGadyetB3rKZfQGgYMfxu7dl5bB5yJwYVOx7uzAN4bjejUtSZ+E8xarliSH +0hxPbekIeSayMfyWp/XYtQ+hENSAnhsSUgezlQ++yl1Al9XLiwELRDTaehbBGjAB +zhnqkpOTxG5Eg220MKD8xyqLWuZTDSHBukOXVHGsr3ct1PRjJ1CH7WIfyzUuq9IH +eVCfGcNaO7BjqJKChGwJGLFVdqfoJqi7vc2475SKvkVa/jbNMyXw/ic0POV9frdg +Nkwxm6jXGeNJ7jaJND02eSinY/glf6Ed1aRK7FEuyh8oM73JmAlJ6up0Axr349HG +Uu6XaUjKCJy/eTwkEaDyeyQRHgsYj2fpl8uGui8CSrBK3+Ha9ai5Ie2LNzI81khq +2ZLH6wbg/Y3UJsTeoXrWhZOcin3z17KJkiOGbsUBntac4ClfMwKNlF1NcUOp6EQh +0N7tXhnimpMCAwEAAaOBnDCBmTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAxBggr +BgEFBQcBAQQlMCMwIQYIKwYBBQUHMAGGFWh0dHA6Ly8xMjcuMC4wLjE6ODg4ODAR +BggrBgEFBQcBGAQFMAMCAQUwOQYDVR0RBDIwMIIJbG9jYWxob3N0giNzZXJ2ZXIt +c3RhdHVzLXJlcXVlc3QtdXJsLmxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAgEA +Vbc6tBhSLLS2/1sYzpjDn293nvBruNeConbj3GmjdFqkp3iD2jsVilLYZbgQfjJY +KEzWXYEXWTtpcpTxVFZFZwdc1C5oMmhUsUOkm7eEcVn5+oGR8k/kQ+pbSQQd3Ii8 +uqZ3Z8vmD3kItM/8MWWGUdpNjqLwronTTO4XfqPLYtNIeFd6pjLmY3xMopOzl5G/ +PY8010e8ZMU5xCzYCJ73vc8bhQMevoMuJFfNEzWWQiKwy74lAiJObIQ8wHkIgaa5 +mrBl7oU7dvuz22eaItCY04DO06A5e3QJKOBsW0NOjcKRw6sHXbVtU1hp0CUsxcrz +HCttM22Pmi/8u+0blmWPHK5cPQoPQsks6QOXD8Ecp74oxB9sbJNUIJ3KJSkUZqUW +T/sbQhqHe/6DGEbl4WWpPsvBp2HRHOhaLlE3tPVLDSQE55mMpaSfeC6FjHvW38MD +aG6rGRI6Y9ibJBHEcy12kPo6APjSPtIGrAdCKBGBgUothKiiWAx/6ul+sKhzSL4N +iFjNODYdwwUxlINyX3jqSyHLjq5QFNH+aF1NOou1D1qbrDhzWJ09u1FrpNRTtf0F +2sAfEPihQu5DxnODqbwH5YOSNfGNXQXbY6JxZE7a5VCfocHa6u/uhN6jJg+aNllM +rTV3nI+hWq4RtvQnqAyuhMkX2q/T+QaRugRtJ1ywUtE= +-----END CERTIFICATE----- diff --git a/test/configs/certs/ocsp/server-status-request-url-key.pem b/test/configs/certs/ocsp/server-status-request-url-key.pem new file mode 100644 index 0000000000..0d02330b85 --- /dev/null +++ b/test/configs/certs/ocsp/server-status-request-url-key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/ocsp_test.go b/test/ocsp_test.go new file mode 100644 index 0000000000..d852a042a4 --- /dev/null +++ b/test/ocsp_test.go @@ -0,0 +1,1039 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/nats-io/nats-server/v2/server" + "github.com/nats-io/nats.go" + "golang.org/x/crypto/ocsp" +) + +func TestOCSPAlwaysMustStapleAndShutdown(t *testing.T) { + // Certs that have must staple will auto shutdown the server. + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-cert.pem" + serverKey = "configs/certs/ocsp/server-key.pem" + ) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + defer ocspr.Shutdown(ctx) + addr := fmt.Sprintf("http://%s", ocspr.Addr) + setOCSPStatus(t, addr, serverCert, ocsp.Good) + + opts := DefaultTestOptions + opts.Port = -1 + opts.TLSCert = serverCert + opts.TLSKey = serverKey + opts.TLSCaCert = caCert + opts.TLSTimeout = 5 + tcOpts := &server.TLSConfigOpts{ + CertFile: opts.TLSCert, + KeyFile: opts.TLSKey, + CaFile: opts.TLSCaCert, + Timeout: opts.TLSTimeout, + } + + tlsConf, err := server.GenTLSConfig(tcOpts) + if err != nil { + t.Fatal(err) + } + opts.TLSConfig = tlsConf + + opts.OCSPConfig = &server.OCSPConfig{ + Mode: server.OCSPModeAlways, + OverrideURLs: []string{addr}, + } + srv := RunServer(&opts) + defer srv.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Good { + return fmt.Errorf("invalid staple") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatal(err) + } + nc.Publish("foo", []byte("hello world")) + nc.Flush() + + _, err = sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // The server will shutdown because the server becomes revoked + // and the policy is to always must-staple. The OCSP Responder + // instructs the NATS Server to fetch OCSP Staples every 2 seconds. + time.Sleep(2 * time.Second) + setOCSPStatus(t, addr, serverCert, ocsp.Revoked) + time.Sleep(2 * time.Second) + + // Should be connection refused since server will abort now. + _, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nats.ErrNoServers { + t.Errorf("Expected connection refused") + } +} + +func TestOCSPMustStapleShutdown(t *testing.T) { + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-status-request-cert.pem" + serverKey = "configs/certs/ocsp/server-status-request-key.pem" + ) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + defer ocspr.Shutdown(ctx) + addr := fmt.Sprintf("http://%s", ocspr.Addr) + setOCSPStatus(t, addr, serverCert, ocsp.Good) + + opts := DefaultTestOptions + opts.Port = -1 + opts.TLSCert = serverCert + opts.TLSKey = serverKey + opts.TLSCaCert = caCert + opts.TLSTimeout = 5 + tlsConfigOpts := &server.TLSConfigOpts{ + CertFile: opts.TLSCert, + KeyFile: opts.TLSKey, + CaFile: opts.TLSCaCert, + Timeout: opts.TLSTimeout, + } + + tlsConf, err := server.GenTLSConfig(tlsConfigOpts) + if err != nil { + t.Fatal(err) + } + opts.TLSConfig = tlsConf + + opts.OCSPConfig = &server.OCSPConfig{ + Mode: server.OCSPModeMust, + OverrideURLs: []string{addr}, + } + + srv := RunServer(&opts) + defer srv.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Good { + return fmt.Errorf("invalid staple") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatal(err) + } + nc.Publish("foo", []byte("hello world")) + nc.Flush() + + _, err = sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // The server will shutdown because the server becomes revoked + // and the policy is to always must-staple. The OCSP Responder + // instructs the NATS Server to fetch OCSP Staples every 2 seconds. + time.Sleep(2 * time.Second) + setOCSPStatus(t, addr, serverCert, ocsp.Revoked) + time.Sleep(2 * time.Second) + + // Should be connection refused since server will abort now. + _, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nats.ErrNoServers { + t.Errorf("Expected connection refused") + } +} + +func TestOCSPMustStapleAutoDoesNotShutdown(t *testing.T) { + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-status-request-url-cert.pem" + ) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + defer ocspr.Shutdown(ctx) + addr := fmt.Sprintf("http://%s", ocspr.Addr) + setOCSPStatus(t, addr, serverCert, ocsp.Good) + + content := ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-status-request-url-cert.pem" + key_file: "configs/certs/ocsp/server-status-request-url-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + ` + conf := createConfFile(t, []byte(content)) + defer removeFile(t, conf) + s, opts := RunServerWithConfig(conf) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Good { + t.Errorf("Expected valid OCSP staple status") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatal(err) + } + nc.Publish("foo", []byte("hello world")) + nc.Flush() + + _, err = sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // The server will shutdown because the server becomes revoked + // and the policy is to always must-staple. The OCSP Responder + // instructs the NATS Server to fetch OCSP Staples every 2 seconds. + time.Sleep(2 * time.Second) + setOCSPStatus(t, addr, serverCert, ocsp.Revoked) + time.Sleep(2 * time.Second) + + // Should not be connection refused, the client will continue running and + // be served the stale OCSP staple instead. + _, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Revoked { + t.Errorf("Expected revoked status") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } +} + +func TestOCSPAutoWithoutMustStapleDoesNotShutdownOnRevoke(t *testing.T) { + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-cert.pem" + serverKey = "configs/certs/ocsp/server-key.pem" + ) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + defer ocspr.Shutdown(ctx) + addr := fmt.Sprintf("http://%s", ocspr.Addr) + setOCSPStatus(t, addr, serverCert, ocsp.Good) + + opts := DefaultTestOptions + opts.Port = -1 + opts.TLSCert = serverCert + opts.TLSKey = serverKey + opts.TLSCaCert = caCert + opts.TLSTimeout = 5 + tlsConfigOpts := &server.TLSConfigOpts{ + CertFile: opts.TLSCert, + KeyFile: opts.TLSKey, + CaFile: opts.TLSCaCert, + Timeout: opts.TLSTimeout, + } + tlsConf, err := server.GenTLSConfig(tlsConfigOpts) + if err != nil { + t.Fatal(err) + } + opts.TLSConfig = tlsConf + + opts.OCSPConfig = &server.OCSPConfig{ + Mode: server.OCSPModeAuto, + OverrideURLs: []string{addr}, + } + + srv := RunServer(&opts) + defer srv.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + if s.OCSPResponse != nil { + return fmt.Errorf("Unexpected OCSP staple for certificate") + } + return nil + }, + }), + + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatal(err) + } + nc.Publish("foo", []byte("hello world")) + nc.Flush() + + _, err = sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // Revoke the client certificate, nothing will happens since does + // not have MustStaple. + time.Sleep(2 * time.Second) + setOCSPStatus(t, addr, serverCert, ocsp.Revoked) + time.Sleep(2 * time.Second) + + // Should not be connection refused since server will continue running. + _, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Errorf("Unexpected error: %s", err) + } +} + +func TestOCSPClient(t *testing.T) { + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-cert.pem" + serverKey = "configs/certs/ocsp/server-key.pem" + ) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + ocspURL := fmt.Sprintf("http://%s", ocspr.Addr) + defer ocspr.Shutdown(ctx) + + for _, test := range []struct { + name string + config string + opts []nats.Option + err error + rerr error + configure func() + }{ + { + "OCSP Stapling makes server fail to boot if status is unknown", + ` + port: -1 + + # Enable OCSP stapling with policy to honor must staple if present. + ocsp: true + + tls { + cert_file: "configs/certs/ocsp/server-cert.pem" + key_file: "configs/certs/ocsp/server-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + `, + []nats.Option{ + nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + }, + nil, + nil, + func() {}, + }, + { + "OCSP Stapling ignored by default if server without must staple status", + ` + port: -1 + + ocsp: true + + tls { + cert_file: "configs/certs/ocsp/server-cert.pem" + key_file: "configs/certs/ocsp/server-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + `, + []nats.Option{ + nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + }, + nil, + nil, + func() { setOCSPStatus(t, ocspURL, serverCert, ocsp.Good) }, + }, + { + "OCSP Stapling honored by default if server has must staple status", + ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-status-request-url-cert.pem" + key_file: "configs/certs/ocsp/server-status-request-url-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + `, + []nats.Option{ + nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + }, + nil, + nil, + func() { + setOCSPStatus(t, ocspURL, "configs/certs/ocsp/server-status-request-url-cert.pem", ocsp.Good) + }, + }, + { + "OCSP Stapling can be disabled even if server has must staple status", + ` + port: -1 + + ocsp: false + + tls { + cert_file: "configs/certs/ocsp/server-status-request-url-cert.pem" + key_file: "configs/certs/ocsp/server-status-request-url-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + `, + []nats.Option{ + nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + }, + nil, + nil, + func() { + setOCSPStatus(t, ocspURL, "configs/certs/ocsp/server-status-request-url-cert.pem", ocsp.Revoked) + }, + }, + } { + t.Run(test.name, func(t *testing.T) { + test.configure() + content := test.config + conf := createConfFile(t, []byte(content)) + defer removeFile(t, conf) + s, opts := RunServerWithConfig(conf) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...) + if test.err == nil && err != nil { + t.Errorf("Expected to connect, got %v", err) + } else if test.err != nil && err == nil { + t.Errorf("Expected error on connect") + } else if test.err != nil && err != nil { + // Error on connect was expected + if test.err.Error() != err.Error() { + t.Errorf("Expected error %s, got: %s", test.err, err) + } + return + } + defer nc.Close() + + nc.Subscribe("ping", func(m *nats.Msg) { + m.Respond([]byte("pong")) + }) + nc.Flush() + + _, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond) + if test.rerr != nil && err == nil { + t.Errorf("Expected error getting response") + } else if test.rerr == nil && err != nil { + t.Errorf("Expected response") + } + }) + } +} + +func TestOCSPReloadRotateTLSCertWithNoURL(t *testing.T) { + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-status-request-url-cert.pem" + updatedServerCert = "configs/certs/ocsp/server-status-request-cert.pem" + ) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + defer ocspr.Shutdown(ctx) + addr := fmt.Sprintf("http://%s", ocspr.Addr) + setOCSPStatus(t, addr, serverCert, ocsp.Good) + + content := ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-status-request-url-cert.pem" + key_file: "configs/certs/ocsp/server-status-request-url-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + ` + conf := createConfFile(t, []byte(content)) + defer removeFile(t, conf) + s, opts := RunServerWithConfig(conf) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Good { + t.Errorf("Expected valid OCSP staple status") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatal(err) + } + nc.Publish("foo", []byte("hello world")) + nc.Flush() + + _, err = sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // Change the contents with another that will fail to get a staple + // since it does not have an URL. + content = ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-status-request-cert.pem" + key_file: "configs/certs/ocsp/server-status-request-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + ` + if err := ioutil.WriteFile(conf, []byte(content), 0666); err != nil { + t.Fatalf("Error writing config: %v", err) + } + // Reload show warning because of cert missing OCSP Url so cannot be used + // with OCSP stapling. + if err := s.Reload(); err != nil { + t.Fatal(err) + } + expectedErr := fmt.Errorf("missing OCSP response") + // The server will not shutdown because the reload will fail. + _, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + // The new certificate does not have OCSP Staples since + // it could not fetch one from a OCSP server. + if s.OCSPResponse == nil { + return expectedErr + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != expectedErr { + t.Fatalf("Unexpected error: %s", expectedErr) + } +} + +func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) { + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-status-request-url-cert.pem" + updatedServerCert = "configs/certs/ocsp/server-status-request-cert.pem" + ) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + defer ocspr.Shutdown(ctx) + addr := fmt.Sprintf("http://%s", ocspr.Addr) + setOCSPStatus(t, addr, serverCert, ocsp.Good) + + content := ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-status-request-url-cert.pem" + key_file: "configs/certs/ocsp/server-status-request-url-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + ` + conf := createConfFile(t, []byte(content)) + defer removeFile(t, conf) + s, opts := RunServerWithConfig(conf) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Good { + t.Errorf("Expected valid OCSP staple status") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatal(err) + } + nc.Publish("foo", []byte("hello world")) + nc.Flush() + + _, err = sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // Change the contents with another that has OCSP Stapling disabled. + content = ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-cert.pem" + key_file: "configs/certs/ocsp/server-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + ` + if err := ioutil.WriteFile(conf, []byte(content), 0666); err != nil { + t.Fatalf("Error writing config: %v", err) + } + if err := s.Reload(); err != nil { + t.Fatal(err) + } + + // The new certificate does not have must staple so they will be missing. + time.Sleep(2 * time.Second) + + nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + if s.OCSPResponse != nil { + return fmt.Errorf("unexpected OCSP Staple!") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + nc.Close() +} + +func TestOCSPReloadRotateTLSCertEnableMustStaple(t *testing.T) { + const ( + caCert = "configs/certs/ocsp/ca-cert.pem" + caKey = "configs/certs/ocsp/ca-key.pem" + serverCert = "configs/certs/ocsp/server-cert.pem" + updatedServerCert = "configs/certs/ocsp/server-status-request-url-cert.pem" + ) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ocspr := newOCSPResponder(t, caCert, caKey) + defer ocspr.Shutdown(ctx) + addr := fmt.Sprintf("http://%s", ocspr.Addr) + setOCSPStatus(t, addr, serverCert, ocsp.Good) + setOCSPStatus(t, addr, updatedServerCert, ocsp.Good) + + // Start without OCSP Stapling MustStaple + content := ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-cert.pem" + key_file: "configs/certs/ocsp/server-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + ` + conf := createConfFile(t, []byte(content)) + defer removeFile(t, conf) + s, opts := RunServerWithConfig(conf) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + if s.OCSPResponse != nil { + return fmt.Errorf("unexpected OCSP Staple!") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatal(err) + } + nc.Publish("foo", []byte("hello world")) + nc.Flush() + + _, err = sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // Change the contents with another that has OCSP Stapling enabled. + content = ` + port: -1 + + tls { + cert_file: "configs/certs/ocsp/server-status-request-url-cert.pem" + key_file: "configs/certs/ocsp/server-status-request-url-key.pem" + ca_file: "configs/certs/ocsp/ca-cert.pem" + timeout: 5 + } + ` + if err := ioutil.WriteFile(conf, []byte(content), 0666); err != nil { + t.Fatalf("Error writing config: %v", err) + } + if err := s.Reload(); err != nil { + t.Fatal(err) + } + + // The new certificate does not have must staple so they will be missing. + time.Sleep(2 * time.Second) + + nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Good { + t.Errorf("Expected valid OCSP staple status") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + nc.Close() +} + +func newOCSPResponder(t *testing.T, issuerCertPEM, issuerKeyPEM string) *http.Server { + t.Helper() + var mu sync.Mutex + status := make(map[string]int) + + issuerCert := parseCertPEM(t, issuerCertPEM) + issuerKey := parseKeyPEM(t, issuerKeyPEM) + + mux := http.NewServeMux() + // The "/statuses/" endpoint is for directly setting a key-value pair in + // the CA's status database. + mux.HandleFunc("/statuses/", func(rw http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + key := r.URL.Path[len("/statuses/"):] + switch r.Method { + case "GET": + mu.Lock() + n, ok := status[key] + if !ok { + n = ocsp.Unknown + } + mu.Unlock() + + fmt.Fprintf(rw, "%s %d", key, n) + case "POST": + data, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + n, err := strconv.Atoi(string(data)) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + mu.Lock() + status[key] = n + mu.Unlock() + + fmt.Fprintf(rw, "%s %d", key, n) + default: + http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + }) + // The "/" endpoint is for normal OCSP requests. This actually parses an + // OCSP status request and signs a response with a CA. Lightly based off: + // https://www.ietf.org/rfc/rfc2560.txt + mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + + reqData, err := base64.StdEncoding.DecodeString(r.URL.Path[1:]) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + ocspReq, err := ocsp.ParseRequest(reqData) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + mu.Lock() + n, ok := status[ocspReq.SerialNumber.String()] + if !ok { + n = ocsp.Unknown + } + mu.Unlock() + + tmpl := ocsp.Response{ + Status: n, + SerialNumber: ocspReq.SerialNumber, + ThisUpdate: time.Now(), + NextUpdate: time.Now().Add(4 * time.Second), + } + respData, err := ocsp.CreateResponse(issuerCert, issuerCert, tmpl, issuerKey) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + rw.Header().Set("Content-Type", "application/ocsp-response") + rw.Header().Set("Content-Length", fmt.Sprint(len(respData))) + + fmt.Fprint(rw, string(respData)) + }) + + srv := &http.Server{ + Addr: "127.0.0.1:8888", + Handler: mux, + } + go srv.ListenAndServe() + time.Sleep(1 * time.Second) + return srv +} + +func setOCSPStatus(t *testing.T, ocspURL, certPEM string, status int) { + t.Helper() + + cert := parseCertPEM(t, certPEM) + + hc := &http.Client{Timeout: 10 * time.Second} + resp, err := hc.Post( + fmt.Sprintf("%s/statuses/%s", ocspURL, cert.SerialNumber), + "", + strings.NewReader(fmt.Sprint(status)), + ) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("failed to read OCSP HTTP response body: %s", err) + } + + if got, want := resp.Status, "200 OK"; got != want { + t.Error(strings.TrimSpace(string(data))) + t.Fatalf("unexpected OCSP HTTP set status, got %q, want %q", got, want) + } +} + +func parseCertPEM(t *testing.T, certPEM string) *x509.Certificate { + t.Helper() + block := parsePEM(t, certPEM) + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatalf("failed to parse cert '%s': %s", certPEM, err) + } + return cert +} + +func parseKeyPEM(t *testing.T, keyPEM string) *rsa.PrivateKey { + t.Helper() + block := parsePEM(t, keyPEM) + + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + t.Fatalf("failed to parse ikey %s: %s", keyPEM, err) + } + return key +} + +func parsePEM(t *testing.T, pemPath string) *pem.Block { + t.Helper() + data, err := ioutil.ReadFile(pemPath) + if err != nil { + t.Fatal(err) + } + + block, _ := pem.Decode(data) + if block == nil { + t.Fatalf("failed to decode PEM %s", pemPath) + } + return block +} + +func getOCSPStatus(s tls.ConnectionState) (*ocsp.Response, error) { + if len(s.VerifiedChains) == 0 { + return nil, fmt.Errorf("missing TLS verified chains") + } + chain := s.VerifiedChains[0] + + if got, want := len(chain), 2; got < want { + return nil, fmt.Errorf("incomplete cert chain, got %d, want at least %d", got, want) + } + leaf, issuer := chain[0], chain[1] + + resp, err := ocsp.ParseResponseForCert(s.OCSPResponse, leaf, issuer) + if err != nil { + return nil, fmt.Errorf("failed to parse OCSP response: %w", err) + } + if err := resp.CheckSignatureFrom(issuer); err != nil { + return resp, err + } + return resp, nil +} From d78a91836b6527f24420004013cfdb31c9bbb9f6 Mon Sep 17 00:00:00 2001 From: Waldemar Quevedo Date: Fri, 21 May 2021 18:27:04 -0700 Subject: [PATCH 3/3] ocsp: Add caching staples to disk to store dir Signed-off-by: Waldemar Quevedo --- server/config_check_test.go | 23 ++++++- server/ocsp.go | 90 +++++++++++++++++++++++++- server/opts.go | 11 ++++ server/reload.go | 12 +++- server/server.go | 23 ++++++- test/ocsp_test.go | 125 ++++++++++++++++++++++++++++++++++-- 6 files changed, 270 insertions(+), 14 deletions(-) diff --git a/server/config_check_test.go b/server/config_check_test.go index 94e0afbe2d..21582afd1d 100644 --- a/server/config_check_test.go +++ b/server/config_check_test.go @@ -1464,6 +1464,16 @@ func TestConfigCheck(t *testing.T) { errorLine: 6, errorPos: 10, }, + { + name: "ambiguous store dir", + config: ` + store_dir: "foo" + jetstream { + store_dir: "bar" + } + `, + err: fmt.Errorf(`Duplicate 'store_dir' configuration`), + }, } checkConfig := func(config string) error { @@ -1499,10 +1509,17 @@ func TestConfigCheck(t *testing.T) { } if err != nil && expectedErr != nil { - msg := fmt.Sprintf("%s:%d:%d: %s", conf, test.errorLine, test.errorPos, expectedErr.Error()) - if test.reason != "" { - msg += ": " + test.reason + var msg string + + if test.errorPos > 0 { + msg = fmt.Sprintf("%s:%d:%d: %s", conf, test.errorLine, test.errorPos, expectedErr.Error()) + if test.reason != "" { + msg += ": " + test.reason + } + } else { + msg = test.reason } + if !strings.Contains(err.Error(), msg) { t.Errorf("Expected:\n%q\ngot:\n%q", msg, err.Error()) } diff --git a/server/ocsp.go b/server/ocsp.go index 6c1c909195..a4556f0ba7 100644 --- a/server/ocsp.go +++ b/server/ocsp.go @@ -14,6 +14,7 @@ package server import ( + "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/asn1" @@ -22,6 +23,8 @@ import ( "fmt" "io/ioutil" "net/http" + "os" + "path/filepath" "strings" "sync" "time" @@ -30,6 +33,7 @@ import ( ) const ( + defaultOCSPStoreDir = "ocsp" defaultOCSPCheckInterval = 24 * time.Hour minOCSPCheckInterval = 2 * time.Minute ) @@ -92,8 +96,17 @@ func (oc *OCSPMonitor) getNextRun() time.Duration { func (oc *OCSPMonitor) getStatus() ([]byte, *ocsp.Response, error) { raw, resp := oc.getCacheStatus() if len(raw) > 0 && resp != nil { + // Check if the OCSP is still valid. + if err := validOCSPResponse(resp); err == nil { + return raw, resp, nil + } + } + var err error + raw, resp, err = oc.getLocalStatus() + if err == nil { return raw, resp, nil } + return oc.getRemoteStatus() } @@ -103,6 +116,41 @@ func (oc *OCSPMonitor) getCacheStatus() ([]byte, *ocsp.Response) { return oc.raw, oc.resp } +func (oc *OCSPMonitor) getLocalStatus() ([]byte, *ocsp.Response, error) { + opts := oc.srv.getOpts() + storeDir := opts.StoreDir + if storeDir == _EMPTY_ { + return nil, nil, fmt.Errorf("store_dir not set") + } + + // This key must be based upon the current full certificate, not the public key, + // so MUST be on the full raw certificate and not an SPKI or other reduced form. + key := fmt.Sprintf("%x", sha256.Sum256(oc.Leaf.Raw)) + + oc.mu.Lock() + raw, err := ioutil.ReadFile(filepath.Join(storeDir, defaultOCSPStoreDir, key)) + oc.mu.Unlock() + if err != nil { + return nil, nil, err + } + + resp, err := ocsp.ParseResponse(raw, oc.Issuer) + if err != nil { + return nil, nil, err + } + if err := validOCSPResponse(resp); err != nil { + return nil, nil, err + } + + // Cache the response. + oc.mu.Lock() + oc.raw = raw + oc.resp = resp + oc.mu.Unlock() + + return raw, resp, nil +} + func (oc *OCSPMonitor) getRemoteStatus() ([]byte, *ocsp.Response, error) { opts := oc.srv.getOpts() var overrideURLs []string @@ -163,6 +211,13 @@ func (oc *OCSPMonitor) getRemoteStatus() ([]byte, *ocsp.Response, error) { return nil, nil, err } + if storeDir := opts.StoreDir; storeDir != _EMPTY_ { + key := fmt.Sprintf("%x", sha256.Sum256(oc.Leaf.Raw)) + if err := oc.writeOCSPStatus(storeDir, key, raw); err != nil { + return nil, nil, fmt.Errorf("failed to write ocsp status: %w", err) + } + } + oc.mu.Lock() oc.raw = raw oc.resp = resp @@ -201,7 +256,7 @@ func (oc *OCSPMonitor) run() { return } - for s.Running() { + for { // On reload, if the certificate changes then need to stop this monitor. select { case <-time.After(nextRun): @@ -294,6 +349,10 @@ func (srv *Server) NewOCSPMonitor(tc *tls.Config) (*tls.Config, *OCSPMonitor, er return tc, nil, nil } + if err := srv.setupOCSPStapleStoreDir(); err != nil { + return nil, nil, err + } + // TODO: Add OCSP 'responder_cert' option in case CA cert not available. issuer, err := getOCSPIssuer(caFile, cert.Certificate) if err != nil { @@ -384,6 +443,35 @@ func hasOCSPStatusRequest(cert *x509.Certificate) bool { return false } +// writeOCSPStatus writes an OCSP status to a temporary file then moves it to a +// new path, in an attempt to avoid corrupting existing data. +func (oc *OCSPMonitor) writeOCSPStatus(storeDir, file string, data []byte) error { + storeDir = filepath.Join(storeDir, defaultOCSPStoreDir) + tmp, err := ioutil.TempFile(storeDir, "tmp-cert-status") + if err != nil { + return err + } + + if _, err := tmp.Write(data); err != nil { + tmp.Close() + os.Remove(tmp.Name()) + return err + } + if err := tmp.Close(); err != nil { + return err + } + + oc.mu.Lock() + err = os.Rename(tmp.Name(), filepath.Join(storeDir, file)) + oc.mu.Unlock() + if err != nil { + os.Remove(tmp.Name()) + return err + } + + return nil +} + func parseCertPEM(name string) (*x509.Certificate, error) { data, err := ioutil.ReadFile(name) if err != nil { diff --git a/server/opts.go b/server/opts.go index e79541fc3f..68d8695333 100644 --- a/server/opts.go +++ b/server/opts.go @@ -793,6 +793,13 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error *errors = append(*errors, err) return } + case "store_dir", "storedir": + // Check if JetStream configuration is also setting the storage directory. + if o.StoreDir != "" { + *errors = append(*errors, &configErr{tk, "Duplicate 'store_dir' configuration"}) + return + } + o.StoreDir = v.(string) case "jetstream": err := parseJetStream(tk, o, errors, warnings) if err != nil { @@ -1558,6 +1565,10 @@ func parseJetStream(v interface{}, opts *Options, errors *[]error, warnings *[]e tk, mv = unwrapValue(mv, <) switch strings.ToLower(mk) { case "store_dir", "storedir": + // StoreDir can be set at the top level as well so have to prevent ambiguous declarations. + if opts.StoreDir != "" { + return &configErr{tk, "Duplicate 'store_dir' configuration"} + } opts.StoreDir = mv.(string) case "max_memory_store", "max_mem_store", "max_mem": opts.JetStreamMaxMemory = mv.(int64) diff --git a/server/reload.go b/server/reload.go index 5704e90e58..ccfec13bdf 100644 --- a/server/reload.go +++ b/server/reload.go @@ -1285,12 +1285,18 @@ func (s *Server) applyOptions(ctx *reloadContext, opts []option) { func (s *Server) reloadOCSP() error { opts := s.getOpts() + if err := s.setupOCSPStapleStoreDir(); err != nil { + return err + } + s.mu.Lock() ocsps := s.ocsps s.mu.Unlock() // Stop all OCSP Stapling monitors in case there were any running. + var wasEnabled bool for _, oc := range ocsps { + wasEnabled = true oc.stop() } @@ -1303,15 +1309,17 @@ func (s *Server) reloadOCSP() error { } // Check if an OCSP stapling monitor is required for this certificate. if mon != nil { + s.Noticef("OCSP Stapling enabled for client connections") ocspm = append(ocspm, mon) - // Override the TLS config with one that follows OCSP. + // Override the TLS config with one that has OCSP enabled. s.optsMu.Lock() s.opts.TLSConfig = tc s.optsMu.Unlock() s.startGoRoutine(func() { mon.run() }) + } else if wasEnabled { + s.Warnf("OCSP Stapling disabled for client connections") } - s.Noticef("OCSP Stapling enabled for client connections") } // Replace stopped monitors with the new ones. s.mu.Lock() diff --git a/server/server.go b/server/server.go index a2ef244f0e..ff1bebb5fc 100644 --- a/server/server.go +++ b/server/server.go @@ -1470,6 +1470,24 @@ func (s *Server) fetchAccount(name string) (*Account, error) { return acc, nil } +func (s *Server) setupOCSPStapleStoreDir() error { + opts := s.getOpts() + storeDir := opts.StoreDir + if storeDir == _EMPTY_ { + s.Warnf("OCSP Stapling disk cache is disabled (missing 'store_dir')") + return nil + } + storeDir = filepath.Join(storeDir, defaultOCSPStoreDir) + if stat, err := os.Stat(storeDir); os.IsNotExist(err) { + if err := os.MkdirAll(storeDir, defaultDirPerms); err != nil { + return fmt.Errorf("could not create OCSP storage directory - %v", err) + } + } else if stat == nil || !stat.IsDir() { + return fmt.Errorf("OCSP storage directory is not a directory") + } + return nil +} + func (s *Server) enableOCSP() error { opts := s.getOpts() @@ -1481,14 +1499,15 @@ func (s *Server) enableOCSP() error { } // Check if an OCSP stapling monitor is required for this certificate. if mon != nil { + s.Noticef("OCSP Stapling enabled for client connections") + s.ocsps = append(s.ocsps, mon) // Override the TLS config with one that follows OCSP. opts.TLSConfig = tc s.startGoRoutine(func() { mon.run() }) } - s.Noticef("OCSP Stapling enabled for client connections") } - // FIXME: Add support for leafnodes, routes, MQTT, WebSocket + // FIXME: Add support for leafnodes, routes, gateways, MQTT, WebSocket return nil } diff --git a/test/ocsp_test.go b/test/ocsp_test.go index d852a042a4..204fb57123 100644 --- a/test/ocsp_test.go +++ b/test/ocsp_test.go @@ -14,6 +14,7 @@ package test import ( + "bytes" "context" "crypto/rsa" "crypto/tls" @@ -23,6 +24,8 @@ import ( "fmt" "io/ioutil" "net/http" + "os" + "path/filepath" "strconv" "strings" "sync" @@ -50,7 +53,11 @@ func TestOCSPAlwaysMustStapleAndShutdown(t *testing.T) { addr := fmt.Sprintf("http://%s", ocspr.Addr) setOCSPStatus(t, addr, serverCert, ocsp.Good) - opts := DefaultTestOptions + opts := server.Options{} + opts.Host = "127.0.0.1" + opts.NoLog = true + opts.NoSigs = true + opts.MaxControlLine = 4096 opts.Port = -1 opts.TLSCert = serverCert opts.TLSKey = serverKey @@ -140,7 +147,11 @@ func TestOCSPMustStapleShutdown(t *testing.T) { addr := fmt.Sprintf("http://%s", ocspr.Addr) setOCSPStatus(t, addr, serverCert, ocsp.Good) - opts := DefaultTestOptions + opts := server.Options{} + opts.Host = "127.0.0.1" + opts.NoLog = true + opts.NoSigs = true + opts.MaxControlLine = 4096 opts.Port = -1 opts.TLSCert = serverCert opts.TLSKey = serverKey @@ -321,7 +332,11 @@ func TestOCSPAutoWithoutMustStapleDoesNotShutdownOnRevoke(t *testing.T) { addr := fmt.Sprintf("http://%s", ocspr.Addr) setOCSPStatus(t, addr, serverCert, ocsp.Good) - opts := DefaultTestOptions + opts := server.Options{} + opts.Host = "127.0.0.1" + opts.NoLog = true + opts.NoSigs = true + opts.MaxControlLine = 4096 opts.Port = -1 opts.TLSCert = serverCert opts.TLSKey = serverKey @@ -663,9 +678,14 @@ func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) { addr := fmt.Sprintf("http://%s", ocspr.Addr) setOCSPStatus(t, addr, serverCert, ocsp.Good) - content := ` + storeDir := createDir(t, "_ocsp") + defer removeDir(t, storeDir) + + originalContent := ` port: -1 + store_dir: "%s" + tls { cert_file: "configs/certs/ocsp/server-status-request-url-cert.pem" key_file: "configs/certs/ocsp/server-status-request-url-key.pem" @@ -673,14 +693,18 @@ func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) { timeout: 5 } ` + + content := fmt.Sprintf(originalContent, storeDir) conf := createConfFile(t, []byte(content)) defer removeFile(t, conf) s, opts := RunServerWithConfig(conf) defer s.Shutdown() + var staple []byte nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), nats.Secure(&tls.Config{ VerifyConnection: func(s tls.ConnectionState) error { + staple = s.OCSPResponse resp, err := getOCSPStatus(s) if err != nil { return err @@ -710,10 +734,37 @@ func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) { } nc.Close() + files := []string{} + err = filepath.Walk(storeDir+"/ocsp/", func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + files = append(files, path) + return nil + }) + if err != nil { + t.Fatal(err) + } + found := false + for _, file := range files { + data, err := ioutil.ReadFile(file) + if err != nil { + t.Error(err) + } + if bytes.Equal(staple, data) { + found = true + } + } + if !found { + t.Error("Could not find OCSP Staple") + } + // Change the contents with another that has OCSP Stapling disabled. - content = ` + updatedContent := ` port: -1 + store_dir: "%s" + tls { cert_file: "configs/certs/ocsp/server-cert.pem" key_file: "configs/certs/ocsp/server-key.pem" @@ -721,6 +772,7 @@ func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) { timeout: 5 } ` + content = fmt.Sprintf(updatedContent, storeDir) if err := ioutil.WriteFile(conf, []byte(content), 0666); err != nil { t.Fatalf("Error writing config: %v", err) } @@ -729,7 +781,7 @@ func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) { } // The new certificate does not have must staple so they will be missing. - time.Sleep(2 * time.Second) + time.Sleep(4 * time.Second) nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), nats.Secure(&tls.Config{ @@ -747,6 +799,67 @@ func TestOCSPReloadRotateTLSCertDisableMustStaple(t *testing.T) { t.Fatal(err) } nc.Close() + + // Re-enable OCSP Stapling + content = fmt.Sprintf(originalContent, storeDir) + if err := ioutil.WriteFile(conf, []byte(content), 0666); err != nil { + t.Fatalf("Error writing config: %v", err) + } + if err := s.Reload(); err != nil { + t.Fatal(err) + } + + var newStaple []byte + nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), + nats.Secure(&tls.Config{ + VerifyConnection: func(s tls.ConnectionState) error { + newStaple = s.OCSPResponse + resp, err := getOCSPStatus(s) + if err != nil { + return err + } + if resp.Status != ocsp.Good { + t.Errorf("Expected valid OCSP staple status") + } + return nil + }, + }), + nats.RootCAs(caCert), + nats.ErrorHandler(noOpErrHandler), + ) + if err != nil { + t.Fatal(err) + } + nc.Close() + + // Confirm that it got a new staple. + files = []string{} + err = filepath.Walk(storeDir+"/ocsp/", func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + files = append(files, path) + return nil + }) + if err != nil { + t.Fatal(err) + } + found = false + for _, file := range files { + data, err := ioutil.ReadFile(file) + if err != nil { + t.Error(err) + } + if bytes.Equal(newStaple, data) { + found = true + } + } + if !found { + t.Error("Could not find OCSP Staple") + } + if bytes.Equal(staple, newStaple) { + t.Error("Expected new OCSP Staple") + } } func TestOCSPReloadRotateTLSCertEnableMustStaple(t *testing.T) {