acme-client 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +36 -0
- data/README.md +138 -94
- data/lib/acme/client.rb +223 -63
- data/lib/acme/client/certificate_request.rb +0 -2
- data/lib/acme/client/error.rb +52 -13
- data/lib/acme/client/faraday_middleware.rb +23 -24
- data/lib/acme/client/jwk/base.rb +7 -6
- data/lib/acme/client/jwk/ecdsa.rb +0 -2
- data/lib/acme/client/resources.rb +4 -2
- data/lib/acme/client/resources/account.rb +49 -0
- data/lib/acme/client/resources/authorization.rb +62 -32
- data/lib/acme/client/resources/challenges.rb +20 -5
- data/lib/acme/client/resources/challenges/base.rb +26 -22
- data/lib/acme/client/resources/challenges/dns01.rb +1 -1
- data/lib/acme/client/resources/challenges/http01.rb +1 -1
- data/lib/acme/client/resources/directory.rb +75 -0
- data/lib/acme/client/resources/order.rb +58 -0
- data/lib/acme/client/version.rb +1 -1
- metadata +5 -5
- data/lib/acme/client/certificate.rb +0 -30
- data/lib/acme/client/resources/challenges/tls_sni01.rb +0 -25
- data/lib/acme/client/resources/registration.rb +0 -37
@@ -15,6 +15,6 @@ class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Chal
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def record_content
|
18
|
-
Acme::Client::Util.urlsafe_base64(DIGEST.digest(
|
18
|
+
Acme::Client::Util.urlsafe_base64(DIGEST.digest(key_authorization))
|
19
19
|
end
|
20
20
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Acme::Client::Resources::Directory
|
4
|
+
DIRECTORY_RESOURCES = {
|
5
|
+
new_nonce: 'newNonce',
|
6
|
+
new_account: 'newAccount',
|
7
|
+
new_order: 'newOrder',
|
8
|
+
new_authz: 'newAuthz',
|
9
|
+
revoke_certificate: 'revokeCert',
|
10
|
+
key_change: 'keyChange'
|
11
|
+
}
|
12
|
+
|
13
|
+
DIRECTORY_META = {
|
14
|
+
terms_of_service: 'termsOfService',
|
15
|
+
website: 'website',
|
16
|
+
caa_identities: 'caaIdentities',
|
17
|
+
external_account_required: 'externalAccountRequired'
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize(url, connection_options)
|
21
|
+
@url, @connection_options = url, connection_options
|
22
|
+
end
|
23
|
+
|
24
|
+
def endpoint_for(key)
|
25
|
+
directory.fetch(key) do |missing_key|
|
26
|
+
raise Acme::Client::Error::UnsupportedOperation,
|
27
|
+
"Directory at #{@url} does not include `#{missing_key}`"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def terms_of_service
|
32
|
+
meta[DIRECTORY_META[:terms_of_service]]
|
33
|
+
end
|
34
|
+
|
35
|
+
def website
|
36
|
+
meta[DIRECTORY_META[:website]]
|
37
|
+
end
|
38
|
+
|
39
|
+
def caa_identities
|
40
|
+
meta[DIRECTORY_META[:caa_identities]]
|
41
|
+
end
|
42
|
+
|
43
|
+
def external_account_required
|
44
|
+
meta[DIRECTORY_META[:external_account_required]]
|
45
|
+
end
|
46
|
+
|
47
|
+
def meta
|
48
|
+
directory[:meta]
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def directory
|
54
|
+
@directory ||= load_directory
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_directory
|
58
|
+
body = fetch_directory
|
59
|
+
result = {}
|
60
|
+
result[:meta] = body.delete('meta')
|
61
|
+
DIRECTORY_RESOURCES.each do |key, entry|
|
62
|
+
result[key] = URI(body[entry]) if body[entry]
|
63
|
+
end
|
64
|
+
result
|
65
|
+
rescue JSON::ParserError => exception
|
66
|
+
raise InvalidDirectory, "Invalid directory url\n#{@directory} did not return a valid directory\n#{exception.inspect}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch_directory
|
70
|
+
connection = Faraday.new(url: @directory, **@connection_options)
|
71
|
+
connection.headers[:user_agent] = Acme::Client::USER_AGENT
|
72
|
+
response = connection.get(@url)
|
73
|
+
JSON.parse(response.body)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Acme::Client::Resources::Order
|
4
|
+
attr_reader :url, :status, :contact, :finalize_url, :identifiers, :authorization_urls, :expires, :certificate_url
|
5
|
+
|
6
|
+
def initialize(client, **arguments)
|
7
|
+
@client = client
|
8
|
+
assign_attributes(arguments)
|
9
|
+
end
|
10
|
+
|
11
|
+
def reload
|
12
|
+
assign_attributes **@client.order(url: url).to_h
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def authorizations
|
17
|
+
@authorization_urls.map do |authorization_url|
|
18
|
+
@client.authorization(url: authorization_url)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def finalize(csr:)
|
23
|
+
assign_attributes **@client.finalize(url: finalize_url, csr: csr).to_h
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def certificate
|
28
|
+
if certificate_url
|
29
|
+
@client.certificate(url: certificate_url)
|
30
|
+
else
|
31
|
+
raise Acme::Client::Error::CertificateNotReady, 'No certificate_url to collect the order'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_h
|
36
|
+
{
|
37
|
+
url: url,
|
38
|
+
status: status,
|
39
|
+
expires: expires,
|
40
|
+
finalize_url: finalize_url,
|
41
|
+
authorization_urls: authorization_urls,
|
42
|
+
identifiers: identifiers,
|
43
|
+
certificate_url: certificate_url
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def assign_attributes(url:, status:, expires:, finalize_url:, authorization_urls:, identifiers:, certificate_url: nil)
|
50
|
+
@url = url
|
51
|
+
@status = status
|
52
|
+
@expires = expires
|
53
|
+
@finalize_url = finalize_url
|
54
|
+
@authorization_urls = authorization_urls
|
55
|
+
@identifiers = identifiers
|
56
|
+
@certificate_url = certificate_url
|
57
|
+
end
|
58
|
+
end
|
data/lib/acme/client/version.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:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Barbier
|
@@ -146,7 +146,6 @@ files:
|
|
146
146
|
- bin/setup
|
147
147
|
- lib/acme-client.rb
|
148
148
|
- lib/acme/client.rb
|
149
|
-
- lib/acme/client/certificate.rb
|
150
149
|
- lib/acme/client/certificate_request.rb
|
151
150
|
- lib/acme/client/certificate_request/ec_key_patch.rb
|
152
151
|
- lib/acme/client/error.rb
|
@@ -156,13 +155,14 @@ files:
|
|
156
155
|
- lib/acme/client/jwk/ecdsa.rb
|
157
156
|
- lib/acme/client/jwk/rsa.rb
|
158
157
|
- lib/acme/client/resources.rb
|
158
|
+
- lib/acme/client/resources/account.rb
|
159
159
|
- lib/acme/client/resources/authorization.rb
|
160
160
|
- lib/acme/client/resources/challenges.rb
|
161
161
|
- lib/acme/client/resources/challenges/base.rb
|
162
162
|
- lib/acme/client/resources/challenges/dns01.rb
|
163
163
|
- lib/acme/client/resources/challenges/http01.rb
|
164
|
-
- lib/acme/client/resources/
|
165
|
-
- lib/acme/client/resources/
|
164
|
+
- lib/acme/client/resources/directory.rb
|
165
|
+
- lib/acme/client/resources/order.rb
|
166
166
|
- lib/acme/client/self_sign_certificate.rb
|
167
167
|
- lib/acme/client/util.rb
|
168
168
|
- lib/acme/client/version.rb
|
@@ -186,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
186
|
version: '0'
|
187
187
|
requirements: []
|
188
188
|
rubyforge_project:
|
189
|
-
rubygems_version: 2.
|
189
|
+
rubygems_version: 2.7.6
|
190
190
|
signing_key:
|
191
191
|
specification_version: 4
|
192
192
|
summary: Client for the ACME protocol.
|
@@ -1,30 +0,0 @@
|
|
1
|
-
class Acme::Client::Certificate
|
2
|
-
extend Forwardable
|
3
|
-
|
4
|
-
attr_reader :x509, :x509_chain, :request, :private_key, :url
|
5
|
-
|
6
|
-
def_delegators :x509, :to_pem, :to_der
|
7
|
-
|
8
|
-
def initialize(certificate, url, chain, request)
|
9
|
-
@x509 = certificate
|
10
|
-
@url = url
|
11
|
-
@x509_chain = chain
|
12
|
-
@request = request
|
13
|
-
end
|
14
|
-
|
15
|
-
def chain_to_pem
|
16
|
-
x509_chain.map(&:to_pem).join
|
17
|
-
end
|
18
|
-
|
19
|
-
def x509_fullchain
|
20
|
-
[x509, *x509_chain]
|
21
|
-
end
|
22
|
-
|
23
|
-
def fullchain_to_pem
|
24
|
-
x509_fullchain.map(&:to_pem).join
|
25
|
-
end
|
26
|
-
|
27
|
-
def common_name
|
28
|
-
x509.subject.to_a.find { |name, _, _| name == 'CN' }[1]
|
29
|
-
end
|
30
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base
|
4
|
-
CHALLENGE_TYPE = 'tls-sni-01'.freeze
|
5
|
-
DIGEST = OpenSSL::Digest::SHA256
|
6
|
-
|
7
|
-
def hostname
|
8
|
-
digest = DIGEST.hexdigest(authorization_key)
|
9
|
-
"#{digest[0..31]}.#{digest[32..64]}.acme.invalid"
|
10
|
-
end
|
11
|
-
|
12
|
-
def certificate
|
13
|
-
self_sign_certificate.certificate
|
14
|
-
end
|
15
|
-
|
16
|
-
def private_key
|
17
|
-
self_sign_certificate.private_key
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def self_sign_certificate
|
23
|
-
@self_sign_certificate ||= Acme::Client::SelfSignCertificate.new(subject_alt_names: [hostname])
|
24
|
-
end
|
25
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
class Acme::Client::Resources::Registration
|
2
|
-
attr_reader :id, :key, :contact, :uri, :next_uri, :recover_uri, :term_of_service_uri
|
3
|
-
|
4
|
-
def initialize(client, response)
|
5
|
-
@client = client
|
6
|
-
@uri = response.headers['location']
|
7
|
-
assign_links(response.headers['Link'])
|
8
|
-
assign_attributes(response.body)
|
9
|
-
end
|
10
|
-
|
11
|
-
def get_terms
|
12
|
-
return unless @term_of_service_uri
|
13
|
-
|
14
|
-
@client.connection.get(@term_of_service_uri).body
|
15
|
-
end
|
16
|
-
|
17
|
-
def agree_terms
|
18
|
-
return true unless @term_of_service_uri
|
19
|
-
|
20
|
-
response = @client.connection.post(@uri, resource: 'reg', agreement: @term_of_service_uri)
|
21
|
-
response.success?
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def assign_links(links)
|
27
|
-
@next_uri = links['next']
|
28
|
-
@recover_uri = links['recover']
|
29
|
-
@term_of_service_uri = links['terms-of-service']
|
30
|
-
end
|
31
|
-
|
32
|
-
def assign_attributes(body)
|
33
|
-
@id = body['id']
|
34
|
-
@key = body['key']
|
35
|
-
@contact = body['contact']
|
36
|
-
end
|
37
|
-
end
|