acme-client 0.2.4 → 0.3.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/.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.
|