acme-client 0.4.1 → 0.5.0
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/README.md +24 -7
- data/lib/acme/client.rb +7 -20
- data/lib/acme/client/resources/authorization.rb +25 -13
- data/lib/acme/client/resources/challenges/base.rb +15 -17
- data/lib/acme/client/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1014fe983481bd49d8eaf71c0f0a746dc0a4ec4b
|
4
|
+
data.tar.gz: eb1fe3e04e23b00110454ad06f55c40542fa6254
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce433ff66f5a5a837d997dbeb5f157e15fe43456ac238c24480f3064353f419f130d62153294f582e2651efc9ff037429ddc6a4f9630e46ee2ecbfbe16dfc90e
|
7
|
+
data.tar.gz: df0392f58d11037c3158e8c783afd5cc55d1a0f85baa5d195dfff1974424bfdb3a8112145349cffef3c4d957b622a3486fcce9ea93ca514d4c5d45d1280fcf7d
|
data/README.md
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# Acme::Client
|
2
|
+
|
2
3
|
[](https://travis-ci.org/unixcharles/acme-client)
|
3
4
|
|
4
5
|
`acme-client` is a client implementation of the [ACME](https://letsencrypt.github.io/acme-spec) protocol in Ruby.
|
5
6
|
|
6
7
|
You can find the ACME reference implementations of the [server](https://github.com/letsencrypt/boulder) in Go and the [client](https://github.com/letsencrypt/letsencrypt) in Python.
|
7
8
|
|
8
|
-
ACME is part of the [Letsencrypt](https://letsencrypt.org/) project, which goal is to provide free SSL/TLS certificates with
|
9
|
+
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.
|
9
10
|
|
10
11
|
## Installation
|
11
12
|
|
12
|
-
Via
|
13
|
+
Via RubyGems:
|
13
14
|
|
14
|
-
|
15
|
+
$ gem install acme-client
|
15
16
|
|
16
17
|
Or add it to a Gemfile:
|
17
18
|
|
@@ -54,6 +55,14 @@ Before you are able to obtain certificates for your domain, you have to prove th
|
|
54
55
|
```ruby
|
55
56
|
authorization = client.authorize(domain: 'example.org')
|
56
57
|
|
58
|
+
# If authorization.status returns 'valid' here you can already get a certificate
|
59
|
+
# and _must not_ try to solve another challenge.
|
60
|
+
authorization.status # => 'pending'
|
61
|
+
|
62
|
+
# You can can store the authorization's URI to fully recover it and
|
63
|
+
# any associated challenges via Acme::Client#fetch_authorization.
|
64
|
+
authorization.uri # => '...'
|
65
|
+
|
57
66
|
# This example is using the http-01 challenge type. Other challenges are dns-01 or tls-sni-01.
|
58
67
|
challenge = authorization.http01
|
59
68
|
|
@@ -90,12 +99,21 @@ challenge = client.challenge_from_hash(JSON.parse(File.read('challenge')))
|
|
90
99
|
|
91
100
|
# Once you are ready to serve the confirmation request you can proceed.
|
92
101
|
challenge.request_verification # => true
|
93
|
-
challenge.verify_status # => 'pending'
|
102
|
+
challenge.authorization.verify_status # => 'pending'
|
94
103
|
|
95
104
|
# Wait a bit for the server to make the request, or just blink. It should be fast.
|
96
105
|
sleep(1)
|
97
106
|
|
98
|
-
|
107
|
+
# Rely on authorization.verify_status more than on challenge.verify_status,
|
108
|
+
# if the former is 'valid' you can already issue a certificate and the status of
|
109
|
+
# the challenge is not relevant and in fact may never change from pending.
|
110
|
+
challenge.authorization.verify_status # => 'valid'
|
111
|
+
challenge.error # => nil
|
112
|
+
|
113
|
+
# If authorization.verify_status is 'invalid', you can get at the error
|
114
|
+
# message only through the failed challenge.
|
115
|
+
authorization.verify_status # => 'invalid'
|
116
|
+
authorization.http01.error # => {"type" => "...", "detail" => "..."}
|
99
117
|
```
|
100
118
|
|
101
119
|
### Obtain a certificate
|
@@ -130,7 +148,6 @@ File.write("fullchain.pem", certificate.fullchain_to_pem)
|
|
130
148
|
# Not implemented
|
131
149
|
|
132
150
|
- Recovery methods are not implemented.
|
133
|
-
- proofOfPossession-01 is not implemented.
|
134
151
|
|
135
152
|
# Requirements
|
136
153
|
|
@@ -139,7 +156,7 @@ Ruby >= 2.1
|
|
139
156
|
## Development
|
140
157
|
|
141
158
|
All the tests use VCR to mock the interaction with the server but if you
|
142
|
-
need to record new
|
159
|
+
need to record new interaction against the server simply clone boulder and
|
143
160
|
run it normally with `./start.py`.
|
144
161
|
|
145
162
|
## Pull request?
|
data/lib/acme/client.rb
CHANGED
@@ -35,7 +35,7 @@ class Acme::Client
|
|
35
35
|
load_directory!
|
36
36
|
end
|
37
37
|
|
38
|
-
attr_reader :private_key, :nonces, :operation_endpoints
|
38
|
+
attr_reader :private_key, :nonces, :endpoint, :directory_uri, :operation_endpoints
|
39
39
|
|
40
40
|
def register(contact:)
|
41
41
|
payload = {
|
@@ -56,7 +56,12 @@ class Acme::Client
|
|
56
56
|
}
|
57
57
|
|
58
58
|
response = connection.post(@operation_endpoints.fetch('new-authz'), payload)
|
59
|
-
::Acme::Client::Resources::Authorization.new(self, response)
|
59
|
+
::Acme::Client::Resources::Authorization.new(self, response.headers['Location'], response)
|
60
|
+
end
|
61
|
+
|
62
|
+
def fetch_authorization(uri)
|
63
|
+
response = connection.get(uri)
|
64
|
+
::Acme::Client::Resources::Authorization.new(self, uri, response)
|
60
65
|
end
|
61
66
|
|
62
67
|
def new_certificate(csr)
|
@@ -88,24 +93,6 @@ class Acme::Client
|
|
88
93
|
end
|
89
94
|
end
|
90
95
|
|
91
|
-
def challenge_from_hash(arguments)
|
92
|
-
attributes = arguments.to_h
|
93
|
-
%w(type uri token).each do |key|
|
94
|
-
raise ArgumentError, "missing key: #{key}" unless attributes.key?(key)
|
95
|
-
end
|
96
|
-
|
97
|
-
case attributes.fetch('type')
|
98
|
-
when 'http-01'
|
99
|
-
Acme::Client::Resources::Challenges::HTTP01.new(self, attributes)
|
100
|
-
when 'dns-01'
|
101
|
-
Acme::Client::Resources::Challenges::DNS01.new(self, attributes)
|
102
|
-
when 'tls-sni-01'
|
103
|
-
Acme::Client::Resources::Challenges::TLSSNI01.new(self, attributes)
|
104
|
-
else
|
105
|
-
raise 'Unsupported resource type'
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
96
|
private
|
110
97
|
|
111
98
|
def fetch_chain(response, limit = 10)
|
@@ -3,30 +3,42 @@ class Acme::Client::Resources::Authorization
|
|
3
3
|
DNS01 = Acme::Client::Resources::Challenges::DNS01
|
4
4
|
TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01
|
5
5
|
|
6
|
-
attr_reader :domain, :status, :expires, :http01, :dns01, :tls_sni01
|
6
|
+
attr_reader :client, :uri, :domain, :status, :expires, :http01, :dns01, :tls_sni01
|
7
7
|
|
8
|
-
def initialize(client, response)
|
8
|
+
def initialize(client, uri, response)
|
9
9
|
@client = client
|
10
|
-
|
10
|
+
@uri = uri
|
11
11
|
assign_attributes(response.body)
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
def verify_status
|
15
|
+
response = @client.connection.get(@uri)
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
case attributes.fetch('type')
|
19
|
-
when 'http-01' then @http01 = HTTP01.new(@client, attributes)
|
20
|
-
when 'dns-01' then @dns01 = DNS01.new(@client, attributes)
|
21
|
-
when 'tls-sni-01' then @tls_sni01 = TLSSNI01.new(@client, attributes)
|
22
|
-
# else no-op
|
23
|
-
end
|
24
|
-
end
|
17
|
+
assign_attributes(response.body)
|
18
|
+
status
|
25
19
|
end
|
26
20
|
|
21
|
+
private
|
22
|
+
|
27
23
|
def assign_attributes(body)
|
28
24
|
@expires = Time.iso8601(body['expires']) if body.key? 'expires'
|
29
25
|
@domain = body['identifier']['value']
|
30
26
|
@status = body['status']
|
27
|
+
assign_challenges(body['challenges'])
|
28
|
+
end
|
29
|
+
|
30
|
+
def assign_challenges(challenges)
|
31
|
+
challenges.each do |attributes|
|
32
|
+
challenge = case attributes.fetch('type')
|
33
|
+
when 'http-01'
|
34
|
+
@http01 ||= HTTP01.new(self)
|
35
|
+
when 'dns-01'
|
36
|
+
@dns01 ||= DNS01.new(self)
|
37
|
+
when 'tls-sni-01'
|
38
|
+
@tls_sni01 ||= TLSSNI01.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
challenge.assign_attributes(attributes) if challenge
|
42
|
+
end
|
31
43
|
end
|
32
44
|
end
|
@@ -1,26 +1,30 @@
|
|
1
1
|
class Acme::Client::Resources::Challenges::Base
|
2
|
-
attr_reader :
|
2
|
+
attr_reader :authorization, :status, :uri, :token, :error
|
3
3
|
|
4
|
-
def initialize(
|
5
|
-
@
|
6
|
-
|
4
|
+
def initialize(authorization)
|
5
|
+
@authorization = authorization
|
6
|
+
end
|
7
|
+
|
8
|
+
def client
|
9
|
+
authorization.client
|
7
10
|
end
|
8
11
|
|
9
12
|
def verify_status
|
10
|
-
|
13
|
+
authorization.verify_status
|
11
14
|
|
12
|
-
assign_attributes(response.body)
|
13
|
-
@error = response.body['error']
|
14
15
|
status
|
15
16
|
end
|
16
17
|
|
17
18
|
def request_verification
|
18
|
-
response =
|
19
|
+
response = client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key)
|
19
20
|
response.success?
|
20
21
|
end
|
21
22
|
|
22
|
-
def
|
23
|
-
|
23
|
+
def assign_attributes(attributes)
|
24
|
+
@status = attributes.fetch('status', 'pending')
|
25
|
+
@uri = attributes.fetch('uri')
|
26
|
+
@token = attributes.fetch('token')
|
27
|
+
@error = attributes['error']
|
24
28
|
end
|
25
29
|
|
26
30
|
private
|
@@ -33,13 +37,7 @@ class Acme::Client::Resources::Challenges::Base
|
|
33
37
|
"#{token}.#{crypto.thumbprint}"
|
34
38
|
end
|
35
39
|
|
36
|
-
def assign_attributes(attributes)
|
37
|
-
@status = attributes.fetch('status', 'pending')
|
38
|
-
@uri = attributes.fetch('uri')
|
39
|
-
@token = attributes.fetch('token')
|
40
|
-
end
|
41
|
-
|
42
40
|
def crypto
|
43
|
-
@crypto ||= Acme::Client::Crypto.new(
|
41
|
+
@crypto ||= Acme::Client::Crypto.new(client.private_key)
|
44
42
|
end
|
45
43
|
end
|
data/lib/acme/client/version.rb
CHANGED
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: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Barbier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|