1// Copyright 2020 Matthew Holt 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package acme 16 17import ( 18 "context" 19 "crypto/sha256" 20 "encoding/base64" 21) 22 23// Challenge holds information about an ACME challenge. 24// 25// "An ACME challenge object represents a server's offer to validate a 26// client's possession of an identifier in a specific way. Unlike the 27// other objects listed above, there is not a single standard structure 28// for a challenge object. The contents of a challenge object depend on 29// the validation method being used. The general structure of challenge 30// objects and an initial set of validation methods are described in 31// Section 8." §7.1.5 32type Challenge struct { 33 // "Challenge objects all contain the following basic fields..." §8 34 35 // type (required, string): The type of challenge encoded in the 36 // object. 37 Type string `json:"type"` 38 39 // url (required, string): The URL to which a response can be posted. 40 URL string `json:"url"` 41 42 // status (required, string): The status of this challenge. Possible 43 // values are "pending", "processing", "valid", and "invalid" (see 44 // Section 7.1.6). 45 Status string `json:"status"` 46 47 // validated (optional, string): The time at which the server validated 48 // this challenge, encoded in the format specified in [RFC3339]. 49 // This field is REQUIRED if the "status" field is "valid". 50 Validated string `json:"validated,omitempty"` 51 52 // error (optional, object): Error that occurred while the server was 53 // validating the challenge, if any, structured as a problem document 54 // [RFC7807]. Multiple errors can be indicated by using subproblems 55 // Section 6.7.1. A challenge object with an error MUST have status 56 // equal to "invalid". 57 Error *Problem `json:"error,omitempty"` 58 59 // "All additional fields are specified by the challenge type." §8 60 // (We also add our own for convenience.) 61 62 // "The token for a challenge is a string comprised entirely of 63 // characters in the URL-safe base64 alphabet." §8.1 64 // 65 // Used by the http-01, tls-alpn-01, and dns-01 challenges. 66 Token string `json:"token,omitempty"` 67 68 // A key authorization is a string that concatenates the token for the 69 // challenge with a key fingerprint, separated by a "." character (§8.1): 70 // 71 // keyAuthorization = token || '.' || base64url(Thumbprint(accountKey)) 72 // 73 // This client package automatically assembles and sets this value for you. 74 KeyAuthorization string `json:"keyAuthorization,omitempty"` 75 76 // We attach the identifier that this challenge is associated with, which 77 // may be useful information for solving a challenge. It is not part of the 78 // structure as defined by the spec but is added by us to provide enough 79 // information to solve the DNS-01 challenge. 80 Identifier Identifier `json:"identifier,omitempty"` 81} 82 83// HTTP01ResourcePath returns the URI path for solving the http-01 challenge. 84// 85// "The path at which the resource is provisioned is comprised of the 86// fixed prefix '/.well-known/acme-challenge/', followed by the 'token' 87// value in the challenge." §8.3 88func (c Challenge) HTTP01ResourcePath() string { 89 return "/.well-known/acme-challenge/" + c.Token 90} 91 92// DNS01TXTRecordName returns the name of the TXT record to create for 93// solving the dns-01 challenge. 94// 95// "The client constructs the validation domain name by prepending the 96// label '_acme-challenge' to the domain name being validated, then 97// provisions a TXT record with the digest value under that name." §8.4 98func (c Challenge) DNS01TXTRecordName() string { 99 return "_acme-challenge." + c.Identifier.Value 100} 101 102// DNS01KeyAuthorization encodes a key authorization value to be used 103// in a TXT record for the _acme-challenge DNS record. 104// 105// "A client fulfills this challenge by constructing a key authorization 106// from the 'token' value provided in the challenge and the client's 107// account key. The client then computes the SHA-256 digest [FIPS180-4] 108// of the key authorization. 109// 110// The record provisioned to the DNS contains the base64url encoding of 111// this digest." §8.4 112func (c Challenge) DNS01KeyAuthorization() string { 113 h := sha256.Sum256([]byte(c.KeyAuthorization)) 114 return base64.RawURLEncoding.EncodeToString(h[:]) 115} 116 117// InitiateChallenge "indicates to the server that it is ready for the challenge 118// validation by sending an empty JSON body ('{}') carried in a POST request to 119// the challenge URL (not the authorization URL)." §7.5.1 120func (c *Client) InitiateChallenge(ctx context.Context, account Account, challenge Challenge) (Challenge, error) { 121 if err := c.provision(ctx); err != nil { 122 return Challenge{}, err 123 } 124 _, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, challenge.URL, struct{}{}, &challenge) 125 return challenge, err 126} 127 128// The standard or well-known ACME challenge types. 129const ( 130 ChallengeTypeHTTP01 = "http-01" // RFC 8555 §8.3 131 ChallengeTypeDNS01 = "dns-01" // RFC 8555 §8.4 132 ChallengeTypeTLSALPN01 = "tls-alpn-01" // RFC 8737 §3 133) 134