acme-client 2.0.32 → 2.0.33
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 +6 -0
- data/acme-client-2.0.32.gem +0 -0
- data/acme-client.gemspec +1 -0
- data/lib/acme/client/error/rate_limited.rb +2 -2
- data/lib/acme/client/error.rb +42 -5
- data/lib/acme/client/http_client.rb +3 -2
- data/lib/acme/client/problem.rb +134 -0
- data/lib/acme/client/resources/challenges/base.rb +4 -3
- data/lib/acme/client/version.rb +1 -1
- data/lib/acme/client.rb +1 -0
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7216d10cb299e96cb201b06b84dd4d37ca10c2a37b5e32bb62f508fe76c7c77
|
|
4
|
+
data.tar.gz: b27c4e177e4ae6332bff52ee4b8afe04a50157bbfcb4cc17a6a9d0bfa94d6864
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df38f4e1c3f961aefb7d39ca86872c3816208349c9c917523b5215e988bad389a3d10133168092260f5c5c845210e4c80aad8705df13606e2c9767a88fda6c7c
|
|
7
|
+
data.tar.gz: 4960de02cea2e1fe074ea1fe6df1ba9ee62c59c6dfe7156359179f04ab6be40155f8a308676f70dc2796eef06641f6781025da7ec8fabc8ee7b36e41b446d0b6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## `2.0.33`
|
|
2
|
+
|
|
3
|
+
* Add `Acme::Client::Problem` for structured RFC 7807 / RFC 8555 ACME problem documents
|
|
4
|
+
* Expose `Acme::Client::Error#problem` and problem field accessors while keeping `#acme_error_body` behavior
|
|
5
|
+
* Add typed error mappings for all currently registered IANA ACME error types and verify them against IANA's CSV registry
|
|
6
|
+
|
|
1
7
|
## `2.0.32`
|
|
2
8
|
|
|
3
9
|
* Fix `valid_ip_address?` method definition. Was mistakenly defined on Object instead of `Acme::Client::CertificateRequest`
|
|
Binary file
|
data/acme-client.gemspec
CHANGED
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.add_development_dependency 'rspec', '~> 3.9'
|
|
21
21
|
spec.add_development_dependency 'vcr', '~> 6.0'
|
|
22
22
|
spec.add_development_dependency 'bigdecimal'
|
|
23
|
+
spec.add_development_dependency 'csv'
|
|
23
24
|
spec.add_development_dependency 'webmock', '~> 3.8'
|
|
24
25
|
spec.add_development_dependency 'webrick', '~> 1.7'
|
|
25
26
|
|
|
@@ -2,14 +2,14 @@ class Acme::Client::Error::RateLimited < Acme::Client::Error::ServerError
|
|
|
2
2
|
DEFAULT_MESSAGE = 'Error message: urn:ietf:params:acme:error:rateLimited'
|
|
3
3
|
DEFAULT_RETRY_SECONDS = 10
|
|
4
4
|
|
|
5
|
-
def initialize(message = DEFAULT_MESSAGE, retry_after = nil, acme_error_body: nil, subproblems: nil)
|
|
5
|
+
def initialize(message = DEFAULT_MESSAGE, retry_after = nil, acme_error_body: nil, subproblems: nil, problem: nil)
|
|
6
6
|
retry_after_time = case retry_after
|
|
7
7
|
when Time then retry_after
|
|
8
8
|
when nil then Time.now + DEFAULT_RETRY_SECONDS
|
|
9
9
|
else Acme::Client::Util.parse_retry_after(retry_after) || Time.now + DEFAULT_RETRY_SECONDS
|
|
10
10
|
end
|
|
11
11
|
int_retry_after = retry_after.nil? ? DEFAULT_RETRY_SECONDS : [(retry_after_time - Time.now).ceil, 0].max
|
|
12
|
-
super(message, retry_after: int_retry_after, acme_error_body: acme_error_body, subproblems: subproblems)
|
|
12
|
+
super(message, retry_after: int_retry_after, acme_error_body: acme_error_body, subproblems: subproblems, problem: problem)
|
|
13
13
|
@retry_after_time = retry_after_time
|
|
14
14
|
end
|
|
15
15
|
end
|
data/lib/acme/client/error.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
require 'acme/client/problem'
|
|
2
|
+
|
|
1
3
|
class Acme::Client::Error < StandardError
|
|
2
|
-
attr_reader :retry_after, :retry_after_time, :subproblems, :acme_error_body
|
|
4
|
+
attr_reader :retry_after, :retry_after_time, :subproblems, :acme_error_body, :problem
|
|
3
5
|
|
|
4
6
|
Subproblem = Struct.new(:type, :detail, :identifier, keyword_init: true) do
|
|
5
7
|
def to_h
|
|
@@ -7,12 +9,13 @@ class Acme::Client::Error < StandardError
|
|
|
7
9
|
end
|
|
8
10
|
end
|
|
9
11
|
|
|
10
|
-
def initialize(message = nil, retry_after: nil, acme_error_body: nil, subproblems: nil)
|
|
12
|
+
def initialize(message = nil, retry_after: nil, acme_error_body: nil, subproblems: nil, problem: nil)
|
|
11
13
|
super(message)
|
|
12
14
|
@retry_after_time = Acme::Client::Util.parse_retry_after(retry_after)
|
|
13
15
|
@retry_after = @retry_after_time ? [(@retry_after_time - Time.now).ceil, 0].max : nil
|
|
14
|
-
@
|
|
15
|
-
@
|
|
16
|
+
@problem = Acme::Client::Problem.from(problem || acme_error_body)
|
|
17
|
+
@acme_error_body = acme_error_body || @problem&.to_h
|
|
18
|
+
@subproblems = parse_subproblems(subproblems || @problem&.raw_subproblems)
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
private
|
|
@@ -31,6 +34,26 @@ class Acme::Client::Error < StandardError
|
|
|
31
34
|
|
|
32
35
|
public
|
|
33
36
|
|
|
37
|
+
def type
|
|
38
|
+
problem&.type
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def code
|
|
42
|
+
problem&.code
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def detail
|
|
46
|
+
problem&.detail || message
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def status
|
|
50
|
+
problem&.status
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def identifier
|
|
54
|
+
problem&.identifier
|
|
55
|
+
end
|
|
56
|
+
|
|
34
57
|
class Timeout < Acme::Client::Error; end
|
|
35
58
|
|
|
36
59
|
class ClientError < Acme::Client::Error; end
|
|
@@ -63,10 +86,17 @@ class Acme::Client::Error < StandardError
|
|
|
63
86
|
class UserActionRequired < ServerError; end
|
|
64
87
|
class BadRevocationReason < ServerError; end
|
|
65
88
|
class Caa < ServerError; end
|
|
89
|
+
class Compound < ServerError; end
|
|
66
90
|
class Dns < ServerError; end
|
|
67
91
|
class Connection < ServerError; end
|
|
68
92
|
class Tls < ServerError; end
|
|
69
93
|
class IncorrectResponse < ServerError; end
|
|
94
|
+
class AutoRenewalCanceled < ServerError; end
|
|
95
|
+
class AutoRenewalExpired < ServerError; end
|
|
96
|
+
class AutoRenewalCancellationInvalid < ServerError; end
|
|
97
|
+
class AutoRenewalRevocationNotSupported < ServerError; end
|
|
98
|
+
class UnknownDelegation < ServerError; end
|
|
99
|
+
class OnionCAARequired < ServerError; end
|
|
70
100
|
|
|
71
101
|
ACME_ERRORS = {
|
|
72
102
|
'urn:ietf:params:acme:error:alreadyReplaced' => AlreadyReplaced,
|
|
@@ -89,9 +119,16 @@ class Acme::Client::Error < StandardError
|
|
|
89
119
|
'urn:ietf:params:acme:error:userActionRequired' => UserActionRequired,
|
|
90
120
|
'urn:ietf:params:acme:error:badRevocationReason' => BadRevocationReason,
|
|
91
121
|
'urn:ietf:params:acme:error:caa' => Caa,
|
|
122
|
+
'urn:ietf:params:acme:error:compound' => Compound,
|
|
92
123
|
'urn:ietf:params:acme:error:dns' => Dns,
|
|
93
124
|
'urn:ietf:params:acme:error:connection' => Connection,
|
|
94
125
|
'urn:ietf:params:acme:error:tls' => Tls,
|
|
95
|
-
'urn:ietf:params:acme:error:incorrectResponse' => IncorrectResponse
|
|
126
|
+
'urn:ietf:params:acme:error:incorrectResponse' => IncorrectResponse,
|
|
127
|
+
'urn:ietf:params:acme:error:autoRenewalCanceled' => AutoRenewalCanceled,
|
|
128
|
+
'urn:ietf:params:acme:error:autoRenewalExpired' => AutoRenewalExpired,
|
|
129
|
+
'urn:ietf:params:acme:error:autoRenewalCancellationInvalid' => AutoRenewalCancellationInvalid,
|
|
130
|
+
'urn:ietf:params:acme:error:autoRenewalRevocationNotSupported' => AutoRenewalRevocationNotSupported,
|
|
131
|
+
'urn:ietf:params:acme:error:unknownDelegation' => UnknownDelegation,
|
|
132
|
+
'urn:ietf:params:acme:error:onionCAARequired' => OnionCAARequired
|
|
96
133
|
}
|
|
97
134
|
end
|
|
@@ -103,11 +103,12 @@ module Acme::Client::HTTPClient
|
|
|
103
103
|
def raise_on_error!
|
|
104
104
|
retry_after = env.response_headers['retry-after']
|
|
105
105
|
body = env.body.is_a?(Hash) ? env.body : nil
|
|
106
|
+
problem = Acme::Client::Problem.from(body)
|
|
106
107
|
subproblems = error_subproblems
|
|
107
108
|
if error_class == Acme::Client::Error::RateLimited
|
|
108
|
-
raise error_class.new(error_message, retry_after, acme_error_body: body, subproblems: subproblems)
|
|
109
|
+
raise error_class.new(error_message, retry_after, acme_error_body: body, subproblems: subproblems, problem: problem)
|
|
109
110
|
end
|
|
110
|
-
raise error_class.new(error_message, retry_after: retry_after, acme_error_body: body, subproblems: subproblems)
|
|
111
|
+
raise error_class.new(error_message, retry_after: retry_after, acme_error_body: body, subproblems: subproblems, problem: problem)
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
def error_message
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Acme; end
|
|
4
|
+
class Acme::Client; end
|
|
5
|
+
|
|
6
|
+
class Acme::Client::Problem
|
|
7
|
+
ERROR_PREFIX = 'urn:ietf:params:acme:error:'.freeze
|
|
8
|
+
|
|
9
|
+
# Registry descriptions for problem types, not fallback values for problem details.
|
|
10
|
+
REGISTERED_ERROR_TYPE_DESCRIPTIONS = {
|
|
11
|
+
'accountDoesNotExist' => 'The request specified an account that does not exist',
|
|
12
|
+
'alreadyRevoked' => 'The request specified a certificate to be revoked that has already been revoked',
|
|
13
|
+
'badCSR' => 'The CSR is unacceptable (e.g., due to a short key)',
|
|
14
|
+
'badNonce' => 'The client sent an unacceptable anti-replay nonce',
|
|
15
|
+
'badPublicKey' => 'The JWS was signed by a public key the server does not support',
|
|
16
|
+
'badRevocationReason' => 'The revocation reason provided is not allowed by the server',
|
|
17
|
+
'badSignatureAlgorithm' => 'The JWS was signed with an algorithm the server does not support',
|
|
18
|
+
'caa' => 'Certification Authority Authorization (CAA) records forbid the CA from issuing a certificate',
|
|
19
|
+
'compound' => 'Specific error conditions are indicated in the "subproblems" array',
|
|
20
|
+
'connection' => 'The server could not connect to validation target',
|
|
21
|
+
'dns' => 'There was a problem with a DNS query during identifier validation',
|
|
22
|
+
'externalAccountRequired' => 'The request must include a value for the "externalAccountBinding" field',
|
|
23
|
+
'incorrectResponse' => "Response received didn't match the challenge's requirements",
|
|
24
|
+
'invalidContact' => 'A contact URL for an account was invalid',
|
|
25
|
+
'malformed' => 'The request message was malformed',
|
|
26
|
+
'orderNotReady' => 'The request attempted to finalize an order that is not ready to be finalized',
|
|
27
|
+
'rateLimited' => 'The request exceeds a rate limit',
|
|
28
|
+
'rejectedIdentifier' => 'The server will not issue certificates for the identifier',
|
|
29
|
+
'serverInternal' => 'The server experienced an internal error',
|
|
30
|
+
'tls' => 'The server received a TLS error during validation',
|
|
31
|
+
'unauthorized' => 'The client lacks sufficient authorization',
|
|
32
|
+
'unsupportedContact' => 'A contact URL for an account used an unsupported protocol scheme',
|
|
33
|
+
'unsupportedIdentifier' => 'An identifier is of an unsupported type',
|
|
34
|
+
'userActionRequired' => 'Visit the "instance" URL and take actions specified there',
|
|
35
|
+
'autoRenewalCanceled' => 'The short-term certificate is no longer available because the auto-renewal Order has been explicitly canceled by the IdO',
|
|
36
|
+
'autoRenewalExpired' => 'The short-term certificate is no longer available because the auto-renewal Order has expired',
|
|
37
|
+
'autoRenewalCancellationInvalid' => 'A request to cancel an auto-renewal Order that is not in state "valid" has been received',
|
|
38
|
+
'autoRenewalRevocationNotSupported' => 'A request to revoke an auto-renewal Order has been received',
|
|
39
|
+
'unknownDelegation' => 'An unknown configuration is listed in the delegation attribute of the order request',
|
|
40
|
+
'onionCAARequired' => 'The CA only supports checking the CAA for Hidden Services in-band, but the client has not provided an in-band CAA',
|
|
41
|
+
'alreadyReplaced' => 'The request specified a predecessor certificate that has already been marked as replaced'
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
def self.from(value)
|
|
45
|
+
case value
|
|
46
|
+
when nil
|
|
47
|
+
nil
|
|
48
|
+
when Acme::Client::Problem
|
|
49
|
+
value
|
|
50
|
+
when Hash
|
|
51
|
+
new(value)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attr_reader :raw
|
|
56
|
+
|
|
57
|
+
def initialize(raw = {})
|
|
58
|
+
@raw = raw || {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def type
|
|
62
|
+
fetch('type')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def code
|
|
66
|
+
return unless type
|
|
67
|
+
return unless type.start_with?(ERROR_PREFIX)
|
|
68
|
+
|
|
69
|
+
type[ERROR_PREFIX.length..-1]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def title
|
|
73
|
+
fetch('title')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def detail
|
|
77
|
+
fetch('detail')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def status
|
|
81
|
+
fetch('status')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def instance
|
|
85
|
+
fetch('instance')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def identifier
|
|
89
|
+
fetch('identifier')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def subproblems
|
|
93
|
+
return [] unless raw_subproblems.is_a?(Array)
|
|
94
|
+
|
|
95
|
+
raw_subproblems.map { |subproblem| self.class.new(subproblem) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def raw_subproblems
|
|
99
|
+
fetch('subproblems')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def registered?
|
|
103
|
+
REGISTERED_ERROR_TYPE_DESCRIPTIONS.key?(code)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
alias standard? registered?
|
|
107
|
+
|
|
108
|
+
def description
|
|
109
|
+
REGISTERED_ERROR_TYPE_DESCRIPTIONS[code]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def matches?(type_or_code)
|
|
113
|
+
candidate = type_or_code.to_s
|
|
114
|
+
candidate == type || candidate == code || "#{ERROR_PREFIX}#{candidate}" == type
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def message
|
|
118
|
+
detail || title || type
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def to_h
|
|
122
|
+
raw
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def as_json(*_arguments)
|
|
126
|
+
to_h
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def fetch(key)
|
|
132
|
+
raw[key] || raw[key.to_sym]
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -31,10 +31,11 @@ class Acme::Client::Resources::Challenges::Base
|
|
|
31
31
|
def typed_error
|
|
32
32
|
return nil unless error
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
problem = Acme::Client::Problem.from(error)
|
|
35
|
+
error_type = problem&.type
|
|
36
|
+
error_detail = problem&.detail || 'Unknown error'
|
|
36
37
|
error_class = Acme::Client::Error::ACME_ERRORS.fetch(error_type, Acme::Client::Error)
|
|
37
|
-
error_class.new(error_detail)
|
|
38
|
+
error_class.new(error_detail, problem: problem)
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def to_h
|
data/lib/acme/client/version.rb
CHANGED
data/lib/acme/client.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.33
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Charles Barbier
|
|
@@ -65,6 +65,20 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: csv
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: webmock
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -158,6 +172,7 @@ files:
|
|
|
158
172
|
- LICENSE.txt
|
|
159
173
|
- README.md
|
|
160
174
|
- Rakefile
|
|
175
|
+
- acme-client-2.0.32.gem
|
|
161
176
|
- acme-client.gemspec
|
|
162
177
|
- bin/console
|
|
163
178
|
- bin/generate_keystash
|
|
@@ -176,6 +191,7 @@ files:
|
|
|
176
191
|
- lib/acme/client/jwk/ecdsa.rb
|
|
177
192
|
- lib/acme/client/jwk/hmac.rb
|
|
178
193
|
- lib/acme/client/jwk/rsa.rb
|
|
194
|
+
- lib/acme/client/problem.rb
|
|
179
195
|
- lib/acme/client/resources.rb
|
|
180
196
|
- lib/acme/client/resources/account.rb
|
|
181
197
|
- lib/acme/client/resources/authorization.rb
|