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 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