5
5
// Package acme provides an implementation of the
6
6
// Automatic Certificate Management Environment (ACME) spec.
7
7
// The intial implementation was based on ACME draft-02 and
8
- // is now being extended to comply with RFC8555 .
8
+ // is now being extended to comply with RFC 8555 .
9
9
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02
10
10
// and https://tools.ietf.org/html/rfc8555 for details.
11
11
//
@@ -60,7 +60,10 @@ var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
60
60
61
61
const (
62
62
maxChainLen = 5 // max depth and breadth of a certificate chain
63
- maxCertSize = 1 << 20 // max size of a certificate, in bytes
63
+ maxCertSize = 1 << 20 // max size of a certificate, in DER bytes
64
+ // Used for decoding certs from application/pem-certificate-chain response,
65
+ // the default when in RFC mode.
66
+ maxCertChainSize = maxCertSize * maxChainLen
64
67
65
68
// Max number of collected nonces kept in memory.
66
69
// Expect usual peak of 1 or 2.
@@ -139,8 +142,7 @@ type Client struct {
139
142
func (c * Client ) accountKID (ctx context.Context ) keyID {
140
143
c .cacheMu .Lock ()
141
144
defer c .cacheMu .Unlock ()
142
- if c .dir .OrderURL == "" {
143
- // Assume legacy CA.
145
+ if ! c .dir .rfcCompliant () {
144
146
return noKeyID
145
147
}
146
148
if c .kid != noKeyID {
@@ -233,6 +235,9 @@ func (c *Client) directoryURL() string {
233
235
}
234
236
235
237
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
238
+ // It is incompatible with RFC 8555. Callers should use CreateOrderCert when interfacing
239
+ // with an RFC-compliant CA.
240
+ //
236
241
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
237
242
// with a different duration.
238
243
// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain.
@@ -284,12 +289,22 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
284
289
// It retries the request until the certificate is successfully retrieved,
285
290
// context is cancelled by the caller or an error response is received.
286
291
//
287
- // The returned value will also contain the CA (issuer) certificate if the bundle argument is true.
292
+ // If the bundle argument is true, the returned value also contains the CA (issuer)
293
+ // certificate chain.
288
294
//
289
295
// FetchCert returns an error if the CA's response or chain was unreasonably large.
290
296
// Callers are encouraged to parse the returned value to ensure the certificate is valid
291
297
// and has expected features.
292
298
func (c * Client ) FetchCert (ctx context.Context , url string , bundle bool ) ([][]byte , error ) {
299
+ dir , err := c .Discover (ctx )
300
+ if err != nil {
301
+ return nil , err
302
+ }
303
+ if dir .rfcCompliant () {
304
+ return c .fetchCertRFC (ctx , url , bundle )
305
+ }
306
+
307
+ // Legacy non-authenticated GET request.
293
308
res , err := c .get (ctx , url , wantStatus (http .StatusOK ))
294
309
if err != nil {
295
310
return nil , err
@@ -304,10 +319,15 @@ func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]by
304
319
// For instance, the key pair of the certificate may be authorized.
305
320
// If the key is nil, c.Key is used instead.
306
321
func (c * Client ) RevokeCert (ctx context.Context , key crypto.Signer , cert []byte , reason CRLReasonCode ) error {
307
- if _ , err := c .Discover (ctx ); err != nil {
322
+ dir , err := c .Discover (ctx )
323
+ if err != nil {
308
324
return err
309
325
}
326
+ if dir .rfcCompliant () {
327
+ return c .revokeCertRFC (ctx , key , cert , reason )
328
+ }
310
329
330
+ // Legacy CA.
311
331
body := & struct {
312
332
Resource string `json:"resource"`
313
333
Cert string `json:"certificate"`
@@ -317,7 +337,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte,
317
337
Cert : base64 .RawURLEncoding .EncodeToString (cert ),
318
338
Reason : int (reason ),
319
339
}
320
- res , err := c .post (ctx , key , c . dir .RevokeURL , body , wantStatus (http .StatusOK ))
340
+ res , err := c .post (ctx , key , dir .RevokeURL , body , wantStatus (http .StatusOK ))
321
341
if err != nil {
322
342
return err
323
343
}
@@ -337,7 +357,7 @@ func AcceptTOS(tosURL string) bool { return true }
337
357
// Register calls prompt with a TOS URL provided by the CA. Prompt should report
338
358
// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS.
339
359
//
340
- // When interfacing with RFC compliant CA, non-RFC8555 compliant fields of acct are ignored
360
+ // When interfacing with an RFC- compliant CA, non-RFC 8555 fields of acct are ignored
341
361
// and prompt is called if Directory's Terms field is non-zero.
342
362
// Also see Error's Instance field for when a CA requires already registered accounts to agree
343
363
// to an updated Terms of Service.
@@ -346,9 +366,7 @@ func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL
346
366
if err != nil {
347
367
return nil , err
348
368
}
349
-
350
- // RFC8555 compliant account registration.
351
- if dir .OrderURL != "" {
369
+ if dir .rfcCompliant () {
352
370
return c .registerRFC (ctx , acct , prompt )
353
371
}
354
372
@@ -370,16 +388,14 @@ func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL
370
388
371
389
// GetReg retrieves an existing account associated with c.Key.
372
390
//
373
- // The url argument is an Account URI used with pre-RFC8555 CAs.
374
- // It is ignored when interfacing with an RFC compliant CA.
391
+ // The url argument is an Account URI used with pre-RFC 8555 CAs.
392
+ // It is ignored when interfacing with an RFC- compliant CA.
375
393
func (c * Client ) GetReg (ctx context.Context , url string ) (* Account , error ) {
376
394
dir , err := c .Discover (ctx )
377
395
if err != nil {
378
396
return nil , err
379
397
}
380
-
381
- // Assume RFC8555 compliant CA.
382
- if dir .OrderURL != "" {
398
+ if dir .rfcCompliant () {
383
399
return c .getRegRFC (ctx )
384
400
}
385
401
@@ -395,16 +411,14 @@ func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
395
411
// UpdateReg updates an existing registration.
396
412
// It returns an updated account copy. The provided account is not modified.
397
413
//
398
- // When interfacing with RFC compliant CAs, a.URI is ignored and the account URL
414
+ // When interfacing with RFC- compliant CAs, a.URI is ignored and the account URL
399
415
// associated with c.Key is used instead.
400
416
func (c * Client ) UpdateReg (ctx context.Context , acct * Account ) (* Account , error ) {
401
417
dir , err := c .Discover (ctx )
402
418
if err != nil {
403
419
return nil , err
404
420
}
405
-
406
- // Assume RFC8555 compliant CA.
407
- if dir .OrderURL != "" {
421
+ if dir .rfcCompliant () {
408
422
return c .updateRegRFC (ctx , acct )
409
423
}
410
424
@@ -418,13 +432,21 @@ func (c *Client) UpdateReg(ctx context.Context, acct *Account) (*Account, error)
418
432
return a , nil
419
433
}
420
434
421
- // Authorize performs the initial step in an authorization flow.
435
+ // Authorize performs the initial step in the pre-authorization flow,
436
+ // as opposed to order-based flow.
422
437
// The caller will then need to choose from and perform a set of returned
423
438
// challenges using c.Accept in order to successfully complete authorization.
424
439
//
440
+ // Once complete, the caller can use AuthorizeOrder which the CA
441
+ // should provision with the already satisfied authorization.
442
+ // For pre-RFC CAs, the caller can proceed directly to requesting a certificate
443
+ // using CreateCert method.
444
+ //
425
445
// If an authorization has been previously granted, the CA may return
426
- // a valid authorization (Authorization.Status is StatusValid). If so, the caller
427
- // need not fulfill any challenge and can proceed to requesting a certificate.
446
+ // a valid authorization which has its Status field set to StatusValid.
447
+ //
448
+ // More about pre-authorization can be found at
449
+ // https://tools.ietf.org/html/rfc8555#section-7.4.1.
428
450
func (c * Client ) Authorize (ctx context.Context , domain string ) (* Authorization , error ) {
429
451
return c .authorize (ctx , "dns" , domain )
430
452
}
@@ -476,7 +498,17 @@ func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization
476
498
// If a caller needs to poll an authorization until its status is final,
477
499
// see the WaitAuthorization method.
478
500
func (c * Client ) GetAuthorization (ctx context.Context , url string ) (* Authorization , error ) {
479
- res , err := c .get (ctx , url , wantStatus (http .StatusOK , http .StatusAccepted ))
501
+ dir , err := c .Discover (ctx )
502
+ if err != nil {
503
+ return nil , err
504
+ }
505
+
506
+ var res * http.Response
507
+ if dir .rfcCompliant () {
508
+ res , err = c .postAsGet (ctx , url , wantStatus (http .StatusOK ))
509
+ } else {
510
+ res , err = c .get (ctx , url , wantStatus (http .StatusOK , http .StatusAccepted ))
511
+ }
480
512
if err != nil {
481
513
return nil , err
482
514
}
@@ -493,8 +525,8 @@ func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorizati
493
525
// The url argument is an Authorization.URI value.
494
526
//
495
527
// If successful, the caller will be required to obtain a new authorization
496
- // using the Authorize method before being able to request a new certificate
497
- // for the domain associated with the authorization.
528
+ // using the Authorize or AuthorizeOrder methods before being able to request
529
+ // a new certificate for the domain associated with the authorization.
498
530
//
499
531
// It does not revoke existing certificates.
500
532
func (c * Client ) RevokeAuthorization (ctx context.Context , url string ) error {
@@ -528,8 +560,18 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
528
560
// In all other cases WaitAuthorization returns an error.
529
561
// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
530
562
func (c * Client ) WaitAuthorization (ctx context.Context , url string ) (* Authorization , error ) {
563
+ // Required for c.accountKID() when in RFC mode.
564
+ dir , err := c .Discover (ctx )
565
+ if err != nil {
566
+ return nil , err
567
+ }
568
+ getfn := c .postAsGet
569
+ if ! dir .rfcCompliant () {
570
+ getfn = c .get
571
+ }
572
+
531
573
for {
532
- res , err := c . get (ctx , url , wantStatus (http .StatusOK , http .StatusAccepted ))
574
+ res , err := getfn (ctx , url , wantStatus (http .StatusOK , http .StatusAccepted ))
533
575
if err != nil {
534
576
return nil , err
535
577
}
@@ -572,10 +614,21 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
572
614
//
573
615
// A client typically polls a challenge status using this method.
574
616
func (c * Client ) GetChallenge (ctx context.Context , url string ) (* Challenge , error ) {
575
- res , err := c .get (ctx , url , wantStatus (http .StatusOK , http .StatusAccepted ))
617
+ // Required for c.accountKID() when in RFC mode.
618
+ dir , err := c .Discover (ctx )
619
+ if err != nil {
620
+ return nil , err
621
+ }
622
+
623
+ getfn := c .postAsGet
624
+ if ! dir .rfcCompliant () {
625
+ getfn = c .get
626
+ }
627
+ res , err := getfn (ctx , url , wantStatus (http .StatusOK , http .StatusAccepted ))
576
628
if err != nil {
577
629
return nil , err
578
630
}
631
+
579
632
defer res .Body .Close ()
580
633
v := wireChallenge {URI : url }
581
634
if err := json .NewDecoder (res .Body ).Decode (& v ); err != nil {
@@ -590,23 +643,26 @@ func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, erro
590
643
// The server will then perform the validation asynchronously.
591
644
func (c * Client ) Accept (ctx context.Context , chal * Challenge ) (* Challenge , error ) {
592
645
// Required for c.accountKID() when in RFC mode.
593
- if _ , err := c .Discover (ctx ); err != nil {
594
- return nil , err
595
- }
596
-
597
- auth , err := keyAuth (c .Key .Public (), chal .Token )
646
+ dir , err := c .Discover (ctx )
598
647
if err != nil {
599
648
return nil , err
600
649
}
601
650
602
- req := struct {
603
- Resource string `json:"resource"`
604
- Type string `json:"type"`
605
- Auth string `json:"keyAuthorization"`
606
- }{
607
- Resource : "challenge" ,
608
- Type : chal .Type ,
609
- Auth : auth ,
651
+ var req interface {} = json .RawMessage ("{}" ) // RFC-compliant CA
652
+ if ! dir .rfcCompliant () {
653
+ auth , err := keyAuth (c .Key .Public (), chal .Token )
654
+ if err != nil {
655
+ return nil , err
656
+ }
657
+ req = struct {
658
+ Resource string `json:"resource"`
659
+ Type string `json:"type"`
660
+ Auth string `json:"keyAuthorization"`
661
+ }{
662
+ Resource : "challenge" ,
663
+ Type : chal .Type ,
664
+ Auth : auth ,
665
+ }
610
666
}
611
667
res , err := c .post (ctx , nil , chal .URI , req , wantStatus (
612
668
http .StatusOK , // according to the spec
@@ -658,21 +714,8 @@ func (c *Client) HTTP01ChallengePath(token string) string {
658
714
}
659
715
660
716
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
661
- // Servers can present the certificate to validate the challenge and prove control
662
- // over a domain name.
663
- //
664
- // The implementation is incomplete in that the returned value is a single certificate,
665
- // computed only for Z0 of the key authorization. ACME CAs are expected to update
666
- // their implementations to use the newer version, TLS-SNI-02.
667
- // For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3.
668
- //
669
- // The token argument is a Challenge.Token value.
670
- // If a WithKey option is provided, its private part signs the returned cert,
671
- // and the public part is used to specify the signee.
672
- // If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
673
717
//
674
- // The returned certificate is valid for the next 24 hours and must be presented only when
675
- // the server name of the TLS ClientHello matches exactly the returned name value.
718
+ // Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec.
676
719
func (c * Client ) TLSSNI01ChallengeCert (token string , opt ... CertOption ) (cert tls.Certificate , name string , err error ) {
677
720
ka , err := keyAuth (c .Key .Public (), token )
678
721
if err != nil {
@@ -689,17 +732,8 @@ func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tl
689
732
}
690
733
691
734
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
692
- // Servers can present the certificate to validate the challenge and prove control
693
- // over a domain name. For more details on TLS-SNI-02 see
694
- // https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3.
695
- //
696
- // The token argument is a Challenge.Token value.
697
- // If a WithKey option is provided, its private part signs the returned cert,
698
- // and the public part is used to specify the signee.
699
- // If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
700
735
//
701
- // The returned certificate is valid for the next 24 hours and must be presented only when
702
- // the server name in the TLS ClientHello matches exactly the returned name value.
736
+ // Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec.
703
737
func (c * Client ) TLSSNI02ChallengeCert (token string , opt ... CertOption ) (cert tls.Certificate , name string , err error ) {
704
738
b := sha256 .Sum256 ([]byte (token ))
705
739
h := hex .EncodeToString (b [:])
@@ -766,7 +800,7 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
766
800
return tlsChallengeCert ([]string {domain }, newOpt )
767
801
}
768
802
769
- // doReg sends all types of registration requests.
803
+ // doReg sends all types of registration requests the old way (pre-RFC world) .
770
804
// The type of request is identified by typ argument, which is a "resource"
771
805
// in the ACME spec terms.
772
806
//
0 commit comments