aboutsummaryrefslogtreecommitdiff
path: root/crypto
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2021-11-10 22:16:10 -0800
committerMike Crute <mike@crute.us>2021-11-10 22:16:10 -0800
commit8c5b1d33cc3d4af0b7c0e0fc37a90ef80ba25fe5 (patch)
treed49f37a47aa3b853052a1f7a5a0e13f3e4a5d9e7 /crypto
parentef4444e6217592e2666b8a46e8c762eaf93fa8a6 (diff)
downloadgolib-8c5b1d33cc3d4af0b7c0e0fc37a90ef80ba25fe5.tar.bz2
golib-8c5b1d33cc3d4af0b7c0e0fc37a90ef80ba25fe5.tar.xz
golib-8c5b1d33cc3d4af0b7c0e0fc37a90ef80ba25fe5.zip
Nest tls package in crypto, split ocsp
Diffstat (limited to 'crypto')
-rw-r--r--crypto/tls/ocsp.go94
-rw-r--r--crypto/tls/ocsp_manager.go134
2 files changed, 228 insertions, 0 deletions
diff --git a/crypto/tls/ocsp.go b/crypto/tls/ocsp.go
new file mode 100644
index 0000000..9ae9828
--- /dev/null
+++ b/crypto/tls/ocsp.go
@@ -0,0 +1,94 @@
1package tls
2
3import (
4 "bytes"
5 "crypto/tls"
6 "crypto/x509"
7 "fmt"
8 "io"
9 "net/http"
10
11 "golang.org/x/crypto/ocsp"
12)
13
14func GetOcspResponse(chain *tls.Certificate) ([]byte, *ocsp.Response, error) {
15 var certs []*x509.Certificate
16 for _, c := range chain.Certificate {
17 cert, err := x509.ParseCertificate(c)
18 if err != nil {
19 return nil, nil, err
20 }
21 certs = append(certs, cert)
22 }
23 if len(certs) == 0 {
24 return nil, nil, fmt.Errorf("no certificates found in bundle")
25 }
26
27 // We expect the certificate slice to be ordered downwards the chain.
28 // SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
29 // which should always be the first two certificates. If there's no
30 // OCSP server listed in the leaf cert, there's nothing to do. And if
31 // we have only one certificate so far, we need to get the issuer cert.
32 leaf := certs[0]
33 if len(leaf.OCSPServer) == 0 {
34 return nil, nil, fmt.Errorf("no OCSP server specified in certificate")
35 }
36
37 if len(certs) == 1 {
38 if len(leaf.IssuingCertificateURL) == 0 {
39 return nil, nil, fmt.Errorf("no URL to issuing certificate")
40 }
41
42 resp, err := http.Get(leaf.IssuingCertificateURL[0])
43 if err != nil {
44 return nil, nil, fmt.Errorf("getting issuer certificate: %w", err)
45 }
46 defer resp.Body.Close()
47
48 issuerBytes, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
49 if err != nil {
50 return nil, nil, fmt.Errorf("reading issuer certificate: %w", err)
51 }
52
53 issuer, err := x509.ParseCertificate(issuerBytes)
54 if err != nil {
55 return nil, nil, fmt.Errorf("parsing issuer certificate: %w", err)
56 }
57
58 certs = append(certs, issuer)
59 }
60
61 issuer := certs[1]
62
63 req, err := ocsp.CreateRequest(leaf, issuer, nil)
64 if err != nil {
65 return nil, nil, fmt.Errorf("creating OCSP request: %w", err)
66 }
67
68 httpRes, err := http.Post(leaf.OCSPServer[0], "application/ocsp-request", bytes.NewReader(req))
69 if err != nil {
70 return nil, nil, fmt.Errorf("making OCSP request: %w", err)
71 }
72 defer httpRes.Body.Close()
73
74 rawRes, err := io.ReadAll(io.LimitReader(httpRes.Body, 1024*1024))
75 if err != nil {
76 return nil, nil, fmt.Errorf("reading OCSP response: %w", err)
77 }
78
79 res, err := ocsp.ParseResponse(rawRes, issuer)
80 if err != nil {
81 return nil, nil, fmt.Errorf("parsing OCSP response: %w", err)
82 }
83
84 if res.Status != ocsp.Good {
85 return nil, nil, fmt.Errorf("invalid: OCSP response was not of Good status")
86 }
87
88 // This is invalid, the response expires after the certificate
89 if res.NextUpdate.After(leaf.NotAfter) {
90 return nil, nil, fmt.Errorf("invalid: OCSP response valid after certificate expiration")
91 }
92
93 return rawRes, res, nil
94}
diff --git a/crypto/tls/ocsp_manager.go b/crypto/tls/ocsp_manager.go
new file mode 100644
index 0000000..dac4c5e
--- /dev/null
+++ b/crypto/tls/ocsp_manager.go
@@ -0,0 +1,134 @@
1package tls
2
3import (
4 "context"
5 "crypto/tls"
6 "errors"
7 "fmt"
8 "sync"
9 "time"
10
11 "golang.org/x/crypto/ocsp"
12)
13
14type OcspError struct {
15 Err error
16 AtBoot bool
17}
18
19func (e OcspError) Error() string {
20 return e.Err.Error()
21}
22
23func (e OcspError) Unwrap() error {
24 return e.Err
25}
26
27type OcspLogger interface {
28 Info(...interface{})
29 Errorf(string, ...interface{})
30}
31
32func OcspErrorLogger(l OcspLogger, c <-chan OcspError) func(context.Context, *sync.WaitGroup) error {
33 return func(ctx context.Context, wg *sync.WaitGroup) error {
34 wg.Add(1)
35 defer wg.Done()
36
37 for {
38 select {
39 case err := <-c:
40 l.Errorf("Error in OCSP stapling: %w", errors.Unwrap(err))
41 case <-ctx.Done():
42 l.Info("Shutting down OCSP logger")
43 return nil
44 }
45 }
46 }
47}
48
49type OcspManager struct {
50 CertPath, KeyPath string
51 Errors chan<- OcspError
52 cert *tls.Certificate
53 ocspRes *ocsp.Response
54 sync.RWMutex
55}
56
57func (m *OcspManager) loadCert() error {
58 cert, err := tls.LoadX509KeyPair(m.CertPath, m.KeyPath)
59 if err != nil {
60 return err
61 }
62 m.Lock()
63 m.cert = &cert
64 m.Unlock()
65
66 return nil
67}
68
69func (m *OcspManager) stapleCert() error {
70 // This makes a network request to an unknown server so don't hold the full
71 // lock while this is happening
72 m.RLock()
73 raw, ocspRes, err := GetOcspResponse(m.cert)
74 if err != nil {
75 return err
76 }
77 m.RUnlock()
78
79 m.Lock()
80 m.cert.OCSPStaple = raw
81 m.ocspRes = ocspRes
82 m.Unlock()
83
84 return nil
85}
86
87func (m *OcspManager) Init() error {
88 // All functions called here will handle locking themselves
89 if err := m.loadCert(); err != nil {
90 return err
91 }
92
93 if err := m.stapleCert(); err != nil {
94 return err
95 }
96
97 return nil
98}
99
100func (m *OcspManager) Run(ctx context.Context, wg *sync.WaitGroup) error {
101 wg.Add(1)
102 defer wg.Done()
103
104 t := time.NewTimer(m.ocspRes.NextUpdate.Sub(time.Now()) - time.Hour)
105
106 for {
107 select {
108 case <-t.C:
109 if err := m.stapleCert(); err != nil {
110 if m.Errors != nil {
111 m.Errors <- OcspError{err, false}
112 }
113 t.Reset(time.Hour)
114 continue
115 }
116 // We own this object and only we write it, no need to lock
117 t.Reset(m.ocspRes.NextUpdate.Sub(time.Now()) - time.Hour)
118 case <-ctx.Done():
119 return nil
120 }
121 }
122}
123
124// TODO: TLS.GetCertificate for dyanmic certs for LE (cache these)
125func (m *OcspManager) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
126 m.RLock()
127 defer m.RUnlock()
128
129 if m.cert != nil {
130 return m.cert, nil
131 }
132
133 return nil, fmt.Errorf("OCSP manager has no certificate stored")
134}