acme-client 2.0.11 → 2.0.14
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/.github/workflows/test.yml +2 -2
- data/CHANGELOG.md +13 -0
- data/README.md +10 -3
- data/acme-client.gemspec +1 -1
- data/lib/acme/client/http_client.rb +162 -0
- data/lib/acme/client/jwk/hmac.rb +30 -0
- data/lib/acme/client/jwk.rb +1 -0
- data/lib/acme/client/resources/directory.rb +2 -7
- data/lib/acme/client/util.rb +2 -2
- data/lib/acme/client/version.rb +1 -1
- data/lib/acme/client.rb +21 -25
- metadata +17 -10
- data/lib/acme/client/faraday_middleware.rb +0 -111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6238349e171138af08de444bf08268ab3bebab0c4d0497bee91d9d4a30d397ab
|
4
|
+
data.tar.gz: 9928049d9997c284cec6d75e12bf6b5f07150459775d8f852adbc09b7c923178
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8543f742ee8fe3822c8c989a7e5f8b1f75473f01a36d7a2218c44b43ebec55fa538beabc4545c4798a7b35005ad1fddbd0ed59dbae6942413812fae9ce625f92
|
7
|
+
data.tar.gz: 77cd41773aa293bcb65ebdb9489c500a06b144e0efb2dc5af23baa8dec8c36c1af32fd9f1c97eb77e40eb4a521a9c904448d5765f64dcbe56a67f4907225aaf3
|
data/.github/workflows/test.yml
CHANGED
@@ -11,8 +11,8 @@ jobs:
|
|
11
11
|
runs-on: ubuntu-latest
|
12
12
|
strategy:
|
13
13
|
matrix:
|
14
|
-
ruby-version: ['2.
|
15
|
-
faraday-version: ['~> 1.
|
14
|
+
ruby-version: ['2.7', '3.0', '3.1', '3.2']
|
15
|
+
faraday-version: ['~> 1.10', '~> 2.7']
|
16
16
|
env:
|
17
17
|
FARADAY_VERSION: ${{ matrix.faraday-version }}
|
18
18
|
steps:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## `2.0.14`
|
2
|
+
|
3
|
+
* Fix Faraday HTTP exceptions leaking out, always raise `Acme::Client::Error` instead
|
4
|
+
|
5
|
+
## `2.0.13`
|
6
|
+
|
7
|
+
* Add support for External Account Binding
|
8
|
+
|
9
|
+
## `2.0.12`
|
10
|
+
|
11
|
+
* Update test matrix to current Ruby versions (2.7 to 3.2)
|
12
|
+
* Support for Faraday retry 2.x
|
13
|
+
|
1
14
|
## `2.0.11`
|
2
15
|
|
3
16
|
* Add support for error code `AlreadyRevoked` and `BadPublicKey`
|
data/README.md
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# Acme::Client
|
2
2
|
|
3
|
-
[](https://travis-ci.org/unixcharles/acme-client)
|
4
|
-
|
5
3
|
`acme-client` is a client implementation of the ACMEv2 / [RFC 8555](https://tools.ietf.org/html/rfc8555) protocol in Ruby.
|
6
4
|
|
7
5
|
You can find the ACME reference implementations of the [server](https://github.com/letsencrypt/boulder) in Go and the [client](https://github.com/certbot/certbot) in Python.
|
@@ -108,6 +106,15 @@ client.kid
|
|
108
106
|
=> "https://acme-staging-v02.api.letsencrypt.org/acme/acct/000000"
|
109
107
|
```
|
110
108
|
|
109
|
+
## External Account Binding support
|
110
|
+
|
111
|
+
You can use External Account Binding by providing a `external_account_binding` with a `kid` and `hmac_key`.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
client = Acme::Client.new(private_key: private_key, directory: 'https://acme.zerossl.com/v2/DV90')
|
115
|
+
account = client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true, external_account_binding: { kid: "your kid", hmac_key: "your hmac key"})
|
116
|
+
```
|
117
|
+
|
111
118
|
## Obtaining a certificate
|
112
119
|
### Ordering a certificate
|
113
120
|
|
@@ -237,7 +244,7 @@ To change the key used for an account you can call `#account_key_change` with th
|
|
237
244
|
```ruby
|
238
245
|
require 'openssl'
|
239
246
|
new_private_key = OpenSSL::PKey::RSA.new(4096)
|
240
|
-
client.account_key_change(
|
247
|
+
client.account_key_change(new_private_key: new_private_key)
|
241
248
|
```
|
242
249
|
|
243
250
|
## Requirements
|
data/acme-client.gemspec
CHANGED
@@ -24,5 +24,5 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency 'webrick'
|
25
25
|
|
26
26
|
spec.add_runtime_dependency 'faraday', '>= 1.0', '< 3.0.0'
|
27
|
-
spec.add_runtime_dependency 'faraday-retry', '
|
27
|
+
spec.add_runtime_dependency 'faraday-retry', '>= 1.0', '< 3.0.0'
|
28
28
|
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Acme::Client::HTTPClient
|
4
|
+
# Creates and returns a new HTTP client, with default settings.
|
5
|
+
#
|
6
|
+
# @param url [URI:HTTPS]
|
7
|
+
# @param options [Hash]
|
8
|
+
# @return [Faraday::Connection]
|
9
|
+
def self.new_connection(url:, options: {})
|
10
|
+
Faraday.new(url, options) do |configuration|
|
11
|
+
configuration.use Acme::Client::HTTPClient::ErrorMiddleware
|
12
|
+
|
13
|
+
yield(configuration) if block_given?
|
14
|
+
|
15
|
+
configuration.headers[:user_agent] = Acme::Client::USER_AGENT
|
16
|
+
configuration.adapter Faraday.default_adapter
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates and returns a new HTTP client designed for the Acme-protocol, with default settings.
|
21
|
+
#
|
22
|
+
# @param url [URI:HTTPS]
|
23
|
+
# @param client [Acme::Client]
|
24
|
+
# @param mode [Symbol]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @param bad_nonce_retry [Integer]
|
27
|
+
# @return [Faraday::Connection]
|
28
|
+
def self.new_acme_connection(url:, client:, mode:, options: {}, bad_nonce_retry: 0)
|
29
|
+
new_connection(url: url, options: options) do |configuration|
|
30
|
+
if bad_nonce_retry > 0
|
31
|
+
configuration.request(:retry,
|
32
|
+
max: bad_nonce_retry,
|
33
|
+
methods: Faraday::Connection::METHODS,
|
34
|
+
exceptions: [Acme::Client::Error::BadNonce])
|
35
|
+
end
|
36
|
+
|
37
|
+
configuration.use Acme::Client::HTTPClient::AcmeMiddleware, client: client, mode: mode
|
38
|
+
|
39
|
+
yield(configuration) if block_given?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# ErrorMiddleware ensures the HTTP Client would not raise exceptions outside the Acme namespace.
|
44
|
+
#
|
45
|
+
# Exceptions are rescued and re-packaged as Acme exceptions.
|
46
|
+
class ErrorMiddleware < Faraday::Middleware
|
47
|
+
# Implements the Rack-alike Faraday::Middleware interface.
|
48
|
+
def call(env)
|
49
|
+
@app.call(env)
|
50
|
+
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
51
|
+
raise Acme::Client::Error::Timeout
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# AcmeMiddleware implements the Acme-protocol requirements for JWK requests.
|
56
|
+
class AcmeMiddleware < Faraday::Middleware
|
57
|
+
attr_reader :env, :response, :client
|
58
|
+
|
59
|
+
CONTENT_TYPE = 'application/jose+json'
|
60
|
+
|
61
|
+
def initialize(app, options)
|
62
|
+
super(app)
|
63
|
+
@client = options.fetch(:client)
|
64
|
+
@mode = options.fetch(:mode)
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(env)
|
68
|
+
@env = env
|
69
|
+
@env[:request_headers]['Content-Type'] = CONTENT_TYPE
|
70
|
+
|
71
|
+
if @env.method != :get
|
72
|
+
@env.body = client.jwk.jws(header: jws_header, payload: env.body)
|
73
|
+
end
|
74
|
+
|
75
|
+
@app.call(env).on_complete { |response_env| on_complete(response_env) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def on_complete(env)
|
79
|
+
@env = env
|
80
|
+
|
81
|
+
raise_on_not_found!
|
82
|
+
store_nonce
|
83
|
+
env.body = decode_body
|
84
|
+
env.response_headers['Link'] = decode_link_headers
|
85
|
+
|
86
|
+
return if env.success?
|
87
|
+
|
88
|
+
raise_on_error!
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def jws_header
|
94
|
+
headers = { nonce: pop_nonce, url: env.url.to_s }
|
95
|
+
headers[:kid] = client.kid if @mode == :kid
|
96
|
+
headers
|
97
|
+
end
|
98
|
+
|
99
|
+
def raise_on_not_found!
|
100
|
+
raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
|
101
|
+
end
|
102
|
+
|
103
|
+
def raise_on_error!
|
104
|
+
raise error_class, error_message
|
105
|
+
end
|
106
|
+
|
107
|
+
def error_message
|
108
|
+
if env.body.is_a? Hash
|
109
|
+
env.body['detail']
|
110
|
+
else
|
111
|
+
"Error message: #{env.body}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def error_class
|
116
|
+
Acme::Client::Error::ACME_ERRORS.fetch(error_name, Acme::Client::Error)
|
117
|
+
end
|
118
|
+
|
119
|
+
def error_name
|
120
|
+
return unless env.body.is_a?(Hash)
|
121
|
+
return unless env.body.key?('type')
|
122
|
+
env.body['type']
|
123
|
+
end
|
124
|
+
|
125
|
+
def decode_body
|
126
|
+
content_type = env.response_headers['Content-Type'].to_s
|
127
|
+
|
128
|
+
if content_type.start_with?('application/json', 'application/problem+json')
|
129
|
+
JSON.load(env.body)
|
130
|
+
else
|
131
|
+
env.body
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def decode_link_headers
|
136
|
+
return unless env.response_headers.key?('Link')
|
137
|
+
link_header = env.response_headers['Link']
|
138
|
+
Acme::Client::Util.decode_link_headers(link_header)
|
139
|
+
end
|
140
|
+
|
141
|
+
def store_nonce
|
142
|
+
nonce = env.response_headers['replay-nonce']
|
143
|
+
nonces << nonce if nonce
|
144
|
+
end
|
145
|
+
|
146
|
+
def pop_nonce
|
147
|
+
if nonces.empty?
|
148
|
+
get_nonce
|
149
|
+
end
|
150
|
+
|
151
|
+
nonces.pop
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_nonce
|
155
|
+
client.get_nonce
|
156
|
+
end
|
157
|
+
|
158
|
+
def nonces
|
159
|
+
client.nonces
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Acme::Client::JWK::HMAC < Acme::Client::JWK::Base
|
4
|
+
# Instantiate a new HMAC JWS.
|
5
|
+
#
|
6
|
+
# key - A string.
|
7
|
+
#
|
8
|
+
# Returns nothing.
|
9
|
+
def initialize(key)
|
10
|
+
@key = key
|
11
|
+
end
|
12
|
+
|
13
|
+
# Sign a message with the private key.
|
14
|
+
#
|
15
|
+
# message - A String message to sign.
|
16
|
+
#
|
17
|
+
# Returns a String signature.
|
18
|
+
def sign(message)
|
19
|
+
OpenSSL::HMAC.digest('SHA256', @key, message)
|
20
|
+
end
|
21
|
+
|
22
|
+
# The name of the algorithm as needed for the `alg` member of a JWS object.
|
23
|
+
#
|
24
|
+
# Returns a String.
|
25
|
+
def jwa_alg
|
26
|
+
# https://tools.ietf.org/html/rfc7518#section-3.1
|
27
|
+
# HMAC using SHA-256
|
28
|
+
'HS256'
|
29
|
+
end
|
30
|
+
end
|
data/lib/acme/client/jwk.rb
CHANGED
@@ -68,13 +68,8 @@ class Acme::Client::Resources::Directory
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def fetch_directory
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
configuration.adapter Faraday.default_adapter
|
75
|
-
end
|
76
|
-
connection.headers[:user_agent] = Acme::Client::USER_AGENT
|
77
|
-
response = connection.get(@url)
|
71
|
+
http_client = Acme::Client::HTTPClient.new_acme_connection(url: @directory, options: @connection_options, client: nil, mode: nil)
|
72
|
+
response = http_client.get(@url)
|
78
73
|
response.body
|
79
74
|
end
|
80
75
|
end
|
data/lib/acme/client/util.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
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
|
@@ -30,6 +32,4 @@ module Acme::Client::Util
|
|
30
32
|
raise ArgumentError, 'priv must be EC or RSA'
|
31
33
|
end
|
32
34
|
end
|
33
|
-
|
34
|
-
extend self
|
35
35
|
end
|
data/lib/acme/client/version.rb
CHANGED
data/lib/acme/client.rb
CHANGED
@@ -14,10 +14,10 @@ module Acme; end
|
|
14
14
|
class Acme::Client; end
|
15
15
|
|
16
16
|
require 'acme/client/version'
|
17
|
+
require 'acme/client/http_client'
|
17
18
|
require 'acme/client/certificate_request'
|
18
19
|
require 'acme/client/self_sign_certificate'
|
19
20
|
require 'acme/client/resources'
|
20
|
-
require 'acme/client/faraday_middleware'
|
21
21
|
require 'acme/client/jwk'
|
22
22
|
require 'acme/client/error'
|
23
23
|
require 'acme/client/util'
|
@@ -50,7 +50,8 @@ class Acme::Client
|
|
50
50
|
|
51
51
|
attr_reader :jwk, :nonces
|
52
52
|
|
53
|
-
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)
|
54
55
|
payload = {
|
55
56
|
contact: Array(contact)
|
56
57
|
}
|
@@ -59,7 +60,18 @@ class Acme::Client
|
|
59
60
|
payload[:termsOfServiceAgreed] = terms_of_service_agreed
|
60
61
|
end
|
61
62
|
|
62
|
-
|
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)
|
63
75
|
@kid = response.headers.fetch(:location)
|
64
76
|
|
65
77
|
if response.body.nil? || response.body.empty?
|
@@ -211,8 +223,8 @@ class Acme::Client
|
|
211
223
|
end
|
212
224
|
|
213
225
|
def get_nonce
|
214
|
-
|
215
|
-
response =
|
226
|
+
http_client = Acme::Client::HTTPClient.new_connection(url: endpoint_for(:new_nonce))
|
227
|
+
response = http_client.head(nil, nil)
|
216
228
|
nonces << response.headers['replay-nonce']
|
217
229
|
true
|
218
230
|
end
|
@@ -320,28 +332,12 @@ class Acme::Client
|
|
320
332
|
def connection_for(url:, mode:)
|
321
333
|
uri = URI(url)
|
322
334
|
endpoint = "#{uri.scheme}://#{uri.hostname}:#{uri.port}"
|
335
|
+
|
323
336
|
@connections ||= {}
|
324
337
|
@connections[mode] ||= {}
|
325
|
-
@connections[mode][endpoint] ||= new_acme_connection(
|
326
|
-
|
327
|
-
|
328
|
-
def new_acme_connection(endpoint:, mode:)
|
329
|
-
new_connection(endpoint: endpoint) do |configuration|
|
330
|
-
configuration.use Acme::Client::FaradayMiddleware, client: self, mode: mode
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
def new_connection(endpoint:)
|
335
|
-
Faraday.new(endpoint, **@connection_options) do |configuration|
|
336
|
-
if @bad_nonce_retry > 0
|
337
|
-
configuration.request(:retry,
|
338
|
-
max: @bad_nonce_retry,
|
339
|
-
methods: Faraday::Connection::METHODS,
|
340
|
-
exceptions: [Acme::Client::Error::BadNonce])
|
341
|
-
end
|
342
|
-
yield(configuration) if block_given?
|
343
|
-
configuration.adapter Faraday.default_adapter
|
344
|
-
end
|
338
|
+
@connections[mode][endpoint] ||= Acme::Client::HTTPClient.new_acme_connection(
|
339
|
+
url: URI(endpoint), mode: mode, client: self, options: @connection_options, bad_nonce_retry: @bad_nonce_retry
|
340
|
+
)
|
345
341
|
end
|
346
342
|
|
347
343
|
def fetch_chain(response, limit = 10)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acme-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Barbier
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -118,17 +118,23 @@ dependencies:
|
|
118
118
|
name: faraday-retry
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
120
120
|
requirements:
|
121
|
-
- - "
|
121
|
+
- - ">="
|
122
122
|
- !ruby/object:Gem::Version
|
123
123
|
version: '1.0'
|
124
|
+
- - "<"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 3.0.0
|
124
127
|
type: :runtime
|
125
128
|
prerelease: false
|
126
129
|
version_requirements: !ruby/object:Gem::Requirement
|
127
130
|
requirements:
|
128
|
-
- - "
|
131
|
+
- - ">="
|
129
132
|
- !ruby/object:Gem::Version
|
130
133
|
version: '1.0'
|
131
|
-
|
134
|
+
- - "<"
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 3.0.0
|
137
|
+
description:
|
132
138
|
email:
|
133
139
|
- unixcharles@gmail.com
|
134
140
|
executables: []
|
@@ -155,10 +161,11 @@ files:
|
|
155
161
|
- lib/acme/client/certificate_request/ec_key_patch.rb
|
156
162
|
- lib/acme/client/chain_identifier.rb
|
157
163
|
- lib/acme/client/error.rb
|
158
|
-
- lib/acme/client/
|
164
|
+
- lib/acme/client/http_client.rb
|
159
165
|
- lib/acme/client/jwk.rb
|
160
166
|
- lib/acme/client/jwk/base.rb
|
161
167
|
- lib/acme/client/jwk/ecdsa.rb
|
168
|
+
- lib/acme/client/jwk/hmac.rb
|
162
169
|
- lib/acme/client/jwk/rsa.rb
|
163
170
|
- lib/acme/client/resources.rb
|
164
171
|
- lib/acme/client/resources/account.rb
|
@@ -177,7 +184,7 @@ homepage: http://github.com/unixcharles/acme-client
|
|
177
184
|
licenses:
|
178
185
|
- MIT
|
179
186
|
metadata: {}
|
180
|
-
post_install_message:
|
187
|
+
post_install_message:
|
181
188
|
rdoc_options: []
|
182
189
|
require_paths:
|
183
190
|
- lib
|
@@ -192,8 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
199
|
- !ruby/object:Gem::Version
|
193
200
|
version: '0'
|
194
201
|
requirements: []
|
195
|
-
rubygems_version: 3.
|
196
|
-
signing_key:
|
202
|
+
rubygems_version: 3.0.3.1
|
203
|
+
signing_key:
|
197
204
|
specification_version: 4
|
198
205
|
summary: Client for the ACME protocol.
|
199
206
|
test_files: []
|
@@ -1,111 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Acme::Client::FaradayMiddleware < Faraday::Middleware
|
4
|
-
attr_reader :env, :response, :client
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'application/jose+json'
|
7
|
-
|
8
|
-
def initialize(app, options)
|
9
|
-
super(app)
|
10
|
-
@client = options.fetch(:client)
|
11
|
-
@mode = options.fetch(:mode)
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(env)
|
15
|
-
@env = env
|
16
|
-
@env[:request_headers]['User-Agent'] = Acme::Client::USER_AGENT
|
17
|
-
@env[:request_headers]['Content-Type'] = CONTENT_TYPE
|
18
|
-
|
19
|
-
if @env.method != :get
|
20
|
-
@env.body = client.jwk.jws(header: jws_header, payload: env.body)
|
21
|
-
end
|
22
|
-
|
23
|
-
@app.call(env).on_complete { |response_env| on_complete(response_env) }
|
24
|
-
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
25
|
-
raise Acme::Client::Error::Timeout
|
26
|
-
end
|
27
|
-
|
28
|
-
def on_complete(env)
|
29
|
-
@env = env
|
30
|
-
|
31
|
-
raise_on_not_found!
|
32
|
-
store_nonce
|
33
|
-
env.body = decode_body
|
34
|
-
env.response_headers['Link'] = decode_link_headers
|
35
|
-
|
36
|
-
return if env.success?
|
37
|
-
|
38
|
-
raise_on_error!
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def jws_header
|
44
|
-
headers = { nonce: pop_nonce, url: env.url.to_s }
|
45
|
-
headers[:kid] = client.kid if @mode == :kid
|
46
|
-
headers
|
47
|
-
end
|
48
|
-
|
49
|
-
def raise_on_not_found!
|
50
|
-
raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
|
51
|
-
end
|
52
|
-
|
53
|
-
def raise_on_error!
|
54
|
-
raise error_class, error_message
|
55
|
-
end
|
56
|
-
|
57
|
-
def error_message
|
58
|
-
if env.body.is_a? Hash
|
59
|
-
env.body['detail']
|
60
|
-
else
|
61
|
-
"Error message: #{env.body}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def error_class
|
66
|
-
Acme::Client::Error::ACME_ERRORS.fetch(error_name, Acme::Client::Error)
|
67
|
-
end
|
68
|
-
|
69
|
-
def error_name
|
70
|
-
return unless env.body.is_a?(Hash)
|
71
|
-
return unless env.body.key?('type')
|
72
|
-
env.body['type']
|
73
|
-
end
|
74
|
-
|
75
|
-
def decode_body
|
76
|
-
content_type = env.response_headers['Content-Type'].to_s
|
77
|
-
|
78
|
-
if content_type.start_with?('application/json', 'application/problem+json')
|
79
|
-
JSON.load(env.body)
|
80
|
-
else
|
81
|
-
env.body
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def decode_link_headers
|
86
|
-
return unless env.response_headers.key?('Link')
|
87
|
-
link_header = env.response_headers['Link']
|
88
|
-
Acme::Client::Util.decode_link_headers(link_header)
|
89
|
-
end
|
90
|
-
|
91
|
-
def store_nonce
|
92
|
-
nonce = env.response_headers['replay-nonce']
|
93
|
-
nonces << nonce if nonce
|
94
|
-
end
|
95
|
-
|
96
|
-
def pop_nonce
|
97
|
-
if nonces.empty?
|
98
|
-
get_nonce
|
99
|
-
end
|
100
|
-
|
101
|
-
nonces.pop
|
102
|
-
end
|
103
|
-
|
104
|
-
def get_nonce
|
105
|
-
client.get_nonce
|
106
|
-
end
|
107
|
-
|
108
|
-
def nonces
|
109
|
-
client.nonces
|
110
|
-
end
|
111
|
-
end
|