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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2bfc1b30ebefbb87d572378e91ac4d9e47552de
4
- data.tar.gz: 51bedd0f2e79328154c044f98c354b0bb2db4860
3
+ metadata.gz: b4f8844db80f42dfbebde13f4834adff81b31a6c
4
+ data.tar.gz: aac066e3a86278081ad327072318cf659065760d
5
5
  SHA512:
6
- metadata.gz: 03c36be67fa43961e1c61ce1ae9abaf0922042e4281240901b520f69bc38961e7690b7301cc95dc9be6962d9e23057f46fba3d154e56c33993f9abb767c29a14
7
- data.tar.gz: fb2079a8eb3c28772b01655e211a82391ee76f68ecc41cc41f95b7b800dc04aec3c6d4f9f36f8574a7d53b7bd78edf7b908a81296065bf0f29d289500a6e5bab
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
@@ -3,5 +3,6 @@ gemspec
3
3
 
4
4
  group :development, :test do
5
5
  gem 'pry'
6
+ gem 'rubocop', '0.36.0'
6
7
  gem 'ruby-prof', require: false
7
8
  end
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('key.pem') ),
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 "bundler/gem_tasks"
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
- task :default => :spec
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 "acme-client"
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
- if limit == 0 || response.headers["link"].nil? || response.headers["link"]["up"].nil?
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(response.headers["link"]["up"])
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
 
@@ -24,6 +24,6 @@ class Acme::Client::Certificate
24
24
  end
25
25
 
26
26
  def common_name
27
- x509.subject.to_a.find {|name, _, _| name == "CN" }[1]
27
+ x509.subject.to_a.find { |name, _, _| name == 'CN' }[1]
28
28
  end
29
29
  end
@@ -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: "CN",
8
- country_name: "C",
9
- organization_name: "O",
10
- organizational_unit: "OU",
11
- state_or_province: "ST",
12
- locality_name: "L"
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
- "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
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({}) {|(key, value), subject|
60
- subject[SUBJECT_KEYS.fetch(key, key)] = value.to_s
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
- unless extra_keys.empty?
77
- raise ArgumentError.new("Unexpected subject attributes given: #{extra_keys.inspect}")
78
- end
71
+ return if extra_keys.empty?
72
+ raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}"
73
+ end
79
74
 
80
- unless @common_name == @subject[SUBJECT_KEYS[:common_name]]
81
- raise ArgumentError.new("Conflicting common name given in arguments and subject")
82
- end
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
- "subjectAltName", @names.map {|name| "DNS:#{name}" }.join(", "), false
101
+ 'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
107
102
  )
108
103
  csr.add_attribute(
109
104
  OpenSSL::X509::Attribute.new(
110
- "extReq",
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 {|env| on_complete(env) }
12
+ @app.call(env).on_complete { |response_env| on_complete(response_env) }
13
13
  end
14
14
 
15
15
  def on_complete(env)
16
- raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
16
+ @env = env
17
17
 
18
- nonces << env.response_headers['replay-nonce']
18
+ raise_on_not_found!
19
+ store_nonce
20
+ env.body = decode_body
21
+ env.response_headers['Link'] = decode_link_headers
19
22
 
20
- content_type = env.response_headers['Content-Type']
23
+ return if env.success?
21
24
 
22
- if content_type == 'application/json' || content_type == 'application/problem+json'
23
- env.body = JSON.load(env.body)
24
- end
25
+ raise_on_error!
26
+ end
25
27
 
26
- if env.response_headers.key?('Link')
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
- env.response_headers['Link'] = Hash[*links.flatten]
35
- end
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
- return if env.success?
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
- error_class = if Acme::Client::Error.qualified_const_defined?(error_name)
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
- message = if env.body.is_a? Hash
47
- env.body['detail']
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
- "Error message: #{env.body}"
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
- raise error_class, message
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
- private
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
- else
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.hexdigest(authorization_key)
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
- if @term_of_service_uri
13
- @client.connection.get(@term_of_service_uri).body
14
- end
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
- if @term_of_service_uri
19
- payload = {
20
- resource: "reg",
21
- agreement: @term_of_service_uri
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 = OpenSSL::X509::Certificate.new
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 + 3600
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
@@ -1,5 +1,5 @@
1
1
  module Acme
2
2
  class Client
3
- VERSION = '0.2.4'
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
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.2.4
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: 2015-12-22 00:00:00.000000000 Z
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.