1 package tls
2
3 import (
4 "crypto/ecdsa"
5 "crypto/elliptic"
6 "crypto/rand"
7 "crypto/x509"
8 "crypto/x509/pkix"
9 "fmt"
10 "math/big"
11 "time"
12 )
13
14 type (
15
16
17 CA struct {
18
19 Cred Cred
20
21
22
23
24
25
26
27 Validity Validity
28
29
30
31
32
33
34
35
36
37 nextSerialNumber uint64
38
39
40
41 firstCrtExpiration time.Time
42 }
43
44
45 Validity struct {
46
47
48
49
50
51
52
53 Lifetime time.Duration
54
55
56
57
58 ClockSkewAllowance time.Duration
59
60
61
62 ValidFrom *time.Time
63 }
64
65
66 Issuer interface {
67 IssueEndEntityCrt(*x509.CertificateRequest) (Crt, error)
68 }
69 )
70
71 const (
72
73
74
75
76
77 DefaultLifetime = (24 * 365) * time.Hour
78
79
80
81
82
83
84
85
86
87 DefaultClockSkewAllowance = 10 * time.Second
88 )
89
90
91
92 func findFirstExpiration(cred *Cred) time.Time {
93 firstExpiration := cred.Certificate.NotAfter
94 for _, c := range cred.TrustChain {
95 if c.NotAfter.Before(firstExpiration) {
96 firstExpiration = c.NotAfter
97 }
98 }
99 return firstExpiration
100 }
101
102
103 func NewCA(cred Cred, validity Validity) *CA {
104 return &CA{cred, validity, uint64(1), findFirstExpiration(&cred)}
105 }
106
107 func init() {
108
109 var _ Issuer = &CA{}
110 }
111
112
113 func CreateRootCA(
114 name string,
115 key *ecdsa.PrivateKey,
116 validity Validity,
117 ) (*CA, error) {
118
119 t := createTemplate(1, &key.PublicKey, validity)
120 t.Subject = pkix.Name{CommonName: name}
121 t.IsCA = true
122 t.MaxPathLen = -1
123 t.BasicConstraintsValid = true
124 t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
125
126
127 crtb, err := x509.CreateCertificate(rand.Reader, t, t, key.Public(), key)
128 if err != nil {
129 return nil, err
130 }
131 c, err := x509.ParseCertificate(crtb)
132 if err != nil {
133 return nil, err
134 }
135
136
137 cred := validCredOrPanic(key, Crt{Certificate: c})
138 ca := NewCA(cred, validity)
139 ca.nextSerialNumber++
140 return ca, nil
141 }
142
143
144
145 func GenerateKey() (*ecdsa.PrivateKey, error) {
146 return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
147 }
148
149
150 func GenerateRootCAWithDefaults(name string) (*CA, error) {
151
152 key, err := GenerateKey()
153 if err != nil {
154 return nil, err
155 }
156
157 return CreateRootCA(name, key, Validity{})
158 }
159
160
161 func (ca *CA) GenerateCA(name string, maxPathLen int) (*CA, error) {
162 key, err := GenerateKey()
163 if err != nil {
164 return nil, err
165 }
166
167 t := ca.createTemplate(&key.PublicKey)
168 t.Subject = pkix.Name{CommonName: name}
169 t.IsCA = true
170 t.MaxPathLen = maxPathLen
171 t.MaxPathLenZero = true
172 t.BasicConstraintsValid = true
173 t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
174 crt, err := ca.Cred.SignCrt(t)
175 if err != nil {
176 return nil, err
177 }
178
179 return NewCA(validCredOrPanic(key, crt), ca.Validity), nil
180 }
181
182
183
184 func (ca *CA) GenerateEndEntityCred(dnsName string) (*Cred, error) {
185 key, err := GenerateKey()
186 if err != nil {
187 return nil, err
188 }
189
190 csr := x509.CertificateRequest{
191 Subject: pkix.Name{CommonName: dnsName},
192 DNSNames: []string{dnsName},
193 PublicKey: &key.PublicKey,
194 }
195 crt, err := ca.IssueEndEntityCrt(&csr)
196 if err != nil {
197 return nil, err
198 }
199
200 c := validCredOrPanic(key, crt)
201 return &c, nil
202 }
203
204
205
206 func (ca *CA) IssueEndEntityCrt(csr *x509.CertificateRequest) (Crt, error) {
207 pubkey, ok := csr.PublicKey.(*ecdsa.PublicKey)
208 if !ok {
209 return Crt{}, fmt.Errorf("CSR must contain an ECDSA public key: %+v", csr.PublicKey)
210 }
211
212 t := ca.createTemplate(pubkey)
213 t.Issuer = ca.Cred.Crt.Certificate.Subject
214 t.Subject = csr.Subject
215 t.Extensions = csr.Extensions
216 t.ExtraExtensions = csr.ExtraExtensions
217 t.DNSNames = csr.DNSNames
218 t.EmailAddresses = csr.EmailAddresses
219 t.IPAddresses = csr.IPAddresses
220 t.URIs = csr.URIs
221
222 return ca.Cred.SignCrt(t)
223 }
224
225
226
227
228 func (ca *CA) createTemplate(pubkey *ecdsa.PublicKey) *x509.Certificate {
229 c := createTemplate(ca.nextSerialNumber, pubkey, ca.Validity)
230 ca.nextSerialNumber++
231
232
233
234
235
236 if ca.firstCrtExpiration.Before(c.NotAfter) {
237 c.NotAfter = ca.firstCrtExpiration
238 }
239 return c
240 }
241
242
243
244
245 func createTemplate(
246 serialNumber uint64,
247 k *ecdsa.PublicKey,
248 v Validity,
249 ) *x509.Certificate {
250
251
252
253
254
255
256
257
258
259
260
261 const SignatureAlgorithm = x509.ECDSAWithSHA256
262
263 if v.ValidFrom == nil {
264 now := time.Now()
265 v.ValidFrom = &now
266 }
267 notBefore, notAfter := v.Window(*v.ValidFrom)
268
269 return &x509.Certificate{
270 SerialNumber: big.NewInt(int64(serialNumber)),
271 SignatureAlgorithm: SignatureAlgorithm,
272 NotBefore: notBefore,
273 NotAfter: notAfter,
274 PublicKey: k,
275 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
276 ExtKeyUsage: []x509.ExtKeyUsage{
277 x509.ExtKeyUsageServerAuth,
278 x509.ExtKeyUsageClientAuth,
279 },
280 }
281 }
282
283
284 func (v *Validity) Window(t time.Time) (time.Time, time.Time) {
285 life := v.Lifetime
286 if life == 0 {
287 life = DefaultLifetime
288 }
289 skew := v.ClockSkewAllowance
290 if skew == 0 {
291 skew = DefaultClockSkewAllowance
292 }
293 start := t.Add(-skew)
294 end := t.Add(life).Add(skew)
295 return start, end
296 }
297
View as plain text