acme-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c20a6817f7b9cabcc97fd675cb6e9977544de7de
4
+ data.tar.gz: 04ab2587617f4c1c94bab02ecc0f902c71c11576
5
+ SHA512:
6
+ metadata.gz: 7cc46125a7e5565dab48ee669330663d471ee93877e6e0751cde792ca389dd03290eade0ef9e4bbb6dbcd50f955d7071fe7de6d3e6bb01fb7439d9c15563ebab
7
+ data.tar.gz: a5b7c82005996edfa8a11bddaeae46df26748a037666f5349980c7d6d0df3be4b63d91908ce24d96fecc42d94140c6b80e7d4877cbdf02ef510cad882146ed6f
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in acme-client.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem 'pry'
8
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Charles Barbier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,84 @@
1
+ # Acme::Client
2
+
3
+ `acme-client` is a client implementation of the [ACME](https://letsencrypt.github.io/acme-spec) protocol in Ruby.
4
+
5
+ You can find the server reference implementation for ACME server at [here](github.com/letsencrypt/boulder) and also the a reference [client](github.com/letsencrypt/letsencrypt) written in python.
6
+
7
+ ACME is part of the [Letsencrypt](https://letsencrypt.org/) project, that are working hard at encrypting all the things.
8
+
9
+ ## Usage
10
+
11
+ ```ruby
12
+ # We're going to need a private key.
13
+ private_key = OpenSSL::PKey::RSA.new(2048)
14
+
15
+ # We need an ACME server to talk to, see github.com/letsencrypt/boulder
16
+ endpoint = 'http://letsencrypt.com/'
17
+
18
+ # Initialize the client
19
+ client = Acme::Client.new(private_key: private_key, endpoint: endpoint)
20
+
21
+ # If the private key is not known to the server we need to register for the first time.
22
+ registration = client.register(contact: 'mailto:unixcharles@gmail.com')
23
+
24
+ # You'll need to agree the term (that's up the to the server to require it or not but boulder does by default)
25
+ registration.agree_terms
26
+
27
+ # Let's try to optain a certificate for yourdomain.com
28
+ authorization = client.authorize(domain: 'yourdomain.com')
29
+
30
+ # We need to prove that we control the domain using one of the challanges method
31
+ simple_http = authorization.simple_http
32
+
33
+ # The SimpleHTTP method will require you to response to an HTTP request.
34
+
35
+ # You can retreive the expected path for the file.
36
+ simple_http.filename # => ".well-known/acme-challenge/:some_token"
37
+
38
+ # You can retrieve the body of the expected response
39
+ simple_http.file_content # => 'string of JWS signed json'
40
+
41
+ # You can send no Content-Type at all but if you send one it has to be 'application/jose+json'
42
+ simple_http.content_type
43
+
44
+ # Once you are ready to serve the confirmation request you can proceed.
45
+ simple_http.request_verification # => true
46
+ simple_http.verify_status # => 'pending'
47
+
48
+ # Wait a bit for the server to make the request, or really just blink, should be fast.
49
+ sleep(1)
50
+
51
+ simple_http.verify_status # => 'pending'
52
+
53
+ # We're going to need a CSR, let do this real quick with Ruby+OpenSSL.
54
+ request = OpenSSL::X509::Request.new
55
+ request.subject = OpenSSL::X509::Name.new([
56
+ ['CN', common_name, OpenSSL::ASN1::UTF8STRING]
57
+ ])
58
+
59
+ request.public_key = private_key.public_key
60
+ request.sign(private_key, OpenSSL::Digest::SHA256.new)
61
+
62
+ # You can request a new certificate
63
+ client.new_certificate(csr) # => #<OpenSSL::X509::Certificate ....>
64
+ ```
65
+
66
+ # Not implemented
67
+
68
+ - Recovery methods are not implemented.
69
+ - SimpleHTTP is the only challenge method implemented
70
+
71
+ ## Development
72
+
73
+ All the tests use VCR to mock the interaction with the server but if you
74
+ need to record new interation against the server simply clone boulder and
75
+ run it normally with `./start.py`.
76
+
77
+ ## Pull request?
78
+
79
+ Yes.
80
+
81
+ ## License
82
+
83
+ [MIT License](http://opensource.org/licenses/MIT)
84
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acme/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'acme-client'
8
+ spec.version = Acme::Client::VERSION
9
+ spec.authors = ['Charles Barbier']
10
+ spec.email = ['unixcharles@gmail.com']
11
+ spec.summary = 'Client for the ACME protocol.'
12
+ spec.homepage = 'http://github.com/unixcharles/acme-client'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.add_development_dependency 'bundler', '~> 1.10'
19
+ spec.add_development_dependency 'rake', '~> 10.0'
20
+ spec.add_development_dependency 'rspec', '~> 3.3', '>= 3.3.0'
21
+ spec.add_development_dependency 'vcr', '~> 2.9', '>= 2.9.3'
22
+ spec.add_development_dependency 'webmock', '~> 1.21', '>= 1.21.0'
23
+
24
+ spec.add_runtime_dependency 'faraday', '~> 0.9', '>= 0.9.1'
25
+ spec.add_runtime_dependency 'url_safe_base64', '~> 0.2', '>= 0.2.2'
26
+ spec.add_runtime_dependency 'json-jwt', '~> 1.2', '>= 1.2.3'
27
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "acme/client"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,66 @@
1
+ class Acme::Client
2
+ DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'
3
+
4
+ def initialize(endpoint: DEFAULT_ENDPOINT, directory_uri: nil, private_key:)
5
+ @endpoint, @private_key, @directory_uri = endpoint, private_key, directory_uri
6
+ @nonces ||= []
7
+ load_directory!
8
+ end
9
+
10
+ attr_reader :private_key, :nonces, :operation_endpoints
11
+
12
+ def register(contact:)
13
+ payload = {
14
+ resource: 'new-reg', contact: Array.wrap(contact)
15
+ }
16
+
17
+ response = connection.post(@operation_endpoints.fetch('new-reg'), payload)
18
+ ::Acme::Resources::Registration.new(self, response)
19
+ end
20
+
21
+ def authorize(domain:)
22
+ payload = {
23
+ resource: "new-authz",
24
+ identifier: {
25
+ type: "dns",
26
+ value: domain
27
+ }
28
+ }
29
+
30
+ response = connection.post(@operation_endpoints.fetch('new-authz'), payload)
31
+ ::Acme::Resources::Authorization.new(self, response)
32
+ end
33
+
34
+ def new_certificate(csr)
35
+ payload = {
36
+ resource: 'new-cert',
37
+ csr: UrlSafeBase64.encode64(csr.to_der)
38
+ }
39
+
40
+ response = connection.post(@operation_endpoints.fetch('new-cert'), payload)
41
+ OpenSSL::X509::Certificate.new(response.body)
42
+ end
43
+
44
+ def connection
45
+ @connection ||= Faraday.new(@endpoint) do |configuration|
46
+ configuration.use Acme::FaradayMiddleware, client: self
47
+ configuration.adapter Faraday.default_adapter
48
+ end
49
+ end
50
+
51
+ def load_directory!
52
+ @operation_endpoints = if @directory_uri
53
+ response = connection.get(@directory_uri)
54
+ body = response.body
55
+ {
56
+ 'new-reg' => body.fetch('new-reg'),
57
+ 'recover-reg' => body.fetch('recover-reg'),
58
+ 'new-authz' => body.fetch('new-authz'),
59
+ 'new-cert' => body.fetch('new-cert'),
60
+ 'revoke-cert' => body.fetch('revoke-cert'),
61
+ }
62
+ else
63
+ DIRECTORY_DEFAULT
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,47 @@
1
+ class Acme::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
+ protection_header = generate_protection_header(header)
10
+ payload = encode64(JSON.dump(payload))
11
+
12
+ JSON.dump(
13
+ {
14
+ header: { alg: :RS256, jwk: jwk },
15
+ protected: protection_header,
16
+ payload: payload,
17
+ signature: generate_signature(protection_header, payload)
18
+ }
19
+ )
20
+ end
21
+
22
+ def generate_signature(protection_header, payload)
23
+ input = "#{protection_header}.#{payload}"
24
+ signature = private_key.sign(digest, input)
25
+ encode64(signature)
26
+ end
27
+
28
+ def generate_protection_header(header)
29
+ encode64(JSON.dump(header))
30
+ end
31
+
32
+ def jwk
33
+ JSON::JWK.new(public_key).to_hash
34
+ end
35
+
36
+ def public_key
37
+ private_key.public_key
38
+ end
39
+
40
+ def digest
41
+ OpenSSL::Digest::SHA256.new
42
+ end
43
+
44
+ def encode64(input)
45
+ UrlSafeBase64.encode64(input)
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ class Acme::Error < StandardError
2
+ class NotFound < Acme::Error; end
3
+ class BadCSR < Acme::Error; end
4
+ class BadNonce < Acme::Error; end
5
+ class Connection < Acme::Error; end
6
+ class Dnssec < Acme::Error; end
7
+ class Malformed < Acme::Error; end
8
+ class ServerInternal < Acme::Error; end
9
+ class Acme::Tls < Acme::Error; end
10
+ class Unauthorized < Acme::Error; end
11
+ class UnknownHost < Acme::Error; end
12
+ end
@@ -0,0 +1,81 @@
1
+ class Acme::FaradayMiddleware < Faraday::Middleware
2
+ attr_reader :env, :response, :client
3
+
4
+ def initialize(app, client:)
5
+ super(app)
6
+ @client = client
7
+ end
8
+
9
+ def call(env)
10
+ @env = env
11
+ @env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body)
12
+ @app.call(env).on_complete {|env| on_complete(env) }
13
+ end
14
+
15
+ def on_complete(env)
16
+ raise Acme::Error::NotFound, env.url.to_s if env.status == 404
17
+
18
+ nonces << env.response_headers['replay-nonce']
19
+
20
+ content_type = env.response_headers['Content-Type']
21
+
22
+ if content_type == 'application/json' || content_type == 'application/problem+json'
23
+ env.body = JSON.load(env.body)
24
+ end
25
+
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
33
+
34
+ env.response_headers['Link'] = Hash[*links.flatten]
35
+ end
36
+
37
+ return if env.success?
38
+
39
+ error_name = env.body['type'].gsub('urn:acme:error:', '').classify
40
+ error_class = if Acme::Error.qualified_const_defined?(error_name)
41
+ "Acme::Error::#{error_name}".constantize
42
+ else
43
+ Acme::Error
44
+ end
45
+
46
+ message = if env.body.is_a? Hash
47
+ env.body['detail']
48
+ else
49
+ "Error message: #{env.body}"
50
+ end
51
+
52
+ raise error_class, env.body['detail']
53
+ end
54
+
55
+ private
56
+
57
+ def pop_nonce
58
+ if nonces.empty?
59
+ get_nonce
60
+ else
61
+ nonces.pop
62
+ end
63
+ end
64
+
65
+ def get_nonce
66
+ response = Faraday.head(env.url)
67
+ response.headers['replay-nonce']
68
+ end
69
+
70
+ def nonces
71
+ client.nonces
72
+ end
73
+
74
+ def private_key
75
+ client.private_key
76
+ end
77
+
78
+ def crypto
79
+ @crypto ||= Acme::Crypto.new(private_key)
80
+ end
81
+ end
@@ -0,0 +1,4 @@
1
+ module Acme::Resources; end
2
+ require 'acme/resources/registration'
3
+ require 'acme/resources/challenges'
4
+ require 'acme/resources/authorization'
@@ -0,0 +1,28 @@
1
+ class Acme::Resources::Authorization
2
+ SimpleHttp = Acme::Resources::Challenges::SimpleHttp
3
+
4
+ attr_reader :domain, :status, :simple_http
5
+
6
+ def initialize(client, response)
7
+ @client = client
8
+ assign_challenges(response.body['challenges'])
9
+ assign_attributes(response.body)
10
+ end
11
+
12
+ private
13
+
14
+ def assign_challenges(challenges)
15
+ challenges.each do |attributes|
16
+ case attributes.fetch('type')
17
+ when 'simpleHttp' then @simple_http = SimpleHttp.new(@client, attributes)
18
+ else
19
+ # no supported
20
+ end
21
+ end
22
+ end
23
+
24
+ def assign_attributes(body)
25
+ @domain = body['identifier']['value']
26
+ @status = body['status']
27
+ end
28
+ end
@@ -0,0 +1,2 @@
1
+ module Acme::Resources::Challenges; end
2
+ require 'acme/resources/challenges/simple_http'
@@ -0,0 +1,50 @@
1
+ class Acme::Resources::Challenges::SimpleHttp
2
+ CONTENT_TYPE = 'application/jose+json'
3
+
4
+ attr_reader :status, :uri, :token, :error
5
+ attr_accessor :tls
6
+
7
+ def initialize(client, attributes)
8
+ @client = client
9
+ assign_attributes(attributes)
10
+ end
11
+
12
+ def content_type
13
+ CONTENT_TYPE
14
+ end
15
+
16
+ def file_content
17
+ message = { 'type' => 'simpleHttp', 'token' => token, 'tls' => tls }
18
+ crypto.generate_signed_jws(header: {}, payload: message)
19
+ end
20
+
21
+ def filename
22
+ ".well-known/acme-challenge/#{token}"
23
+ end
24
+
25
+ def request_verification
26
+ response = @client.connection.post(@uri, { resource: 'challenge', type: 'simpleHttp', tls: tls })
27
+ response.success?
28
+ end
29
+
30
+ def verify_status
31
+ response = @client.connection.get(@uri)
32
+
33
+ assign_attributes(response.body)
34
+ @error = response.body['error']
35
+ status
36
+ end
37
+
38
+ private
39
+
40
+ def assign_attributes(attributes)
41
+ @status = attributes.fetch('status', 'pending')
42
+ @uri = attributes.fetch('uri')
43
+ @token = attributes.fetch('token')
44
+ @tls = attributes.fetch('tls')
45
+ end
46
+
47
+ def crypto
48
+ @crypto ||= Acme::Crypto.new(@client.private_key)
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ class Acme::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
+ if @term_of_service_uri
13
+ @client.connection.get(@term_of_service_uri).body
14
+ end
15
+ end
16
+
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
28
+ end
29
+
30
+ private
31
+
32
+ def assign_links(links)
33
+ @next_uri = links['next']
34
+ @recover_uri = links['recover']
35
+ @term_of_service_uri = links['terms-of-service']
36
+ end
37
+
38
+ def assign_attributes(body)
39
+ @id = body['id']
40
+ @key = body['key']
41
+ @contact = body['contact']
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ module Acme
2
+ class Client
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ module Acme; end
2
+
3
+ require 'url_safe_base64'
4
+ require 'faraday'
5
+ require 'json'
6
+ require 'json/jwt'
7
+ require 'openssl'
8
+ require 'digest'
9
+
10
+ require 'acme/crypto'
11
+ require 'acme/client'
12
+ require 'acme/resources'
13
+ require 'acme/faraday_middleware'
14
+ require 'acme/error'
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acme-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Charles Barbier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.3'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 3.3.0
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '3.3'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 3.3.0
61
+ - !ruby/object:Gem::Dependency
62
+ name: vcr
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.9'
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 2.9.3
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '2.9'
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 2.9.3
81
+ - !ruby/object:Gem::Dependency
82
+ name: webmock
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.21'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 1.21.0
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.21'
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 1.21.0
101
+ - !ruby/object:Gem::Dependency
102
+ name: faraday
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '0.9'
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 0.9.1
111
+ type: :runtime
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.9'
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: 0.9.1
121
+ - !ruby/object:Gem::Dependency
122
+ name: url_safe_base64
123
+ requirement: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - "~>"
126
+ - !ruby/object:Gem::Version
127
+ version: '0.2'
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 0.2.2
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.2'
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: 0.2.2
141
+ - !ruby/object:Gem::Dependency
142
+ name: json-jwt
143
+ requirement: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - "~>"
146
+ - !ruby/object:Gem::Version
147
+ version: '1.2'
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: 1.2.3
151
+ type: :runtime
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '1.2'
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: 1.2.3
161
+ description:
162
+ email:
163
+ - unixcharles@gmail.com
164
+ executables: []
165
+ extensions: []
166
+ extra_rdoc_files: []
167
+ files:
168
+ - ".gitignore"
169
+ - ".rspec"
170
+ - ".travis.yml"
171
+ - Gemfile
172
+ - LICENSE.txt
173
+ - README.md
174
+ - Rakefile
175
+ - acme-client.gemspec
176
+ - bin/console
177
+ - bin/setup
178
+ - lib/acme/client.rb
179
+ - lib/acme/crypto.rb
180
+ - lib/acme/error.rb
181
+ - lib/acme/faraday_middleware.rb
182
+ - lib/acme/resources.rb
183
+ - lib/acme/resources/authorization.rb
184
+ - lib/acme/resources/challenges.rb
185
+ - lib/acme/resources/challenges/simple_http.rb
186
+ - lib/acme/resources/registration.rb
187
+ - lib/acme/version.rb
188
+ - lib/acme_client.rb
189
+ homepage: http://github.com/unixcharles/acme-client
190
+ licenses:
191
+ - MIT
192
+ metadata: {}
193
+ post_install_message:
194
+ rdoc_options: []
195
+ require_paths:
196
+ - lib
197
+ required_ruby_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ required_rubygems_version: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ requirements: []
208
+ rubyforge_project:
209
+ rubygems_version: 2.2.0
210
+ signing_key:
211
+ specification_version: 4
212
+ summary: Client for the ACME protocol.
213
+ test_files: []