letscert 0.3.1 → 0.4.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 +1 -1
- data/lib/letscert/certificate.rb +28 -20
- data/lib/letscert/io_plugin.rb +48 -6
- data/lib/letscert/runner.rb +4 -2
- data/lib/letscert.rb +1 -1
- data/spec/certificate_spec.rb +61 -19
- data/spec/spec_helper.rb +26 -0
- data/tasks/gem.rake +4 -2
- metadata +36 -10
- data/spec/certificate_spec.rb~ +0 -8
- data/spec/runner_spec.rb~ +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aaf92c6fdc9f696efb73eae2d3a86577b2dd37e
|
4
|
+
data.tar.gz: ec9885ad548b9ff43a645a2990eff2969326d646
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 298d19dda74b12aa3b44d67d80ecbf74f26ed9d68609eb8efe6b82caa5fa7f34e3a48460949350b80c0d4abee890d6b13f0fdf32a89445d4cd25cdbe55c79379
|
7
|
+
data.tar.gz: f2e907cb8c7da052030e5b743419839f45623a2c612e45f0d2e5e042e5ffd2f32480711d15de6bcce09a1717aae1026b30ea1a96ca431b3da307b35621c24e31
|
data/README.md
CHANGED
@@ -61,7 +61,7 @@ letscert -d example.com:/var/www/example.com/html --email my.name@domain.tld --r
|
|
61
61
|
* Renew certificate only if needed.
|
62
62
|
* Only `http-01` challenge supported. An existing web server must be alreay running.
|
63
63
|
`letscert` should have write access to `${webroot}/.well-known/acme-challenge`.
|
64
|
-
* Crontab friendly: no
|
64
|
+
* Crontab friendly: no prompts.
|
65
65
|
* No configuration file.
|
66
66
|
* Support multiple domains with multiple roots. Always create a single certificate per
|
67
67
|
run (ie a certificate may have multiple SANs).
|
data/lib/letscert/certificate.rb
CHANGED
@@ -56,6 +56,7 @@ module LetsCert
|
|
56
56
|
# @option options [Hash] :roots hash associating domains as keys to web roots as
|
57
57
|
# values
|
58
58
|
# @option options [String] :server ACME servel URL
|
59
|
+
# @return [void]
|
59
60
|
def get(account_key, key, options)
|
60
61
|
logger.info {"create key/cert/chain..." }
|
61
62
|
check_roots(options[:roots])
|
@@ -85,8 +86,12 @@ module LetsCert
|
|
85
86
|
|
86
87
|
# Revoke certificate
|
87
88
|
# @param [OpenSSL::PKey::PKey] account_key
|
89
|
+
# @param [Hash] options
|
90
|
+
# @option options [Fixnum] :account_key_size ACME account private key size in bits
|
91
|
+
# @option options [String] :email e-mail used as ACME account
|
92
|
+
# @option options [String] :server ACME servel URL
|
88
93
|
# @return [Boolean]
|
89
|
-
def revoke(account_key, options)
|
94
|
+
def revoke(account_key, options={})
|
90
95
|
if @cert.nil?
|
91
96
|
raise Error, 'no certification data to revoke'
|
92
97
|
end
|
@@ -135,27 +140,12 @@ module LetsCert
|
|
135
140
|
!renewal_necessary?(valid_min)
|
136
141
|
end
|
137
142
|
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
# check webroots.
|
142
|
-
# @param [Hash] roots
|
143
|
-
# @raise [Error] if some domains have no defined root.
|
144
|
-
def check_roots(roots)
|
145
|
-
no_roots = roots.select { |k,v| v.nil? }
|
146
|
-
|
147
|
-
if !no_roots.empty?
|
148
|
-
raise Error, 'root for the following domain(s) are not specified: ' +
|
149
|
-
no_roots.keys.join(', ') + ".\nTry --default_root or use " +
|
150
|
-
'-d example.com:/var/www/html syntax.'
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
143
|
# Get ACME client.
|
155
144
|
#
|
156
145
|
# Client is only created on first call, then it is cached.
|
157
146
|
# @param [Hash] account_key
|
158
147
|
# @param [Hash] options
|
148
|
+
# @return [Acme::Client]
|
159
149
|
def get_acme_client(account_key, options)
|
160
150
|
return @client if @client
|
161
151
|
|
@@ -164,6 +154,8 @@ module LetsCert
|
|
164
154
|
logger.debug { "connect to #{options[:server]}" }
|
165
155
|
@client = Acme::Client.new(private_key: key, endpoint: options[:server])
|
166
156
|
|
157
|
+
yield @client if block_given?
|
158
|
+
|
167
159
|
if options[:email].nil?
|
168
160
|
logger.warn { '--email was not provided. ACME CA will have no way to ' +
|
169
161
|
'contact you!' }
|
@@ -197,9 +189,26 @@ module LetsCert
|
|
197
189
|
@client
|
198
190
|
end
|
199
191
|
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
# check webroots.
|
196
|
+
# @param [Hash] roots
|
197
|
+
# @raise [Error] if some domains have no defined root.
|
198
|
+
def check_roots(roots)
|
199
|
+
no_roots = roots.select { |k,v| v.nil? }
|
200
|
+
|
201
|
+
if !no_roots.empty?
|
202
|
+
raise Error, 'root for the following domain(s) are not specified: ' +
|
203
|
+
no_roots.keys.join(', ') + ".\nTry --default_root or use " +
|
204
|
+
'-d example.com:/var/www/html syntax.'
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
200
208
|
# Generate a new account key if no one is given in +data+
|
201
209
|
# @param [OpenSSL::PKey,nil] key
|
202
|
-
# @param [
|
210
|
+
# @param [Integer] key_size
|
211
|
+
# @return [OpenSSL::PKey::PKey]
|
203
212
|
def get_account_key(key, key_size)
|
204
213
|
if key.nil?
|
205
214
|
logger.info { 'No account key. Generate a new one...' }
|
@@ -260,8 +269,7 @@ module LetsCert
|
|
260
269
|
end
|
261
270
|
end
|
262
271
|
|
263
|
-
# Check if a renewal is necessary
|
264
|
-
# @param [OpenSSL::X509::Certificate] cert
|
272
|
+
# Check if a renewal is necessary
|
265
273
|
# @param [Number] valid_min minimum validity in seconds to ensure
|
266
274
|
# @return [Boolean]
|
267
275
|
def renewal_necessary?(valid_min)
|
data/lib/letscert/io_plugin.rb
CHANGED
@@ -19,7 +19,7 @@
|
|
19
19
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
21
|
# SOFTWARE.
|
22
|
-
require '
|
22
|
+
require 'base64'
|
23
23
|
require_relative 'loggable'
|
24
24
|
|
25
25
|
module LetsCert
|
@@ -86,7 +86,7 @@ module LetsCert
|
|
86
86
|
# @author Sylvain Daubert
|
87
87
|
module FileIOPluginMixin
|
88
88
|
|
89
|
-
# Load data from file named
|
89
|
+
# Load data from file named +#name+
|
90
90
|
# @return [Hash]
|
91
91
|
def load
|
92
92
|
logger.debug { "Loading #@name" }
|
@@ -110,7 +110,7 @@ module LetsCert
|
|
110
110
|
raise NotImplementedError
|
111
111
|
end
|
112
112
|
|
113
|
-
# Save data to file
|
113
|
+
# Save data to file +#name+
|
114
114
|
# @param [Hash] data
|
115
115
|
# @return [void]
|
116
116
|
def save_to_file(data)
|
@@ -134,20 +134,62 @@ module LetsCert
|
|
134
134
|
# @author Sylvain Daubert
|
135
135
|
module JWKIOPluginMixin
|
136
136
|
|
137
|
+
# Encode string +data+ to base64
|
138
|
+
# @param [String] data
|
139
|
+
# @return [String]
|
140
|
+
def urlsafe_encode64(data)
|
141
|
+
Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
|
142
|
+
end
|
143
|
+
|
144
|
+
# Decode base64 string +data+
|
145
|
+
# @param [String] data
|
146
|
+
# @return [String]
|
147
|
+
def urlsafe_decode64(data)
|
148
|
+
Base64.urlsafe_decode64(data.sub(/[\s=]*\z/, ''))
|
149
|
+
end
|
150
|
+
|
137
151
|
# Load crypto data from JSON-encoded file
|
138
152
|
# @param [String] data JSON-encoded data
|
139
|
-
# @return [
|
153
|
+
# @return [OpenSSL::PKey::PKey]
|
140
154
|
def load_jwk(data)
|
141
155
|
return nil if data.empty?
|
142
156
|
|
143
|
-
JSON
|
157
|
+
h = JSON.parse(data)
|
158
|
+
case h['kty']
|
159
|
+
when 'RSA'
|
160
|
+
pkey = OpenSSL::PKey::RSA.new
|
161
|
+
%w(e n d p q).collect do |key|
|
162
|
+
next if h[key].nil?
|
163
|
+
value = OpenSSL::BN.new(urlsafe_decode64(h[key]), 2)
|
164
|
+
pkey.send "#{key}=".to_sym, value
|
165
|
+
end
|
166
|
+
else
|
167
|
+
raise Error, "unknown account key type '#{k['kty']}'"
|
168
|
+
end
|
169
|
+
|
170
|
+
pkey
|
144
171
|
end
|
145
172
|
|
146
173
|
# Dump crypto data (key) to a JSON-encoded string
|
147
174
|
# @param [OpenSSL::PKey] key
|
148
175
|
# @return [String]
|
149
176
|
def dump_jwk(key)
|
150
|
-
key.
|
177
|
+
return {}.to_json if key.nil?
|
178
|
+
|
179
|
+
h = { 'kty' => 'RSA' }
|
180
|
+
case key
|
181
|
+
when OpenSSL::PKey::RSA
|
182
|
+
h['e'] = urlsafe_encode64(key.e.to_s(2)) if key.e
|
183
|
+
h['n'] = urlsafe_encode64(key.n.to_s(2)) if key.n
|
184
|
+
if key.private?
|
185
|
+
h['d'] = urlsafe_encode64(key.d.to_s(2))
|
186
|
+
h['p'] = urlsafe_encode64(key.p.to_s(2))
|
187
|
+
h['q'] = urlsafe_encode64(key.q.to_s(2))
|
188
|
+
end
|
189
|
+
else
|
190
|
+
raise Error, "only RSA keys are supported"
|
191
|
+
end
|
192
|
+
h.to_json
|
151
193
|
end
|
152
194
|
end
|
153
195
|
|
data/lib/letscert/runner.rb
CHANGED
@@ -203,8 +203,10 @@ module LetsCert
|
|
203
203
|
end
|
204
204
|
|
205
205
|
rescue Error, Acme::Client::Error => ex
|
206
|
-
|
207
|
-
|
206
|
+
msg = ex.message
|
207
|
+
msg = "[Acme] #{msg}" if ex.is_a?(Acme::Client::Error)
|
208
|
+
@logger.error msg
|
209
|
+
puts "Error: #{msg}"
|
208
210
|
RETURN_ERROR
|
209
211
|
end
|
210
212
|
end
|
data/lib/letscert.rb
CHANGED
data/spec/certificate_spec.rb
CHANGED
@@ -44,9 +44,11 @@ module LetsCert
|
|
44
44
|
ARGV << '-d' << 'example.com:/var/ww/html'
|
45
45
|
ARGV << '--server' << 'https://acme-staging.api.letsencrypt.org/directory'
|
46
46
|
runner.parse_options
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
VCR.use_cassette('single-domain') do
|
48
|
+
# raise error because no e-mail address was given
|
49
|
+
expect { certificate.get(nil, nil, runner.options) }.
|
50
|
+
to raise_error(Acme::Client::Error)
|
51
|
+
end
|
50
52
|
|
51
53
|
ARGV.clear
|
52
54
|
ARGV << '-d' << 'example.com:/var/www/html'
|
@@ -64,9 +66,11 @@ module LetsCert
|
|
64
66
|
ARGV << '--default-root' << '/opt/www'
|
65
67
|
ARGV << '--server' << 'https://acme-staging.api.letsencrypt.org/directory'
|
66
68
|
runner.parse_options
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
VCR.use_cassette('default-root') do
|
70
|
+
# raise error because no e-mail address was given
|
71
|
+
expect { certificate.get(nil, nil, runner.options) }.
|
72
|
+
to raise_error(Acme::Client::Error)
|
73
|
+
end
|
70
74
|
expect(runner.options[:roots]['example.com']).to eq('/var/www/html')
|
71
75
|
expect(runner.options[:roots]['www.example.com']).to eq('/opt/www')
|
72
76
|
end
|
@@ -74,9 +78,11 @@ module LetsCert
|
|
74
78
|
it 'uses existing account key' do
|
75
79
|
options = { roots: { 'example.com' => '/var/www/html' } }
|
76
80
|
|
77
|
-
|
78
|
-
|
79
|
-
|
81
|
+
VCR.use_cassette('no-server') do
|
82
|
+
# Connection error: no server to connect to
|
83
|
+
expect { certificate.get(@account_key2048, nil, options) }.
|
84
|
+
to raise_error(Faraday::ConnectionFailed)
|
85
|
+
end
|
80
86
|
expect(certificate.client.private_key).to eq(@account_key2048)
|
81
87
|
end
|
82
88
|
|
@@ -86,9 +92,11 @@ module LetsCert
|
|
86
92
|
account_key_size: 128,
|
87
93
|
}
|
88
94
|
|
89
|
-
|
90
|
-
|
91
|
-
|
95
|
+
VCR.use_cassette('no-server') do
|
96
|
+
# Connection error: no server to connect to
|
97
|
+
expect { certificate.get(nil, nil, options) }.
|
98
|
+
to raise_error(Faraday::ConnectionFailed)
|
99
|
+
end
|
92
100
|
expect(certificate.client.private_key).to be_a(OpenSSL::PKey::RSA)
|
93
101
|
end
|
94
102
|
|
@@ -98,9 +106,11 @@ module LetsCert
|
|
98
106
|
server: 'https://acme-staging.api.letsencrypt.org/directory',
|
99
107
|
}
|
100
108
|
|
101
|
-
|
102
|
-
|
103
|
-
|
109
|
+
VCR.use_cassette('create-acme-client') do
|
110
|
+
# Acme error: not valid e-mail address
|
111
|
+
expect { certificate.get(@account_key2048, nil, options) }.
|
112
|
+
to raise_error(Acme::Client::Error)
|
113
|
+
end
|
104
114
|
expect(certificate.client.private_key).to eq(@account_key2048)
|
105
115
|
expect(certificate.client.instance_eval { @endpoint }).to eq(options[:server])
|
106
116
|
end
|
@@ -111,12 +121,44 @@ module LetsCert
|
|
111
121
|
server: 'https://acme-staging.api.letsencrypt.org/directory',
|
112
122
|
}
|
113
123
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
124
|
+
VCR.use_cassette('create-acme-client-but-bad-email') do
|
125
|
+
# Acme error: not valid e-mail address
|
126
|
+
expect { certificate.get(@account_key2048, nil, options) }.
|
127
|
+
to raise_error(Acme::Client::Error).
|
128
|
+
with_message('not a valid e-mail address')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'responds to HTTP-01 challenge'
|
133
|
+
|
134
|
+
it 'raises if HTTP-01 challenge is unavailable' do
|
135
|
+
options = {
|
136
|
+
roots: { 'example.com' => '/var/www/html' },
|
137
|
+
server: 'https://acme-staging.api.letsencrypt.org/directory',
|
138
|
+
email: 'test@example.org',
|
139
|
+
}
|
140
|
+
|
141
|
+
VCR.use_cassette('no-http-01-challenge') do
|
142
|
+
certificate.get_acme_client(@account_key2048, options) do |client|
|
143
|
+
client.connection.builder.insert 0, RemoveHttp01Middleware
|
144
|
+
end
|
145
|
+
expect { certificate.get(@account_key2048, nil, options) }.
|
146
|
+
to raise_error(LetsCert::Error).with_message(/not offer http-01/)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'reuses existing private key if --reuse-key is present'
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
context '#revoke' do
|
155
|
+
it 'raises if no certificate is given' do
|
156
|
+
certificate = Certificate.new(nil)
|
157
|
+
expect { certificate.revoke(@account_key2048) }.
|
158
|
+
to raise_error(LetsCert::Error)
|
118
159
|
end
|
119
160
|
|
161
|
+
it 'revokes an existing certificate'
|
120
162
|
end
|
121
163
|
|
122
164
|
context '#valid?' do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,2 +1,28 @@
|
|
1
1
|
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
2
2
|
require 'letscert'
|
3
|
+
|
4
|
+
require 'vcr'
|
5
|
+
require 'faraday'
|
6
|
+
|
7
|
+
VCR.configure do |config|
|
8
|
+
config.cassette_library_dir = "fixtures/vcr_cassettes"
|
9
|
+
config.hook_into :faraday
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
# Faraday Middleware to remove HTTP-01 challenge
|
14
|
+
class RemoveHttp01Middleware < Faraday::Middleware
|
15
|
+
def call(request_env)
|
16
|
+
@app.call(request_env).on_complete do |response_env|
|
17
|
+
body = response_env.response.body
|
18
|
+
if body['challenges'] and !body['challenges'].empty?
|
19
|
+
body['challenges'].each_with_index do |challenge, index|
|
20
|
+
if challenge['type'] == 'http-01'
|
21
|
+
body['challenges'].delete_at(index)
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/tasks/gem.rake
CHANGED
@@ -23,10 +23,12 @@ EOF
|
|
23
23
|
|
24
24
|
s.required_ruby_version = '>= 2.1.0'
|
25
25
|
|
26
|
-
s.add_dependency 'acme-client', '~>0.
|
27
|
-
s.add_dependency 'json
|
26
|
+
s.add_dependency 'acme-client', '~>0.4.0'
|
27
|
+
s.add_dependency 'json', '~>1.8.3'
|
28
28
|
|
29
29
|
s.add_development_dependency 'rspec', '~>3.4'
|
30
|
+
s.add_development_dependency 'vcr', '~>3.0'
|
31
|
+
s.add_development_dependency 'faraday', '~>0.9'
|
30
32
|
s.add_development_dependency 'yard', '~>0.8'
|
31
33
|
end
|
32
34
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: letscert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sylvain Daubert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: acme-client
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.4.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.4.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: json
|
28
|
+
name: json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 1.8.3
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 1.8.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: vcr
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: faraday
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.9'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: yard
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -88,7 +116,6 @@ files:
|
|
88
116
|
- spec/cert.der
|
89
117
|
- spec/cert.pem
|
90
118
|
- spec/certificate_spec.rb
|
91
|
-
- spec/certificate_spec.rb~
|
92
119
|
- spec/chain.pem
|
93
120
|
- spec/fullchain.pem
|
94
121
|
- spec/io_plugin_spec.rb
|
@@ -96,7 +123,6 @@ files:
|
|
96
123
|
- spec/key.pem
|
97
124
|
- spec/loggable_spec.rb
|
98
125
|
- spec/runner_spec.rb
|
99
|
-
- spec/runner_spec.rb~
|
100
126
|
- spec/spec_helper.rb
|
101
127
|
- spec/test.json
|
102
128
|
- tasks/gem.rake
|
@@ -122,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
148
|
version: '0'
|
123
149
|
requirements: []
|
124
150
|
rubyforge_project:
|
125
|
-
rubygems_version: 2.
|
151
|
+
rubygems_version: 2.5.1
|
126
152
|
signing_key:
|
127
153
|
specification_version: 4
|
128
154
|
summary: letscert, a simple Let's Encrypt client
|
data/spec/certificate_spec.rb~
DELETED