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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 199f39943a323bd98c3b300c6fb36cf1502755b9
4
- data.tar.gz: 0d59e672b06a208dff74d20513b5a635ac722324
2
+ SHA256:
3
+ metadata.gz: 7036c788bf8330ed986f1e6ec6bb84d6ae308f6dff67e842d0c7e337009b9bc6
4
+ data.tar.gz: 8b60428b4d37fbccd4585cfb5698f17c1640994f4069571428c181685d408f75
5
5
  SHA512:
6
- metadata.gz: 413f3dc9fdcea80c831287d2e12b7ac8addb7b854e0ba99d851e939ab59235f6ba64e8e8bfaca9a88f4a1961e8104ba864bbe7b733f736392a036d8cc5024d36
7
- data.tar.gz: 301f8476f672f6d994589632755ca4ba4c871c22b44486ec34be5db852c1eaa812052cef582c127febf5b871699e18e2015248d9493be6235aed9715996be5b1
6
+ metadata.gz: 2ad171c2a0a7d66e2e9519a7a909b4114623ed195cafebd15c5190923a382fa354f532b2df0af234dd4ed71e2e1b323b6d0ef87ba3c538fa0e9e13f9be43c4e0
7
+ data.tar.gz: 84b46c58d2e186f760f686bd92dadf13d7aa3c091e11138297f7215f0ca7698a0c81bf02e6d93c1c2c6ebef61223f22c79008d95cb1a642956a32ca2adef098d
data/.gitignore CHANGED
@@ -1,7 +1,3 @@
1
1
  *.gem
2
2
  *.iml
3
- *.key
4
- *.pem
5
- *.crt
6
- *.csr
7
- Gemfile.lock
3
+ pki/
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', '~> 1.11'
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.5'
24
- spec.add_dependency 'faraday_middleware', '~> 0.11.0.1'
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.0.7'
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::DEFAULT_KEY
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
- class PKI
17
- include Information
18
-
19
- DEFAULT_ENDPOINT = ENV['ACME_ENDPOINT'] || 'https://acme-v01.api.letsencrypt.org/'
20
- DEFAULT_DIRECTORY = ENV['ACME_DIRECTORY'] || Dir.pwd
21
- DEFAULT_ACCOUNT_KEY = ENV['ACME_ACCOUNT_KEY'] || 'account.key'
22
- DEFAULT_KEY = [:ecc, 'prime256v1'].freeze
23
- DEFAULT_RENEW_DURATION = 60 * 60 * 24 * 30 # 1 month
24
-
25
- def initialize(directory: DEFAULT_DIRECTORY, account_key: DEFAULT_ACCOUNT_KEY, endpoint: DEFAULT_ENDPOINT)
26
- @directory = directory
27
- @challenge_dir = ENV['ACME_CHALLENGE'] || File.join(@directory, 'acme-challenge')
28
- @account_key_file = File.join @directory, account_key
29
- @account_key = if File.exists? @account_key_file
30
- open(@account_key_file, 'r') { |f| OpenSSL::PKey.read f }
31
- else
32
- nil
33
- end
34
- @endpoint = endpoint
35
- end
36
-
37
- def key(name)
38
- file name, 'pem'
39
- end
40
-
41
- def csr(name)
42
- file name, 'csr'
43
- end
44
-
45
- def crt(name)
46
- file name, 'crt'
47
- end
48
-
49
- def register(mail)
50
- process("Generating RSA 4096 bits account key into #{@account_key_file}") do
51
- @account_key = OpenSSL::PKey::RSA.new 4096
52
- File.write @account_key_file, @account_key.to_pem
53
- end
54
-
55
- process("Registering account key #{@account_key_file}") do
56
- registration = client.register contact: "mailto:#{mail}"
57
- registration.agree_terms
58
- end
59
- end
60
-
61
- def generate_key(name, type: DEFAULT_KEY)
62
- key_file = self.key name
63
- type, size = type
64
-
65
- key = case type
66
- when :rsa
67
- process "Generating RSA #{size} bits private key into #{key_file}" do
68
- key = OpenSSL::PKey::RSA.new size
69
- open(key_file, 'w') { |f| f.write key.to_pem }
70
- key
71
- end
72
- when :ecc
73
- process "Generating ECC #{size} curve private key into #{key_file}" do
74
- key = OpenSSL::PKey::EC.new(size).generate_key
75
- open(key_file, 'w') { |f| f.write key.to_pem }
76
- key
77
- end
78
- end
79
-
80
- key_info key
81
- end
82
-
83
- def generate_csr(csr, domains: [], key: nil)
84
- key = csr unless key
85
- domains = [csr, *domains].collect { |d| SimpleIDN.to_ascii d }
86
- csr_file = self.csr csr
87
- key_file = self.key key
88
-
89
- generate_key key unless File.exist? key_file
90
-
91
- process "Generating CSR for #{domains.join ', '} with key #{key_file} into #{csr_file}" do
92
- key_file = open(key_file, 'r') { |f| OpenSSL::PKey.read f }
93
- csr = OpenSSL::X509::Request.new
94
- csr.subject = OpenSSL::X509::Name.parse "/CN=#{domains.first}"
95
-
96
- public_key = case key_file
97
- when OpenSSL::PKey::EC
98
- curve = key_file.group.curve_name
99
- public = OpenSSL::PKey::EC.new curve
100
- public.public_key = key_file.public_key
101
- public
102
- else
103
- key_file.public_key
104
- end
105
- csr.public_key = public_key
106
-
107
- factory = OpenSSL::X509::ExtensionFactory.new
108
- extensions = []
109
- #extensions << factory.create_extension('basicConstraints', 'CA:FALSE', true)
110
- extensions << factory.create_extension('keyUsage', 'digitalSignature,nonRepudiation,keyEncipherment')
111
- extensions << factory.create_extension('subjectAltName', domains.collect { |d| "DNS:#{d}" }.join(', '))
112
-
113
- extensions = OpenSSL::ASN1::Sequence extensions
114
- extensions = OpenSSL::ASN1::Set [extensions]
115
- csr.add_attribute OpenSSL::X509::Attribute.new 'extReq', extensions
116
-
117
- csr.sign key_file, OpenSSL::Digest::SHA512.new
118
- open(csr_file, 'w') { |f| f.write csr.to_pem }
119
- end
120
- end
121
-
122
- def generate_crt(crt, csr: nil)
123
- csr = crt unless csr
124
- short_csr = csr
125
- crt = self.crt crt
126
- csr = self.csr csr
127
- generate_csr short_csr unless File.exist? csr
128
- internal_generate_crt crt, csr: csr
129
- end
130
-
131
- def renew(crt, csr: nil, duration: DEFAULT_RENEW_DURATION)
132
- csr = crt unless csr
133
- crt = self.crt crt
134
- csr = self.csr csr
135
- puts "Renewing #{crt} CRT from #{csr} CSR"
136
-
137
- if File.exists? crt
138
- x509 = OpenSSL::X509::Certificate.new File.read crt
139
- delay = x509.not_after - Time.now
140
- if delay > duration
141
- puts "No need to renew (#{humanize delay})"
142
- return false
143
- end
144
- end
145
-
146
- internal_generate_crt crt, csr: csr
147
- true
148
- end
149
-
150
- private
151
- def client
152
- unless @account_key
153
- puts 'No account key available'.colorize :yellow
154
- puts 'Please register yourself before'.colorize :red
155
- exit -1
156
- end
157
- @client ||= Acme::Client.new private_key: @account_key, endpoint: @endpoint
158
- end
159
-
160
- def process(line, io: STDOUT)
161
- io.print "#{line}..."
162
- io.flush
163
-
164
- result = yield
165
-
166
- io.puts " [#{'OK'.colorize :green}]"
167
-
168
- result
169
- rescue Exception
170
- io.puts " [#{'KO'.colorize :red}]"
171
- raise
172
- end
173
-
174
- def file(name, extension=nil)
175
- return nil unless name
176
- name = name.split('.').reverse.join('.')
177
- name = "#{name}.#{extension}" if extension
178
- File.join @directory, name
179
- end
180
-
181
- def domains(csr)
182
- domains = []
183
-
184
- cn = csr.subject.to_a.first { |n, _, _| n == 'CN' }
185
- domains << cn[1] if cn
186
-
187
- attribute = csr.attributes.detect { |a| %w(extReq msExtReq).include? a.oid }
188
- if attribute
189
- set = OpenSSL::ASN1.decode attribute.value
190
- seq = set.value.first
191
- sans = seq.value.collect { |s| OpenSSL::X509::Extension.new(s).to_a }
192
- .detect { |e| e.first == 'subjectAltName' }
193
- if sans
194
- sans = sans[1]
195
- sans = sans.split(/\s*,\s*/)
196
- .collect { |s| s.split /\s*:\s*/ }
197
- .select { |t, _| t == 'DNS' }
198
- .collect { |_, v| v }
199
- domains.concat sans
200
- end
201
- end
202
-
203
- domains.uniq
204
- end
205
-
206
- def authorize(domain)
207
- authorization = client.authorize domain: domain
208
- challenge = authorization.http01
209
-
210
- unless Dir.exists? @challenge_dir
211
- process "Creating directory #{@challenge_dir}" do
212
- FileUtils.mkdir_p @challenge_dir
213
- end
214
- end
215
-
216
- filename = challenge.token
217
- file = File.join @challenge_dir, filename
218
- content = challenge.file_content
219
- process "Writing challenge for #{domain} into #{file}" do
220
- File.write file, content
221
- end
222
-
223
- url = "http://#{domain}/.well-known/acme-challenge/#{filename}"
224
- process "Test challenge for #{url}" do
225
- response = begin
226
- Faraday.new do |conn|
227
- conn.use FaradayMiddleware::FollowRedirects
228
- conn.adapter Faraday.default_adapter
229
- end.get url
230
- rescue => e
231
- raise Exception, e.message
232
- end
233
- raise Exception, "Got response code #{response.status}" unless response.success?
234
- real_content = response.body
235
- raise Exception, "Got #{real_content}, expected #{content}" unless real_content == content
236
- end
237
-
238
- process "Authorizing domain #{domain}" do
239
- challenge.request_verification
240
- status = nil
241
- 60.times do
242
- sleep 1
243
- status = challenge.verify_status
244
- break if status != 'pending'
245
- end
246
-
247
- raise Exception, "Got status #{status} instead of valid" unless status == 'valid'
248
- end
249
- end
250
-
251
- def internal_generate_crt(crt, csr: nil)
252
- csr = crt unless csr
253
- csr_file = csr
254
- csr = OpenSSL::X509::Request.new File.read csr
255
- domains = domains csr
256
-
257
- domains.each { |d| authorize d }
258
-
259
- crt = process "Generating CRT #{crt} from CSR #{csr_file}" do
260
- certificate = client.new_certificate csr
261
- File.write crt, certificate.fullchain_to_pem
262
- OpenSSL::X509::Certificate.new certificate.to_pem
263
- end
264
-
265
- certifificate_info crt
266
- end
267
-
268
- def humanize(secs)
269
- [[60, :seconds], [60, :minutes], [24, :hours], [30, :days], [12, :months]].map { |count, name|
270
- if secs > 0
271
- secs, n = secs.divmod count
272
- "#{n.to_i} #{name}"
273
- end
274
- }.compact.reverse.join(' ')
275
- end
276
- end
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
@@ -1,5 +1,5 @@
1
1
  module Acme
2
2
  class PKI
3
- VERSION = '0.1.5'
3
+ VERSION = '0.2.1'
4
4
  end
5
5
  end
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.5
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: 2017-06-04 00:00:00.000000000 Z
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: '1.11'
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: '1.11'
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.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.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.11.0.1
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.11.0.1
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.0.7
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.0.7
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
- rubyforge_project:
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