// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package acme import ( "crypto" "crypto/x509" "errors" "fmt" "net/http" "strings" "time" ) // ACME server response statuses used to describe Authorization and Challenge states. const ( StatusUnknown = "unknown" StatusPending = "pending" StatusProcessing = "processing" StatusValid = "valid" StatusInvalid = "invalid" StatusRevoked = "revoked" ) // CRLReasonCode identifies the reason for a certificate revocation. type CRLReasonCode int // CRL reason codes as defined in RFC 5280. const ( CRLReasonUnspecified CRLReasonCode = 0 CRLReasonKeyCompromise CRLReasonCode = 1 CRLReasonCACompromise CRLReasonCode = 2 CRLReasonAffiliationChanged CRLReasonCode = 3 CRLReasonSuperseded CRLReasonCode = 4 CRLReasonCessationOfOperation CRLReasonCode = 5 CRLReasonCertificateHold CRLReasonCode = 6 CRLReasonRemoveFromCRL CRLReasonCode = 8 CRLReasonPrivilegeWithdrawn CRLReasonCode = 9 CRLReasonAACompromise CRLReasonCode = 10 ) // ErrUnsupportedKey is returned when an unsupported key type is encountered. var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported") // Error is an ACME error, defined in Problem Details for HTTP APIs doc // http://tools.ietf.org/html/draft-ietf-appsawg-http-problem. type Error struct { // StatusCode is The HTTP status code generated by the origin server. StatusCode int // ProblemType is a URI reference that identifies the problem type, // typically in a "urn:acme:error:xxx" form. ProblemType string // Detail is a human-readable explanation specific to this occurrence of the problem. Detail string // Header is the original server error response headers. // It may be nil. Header http.Header } func (e *Error) Error() string { return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail) } // AuthorizationError indicates that an authorization for an identifier // did not succeed. // It contains all errors from Challenge items of the failed Authorization. type AuthorizationError struct { // URI uniquely identifies the failed Authorization. URI string // Identifier is an AuthzID.Value of the failed Authorization. Identifier string // Errors is a collection of non-nil error values of Challenge items // of the failed Authorization. Errors []error } func (a *AuthorizationError) Error() string { e := make([]string, len(a.Errors)) for i, err := range a.Errors { e[i] = err.Error() } return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; ")) } // RateLimit reports whether err represents a rate limit error and // any Retry-After duration returned by the server. // // See the following for more details on rate limiting: // https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6 func RateLimit(err error) (time.Duration, bool) { e, ok := err.(*Error) if !ok { return 0, false } // Some CA implementations may return incorrect values. // Use case-insensitive comparison. if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") { return 0, false } if e.Header == nil { return 0, true } return retryAfter(e.Header.Get("Retry-After")), true } // Account is a user account. It is associated with a private key. type Account struct { // URI is the account unique ID, which is also a URL used to retrieve // account data from the CA. URI string // Contact is a slice of contact info used during registration. Contact []string // The terms user has agreed to. // A value not matching CurrentTerms indicates that the user hasn't agreed // to the actual Terms of Service of the CA. AgreedTerms string // Actual terms of a CA. CurrentTerms string // Authz is the authorization URL used to initiate a new authz flow. Authz string // Authorizations is a URI from which a list of authorizations // granted to this account can be fetched via a GET request. Authorizations string // Certificates is a URI from which a list of certificates // issued for this account can be fetched via a GET request. Certificates string } // Directory is ACME server discovery data. type Directory struct { // RegURL is an account endpoint URL, allowing for creating new // and modifying existing accounts. RegURL string // AuthzURL is used to initiate Identifier Authorization flow. AuthzURL string // CertURL is a new certificate issuance endpoint URL. CertURL string // RevokeURL is used to initiate a certificate revocation flow. RevokeURL string // Term is a URI identifying the current terms of service. Terms string // Website is an HTTP or HTTPS URL locating a website // providing more information about the ACME server. Website string // CAA consists of lowercase hostname elements, which the ACME server // recognises as referring to itself for the purposes of CAA record validation // as defined in RFC6844. CAA []string } // Challenge encodes a returned CA challenge. // Its Error field may be non-nil if the challenge is part of an Authorization // with StatusInvalid. type Challenge struct { // Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01". Type string // URI is where a challenge response can be posted to. URI string // Token is a random value that uniquely identifies the challenge. Token string // Status identifies the status of this challenge. Status string // Error indicates the reason for an authorization failure // when this challenge was used. // The type of a non-nil value is *Error. Error error } // Authorization encodes an authorization response. type Authorization struct { // URI uniquely identifies a authorization. URI string // Status identifies the status of an authorization. Status string // Identifier is what the account is authorized to represent. Identifier AuthzID // Challenges that the client needs to fulfill in order to prove possession // of the identifier (for pending authorizations). // For final authorizations, the challenges that were used. Challenges []*Challenge // A collection of sets of challenges, each of which would be sufficient // to prove possession of the identifier. // Clients must complete a set of challenges that covers at least one set. // Challenges are identified by their indices in the challenges array. // If this field is empty, the client needs to complete all challenges. Combinations [][]int } // AuthzID is an identifier that an account is authorized to represent. type AuthzID struct { Type string // The type of identifier, e.g. "dns". Value string // The identifier itself, e.g. "example.org". } // wireAuthz is ACME JSON representation of Authorization objects. type wireAuthz struct { Status string Challenges []wireChallenge Combinations [][]int Identifier struct { Type string Value string } } func (z *wireAuthz) authorization(uri string) *Authorization { a := &Authorization{ URI: uri, Status: z.Status, Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value}, Combinations: z.Combinations, // shallow copy Challenges: make([]*Challenge, len(z.Challenges)), } for i, v := range z.Challenges { a.Challenges[i] = v.challenge() } return a } func (z *wireAuthz) error(uri string) *AuthorizationError { err := &AuthorizationError{ URI: uri, Identifier: z.Identifier.Value, } for _, raw := range z.Challenges { if raw.Error != nil { err.Errors = append(err.Errors, raw.Error.error(nil)) } } return err } // wireChallenge is ACME JSON challenge representation. type wireChallenge struct { URI string `json:"uri"` Type string Token string Status string Error *wireError } func (c *wireChallenge) challenge() *Challenge { v := &Challenge{ URI: c.URI, Type: c.Type, Token: c.Token, Status: c.Status, } if v.Status == "" { v.Status = StatusPending } if c.Error != nil { v.Error = c.Error.error(nil) } return v } // wireError is a subset of fields of the Problem Details object // as described in https://tools.ietf.org/html/rfc7807#section-3.1. type wireError struct { Status int Type string Detail string } func (e *wireError) error(h http.Header) *Error { return &Error{ StatusCode: e.Status, ProblemType: e.Type, Detail: e.Detail, Header: h, } } // CertOption is an optional argument type for the TLS ChallengeCert methods for // customizing a temporary certificate for TLS-based challenges. type CertOption interface { privateCertOpt() } // WithKey creates an option holding a private/public key pair. // The private part signs a certificate, and the public part represents the signee. func WithKey(key crypto.Signer) CertOption { return &certOptKey{key} } type certOptKey struct { key crypto.Signer } func (*certOptKey) privateCertOpt() {} // WithTemplate creates an option for specifying a certificate template. // See x509.CreateCertificate for template usage details. // // In TLS ChallengeCert methods, the template is also used as parent, // resulting in a self-signed certificate. // The DNSNames field of t is always overwritten for tls-sni challenge certs. func WithTemplate(t *x509.Certificate) CertOption { return (*certOptTemplate)(t) } type certOptTemplate x509.Certificate func (*certOptTemplate) privateCertOpt() {}