acme-client 2.0.13 → 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 +23 -1
- data/Gemfile +0 -1
- data/README.md +4 -7
- data/Rakefile +1 -4
- data/acme-client.gemspec +2 -3
- data/bin/generate_keystash +9 -0
- data/lib/acme/client/http_client.rb +162 -0
- data/lib/acme/client/jwk/ecdsa.rb +4 -4
- data/lib/acme/client/resources/authorization.rb +1 -1
- data/lib/acme/client/resources/directory.rb +9 -28
- data/lib/acme/client/util.rb +2 -2
- data/lib/acme/client/version.rb +1 -1
- data/lib/acme/client.rb +31 -41
- metadata +9 -27
- data/.github/workflows/rubocop.yml +0 -23
- data/.github/workflows/test.yml +0 -26
- data/.gitignore +0 -12
- data/.rspec +0 -3
- data/.rubocop.yml +0 -134
- 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: 88c2953c9fcfd9a7f7825b4c69b2cf0ff86befb504914e6f78b03f6fdaab052b
|
4
|
+
data.tar.gz: bddedcd46dc0b2d1224a7d409916668aa31bfad3c6576a0d09376257c654f434
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 999d2d254b29f3fdefe3af90333091c5a034d5aa3b3c3274bfe1c7787fe9c14723bf288ba0d7e4dce5fa3aad3352ecd0ef49bd60c93f4b51ab7e5d51017a9a1e
|
7
|
+
data.tar.gz: 0d16f423760bd8f714ce94de1767201dd8c842ae3fca2ec8d93c7364ea15c61f24f9c66c4175e2d7091cf263467caeb4a5e049f996c92173d64269ba5c11629b
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,26 @@
|
|
1
|
-
## `2.0.
|
1
|
+
## `2.0.18`
|
2
|
+
|
3
|
+
* Fix an issue public key encoding. `OpenSSL::BN` cause keys with leading zero to fail.
|
4
|
+
|
5
|
+
## `2.0.17`
|
6
|
+
|
7
|
+
* Fix bug where depending on call order `jws` get generated with the wrong `kid`
|
8
|
+
|
9
|
+
## `2.0.16`
|
10
|
+
|
11
|
+
* Refactor Directory
|
12
|
+
* Fix an issue where the client would crash when ACME provider return nonce for directory endpoint
|
13
|
+
|
14
|
+
## `2.0.15`
|
15
|
+
|
16
|
+
* Also pass connection_options to Faraday for Client#get_nonce
|
17
|
+
|
18
|
+
|
19
|
+
## `2.0.14`
|
20
|
+
|
21
|
+
* Fix Faraday HTTP exceptions leaking out, always raise `Acme::Client::Error` instead
|
22
|
+
|
23
|
+
## `2.0.13`
|
2
24
|
|
3
25
|
* Add support for External Account Binding
|
4
26
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
# Acme::Client
|
2
2
|
|
3
|
-
`acme-client` is a client implementation of the
|
3
|
+
`acme-client` is a client implementation of the ACME / [RFC 8555](https://tools.ietf.org/html/rfc8555) protocol in Ruby.
|
4
4
|
|
5
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.
|
6
6
|
|
7
7
|
ACME is part of the [Letsencrypt](https://letsencrypt.org/) project, which goal is to provide free SSL/TLS certificates with automation of the acquiring and renewal process.
|
8
8
|
|
9
|
-
You can find ACMEv1 compatible client in the [acme-v1](https://github.com/unixcharles/acme-client/tree/acme-v1) branch.
|
10
|
-
|
11
9
|
## Installation
|
12
10
|
|
13
11
|
Via RubyGems:
|
@@ -207,8 +205,7 @@ order.certificate # => PEM-formatted certificate
|
|
207
205
|
|
208
206
|
### Ordering an alternative certificate
|
209
207
|
|
210
|
-
|
211
|
-
For example, to download the cross-signed certificate after January 11, 2021, call `Order#certificate` as follows:
|
208
|
+
The provider may provide alternate certificate with different certificate chain. You can specify the required chain and the client will automatically download alternate certificate and match the chain by name.
|
212
209
|
|
213
210
|
```ruby
|
214
211
|
begin
|
@@ -244,12 +241,12 @@ To change the key used for an account you can call `#account_key_change` with th
|
|
244
241
|
```ruby
|
245
242
|
require 'openssl'
|
246
243
|
new_private_key = OpenSSL::PKey::RSA.new(4096)
|
247
|
-
client.account_key_change(
|
244
|
+
client.account_key_change(new_private_key: new_private_key)
|
248
245
|
```
|
249
246
|
|
250
247
|
## Requirements
|
251
248
|
|
252
|
-
Ruby >=
|
249
|
+
Ruby >= 3.0
|
253
250
|
|
254
251
|
## Development
|
255
252
|
|
data/Rakefile
CHANGED
data/acme-client.gemspec
CHANGED
@@ -11,17 +11,16 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.homepage = 'http://github.com/unixcharles/acme-client'
|
12
12
|
spec.license = 'MIT'
|
13
13
|
|
14
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) || f.start_with?('.') }
|
15
15
|
spec.require_paths = ['lib']
|
16
16
|
|
17
17
|
spec.required_ruby_version = '>= 2.3.0'
|
18
18
|
|
19
|
-
spec.add_development_dependency 'bundler', '>= 1.17.3'
|
20
19
|
spec.add_development_dependency 'rake', '~> 13.0'
|
21
20
|
spec.add_development_dependency 'rspec', '~> 3.9'
|
22
21
|
spec.add_development_dependency 'vcr', '~> 2.9'
|
23
22
|
spec.add_development_dependency 'webmock', '~> 3.8'
|
24
|
-
spec.add_development_dependency 'webrick'
|
23
|
+
spec.add_development_dependency 'webrick', '~> 1.7'
|
25
24
|
|
26
25
|
spec.add_runtime_dependency 'faraday', '>= 1.0', '< 3.0.0'
|
27
26
|
spec.add_runtime_dependency 'faraday-retry', '>= 1.0', '< 3.0.0'
|
@@ -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
|
@@ -50,8 +50,8 @@ class Acme::Client::JWK::ECDSA < Acme::Client::JWK::Base
|
|
50
50
|
{
|
51
51
|
crv: @curve_params[:jwa_crv],
|
52
52
|
kty: 'EC',
|
53
|
-
x: Acme::Client::Util.urlsafe_base64(coordinates[:x]
|
54
|
-
y: Acme::Client::Util.urlsafe_base64(coordinates[:y]
|
53
|
+
x: Acme::Client::Util.urlsafe_base64(coordinates[:x]),
|
54
|
+
y: Acme::Client::Util.urlsafe_base64(coordinates[:y])
|
55
55
|
}
|
56
56
|
end
|
57
57
|
|
@@ -92,8 +92,8 @@ class Acme::Client::JWK::ECDSA < Acme::Client::JWK::Base
|
|
92
92
|
hex_y = hex[2 + data_len / 2, data_len / 2]
|
93
93
|
|
94
94
|
{
|
95
|
-
x:
|
96
|
-
y:
|
95
|
+
x: [hex_x].pack('H*'),
|
96
|
+
y: [hex_y].pack('H*')
|
97
97
|
}
|
98
98
|
end
|
99
99
|
end
|
@@ -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)
|
@@ -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,36 +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
|
-
|
63
|
-
end
|
64
|
-
result
|
65
|
-
rescue JSON::ParserError => exception
|
66
|
-
raise Acme::Client::Error::InvalidDirectory,
|
67
|
-
"Invalid directory url\n#{@directory} did not return a valid directory\n#{exception.inspect}"
|
68
|
-
end
|
69
|
-
|
70
|
-
def fetch_directory
|
71
|
-
connection = Faraday.new(url: @directory, **@connection_options) do |configuration|
|
72
|
-
configuration.use Acme::Client::FaradayMiddleware, client: nil, mode: nil
|
73
|
-
|
74
|
-
configuration.adapter Faraday.default_adapter
|
58
|
+
@directory[key] = URI(directory[entry]) if directory[entry]
|
75
59
|
end
|
76
|
-
connection.headers[:user_agent] = Acme::Client::USER_AGENT
|
77
|
-
response = connection.get(@url)
|
78
|
-
response.body
|
79
60
|
end
|
80
61
|
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'
|
@@ -44,7 +44,7 @@ class Acme::Client
|
|
44
44
|
|
45
45
|
@kid, @connection_options = kid, connection_options
|
46
46
|
@bad_nonce_retry = bad_nonce_retry
|
47
|
-
@
|
47
|
+
@directory_url = URI(directory)
|
48
48
|
@nonces ||= []
|
49
49
|
end
|
50
50
|
|
@@ -223,34 +223,50 @@ class Acme::Client
|
|
223
223
|
end
|
224
224
|
|
225
225
|
def get_nonce
|
226
|
-
|
227
|
-
response =
|
226
|
+
http_client = Acme::Client::HTTPClient.new_connection(url: endpoint_for(:new_nonce), options: @connection_options)
|
227
|
+
response = http_client.head(nil, nil)
|
228
228
|
nonces << response.headers['replay-nonce']
|
229
229
|
true
|
230
230
|
end
|
231
231
|
|
232
|
+
def directory
|
233
|
+
@directory ||= load_directory
|
234
|
+
end
|
235
|
+
|
232
236
|
def meta
|
233
|
-
|
237
|
+
directory.meta
|
234
238
|
end
|
235
239
|
|
236
240
|
def terms_of_service
|
237
|
-
|
241
|
+
directory.terms_of_service
|
238
242
|
end
|
239
243
|
|
240
244
|
def website
|
241
|
-
|
245
|
+
directory.website
|
242
246
|
end
|
243
247
|
|
244
248
|
def caa_identities
|
245
|
-
|
249
|
+
directory.caa_identities
|
246
250
|
end
|
247
251
|
|
248
252
|
def external_account_required
|
249
|
-
|
253
|
+
directory.external_account_required
|
250
254
|
end
|
251
255
|
|
252
256
|
private
|
253
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
|
+
|
254
270
|
def prepare_order_identifiers(identifiers)
|
255
271
|
if identifiers.is_a?(Hash)
|
256
272
|
[identifiers]
|
@@ -316,7 +332,7 @@ class Acme::Client
|
|
316
332
|
connection.post(url, nil)
|
317
333
|
end
|
318
334
|
|
319
|
-
def get(url, mode: :
|
335
|
+
def get(url, mode: :get)
|
320
336
|
connection = connection_for(url: url, mode: mode)
|
321
337
|
connection.get(url)
|
322
338
|
end
|
@@ -332,41 +348,15 @@ class Acme::Client
|
|
332
348
|
def connection_for(url:, mode:)
|
333
349
|
uri = URI(url)
|
334
350
|
endpoint = "#{uri.scheme}://#{uri.hostname}:#{uri.port}"
|
351
|
+
|
335
352
|
@connections ||= {}
|
336
353
|
@connections[mode] ||= {}
|
337
|
-
@connections[mode][endpoint] ||= new_acme_connection(
|
338
|
-
|
339
|
-
|
340
|
-
def new_acme_connection(endpoint:, mode:)
|
341
|
-
new_connection(endpoint: endpoint) do |configuration|
|
342
|
-
configuration.use Acme::Client::FaradayMiddleware, client: self, mode: mode
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
def new_connection(endpoint:)
|
347
|
-
Faraday.new(endpoint, **@connection_options) do |configuration|
|
348
|
-
if @bad_nonce_retry > 0
|
349
|
-
configuration.request(:retry,
|
350
|
-
max: @bad_nonce_retry,
|
351
|
-
methods: Faraday::Connection::METHODS,
|
352
|
-
exceptions: [Acme::Client::Error::BadNonce])
|
353
|
-
end
|
354
|
-
yield(configuration) if block_given?
|
355
|
-
configuration.adapter Faraday.default_adapter
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
def fetch_chain(response, limit = 10)
|
360
|
-
links = response.headers['link']
|
361
|
-
if limit.zero? || links.nil? || links['up'].nil?
|
362
|
-
[]
|
363
|
-
else
|
364
|
-
issuer = get(links['up'])
|
365
|
-
[OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)]
|
366
|
-
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
|
+
)
|
367
357
|
end
|
368
358
|
|
369
359
|
def endpoint_for(key)
|
370
|
-
|
360
|
+
directory.endpoint_for(key)
|
371
361
|
end
|
372
362
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
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.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Barbier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.17.3
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.17.3
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rake
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,16 +70,16 @@ dependencies:
|
|
84
70
|
name: webrick
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
|
-
- - "
|
73
|
+
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
75
|
+
version: '1.7'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
|
-
- - "
|
80
|
+
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
82
|
+
version: '1.7'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: faraday
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,11 +127,6 @@ executables: []
|
|
141
127
|
extensions: []
|
142
128
|
extra_rdoc_files: []
|
143
129
|
files:
|
144
|
-
- ".github/workflows/rubocop.yml"
|
145
|
-
- ".github/workflows/test.yml"
|
146
|
-
- ".gitignore"
|
147
|
-
- ".rspec"
|
148
|
-
- ".rubocop.yml"
|
149
130
|
- CHANGELOG.md
|
150
131
|
- Gemfile
|
151
132
|
- LICENSE.txt
|
@@ -153,6 +134,7 @@ files:
|
|
153
134
|
- Rakefile
|
154
135
|
- acme-client.gemspec
|
155
136
|
- bin/console
|
137
|
+
- bin/generate_keystash
|
156
138
|
- bin/release
|
157
139
|
- bin/setup
|
158
140
|
- lib/acme-client.rb
|
@@ -161,7 +143,7 @@ files:
|
|
161
143
|
- lib/acme/client/certificate_request/ec_key_patch.rb
|
162
144
|
- lib/acme/client/chain_identifier.rb
|
163
145
|
- lib/acme/client/error.rb
|
164
|
-
- lib/acme/client/
|
146
|
+
- lib/acme/client/http_client.rb
|
165
147
|
- lib/acme/client/jwk.rb
|
166
148
|
- lib/acme/client/jwk/base.rb
|
167
149
|
- lib/acme/client/jwk/ecdsa.rb
|
@@ -199,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
181
|
- !ruby/object:Gem::Version
|
200
182
|
version: '0'
|
201
183
|
requirements: []
|
202
|
-
rubygems_version: 3.4.
|
184
|
+
rubygems_version: 3.4.20
|
203
185
|
signing_key:
|
204
186
|
specification_version: 4
|
205
187
|
summary: Client for the ACME protocol.
|
@@ -1,23 +0,0 @@
|
|
1
|
-
name: Lint
|
2
|
-
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches: [ master ]
|
6
|
-
pull_request:
|
7
|
-
branches: [ master ]
|
8
|
-
|
9
|
-
jobs:
|
10
|
-
rubocop:
|
11
|
-
runs-on: ubuntu-latest
|
12
|
-
strategy:
|
13
|
-
matrix:
|
14
|
-
ruby-version: ['3.0']
|
15
|
-
steps:
|
16
|
-
- uses: actions/checkout@v2
|
17
|
-
- name: Set up Ruby
|
18
|
-
uses: ruby/setup-ruby@v1
|
19
|
-
with:
|
20
|
-
ruby-version: ${{ matrix.ruby-version }}
|
21
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
22
|
-
- name: Run tests
|
23
|
-
run: bundle exec rake rubocop
|
data/.github/workflows/test.yml
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
name: CI
|
2
|
-
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches: [ master ]
|
6
|
-
pull_request:
|
7
|
-
branches: [ master ]
|
8
|
-
|
9
|
-
jobs:
|
10
|
-
test:
|
11
|
-
runs-on: ubuntu-latest
|
12
|
-
strategy:
|
13
|
-
matrix:
|
14
|
-
ruby-version: ['2.7', '3.0', '3.1', '3.2']
|
15
|
-
faraday-version: ['~> 1.10', '~> 2.7']
|
16
|
-
env:
|
17
|
-
FARADAY_VERSION: ${{ matrix.faraday-version }}
|
18
|
-
steps:
|
19
|
-
- uses: actions/checkout@v2
|
20
|
-
- name: Set up Ruby
|
21
|
-
uses: ruby/setup-ruby@v1
|
22
|
-
with:
|
23
|
-
ruby-version: ${{ matrix.ruby-version }}
|
24
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
25
|
-
- name: Run tests
|
26
|
-
run: bundle exec rake spec
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.rubocop.yml
DELETED
@@ -1,134 +0,0 @@
|
|
1
|
-
AllCops:
|
2
|
-
TargetRubyVersion: 2.1
|
3
|
-
Exclude:
|
4
|
-
- 'bin/*'
|
5
|
-
- 'vendor/**/*'
|
6
|
-
|
7
|
-
Rails:
|
8
|
-
Enabled: false
|
9
|
-
|
10
|
-
Layout/AlignParameters:
|
11
|
-
EnforcedStyle: with_fixed_indentation
|
12
|
-
|
13
|
-
Layout/ElseAlignment:
|
14
|
-
Enabled: false
|
15
|
-
|
16
|
-
Layout/FirstParameterIndentation:
|
17
|
-
EnforcedStyle: consistent
|
18
|
-
|
19
|
-
Layout/IndentationWidth:
|
20
|
-
Enabled: false
|
21
|
-
|
22
|
-
Layout/MultilineOperationIndentation:
|
23
|
-
Enabled: false
|
24
|
-
|
25
|
-
Layout/SpaceInsideBlockBraces:
|
26
|
-
Enabled: false
|
27
|
-
|
28
|
-
Lint/AmbiguousOperator:
|
29
|
-
Enabled: false
|
30
|
-
|
31
|
-
Lint/AssignmentInCondition:
|
32
|
-
Enabled: false
|
33
|
-
|
34
|
-
Lint/EndAlignment:
|
35
|
-
Enabled: false
|
36
|
-
|
37
|
-
Lint/UnusedMethodArgument:
|
38
|
-
AllowUnusedKeywordArguments: true
|
39
|
-
|
40
|
-
Metrics/AbcSize:
|
41
|
-
Enabled: false
|
42
|
-
|
43
|
-
Metrics/BlockLength:
|
44
|
-
Enabled: false
|
45
|
-
|
46
|
-
Metrics/ClassLength:
|
47
|
-
Enabled: false
|
48
|
-
|
49
|
-
Metrics/CyclomaticComplexity:
|
50
|
-
Enabled: false
|
51
|
-
|
52
|
-
Metrics/LineLength:
|
53
|
-
Max: 140
|
54
|
-
|
55
|
-
Metrics/MethodLength:
|
56
|
-
Max: 15
|
57
|
-
Enabled: false
|
58
|
-
|
59
|
-
Metrics/ParameterLists:
|
60
|
-
Max: 5
|
61
|
-
CountKeywordArgs: false
|
62
|
-
|
63
|
-
Metrics/PerceivedComplexity:
|
64
|
-
Enabled: false
|
65
|
-
|
66
|
-
Security/JSONLoad:
|
67
|
-
Enabled: false
|
68
|
-
|
69
|
-
Style/AccessorMethodName:
|
70
|
-
Enabled: false
|
71
|
-
|
72
|
-
Style/Alias:
|
73
|
-
Enabled: false
|
74
|
-
|
75
|
-
Style/BlockDelimiters:
|
76
|
-
EnforcedStyle: semantic
|
77
|
-
|
78
|
-
Style/ClassAndModuleChildren:
|
79
|
-
Enabled: false
|
80
|
-
|
81
|
-
Style/Documentation:
|
82
|
-
Enabled: false
|
83
|
-
|
84
|
-
Style/DoubleNegation:
|
85
|
-
Enabled: false
|
86
|
-
|
87
|
-
Style/FileName:
|
88
|
-
Exclude:
|
89
|
-
- 'lib/acme-client.rb'
|
90
|
-
|
91
|
-
Style/GlobalVars:
|
92
|
-
Enabled: false
|
93
|
-
|
94
|
-
Style/GuardClause:
|
95
|
-
Enabled: false
|
96
|
-
|
97
|
-
Style/IfUnlessModifier:
|
98
|
-
Enabled: false
|
99
|
-
|
100
|
-
Style/Lambda:
|
101
|
-
Enabled: false
|
102
|
-
|
103
|
-
Style/ModuleFunction:
|
104
|
-
Enabled: false
|
105
|
-
|
106
|
-
Style/MultilineBlockChain:
|
107
|
-
Enabled: false
|
108
|
-
|
109
|
-
Style/MultipleComparison:
|
110
|
-
Enabled: false
|
111
|
-
|
112
|
-
Style/MutableConstant:
|
113
|
-
Enabled: false
|
114
|
-
|
115
|
-
Style/ParallelAssignment:
|
116
|
-
Enabled: false
|
117
|
-
|
118
|
-
Style/PercentLiteralDelimiters:
|
119
|
-
Enabled: false
|
120
|
-
|
121
|
-
Style/SignalException:
|
122
|
-
EnforcedStyle: only_raise
|
123
|
-
|
124
|
-
Style/SymbolArray:
|
125
|
-
Enabled: false
|
126
|
-
|
127
|
-
Style/StringLiterals:
|
128
|
-
Enabled: single_quotes
|
129
|
-
|
130
|
-
Style/TrailingCommaInArguments:
|
131
|
-
Enabled: false
|
132
|
-
|
133
|
-
Style/TrivialAccessors:
|
134
|
-
AllowPredicates: true
|
@@ -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
|