acme-client 2.0.5 → 2.0.9
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/rubocop.yml +23 -0
- data/.github/workflows/test.yml +26 -0
- data/.rubocop.yml +51 -56
- data/CHANGELOG.md +21 -0
- data/Gemfile +5 -0
- data/README.md +48 -17
- data/acme-client.gemspec +11 -6
- data/lib/acme/client/chain_identifier.rb +27 -0
- data/lib/acme/client/error.rb +1 -0
- data/lib/acme/client/faraday_middleware.rb +4 -12
- data/lib/acme/client/jwk/ecdsa.rb +3 -1
- data/lib/acme/client/resources/account.rb +1 -1
- data/lib/acme/client/resources/authorization.rb +1 -1
- data/lib/acme/client/resources/challenges/base.rb +1 -1
- data/lib/acme/client/resources/directory.rb +6 -2
- data/lib/acme/client/resources/order.rb +3 -3
- data/lib/acme/client/util.rb +11 -0
- data/lib/acme/client/version.rb +1 -1
- data/lib/acme/client.rb +40 -2
- metadata +36 -38
- data/.travis.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1fcaf1206f38a9ded860ad3685de94d3c2743a04222d63a6d5a14ca35146daa
|
4
|
+
data.tar.gz: 766a3bf59e3877a3a4ab457e369f9d09626204afacc8e1b8cb23015cde9ccf12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 814205156a08f49d3481545f35306f63e1f342267f126edc528b22c78ce477b4696fd4a9f401a501bb8ffe33c57f42e8e6826de830e5321289555972d841bcb3
|
7
|
+
data.tar.gz: 493a4a2ebe8803b3949797bd74061d500bb66e7a79c5d62e9dfb98af7b995a46d3a2847c88d14ad7585f8b25567ccdabdf5bdaa25eafdb045a1f3aaed7b05f13
|
@@ -0,0 +1,23 @@
|
|
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
|
@@ -0,0 +1,26 @@
|
|
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.6', '2.7', '3.0']
|
15
|
+
faraday-version: ['~> 0.17.3', '~> 1.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/.rubocop.yml
CHANGED
@@ -7,133 +7,128 @@ AllCops:
|
|
7
7
|
Rails:
|
8
8
|
Enabled: false
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
- 'lib/acme-client.rb'
|
10
|
+
Layout/AlignParameters:
|
11
|
+
EnforcedStyle: with_fixed_indentation
|
13
12
|
|
14
|
-
|
13
|
+
Layout/ElseAlignment:
|
15
14
|
Enabled: false
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
Layout/FirstParameterIndentation:
|
17
|
+
EnforcedStyle: consistent
|
19
18
|
|
20
|
-
|
19
|
+
Layout/IndentationWidth:
|
21
20
|
Enabled: false
|
22
21
|
|
23
22
|
Layout/MultilineOperationIndentation:
|
24
23
|
Enabled: false
|
25
24
|
|
26
|
-
|
27
|
-
EnforcedStyle: only_raise
|
28
|
-
|
29
|
-
Layout/AlignParameters:
|
30
|
-
EnforcedStyle: with_fixed_indentation
|
31
|
-
|
32
|
-
Layout/ElseAlignment:
|
25
|
+
Layout/SpaceInsideBlockBraces:
|
33
26
|
Enabled: false
|
34
27
|
|
35
|
-
|
28
|
+
Lint/AmbiguousOperator:
|
36
29
|
Enabled: false
|
37
30
|
|
38
|
-
|
31
|
+
Lint/AssignmentInCondition:
|
39
32
|
Enabled: false
|
40
33
|
|
41
|
-
|
34
|
+
Lint/EndAlignment:
|
42
35
|
Enabled: false
|
43
36
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
Style/TrailingCommaInArguments:
|
48
|
-
Enabled: false
|
37
|
+
Lint/UnusedMethodArgument:
|
38
|
+
AllowUnusedKeywordArguments: true
|
49
39
|
|
50
|
-
|
40
|
+
Metrics/AbcSize:
|
51
41
|
Enabled: false
|
52
42
|
|
53
43
|
Metrics/BlockLength:
|
54
44
|
Enabled: false
|
55
45
|
|
56
|
-
|
46
|
+
Metrics/ClassLength:
|
57
47
|
Enabled: false
|
58
48
|
|
59
|
-
|
60
|
-
Enabled:
|
49
|
+
Metrics/CyclomaticComplexity:
|
50
|
+
Enabled: false
|
61
51
|
|
62
52
|
Metrics/LineLength:
|
63
53
|
Max: 140
|
64
54
|
|
55
|
+
Metrics/MethodLength:
|
56
|
+
Max: 15
|
57
|
+
Enabled: false
|
58
|
+
|
65
59
|
Metrics/ParameterLists:
|
66
60
|
Max: 5
|
67
61
|
CountKeywordArgs: false
|
68
62
|
|
69
|
-
|
63
|
+
Metrics/PerceivedComplexity:
|
70
64
|
Enabled: false
|
71
65
|
|
72
|
-
|
66
|
+
Security/JSONLoad:
|
73
67
|
Enabled: false
|
74
68
|
|
75
|
-
Style/
|
69
|
+
Style/AccessorMethodName:
|
76
70
|
Enabled: false
|
77
71
|
|
78
|
-
Style/
|
79
|
-
|
80
|
-
|
81
|
-
Lint/UnusedMethodArgument:
|
82
|
-
AllowUnusedKeywordArguments: true
|
72
|
+
Style/Alias:
|
73
|
+
Enabled: false
|
83
74
|
|
84
|
-
|
85
|
-
|
75
|
+
Style/BlockDelimiters:
|
76
|
+
EnforcedStyle: semantic
|
86
77
|
|
87
|
-
Style/
|
78
|
+
Style/ClassAndModuleChildren:
|
88
79
|
Enabled: false
|
89
80
|
|
90
|
-
Style/
|
81
|
+
Style/Documentation:
|
91
82
|
Enabled: false
|
92
83
|
|
93
|
-
Style/
|
84
|
+
Style/DoubleNegation:
|
94
85
|
Enabled: false
|
95
86
|
|
96
|
-
Style/
|
97
|
-
|
87
|
+
Style/FileName:
|
88
|
+
Exclude:
|
89
|
+
- 'lib/acme-client.rb'
|
98
90
|
|
99
|
-
Style/
|
91
|
+
Style/GlobalVars:
|
100
92
|
Enabled: false
|
101
93
|
|
102
94
|
Style/GuardClause:
|
103
95
|
Enabled: false
|
104
96
|
|
105
|
-
Style/
|
97
|
+
Style/IfUnlessModifier:
|
106
98
|
Enabled: false
|
107
99
|
|
108
|
-
|
100
|
+
Style/Lambda:
|
109
101
|
Enabled: false
|
110
102
|
|
111
|
-
|
103
|
+
Style/ModuleFunction:
|
112
104
|
Enabled: false
|
113
105
|
|
114
|
-
|
106
|
+
Style/MultilineBlockChain:
|
115
107
|
Enabled: false
|
116
108
|
|
117
|
-
|
109
|
+
Style/MultipleComparison:
|
118
110
|
Enabled: false
|
119
111
|
|
120
|
-
|
112
|
+
Style/MutableConstant:
|
121
113
|
Enabled: false
|
122
114
|
|
123
|
-
|
115
|
+
Style/ParallelAssignment:
|
124
116
|
Enabled: false
|
125
117
|
|
126
|
-
Style/
|
118
|
+
Style/PercentLiteralDelimiters:
|
127
119
|
Enabled: false
|
128
120
|
|
129
|
-
Style/
|
130
|
-
|
121
|
+
Style/SignalException:
|
122
|
+
EnforcedStyle: only_raise
|
131
123
|
|
132
|
-
Style/
|
124
|
+
Style/SymbolArray:
|
133
125
|
Enabled: false
|
134
126
|
|
135
|
-
|
136
|
-
Enabled:
|
127
|
+
Style/StringLiterals:
|
128
|
+
Enabled: single_quotes
|
137
129
|
|
138
|
-
Style/
|
130
|
+
Style/TrailingCommaInArguments:
|
139
131
|
Enabled: false
|
132
|
+
|
133
|
+
Style/TrivialAccessors:
|
134
|
+
AllowPredicates: true
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## `2.0.9`
|
2
|
+
|
3
|
+
* Support for Ruby 3.0 and Faraday 0.17.x
|
4
|
+
* Raise when directory is rate limited
|
5
|
+
|
6
|
+
## `2.0.8`
|
7
|
+
|
8
|
+
* Add support for the keyChange endpoint
|
9
|
+
|
10
|
+
https://tools.ietf.org/html/rfc8555#section-7.3.5
|
11
|
+
|
12
|
+
|
13
|
+
## `2.0.7`
|
14
|
+
|
15
|
+
* Add support for alternate certificate chain
|
16
|
+
* Change `Link` headers parsing to return array of value. This add support multiple entries at the same `rel`
|
17
|
+
|
18
|
+
## `2.0.6`
|
19
|
+
|
20
|
+
* Allow Faraday up to `< 2.0`
|
21
|
+
|
1
22
|
## `2.0.5`
|
2
23
|
|
3
24
|
* Use post-as-get
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -23,17 +23,26 @@ gem 'acme-client'
|
|
23
23
|
```
|
24
24
|
|
25
25
|
## Usage
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
- [Acme::Client](#acmeclient)
|
27
|
+
- [Installation](#installation)
|
28
|
+
- [Usage](#usage)
|
29
|
+
- [Setting up a client](#setting-up-a-client)
|
30
|
+
- [Account management](#account-management)
|
31
|
+
- [Obtaining a certificate](#obtaining-a-certificate)
|
32
|
+
- [Ordering a certificate](#ordering-a-certificate)
|
33
|
+
- [Preparing for HTTP challenge](#preparing-for-http-challenge)
|
34
|
+
- [Preparing for DNS challenge](#preparing-for-dns-challenge)
|
35
|
+
- [Requesting a challenge verification](#requesting-a-challenge-verification)
|
36
|
+
- [Downloading a certificate](#downloading-a-certificate)
|
37
|
+
- [Ordering an alternative certificate](#ordering-an-alternative-certificate)
|
38
|
+
- [Extra](#extra)
|
39
|
+
- [Certificate revokation](#certificate-revokation)
|
40
|
+
- [Certificate renewal](#certificate-renewal)
|
41
|
+
- [Not implemented](#not-implemented)
|
42
|
+
- [Requirements](#requirements)
|
43
|
+
- [Development](#development)
|
44
|
+
- [Pull request?](#pull-request)
|
45
|
+
- [License](#license)
|
37
46
|
|
38
47
|
## Setting up a client
|
39
48
|
|
@@ -91,7 +100,7 @@ account.kid # => <kid string>
|
|
91
100
|
|
92
101
|
If you already have an existing account (for example one created in ACME v1) please note that unless the `kid` is provided at initialization, the client will lazy load the `kid` by doing a `POST` to `newAccount` whenever the `kid` is required. Therefore, you can easily get your `kid` for an existing account and (if needed) store it for reuse:
|
93
102
|
|
94
|
-
```
|
103
|
+
```ruby
|
95
104
|
client = Acme::Client.new(private_key: private_key, directory: 'https://acme-staging-v02.api.letsencrypt.org/directory')
|
96
105
|
|
97
106
|
# kid is not set, therefore a call to newAccount is made to lazy-initialize the kid
|
@@ -184,11 +193,28 @@ csr = Acme::Client::CertificateRequest.new(private_key: a_different_private_key,
|
|
184
193
|
order.finalize(csr: csr)
|
185
194
|
while order.status == 'processing'
|
186
195
|
sleep(1)
|
187
|
-
|
196
|
+
order.reload
|
188
197
|
end
|
189
198
|
order.certificate # => PEM-formatted certificate
|
190
199
|
```
|
191
200
|
|
201
|
+
### Ordering an alternative certificate
|
202
|
+
|
203
|
+
Let's Encrypt is [transitioning](https://letsencrypt.org/2019/04/15/transitioning-to-isrg-root.html) to use a new intermediate certificate. Starting January 11, 2021 new certificates will be signed by their own intermediate. To ease the transition on clients Let's Encrypt will continue signing an alternative version of the certificate using the old, cross-signed intermediate until September 29, 2021. In order to utilize an alternative certificate the `Order#certificate` method accepts a `force_chain` keyword argument, which takes the issuer name of the intermediate certificate.
|
204
|
+
For example, to download the cross-signed certificate after January 11, 2021, call `Order#certificate` as follows:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
begin
|
208
|
+
order.certificate(force_chain: 'DST Root CA X3')
|
209
|
+
rescue Acme::Client::Error::ForcedChainNotFound
|
210
|
+
order.certificate
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
Note: if the specified forced chain doesn't match an existing alternative certificate the method will raise an `Acme::Client::Error::ForcedChainNotFound` error.
|
215
|
+
|
216
|
+
Learn more about the original Github issue for this client [here](https://github.com/unixcharles/acme-client/issues/186), information from Let's Encrypt [here](https://letsencrypt.org/2019/04/15/transitioning-to-isrg-root.html), and cross-signing [here](https://letsencrypt.org/certificates/#cross-signing).
|
217
|
+
|
192
218
|
## Extra
|
193
219
|
|
194
220
|
### Certificate revokation
|
@@ -201,12 +227,18 @@ client.revoke(certificate: certificate)
|
|
201
227
|
|
202
228
|
### Certificate renewal
|
203
229
|
|
204
|
-
|
230
|
+
There is no renewal process, just create a new order.
|
231
|
+
|
205
232
|
|
233
|
+
### Account Key Roll-over
|
206
234
|
|
207
|
-
|
235
|
+
To change the key used for an account you can call `#account_key_change` with the new private key or jwk.
|
208
236
|
|
209
|
-
|
237
|
+
```ruby
|
238
|
+
require 'openssl'
|
239
|
+
new_private_key = OpenSSL::PKey::RSA.new(4096)
|
240
|
+
client.account_key_change(private_key: new_private_key)
|
241
|
+
```
|
210
242
|
|
211
243
|
## Requirements
|
212
244
|
|
@@ -227,4 +259,3 @@ Yes.
|
|
227
259
|
## License
|
228
260
|
|
229
261
|
[MIT License](http://opensource.org/licenses/MIT)
|
230
|
-
|
data/acme-client.gemspec
CHANGED
@@ -16,11 +16,16 @@ Gem::Specification.new do |spec|
|
|
16
16
|
|
17
17
|
spec.required_ruby_version = '>= 2.1.0'
|
18
18
|
|
19
|
-
spec.add_development_dependency 'bundler', '
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
spec.add_development_dependency 'bundler', '>= 1.17.3'
|
20
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2')
|
21
|
+
spec.add_development_dependency 'rake', '>= 12.0'
|
22
|
+
else
|
23
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
24
|
+
end
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.9'
|
26
|
+
spec.add_development_dependency 'vcr', '~> 2.9'
|
27
|
+
spec.add_development_dependency 'webmock', '~> 3.8'
|
28
|
+
spec.add_development_dependency 'webrick'
|
24
29
|
|
25
|
-
spec.add_runtime_dependency 'faraday', '
|
30
|
+
spec.add_runtime_dependency 'faraday', '>= 0.17', '< 2.0.0'
|
26
31
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Acme::Client
|
2
|
+
class ChainIdentifier
|
3
|
+
def initialize(pem_certificate_chain)
|
4
|
+
@pem_certificate_chain = pem_certificate_chain
|
5
|
+
end
|
6
|
+
|
7
|
+
def match_name?(name)
|
8
|
+
issuers.any? do |issuer|
|
9
|
+
issuer.include?(name)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def issuers
|
16
|
+
x509_certificates.map(&:issuer).map(&:to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def x509_certificates
|
20
|
+
@x509_certificates ||= splitted_pem_certificates.map { |pem| OpenSSL::X509::Certificate.new(pem) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def splitted_pem_certificates
|
24
|
+
@pem_certificate_chain.each_line.slice_after(/END CERTIFICATE/).map(&:join)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/acme/client/error.rb
CHANGED
@@ -7,6 +7,7 @@ class Acme::Client::Error < StandardError
|
|
7
7
|
class UnsupportedChallengeType < ClientError; end
|
8
8
|
class NotFound < ClientError; end
|
9
9
|
class CertificateNotReady < ClientError; end
|
10
|
+
class ForcedChainNotFound < ClientError; end
|
10
11
|
|
11
12
|
class ServerError < Acme::Client::Error; end
|
12
13
|
class BadCSR < ServerError; end
|
@@ -5,10 +5,10 @@ class Acme::Client::FaradayMiddleware < Faraday::Middleware
|
|
5
5
|
|
6
6
|
CONTENT_TYPE = 'application/jose+json'
|
7
7
|
|
8
|
-
def initialize(app,
|
8
|
+
def initialize(app, options)
|
9
9
|
super(app)
|
10
|
-
@client = client
|
11
|
-
@mode = mode
|
10
|
+
@client = options.fetch(:client)
|
11
|
+
@mode = options.fetch(:mode)
|
12
12
|
end
|
13
13
|
|
14
14
|
def call(env)
|
@@ -82,18 +82,10 @@ class Acme::Client::FaradayMiddleware < Faraday::Middleware
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/
|
86
|
-
|
87
85
|
def decode_link_headers
|
88
86
|
return unless env.response_headers.key?('Link')
|
89
87
|
link_header = env.response_headers['Link']
|
90
|
-
|
91
|
-
links = link_header.split(', ').map { |entry|
|
92
|
-
_, link, name = *entry.match(LINK_MATCH)
|
93
|
-
[name, link]
|
94
|
-
}
|
95
|
-
|
96
|
-
Hash[*links.flatten]
|
88
|
+
Acme::Client::Util.decode_link_headers(link_header)
|
97
89
|
end
|
98
90
|
|
99
91
|
def store_nonce
|
@@ -73,8 +73,10 @@ class Acme::Client::JWK::ECDSA < Acme::Client::JWK::Base
|
|
73
73
|
# BigNumbers
|
74
74
|
bns = ints.map(&:value)
|
75
75
|
|
76
|
+
byte_size = (@private_key.group.degree + 7) / 8
|
77
|
+
|
76
78
|
# Binary R/S values
|
77
|
-
r, s = bns.map { |bn|
|
79
|
+
r, s = bns.map { |bn| bn.to_s(2).rjust(byte_size, "\x00") }
|
78
80
|
|
79
81
|
# JWS wants raw R/S concatenated.
|
80
82
|
[r, s].join
|
@@ -68,9 +68,13 @@ class Acme::Client::Resources::Directory
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def fetch_directory
|
71
|
-
connection = Faraday.new(url: @directory, **@connection_options)
|
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
|
75
|
+
end
|
72
76
|
connection.headers[:user_agent] = Acme::Client::USER_AGENT
|
73
77
|
response = connection.get(@url)
|
74
|
-
|
78
|
+
response.body
|
75
79
|
end
|
76
80
|
end
|
@@ -5,7 +5,7 @@ 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
|
@@ -24,9 +24,9 @@ class Acme::Client::Resources::Order
|
|
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
@@ -3,6 +3,17 @@ module Acme::Client::Util
|
|
3
3
|
Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
|
4
4
|
end
|
5
5
|
|
6
|
+
LINK_MATCH = /<(.*?)>\s?;\s?rel="([\w-]+)"/
|
7
|
+
|
8
|
+
# See RFC 8288 - https://tools.ietf.org/html/rfc8288#section-3
|
9
|
+
def decode_link_headers(link_header)
|
10
|
+
link_header.split(',').each_with_object({}) { |entry, hash|
|
11
|
+
_, link, name = *entry.match(LINK_MATCH)
|
12
|
+
hash[name] ||= []
|
13
|
+
hash[name].push(link)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
6
17
|
# Sets public key on CSR or cert.
|
7
18
|
#
|
8
19
|
# obj - An OpenSSL::X509::Certificate or OpenSSL::X509::Request instance.
|
data/lib/acme/client/version.rb
CHANGED
data/lib/acme/client.rb
CHANGED
@@ -20,6 +20,7 @@ require 'acme/client/faraday_middleware'
|
|
20
20
|
require 'acme/client/jwk'
|
21
21
|
require 'acme/client/error'
|
22
22
|
require 'acme/client/util'
|
23
|
+
require 'acme/client/chain_identifier'
|
23
24
|
|
24
25
|
class Acme::Client
|
25
26
|
DEFAULT_DIRECTORY = 'http://127.0.0.1:4000/directory'.freeze
|
@@ -84,6 +85,28 @@ class Acme::Client
|
|
84
85
|
Acme::Client::Resources::Account.new(self, url: kid, **arguments)
|
85
86
|
end
|
86
87
|
|
88
|
+
def account_key_change(new_private_key: nil, new_jwk: nil)
|
89
|
+
if new_private_key.nil? && new_jwk.nil?
|
90
|
+
raise ArgumentError, 'must specify new_jwk or new_private_key'
|
91
|
+
end
|
92
|
+
old_jwk = jwk
|
93
|
+
new_jwk ||= Acme::Client::JWK.from_private_key(new_private_key)
|
94
|
+
|
95
|
+
inner_payload_header = {
|
96
|
+
url: endpoint_for(:key_change)
|
97
|
+
}
|
98
|
+
inner_payload = {
|
99
|
+
account: kid,
|
100
|
+
oldKey: old_jwk.to_h
|
101
|
+
}
|
102
|
+
payload = JSON.parse(new_jwk.jws(header: inner_payload_header, payload: inner_payload))
|
103
|
+
|
104
|
+
response = post(endpoint_for(:key_change), payload: payload, mode: :kid)
|
105
|
+
arguments = attributes_from_account_response(response)
|
106
|
+
@jwk = new_jwk
|
107
|
+
Acme::Client::Resources::Account.new(self, url: kid, **arguments)
|
108
|
+
end
|
109
|
+
|
87
110
|
def account
|
88
111
|
@kid ||= begin
|
89
112
|
response = post(endpoint_for(:new_account), payload: { onlyReturnExisting: true }, mode: :jwk)
|
@@ -127,9 +150,24 @@ class Acme::Client
|
|
127
150
|
Acme::Client::Resources::Order.new(self, **arguments)
|
128
151
|
end
|
129
152
|
|
130
|
-
def certificate(url:)
|
153
|
+
def certificate(url:, force_chain: nil)
|
131
154
|
response = download(url, format: :pem)
|
132
|
-
response.body
|
155
|
+
pem = response.body
|
156
|
+
|
157
|
+
return pem if force_chain.nil?
|
158
|
+
|
159
|
+
return pem if ChainIdentifier.new(pem).match_name?(force_chain)
|
160
|
+
|
161
|
+
alternative_urls = Array(response.headers.dig('link', 'alternate'))
|
162
|
+
alternative_urls.each do |alternate_url|
|
163
|
+
response = download(alternate_url, format: :pem)
|
164
|
+
pem = response.body
|
165
|
+
if ChainIdentifier.new(pem).match_name?(force_chain)
|
166
|
+
return pem
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
raise Acme::Client::Error::ForcedChainNotFound, "Could not find any matching chain for `#{force_chain}`"
|
133
171
|
end
|
134
172
|
|
135
173
|
def authorization(url:)
|
metadata
CHANGED
@@ -1,69 +1,57 @@
|
|
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.9
|
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: 2021-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.6'
|
20
17
|
- - ">="
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: 1.
|
19
|
+
version: 1.17.3
|
23
20
|
type: :development
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.6'
|
30
24
|
- - ">="
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.
|
26
|
+
version: 1.17.3
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: rake
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
31
|
- - "~>"
|
38
32
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
33
|
+
version: '13.0'
|
40
34
|
type: :development
|
41
35
|
prerelease: false
|
42
36
|
version_requirements: !ruby/object:Gem::Requirement
|
43
37
|
requirements:
|
44
38
|
- - "~>"
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
40
|
+
version: '13.0'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: rspec
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
50
44
|
requirements:
|
51
|
-
- - ">="
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: 3.3.0
|
54
45
|
- - "~>"
|
55
46
|
- !ruby/object:Gem::Version
|
56
|
-
version: '3.
|
47
|
+
version: '3.9'
|
57
48
|
type: :development
|
58
49
|
prerelease: false
|
59
50
|
version_requirements: !ruby/object:Gem::Requirement
|
60
51
|
requirements:
|
61
|
-
- - ">="
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
version: 3.3.0
|
64
52
|
- - "~>"
|
65
53
|
- !ruby/object:Gem::Version
|
66
|
-
version: '3.
|
54
|
+
version: '3.9'
|
67
55
|
- !ruby/object:Gem::Dependency
|
68
56
|
name: vcr
|
69
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -71,9 +59,6 @@ dependencies:
|
|
71
59
|
- - "~>"
|
72
60
|
- !ruby/object:Gem::Version
|
73
61
|
version: '2.9'
|
74
|
-
- - ">="
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: 2.9.3
|
77
62
|
type: :development
|
78
63
|
prerelease: false
|
79
64
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -81,43 +66,54 @@ dependencies:
|
|
81
66
|
- - "~>"
|
82
67
|
- !ruby/object:Gem::Version
|
83
68
|
version: '2.9'
|
84
|
-
- - ">="
|
85
|
-
- !ruby/object:Gem::Version
|
86
|
-
version: 2.9.3
|
87
69
|
- !ruby/object:Gem::Dependency
|
88
70
|
name: webmock
|
89
71
|
requirement: !ruby/object:Gem::Requirement
|
90
72
|
requirements:
|
91
73
|
- - "~>"
|
92
74
|
- !ruby/object:Gem::Version
|
93
|
-
version: '3.
|
75
|
+
version: '3.8'
|
94
76
|
type: :development
|
95
77
|
prerelease: false
|
96
78
|
version_requirements: !ruby/object:Gem::Requirement
|
97
79
|
requirements:
|
98
80
|
- - "~>"
|
99
81
|
- !ruby/object:Gem::Version
|
100
|
-
version: '3.
|
82
|
+
version: '3.8'
|
101
83
|
- !ruby/object:Gem::Dependency
|
102
|
-
name:
|
84
|
+
name: webrick
|
103
85
|
requirement: !ruby/object:Gem::Requirement
|
104
86
|
requirements:
|
105
|
-
- - "
|
87
|
+
- - ">="
|
106
88
|
- !ruby/object:Gem::Version
|
107
|
-
version: '0
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: faraday
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
108
101
|
- - ">="
|
109
102
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
103
|
+
version: '0.17'
|
104
|
+
- - "<"
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 2.0.0
|
111
107
|
type: :runtime
|
112
108
|
prerelease: false
|
113
109
|
version_requirements: !ruby/object:Gem::Requirement
|
114
110
|
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0.9'
|
118
111
|
- - ">="
|
119
112
|
- !ruby/object:Gem::Version
|
120
|
-
version: 0.
|
113
|
+
version: '0.17'
|
114
|
+
- - "<"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 2.0.0
|
121
117
|
description:
|
122
118
|
email:
|
123
119
|
- unixcharles@gmail.com
|
@@ -125,10 +121,11 @@ executables: []
|
|
125
121
|
extensions: []
|
126
122
|
extra_rdoc_files: []
|
127
123
|
files:
|
124
|
+
- ".github/workflows/rubocop.yml"
|
125
|
+
- ".github/workflows/test.yml"
|
128
126
|
- ".gitignore"
|
129
127
|
- ".rspec"
|
130
128
|
- ".rubocop.yml"
|
131
|
-
- ".travis.yml"
|
132
129
|
- CHANGELOG.md
|
133
130
|
- Gemfile
|
134
131
|
- LICENSE.txt
|
@@ -142,6 +139,7 @@ files:
|
|
142
139
|
- lib/acme/client.rb
|
143
140
|
- lib/acme/client/certificate_request.rb
|
144
141
|
- lib/acme/client/certificate_request/ec_key_patch.rb
|
142
|
+
- lib/acme/client/chain_identifier.rb
|
145
143
|
- lib/acme/client/error.rb
|
146
144
|
- lib/acme/client/faraday_middleware.rb
|
147
145
|
- lib/acme/client/jwk.rb
|
@@ -180,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
178
|
- !ruby/object:Gem::Version
|
181
179
|
version: '0'
|
182
180
|
requirements: []
|
183
|
-
rubygems_version: 3.
|
181
|
+
rubygems_version: 3.1.2
|
184
182
|
signing_key:
|
185
183
|
specification_version: 4
|
186
184
|
summary: Client for the ACME protocol.
|