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