acme-client 0.3.0 → 0.3.1
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 +56 -18
- data/acme-client.gemspec +2 -0
- data/lib/acme/client.rb +11 -0
- data/lib/acme/client/resources/challenges/base.rb +4 -0
- data/lib/acme/client/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 056d35b4c628c93ad60a134ab46044cdbc5f9e38
|
|
4
|
+
data.tar.gz: d90a2d072631c7f68e9ea783e1a8878bc844314f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8b93b207b8016d1464f9e9f4fa20e7e303554eee08ac01eed069de34db084318dfb440e78d1ef07978bbd91ae385c8eaae3350b18b5f806c8a170d6bb776a0a
|
|
7
|
+
data.tar.gz: a23f0b2e33d7434abfe4e77bcb51e4d4d867cff629781f820704a484c89c0a6abfab028099542936c539b81c0e11d0abd9a05da4a96485425a91cb1034847ba0
|
data/README.md
CHANGED
|
@@ -3,18 +3,37 @@
|
|
|
3
3
|
|
|
4
4
|
`acme-client` is a client implementation of the [ACME](https://letsencrypt.github.io/acme-spec) protocol in Ruby.
|
|
5
5
|
|
|
6
|
-
You can find the
|
|
6
|
+
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
7
|
|
|
8
|
-
ACME is part of the [Letsencrypt](https://letsencrypt.org/) project,
|
|
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
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Via Rubygems:
|
|
13
|
+
|
|
14
|
+
$ gem install acme-client
|
|
15
|
+
|
|
16
|
+
Or add it to a Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem 'acme-client'
|
|
20
|
+
```
|
|
9
21
|
|
|
10
22
|
## Usage
|
|
11
23
|
|
|
24
|
+
### Register client
|
|
25
|
+
|
|
26
|
+
In order to authenticate our client, we have to create an account for it.
|
|
27
|
+
|
|
12
28
|
```ruby
|
|
13
29
|
# We're going to need a private key.
|
|
14
30
|
require 'openssl'
|
|
15
|
-
private_key = OpenSSL::PKey::RSA.new(
|
|
31
|
+
private_key = OpenSSL::PKey::RSA.new(4096)
|
|
16
32
|
|
|
17
33
|
# We need an ACME server to talk to, see github.com/letsencrypt/boulder
|
|
34
|
+
# WARNING: This endpoint is the production endpoint, which is rate limited and will produce valid certificates.
|
|
35
|
+
# You should probably use the staging endpoint for all your experimentation:
|
|
36
|
+
# endpoint = 'https://acme-staging.api.letsencrypt.org/'
|
|
18
37
|
endpoint = 'https://acme-v01.api.letsencrypt.org/'
|
|
19
38
|
|
|
20
39
|
# Initialize the client
|
|
@@ -24,18 +43,24 @@ client = Acme::Client.new(private_key: private_key, endpoint: endpoint)
|
|
|
24
43
|
# If the private key is not known to the server, we need to register it for the first time.
|
|
25
44
|
registration = client.register(contact: 'mailto:contact@example.com')
|
|
26
45
|
|
|
27
|
-
# You
|
|
46
|
+
# You may need to agree to the terms of service (that's up the to the server to require it or not but boulder does by default)
|
|
28
47
|
registration.agree_terms
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Authorize for domain
|
|
29
51
|
|
|
30
|
-
|
|
52
|
+
Before you are able to obtain certificates for your domain, you have to prove that you are in control of it.
|
|
31
53
|
|
|
32
|
-
|
|
54
|
+
```ruby
|
|
33
55
|
authorization = client.authorize(domain: 'example.org')
|
|
34
56
|
|
|
35
|
-
#
|
|
57
|
+
# This example is using the http-01 challenge type. Other challenges are dns-01 or tls-sni-01.
|
|
36
58
|
challenge = authorization.http01
|
|
37
59
|
|
|
38
|
-
# The http-01 method will require you to
|
|
60
|
+
# The http-01 method will require you to respond to a HTTP request.
|
|
61
|
+
|
|
62
|
+
# You can retrieve the challenge token
|
|
63
|
+
challenge.token # => "some_token"
|
|
39
64
|
|
|
40
65
|
# You can retrieve the expected path for the file.
|
|
41
66
|
challenge.filename # => ".well-known/acme-challenge/:some_token"
|
|
@@ -43,38 +68,51 @@ challenge.filename # => ".well-known/acme-challenge/:some_token"
|
|
|
43
68
|
# You can generate the body of the expected response.
|
|
44
69
|
challenge.file_content # => 'string token and JWK thumbprint'
|
|
45
70
|
|
|
46
|
-
# You
|
|
71
|
+
# You are not required to send a Content-Type. This method will return the right Content-Type should you decide to include one.
|
|
47
72
|
challenge.content_type
|
|
48
73
|
|
|
49
|
-
# Save the file. We'll create a public directory to serve it from, and we'll
|
|
74
|
+
# Save the file. We'll create a public directory to serve it from, and inside it we'll create the challenge file.
|
|
50
75
|
FileUtils.mkdir_p( File.join( 'public', File.dirname( challenge.filename ) ) )
|
|
51
76
|
|
|
52
|
-
#
|
|
77
|
+
# We'll write the content of the file
|
|
53
78
|
File.write( File.join( 'public', challenge.filename), challenge.file_content )
|
|
54
79
|
|
|
55
|
-
#
|
|
56
|
-
|
|
80
|
+
# Optionally save the challenge for use at another time (eg: by a background job processor)
|
|
81
|
+
File.write('challenge', challenge.to_h.to_json)
|
|
82
|
+
|
|
83
|
+
# The challenge file can be served with a Ruby webserver.
|
|
84
|
+
# You can run a webserver in another console for that purpose. You may need to forward ports on your router.
|
|
85
|
+
#
|
|
86
|
+
# $ ruby -run -e httpd public -p 8080 --bind-address 0.0.0.0
|
|
57
87
|
|
|
88
|
+
# Load a saved challenge. This is only required if you need to reuse a saved challenge as outlined above.
|
|
89
|
+
challenge = client.challenge_from_hash(JSON.parse(File.read('challenge')))
|
|
58
90
|
|
|
59
91
|
# Once you are ready to serve the confirmation request you can proceed.
|
|
60
92
|
challenge.request_verification # => true
|
|
61
93
|
challenge.verify_status # => 'pending'
|
|
62
94
|
|
|
63
|
-
# Wait a bit for the server to make the request, or
|
|
95
|
+
# Wait a bit for the server to make the request, or just blink. It should be fast.
|
|
64
96
|
sleep(1)
|
|
65
97
|
|
|
66
98
|
challenge.verify_status # => 'valid'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Obtain a certificate
|
|
67
102
|
|
|
103
|
+
Now that your account is authorized for the domain, you should be able to obtain a certificate for it.
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
68
106
|
# We're going to need a certificate signing request. If not explicitly
|
|
69
107
|
# specified, the first name listed becomes the common name.
|
|
70
108
|
csr = Acme::Client::CertificateRequest.new(names: %w[example.org www.example.org])
|
|
71
109
|
|
|
72
|
-
# We can now request a certificate
|
|
73
|
-
# a valid DER encoded CSR when calling to_der on it
|
|
74
|
-
# OpenSSL::X509::Request too.
|
|
110
|
+
# We can now request a certificate. You can pass anything that returns
|
|
111
|
+
# a valid DER encoded CSR when calling to_der on it. For example an
|
|
112
|
+
# OpenSSL::X509::Request should work too.
|
|
75
113
|
certificate = client.new_certificate(csr) # => #<Acme::Client::Certificate ....>
|
|
76
114
|
|
|
77
|
-
# Save the certificate and key
|
|
115
|
+
# Save the certificate and the private key to files
|
|
78
116
|
File.write("privkey.pem", certificate.request.private_key.to_pem)
|
|
79
117
|
File.write("cert.pem", certificate.to_pem)
|
|
80
118
|
File.write("chain.pem", certificate.chain_to_pem)
|
data/acme-client.gemspec
CHANGED
|
@@ -15,6 +15,8 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
16
16
|
spec.require_paths = ['lib']
|
|
17
17
|
|
|
18
|
+
spec.required_ruby_version = '>= 2.1.0'
|
|
19
|
+
|
|
18
20
|
spec.add_development_dependency 'bundler', '~> 1.6', '>= 1.6.9'
|
|
19
21
|
spec.add_development_dependency 'rake', '~> 10.0'
|
|
20
22
|
spec.add_development_dependency 'rspec', '~> 3.3', '>= 3.3.0'
|
data/lib/acme/client.rb
CHANGED
|
@@ -68,6 +68,17 @@ class Acme::Client
|
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
def challenge_from_hash(attributes)
|
|
72
|
+
case attributes.fetch('type')
|
|
73
|
+
when 'http-01'
|
|
74
|
+
Acme::Client::Resources::Challenges::HTTP01.new(self, attributes)
|
|
75
|
+
when 'dns-01'
|
|
76
|
+
Acme::Client::Resources::Challenges::DNS01.new(self, attributes)
|
|
77
|
+
when 'tls-sni-01'
|
|
78
|
+
Acme::Client::Resources::Challenges::TLSSNI01.new(self, attributes)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
71
82
|
private
|
|
72
83
|
|
|
73
84
|
def fetch_chain(response, limit = 10)
|
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.3.
|
|
4
|
+
version: 0.3.1
|
|
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-04-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -191,7 +191,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
191
191
|
requirements:
|
|
192
192
|
- - ">="
|
|
193
193
|
- !ruby/object:Gem::Version
|
|
194
|
-
version:
|
|
194
|
+
version: 2.1.0
|
|
195
195
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
196
196
|
requirements:
|
|
197
197
|
- - ">="
|