leap_cli 1.9 → 1.9.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
2
  SHA1:
3
- metadata.gz: c76581ff17beffa03bea2e6447ec048d9282cd43
4
- data.tar.gz: e1b082558769fad1644c0bf78afa138aa66fcb15
3
+ metadata.gz: 9e4ec8cc1569dc2e96c778ff53c1539c8e34865f
4
+ data.tar.gz: 2a2a84cc1d7f152addf10af08ebb36d9f05dface
5
5
  SHA512:
6
- metadata.gz: fc24e99df33af964af23fdff085bb2a87808a92138545378c70fd43e0a1c0581394c3d7c9faffa774feffb063c3da491f7a892267beb985672d736c603aff79a
7
- data.tar.gz: 67c3838bc0eeaf0bd72eaf1d6c843fbfeca25414bec9ccc4120cf2e514cb2f157cad942fd4b7c67b4d9803097e11694ff0360f4eb0cf0b1e312bf0734fce1449
6
+ metadata.gz: ee2957c11ec13d6ffc44ab8b016543f7b1e37f6e9a8f8127e018878cd4db8e9494c2c2309c5ffd70ffe93b08c894fc1f90134ea7f3a3e6b9501623a81ebff757
7
+ data.tar.gz: 27a85ee4027b6e79995de7896ddb10530a50abb79d94e4c5b4116445429fca04cffb7f5c6963ab614ec5689f1c8d4bc782e9f0f4767d608df57175b22c7d7b75
@@ -84,9 +84,8 @@ module LeapCli; module Commands
84
84
  assert_bin! 'git'
85
85
  ensure_dir platform_dir
86
86
  Dir.chdir(platform_dir) do
87
- log :cloning, "leap_platform into #{platform_dir}"
88
- pty_run "git clone --branch master #{DEFAULT_REPO} ."
89
- pty_run 'git submodule update --init'
87
+ log :cloning, "leap_platform:stable into #{platform_dir}"
88
+ pty_run "git clone --branch stable #{DEFAULT_REPO} ."
90
89
  end
91
90
  else
92
91
  bail!
@@ -1,6 +1,6 @@
1
1
  module LeapCli
2
2
  unless defined?(LeapCli::VERSION)
3
- VERSION = '1.9'
3
+ VERSION = '1.9.1'
4
4
  COMPATIBLE_PLATFORM_VERSION = '0.9'..'0.99'
5
5
  SUMMARY = 'Command line interface to the LEAP platform'
6
6
  DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leap_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.9'
4
+ version: 1.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - LEAP Encryption Access Project
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-05 00:00:00.000000000 Z
11
+ date: 2017-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -73,39 +73,19 @@ dependencies:
73
73
  - !ruby/object:Gem::Version
74
74
  version: '1.11'
75
75
  - !ruby/object:Gem::Dependency
76
- name: fog-aws
76
+ name: acme-client
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0.11'
81
+ version: '0.6'
82
82
  type: :runtime
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0.11'
89
- - !ruby/object:Gem::Dependency
90
- name: faraday
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '0.9'
96
- - - ">="
97
- - !ruby/object:Gem::Version
98
- version: 0.9.1
99
- type: :runtime
100
- prerelease: false
101
- version_requirements: !ruby/object:Gem::Requirement
102
- requirements:
103
- - - "~>"
104
- - !ruby/object:Gem::Version
105
- version: '0.9'
106
- - - ">="
107
- - !ruby/object:Gem::Version
108
- version: 0.9.1
88
+ version: '0.6'
109
89
  - !ruby/object:Gem::Dependency
110
90
  name: ya2yaml
111
91
  requirement: !ruby/object:Gem::Requirement
@@ -166,23 +146,6 @@ files:
166
146
  - lib/leap_cli/path.rb
167
147
  - lib/leap_cli/util.rb
168
148
  - lib/leap_cli/version.rb
169
- - vendor/acme-client/lib/acme-client.rb
170
- - vendor/acme-client/lib/acme/client.rb
171
- - vendor/acme-client/lib/acme/client/certificate.rb
172
- - vendor/acme-client/lib/acme/client/certificate_request.rb
173
- - vendor/acme-client/lib/acme/client/crypto.rb
174
- - vendor/acme-client/lib/acme/client/error.rb
175
- - vendor/acme-client/lib/acme/client/faraday_middleware.rb
176
- - vendor/acme-client/lib/acme/client/resources.rb
177
- - vendor/acme-client/lib/acme/client/resources/authorization.rb
178
- - vendor/acme-client/lib/acme/client/resources/challenges.rb
179
- - vendor/acme-client/lib/acme/client/resources/challenges/base.rb
180
- - vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb
181
- - vendor/acme-client/lib/acme/client/resources/challenges/http01.rb
182
- - vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb
183
- - vendor/acme-client/lib/acme/client/resources/registration.rb
184
- - vendor/acme-client/lib/acme/client/self_sign_certificate.rb
185
- - vendor/acme-client/lib/acme/client/version.rb
186
149
  - vendor/base32/lib/base32.rb
187
150
  - vendor/certificate_authority/lib/certificate_authority.rb
188
151
  - vendor/certificate_authority/lib/certificate_authority/certificate.rb
@@ -231,9 +194,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
194
  version: '0'
232
195
  requirements: []
233
196
  rubyforge_project:
234
- rubygems_version: 2.2.2
197
+ rubygems_version: 2.5.2
235
198
  signing_key:
236
199
  specification_version: 4
237
200
  summary: Command line interface to the LEAP platform
238
201
  test_files: []
239
- has_rdoc:
@@ -1,30 +0,0 @@
1
- class Acme::Client::Certificate
2
- extend Forwardable
3
-
4
- attr_reader :x509, :x509_chain, :request, :private_key, :url
5
-
6
- def_delegators :x509, :to_pem, :to_der
7
-
8
- def initialize(certificate, url, chain, request)
9
- @x509 = certificate
10
- @url = url
11
- @x509_chain = chain
12
- @request = request
13
- end
14
-
15
- def chain_to_pem
16
- x509_chain.map(&:to_pem).join
17
- end
18
-
19
- def x509_fullchain
20
- [x509, *x509_chain]
21
- end
22
-
23
- def fullchain_to_pem
24
- x509_fullchain.map(&:to_pem).join
25
- end
26
-
27
- def common_name
28
- x509.subject.to_a.find { |name, _, _| name == 'CN' }[1]
29
- end
30
- end
@@ -1,111 +0,0 @@
1
- class Acme::Client::CertificateRequest
2
- extend Forwardable
3
-
4
- DEFAULT_KEY_LENGTH = 2048
5
- DEFAULT_DIGEST = OpenSSL::Digest::SHA256
6
- SUBJECT_KEYS = {
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
- }.freeze
14
-
15
- SUBJECT_TYPES = {
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
- }.freeze
23
-
24
- attr_reader :private_key, :common_name, :names, :subject
25
-
26
- def_delegators :csr, :to_pem, :to_der
27
-
28
- def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new)
29
- @digest = digest
30
- @private_key = private_key
31
- @subject = normalize_subject(subject)
32
- @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name]
33
- @names = names.to_a.dup
34
- normalize_names
35
- @subject[SUBJECT_KEYS[:common_name]] ||= @common_name
36
- validate_subject
37
- end
38
-
39
- def csr
40
- @csr ||= generate
41
- end
42
-
43
- private
44
-
45
- def generate_private_key
46
- OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH)
47
- end
48
-
49
- def normalize_subject(subject)
50
- @subject = subject.each_with_object({}) do |(key, value), hash|
51
- hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s
52
- end
53
- end
54
-
55
- def normalize_names
56
- if @common_name
57
- @names.unshift(@common_name) unless @names.include?(@common_name)
58
- else
59
- raise ArgumentError, 'No common name and no list of names given' if @names.empty?
60
- @common_name = @names.first
61
- end
62
- end
63
-
64
- def validate_subject
65
- validate_subject_attributes
66
- validate_subject_common_name
67
- end
68
-
69
- def validate_subject_attributes
70
- extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values
71
- return if extra_keys.empty?
72
- raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}"
73
- end
74
-
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'
78
- end
79
-
80
- def generate
81
- OpenSSL::X509::Request.new.tap do |csr|
82
- csr.public_key = @private_key.public_key
83
- csr.subject = generate_subject
84
- csr.version = 2
85
- add_extension(csr)
86
- csr.sign @private_key, @digest
87
- end
88
- end
89
-
90
- def generate_subject
91
- OpenSSL::X509::Name.new(
92
- @subject.map {|name, value|
93
- [name, value, SUBJECT_TYPES[name]]
94
- }
95
- )
96
- end
97
-
98
- def add_extension(csr)
99
- return if @names.size <= 1
100
-
101
- extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
102
- 'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
103
- )
104
- csr.add_attribute(
105
- OpenSSL::X509::Attribute.new(
106
- 'extReq',
107
- OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])])
108
- )
109
- )
110
- end
111
- end
@@ -1,98 +0,0 @@
1
- class Acme::Client::Crypto
2
- attr_reader :private_key
3
-
4
- def initialize(private_key)
5
- @private_key = private_key
6
- end
7
-
8
- def generate_signed_jws(header:, payload:)
9
- header = { typ: 'JWT', alg: jws_alg, jwk: jwk }.merge(header)
10
-
11
- encoded_header = urlsafe_base64(header.to_json)
12
- encoded_payload = urlsafe_base64(payload.to_json)
13
- signature_data = "#{encoded_header}.#{encoded_payload}"
14
-
15
- signature = private_key.sign digest, signature_data
16
- encoded_signature = urlsafe_base64(signature)
17
-
18
- {
19
- protected: encoded_header,
20
- payload: encoded_payload,
21
- signature: encoded_signature
22
- }.to_json
23
- end
24
-
25
- def thumbprint
26
- urlsafe_base64 digest.digest(jwk.to_json)
27
- end
28
-
29
- def digest
30
- OpenSSL::Digest::SHA256.new
31
- end
32
-
33
- def urlsafe_base64(data)
34
- Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
35
- end
36
-
37
- private
38
-
39
- def jws_alg
40
- { 'RSA' => 'RS256', 'EC' => 'ES256' }.fetch(jwk[:kty])
41
- end
42
-
43
- def jwk
44
- @jwk ||= case private_key
45
- when OpenSSL::PKey::RSA
46
- rsa_jwk
47
- when OpenSSL::PKey::EC
48
- ec_jwk
49
- else
50
- raise ArgumentError, "Can't handle #{private_key} as private key, only OpenSSL::PKey::RSA and OpenSSL::PKey::EC"
51
- end
52
- end
53
-
54
- def rsa_jwk
55
- {
56
- e: urlsafe_base64(public_key.e.to_s(2)),
57
- kty: 'RSA',
58
- n: urlsafe_base64(public_key.n.to_s(2))
59
- }
60
- end
61
-
62
- def ec_jwk
63
- {
64
- crv: curve_name,
65
- kty: 'EC',
66
- x: urlsafe_base64(coordinates[:x].to_s(2)),
67
- y: urlsafe_base64(coordinates[:y].to_s(2))
68
- }
69
- end
70
-
71
- def curve_name
72
- {
73
- 'prime256v1' => 'P-256',
74
- 'secp384r1' => 'P-384',
75
- 'secp521r1' => 'P-521'
76
- }.fetch(private_key.group.curve_name) { raise ArgumentError, 'Unknown EC curve' }
77
- end
78
-
79
- # rubocop:disable Metrics/AbcSize
80
- def coordinates
81
- @coordinates ||= begin
82
- hex = public_key.to_bn.to_s(16)
83
- data_len = hex.length - 2
84
- hex_x = hex[2, data_len / 2]
85
- hex_y = hex[2 + data_len / 2, data_len / 2]
86
-
87
- {
88
- x: OpenSSL::BN.new([hex_x].pack('H*'), 2),
89
- y: OpenSSL::BN.new([hex_y].pack('H*'), 2)
90
- }
91
- end
92
- end
93
- # rubocop:enable Metrics/AbcSize
94
-
95
- def public_key
96
- @public_key ||= private_key.public_key
97
- end
98
- end
@@ -1,16 +0,0 @@
1
- class Acme::Client::Error < StandardError
2
- class NotFound < Acme::Client::Error; end
3
- class BadCSR < Acme::Client::Error; end
4
- class BadNonce < Acme::Client::Error; end
5
- class Connection < Acme::Client::Error; end
6
- class Dnssec < Acme::Client::Error; end
7
- class Malformed < Acme::Client::Error; end
8
- class ServerInternal < Acme::Client::Error; end
9
- class Acme::Tls < Acme::Client::Error; end
10
- class Unauthorized < Acme::Client::Error; end
11
- class UnknownHost < Acme::Client::Error; end
12
- class Timeout < Acme::Client::Error; end
13
- class RateLimited < Acme::Client::Error; end
14
- class RejectedIdentifier < Acme::Client::Error; end
15
- class UnsupportedIdentifier < Acme::Client::Error; end
16
- end
@@ -1,123 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Acme::Client::FaradayMiddleware < Faraday::Middleware
4
- attr_reader :env, :response, :client
5
-
6
- repo_url = 'https://github.com/unixcharles/acme-client'
7
- USER_AGENT = "Acme::Client v#{Acme::Client::VERSION} (#{repo_url})".freeze
8
-
9
- def initialize(app, client:)
10
- super(app)
11
- @client = client
12
- end
13
-
14
- def call(env)
15
- @env = env
16
- @env[:request_headers]['User-Agent'] = USER_AGENT
17
- @env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body)
18
- @app.call(env).on_complete { |response_env| on_complete(response_env) }
19
- rescue Faraday::TimeoutError
20
- raise Acme::Client::Error::Timeout
21
- end
22
-
23
- def on_complete(env)
24
- @env = env
25
-
26
- raise_on_not_found!
27
- store_nonce
28
- env.body = decode_body
29
- env.response_headers['Link'] = decode_link_headers
30
-
31
- return if env.success?
32
-
33
- raise_on_error!
34
- end
35
-
36
- private
37
-
38
- def raise_on_not_found!
39
- raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
40
- end
41
-
42
- def raise_on_error!
43
- raise error_class, error_message
44
- end
45
-
46
- def error_message
47
- if env.body.is_a? Hash
48
- env.body['detail']
49
- else
50
- "Error message: #{env.body}"
51
- end
52
- end
53
-
54
- def error_class
55
- if error_name && !error_name.empty? && Acme::Client::Error.const_defined?(error_name)
56
- Object.const_get("Acme::Client::Error::#{error_name}")
57
- else
58
- Acme::Client::Error
59
- end
60
- end
61
-
62
- def error_name
63
- @error_name ||= begin
64
- return unless env.body.is_a?(Hash)
65
- return unless env.body.key?('type')
66
-
67
- env.body['type'].gsub('urn:acme:error:', '').split(/[_-]/).map(&:capitalize).join
68
- end
69
- end
70
-
71
- def decode_body
72
- content_type = env.response_headers['Content-Type']
73
-
74
- if content_type == 'application/json' || content_type == 'application/problem+json'
75
- JSON.load(env.body)
76
- else
77
- env.body
78
- end
79
- end
80
-
81
- LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/
82
-
83
- def decode_link_headers
84
- return unless env.response_headers.key?('Link')
85
- link_header = env.response_headers['Link']
86
-
87
- links = link_header.split(', ').map { |entry|
88
- _, link, name = *entry.match(LINK_MATCH)
89
- [name, link]
90
- }
91
-
92
- Hash[*links.flatten]
93
- end
94
-
95
- def store_nonce
96
- nonces << env.response_headers['replay-nonce']
97
- end
98
-
99
- def pop_nonce
100
- if nonces.empty?
101
- get_nonce
102
- else
103
- nonces.pop
104
- end
105
- end
106
-
107
- def get_nonce
108
- response = Faraday.head(env.url, nil, 'User-Agent' => USER_AGENT)
109
- response.headers['replay-nonce']
110
- end
111
-
112
- def nonces
113
- client.nonces
114
- end
115
-
116
- def private_key
117
- client.private_key
118
- end
119
-
120
- def crypto
121
- @crypto ||= Acme::Client::Crypto.new(private_key)
122
- end
123
- end
@@ -1,44 +0,0 @@
1
- class Acme::Client::Resources::Authorization
2
- HTTP01 = Acme::Client::Resources::Challenges::HTTP01
3
- DNS01 = Acme::Client::Resources::Challenges::DNS01
4
- TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01
5
-
6
- attr_reader :client, :uri, :domain, :status, :expires, :http01, :dns01, :tls_sni01
7
-
8
- def initialize(client, uri, response)
9
- @client = client
10
- @uri = uri
11
- assign_attributes(response.body)
12
- end
13
-
14
- def verify_status
15
- response = @client.connection.get(@uri)
16
-
17
- assign_attributes(response.body)
18
- status
19
- end
20
-
21
- private
22
-
23
- def assign_attributes(body)
24
- @expires = Time.iso8601(body['expires']) if body.key? 'expires'
25
- @domain = body['identifier']['value']
26
- @status = body['status']
27
- assign_challenges(body['challenges'])
28
- end
29
-
30
- def assign_challenges(challenges)
31
- challenges.each do |attributes|
32
- challenge = case attributes.fetch('type')
33
- when 'http-01'
34
- @http01 ||= HTTP01.new(self)
35
- when 'dns-01'
36
- @dns01 ||= DNS01.new(self)
37
- when 'tls-sni-01'
38
- @tls_sni01 ||= TLSSNI01.new(self)
39
- end
40
-
41
- challenge.assign_attributes(attributes) if challenge
42
- end
43
- end
44
- end
@@ -1,43 +0,0 @@
1
- class Acme::Client::Resources::Challenges::Base
2
- attr_reader :authorization, :status, :uri, :token, :error
3
-
4
- def initialize(authorization)
5
- @authorization = authorization
6
- end
7
-
8
- def client
9
- authorization.client
10
- end
11
-
12
- def verify_status
13
- authorization.verify_status
14
-
15
- status
16
- end
17
-
18
- def request_verification
19
- response = client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key)
20
- response.success?
21
- end
22
-
23
- def assign_attributes(attributes)
24
- @status = attributes.fetch('status', 'pending')
25
- @uri = attributes.fetch('uri')
26
- @token = attributes.fetch('token')
27
- @error = attributes['error']
28
- end
29
-
30
- private
31
-
32
- def challenge_type
33
- self.class::CHALLENGE_TYPE
34
- end
35
-
36
- def authorization_key
37
- "#{token}.#{crypto.thumbprint}"
38
- end
39
-
40
- def crypto
41
- @crypto ||= Acme::Client::Crypto.new(client.private_key)
42
- end
43
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base
4
- CHALLENGE_TYPE = 'dns-01'.freeze
5
- RECORD_NAME = '_acme-challenge'.freeze
6
- RECORD_TYPE = 'TXT'.freeze
7
-
8
- def record_name
9
- RECORD_NAME
10
- end
11
-
12
- def record_type
13
- RECORD_TYPE
14
- end
15
-
16
- def record_content
17
- crypto.urlsafe_base64(crypto.digest.digest(authorization_key))
18
- end
19
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base
4
- CHALLENGE_TYPE = 'http-01'.freeze
5
- CONTENT_TYPE = 'text/plain'.freeze
6
-
7
- def content_type
8
- CONTENT_TYPE
9
- end
10
-
11
- def file_content
12
- authorization_key
13
- end
14
-
15
- def filename
16
- ".well-known/acme-challenge/#{token}"
17
- end
18
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base
4
- CHALLENGE_TYPE = 'tls-sni-01'.freeze
5
-
6
- def hostname
7
- digest = crypto.digest.hexdigest(authorization_key)
8
- "#{digest[0..31]}.#{digest[32..64]}.acme.invalid"
9
- end
10
-
11
- def certificate
12
- self_sign_certificate.certificate
13
- end
14
-
15
- def private_key
16
- self_sign_certificate.private_key
17
- end
18
-
19
- private
20
-
21
- def self_sign_certificate
22
- @self_sign_certificate ||= Acme::Client::SelfSignCertificate.new(subject_alt_names: [hostname])
23
- end
24
- end
@@ -1,6 +0,0 @@
1
- module Acme::Client::Resources::Challenges; end
2
-
3
- require 'acme/client/resources/challenges/base'
4
- require 'acme/client/resources/challenges/http01'
5
- require 'acme/client/resources/challenges/dns01'
6
- require 'acme/client/resources/challenges/tls_sni01'
@@ -1,37 +0,0 @@
1
- class Acme::Client::Resources::Registration
2
- attr_reader :id, :key, :contact, :uri, :next_uri, :recover_uri, :term_of_service_uri
3
-
4
- def initialize(client, response)
5
- @client = client
6
- @uri = response.headers['location']
7
- assign_links(response.headers['Link'])
8
- assign_attributes(response.body)
9
- end
10
-
11
- def get_terms
12
- return unless @term_of_service_uri
13
-
14
- @client.connection.get(@term_of_service_uri).body
15
- end
16
-
17
- def agree_terms
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?
22
- end
23
-
24
- private
25
-
26
- def assign_links(links)
27
- @next_uri = links['next']
28
- @recover_uri = links['recover']
29
- @term_of_service_uri = links['terms-of-service']
30
- end
31
-
32
- def assign_attributes(body)
33
- @id = body['id']
34
- @key = body['key']
35
- @contact = body['contact']
36
- end
37
- end
@@ -1,5 +0,0 @@
1
- module Acme::Client::Resources; end
2
-
3
- require 'acme/client/resources/registration'
4
- require 'acme/client/resources/challenges'
5
- require 'acme/client/resources/authorization'
@@ -1,60 +0,0 @@
1
- class Acme::Client::SelfSignCertificate
2
- attr_reader :private_key, :subject_alt_names, :not_before, :not_after
3
-
4
- extend Forwardable
5
- def_delegators :certificate, :to_pem, :to_der
6
-
7
- def initialize(subject_alt_names:, not_before: default_not_before, not_after: default_not_after, private_key: generate_private_key)
8
- @private_key = private_key
9
- @subject_alt_names = subject_alt_names
10
- @not_before = not_before
11
- @not_after = not_after
12
- end
13
-
14
- def certificate
15
- @certificate ||= begin
16
- certificate = generate_certificate
17
-
18
- extension_factory = generate_extension_factory(certificate)
19
- subject_alt_name_entry = subject_alt_names.map { |d| "DNS: #{d}" }.join(',')
20
- subject_alt_name_extension = extension_factory.create_extension('subjectAltName', subject_alt_name_entry)
21
- certificate.add_extension(subject_alt_name_extension)
22
-
23
- certificate.sign(private_key, digest)
24
- end
25
- end
26
-
27
- private
28
-
29
- def generate_private_key
30
- OpenSSL::PKey::RSA.new(2048)
31
- end
32
-
33
- def default_not_before
34
- Time.now - 3600
35
- end
36
-
37
- def default_not_after
38
- Time.now + 30 * 24 * 3600
39
- end
40
-
41
- def digest
42
- OpenSSL::Digest::SHA256.new
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.version = 2
51
- certificate
52
- end
53
-
54
- def generate_extension_factory(certificate)
55
- extension_factory = OpenSSL::X509::ExtensionFactory.new
56
- extension_factory.subject_certificate = certificate
57
- extension_factory.issuer_certificate = certificate
58
- extension_factory
59
- end
60
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Acme
4
- class Client
5
- VERSION = '0.4.1'.freeze
6
- end
7
- end
@@ -1,122 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'faraday'
4
- require 'json'
5
- require 'openssl'
6
- require 'digest'
7
- require 'forwardable'
8
- require 'base64'
9
- require 'time'
10
-
11
- module Acme; end
12
- class Acme::Client; end
13
-
14
- require 'acme/client/version'
15
- require 'acme/client/certificate'
16
- require 'acme/client/certificate_request'
17
- require 'acme/client/self_sign_certificate'
18
- require 'acme/client/crypto'
19
- require 'acme/client/resources'
20
- require 'acme/client/faraday_middleware'
21
- require 'acme/client/error'
22
-
23
- class Acme::Client
24
- DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'.freeze
25
- DIRECTORY_DEFAULT = {
26
- 'new-authz' => '/acme/new-authz',
27
- 'new-cert' => '/acme/new-cert',
28
- 'new-reg' => '/acme/new-reg',
29
- 'revoke-cert' => '/acme/revoke-cert'
30
- }.freeze
31
-
32
- def initialize(private_key:, endpoint: DEFAULT_ENDPOINT, directory_uri: nil, connection_options: {})
33
- @endpoint, @private_key, @directory_uri, @connection_options = endpoint, private_key, directory_uri, connection_options
34
- @nonces ||= []
35
- load_directory!
36
- end
37
-
38
- attr_reader :private_key, :nonces, :operation_endpoints
39
-
40
- def register(contact:)
41
- payload = {
42
- resource: 'new-reg', contact: Array(contact)
43
- }
44
-
45
- response = connection.post(@operation_endpoints.fetch('new-reg'), payload)
46
- ::Acme::Client::Resources::Registration.new(self, response)
47
- end
48
-
49
- def authorize(domain:)
50
- payload = {
51
- resource: 'new-authz',
52
- identifier: {
53
- type: 'dns',
54
- value: domain
55
- }
56
- }
57
-
58
- response = connection.post(@operation_endpoints.fetch('new-authz'), payload)
59
- ::Acme::Client::Resources::Authorization.new(self, response.headers['Location'], response)
60
- end
61
-
62
- def fetch_authorization(uri)
63
- response = connection.get(uri)
64
- ::Acme::Client::Resources::Authorization.new(self, uri, response)
65
- end
66
-
67
- def new_certificate(csr)
68
- payload = {
69
- resource: 'new-cert',
70
- csr: Base64.urlsafe_encode64(csr.to_der)
71
- }
72
-
73
- response = connection.post(@operation_endpoints.fetch('new-cert'), payload)
74
- ::Acme::Client::Certificate.new(OpenSSL::X509::Certificate.new(response.body), response.headers['location'], fetch_chain(response), csr)
75
- end
76
-
77
- def revoke_certificate(certificate)
78
- payload = { resource: 'revoke-cert', certificate: Base64.urlsafe_encode64(certificate.to_der) }
79
- endpoint = @operation_endpoints.fetch('revoke-cert')
80
- response = connection.post(endpoint, payload)
81
- response.success?
82
- end
83
-
84
- def self.revoke_certificate(certificate, *arguments)
85
- client = new(*arguments)
86
- client.revoke_certificate(certificate)
87
- end
88
-
89
- def connection
90
- @connection ||= Faraday.new(@endpoint, **@connection_options) do |configuration|
91
- configuration.use Acme::Client::FaradayMiddleware, client: self
92
- configuration.adapter Faraday.default_adapter
93
- end
94
- end
95
-
96
- private
97
-
98
- def fetch_chain(response, limit = 10)
99
- links = response.headers['link']
100
- if limit.zero? || links.nil? || links['up'].nil?
101
- []
102
- else
103
- issuer = connection.get(links['up'])
104
- [OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)]
105
- end
106
- end
107
-
108
- def load_directory!
109
- @operation_endpoints = if @directory_uri
110
- response = connection.get(@directory_uri)
111
- body = response.body
112
- {
113
- 'new-reg' => body.fetch('new-reg'),
114
- 'new-authz' => body.fetch('new-authz'),
115
- 'new-cert' => body.fetch('new-cert'),
116
- 'revoke-cert' => body.fetch('revoke-cert'),
117
- }
118
- else
119
- DIRECTORY_DEFAULT
120
- end
121
- end
122
- end
@@ -1 +0,0 @@
1
- require 'acme/client'