1 package core
2
3 import (
4 "crypto"
5 "encoding/base64"
6 "encoding/json"
7 "fmt"
8 "hash/fnv"
9 "net"
10 "strings"
11 "time"
12
13 "golang.org/x/crypto/ocsp"
14 "gopkg.in/go-jose/go-jose.v2"
15
16 "github.com/letsencrypt/boulder/identifier"
17 "github.com/letsencrypt/boulder/probs"
18 "github.com/letsencrypt/boulder/revocation"
19 )
20
21
22 type AcmeStatus string
23
24
25 const (
26 StatusUnknown = AcmeStatus("unknown")
27 StatusPending = AcmeStatus("pending")
28 StatusProcessing = AcmeStatus("processing")
29 StatusReady = AcmeStatus("ready")
30 StatusValid = AcmeStatus("valid")
31 StatusInvalid = AcmeStatus("invalid")
32 StatusRevoked = AcmeStatus("revoked")
33 StatusDeactivated = AcmeStatus("deactivated")
34 )
35
36
37 type AcmeResource string
38
39
40 const (
41 ResourceNewReg = AcmeResource("new-reg")
42 ResourceNewAuthz = AcmeResource("new-authz")
43 ResourceNewCert = AcmeResource("new-cert")
44 ResourceRevokeCert = AcmeResource("revoke-cert")
45 ResourceRegistration = AcmeResource("reg")
46 ResourceChallenge = AcmeResource("challenge")
47 ResourceAuthz = AcmeResource("authz")
48 ResourceKeyChange = AcmeResource("key-change")
49 )
50
51
52 type AcmeChallenge string
53
54
55 const (
56 ChallengeTypeHTTP01 = AcmeChallenge("http-01")
57 ChallengeTypeDNS01 = AcmeChallenge("dns-01")
58 ChallengeTypeTLSALPN01 = AcmeChallenge("tls-alpn-01")
59 )
60
61
62 func (c AcmeChallenge) IsValid() bool {
63 switch c {
64 case ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01:
65 return true
66 default:
67 return false
68 }
69 }
70
71
72 type OCSPStatus string
73
74
75 const (
76 OCSPStatusGood = OCSPStatus("good")
77 OCSPStatusRevoked = OCSPStatus("revoked")
78
79
80
81 OCSPStatusNotReady = OCSPStatus("wait")
82 )
83
84 var OCSPStatusToInt = map[OCSPStatus]int{
85 OCSPStatusGood: ocsp.Good,
86 OCSPStatusRevoked: ocsp.Revoked,
87 OCSPStatusNotReady: -1,
88 }
89
90
91 const DNSPrefix = "_acme-challenge"
92
93 type RawCertificateRequest struct {
94 CSR JSONBuffer `json:"csr"`
95 }
96
97
98
99 type Registration struct {
100
101 ID int64 `json:"id,omitempty" db:"id"`
102
103
104 Key *jose.JSONWebKey `json:"key"`
105
106
107 Contact *[]string `json:"contact,omitempty"`
108
109
110 Agreement string `json:"agreement,omitempty"`
111
112
113 InitialIP net.IP `json:"initialIp"`
114
115
116 CreatedAt *time.Time `json:"createdAt,omitempty"`
117
118 Status AcmeStatus `json:"status"`
119 }
120
121
122
123 type ValidationRecord struct {
124
125 URL string `json:"url,omitempty"`
126
127
128 Hostname string `json:"hostname,omitempty"`
129 Port string `json:"port,omitempty"`
130 AddressesResolved []net.IP `json:"addressesResolved,omitempty"`
131 AddressUsed net.IP `json:"addressUsed,omitempty"`
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 AddressesTried []net.IP `json:"addressesTried,omitempty"`
147 }
148
149 func looksLikeKeyAuthorization(str string) error {
150 parts := strings.Split(str, ".")
151 if len(parts) != 2 {
152 return fmt.Errorf("Invalid key authorization: does not look like a key authorization")
153 } else if !LooksLikeAToken(parts[0]) {
154 return fmt.Errorf("Invalid key authorization: malformed token")
155 } else if !LooksLikeAToken(parts[1]) {
156
157
158 return fmt.Errorf("Invalid key authorization: malformed key thumbprint")
159 }
160 return nil
161 }
162
163
164
165
166
167
168 type Challenge struct {
169
170 Type AcmeChallenge `json:"type"`
171
172
173 Status AcmeStatus `json:"status,omitempty"`
174
175
176 Error *probs.ProblemDetails `json:"error,omitempty"`
177
178
179 URI string `json:"uri,omitempty"`
180
181
182 URL string `json:"url,omitempty"`
183
184
185 Token string `json:"token,omitempty"`
186
187
188
189
190
191
192
193 ProvidedKeyAuthorization string `json:"keyAuthorization,omitempty"`
194
195
196
197 ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"`
198
199
200 Validated *time.Time `json:"validated,omitempty"`
201 }
202
203
204
205 func (ch Challenge) ExpectedKeyAuthorization(key *jose.JSONWebKey) (string, error) {
206 if key == nil {
207 return "", fmt.Errorf("Cannot authorize a nil key")
208 }
209
210 thumbprint, err := key.Thumbprint(crypto.SHA256)
211 if err != nil {
212 return "", err
213 }
214
215 return ch.Token + "." + base64.RawURLEncoding.EncodeToString(thumbprint), nil
216 }
217
218
219
220 func (ch Challenge) RecordsSane() bool {
221 if ch.ValidationRecord == nil || len(ch.ValidationRecord) == 0 {
222 return false
223 }
224
225 switch ch.Type {
226 case ChallengeTypeHTTP01:
227 for _, rec := range ch.ValidationRecord {
228 if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil ||
229 len(rec.AddressesResolved) == 0 {
230 return false
231 }
232 }
233 case ChallengeTypeTLSALPN01:
234 if len(ch.ValidationRecord) > 1 {
235 return false
236 }
237 if ch.ValidationRecord[0].URL != "" {
238 return false
239 }
240 if ch.ValidationRecord[0].Hostname == "" || ch.ValidationRecord[0].Port == "" ||
241 ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
242 return false
243 }
244 case ChallengeTypeDNS01:
245 if len(ch.ValidationRecord) > 1 {
246 return false
247 }
248 if ch.ValidationRecord[0].Hostname == "" {
249 return false
250 }
251 return true
252 default:
253 return false
254 }
255
256 return true
257 }
258
259
260
261 func (ch Challenge) CheckConsistencyForClientOffer() error {
262 err := ch.checkConsistency()
263 if err != nil {
264 return err
265 }
266
267
268 if ch.ProvidedKeyAuthorization != "" {
269 return fmt.Errorf("A response to this challenge was already submitted.")
270 }
271 return nil
272 }
273
274
275
276 func (ch Challenge) CheckConsistencyForValidation() error {
277 err := ch.checkConsistency()
278 if err != nil {
279 return err
280 }
281
282
283 return looksLikeKeyAuthorization(ch.ProvidedKeyAuthorization)
284 }
285
286
287 func (ch Challenge) checkConsistency() error {
288 if ch.Status != StatusPending {
289 return fmt.Errorf("The challenge is not pending.")
290 }
291
292
293 if !LooksLikeAToken(ch.Token) {
294 return fmt.Errorf("The token is missing.")
295 }
296 return nil
297 }
298
299
300
301
302
303 func (ch Challenge) StringID() string {
304 h := fnv.New128a()
305 h.Write([]byte(ch.Token))
306 h.Write([]byte(ch.Type))
307 return base64.RawURLEncoding.EncodeToString(h.Sum(nil)[0:4])
308 }
309
310
311
312
313
314 type Authorization struct {
315
316
317 ID string `json:"id,omitempty" db:"id"`
318
319
320 Identifier identifier.ACMEIdentifier `json:"identifier,omitempty" db:"identifier"`
321
322
323 RegistrationID int64 `json:"regId,omitempty" db:"registrationID"`
324
325
326 Status AcmeStatus `json:"status,omitempty" db:"status"`
327
328
329
330
331
332
333 Expires *time.Time `json:"expires,omitempty" db:"expires"`
334
335
336
337
338
339
340
341
342
343 Challenges []Challenge `json:"challenges,omitempty" db:"-"`
344
345
346
347
348
349
350
351
352
353
354
355
356
357 Wildcard bool `json:"wildcard,omitempty" db:"-"`
358 }
359
360
361
362
363 func (authz *Authorization) FindChallengeByStringID(id string) int {
364 for i, c := range authz.Challenges {
365 if c.StringID() == id {
366 return i
367 }
368 }
369 return -1
370 }
371
372
373
374
375 func (authz *Authorization) SolvedBy() (AcmeChallenge, error) {
376 if len(authz.Challenges) == 0 {
377 return "", fmt.Errorf("Authorization has no challenges")
378 }
379 for _, chal := range authz.Challenges {
380 if chal.Status == StatusValid {
381 return chal.Type, nil
382 }
383 }
384 return "", fmt.Errorf("Authorization not solved by any challenge")
385 }
386
387
388
389 type JSONBuffer []byte
390
391
392 func (jb JSONBuffer) MarshalJSON() (result []byte, err error) {
393 return json.Marshal(base64.RawURLEncoding.EncodeToString(jb))
394 }
395
396
397 func (jb *JSONBuffer) UnmarshalJSON(data []byte) (err error) {
398 var str string
399 err = json.Unmarshal(data, &str)
400 if err != nil {
401 return err
402 }
403 *jb, err = base64.RawURLEncoding.DecodeString(strings.TrimRight(str, "="))
404 return
405 }
406
407
408
409 type Certificate struct {
410 ID int64 `db:"id"`
411 RegistrationID int64 `db:"registrationID"`
412
413 Serial string `db:"serial"`
414 Digest string `db:"digest"`
415 DER []byte `db:"der"`
416 Issued time.Time `db:"issued"`
417 Expires time.Time `db:"expires"`
418 }
419
420
421
422
423 type CertificateStatus struct {
424 ID int64 `db:"id"`
425
426 Serial string `db:"serial"`
427
428
429
430 Status OCSPStatus `db:"status"`
431
432
433
434
435 OCSPLastUpdated time.Time `db:"ocspLastUpdated"`
436
437
438
439 RevokedDate time.Time `db:"revokedDate"`
440
441
442
443
444 RevokedReason revocation.Reason `db:"revokedReason"`
445
446 LastExpirationNagSent time.Time `db:"lastExpirationNagSent"`
447
448
449
450
451
452 NotAfter time.Time `db:"notAfter"`
453 IsExpired bool `db:"isExpired"`
454
455
456
457
458
459
460 IssuerNameID int64 `db:"issuerID"`
461 }
462
463
464
465 type FQDNSet struct {
466 ID int64
467 SetHash []byte
468 Serial string
469 Issued time.Time
470 Expires time.Time
471 }
472
473
474 type SCTDERs [][]byte
475
476
477
478 type CertDER []byte
479
480
481 type SuggestedWindow struct {
482 Start time.Time `json:"start"`
483 End time.Time `json:"end"`
484 }
485
486
487
488 type RenewalInfo struct {
489 SuggestedWindow SuggestedWindow `json:"suggestedWindow"`
490 }
491
492
493
494
495
496 func RenewalInfoSimple(issued time.Time, expires time.Time) RenewalInfo {
497 validity := expires.Add(time.Second).Sub(issued)
498 renewalOffset := validity / time.Duration(3)
499 idealRenewal := expires.Add(-renewalOffset)
500 return RenewalInfo{
501 SuggestedWindow: SuggestedWindow{
502 Start: idealRenewal.Add(-24 * time.Hour),
503 End: idealRenewal.Add(24 * time.Hour),
504 },
505 }
506 }
507
508
509
510
511
512
513 func RenewalInfoImmediate(now time.Time) RenewalInfo {
514 oneHourAgo := now.Add(-1 * time.Hour)
515 return RenewalInfo{
516 SuggestedWindow: SuggestedWindow{
517 Start: oneHourAgo,
518 End: oneHourAgo.Add(time.Minute * 30),
519 },
520 }
521 }
522
View as plain text