acme-client 2.0.0 → 2.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +91 -0
- data/Gemfile +5 -5
- data/README.md +75 -27
- data/Rakefile +1 -4
- data/acme-client.gemspec +9 -8
- data/bin/generate_keystash +9 -0
- data/bin/release +1 -0
- data/lib/acme/client/chain_identifier.rb +27 -0
- data/lib/acme/client/error.rb +5 -0
- data/lib/acme/client/http_client.rb +162 -0
- data/lib/acme/client/jwk/base.rb +2 -2
- data/lib/acme/client/jwk/ecdsa.rb +7 -5
- data/lib/acme/client/jwk/hmac.rb +30 -0
- data/lib/acme/client/jwk.rb +1 -0
- data/lib/acme/client/resources/account.rb +5 -5
- data/lib/acme/client/resources/authorization.rb +4 -4
- data/lib/acme/client/resources/challenges/base.rb +11 -5
- data/lib/acme/client/resources/challenges/unsupported_challenge.rb +2 -0
- data/lib/acme/client/resources/challenges.rb +2 -6
- data/lib/acme/client/resources/directory.rb +9 -23
- data/lib/acme/client/resources/order.rb +5 -5
- data/lib/acme/client/util.rb +13 -2
- data/lib/acme/client/version.rb +1 -1
- data/lib/acme/client.rb +117 -48
- metadata +55 -60
- data/.gitignore +0 -10
- data/.rspec +0 -3
- data/.rubocop.yml +0 -139
- data/.travis.yml +0 -7
- data/lib/acme/client/faraday_middleware.rb +0 -116
@@ -5,7 +5,7 @@ class Acme::Client::Resources::Account
|
|
5
5
|
|
6
6
|
def initialize(client, **arguments)
|
7
7
|
@client = client
|
8
|
-
assign_attributes(arguments)
|
8
|
+
assign_attributes(**arguments)
|
9
9
|
end
|
10
10
|
|
11
11
|
def kid
|
@@ -13,19 +13,19 @@ class Acme::Client::Resources::Account
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def update(contact: nil, terms_of_service_agreed: nil)
|
16
|
-
assign_attributes
|
16
|
+
assign_attributes(**@client.account_update(
|
17
17
|
contact: contact, terms_of_service_agreed: term_of_service
|
18
|
-
).to_h
|
18
|
+
).to_h)
|
19
19
|
true
|
20
20
|
end
|
21
21
|
|
22
22
|
def deactivate
|
23
|
-
assign_attributes
|
23
|
+
assign_attributes(**@client.account_deactivate.to_h)
|
24
24
|
true
|
25
25
|
end
|
26
26
|
|
27
27
|
def reload
|
28
|
-
assign_attributes
|
28
|
+
assign_attributes(**@client.account.to_h)
|
29
29
|
true
|
30
30
|
end
|
31
31
|
|
@@ -5,16 +5,16 @@ class Acme::Client::Resources::Authorization
|
|
5
5
|
|
6
6
|
def initialize(client, **arguments)
|
7
7
|
@client = client
|
8
|
-
assign_attributes(arguments)
|
8
|
+
assign_attributes(**arguments)
|
9
9
|
end
|
10
10
|
|
11
11
|
def deactivate
|
12
|
-
assign_attributes
|
12
|
+
assign_attributes(**@client.deactivate_authorization(url: url).to_h)
|
13
13
|
true
|
14
14
|
end
|
15
15
|
|
16
16
|
def reload
|
17
|
-
assign_attributes
|
17
|
+
assign_attributes(**@client.authorization(url: url).to_h)
|
18
18
|
true
|
19
19
|
end
|
20
20
|
|
@@ -56,7 +56,7 @@ class Acme::Client::Resources::Authorization
|
|
56
56
|
type: attributes.fetch('type'),
|
57
57
|
status: attributes.fetch('status'),
|
58
58
|
url: attributes.fetch('url'),
|
59
|
-
token: attributes.fetch('token'),
|
59
|
+
token: attributes.fetch('token', nil),
|
60
60
|
error: attributes['error']
|
61
61
|
}
|
62
62
|
Acme::Client::Resources::Challenges.new(@client, **arguments)
|
@@ -5,7 +5,7 @@ class Acme::Client::Resources::Challenges::Base
|
|
5
5
|
|
6
6
|
def initialize(client, **arguments)
|
7
7
|
@client = client
|
8
|
-
assign_attributes(arguments)
|
8
|
+
assign_attributes(**arguments)
|
9
9
|
end
|
10
10
|
|
11
11
|
def challenge_type
|
@@ -17,14 +17,14 @@ class Acme::Client::Resources::Challenges::Base
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def reload
|
20
|
-
assign_attributes
|
20
|
+
assign_attributes(**@client.challenge(url: url).to_h)
|
21
21
|
true
|
22
22
|
end
|
23
23
|
|
24
24
|
def request_validation
|
25
|
-
assign_attributes
|
26
|
-
url: url
|
27
|
-
)
|
25
|
+
assign_attributes(**send_challenge_validation(
|
26
|
+
url: url
|
27
|
+
))
|
28
28
|
true
|
29
29
|
end
|
30
30
|
|
@@ -34,6 +34,12 @@ class Acme::Client::Resources::Challenges::Base
|
|
34
34
|
|
35
35
|
private
|
36
36
|
|
37
|
+
def send_challenge_validation(url:)
|
38
|
+
@client.request_challenge_validation(
|
39
|
+
url: url
|
40
|
+
).to_h
|
41
|
+
end
|
42
|
+
|
37
43
|
def assign_attributes(status:, url:, token:, error: nil)
|
38
44
|
@status = status
|
39
45
|
@url = url
|
@@ -4,6 +4,7 @@ module Acme::Client::Resources::Challenges
|
|
4
4
|
require 'acme/client/resources/challenges/base'
|
5
5
|
require 'acme/client/resources/challenges/http01'
|
6
6
|
require 'acme/client/resources/challenges/dns01'
|
7
|
+
require 'acme/client/resources/challenges/unsupported_challenge'
|
7
8
|
|
8
9
|
CHALLENGE_TYPES = {
|
9
10
|
'http-01' => Acme::Client::Resources::Challenges::HTTP01,
|
@@ -11,11 +12,6 @@ module Acme::Client::Resources::Challenges
|
|
11
12
|
}
|
12
13
|
|
13
14
|
def self.new(client, type:, **arguments)
|
14
|
-
|
15
|
-
if klass
|
16
|
-
klass.new(client, **arguments)
|
17
|
-
else
|
18
|
-
{ type: type }.merge(arguments)
|
19
|
-
end
|
15
|
+
CHALLENGE_TYPES.fetch(type, Unsupported).new(client, **arguments)
|
20
16
|
end
|
21
17
|
end
|
@@ -17,12 +17,13 @@ class Acme::Client::Resources::Directory
|
|
17
17
|
external_account_required: 'externalAccountRequired'
|
18
18
|
}
|
19
19
|
|
20
|
-
def initialize(
|
21
|
-
@
|
20
|
+
def initialize(client, **arguments)
|
21
|
+
@client = client
|
22
|
+
assign_attributes(**arguments)
|
22
23
|
end
|
23
24
|
|
24
25
|
def endpoint_for(key)
|
25
|
-
directory.fetch(key) do |missing_key|
|
26
|
+
@directory.fetch(key) do |missing_key|
|
26
27
|
raise Acme::Client::Error::UnsupportedOperation,
|
27
28
|
"Directory at #{@url} does not include `#{missing_key}`"
|
28
29
|
end
|
@@ -45,31 +46,16 @@ class Acme::Client::Resources::Directory
|
|
45
46
|
end
|
46
47
|
|
47
48
|
def meta
|
48
|
-
directory[:meta]
|
49
|
+
@directory[:meta]
|
49
50
|
end
|
50
51
|
|
51
52
|
private
|
52
53
|
|
53
|
-
def directory
|
54
|
-
@directory
|
55
|
-
|
56
|
-
|
57
|
-
def load_directory
|
58
|
-
body = fetch_directory
|
59
|
-
result = {}
|
60
|
-
result[:meta] = body.delete('meta')
|
54
|
+
def assign_attributes(directory:)
|
55
|
+
@directory = {}
|
56
|
+
@directory[:meta] = directory.delete('meta')
|
61
57
|
DIRECTORY_RESOURCES.each do |key, entry|
|
62
|
-
|
58
|
+
@directory[key] = URI(directory[entry]) if directory[entry]
|
63
59
|
end
|
64
|
-
result
|
65
|
-
rescue JSON::ParserError => exception
|
66
|
-
raise InvalidDirectory, "Invalid directory url\n#{@directory} did not return a valid directory\n#{exception.inspect}"
|
67
|
-
end
|
68
|
-
|
69
|
-
def fetch_directory
|
70
|
-
connection = Faraday.new(url: @directory, **@connection_options)
|
71
|
-
connection.headers[:user_agent] = Acme::Client::USER_AGENT
|
72
|
-
response = connection.get(@url)
|
73
|
-
JSON.parse(response.body)
|
74
60
|
end
|
75
61
|
end
|
@@ -5,11 +5,11 @@ class Acme::Client::Resources::Order
|
|
5
5
|
|
6
6
|
def initialize(client, **arguments)
|
7
7
|
@client = client
|
8
|
-
assign_attributes(arguments)
|
8
|
+
assign_attributes(**arguments)
|
9
9
|
end
|
10
10
|
|
11
11
|
def reload
|
12
|
-
assign_attributes
|
12
|
+
assign_attributes(**@client.order(url: url).to_h)
|
13
13
|
true
|
14
14
|
end
|
15
15
|
|
@@ -20,13 +20,13 @@ class Acme::Client::Resources::Order
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def finalize(csr:)
|
23
|
-
assign_attributes
|
23
|
+
assign_attributes(**@client.finalize(url: finalize_url, csr: csr).to_h)
|
24
24
|
true
|
25
25
|
end
|
26
26
|
|
27
|
-
def certificate
|
27
|
+
def certificate(force_chain: nil)
|
28
28
|
if certificate_url
|
29
|
-
@client.certificate(url: certificate_url)
|
29
|
+
@client.certificate(url: certificate_url, force_chain: force_chain)
|
30
30
|
else
|
31
31
|
raise Acme::Client::Error::CertificateNotReady, 'No certificate_url to collect the order'
|
32
32
|
end
|
data/lib/acme/client/util.rb
CHANGED
@@ -1,8 +1,21 @@
|
|
1
1
|
module Acme::Client::Util
|
2
|
+
extend self
|
3
|
+
|
2
4
|
def urlsafe_base64(data)
|
3
5
|
Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
|
4
6
|
end
|
5
7
|
|
8
|
+
LINK_MATCH = /<(.*?)>\s?;\s?rel="([\w-]+)"/
|
9
|
+
|
10
|
+
# See RFC 8288 - https://tools.ietf.org/html/rfc8288#section-3
|
11
|
+
def decode_link_headers(link_header)
|
12
|
+
link_header.split(',').each_with_object({}) { |entry, hash|
|
13
|
+
_, link, name = *entry.match(LINK_MATCH)
|
14
|
+
hash[name] ||= []
|
15
|
+
hash[name].push(link)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
6
19
|
# Sets public key on CSR or cert.
|
7
20
|
#
|
8
21
|
# obj - An OpenSSL::X509::Certificate or OpenSSL::X509::Request instance.
|
@@ -19,6 +32,4 @@ module Acme::Client::Util
|
|
19
32
|
raise ArgumentError, 'priv must be EC or RSA'
|
20
33
|
end
|
21
34
|
end
|
22
|
-
|
23
|
-
extend self
|
24
35
|
end
|
data/lib/acme/client/version.rb
CHANGED
data/lib/acme/client.rb
CHANGED
@@ -1,24 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'faraday'
|
4
|
+
require 'faraday/retry'
|
4
5
|
require 'json'
|
5
6
|
require 'openssl'
|
6
7
|
require 'digest'
|
7
8
|
require 'forwardable'
|
8
9
|
require 'base64'
|
9
10
|
require 'time'
|
11
|
+
require 'uri'
|
10
12
|
|
11
13
|
module Acme; end
|
12
14
|
class Acme::Client; end
|
13
15
|
|
14
16
|
require 'acme/client/version'
|
17
|
+
require 'acme/client/http_client'
|
15
18
|
require 'acme/client/certificate_request'
|
16
19
|
require 'acme/client/self_sign_certificate'
|
17
20
|
require 'acme/client/resources'
|
18
|
-
require 'acme/client/faraday_middleware'
|
19
21
|
require 'acme/client/jwk'
|
20
22
|
require 'acme/client/error'
|
21
23
|
require 'acme/client/util'
|
24
|
+
require 'acme/client/chain_identifier'
|
22
25
|
|
23
26
|
class Acme::Client
|
24
27
|
DEFAULT_DIRECTORY = 'http://127.0.0.1:4000/directory'.freeze
|
@@ -28,7 +31,7 @@ class Acme::Client
|
|
28
31
|
pem: 'application/pem-certificate-chain'
|
29
32
|
}
|
30
33
|
|
31
|
-
def initialize(jwk: nil, kid: nil, private_key: nil, directory: DEFAULT_DIRECTORY, connection_options: {})
|
34
|
+
def initialize(jwk: nil, kid: nil, private_key: nil, directory: DEFAULT_DIRECTORY, connection_options: {}, bad_nonce_retry: 0)
|
32
35
|
if jwk.nil? && private_key.nil?
|
33
36
|
raise ArgumentError, 'must specify jwk or private_key'
|
34
37
|
end
|
@@ -40,13 +43,15 @@ class Acme::Client
|
|
40
43
|
end
|
41
44
|
|
42
45
|
@kid, @connection_options = kid, connection_options
|
43
|
-
@
|
46
|
+
@bad_nonce_retry = bad_nonce_retry
|
47
|
+
@directory_url = URI(directory)
|
44
48
|
@nonces ||= []
|
45
49
|
end
|
46
50
|
|
47
51
|
attr_reader :jwk, :nonces
|
48
52
|
|
49
|
-
def new_account(contact:, terms_of_service_agreed: nil)
|
53
|
+
def new_account(contact:, terms_of_service_agreed: nil, external_account_binding: nil)
|
54
|
+
new_account_endpoint = endpoint_for(:new_account)
|
50
55
|
payload = {
|
51
56
|
contact: Array(contact)
|
52
57
|
}
|
@@ -55,7 +60,18 @@ class Acme::Client
|
|
55
60
|
payload[:termsOfServiceAgreed] = terms_of_service_agreed
|
56
61
|
end
|
57
62
|
|
58
|
-
|
63
|
+
if external_account_binding
|
64
|
+
kid, hmac_key = external_account_binding.values_at(:kid, :hmac_key)
|
65
|
+
if kid.nil? || hmac_key.nil?
|
66
|
+
raise ArgumentError, 'must specify kid and hmac_key key for external_account_binding'
|
67
|
+
end
|
68
|
+
|
69
|
+
hmac = Acme::Client::JWK::HMAC.new(Base64.urlsafe_decode64(hmac_key))
|
70
|
+
external_account_payload = hmac.jws(header: { kid: kid, url: new_account_endpoint }, payload: @jwk)
|
71
|
+
payload[:externalAccountBinding] = JSON.parse(external_account_payload)
|
72
|
+
end
|
73
|
+
|
74
|
+
response = post(new_account_endpoint, payload: payload, mode: :jws)
|
59
75
|
@kid = response.headers.fetch(:location)
|
60
76
|
|
61
77
|
if response.body.nil? || response.body.empty?
|
@@ -82,13 +98,35 @@ class Acme::Client
|
|
82
98
|
Acme::Client::Resources::Account.new(self, url: kid, **arguments)
|
83
99
|
end
|
84
100
|
|
101
|
+
def account_key_change(new_private_key: nil, new_jwk: nil)
|
102
|
+
if new_private_key.nil? && new_jwk.nil?
|
103
|
+
raise ArgumentError, 'must specify new_jwk or new_private_key'
|
104
|
+
end
|
105
|
+
old_jwk = jwk
|
106
|
+
new_jwk ||= Acme::Client::JWK.from_private_key(new_private_key)
|
107
|
+
|
108
|
+
inner_payload_header = {
|
109
|
+
url: endpoint_for(:key_change)
|
110
|
+
}
|
111
|
+
inner_payload = {
|
112
|
+
account: kid,
|
113
|
+
oldKey: old_jwk.to_h
|
114
|
+
}
|
115
|
+
payload = JSON.parse(new_jwk.jws(header: inner_payload_header, payload: inner_payload))
|
116
|
+
|
117
|
+
response = post(endpoint_for(:key_change), payload: payload, mode: :kid)
|
118
|
+
arguments = attributes_from_account_response(response)
|
119
|
+
@jwk = new_jwk
|
120
|
+
Acme::Client::Resources::Account.new(self, url: kid, **arguments)
|
121
|
+
end
|
122
|
+
|
85
123
|
def account
|
86
124
|
@kid ||= begin
|
87
125
|
response = post(endpoint_for(:new_account), payload: { onlyReturnExisting: true }, mode: :jwk)
|
88
126
|
response.headers.fetch(:location)
|
89
127
|
end
|
90
128
|
|
91
|
-
response =
|
129
|
+
response = post_as_get(@kid)
|
92
130
|
arguments = attributes_from_account_response(response)
|
93
131
|
Acme::Client::Resources::Account.new(self, url: @kid, **arguments)
|
94
132
|
end
|
@@ -99,13 +137,7 @@ class Acme::Client
|
|
99
137
|
|
100
138
|
def new_order(identifiers:, not_before: nil, not_after: nil)
|
101
139
|
payload = {}
|
102
|
-
payload['identifiers'] =
|
103
|
-
identifiers
|
104
|
-
else
|
105
|
-
Array(identifiers).map do |identifier|
|
106
|
-
{ type: 'dns', value: identifier }
|
107
|
-
end
|
108
|
-
end
|
140
|
+
payload['identifiers'] = prepare_order_identifiers(identifiers)
|
109
141
|
payload['notBefore'] = not_before if not_before
|
110
142
|
payload['notAfter'] = not_after if not_after
|
111
143
|
|
@@ -115,7 +147,7 @@ class Acme::Client
|
|
115
147
|
end
|
116
148
|
|
117
149
|
def order(url:)
|
118
|
-
response =
|
150
|
+
response = post_as_get(url)
|
119
151
|
arguments = attributes_from_order_response(response)
|
120
152
|
Acme::Client::Resources::Order.new(self, **arguments.merge(url: url))
|
121
153
|
end
|
@@ -131,13 +163,28 @@ class Acme::Client
|
|
131
163
|
Acme::Client::Resources::Order.new(self, **arguments)
|
132
164
|
end
|
133
165
|
|
134
|
-
def certificate(url:)
|
166
|
+
def certificate(url:, force_chain: nil)
|
135
167
|
response = download(url, format: :pem)
|
136
|
-
response.body
|
168
|
+
pem = response.body
|
169
|
+
|
170
|
+
return pem if force_chain.nil?
|
171
|
+
|
172
|
+
return pem if ChainIdentifier.new(pem).match_name?(force_chain)
|
173
|
+
|
174
|
+
alternative_urls = Array(response.headers.dig('link', 'alternate'))
|
175
|
+
alternative_urls.each do |alternate_url|
|
176
|
+
response = download(alternate_url, format: :pem)
|
177
|
+
pem = response.body
|
178
|
+
if ChainIdentifier.new(pem).match_name?(force_chain)
|
179
|
+
return pem
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
raise Acme::Client::Error::ForcedChainNotFound, "Could not find any matching chain for `#{force_chain}`"
|
137
184
|
end
|
138
185
|
|
139
186
|
def authorization(url:)
|
140
|
-
response =
|
187
|
+
response = post_as_get(url)
|
141
188
|
arguments = attributes_from_authorization_response(response)
|
142
189
|
Acme::Client::Resources::Authorization.new(self, url: url, **arguments)
|
143
190
|
end
|
@@ -149,13 +196,13 @@ class Acme::Client
|
|
149
196
|
end
|
150
197
|
|
151
198
|
def challenge(url:)
|
152
|
-
response =
|
199
|
+
response = post_as_get(url)
|
153
200
|
arguments = attributes_from_challenge_response(response)
|
154
201
|
Acme::Client::Resources::Challenges.new(self, **arguments)
|
155
202
|
end
|
156
203
|
|
157
|
-
def request_challenge_validation(url:, key_authorization:)
|
158
|
-
response = post(url, payload: {
|
204
|
+
def request_challenge_validation(url:, key_authorization: nil)
|
205
|
+
response = post(url, payload: {})
|
159
206
|
arguments = attributes_from_challenge_response(response)
|
160
207
|
Acme::Client::Resources::Challenges.new(self, **arguments)
|
161
208
|
end
|
@@ -176,33 +223,64 @@ class Acme::Client
|
|
176
223
|
end
|
177
224
|
|
178
225
|
def get_nonce
|
179
|
-
|
226
|
+
http_client = Acme::Client::HTTPClient.new_connection(url: endpoint_for(:new_nonce), options: @connection_options)
|
227
|
+
response = http_client.head(nil, nil)
|
180
228
|
nonces << response.headers['replay-nonce']
|
181
229
|
true
|
182
230
|
end
|
183
231
|
|
232
|
+
def directory
|
233
|
+
@directory ||= load_directory
|
234
|
+
end
|
235
|
+
|
184
236
|
def meta
|
185
|
-
|
237
|
+
directory.meta
|
186
238
|
end
|
187
239
|
|
188
240
|
def terms_of_service
|
189
|
-
|
241
|
+
directory.terms_of_service
|
190
242
|
end
|
191
243
|
|
192
244
|
def website
|
193
|
-
|
245
|
+
directory.website
|
194
246
|
end
|
195
247
|
|
196
248
|
def caa_identities
|
197
|
-
|
249
|
+
directory.caa_identities
|
198
250
|
end
|
199
251
|
|
200
252
|
def external_account_required
|
201
|
-
|
253
|
+
directory.external_account_required
|
202
254
|
end
|
203
255
|
|
204
256
|
private
|
205
257
|
|
258
|
+
def load_directory
|
259
|
+
Acme::Client::Resources::Directory.new(self, directory: fetch_directory)
|
260
|
+
end
|
261
|
+
|
262
|
+
def fetch_directory
|
263
|
+
response = get(@directory_url)
|
264
|
+
response.body
|
265
|
+
rescue JSON::ParserError => exception
|
266
|
+
raise Acme::Client::Error::InvalidDirectory,
|
267
|
+
"Invalid directory url\n#{@directory_url} did not return a valid directory\n#{exception.inspect}"
|
268
|
+
end
|
269
|
+
|
270
|
+
def prepare_order_identifiers(identifiers)
|
271
|
+
if identifiers.is_a?(Hash)
|
272
|
+
[identifiers]
|
273
|
+
else
|
274
|
+
Array(identifiers).map do |identifier|
|
275
|
+
if identifier.is_a?(String)
|
276
|
+
{ type: 'dns', value: identifier }
|
277
|
+
else
|
278
|
+
identifier
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
206
284
|
def attributes_from_account_response(response)
|
207
285
|
extract_attributes(
|
208
286
|
response.body,
|
@@ -249,14 +327,19 @@ class Acme::Client
|
|
249
327
|
connection.post(url, payload)
|
250
328
|
end
|
251
329
|
|
252
|
-
def
|
330
|
+
def post_as_get(url, mode: :kid)
|
331
|
+
connection = connection_for(url: url, mode: mode)
|
332
|
+
connection.post(url, nil)
|
333
|
+
end
|
334
|
+
|
335
|
+
def get(url, mode: :get)
|
253
336
|
connection = connection_for(url: url, mode: mode)
|
254
337
|
connection.get(url)
|
255
338
|
end
|
256
339
|
|
257
340
|
def download(url, format:)
|
258
|
-
connection = connection_for(url: url, mode: :
|
259
|
-
connection.
|
341
|
+
connection = connection_for(url: url, mode: :kid)
|
342
|
+
connection.post do |request|
|
260
343
|
request.url(url)
|
261
344
|
request.headers['Accept'] = CONTENT_TYPES.fetch(format)
|
262
345
|
end
|
@@ -265,29 +348,15 @@ class Acme::Client
|
|
265
348
|
def connection_for(url:, mode:)
|
266
349
|
uri = URI(url)
|
267
350
|
endpoint = "#{uri.scheme}://#{uri.hostname}:#{uri.port}"
|
351
|
+
|
268
352
|
@connections ||= {}
|
269
353
|
@connections[mode] ||= {}
|
270
|
-
@connections[mode][endpoint] ||=
|
271
|
-
|
272
|
-
|
273
|
-
def new_connection(endpoint:, mode:)
|
274
|
-
Faraday.new(endpoint, **@connection_options) do |configuration|
|
275
|
-
configuration.use Acme::Client::FaradayMiddleware, client: self, mode: mode
|
276
|
-
configuration.adapter Faraday.default_adapter
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
def fetch_chain(response, limit = 10)
|
281
|
-
links = response.headers['link']
|
282
|
-
if limit.zero? || links.nil? || links['up'].nil?
|
283
|
-
[]
|
284
|
-
else
|
285
|
-
issuer = get(links['up'])
|
286
|
-
[OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)]
|
287
|
-
end
|
354
|
+
@connections[mode][endpoint] ||= Acme::Client::HTTPClient.new_acme_connection(
|
355
|
+
url: URI(endpoint), mode: mode, client: self, options: @connection_options, bad_nonce_retry: @bad_nonce_retry
|
356
|
+
)
|
288
357
|
end
|
289
358
|
|
290
359
|
def endpoint_for(key)
|
291
|
-
|
360
|
+
directory.endpoint_for(key)
|
292
361
|
end
|
293
362
|
end
|