From 46eccd76581d2ce30272e4ae556d1e6366fffa8f Mon Sep 17 00:00:00 2001 From: Waldemar Quevedo Date: Mon, 26 Apr 2021 14:20:25 -0700 Subject: [PATCH] WIP: NATS Server + OCSP Support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Waldemar Quevedo Signed-off-by: Jaime PiƱa --- go.mod | 2 +- go.sum | 2 + server/client.go | 106 ++++ server/opts.go | 22 +- server/server.go | 13 + test/configs/certs/ocsp/ca-cert.pem | 33 + test/configs/certs/ocsp/ca-key.pem | 51 ++ test/configs/certs/ocsp/client-cert.pem | 126 ++++ test/configs/certs/ocsp/client-key.pem | 51 ++ test/configs/certs/ocsp/server-cert.pem | 126 ++++ test/configs/certs/ocsp/server-key.pem | 51 ++ test/ocspresponder_test.go | 245 ++++++++ test/tls_test.go | 105 ++++ vendor/golang.org/x/crypto/ocsp/ocsp.go | 789 ++++++++++++++++++++++++ vendor/modules.txt | 3 +- 15 files changed, 1722 insertions(+), 3 deletions(-) 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/ocspresponder_test.go create mode 100644 vendor/golang.org/x/crypto/ocsp/ocsp.go diff --git a/go.mod b/go.mod index ff9c0d72..811cff76 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/nats-io/nats.go v1.10.1-0.20210419223411-20527524c393 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-20210421170649-83a5a9bb288b 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 9c617a6c..3da20b46 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-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 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/server/client.go b/server/client.go index ccca42b4..91936225 100644 --- a/server/client.go +++ b/server/client.go @@ -15,15 +15,20 @@ package server import ( "bytes" + "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/json" + "encoding/pem" "fmt" + "golang.org/x/crypto/ocsp" "io" + "io/ioutil" "math/rand" "net" "net/http" "net/url" + "path/filepath" "regexp" "runtime" "strconv" @@ -1645,6 +1650,107 @@ func computeRTT(start time.Time) time.Duration { return rtt } +func (srv *Server) fetchOCSPStaples(tlsConfig *tls.Config) error { + opts := srv.getOpts() + + // FIXME: Need to do this for all types of connections. + // FIXME: Add option for the state directory. + dir, err := ioutil.TempDir("", "nats-staples-") + if err != nil { + return err + } + srv.stateDir = dir + + // Parse and decode CA certificate to verify OCSP signatures. + caCert := opts.OCSPConfig.CaFile + data, err := ioutil.ReadFile(caCert) + if err != nil { + return err + } + block, _ := pem.Decode(data) + if block == nil { + return fmt.Errorf("Error decoding CA certificate") + } + issuer, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return err + } + if !issuer.IsCA { + return fmt.Errorf("Invalid CA certificate: %s", caCert) + } + + for _, cert := range tlsConfig.Certificates { + leaf := cert.Leaf + + // Take the URL. + ocspServer := leaf.OCSPServer[0] + if ocspServer == "" { + return fmt.Errorf("Missing OCSP Url in certificate") + } + ocspRequest, err := ocsp.CreateRequest(leaf, issuer, nil) + if err != nil { + return err + } + ocspRequestReader := bytes.NewReader(ocspRequest) + resp, err := http.Post(ocspServer, "application/ocsp-request", ocspRequestReader) + if err != nil { + return err + } + defer resp.Body.Close() + + ocspResponseBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + ocspResponse, err := ocsp.ParseResponse(ocspResponseBytes, issuer) + if err != nil { + return err + } + switch ocspResponse.Status { + case ocsp.Good: + // Store the certificate that can be served to the user on connect. + sha := sha256.New() + sha.Write(leaf.Raw) + digest := fmt.Sprintf("%x", sha.Sum(nil)) + stapleFile := filepath.Join(dir, digest) + srv.Debugf("Storing OCSP staple at %s", stapleFile) + err := ioutil.WriteFile(stapleFile, ocspResponseBytes, 0644) + if err != nil { + return fmt.Errorf("Error storing OCSP staple: %s", err) + } + case ocsp.Revoked: + return fmt.Errorf("Error fetching OCSP staples, certificate is revoked") + default: + // FIXME: Handle unknown state certificates. + continue + } + } + + // Setup the callback to server the staples to the user. + tlsConfig.GetCertificate = func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { + // TODO: Multi cert? + // TODO: Race condition + for _, cert := range tlsConfig.Certificates { + leaf := cert.Leaf + sha := sha256.New() + sha.Write(leaf.Raw) + digest := fmt.Sprintf("%x", sha.Sum(nil)) + stapleFile := filepath.Join(dir, digest) + data, err := ioutil.ReadFile(stapleFile) + if err != nil { + return nil, err + } + cert.OCSPStaple = data + + // Return the first cert. + return &cert, nil + } + return nil, fmt.Errorf("No certs") + } + + return nil +} + // processConnect will process a client connect op. func (c *client) processConnect(arg []byte) error { supportsHeaders := c.srv.supportsHeaders() diff --git a/server/opts.go b/server/opts.go index 10b08549..0f4d22af 100644 --- a/server/opts.go +++ b/server/opts.go @@ -225,6 +225,7 @@ type Options struct { TLSKey string `json:"-"` TLSCaCert string `json:"-"` TLSConfig *tls.Config `json:"-"` + OCSPConfig *OCSPConfig `json:"-"` AllowNonTLS bool `json:"-"` WriteDeadline time.Duration `json:"-"` MaxClosedClients int `json:"-"` @@ -469,6 +470,13 @@ type TLSConfigOpts struct { Timeout float64 Ciphers []uint16 CurvePreferences []tls.CurveID + OCSP *OCSPConfig +} + +// OCSPConfig represents the options of OCSP stapling options. +type OCSPConfig struct { + MustStaple bool + CaFile string } var tlsUsage = ` @@ -827,6 +835,12 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error o.TLSTimeout = tc.Timeout o.TLSMap = tc.Map + // TODO: Make this DRY for leafnode, gateway, routes, websockets, mqtt... + o.OCSPConfig = tc.OCSP + if o.OCSPConfig != nil { + // Need to keep state of the CA directory to verify OCSP signatures. + o.OCSPConfig.CaFile = tc.CaFile + } case "allow_non_tls": o.AllowNonTLS = v.(bool) case "write_deadline": @@ -3432,6 +3446,13 @@ func parseTLS(v interface{}, isClientCtx bool) (t *TLSConfigOpts, retErr error) at = mv } tc.Timeout = at + case "ocsp": + switch v := mv.(type) { + case bool: + tc.OCSP = &OCSPConfig{MustStaple: v} + default: + return nil, &configErr{tk, fmt.Sprintf("error parsing ocsp config: unsupported type %T", v)} + } default: return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field [%q]", mk)} } @@ -3732,7 +3753,6 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) { } config.ClientCAs = pool } - return &config, nil } diff --git a/server/server.go b/server/server.go index a9612e76..f3418916 100644 --- a/server/server.go +++ b/server/server.go @@ -250,6 +250,9 @@ type Server struct { // For out of resources to not log errors too fast. rerrMu sync.Mutex rerrLast time.Time + + // stateDir used for caching things like OCSP staples. + stateDir string } type nodeInfo struct { @@ -1480,6 +1483,16 @@ func (s *Server) Start() { s.Noticef("Using configuration file: %s", opts.ConfigFile) } + // Check if need to get staples. + if opts.OCSPConfig != nil { + s.Noticef("OCSP Stapling Configuration: must_staple=%v", opts.OCSPConfig.MustStaple) + err := s.fetchOCSPStaples(opts.TLSConfig) + if err != nil { + s.Fatalf("Error getting OCSP staples: %v", err) + return + } + } + hasOperators := len(opts.TrustedOperators) > 0 if hasOperators { s.Noticef("Trusted Operators") diff --git a/test/configs/certs/ocsp/ca-cert.pem b/test/configs/certs/ocsp/ca-cert.pem new file mode 100644 index 00000000..1aad87f3 --- /dev/null +++ b/test/configs/certs/ocsp/ca-cert.pem @@ -0,0 +1,33 @@ +-----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 00000000..8861ff81 --- /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 00000000..bf3f98a5 --- /dev/null +++ b/test/configs/certs/ocsp/client-cert.pem @@ -0,0 +1,126 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=California, L=San Francisco, O=Example CA, OU=IT, CN=ca.example + Validity + Not Before: Apr 29 22:27:06 2021 GMT + Not After : Jul 28 22:27:06 2021 GMT + Subject: C=US, ST=California, L=San Francisco, O=Example Widgets Ltd, OU=IT, CN=NATS Client + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (4096 bit) + Modulus: + 00:a6:48:c5:e0:d0:74:d0:bd:d3:22:6f:a5:19:20: + 9c:a2:7b:d8:cd:7a:69:1a:5f:8f:61:b6:a3:59:92: + 90:a6:32:2a:66:31:ff:a5:10:69:ae:6b:b7:5f:d9: + f1:00:f4:6f:af:8f:f4:31:24:33:bb:a7:ed:4a:ca: + 43:c3:d9:69:24:c0:04:85:53:df:e9:63:93:82:65: + 33:90:c4:3a:3f:ee:7c:f3:67:71:97:21:ac:97:0c: + db:b3:ac:89:6f:ee:f7:d2:cb:aa:92:57:99:3d:f0: + 6f:84:a9:f7:fa:52:b4:53:46:df:8d:95:6a:1c:18: + 30:f3:df:a2:46:b5:db:ff:f5:ce:1c:52:b8:05:0d: + 22:f5:40:18:1d:5b:c7:98:7d:eb:58:da:74:b8:05: + 05:8b:3a:aa:5f:8d:98:dc:7e:5b:fc:eb:b9:44:0a: + 93:ff:b5:ff:31:8b:2c:cd:cd:29:37:58:4f:bf:ce: + ac:0f:b3:bc:c4:89:71:b4:61:a7:f8:00:bd:c2:10: + f8:97:3b:ec:e7:b1:00:4a:25:68:f8:2e:00:15:74: + bb:7c:0d:eb:c5:2e:87:f2:75:96:07:c4:22:fa:a3: + a7:9e:f1:89:e3:d0:54:ae:c1:a0:ed:1f:a6:ed:fc: + c8:36:08:97:bb:7f:c4:26:4a:7d:a2:17:86:86:6b: + 18:4c:48:bf:ec:12:c7:98:19:09:61:8e:2b:cd:2f: + ca:ae:a3:21:8a:c8:e4:eb:36:fe:e7:69:32:e0:28: + 31:0f:b1:a9:ec:d2:ce:87:bc:5c:99:b8:ec:19:d7: + 03:53:38:1a:95:46:0b:2b:3e:6e:fe:48:70:10:b9: + b6:cd:8f:f0:e3:8a:41:ce:cb:9b:96:15:c6:6c:09: + bd:9e:ef:9f:50:3b:ea:78:2f:40:a6:0d:1d:f9:98: + c5:90:ed:2e:ac:47:44:32:22:d7:2d:de:6d:31:07: + c0:ef:94:d5:b7:4d:0d:8a:07:86:02:16:5c:57:c5: + d2:ce:d3:57:45:66:0f:bb:b5:f0:89:e6:54:c6:27: + 01:fa:b4:41:85:92:ed:be:68:59:b2:91:ed:7c:49: + 68:04:dc:f2:da:70:05:6c:2d:60:79:0c:7f:2b:70: + 1f:50:0c:e6:99:5d:75:1a:d9:b0:2a:a2:d5:44:69: + 9d:9a:e0:e7:c4:10:36:4f:e0:04:a8:de:8b:b5:12: + 9b:d4:3f:33:4c:5d:cb:0d:dc:1e:44:b5:94:13:9b: + 7a:4c:69:92:bc:9f:20:cb:58:be:66:3f:45:1f:37: + 61:63:e7:ab:d7:81:ef:b4:14:7c:0f:56:bc:a1:f9: + b9:8d:46:46:96:a9:0a:b6:42:28:70:e0:20:71:ef: + 0c:ca:69 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888 + + TLS Feature: + status_request + X509v3 Subject Alternative Name: + DNS:localhost, DNS:client.localhost + Signature Algorithm: sha256WithRSAEncryption + 0c:71:0f:85:e8:22:48:59:e1:29:52:e2:06:f6:87:94:38:df: + 96:5e:46:25:2a:a1:37:cd:8c:62:79:a5:a5:db:6f:4a:a8:7f: + e4:f9:7f:e4:80:d3:2c:8a:86:8d:2c:97:6b:d2:b5:21:75:a1: + 1d:de:69:7a:e7:cb:2c:14:cf:ba:e7:e7:01:b6:f4:52:f7:d7: + ae:85:fa:66:b2:25:df:6a:ef:98:30:3c:fc:30:4b:62:30:a9: + 37:b6:45:9e:5d:b7:b0:45:b2:88:e3:2a:c0:91:eb:2d:35:1f: + d6:80:45:6f:88:1b:63:13:b4:85:59:69:67:a3:5d:18:da:a3: + ec:65:30:6e:3a:a7:76:c6:e5:a3:0d:ea:53:6d:e6:1d:99:d2: + d1:26:0f:a5:b3:5c:e4:a6:96:62:51:f6:67:70:c1:9c:c5:09: + e9:e1:e0:0c:b4:ed:be:9b:3b:4e:8b:c9:65:55:09:28:87:50: + 29:e6:28:66:2c:9e:66:cf:d0:c4:47:85:b0:00:51:1b:8b:4f: + 56:b5:69:f3:8c:30:13:e8:13:d3:d1:3a:15:2e:36:16:cd:4c: + 40:4c:16:99:a2:f6:96:f1:b7:4a:05:ff:54:37:05:06:9b:06: + 50:50:66:ae:b4:b1:81:17:c6:54:84:d4:d3:88:1c:4a:99:4d: + 10:ff:38:c7:8b:7f:e8:5e:d0:5d:d3:4c:3e:a6:7e:13:cf:87: + 49:d0:fa:50:8a:10:5b:c9:89:50:3f:d8:ca:8f:11:5f:f6:62: + 03:7d:06:2a:59:b2:f7:0f:b6:5d:40:5e:5e:67:f8:68:45:f2: + d4:d8:67:b1:6d:3d:06:89:ac:c8:45:47:97:77:2f:9d:95:d7: + e2:c3:61:ad:07:df:b9:fd:3d:b3:f1:d1:19:88:07:03:c6:7d: + 37:f0:34:18:1c:f5:85:c3:d4:03:70:19:c3:8a:bd:76:d9:7e: + e4:49:f7:62:b7:f3:81:b4:c9:a3:e6:28:e6:7c:43:d8:7d:30: + 37:1d:1b:15:72:52:ae:6c:94:6e:2d:83:74:49:91:59:e1:aa: + 04:96:68:29:a1:ca:8b:23:c9:7d:93:1a:5b:d7:bb:4a:32:98: + 13:c9:64:5a:cc:3d:ca:dc:66:fe:11:2c:b4:98:32:1f:45:f4: + 79:76:b7:f0:60:4d:fa:ae:b3:b9:37:00:29:c4:8f:51:51:32: + 94:6a:6c:fc:b7:37:4d:46:40:9c:67:92:4b:63:dc:84:bd:6d: + 17:e5:ca:ae:b9:cb:6f:20:ab:e3:e5:12:5f:0a:2c:91:a6:43: + b5:2b:c1:a8:d1:41:42:a8:23:e6:57:d4:46:e4:84:1a:58:11: + b9:10:f4:65:19:dd:57:bf +-----BEGIN CERTIFICATE----- +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 +-----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 00000000..995b5c9d --- /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 00000000..15c043ec --- /dev/null +++ b/test/configs/certs/ocsp/server-cert.pem @@ -0,0 +1,126 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=California, L=San Francisco, O=Example CA, OU=IT, CN=ca.example + Validity + Not Before: Apr 29 22:27:06 2021 GMT + Not After : Jul 28 22:27:06 2021 GMT + Subject: C=US, ST=California, L=San Francisco, O=Example Widgets Ltd, OU=IT, CN=NATS Server + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (4096 bit) + Modulus: + 00:b2:95:25:59:11:6a:42:42:f0:31:e7:49:f7:a0: + 8f:fc:2f:33:b6:43:4d:3b:98:d6:c3:06:a9:b0:dc: + a9:c0:ee:77:e2:27:8c:8a:cd:be:0d:4d:45:64:d4: + be:17:9e:a9:c0:5f:b2:21:42:5c:2c:a0:09:32:2a: + 01:75:1d:e7:05:62:33:31:9d:96:fe:fa:44:c3:0a: + 08:50:80:dc:0e:26:dd:c5:c6:5a:c6:66:c7:f6:4c: + db:2e:12:36:a7:a0:1e:63:ba:66:92:e5:d6:af:c0: + fb:da:2c:01:fe:6b:91:1e:1d:2d:e7:19:27:56:69: + 3e:90:90:ca:11:6b:9a:e3:22:76:5e:42:d5:63:2b: + 3f:1c:77:f2:89:a9:d4:6a:a8:34:ec:6a:52:74:1e: + 0e:a8:c7:bd:98:b0:a5:f7:11:90:7f:37:aa:05:d8: + 07:6d:55:ff:1f:3a:71:54:a2:76:1d:a5:59:93:92: + b8:77:b9:57:33:fc:61:35:64:76:96:b5:3f:44:88: + b2:05:d6:b9:e7:71:66:b6:a5:ce:05:0a:df:9d:b1: + b2:8a:24:ac:7a:62:8c:39:18:e7:ec:6d:e8:62:37: + 4e:2a:78:83:3c:6f:25:48:ef:e5:13:7e:b8:89:9f: + 39:57:95:67:99:c6:96:db:52:24:a6:15:18:11:b4: + 01:a0:e0:35:8d:d7:95:3b:ba:35:af:33:6d:bd:61: + 6c:3e:5d:cb:f6:39:e4:f6:35:28:bb:7a:07:70:25: + 14:28:65:0b:10:59:d8:c4:5e:66:df:b0:fb:fe:87: + d2:7e:e7:cf:05:06:a5:da:c1:07:37:2a:ce:64:4b: + af:2d:e5:2c:3f:41:1d:08:bd:49:b7:3b:22:67:00: + a1:45:51:92:70:6e:b6:76:5f:4b:b8:40:bd:e9:4a: + 75:33:59:36:3f:9e:3a:69:66:2f:9c:3e:37:8c:62: + f4:41:bc:6f:c7:74:80:52:a1:a9:c5:63:3d:50:2c: + 27:54:e9:57:f1:58:41:91:45:26:95:3f:df:3a:92: + 12:db:21:e4:08:61:b2:f6:d1:b1:1a:e5:b1:cf:7c: + e9:7d:c7:27:a2:0f:c6:11:a4:8f:c2:6f:6a:51:fe: + 76:be:75:29:69:f2:dc:a5:28:d6:e7:b3:ca:ed:f2: + 7f:59:cb:5f:75:d0:f6:6a:71:50:81:e2:9f:5b:e7: + 88:98:cd:15:e0:d1:61:53:65:04:08:e9:ab:cb:14: + 5c:6f:34:9b:ca:30:99:e0:bf:0e:de:4d:9a:bf:8f: + c7:dd:e1:6c:7d:24:b8:89:53:70:4a:16:d0:15:bc: + 6d:40:63:3e:df:90:1b:a2:2b:bb:78:e6:47:9e:08: + e3:ea:79 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888 + + TLS Feature: + status_request + X509v3 Subject Alternative Name: + DNS:localhost, DNS:server.localhost + Signature Algorithm: sha256WithRSAEncryption + 3b:ea:73:11:c9:25:93:5e:00:56:bd:a6:ea:0d:9f:b6:77:10: + a6:8a:2a:f0:b9:bc:60:99:d9:aa:7d:c2:a0:cc:37:9e:8a:65: + 6c:b1:24:9e:75:e1:53:e3:65:12:fa:ca:d4:ad:c6:07:60:a3: + 9b:f4:c1:64:0c:04:33:28:67:1f:55:3d:1b:eb:05:b5:90:b0: + 5d:36:ea:47:7c:ab:8c:75:36:f4:76:89:c6:8f:e3:3e:f0:d3: + 82:d0:55:f5:76:4e:88:74:bc:29:d9:c8:ee:8e:42:56:f1:72: + 39:1d:5f:b1:ef:00:d4:86:a0:0f:82:39:98:6a:fb:fc:44:e2: + e3:e5:0a:5a:ed:36:f9:32:d6:e8:93:a8:a0:e1:7a:ff:4f:23: + e6:d1:70:af:6a:95:0f:98:94:d6:f4:bd:e1:aa:89:6c:68:04: + c9:ea:eb:39:64:d7:4a:8d:d1:3f:68:36:f6:45:55:d6:0f:6e: + 3c:f0:19:55:fb:73:01:64:7d:f3:92:8f:4a:29:08:70:f3:f7: + 75:c8:b8:75:1d:55:7c:e8:36:2d:47:96:52:f3:23:c2:03:10: + 9b:4f:eb:df:53:11:15:d7:5c:a9:32:59:45:a8:44:23:a8:63: + db:a4:4a:14:e1:ea:56:00:b6:e7:9b:7f:29:5b:54:6b:84:79: + 80:05:1a:e3:1a:b5:a0:0d:bc:e4:15:aa:f9:98:05:05:ac:10: + a0:37:13:a4:0f:46:7e:2f:d1:b4:12:a8:e5:18:5f:13:8f:09: + 06:2d:20:1b:f1:74:5e:2f:c0:a0:80:97:da:7b:94:29:b9:72: + be:13:47:91:d6:c2:3a:51:25:e1:e1:a1:da:83:6c:8b:0a:87: + b8:fb:a4:91:e2:80:62:cf:c7:b7:ad:69:40:bd:de:df:b0:73: + de:eb:82:69:a9:16:04:75:2f:76:8c:49:ea:d1:e0:61:e8:91: + 8e:a4:fd:5e:90:7c:2f:46:99:ed:bc:2e:61:a8:ba:9f:ff:52: + c0:3d:11:2c:cd:96:e3:f7:d1:9a:52:b7:8f:56:cd:23:6e:a5: + 31:f4:32:f3:dd:26:41:1f:bb:94:93:54:c0:7d:c1:4b:fc:0a: + 5b:5a:53:83:cc:da:9a:92:48:3b:ab:ff:bb:ad:9b:af:8b:cd: + 84:20:b9:57:75:a0:19:0a:07:7b:8c:7b:39:34:db:d8:dc:15: + 22:08:69:20:2b:a8:29:e2:9f:f3:f8:e1:c9:87:a8:02:d2:20: + a4:65:26:91:6f:e9:e5:37:9c:c8:f8:61:47:12:52:8b:0d:0d: + c1:67:57:ba:eb:f3:d1:09:58:af:38:c5:69:fe:0d:06:2b:3c: + 73:31:35:a7:05:2e:c2:7e +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIBATANBgkqhkiG9w0BAQsFADBxMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEG +A1UECgwKRXhhbXBsZSBDQTELMAkGA1UECwwCSVQxEzARBgNVBAMMCmNhLmV4YW1w +bGUwHhcNMjEwNDI5MjIyNzA2WhcNMjEwNzI4MjIyNzA2WjB7MQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEc +MBoGA1UECgwTRXhhbXBsZSBXaWRnZXRzIEx0ZDELMAkGA1UECwwCSVQxFDASBgNV +BAMMC05BVFMgU2VydmVyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +spUlWRFqQkLwMedJ96CP/C8ztkNNO5jWwwapsNypwO534ieMis2+DU1FZNS+F56p +wF+yIUJcLKAJMioBdR3nBWIzMZ2W/vpEwwoIUIDcDibdxcZaxmbH9kzbLhI2p6Ae +Y7pmkuXWr8D72iwB/muRHh0t5xknVmk+kJDKEWua4yJ2XkLVYys/HHfyianUaqg0 +7GpSdB4OqMe9mLCl9xGQfzeqBdgHbVX/HzpxVKJ2HaVZk5K4d7lXM/xhNWR2lrU/ +RIiyBda553FmtqXOBQrfnbGyiiSsemKMORjn7G3oYjdOKniDPG8lSO/lE364iZ85 +V5VnmcaW21IkphUYEbQBoOA1jdeVO7o1rzNtvWFsPl3L9jnk9jUou3oHcCUUKGUL +EFnYxF5m37D7/ofSfufPBQal2sEHNyrOZEuvLeUsP0EdCL1JtzsiZwChRVGScG62 +dl9LuEC96Up1M1k2P546aWYvnD43jGL0Qbxvx3SAUqGpxWM9UCwnVOlX8VhBkUUm +lT/fOpIS2yHkCGGy9tGxGuWxz3zpfccnog/GEaSPwm9qUf52vnUpafLcpSjW57PK +7fJ/WctfddD2anFQgeKfW+eImM0V4NFhU2UECOmryxRcbzSbyjCZ4L8O3k2av4/H +3eFsfSS4iVNwShbQFbxtQGM+35Aboiu7eOZHngjj6nkCAwEAAaOBiTCBhjAJBgNV +HRMEAjAAMAsGA1UdDwQEAwIF4DAxBggrBgEFBQcBAQQlMCMwIQYIKwYBBQUHMAGG +FWh0dHA6Ly8xMjcuMC4wLjE6ODg4ODARBggrBgEFBQcBGAQFMAMCAQUwJgYDVR0R +BB8wHYIJbG9jYWxob3N0ghBzZXJ2ZXIubG9jYWxob3N0MA0GCSqGSIb3DQEBCwUA +A4ICAQA76nMRySWTXgBWvabqDZ+2dxCmiirwubxgmdmqfcKgzDeeimVssSSedeFT +42US+srUrcYHYKOb9MFkDAQzKGcfVT0b6wW1kLBdNupHfKuMdTb0donGj+M+8NOC +0FX1dk6IdLwp2cjujkJW8XI5HV+x7wDUhqAPgjmYavv8ROLj5Qpa7Tb5Mtbok6ig +4Xr/TyPm0XCvapUPmJTW9L3hqolsaATJ6us5ZNdKjdE/aDb2RVXWD2488BlV+3MB +ZH3zko9KKQhw8/d1yLh1HVV86DYtR5ZS8yPCAxCbT+vfUxEV11ypMllFqEQjqGPb +pEoU4epWALbnm38pW1RrhHmABRrjGrWgDbzkFar5mAUFrBCgNxOkD0Z+L9G0Eqjl +GF8TjwkGLSAb8XReL8CggJfae5QpuXK+E0eR1sI6USXh4aHag2yLCoe4+6SR4oBi +z8e3rWlAvd7fsHPe64JpqRYEdS92jEnq0eBh6JGOpP1ekHwvRpntvC5hqLqf/1LA +PREszZbj99GaUrePVs0jbqUx9DLz3SZBH7uUk1TAfcFL/ApbWlODzNqakkg7q/+7 +rZuvi82EILlXdaAZCgd7jHs5NNvY3BUiCGkgK6gp4p/z+OHJh6gC0iCkZSaRb+nl +N5zI+GFHElKLDQ3BZ1e66/PRCVivOMVp/g0GKzxzMTWnBS7Cfg== +-----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 00000000..bfec971d --- /dev/null +++ b/test/configs/certs/ocsp/server-key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEAspUlWRFqQkLwMedJ96CP/C8ztkNNO5jWwwapsNypwO534ieM +is2+DU1FZNS+F56pwF+yIUJcLKAJMioBdR3nBWIzMZ2W/vpEwwoIUIDcDibdxcZa +xmbH9kzbLhI2p6AeY7pmkuXWr8D72iwB/muRHh0t5xknVmk+kJDKEWua4yJ2XkLV +Yys/HHfyianUaqg07GpSdB4OqMe9mLCl9xGQfzeqBdgHbVX/HzpxVKJ2HaVZk5K4 +d7lXM/xhNWR2lrU/RIiyBda553FmtqXOBQrfnbGyiiSsemKMORjn7G3oYjdOKniD +PG8lSO/lE364iZ85V5VnmcaW21IkphUYEbQBoOA1jdeVO7o1rzNtvWFsPl3L9jnk +9jUou3oHcCUUKGULEFnYxF5m37D7/ofSfufPBQal2sEHNyrOZEuvLeUsP0EdCL1J +tzsiZwChRVGScG62dl9LuEC96Up1M1k2P546aWYvnD43jGL0Qbxvx3SAUqGpxWM9 +UCwnVOlX8VhBkUUmlT/fOpIS2yHkCGGy9tGxGuWxz3zpfccnog/GEaSPwm9qUf52 +vnUpafLcpSjW57PK7fJ/WctfddD2anFQgeKfW+eImM0V4NFhU2UECOmryxRcbzSb +yjCZ4L8O3k2av4/H3eFsfSS4iVNwShbQFbxtQGM+35Aboiu7eOZHngjj6nkCAwEA +AQKCAgATS4wagIgzmpnrOmtChyWngM4cjk8E9nGV5t1kz6LDxsBLPiywPqSfOU8b +IuVbFfiPYYeUkl/1LmFv+xvnNJDmgwK3BKjw6ceOL0JQuGuOV+K0YMckEqzeTtWL +t8oR+HnwGo7EpnK40KglnYWloKewUkTIaG5YpsxswEgk87RrgCi77IqzyVNrVwZs +m13pHD2DocVA6JJ66AHzQnrcJBixtij3sqyywozrKdxe4aLTdy8jg8HMpcjnRAiY +xT8O74xjckWR3e5+NEF90JJe9xrMJgv9YxMDsiwmayt73zAxlHosmpLLb9Lee5UL +szBrJjoC3ZCxC8T283wkObk9CG74baJDHcJrRYJ6XMY9lZ6X0jgY0HFU3aSovs49 +9FSCxAMhdpQ7ej6GzOUBLs6NH4mUNm7SkrU3PJ5bEd5F9gxs+cQaMmy2tf91rZ96 +eeQdP+IpL8XDqwPfF2VnqQMc8bFjYwhOtrPSnjoWc/WdyigpNbRbUAKzRb8KOO8e +JUbcG57egbAFkfOjW8Vy1qS1+DEEoiwjik6+bQA5/3HisJYH9v90JzR74zQmQhou +CPqJfdjIYiqGH8LnT8hSsCBAIb+Afaqm2FHAncfPbzNaIAeQgLfT9S3JlHWsCZg7 +/lgWTks9v09r4zOG9H8UkNJJujLVWzIzn8afXBwfCx5C23D/RQKCAQEA3xIM/FzO +iOcv0oGX343MoPcGvcSaUOQ4YI8pa1tjLx3iBkdovwO9tgA2POCEVi7q0ajRPSZx +qAC+no+SoX7Q1Bl6aFaR6k8fwtZwuG8QrRSHsdjeeYn2pVyHTxRAvfGVm2seK9x/ +6GqkyyYhFYdz6h1O5hunKQBwyYCWCroTgLW2WhKEdgiT9Mk89nS/ZL2yZkFr5jOP +9oXm+XApibInn8ZdpXE3FvkZ0ykXqaZ2oV6pyhZFFmDv5ImDb0vjUI8o56cokQAV +e0m3dcvB566yeJLj6/SnpZjc5/7aHQM8n+SGckHgE4bskTQLXnO+DZb7D0kMCMgE +Xpo81zwG6lAxcwKCAQEAzPHfyyYrCPgGJe9Z5e8qz+MtuJGwDpV7IQa6ZQjPUIn1 +r18aYp0e5GUX8d+m1cjkX+afPfcwPag9cFZ093PpMd1X8EREtP/UohNHFOeuIufZ +kQi5t+QCbyNVzKUEFtPVQlY8F4rU35iXjdP7DLnFBFp/qI0GTqoXf7T44UAlt5t9 +lu5AimhyEzndPTNGr4uVNliAkT3EB/6k7/H5g4C1ISaIsjlw9tyjVtGpzIlAuK5Y +g45FnKBE98EogERP1EXXfW9koW/RWsbV2e7yd9KiFqjfl7FWvTYkXkPpMt3XhYGC +gj/Kxz7pOL5uFZGTf6xQLD523nuYySK2GlmlTV1JYwKCAQEAnd5bfFp9/JuI17TJ +c/gZoq1QgwrZn3f0gjmvOYBCsHy4knMFKXIPV/fMascGQSwRGXIdKubDNv/X58V6 +I3rpdcHWRHNH7wgyVSSIP66yoZtO8UmnIWLHQH48cHXH3+VSWNy1TJtPln+qwu7o +KSFkooI9bVKSuczLBYBQL6VC2is/s305a4Nkmq+0ykMTlMs/r2iLYS6KqxBWx59e +dkm0CzpK2iRGtaZzZWLGkVTyzf99sDyUhmLyQ84iS6jsZf37F/7QEdZ6XXdtNBuQ +zlNvkS97yGisIOEPLczKNhAtXodPsxr15zvsvlKT3TOShHGemlG7B5fup7kGZ9je +mANLCwKCAQBrRvzPOPZz5mhJeSWEj5HIafFn2JLgv2xJWF0/P8JNNTnuh4inL/mY +sjY7fMe2xkaM0IpwhDCzRZqLcCYf1YOuf19lvEZ+vB+Qiyi9D5z+VoBzQ677TBXe +GQ/zZDlkvZO2+q7P3/MX66cBLYIPIjq30atbDh7MFIriZVeViN91YaX22FXeXVfB +KpvdSjbo8tazMNZzsHIpgSjTFMV4CWpGpQYOzLSN8+ZoLq2EDCBdy503l8RmfRo8 +2dYadieXRMvMs/9n5cEflE8imco/4HjKBbGslL6DopCc601pO+hx0N4A0eWSMCMi +ecPj9GTSAt+gmT3drbLHLi1GU8CMY5VjAoIBAQCbKEHibCNoY421jvMwYiwYAEqq +/xxGJQyuO+5Nc9XwxU+jMW7O9nYSSuuuXxhRNG7LXdn31icrzuMM61STXgef8Cuf +QIYLZrTwVQfmdUt8YJiMprjwCors5hax7XnQQaVWon0jhX2wA6yG+UNNUhi9+i8i +7xog8FnJszGL98gHVnohkk7nq4VLuzm36I74aLfEU58jWvCv3b03bCGJaXXtMKan +aj3IE28qDvtJCxPsF9UltbnpUrJOP4ghZQtq0vlmZkLHySAV7o4D8Ffw4qdBQFOy +3SUCcH7wZAyGUFt+r/B9GkPeKrWgJ/Q8jQ7KoAtlWBAhAJT0zfrtwxAqeqTn +-----END RSA PRIVATE KEY----- diff --git a/test/ocspresponder_test.go b/test/ocspresponder_test.go new file mode 100644 index 00000000..a9692a71 --- /dev/null +++ b/test/ocspresponder_test.go @@ -0,0 +1,245 @@ +package test + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/crypto/ocsp" +) + +func TestOCSPResponder(t *testing.T) { + const ( + caCert = "../test/configs/certs/ocsp/ca-cert.pem" + caKey = "../test/configs/certs/ocsp/ca-key.pem" + userCert = "../test/configs/certs/ocsp/server.crt" + ) + + s := newOCSPResponder(t, caCert, caKey) + defer s.Close() + + // setOCSPDatabase(t, s.URL, userCert, ocsp.Good) // FAIL + setOCSPDatabase(t, s.Addr, userCert, ocsp.Revoked) // PASS + + resp := postOCSPRequest(t, s.Addr, userCert, caCert) + if got, want := resp.Status, ocsp.Revoked; got != want { + t.Fatalf("unexpected cert status, got %d, want %d", got, want) + } +} + +func postOCSPRequest(t *testing.T, ocspURL, certPEM, issuerPEM string) *ocsp.Response { + t.Helper() + + cert := parseCertPEM(t, certPEM) + issuer := parseCertPEM(t, issuerPEM) + + reqData, err := ocsp.CreateRequest(cert, issuer, nil) + if err != nil { + t.Fatalf("failed to create OCSP request: %s", err) + } + + r := bytes.NewReader(reqData) + hc := &http.Client{Timeout: 3 * time.Second} + + httpResp, err := hc.Post(ocspURL, "application/ocsp-request", r) + if err != nil { + t.Fatalf("failed POST request: %s", err) + } + defer httpResp.Body.Close() + + respData, err := io.ReadAll(httpResp.Body) + if err != nil { + t.Fatalf("failed to read OCSP HTTP response body: %s", err) + } + + if got, want := httpResp.Status, "200 OK"; got != want { + t.Error(strings.TrimSpace(string(respData))) + t.Fatalf("unexpected OCSP HTTP status, got %q, want %q", got, want) + } + + resp, err := ocsp.ParseResponse(respData, issuer) + if err != nil { + t.Fatalf("failed to parse OCSP response: %s", err) + } + + return resp +} + +func setOCSPDatabase(t *testing.T, ocspURL, certPEM string, status int) { + t.Helper() + + cert := parseCertPEM(t, certPEM) + + hc := &http.Client{Timeout: 3 * 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 := io.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 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 := io.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) { + defer r.Body.Close() + + if r.Method != "POST" { + http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + if r.Header.Get("Content-Type") != "application/ocsp-request" { + http.Error(rw, "Unsupported Media Type", http.StatusUnsupportedMediaType) + return + } + + reqData, err := io.ReadAll(r.Body) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + 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, + } + 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() + return srv +} + +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 := os.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 +} diff --git a/test/tls_test.go b/test/tls_test.go index 87c77dbf..81f3714b 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -15,6 +15,7 @@ package test import ( "bufio" + "context" "crypto/tls" "crypto/x509" "errors" @@ -29,6 +30,7 @@ import ( "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" + "golang.org/x/crypto/ocsp" ) var noOpErrHandler = func(_ *nats.Conn, _ *nats.Subscription, _ error) {} @@ -1935,3 +1937,106 @@ func TestTLSClientSVIDAuth(t *testing.T) { }) } } + +func TestTLSClientOCSP(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Start mock HTTP responder service. + s := newOCSPResponder(t, "configs/certs/ocsp/ca-cert.pem", "configs/certs/ocsp/ca-key.pem") + defer s.Shutdown(ctx) + + // Give it some time for the HTTP server to start. + time.Sleep(2 * time.Second) + + setCertStatus := func(t *testing.T, cert string, ocspStatus int) { + setOCSPDatabase(t, + fmt.Sprintf("http://%s", s.Addr), + cert, + ocspStatus, + ) + } + + for _, test := range []struct { + name string + config string + certs nats.Option + err error + rerr error + configure func() + }{ + { + "connect with tls and get a staple from valid certificate", + ` + port: -1 + %s + `, + nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"), + nil, + nil, + func() { setCertStatus(t, "configs/certs/ocsp/server-cert.pem", ocsp.Good) }, + }, + // FIXME: Test that the server will not start with an invalid cert. + // { + // "connect with tls and get a staple from invalid certificate", + // ` + // port: -1 + // %s + // `, + // nats.ClientCert("./configs/certs/ocsp/client-cert.pem", "./configs/certs/ocsp/client-key.pem"), + // nil, + // nil, + // func() { setCertStatus(t, "configs/certs/ocsp/server-cert.pem", ocsp.Revoked) }, + // }, + } { + t.Run(test.name, func(t *testing.T) { + test.configure() + + content := fmt.Sprintf(test.config, ` + 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 + + # Makes the server honor must-staple flag if present in server cert. + ocsp: true + } + `) + 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.certs, + nats.RootCAs("configs/certs/ocsp/ca-cert.pem"), + nats.ErrorHandler(noOpErrHandler), + ) + 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") + } + }) + } +} 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 00000000..9d3fffa8 --- /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 f4dbbff6..3a1cfa21 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-20210421170649-83a5a9bb288b ## 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