diff --git a/leap_cli.gemspec b/leap_cli.gemspec
index ca4518a03e3cc4020c1dfd1ccad1b9932038716f..48c75ceff6d1987b90d1f8d7ecdf39ce430a79f2 100644
--- a/leap_cli.gemspec
+++ b/leap_cli.gemspec
@@ -57,6 +57,10 @@ spec = Gem::Specification.new do |s|
   # s.add_runtime_dependency('gpgme')    # << does not build on debian jessie, so now optional.
                                          # also, there is a ruby-gpgme package anyway.
 
+  # acme-client is vendored for now, we need pre-lease version
+  # s.add_runtime_dependency('acme-client', '~> 0.4.2')
+  s.add_runtime_dependency('faraday', '~> 0.9', '>= 0.9.1') # for acme-client
+
   # misc gems
   s.add_runtime_dependency('ya2yaml', '~> 0.31')    # pure ruby yaml, so we can better control output. see https://github.com/afunai/ya2yaml
   s.add_runtime_dependency('json_pure', '~> 1.8')   # pure ruby json, so we can better control output.
diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb
index bb8bbaf6f130e38eb6a2194917861ee558075121..bb8b57c3bde5395e4540f83567c4a50c1d5b3b87 100644
--- a/lib/leap_cli/version.rb
+++ b/lib/leap_cli/version.rb
@@ -7,7 +7,8 @@ module LeapCli
     LOAD_PATHS = ['lib',
       'vendor/certificate_authority/lib',
       'vendor/rsync_command/lib',
-      'vendor/base32/lib'
+      'vendor/base32/lib',
+      'vendor/acme-client/lib'
     ]
   end
 end
diff --git a/vendor/acme-client/Gemfile b/vendor/acme-client/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..e0b10dfdba1e051efc85d14a48702244d8c20fc0
--- /dev/null
+++ b/vendor/acme-client/Gemfile
@@ -0,0 +1,12 @@
+source 'https://rubygems.org'
+gemspec
+
+group :development, :test do
+  gem 'pry'
+  gem 'rubocop', '0.36.0'
+  gem 'ruby-prof', require: false
+
+  if Gem::Version.new(RUBY_VERSION) <= Gem::Version.new('2.2.2')
+    gem 'activesupport', '~> 4.2.6'
+  end
+end
diff --git a/vendor/acme-client/LICENSE.txt b/vendor/acme-client/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..73b96b4863efeee3472fe037367b71706c6660ce
--- /dev/null
+++ b/vendor/acme-client/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Charles Barbier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/acme-client/README.md b/vendor/acme-client/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..2047885ac03b98819d6a87187f8231595a728a76
--- /dev/null
+++ b/vendor/acme-client/README.md
@@ -0,0 +1,168 @@
+# Acme::Client
+[![Build Status](https://travis-ci.org/unixcharles/acme-client.svg?branch=master)](https://travis-ci.org/unixcharles/acme-client)
+
+`acme-client` is a client implementation of the [ACME](https://letsencrypt.github.io/acme-spec) protocol in Ruby.
+
+You can find the ACME reference implementations of the [server](https://github.com/letsencrypt/boulder) in Go and the [client](https://github.com/letsencrypt/letsencrypt) in Python.
+
+ACME is part of the [Letsencrypt](https://letsencrypt.org/) project, which goal is to provide free SSL/TLS certificates with  automation of the acquiring and renewal process.
+
+## Installation
+
+Via Rubygems:
+
+	$ gem install acme-client
+
+Or add it to a Gemfile:
+
+```ruby
+gem 'acme-client'
+```
+
+## Usage
+
+### Register client
+
+In order to authenticate our client, we have to create an account for it.
+
+```ruby
+# We're going to need a private key.
+require 'openssl'
+private_key = OpenSSL::PKey::RSA.new(4096)
+
+# We need an ACME server to talk to, see github.com/letsencrypt/boulder
+# WARNING: This endpoint is the production endpoint, which is rate limited and will produce valid certificates.
+# You should probably use the staging endpoint for all your experimentation:
+# endpoint = 'https://acme-staging.api.letsencrypt.org/'
+endpoint = 'https://acme-v01.api.letsencrypt.org/'
+
+# Initialize the client
+require 'acme-client'
+client = Acme::Client.new(private_key: private_key, endpoint: endpoint, connection_options: { request: { open_timeout: 5, timeout: 5 } })
+
+# If the private key is not known to the server, we need to register it for the first time.
+registration = client.register(contact: 'mailto:contact@example.com')
+
+# You may need to agree to the terms of service (that's up the to the server to require it or not but boulder does by default)
+registration.agree_terms
+```
+
+### Authorize for domain
+
+Before you are able to obtain certificates for your domain, you have to prove that you are in control of it.
+
+```ruby
+authorization = client.authorize(domain: 'example.org')
+
+# If authorization.status returns 'valid' here you can already get a certificate
+# and _must not_ try to solve another challenge.
+authorization.status # => 'pending'
+
+# You can can store the authorization's URI to fully recover it and
+# any associated challenges via Acme::Client#fetch_authorization.
+authorization.uri # => '...'
+
+# This example is using the http-01 challenge type. Other challenges are dns-01 or tls-sni-01.
+challenge = authorization.http01
+
+# The http-01 method will require you to respond to a HTTP request.
+
+# You can retrieve the challenge token
+challenge.token # => "some_token"
+
+# You can retrieve the expected path for the file.
+challenge.filename # => ".well-known/acme-challenge/:some_token"
+
+# You can generate the body of the expected response.
+challenge.file_content # => 'string token and JWK thumbprint'
+
+# You are not required to send a Content-Type. This method will return the right Content-Type should you decide to include one.
+challenge.content_type
+
+# Save the file. We'll create a public directory to serve it from, and inside it we'll create the challenge file.
+FileUtils.mkdir_p( File.join( 'public', File.dirname( challenge.filename ) ) )
+
+# We'll write the content of the file
+File.write( File.join( 'public', challenge.filename), challenge.file_content )
+
+# Optionally save the challenge for use at another time (eg: by a background job processor)
+File.write('challenge', challenge.to_h.to_json)
+
+# The challenge file can be served with a Ruby webserver.
+# You can run a webserver in another console for that purpose. You may need to forward ports on your router.
+#
+# $ ruby -run -e httpd public -p 8080 --bind-address 0.0.0.0
+
+# Load a saved challenge. This is only required if you need to reuse a saved challenge as outlined above.
+challenge = client.challenge_from_hash(JSON.parse(File.read('challenge')))
+
+# Once you are ready to serve the confirmation request you can proceed.
+challenge.request_verification # => true
+challenge.authorization.verify_status # => 'pending'
+
+# Wait a bit for the server to make the request, or just blink. It should be fast.
+sleep(1)
+
+# Rely on authorization.verify_status more than on challenge.verify_status,
+# if the former is 'valid' you can already issue a certificate and the status of
+# the challenge is not relevant and in fact may never change from pending.
+challenge.authorization.verify_status # => 'valid'
+challenge.error # => nil
+
+# If authorization.verify_status is 'invalid', you can get at the error
+# message only through the failed challenge.
+authorization.verify_status # => 'invalid'
+authorization.http01.error # => {"type" => "...", "detail" => "..."}
+```
+
+### Obtain a certificate
+
+Now that your account is authorized for the domain, you should be able to obtain a certificate for it.
+
+```ruby
+# We're going to need a certificate signing request. If not explicitly
+# specified, the first name listed becomes the common name.
+csr = Acme::Client::CertificateRequest.new(names: %w[example.org www.example.org])
+
+# We can now request a certificate. You can pass anything that returns
+# a valid DER encoded CSR when calling to_der on it. For example an
+# OpenSSL::X509::Request should work too.
+certificate = client.new_certificate(csr) # => #<Acme::Client::Certificate ....>
+
+# Save the certificate and the private key to files
+File.write("privkey.pem", certificate.request.private_key.to_pem)
+File.write("cert.pem", certificate.to_pem)
+File.write("chain.pem", certificate.chain_to_pem)
+File.write("fullchain.pem", certificate.fullchain_to_pem)
+
+# Start a webserver, using your shiny new certificate
+# ruby -r openssl -r webrick -r 'webrick/https' -e "s = WEBrick::HTTPServer.new(
+#   :Port => 8443,
+#   :DocumentRoot => Dir.pwd,
+#   :SSLEnable => true,
+#   :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.read('privkey.pem') ),
+#   :SSLCertificate => OpenSSL::X509::Certificate.new( File.read('cert.pem') )); trap('INT') { s.shutdown }; s.start"
+```
+
+# Not implemented
+
+- Recovery methods are not implemented.
+
+# Requirements
+
+Ruby >= 2.1
+
+## Development
+
+All the tests use VCR to mock the interaction with the server but if you
+need to record new interation against the server simply clone boulder and
+run it normally with `./start.py`.
+
+## Pull request?
+
+Yes.
+
+## License
+
+[MIT License](http://opensource.org/licenses/MIT)
+
diff --git a/vendor/acme-client/acme-client.gemspec b/vendor/acme-client/acme-client.gemspec
new file mode 100644
index 0000000000000000000000000000000000000000..b62d60c3f9744c2709aa4c042334ff2900f79dfb
--- /dev/null
+++ b/vendor/acme-client/acme-client.gemspec
@@ -0,0 +1,27 @@
+# coding: utf-8
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'acme/client/version'
+
+Gem::Specification.new do |spec|
+  spec.name          = 'acme-client'
+  spec.version       = Acme::Client::VERSION
+  spec.authors       = ['Charles Barbier']
+  spec.email         = ['unixcharles@gmail.com']
+  spec.summary       = 'Client for the ACME protocol.'
+  spec.homepage      = 'http://github.com/unixcharles/acme-client'
+  spec.license       = 'MIT'
+
+  spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
+  spec.require_paths = ['lib']
+
+  spec.required_ruby_version = '>= 2.1.0'
+
+  spec.add_development_dependency 'bundler', '~> 1.6', '>= 1.6.9'
+  spec.add_development_dependency 'rake', '~> 10.0'
+  spec.add_development_dependency 'rspec', '~> 3.3', '>= 3.3.0'
+  spec.add_development_dependency 'vcr', '~> 2.9', '>= 2.9.3'
+  spec.add_development_dependency 'webmock', '~> 1.21', '>= 1.21.0'
+
+  spec.add_runtime_dependency 'faraday', '~> 0.9', '>= 0.9.1'
+end
diff --git a/vendor/acme-client/lib/acme-client.rb b/vendor/acme-client/lib/acme-client.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7cc7a0a9d1c41b26aff613f95a688b7f43114e3b
--- /dev/null
+++ b/vendor/acme-client/lib/acme-client.rb
@@ -0,0 +1 @@
+require 'acme/client'
diff --git a/vendor/acme-client/lib/acme/client.rb b/vendor/acme-client/lib/acme/client.rb
new file mode 100644
index 0000000000000000000000000000000000000000..801479e585de7cce4ddd322216e9b8ed0e19157c
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'faraday'
+require 'json'
+require 'openssl'
+require 'digest'
+require 'forwardable'
+require 'base64'
+require 'time'
+
+module Acme; end
+class Acme::Client; end
+
+require 'acme/client/version'
+require 'acme/client/certificate'
+require 'acme/client/certificate_request'
+require 'acme/client/self_sign_certificate'
+require 'acme/client/crypto'
+require 'acme/client/resources'
+require 'acme/client/faraday_middleware'
+require 'acme/client/error'
+
+class Acme::Client
+  DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'.freeze
+  DIRECTORY_DEFAULT = {
+    'new-authz' => '/acme/new-authz',
+    'new-cert' => '/acme/new-cert',
+    'new-reg' => '/acme/new-reg',
+    'revoke-cert' => '/acme/revoke-cert'
+  }.freeze
+
+  def initialize(private_key:, endpoint: DEFAULT_ENDPOINT, directory_uri: nil, connection_options: {})
+    @endpoint, @private_key, @directory_uri, @connection_options = endpoint, private_key, directory_uri, connection_options
+    @nonces ||= []
+    load_directory!
+  end
+
+  attr_reader :private_key, :nonces, :operation_endpoints
+
+  def register(contact:)
+    payload = {
+      resource: 'new-reg', contact: Array(contact)
+    }
+
+    response = connection.post(@operation_endpoints.fetch('new-reg'), payload)
+    ::Acme::Client::Resources::Registration.new(self, response)
+  end
+
+  def authorize(domain:)
+    payload = {
+      resource: 'new-authz',
+      identifier: {
+        type: 'dns',
+        value: domain
+      }
+    }
+
+    response = connection.post(@operation_endpoints.fetch('new-authz'), payload)
+    ::Acme::Client::Resources::Authorization.new(self, response.headers['Location'], response)
+  end
+
+  def fetch_authorization(uri)
+    response = connection.get(uri)
+    ::Acme::Client::Resources::Authorization.new(self, uri, response)
+  end
+
+  def new_certificate(csr)
+    payload = {
+      resource: 'new-cert',
+      csr: Base64.urlsafe_encode64(csr.to_der)
+    }
+
+    response = connection.post(@operation_endpoints.fetch('new-cert'), payload)
+    ::Acme::Client::Certificate.new(OpenSSL::X509::Certificate.new(response.body), response.headers['location'], fetch_chain(response), csr)
+  end
+
+  def revoke_certificate(certificate)
+    payload = { resource: 'revoke-cert', certificate: Base64.urlsafe_encode64(certificate.to_der) }
+    endpoint = @operation_endpoints.fetch('revoke-cert')
+    response = connection.post(endpoint, payload)
+    response.success?
+  end
+
+  def self.revoke_certificate(certificate, *arguments)
+    client = new(*arguments)
+    client.revoke_certificate(certificate)
+  end
+
+  def connection
+    @connection ||= Faraday.new(@endpoint, **@connection_options) do |configuration|
+      configuration.use Acme::Client::FaradayMiddleware, client: self
+      configuration.adapter Faraday.default_adapter
+    end
+  end
+
+  private
+
+  def fetch_chain(response, limit = 10)
+    links = response.headers['link']
+    if limit.zero? || links.nil? || links['up'].nil?
+      []
+    else
+      issuer = connection.get(links['up'])
+      [OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)]
+    end
+  end
+
+  def load_directory!
+    @operation_endpoints = if @directory_uri
+      response = connection.get(@directory_uri)
+      body = response.body
+      {
+        'new-reg' => body.fetch('new-reg'),
+        'new-authz' => body.fetch('new-authz'),
+        'new-cert' => body.fetch('new-cert'),
+        'revoke-cert' => body.fetch('revoke-cert'),
+      }
+    else
+      DIRECTORY_DEFAULT
+    end
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/certificate.rb b/vendor/acme-client/lib/acme/client/certificate.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c68cc56531199d96162f7f8a01d8e7eb0e19396
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/certificate.rb
@@ -0,0 +1,30 @@
+class Acme::Client::Certificate
+  extend Forwardable
+
+  attr_reader :x509, :x509_chain, :request, :private_key, :url
+
+  def_delegators :x509, :to_pem, :to_der
+
+  def initialize(certificate, url, chain, request)
+    @x509 = certificate
+    @url = url
+    @x509_chain = chain
+    @request = request
+  end
+
+  def chain_to_pem
+    x509_chain.map(&:to_pem).join
+  end
+
+  def x509_fullchain
+    [x509, *x509_chain]
+  end
+
+  def fullchain_to_pem
+    x509_fullchain.map(&:to_pem).join
+  end
+
+  def common_name
+    x509.subject.to_a.find { |name, _, _| name == 'CN' }[1]
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/certificate_request.rb b/vendor/acme-client/lib/acme/client/certificate_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8eae0c62bd7f578026f7776d021c2461ddb086b3
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/certificate_request.rb
@@ -0,0 +1,111 @@
+class Acme::Client::CertificateRequest
+  extend Forwardable
+
+  DEFAULT_KEY_LENGTH = 2048
+  DEFAULT_DIGEST = OpenSSL::Digest::SHA256
+  SUBJECT_KEYS = {
+    common_name:         'CN',
+    country_name:        'C',
+    organization_name:   'O',
+    organizational_unit: 'OU',
+    state_or_province:   'ST',
+    locality_name:       'L'
+  }.freeze
+
+  SUBJECT_TYPES = {
+    'CN' => OpenSSL::ASN1::UTF8STRING,
+    'C'  => OpenSSL::ASN1::UTF8STRING,
+    'O'  => OpenSSL::ASN1::UTF8STRING,
+    'OU' => OpenSSL::ASN1::UTF8STRING,
+    'ST' => OpenSSL::ASN1::UTF8STRING,
+    'L'  => OpenSSL::ASN1::UTF8STRING
+  }.freeze
+
+  attr_reader :private_key, :common_name, :names, :subject
+
+  def_delegators :csr, :to_pem, :to_der
+
+  def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new)
+    @digest = digest
+    @private_key = private_key
+    @subject = normalize_subject(subject)
+    @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name]
+    @names = names.to_a.dup
+    normalize_names
+    @subject[SUBJECT_KEYS[:common_name]] ||= @common_name
+    validate_subject
+  end
+
+  def csr
+    @csr ||= generate
+  end
+
+  private
+
+  def generate_private_key
+    OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH)
+  end
+
+  def normalize_subject(subject)
+    @subject = subject.each_with_object({}) do |(key, value), hash|
+      hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s
+    end
+  end
+
+  def normalize_names
+    if @common_name
+      @names.unshift(@common_name) unless @names.include?(@common_name)
+    else
+      raise ArgumentError, 'No common name and no list of names given' if @names.empty?
+      @common_name = @names.first
+    end
+  end
+
+  def validate_subject
+    validate_subject_attributes
+    validate_subject_common_name
+  end
+
+  def validate_subject_attributes
+    extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values
+    return if extra_keys.empty?
+    raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}"
+  end
+
+  def validate_subject_common_name
+    return if @common_name == @subject[SUBJECT_KEYS[:common_name]]
+    raise ArgumentError, 'Conflicting common name given in arguments and subject'
+  end
+
+  def generate
+    OpenSSL::X509::Request.new.tap do |csr|
+      csr.public_key = @private_key.public_key
+      csr.subject = generate_subject
+      csr.version = 2
+      add_extension(csr)
+      csr.sign @private_key, @digest
+    end
+  end
+
+  def generate_subject
+    OpenSSL::X509::Name.new(
+      @subject.map {|name, value|
+        [name, value, SUBJECT_TYPES[name]]
+      }
+    )
+  end
+
+  def add_extension(csr)
+    return if @names.size <= 1
+
+    extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
+      'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
+    )
+    csr.add_attribute(
+      OpenSSL::X509::Attribute.new(
+        'extReq',
+        OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])])
+      )
+    )
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/crypto.rb b/vendor/acme-client/lib/acme/client/crypto.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dfa5cdc935037ad44f23763742e7fe777362e834
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/crypto.rb
@@ -0,0 +1,98 @@
+class Acme::Client::Crypto
+  attr_reader :private_key
+
+  def initialize(private_key)
+    @private_key = private_key
+  end
+
+  def generate_signed_jws(header:, payload:)
+    header = { typ: 'JWT', alg: jws_alg, jwk: jwk }.merge(header)
+
+    encoded_header = urlsafe_base64(header.to_json)
+    encoded_payload = urlsafe_base64(payload.to_json)
+    signature_data = "#{encoded_header}.#{encoded_payload}"
+
+    signature = private_key.sign digest, signature_data
+    encoded_signature = urlsafe_base64(signature)
+
+    {
+      protected: encoded_header,
+      payload: encoded_payload,
+      signature: encoded_signature
+    }.to_json
+  end
+
+  def thumbprint
+    urlsafe_base64 digest.digest(jwk.to_json)
+  end
+
+  def digest
+    OpenSSL::Digest::SHA256.new
+  end
+
+  def urlsafe_base64(data)
+    Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
+  end
+
+  private
+
+  def jws_alg
+    { 'RSA' => 'RS256', 'EC' => 'ES256' }.fetch(jwk[:kty])
+  end
+
+  def jwk
+    @jwk ||= case private_key
+             when OpenSSL::PKey::RSA
+               rsa_jwk
+             when OpenSSL::PKey::EC
+               ec_jwk
+             else
+               raise ArgumentError, "Can't handle #{private_key} as private key, only OpenSSL::PKey::RSA and OpenSSL::PKey::EC"
+    end
+  end
+
+  def rsa_jwk
+    {
+      e: urlsafe_base64(public_key.e.to_s(2)),
+      kty: 'RSA',
+      n: urlsafe_base64(public_key.n.to_s(2))
+    }
+  end
+
+  def ec_jwk
+    {
+      crv: curve_name,
+      kty: 'EC',
+      x: urlsafe_base64(coordinates[:x].to_s(2)),
+      y: urlsafe_base64(coordinates[:y].to_s(2))
+    }
+  end
+
+  def curve_name
+    {
+      'prime256v1' => 'P-256',
+      'secp384r1' => 'P-384',
+      'secp521r1' => 'P-521'
+    }.fetch(private_key.group.curve_name) { raise ArgumentError, 'Unknown EC curve' }
+  end
+
+  # rubocop:disable Metrics/AbcSize
+  def coordinates
+    @coordinates ||= begin
+      hex = public_key.to_bn.to_s(16)
+      data_len = hex.length - 2
+      hex_x = hex[2, data_len / 2]
+      hex_y = hex[2 + data_len / 2, data_len / 2]
+
+      {
+        x: OpenSSL::BN.new([hex_x].pack('H*'), 2),
+        y: OpenSSL::BN.new([hex_y].pack('H*'), 2)
+      }
+    end
+  end
+  # rubocop:enable Metrics/AbcSize
+
+  def public_key
+    @public_key ||= private_key.public_key
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/error.rb b/vendor/acme-client/lib/acme/client/error.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2b356238be33af711108276ee56cb6c980e62433
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/error.rb
@@ -0,0 +1,16 @@
+class Acme::Client::Error < StandardError
+  class NotFound < Acme::Client::Error; end
+  class BadCSR < Acme::Client::Error; end
+  class BadNonce < Acme::Client::Error; end
+  class Connection < Acme::Client::Error; end
+  class Dnssec < Acme::Client::Error; end
+  class Malformed < Acme::Client::Error; end
+  class ServerInternal < Acme::Client::Error; end
+  class Acme::Tls < Acme::Client::Error; end
+  class Unauthorized < Acme::Client::Error; end
+  class UnknownHost < Acme::Client::Error; end
+  class Timeout < Acme::Client::Error; end
+  class RateLimited < Acme::Client::Error; end
+  class RejectedIdentifier < Acme::Client::Error; end
+  class UnsupportedIdentifier < Acme::Client::Error; end
+end
diff --git a/vendor/acme-client/lib/acme/client/faraday_middleware.rb b/vendor/acme-client/lib/acme/client/faraday_middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..21e29c9781ba0016e0cd468e053e145ad1d8cc6d
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/faraday_middleware.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+class Acme::Client::FaradayMiddleware < Faraday::Middleware
+  attr_reader :env, :response, :client
+
+  repo_url = 'https://github.com/unixcharles/acme-client'
+  USER_AGENT = "Acme::Client v#{Acme::Client::VERSION} (#{repo_url})".freeze
+
+  def initialize(app, client:)
+    super(app)
+    @client = client
+  end
+
+  def call(env)
+    @env = env
+    @env[:request_headers]['User-Agent'] = USER_AGENT
+    @env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body)
+    @app.call(env).on_complete { |response_env| on_complete(response_env) }
+  rescue Faraday::TimeoutError
+    raise Acme::Client::Error::Timeout
+  end
+
+  def on_complete(env)
+    @env = env
+
+    raise_on_not_found!
+    store_nonce
+    env.body = decode_body
+    env.response_headers['Link'] = decode_link_headers
+
+    return if env.success?
+
+    raise_on_error!
+  end
+
+  private
+
+  def raise_on_not_found!
+    raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
+  end
+
+  def raise_on_error!
+    raise error_class, error_message
+  end
+
+  def error_message
+    if env.body.is_a? Hash
+      env.body['detail']
+    else
+      "Error message: #{env.body}"
+    end
+  end
+
+  def error_class
+    if error_name && !error_name.empty? && Acme::Client::Error.const_defined?(error_name)
+      Object.const_get("Acme::Client::Error::#{error_name}")
+    else
+      Acme::Client::Error
+    end
+  end
+
+  def error_name
+    @error_name ||= begin
+      return unless env.body.is_a?(Hash)
+      return unless env.body.key?('type')
+
+      env.body['type'].gsub('urn:acme:error:', '').split(/[_-]/).map(&:capitalize).join
+    end
+  end
+
+  def decode_body
+    content_type = env.response_headers['Content-Type']
+
+    if content_type == 'application/json' || content_type == 'application/problem+json'
+      JSON.load(env.body)
+    else
+      env.body
+    end
+  end
+
+  LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/
+
+  def decode_link_headers
+    return unless env.response_headers.key?('Link')
+    link_header = env.response_headers['Link']
+
+    links = link_header.split(', ').map { |entry|
+      _, link, name = *entry.match(LINK_MATCH)
+      [name, link]
+    }
+
+    Hash[*links.flatten]
+  end
+
+  def store_nonce
+    nonces << env.response_headers['replay-nonce']
+  end
+
+  def pop_nonce
+    if nonces.empty?
+      get_nonce
+    else
+      nonces.pop
+    end
+  end
+
+  def get_nonce
+    response = Faraday.head(env.url, nil, 'User-Agent' => USER_AGENT)
+    response.headers['replay-nonce']
+  end
+
+  def nonces
+    client.nonces
+  end
+
+  def private_key
+    client.private_key
+  end
+
+  def crypto
+    @crypto ||= Acme::Client::Crypto.new(private_key)
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/resources.rb b/vendor/acme-client/lib/acme/client/resources.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ad556889e79810223c627abd619d5b1482ea7e6b
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources.rb
@@ -0,0 +1,5 @@
+module Acme::Client::Resources; end
+
+require 'acme/client/resources/registration'
+require 'acme/client/resources/challenges'
+require 'acme/client/resources/authorization'
diff --git a/vendor/acme-client/lib/acme/client/resources/authorization.rb b/vendor/acme-client/lib/acme/client/resources/authorization.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9ca2e76936de4b8638c8203083c19677de38c589
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources/authorization.rb
@@ -0,0 +1,44 @@
+class Acme::Client::Resources::Authorization
+  HTTP01 = Acme::Client::Resources::Challenges::HTTP01
+  DNS01 = Acme::Client::Resources::Challenges::DNS01
+  TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01
+
+  attr_reader :client, :uri, :domain, :status, :expires, :http01, :dns01, :tls_sni01
+
+  def initialize(client, uri, response)
+    @client = client
+    @uri = uri
+    assign_attributes(response.body)
+  end
+
+  def verify_status
+    response = @client.connection.get(@uri)
+
+    assign_attributes(response.body)
+    status
+  end
+
+  private
+
+  def assign_attributes(body)
+    @expires = Time.iso8601(body['expires']) if body.key? 'expires'
+    @domain = body['identifier']['value']
+    @status = body['status']
+    assign_challenges(body['challenges'])
+  end
+
+  def assign_challenges(challenges)
+    challenges.each do |attributes|
+      challenge = case attributes.fetch('type')
+                  when 'http-01'
+                    @http01 ||= HTTP01.new(self)
+                  when 'dns-01'
+                    @dns01 ||= DNS01.new(self)
+                  when 'tls-sni-01'
+                    @tls_sni01 ||= TLSSNI01.new(self)
+      end
+
+      challenge.assign_attributes(attributes) if challenge
+    end
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/resources/challenges.rb b/vendor/acme-client/lib/acme/client/resources/challenges.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec92d471a89c94f4d72690245ab27d1b826529d3
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources/challenges.rb
@@ -0,0 +1,6 @@
+module Acme::Client::Resources::Challenges; end
+
+require 'acme/client/resources/challenges/base'
+require 'acme/client/resources/challenges/http01'
+require 'acme/client/resources/challenges/dns01'
+require 'acme/client/resources/challenges/tls_sni01'
diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/base.rb b/vendor/acme-client/lib/acme/client/resources/challenges/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c78c74ec5e5ac0fd6af429939f47999485b1fa51
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources/challenges/base.rb
@@ -0,0 +1,43 @@
+class Acme::Client::Resources::Challenges::Base
+  attr_reader :authorization, :status, :uri, :token, :error
+
+  def initialize(authorization)
+    @authorization = authorization
+  end
+
+  def client
+    authorization.client
+  end
+
+  def verify_status
+    authorization.verify_status
+
+    status
+  end
+
+  def request_verification
+    response = client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key)
+    response.success?
+  end
+
+  def assign_attributes(attributes)
+    @status = attributes.fetch('status', 'pending')
+    @uri = attributes.fetch('uri')
+    @token = attributes.fetch('token')
+    @error = attributes['error']
+  end
+
+  private
+
+  def challenge_type
+    self.class::CHALLENGE_TYPE
+  end
+
+  def authorization_key
+    "#{token}.#{crypto.thumbprint}"
+  end
+
+  def crypto
+    @crypto ||= Acme::Client::Crypto.new(client.private_key)
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb b/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb
new file mode 100644
index 0000000000000000000000000000000000000000..543f43861b357a792737809ca518e1516d460f8a
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base
+  CHALLENGE_TYPE = 'dns-01'.freeze
+  RECORD_NAME = '_acme-challenge'.freeze
+  RECORD_TYPE = 'TXT'.freeze
+
+  def record_name
+    RECORD_NAME
+  end
+
+  def record_type
+    RECORD_TYPE
+  end
+
+  def record_content
+    crypto.urlsafe_base64(crypto.digest.digest(authorization_key))
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb b/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4966091ad72efeb6772ef6afd523706fe3e17385
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base
+  CHALLENGE_TYPE = 'http-01'.freeze
+  CONTENT_TYPE = 'text/plain'.freeze
+
+  def content_type
+    CONTENT_TYPE
+  end
+
+  def file_content
+    authorization_key
+  end
+
+  def filename
+    ".well-known/acme-challenge/#{token}"
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb b/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8f455f5dbf161583f8a442c6d598ccd16cdc302d
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base
+  CHALLENGE_TYPE = 'tls-sni-01'.freeze
+
+  def hostname
+    digest = crypto.digest.hexdigest(authorization_key)
+    "#{digest[0..31]}.#{digest[32..64]}.acme.invalid"
+  end
+
+  def certificate
+    self_sign_certificate.certificate
+  end
+
+  def private_key
+    self_sign_certificate.private_key
+  end
+
+  private
+
+  def self_sign_certificate
+    @self_sign_certificate ||= Acme::Client::SelfSignCertificate.new(subject_alt_names: [hostname])
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/resources/registration.rb b/vendor/acme-client/lib/acme/client/resources/registration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b7a4c1133c84e7442bb8a44be8a36cd4e9dc71aa
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/resources/registration.rb
@@ -0,0 +1,37 @@
+class Acme::Client::Resources::Registration
+  attr_reader :id, :key, :contact, :uri, :next_uri, :recover_uri, :term_of_service_uri
+
+  def initialize(client, response)
+    @client = client
+    @uri = response.headers['location']
+    assign_links(response.headers['Link'])
+    assign_attributes(response.body)
+  end
+
+  def get_terms
+    return unless @term_of_service_uri
+
+    @client.connection.get(@term_of_service_uri).body
+  end
+
+  def agree_terms
+    return true unless @term_of_service_uri
+
+    response = @client.connection.post(@uri, resource: 'reg', agreement: @term_of_service_uri)
+    response.success?
+  end
+
+  private
+
+  def assign_links(links)
+    @next_uri = links['next']
+    @recover_uri = links['recover']
+    @term_of_service_uri = links['terms-of-service']
+  end
+
+  def assign_attributes(body)
+    @id = body['id']
+    @key = body['key']
+    @contact = body['contact']
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/self_sign_certificate.rb b/vendor/acme-client/lib/acme/client/self_sign_certificate.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2e7d98cba149d4fcbda721d0bc27f88d594b5150
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/self_sign_certificate.rb
@@ -0,0 +1,60 @@
+class Acme::Client::SelfSignCertificate
+  attr_reader :private_key, :subject_alt_names, :not_before, :not_after
+
+  extend Forwardable
+  def_delegators :certificate, :to_pem, :to_der
+
+  def initialize(subject_alt_names:, not_before: default_not_before, not_after: default_not_after, private_key: generate_private_key)
+    @private_key = private_key
+    @subject_alt_names = subject_alt_names
+    @not_before = not_before
+    @not_after = not_after
+  end
+
+  def certificate
+    @certificate ||= begin
+      certificate = generate_certificate
+
+      extension_factory = generate_extension_factory(certificate)
+      subject_alt_name_entry = subject_alt_names.map { |d| "DNS: #{d}" }.join(',')
+      subject_alt_name_extension = extension_factory.create_extension('subjectAltName', subject_alt_name_entry)
+      certificate.add_extension(subject_alt_name_extension)
+
+      certificate.sign(private_key, digest)
+    end
+  end
+
+  private
+
+  def generate_private_key
+    OpenSSL::PKey::RSA.new(2048)
+  end
+
+  def default_not_before
+    Time.now - 3600
+  end
+
+  def default_not_after
+    Time.now + 30 * 24 * 3600
+  end
+
+  def digest
+    OpenSSL::Digest::SHA256.new
+  end
+
+  def generate_certificate
+    certificate = OpenSSL::X509::Certificate.new
+    certificate.not_before = not_before
+    certificate.not_after = not_after
+    certificate.public_key = private_key.public_key
+    certificate.version = 2
+    certificate
+  end
+
+  def generate_extension_factory(certificate)
+    extension_factory = OpenSSL::X509::ExtensionFactory.new
+    extension_factory.subject_certificate = certificate
+    extension_factory.issuer_certificate = certificate
+    extension_factory
+  end
+end
diff --git a/vendor/acme-client/lib/acme/client/version.rb b/vendor/acme-client/lib/acme/client/version.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c989c12522dec58ea7fc6d374a9613d310c1556d
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/version.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Acme
+  class Client
+    VERSION = '0.4.1'.freeze
+  end
+end