leap_cli 1.9 → 1.9.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
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'