diff options
Diffstat (limited to 'crypto/tls/ocsp.go')
-rw-r--r-- | crypto/tls/ocsp.go | 94 |
1 files changed, 94 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 @@ | |||
1 | package tls | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "crypto/tls" | ||
6 | "crypto/x509" | ||
7 | "fmt" | ||
8 | "io" | ||
9 | "net/http" | ||
10 | |||
11 | "golang.org/x/crypto/ocsp" | ||
12 | ) | ||
13 | |||
14 | func 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 | } | ||