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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4adff8940d4acfa29ba2f6992f927ec475f26957
4
- data.tar.gz: 50b37e6582a2cf0ffa1501ce8c31a4ff0589a367
3
+ metadata.gz: 1014fe983481bd49d8eaf71c0f0a746dc0a4ec4b
4
+ data.tar.gz: eb1fe3e04e23b00110454ad06f55c40542fa6254
5
5
  SHA512:
6
- metadata.gz: f5ea7f5637190bb8f22b933ac6f7fb589a9db950e5b2c392bcfa1e109c7d12b33ebc24fd22bca746173d70c8971a3ef927b3626c67d82506cf4a5c7d902fadea
7
- data.tar.gz: 71b5a3660e16344cca2e707a8ead61025b293e470a662c0d13991bf42e72def23b761b04254450ea24b7208fbfd4d362f1c930eceeb1dd0361a003b4586d8b51
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 automation of the acquiring and renewal process.
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 Rubygems:
13
+ Via RubyGems:
13
14
 
14
- $ gem install acme-client
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
- challenge.verify_status # => 'valid'
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 interation against the server simply clone boulder and
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
- assign_challenges(response.body['challenges'])
10
+ @uri = uri
11
11
  assign_attributes(response.body)
12
12
  end
13
13
 
14
- private
14
+ def verify_status
15
+ response = @client.connection.get(@uri)
15
16
 
16
- def assign_challenges(challenges)
17
- challenges.each do |attributes|
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 :client, :status, :uri, :token, :error
2
+ attr_reader :authorization, :status, :uri, :token, :error
3
3
 
4
- def initialize(client, attributes)
5
- @client = client
6
- assign_attributes(attributes)
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
- response = @client.connection.get(@uri)
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 = @client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key)
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 to_h
23
- { 'token' => token, 'uri' => uri, 'type' => challenge_type }
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(@client.private_key)
41
+ @crypto ||= Acme::Client::Crypto.new(client.private_key)
44
42
  end
45
43
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Acme
4
4
  class Client
5
- VERSION = '0.4.1'.freeze
5
+ VERSION = '0.5.0'.freeze
6
6
  end
7
7
  end
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.1
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-08-17 00:00:00.000000000 Z
11
+ date: 2016-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler