/
ocspstapling.go
118 lines (102 loc) · 3.55 KB
/
ocspstapling.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Package ocspstapling implements OCSP stapling of Signed Certificate
// Timestamps (SCTs) into OCSP responses in a database. See RFC 6962.
package ocspstapling
import (
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"errors"
"github.com/cloudflare/cfssl/certdb"
cferr "github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
ct "github.com/google/certificate-transparency-go"
"golang.org/x/crypto/ocsp"
)
// sctExtOid is the OID of the OCSP Stapling SCT extension (see section 3.3. of RFC 6962).
var sctExtOid = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5}
// StapleSCTList inserts a list of Signed Certificate Timestamps into all OCSP
// responses in a database wrapped by a given certdb.Accessor.
//
// NOTE: This function is patterned after the exported Sign method in
// https://github.com/cloudflare/cfssl/blob/master/signer/local/local.go
func StapleSCTList(acc certdb.Accessor, serial, aki string, scts []ct.SignedCertificateTimestamp,
responderCert, issuer *x509.Certificate, priv crypto.Signer) error {
ocspRecs, err := acc.GetOCSP(serial, aki)
if err != nil {
return err
}
if len(ocspRecs) == 0 {
return cferr.Wrap(cferr.CertStoreError, cferr.RecordNotFound, errors.New("empty OCSPRecord"))
}
// This loop adds the SCTs to each OCSP response in ocspRecs.
for _, rec := range ocspRecs {
der, err := base64.StdEncoding.DecodeString(rec.Body)
if err != nil {
return cferr.Wrap(cferr.CertificateError, cferr.DecodeFailed,
errors.New("failed to decode Base64-encoded OCSP response"))
}
response, err := ocsp.ParseResponse(der, nil)
if err != nil {
return cferr.Wrap(cferr.CertificateError, cferr.ParseFailed,
errors.New("failed to parse DER-encoded OCSP response"))
}
serializedSCTList, err := helpers.SerializeSCTList(scts)
if err != nil {
return cferr.Wrap(cferr.CTError, cferr.Unknown,
errors.New("failed to serialize SCT list"))
}
serializedSCTList, err = asn1.Marshal(serializedSCTList)
if err != nil {
return cferr.Wrap(cferr.CTError, cferr.Unknown,
errors.New("failed to serialize SCT list"))
}
sctExtension := pkix.Extension{
Id: sctExtOid,
Critical: false,
Value: serializedSCTList,
}
// This loop finds the SCTListExtension in the ocsp response.
var idxExt int
for _, ext := range response.Extensions {
if ext.Id.Equal(sctExtOid) {
break
}
idxExt++
}
newExtensions := make([]pkix.Extension, len(response.Extensions))
copy(newExtensions, response.Extensions)
if idxExt >= len(response.Extensions) {
// No SCT extension was found.
newExtensions = append(newExtensions, sctExtension)
} else {
newExtensions[idxExt] = sctExtension
}
// Here we write the updated extensions to replace the old
// response extensions when re-marshalling.
newSN := *response.SerialNumber
template := ocsp.Response{
Status: response.Status,
SerialNumber: &newSN,
ThisUpdate: response.ThisUpdate,
NextUpdate: response.NextUpdate,
Certificate: response.Certificate,
ExtraExtensions: newExtensions,
IssuerHash: response.IssuerHash,
}
// Finally, we re-sign the response to generate the new
// DER-encoded response.
der, err = ocsp.CreateResponse(issuer, responderCert, template, priv)
if err != nil {
return cferr.Wrap(cferr.CTError, cferr.Unknown,
errors.New("failed to sign new OCSP response"))
}
body := base64.StdEncoding.EncodeToString(der)
err = acc.UpdateOCSP(serial, aki, body, rec.Expiry)
if err != nil {
return err
}
}
return nil
}