acme-pki 0.1.5 → 0.2.1

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