acme-client 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +85 -0
- data/Gemfile +1 -0
- data/README.md +5 -1
- data/Rakefile +6 -3
- data/lib/acme/client.rb +8 -7
- data/lib/acme/client/certificate.rb +1 -1
- data/lib/acme/client/certificate_request.rb +30 -35
- data/lib/acme/client/faraday_middleware.rb +49 -23
- data/lib/acme/client/resources/authorization.rb +3 -3
- data/lib/acme/client/resources/challenges/base.rb +9 -1
- data/lib/acme/client/resources/challenges/dns01.rb +2 -6
- data/lib/acme/client/resources/challenges/http01.rb +1 -5
- data/lib/acme/client/resources/challenges/tls_sni01.rb +2 -5
- data/lib/acme/client/resources/registration.rb +7 -13
- data/lib/acme/client/self_sign_certificate.rb +18 -9
- data/lib/acme/client/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4f8844db80f42dfbebde13f4834adff81b31a6c
|
4
|
+
data.tar.gz: aac066e3a86278081ad327072318cf659065760d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d8ae92a14464b33d91f07e127c35016987c4b912cb65edfa2d416f1471127f3a6ea8df093ca349778a45cf745d5caeae734d8c2f2f93b6a029deb04d7841ff0
|
7
|
+
data.tar.gz: 7e16c99dfedf9620fb9f1879ce89fba8bbc6f547f4c589b2177e9ee96966074f35bb85ff4c0afa9edd94ec9d3203dc05f8fb5568c57754300e7ed2ea1a9b7752
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.2
|
3
|
+
Exclude:
|
4
|
+
- 'bin/*'
|
5
|
+
- 'vendor/**/*'
|
6
|
+
|
7
|
+
Rails:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Style/FileName:
|
11
|
+
Exclude:
|
12
|
+
- 'lib/acme-client.rb'
|
13
|
+
|
14
|
+
Lint/AssignmentInCondition:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Style/ClassAndModuleChildren:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Style/Documentation:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Style/MultilineOperationIndentation:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/SignalException:
|
27
|
+
EnforcedStyle: only_raise
|
28
|
+
|
29
|
+
Style/AlignParameters:
|
30
|
+
EnforcedStyle: with_fixed_indentation
|
31
|
+
|
32
|
+
Style/FirstParameterIndentation:
|
33
|
+
EnforcedStyle: consistent
|
34
|
+
|
35
|
+
Style/TrailingCommaInArguments:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Style/TrailingCommaInLiteral:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Style/StringLiterals:
|
42
|
+
Enabled: single_quotes
|
43
|
+
|
44
|
+
Metrics/LineLength:
|
45
|
+
Max: 140
|
46
|
+
|
47
|
+
Metrics/ParameterLists:
|
48
|
+
Max: 5
|
49
|
+
CountKeywordArgs: false
|
50
|
+
|
51
|
+
Lint/EndAlignment:
|
52
|
+
AlignWith: variable
|
53
|
+
|
54
|
+
Style/ParallelAssignment:
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
Style/ModuleFunction:
|
58
|
+
Enabled: false
|
59
|
+
|
60
|
+
Style/TrivialAccessors:
|
61
|
+
AllowPredicates: true
|
62
|
+
|
63
|
+
Lint/UnusedMethodArgument:
|
64
|
+
AllowUnusedKeywordArguments: true
|
65
|
+
|
66
|
+
Metrics/MethodLength:
|
67
|
+
Max: 15
|
68
|
+
|
69
|
+
Style/DoubleNegation:
|
70
|
+
Enabled: false
|
71
|
+
|
72
|
+
Style/IfUnlessModifier:
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
Style/MultilineBlockChain:
|
76
|
+
Enabled: false
|
77
|
+
|
78
|
+
Style/BlockDelimiters:
|
79
|
+
EnforcedStyle: semantic
|
80
|
+
|
81
|
+
Style/Lambda:
|
82
|
+
Enabled: false
|
83
|
+
|
84
|
+
Style/AccessorMethodName:
|
85
|
+
Enabled: false
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -85,7 +85,7 @@ File.write("fullchain.pem", certificate.fullchain_to_pem)
|
|
85
85
|
# :Port => 8443,
|
86
86
|
# :DocumentRoot => Dir.pwd,
|
87
87
|
# :SSLEnable => true,
|
88
|
-
# :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.read('
|
88
|
+
# :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.read('privkey.pem') ),
|
89
89
|
# :SSLCertificate => OpenSSL::X509::Certificate.new( File.read('cert.pem') )); trap('INT') { s.shutdown }; s.start"
|
90
90
|
```
|
91
91
|
|
@@ -94,6 +94,10 @@ File.write("fullchain.pem", certificate.fullchain_to_pem)
|
|
94
94
|
- Recovery methods are not implemented.
|
95
95
|
- proofOfPossession-01 is not implemented.
|
96
96
|
|
97
|
+
# Requirements
|
98
|
+
|
99
|
+
Ruby >= 2.1
|
100
|
+
|
97
101
|
## Development
|
98
102
|
|
99
103
|
All the tests use VCR to mock the interaction with the server but if you
|
data/Rakefile
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require "rspec/core/rake_task"
|
1
|
+
require 'bundler/gem_tasks'
|
3
2
|
|
3
|
+
require 'rspec/core/rake_task'
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
|
-
|
6
|
+
require 'rubocop/rake_task'
|
7
|
+
RuboCop::RakeTask.new
|
8
|
+
|
9
|
+
task default: [:spec, :rubocop]
|
data/lib/acme/client.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require 'acme-client'
|
2
2
|
|
3
3
|
class Acme::Client
|
4
|
-
DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'
|
4
|
+
DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'.freeze
|
5
5
|
DIRECTORY_DEFAULT = {
|
6
6
|
'new-authz' => '/acme/new-authz',
|
7
7
|
'new-cert' => '/acme/new-cert',
|
8
8
|
'new-reg' => '/acme/new-reg',
|
9
9
|
'revoke-cert' => '/acme/revoke-cert'
|
10
|
-
}
|
10
|
+
}.freeze
|
11
11
|
|
12
12
|
def initialize(private_key:, endpoint: DEFAULT_ENDPOINT, directory_uri: nil)
|
13
13
|
@endpoint, @private_key, @directory_uri = endpoint, private_key, directory_uri
|
@@ -70,12 +70,13 @@ class Acme::Client
|
|
70
70
|
|
71
71
|
private
|
72
72
|
|
73
|
-
def fetch_chain(response, limit=10)
|
74
|
-
|
73
|
+
def fetch_chain(response, limit = 10)
|
74
|
+
links = response.headers['link']
|
75
|
+
if limit.zero? || links.nil? || links['up'].nil?
|
75
76
|
[]
|
76
77
|
else
|
77
|
-
issuer = connection.get(
|
78
|
-
[OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit-1)]
|
78
|
+
issuer = connection.get(links['up'])
|
79
|
+
[OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)]
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
@@ -4,43 +4,34 @@ class Acme::Client::CertificateRequest
|
|
4
4
|
DEFAULT_KEY_LENGTH = 2048
|
5
5
|
DEFAULT_DIGEST = OpenSSL::Digest::SHA256
|
6
6
|
SUBJECT_KEYS = {
|
7
|
-
common_name:
|
8
|
-
country_name:
|
9
|
-
organization_name:
|
10
|
-
organizational_unit:
|
11
|
-
state_or_province:
|
12
|
-
locality_name:
|
7
|
+
common_name: 'CN',
|
8
|
+
country_name: 'C',
|
9
|
+
organization_name: 'O',
|
10
|
+
organizational_unit: 'OU',
|
11
|
+
state_or_province: 'ST',
|
12
|
+
locality_name: 'L'
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
SUBJECT_TYPES = {
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
'CN' => OpenSSL::ASN1::UTF8STRING,
|
17
|
+
'C' => OpenSSL::ASN1::UTF8STRING,
|
18
|
+
'O' => OpenSSL::ASN1::UTF8STRING,
|
19
|
+
'OU' => OpenSSL::ASN1::UTF8STRING,
|
20
|
+
'ST' => OpenSSL::ASN1::UTF8STRING,
|
21
|
+
'L' => OpenSSL::ASN1::UTF8STRING
|
22
22
|
}.freeze
|
23
23
|
|
24
24
|
attr_reader :private_key, :common_name, :names, :subject
|
25
25
|
|
26
26
|
def_delegators :csr, :to_pem, :to_der
|
27
27
|
|
28
|
-
def initialize(common_name: nil,
|
29
|
-
names: [],
|
30
|
-
private_key: generate_private_key,
|
31
|
-
subject: {},
|
32
|
-
digest: DEFAULT_DIGEST.new)
|
33
|
-
raise ArgumentError.new("Digest must be a OpenSSL::Digest") unless digest.is_a?(OpenSSL::Digest)
|
28
|
+
def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new)
|
34
29
|
@digest = digest
|
35
|
-
|
36
30
|
@private_key = private_key
|
37
|
-
|
38
31
|
@subject = normalize_subject(subject)
|
39
32
|
@common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name]
|
40
|
-
|
41
33
|
@names = names.to_a.dup
|
42
34
|
normalize_names
|
43
|
-
|
44
35
|
@subject[SUBJECT_KEYS[:common_name]] ||= @common_name
|
45
36
|
validate_subject
|
46
37
|
end
|
@@ -56,30 +47,34 @@ class Acme::Client::CertificateRequest
|
|
56
47
|
end
|
57
48
|
|
58
49
|
def normalize_subject(subject)
|
59
|
-
@subject = subject.each_with_object({})
|
60
|
-
|
61
|
-
|
50
|
+
@subject = subject.each_with_object({}) do |(key, value), hash|
|
51
|
+
hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s
|
52
|
+
end
|
62
53
|
end
|
63
54
|
|
64
55
|
def normalize_names
|
65
56
|
if @common_name
|
66
57
|
@names.unshift(@common_name) unless @names.include?(@common_name)
|
67
|
-
elsif @names.empty?
|
68
|
-
raise ArgumentError.new("No common name and no list of names given")
|
69
58
|
else
|
59
|
+
raise ArgumentError, 'No common name and no list of names given' if @names.empty?
|
70
60
|
@common_name = @names.first
|
71
61
|
end
|
72
62
|
end
|
73
63
|
|
74
64
|
def validate_subject
|
65
|
+
validate_subject_attributes
|
66
|
+
validate_subject_common_name
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_subject_attributes
|
75
70
|
extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values
|
76
|
-
|
77
|
-
|
78
|
-
|
71
|
+
return if extra_keys.empty?
|
72
|
+
raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}"
|
73
|
+
end
|
79
74
|
|
80
|
-
|
81
|
-
|
82
|
-
|
75
|
+
def validate_subject_common_name
|
76
|
+
return if @common_name == @subject[SUBJECT_KEYS[:common_name]]
|
77
|
+
raise ArgumentError, 'Conflicting common name given in arguments and subject'
|
83
78
|
end
|
84
79
|
|
85
80
|
def generate
|
@@ -103,11 +98,11 @@ class Acme::Client::CertificateRequest
|
|
103
98
|
return if @names.size <= 1
|
104
99
|
|
105
100
|
extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
|
106
|
-
|
101
|
+
'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
|
107
102
|
)
|
108
103
|
csr.add_attribute(
|
109
104
|
OpenSSL::X509::Attribute.new(
|
110
|
-
|
105
|
+
'extReq',
|
111
106
|
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])])
|
112
107
|
)
|
113
108
|
)
|
@@ -9,50 +9,76 @@ class Acme::Client::FaradayMiddleware < Faraday::Middleware
|
|
9
9
|
def call(env)
|
10
10
|
@env = env
|
11
11
|
@env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body)
|
12
|
-
@app.call(env).on_complete {|
|
12
|
+
@app.call(env).on_complete { |response_env| on_complete(response_env) }
|
13
13
|
end
|
14
14
|
|
15
15
|
def on_complete(env)
|
16
|
-
|
16
|
+
@env = env
|
17
17
|
|
18
|
-
|
18
|
+
raise_on_not_found!
|
19
|
+
store_nonce
|
20
|
+
env.body = decode_body
|
21
|
+
env.response_headers['Link'] = decode_link_headers
|
19
22
|
|
20
|
-
|
23
|
+
return if env.success?
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
+
raise_on_error!
|
26
|
+
end
|
25
27
|
|
26
|
-
|
27
|
-
link_header = env.response_headers['Link']
|
28
|
-
links = link_header.split(', ').map do |entry|
|
29
|
-
link = entry.match(/<(.*?)>;/).captures.first
|
30
|
-
name = entry.match(/rel="([\w-]+)"/).captures.first
|
31
|
-
[name, link]
|
32
|
-
end
|
28
|
+
private
|
33
29
|
|
34
|
-
|
35
|
-
|
30
|
+
def raise_on_not_found!
|
31
|
+
raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
|
32
|
+
end
|
36
33
|
|
37
|
-
|
34
|
+
def raise_on_error!
|
35
|
+
raise error_class, error_message
|
36
|
+
end
|
38
37
|
|
38
|
+
def error_message
|
39
|
+
if env.body.is_a? Hash
|
40
|
+
env.body['detail']
|
41
|
+
else
|
42
|
+
"Error message: #{env.body}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def error_class
|
39
47
|
error_name = env.body['type'].gsub('urn:acme:error:', '').classify
|
40
|
-
|
48
|
+
if Acme::Client::Error.qualified_const_defined?(error_name)
|
41
49
|
"Acme::Client::Error::#{error_name}".constantize
|
42
50
|
else
|
43
51
|
Acme::Client::Error
|
44
52
|
end
|
53
|
+
end
|
45
54
|
|
46
|
-
|
47
|
-
|
55
|
+
def decode_body
|
56
|
+
content_type = env.response_headers['Content-Type']
|
57
|
+
|
58
|
+
if content_type == 'application/json' || content_type == 'application/problem+json'
|
59
|
+
JSON.load(env.body)
|
48
60
|
else
|
49
|
-
|
61
|
+
env.body
|
50
62
|
end
|
63
|
+
end
|
64
|
+
|
65
|
+
LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/
|
66
|
+
|
67
|
+
def decode_link_headers
|
68
|
+
return unless env.response_headers.key?('Link')
|
69
|
+
link_header = env.response_headers['Link']
|
51
70
|
|
52
|
-
|
71
|
+
links = link_header.split(', ').map { |entry|
|
72
|
+
_, link, name = *entry.match(LINK_MATCH)
|
73
|
+
[name, link]
|
74
|
+
}
|
75
|
+
|
76
|
+
Hash[*links.flatten]
|
53
77
|
end
|
54
78
|
|
55
|
-
|
79
|
+
def store_nonce
|
80
|
+
nonces << env.response_headers['replay-nonce']
|
81
|
+
end
|
56
82
|
|
57
83
|
def pop_nonce
|
58
84
|
if nonces.empty?
|
@@ -3,7 +3,7 @@ class Acme::Client::Resources::Authorization
|
|
3
3
|
DNS01 = Acme::Client::Resources::Challenges::DNS01
|
4
4
|
TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01
|
5
5
|
|
6
|
-
attr_reader :domain, :status, :http01, :dns01, :tls_sni01
|
6
|
+
attr_reader :domain, :status, :expires, :http01, :dns01, :tls_sni01
|
7
7
|
|
8
8
|
def initialize(client, response)
|
9
9
|
@client = client
|
@@ -19,13 +19,13 @@ class Acme::Client::Resources::Authorization
|
|
19
19
|
when 'http-01' then @http01 = HTTP01.new(@client, attributes)
|
20
20
|
when 'dns-01' then @dns01 = DNS01.new(@client, attributes)
|
21
21
|
when 'tls-sni-01' then @tls_sni01 = TLSSNI01.new(@client, attributes)
|
22
|
-
|
23
|
-
# no supported
|
22
|
+
# else no-op
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
27
|
def assign_attributes(body)
|
28
|
+
@expires = Time.iso8601(body['expires']) if body.key? 'expires'
|
29
29
|
@domain = body['identifier']['value']
|
30
30
|
@status = body['status']
|
31
31
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
class Acme::Client::Resources::Challenges::Base
|
2
|
-
|
3
2
|
attr_reader :client, :status, :uri, :token, :error
|
4
3
|
|
5
4
|
def initialize(client, attributes)
|
@@ -15,8 +14,17 @@ class Acme::Client::Resources::Challenges::Base
|
|
15
14
|
status
|
16
15
|
end
|
17
16
|
|
17
|
+
def request_verification
|
18
|
+
response = @client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key)
|
19
|
+
response.success?
|
20
|
+
end
|
21
|
+
|
18
22
|
private
|
19
23
|
|
24
|
+
def challenge_type
|
25
|
+
self.class::CHALLENGE_TYPE
|
26
|
+
end
|
27
|
+
|
20
28
|
def authorization_key
|
21
29
|
"#{token}.#{crypto.thumbprint}"
|
22
30
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base
|
2
|
+
CHALLENGE_TYPE = 'dns-01'.freeze
|
2
3
|
RECORD_NAME = '_acme-challenge'.freeze
|
3
4
|
RECORD_TYPE = 'TXT'.freeze
|
4
5
|
|
@@ -11,11 +12,6 @@ class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Chal
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def record_content
|
14
|
-
crypto.digest.
|
15
|
-
end
|
16
|
-
|
17
|
-
def request_verification
|
18
|
-
response = @client.connection.post(@uri, { resource: 'challenge', type: 'dns-01', keyAuthorization: authorization_key })
|
19
|
-
response.success?
|
15
|
+
Base64.urlsafe_encode64(crypto.digest.digest(authorization_key)).sub(/[\s=]*\z/, '')
|
20
16
|
end
|
21
17
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base
|
2
|
+
CHALLENGE_TYPE = 'http-01'.freeze
|
2
3
|
CONTENT_TYPE = 'text/plain'.freeze
|
3
4
|
|
4
5
|
def content_type
|
@@ -12,9 +13,4 @@ class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Cha
|
|
12
13
|
def filename
|
13
14
|
".well-known/acme-challenge/#{token}"
|
14
15
|
end
|
15
|
-
|
16
|
-
def request_verification
|
17
|
-
response = client.connection.post(@uri, { resource: 'challenge', type: 'http-01', keyAuthorization: authorization_key })
|
18
|
-
response.success?
|
19
|
-
end
|
20
16
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base
|
2
|
+
CHALLENGE_TYPE = 'tls-sni-01'.freeze
|
3
|
+
|
2
4
|
def hostname
|
3
5
|
digest = crypto.digest.hexdigest(authorization_key)
|
4
6
|
"#{digest[0..31]}.#{digest[32..64]}.acme.invalid"
|
@@ -12,11 +14,6 @@ class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::C
|
|
12
14
|
self_sign_certificate.private_key
|
13
15
|
end
|
14
16
|
|
15
|
-
def request_verification
|
16
|
-
response = client.connection.post(@uri, { resource: 'challenge', type: 'tls-sni-01', keyAuthorization: authorization_key })
|
17
|
-
response.success?
|
18
|
-
end
|
19
|
-
|
20
17
|
private
|
21
18
|
|
22
19
|
def self_sign_certificate
|
@@ -9,22 +9,16 @@ class Acme::Client::Resources::Registration
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def get_terms
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
return unless @term_of_service_uri
|
13
|
+
|
14
|
+
@client.connection.get(@term_of_service_uri).body
|
15
15
|
end
|
16
16
|
|
17
17
|
def agree_terms
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
}
|
23
|
-
response = @client.connection.post(@uri, { resource: 'reg', agreement: @term_of_service_uri })
|
24
|
-
response.success?
|
25
|
-
else
|
26
|
-
true
|
27
|
-
end
|
18
|
+
return true unless @term_of_service_uri
|
19
|
+
|
20
|
+
response = @client.connection.post(@uri, resource: 'reg', agreement: @term_of_service_uri)
|
21
|
+
response.success?
|
28
22
|
end
|
29
23
|
|
30
24
|
private
|
@@ -13,15 +13,9 @@ class Acme::Client::SelfSignCertificate
|
|
13
13
|
|
14
14
|
def certificate
|
15
15
|
@certificate ||= begin
|
16
|
-
certificate =
|
17
|
-
certificate.not_before = not_before
|
18
|
-
certificate.not_after = not_after
|
19
|
-
certificate.public_key = private_key.public_key
|
20
|
-
|
21
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
22
|
-
extension_factory.subject_certificate = certificate
|
23
|
-
extension_factory.issuer_certificate = certificate
|
16
|
+
certificate = generate_certificate
|
24
17
|
|
18
|
+
extension_factory = generate_extension_factory(certificate)
|
25
19
|
subject_alt_name_entry = subject_alt_names.map { |d| "DNS: #{d}" }.join(',')
|
26
20
|
subject_alt_name_extension = extension_factory.create_extension('subjectAltName', subject_alt_name_entry)
|
27
21
|
certificate.add_extension(subject_alt_name_extension)
|
@@ -37,7 +31,7 @@ class Acme::Client::SelfSignCertificate
|
|
37
31
|
end
|
38
32
|
|
39
33
|
def default_not_before
|
40
|
-
Time.now
|
34
|
+
Time.now - 3600
|
41
35
|
end
|
42
36
|
|
43
37
|
def default_not_after
|
@@ -47,4 +41,19 @@ class Acme::Client::SelfSignCertificate
|
|
47
41
|
def digest
|
48
42
|
OpenSSL::Digest::SHA256.new
|
49
43
|
end
|
44
|
+
|
45
|
+
def generate_certificate
|
46
|
+
certificate = OpenSSL::X509::Certificate.new
|
47
|
+
certificate.not_before = not_before
|
48
|
+
certificate.not_after = not_after
|
49
|
+
certificate.public_key = private_key.public_key
|
50
|
+
certificate
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_extension_factory(certificate)
|
54
|
+
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
55
|
+
extension_factory.subject_certificate = certificate
|
56
|
+
extension_factory.issuer_certificate = certificate
|
57
|
+
extension_factory
|
58
|
+
end
|
50
59
|
end
|
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.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Barbier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -153,6 +153,7 @@ extra_rdoc_files: []
|
|
153
153
|
files:
|
154
154
|
- ".gitignore"
|
155
155
|
- ".rspec"
|
156
|
+
- ".rubocop.yml"
|
156
157
|
- ".travis.yml"
|
157
158
|
- Gemfile
|
158
159
|
- LICENSE.txt
|
@@ -198,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
199
|
version: '0'
|
199
200
|
requirements: []
|
200
201
|
rubyforge_project:
|
201
|
-
rubygems_version: 2.4.5
|
202
|
+
rubygems_version: 2.4.5.1
|
202
203
|
signing_key:
|
203
204
|
specification_version: 4
|
204
205
|
summary: Client for the ACME protocol.
|