acme-client 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/unixcharles/acme-client.svg?branch=master)](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
|