acme-client 2.0.0 → 2.0.18
Sign up to get free protection for your applications and to get access to all the features.
- 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
|