acme-pki 0.1.5 → 0.2.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 +5 -5
- data/.gitignore +1 -5
- data/Gemfile.lock +47 -0
- data/acme-pki.gemspec +6 -4
- data/bin/letsencrypt +1 -1
- data/lib/acme/pki.rb +281 -261
- data/lib/acme/pki/version.rb +1 -1
- metadata +40 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7036c788bf8330ed986f1e6ec6bb84d6ae308f6dff67e842d0c7e337009b9bc6
|
4
|
+
data.tar.gz: 8b60428b4d37fbccd4585cfb5698f17c1640994f4069571428c181685d408f75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ad171c2a0a7d66e2e9519a7a909b4114623ed195cafebd15c5190923a382fa354f532b2df0af234dd4ed71e2e1b323b6d0ef87ba3c538fa0e9e13f9be43c4e0
|
7
|
+
data.tar.gz: 84b46c58d2e186f760f686bd92dadf13d7aa3c091e11138297f7215f0ca7698a0c81bf02e6d93c1c2c6ebef61223f22c79008d95cb1a642956a32ca2adef098d
|
data/.gitignore
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
acme-pki (0.1.5)
|
5
|
+
acme-client (~> 2.0.5)
|
6
|
+
colorize (~> 0.8.1)
|
7
|
+
faraday_middleware (~> 0.13.1)
|
8
|
+
simpleidn (~> 0.1.1)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
acme-client (2.0.5)
|
14
|
+
faraday (~> 0.9, >= 0.9.1)
|
15
|
+
awesome_print (1.8.0)
|
16
|
+
byebug (11.0.1)
|
17
|
+
coderay (1.1.2)
|
18
|
+
colorize (0.8.1)
|
19
|
+
faraday (0.17.0)
|
20
|
+
multipart-post (>= 1.2, < 3)
|
21
|
+
faraday_middleware (0.13.1)
|
22
|
+
faraday (>= 0.7.4, < 1.0)
|
23
|
+
method_source (0.9.2)
|
24
|
+
multipart-post (2.1.1)
|
25
|
+
pry (0.12.2)
|
26
|
+
coderay (~> 1.1.0)
|
27
|
+
method_source (~> 0.9.0)
|
28
|
+
pry-byebug (3.7.0)
|
29
|
+
byebug (~> 11.0)
|
30
|
+
pry (~> 0.10)
|
31
|
+
simpleidn (0.1.1)
|
32
|
+
unf (~> 0.1.4)
|
33
|
+
unf (0.1.4)
|
34
|
+
unf_ext
|
35
|
+
unf_ext (0.0.7.6)
|
36
|
+
|
37
|
+
PLATFORMS
|
38
|
+
ruby
|
39
|
+
|
40
|
+
DEPENDENCIES
|
41
|
+
acme-pki!
|
42
|
+
awesome_print (~> 1.8.0)
|
43
|
+
bundler (~> 2.0.2)
|
44
|
+
pry-byebug (~> 3.7.0)
|
45
|
+
|
46
|
+
BUNDLED WITH
|
47
|
+
2.0.2
|
data/acme-pki.gemspec
CHANGED
@@ -18,10 +18,12 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep %r{^(test|spec|features)/}
|
19
19
|
spec.require_paths = %w(lib)
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler', '~>
|
21
|
+
spec.add_development_dependency 'bundler', '~> 2.0.2'
|
22
|
+
spec.add_development_dependency 'awesome_print', '~> 1.8.0'
|
23
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.7.0'
|
22
24
|
|
23
|
-
spec.add_dependency 'acme-client', '~> 0.5
|
24
|
-
spec.add_dependency 'faraday_middleware', '~> 0.
|
25
|
+
spec.add_dependency 'acme-client', '~> 2.0.5'
|
26
|
+
spec.add_dependency 'faraday_middleware', '~> 0.13.1'
|
25
27
|
spec.add_dependency 'colorize', '~> 0.8.1'
|
26
|
-
spec.add_dependency 'simpleidn', '~> 0.
|
28
|
+
spec.add_dependency 'simpleidn', '~> 0.1.1'
|
27
29
|
end
|
data/bin/letsencrypt
CHANGED
@@ -35,7 +35,7 @@ case ARGV.shift
|
|
35
35
|
end
|
36
36
|
pki.register ARGV.first
|
37
37
|
when 'key'
|
38
|
-
options = OpenStruct.new type: Acme::PKI::
|
38
|
+
options = OpenStruct.new type: Acme::PKI::DEFAULT_KEY_TYPE
|
39
39
|
OptionParser.new do |opts|
|
40
40
|
opts.banner = "Usage: #{File.basename __FILE__} key <domain> [options]"
|
41
41
|
opts.on('-r [KEYSIZE]', '--rsa [KEYSIZE]', 'RSA key, key size') { |k| options.type = [:rsa, k.to_i] }
|
data/lib/acme/pki.rb
CHANGED
@@ -13,265 +13,285 @@ require 'acme/pki/information'
|
|
13
13
|
require 'acme/pki/version'
|
14
14
|
|
15
15
|
module Acme
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
16
|
+
class PKI
|
17
|
+
include Information
|
18
|
+
|
19
|
+
if ENV['ACME_STAGING']
|
20
|
+
puts 'Using Let\'s Encrypt ACME staging'.colorize :yellow
|
21
|
+
ENV['ACME_ENDPOINT'] = 'https://acme-staging-v02.api.letsencrypt.org/directory'
|
22
|
+
ENV['ACME_ACCOUNT_KEY'] = 'account-staging'
|
23
|
+
end
|
24
|
+
DEFAULT_ENDPOINT = ENV['ACME_ENDPOINT'] || 'https://acme-v02.api.letsencrypt.org/directory'
|
25
|
+
DEFAULT_DIRECTORY = ENV['ACME_DIRECTORY'] || Dir.pwd
|
26
|
+
DEFAULT_ACCOUNT_KEY = ENV['ACME_ACCOUNT_KEY'] || 'account'
|
27
|
+
DEFAULT_ACCOUNT_KEY_TYPE = [:rsa, 4096].freeze
|
28
|
+
DEFAULT_KEY_TYPE = [:ecc, 'prime256v1'].freeze
|
29
|
+
DEFAULT_RENEW_DURATION = 60 * 60 * 24 * 30 # 1 month
|
30
|
+
|
31
|
+
def initialize(directory: DEFAULT_DIRECTORY, account_key: DEFAULT_ACCOUNT_KEY, endpoint: DEFAULT_ENDPOINT)
|
32
|
+
@directory = directory
|
33
|
+
@challenge_dir = ENV['ACME_CHALLENGE'] || File.join(@directory, 'acme-challenge')
|
34
|
+
@account_key_file = self.key DEFAULT_ACCOUNT_KEY, 'key'
|
35
|
+
@account_key = if File.exists? @account_key_file
|
36
|
+
open(@account_key_file, 'r') { |f| OpenSSL::PKey.read f }
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
@endpoint = endpoint
|
41
|
+
end
|
42
|
+
|
43
|
+
def key(name, extension = 'pem')
|
44
|
+
self.file name, extension
|
45
|
+
end
|
46
|
+
|
47
|
+
def csr(name)
|
48
|
+
self.file name, 'csr'
|
49
|
+
end
|
50
|
+
|
51
|
+
def crt(name)
|
52
|
+
self.file name, 'crt'
|
53
|
+
end
|
54
|
+
|
55
|
+
def register(mail)
|
56
|
+
@account_key_file, @account_key = self.generate_key DEFAULT_ACCOUNT_KEY, DEFAULT_ACCOUNT_KEY_TYPE, 'key'
|
57
|
+
tos = self.client.meta['termsOfService']
|
58
|
+
$stdout.print "Are you agree with Let's Encrypt terms of service available at #{tos}? [yN] "
|
59
|
+
$stdout.flush
|
60
|
+
accept = $stdin.gets.chomp.downcase == 'y'
|
61
|
+
exit unless accept
|
62
|
+
self.process("Registering account key #{@account_key_file}") do
|
63
|
+
self.client.new_account contact: "mailto:#{mail}", terms_of_service_agreed: accept
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_key(name, type = DEFAULT_KEY_TYPE, extension = 'pem')
|
68
|
+
key_file = self.key name, extension
|
69
|
+
type, size = type
|
70
|
+
|
71
|
+
log = case type
|
72
|
+
when :rsa
|
73
|
+
"RSA #{size} bits"
|
74
|
+
when :ecc
|
75
|
+
"ECC #{size} curve"
|
76
|
+
end
|
77
|
+
|
78
|
+
key = self.process "Generating #{log} private key into #{key_file}" do
|
79
|
+
key = case type
|
80
|
+
when :rsa
|
81
|
+
OpenSSL::PKey::RSA.new size
|
82
|
+
when :ecc
|
83
|
+
OpenSSL::PKey::EC.new(size).generate_key
|
84
|
+
end
|
85
|
+
open(key_file, 'w') { |f| f.write key.to_pem }
|
86
|
+
key
|
87
|
+
end
|
88
|
+
self.key_info key
|
89
|
+
[key_file, key]
|
90
|
+
end
|
91
|
+
|
92
|
+
def generate_csr(csr, domains: [], key: nil)
|
93
|
+
key = csr unless key
|
94
|
+
domains = [csr, *domains].collect { |d| SimpleIDN.to_ascii d }
|
95
|
+
csr_file = self.csr csr
|
96
|
+
key_file = self.key key
|
97
|
+
|
98
|
+
self.generate_key key unless File.exist? key_file
|
99
|
+
|
100
|
+
self.process "Generating CSR for #{domains.join ', '} with key #{key_file} into #{csr_file}" do
|
101
|
+
key_file = open(key_file, 'r') { |f| OpenSSL::PKey.read f }
|
102
|
+
csr = OpenSSL::X509::Request.new
|
103
|
+
csr.subject = OpenSSL::X509::Name.parse "/CN=#{domains.first}"
|
104
|
+
|
105
|
+
public_key = case key_file
|
106
|
+
when OpenSSL::PKey::EC
|
107
|
+
curve = key_file.group.curve_name
|
108
|
+
public = OpenSSL::PKey::EC.new curve
|
109
|
+
public.public_key = key_file.public_key
|
110
|
+
|
111
|
+
public
|
112
|
+
else
|
113
|
+
key_file.public_key
|
114
|
+
end
|
115
|
+
csr.public_key = public_key
|
116
|
+
|
117
|
+
factory = OpenSSL::X509::ExtensionFactory.new
|
118
|
+
extensions = []
|
119
|
+
#extensions << factory.create_extension('basicConstraints', 'CA:FALSE', true)
|
120
|
+
extensions << factory.create_extension('keyUsage', 'digitalSignature,nonRepudiation,keyEncipherment')
|
121
|
+
extensions << factory.create_extension('subjectAltName', domains.collect { |d| "DNS:#{d}" }.join(', '))
|
122
|
+
|
123
|
+
extensions = OpenSSL::ASN1::Sequence extensions
|
124
|
+
extensions = OpenSSL::ASN1::Set [extensions]
|
125
|
+
csr.add_attribute OpenSSL::X509::Attribute.new 'extReq', extensions
|
126
|
+
|
127
|
+
csr.sign key_file, OpenSSL::Digest::SHA512.new
|
128
|
+
open(csr_file, 'w') { |f| f.write csr.to_pem }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def generate_crt(crt, csr: nil)
|
133
|
+
csr = crt unless csr
|
134
|
+
short_csr = csr
|
135
|
+
crt = self.crt crt
|
136
|
+
csr = self.csr csr
|
137
|
+
self.generate_csr short_csr unless File.exist? csr
|
138
|
+
self.internal_generate_crt crt, csr: csr
|
139
|
+
end
|
140
|
+
|
141
|
+
def renew(crt, csr: nil, duration: DEFAULT_RENEW_DURATION)
|
142
|
+
csr = crt unless csr
|
143
|
+
crt = self.crt crt
|
144
|
+
csr = self.csr csr
|
145
|
+
puts "Renewing #{crt} CRT from #{csr} CSR"
|
146
|
+
|
147
|
+
if File.exists? crt
|
148
|
+
x509 = OpenSSL::X509::Certificate.new File.read crt
|
149
|
+
delay = x509.not_after - Time.now
|
150
|
+
if delay > duration
|
151
|
+
puts "No need to renew (#{humanize delay})"
|
152
|
+
return false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
self.internal_generate_crt crt, csr: csr
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
160
|
+
def client
|
161
|
+
unless @account_key
|
162
|
+
puts 'No account key available'.colorize :yellow
|
163
|
+
puts 'Please register yourself before'.colorize :red
|
164
|
+
exit -1
|
165
|
+
end
|
166
|
+
@client ||= Acme::Client.new private_key: @account_key, directory: @endpoint
|
167
|
+
end
|
168
|
+
|
169
|
+
def process(line, io: STDOUT)
|
170
|
+
io.print "#{line}..."
|
171
|
+
io.flush
|
172
|
+
|
173
|
+
result = yield
|
174
|
+
|
175
|
+
io.puts " [#{'OK'.colorize :green}]"
|
176
|
+
|
177
|
+
result
|
178
|
+
rescue Exception
|
179
|
+
io.puts " [#{'KO'.colorize :red}]"
|
180
|
+
raise
|
181
|
+
end
|
182
|
+
|
183
|
+
def file(name, extension = nil)
|
184
|
+
return nil unless name
|
185
|
+
name = name.split('.').reverse.join('.')
|
186
|
+
name = "#{name}.#{extension}" if extension
|
187
|
+
File.join @directory, name
|
188
|
+
end
|
189
|
+
|
190
|
+
def domains(csr)
|
191
|
+
domains = []
|
192
|
+
|
193
|
+
cn = csr.subject.to_a.first { |n, _, _| n == 'CN' }
|
194
|
+
domains << cn[1] if cn
|
195
|
+
|
196
|
+
attribute = csr.attributes.detect { |a| %w(extReq msExtReq).include? a.oid }
|
197
|
+
if attribute
|
198
|
+
set = OpenSSL::ASN1.decode attribute.value
|
199
|
+
seq = set.value.first
|
200
|
+
sans = seq.value.collect { |s| OpenSSL::X509::Extension.new(s).to_a }
|
201
|
+
.detect { |e| e.first == 'subjectAltName' }
|
202
|
+
if sans
|
203
|
+
sans = sans[1]
|
204
|
+
sans = sans.split(/\s*,\s*/)
|
205
|
+
.collect { |s| s.split /\s*:\s*/ }
|
206
|
+
.select { |t, _| t == 'DNS' }
|
207
|
+
.collect { |_, v| v }
|
208
|
+
domains.concat sans
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
domains.uniq
|
213
|
+
end
|
214
|
+
|
215
|
+
def authorize(authorization)
|
216
|
+
domain = authorization.domain
|
217
|
+
challenge = authorization.http
|
218
|
+
status = challenge.status
|
219
|
+
if status == 'valid'
|
220
|
+
puts "Domain #{domain.colorize :green} already authorized"
|
221
|
+
return
|
222
|
+
end
|
223
|
+
puts "Authorizing domain #{domain.colorize :yellow} (current status: #{status.colorize :yellow})"
|
224
|
+
|
225
|
+
unless Dir.exists? @challenge_dir
|
226
|
+
self.process "Creating directory #{@challenge_dir}" do
|
227
|
+
FileUtils.mkdir_p @challenge_dir
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
filename = challenge.token
|
232
|
+
file = File.join @challenge_dir, filename
|
233
|
+
content = challenge.file_content
|
234
|
+
self.process "Writing challenge for #{domain.colorize :yellow} into #{file.colorize :yellow}" do
|
235
|
+
File.write file, content
|
236
|
+
end
|
237
|
+
|
238
|
+
url = "http://#{domain}/.well-known/acme-challenge/#{filename}"
|
239
|
+
self.process "Test challenge for #{url.colorize :yellow}" do
|
240
|
+
response = begin
|
241
|
+
Faraday.new do |conn|
|
242
|
+
conn.use FaradayMiddleware::FollowRedirects
|
243
|
+
conn.adapter Faraday.default_adapter
|
244
|
+
end.get url
|
245
|
+
rescue => e
|
246
|
+
raise Exception, e.message
|
247
|
+
end
|
248
|
+
raise Exception, "Got response code #{response.status.to_s.colorize :red}" unless response.success?
|
249
|
+
real_content = response.body
|
250
|
+
raise Exception, "Got #{real_content.colorize :red}, expected #{content.colorize :green}" unless real_content == content
|
251
|
+
end
|
252
|
+
|
253
|
+
self.process "Authorizing domain #{domain.colorize :yellow}" do
|
254
|
+
challenge.request_validation
|
255
|
+
status = nil
|
256
|
+
60.times do
|
257
|
+
sleep 1
|
258
|
+
challenge.reload
|
259
|
+
status = challenge.status
|
260
|
+
break if status != 'pending'
|
261
|
+
end
|
262
|
+
|
263
|
+
raise Exception, "Got status #{status.colorize :red} instead of valid" unless status == 'valid'
|
264
|
+
end
|
265
|
+
|
266
|
+
File.unlink file
|
267
|
+
end
|
268
|
+
|
269
|
+
def internal_generate_crt(crt, csr: nil)
|
270
|
+
csr = crt unless csr
|
271
|
+
csr_file = csr
|
272
|
+
csr = OpenSSL::X509::Request.new File.read csr
|
273
|
+
domains = self.domains csr
|
274
|
+
|
275
|
+
order = client.new_order identifiers: domains
|
276
|
+
order.authorizations.each { |a| self.authorize a }
|
277
|
+
|
278
|
+
crt = self.process "Generating CRT #{crt} from CSR #{csr_file}" do
|
279
|
+
order.finalize csr: csr
|
280
|
+
certificate = order.certificate
|
281
|
+
File.write crt, certificate
|
282
|
+
OpenSSL::X509::Certificate.new certificate
|
283
|
+
end
|
284
|
+
|
285
|
+
self.certifificate_info crt
|
286
|
+
end
|
287
|
+
|
288
|
+
def humanize(secs)
|
289
|
+
[[60, :seconds], [60, :minutes], [24, :hours], [30, :days], [12, :months]].map { |count, name|
|
290
|
+
if secs > 0
|
291
|
+
secs, n = secs.divmod count
|
292
|
+
"#{n.to_i} #{name}"
|
293
|
+
end
|
294
|
+
}.compact.reverse.join(' ')
|
295
|
+
end
|
296
|
+
end
|
277
297
|
end
|
data/lib/acme/pki/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acme-pki
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aeris
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,42 +16,70 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.0.2
|
20
20
|
type: :development
|
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:
|
26
|
+
version: 2.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: awesome_print
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.8.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.8.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.7.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.7.0
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: acme-client
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
30
58
|
requirements:
|
31
59
|
- - "~>"
|
32
60
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.5
|
61
|
+
version: 2.0.5
|
34
62
|
type: :runtime
|
35
63
|
prerelease: false
|
36
64
|
version_requirements: !ruby/object:Gem::Requirement
|
37
65
|
requirements:
|
38
66
|
- - "~>"
|
39
67
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.5
|
68
|
+
version: 2.0.5
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: faraday_middleware
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
73
|
- - "~>"
|
46
74
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
75
|
+
version: 0.13.1
|
48
76
|
type: :runtime
|
49
77
|
prerelease: false
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
51
79
|
requirements:
|
52
80
|
- - "~>"
|
53
81
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
82
|
+
version: 0.13.1
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: colorize
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +100,14 @@ dependencies:
|
|
72
100
|
requirements:
|
73
101
|
- - "~>"
|
74
102
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.
|
103
|
+
version: 0.1.1
|
76
104
|
type: :runtime
|
77
105
|
prerelease: false
|
78
106
|
version_requirements: !ruby/object:Gem::Requirement
|
79
107
|
requirements:
|
80
108
|
- - "~>"
|
81
109
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.
|
110
|
+
version: 0.1.1
|
83
111
|
description: Manage your keys, requests and certificates.
|
84
112
|
email:
|
85
113
|
- aeris@imirhil.fr
|
@@ -90,6 +118,7 @@ extra_rdoc_files: []
|
|
90
118
|
files:
|
91
119
|
- ".gitignore"
|
92
120
|
- Gemfile
|
121
|
+
- Gemfile.lock
|
93
122
|
- LICENSE
|
94
123
|
- README.md
|
95
124
|
- acme-pki.gemspec
|
@@ -117,8 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
146
|
- !ruby/object:Gem::Version
|
118
147
|
version: '0'
|
119
148
|
requirements: []
|
120
|
-
|
121
|
-
rubygems_version: 2.6.11
|
149
|
+
rubygems_version: 3.0.3
|
122
150
|
signing_key:
|
123
151
|
specification_version: 4
|
124
152
|
summary: Ruby client for Let's Encrypt
|