├── lib ├── acmev2-client.rb └── acmev2 │ └── client │ ├── version.rb │ ├── resources │ ├── challenges │ │ ├── unsupported_challenge.rb │ │ ├── http01.rb │ │ ├── dns01.rb │ │ └── base.rb │ ├── challenges.rb │ ├── account.rb │ ├── order.rb │ ├── authorization.rb │ └── directory.rb │ ├── certificate_request │ └── ec_key_patch.rb │ ├── resources.rb │ ├── jwk.rb │ ├── chain_identifier.rb │ ├── util.rb │ ├── jwk │ ├── rsa.rb │ ├── base.rb │ └── ecdsa.rb │ ├── self_sign_certificate.rb │ ├── error.rb │ ├── faraday_middleware.rb │ └── certificate_request.rb ├── .rspec ├── bin ├── setup ├── console └── release ├── .gitignore ├── Rakefile ├── .travis.yml ├── spec ├── support │ ├── asn1_helper.rb │ ├── retry_helper.rb │ ├── profile_helper.rb │ ├── http_helper.rb │ ├── tls_helper.rb │ └── ssl_helper.rb ├── chain_identifier_spec.rb ├── util_spec.rb ├── dns01_spec.rb ├── http01_spec.rb ├── directory_spec.rb ├── spec_helper.rb ├── cassettes │ ├── directory_meta.yml │ ├── directory_endpoint_for.yml │ ├── get_nonce.yml │ ├── nonce_fail.yml │ ├── client_meta.yml │ ├── new_account_refuse_terms.yml │ ├── new_account_agree_terms.yml │ ├── registration_agree_terms.yml │ ├── order_status.yml │ ├── new_order.yml │ ├── nonce_retry.yml │ ├── simpler_identifiers_order.yml │ ├── account_contact_deactivate.yml │ ├── order_certificate_download_fail.yml │ ├── account_reload.yml │ ├── account_deactivate.yml │ ├── account_update.yml │ ├── load_account_valid_kid.yml │ ├── fail_fetch_order.yml │ ├── finalize_incomplete_challenge.yml │ ├── authorization_http_challenge.yml │ └── challenge_key_authorization.yml ├── self_sign_certificate_spec.rb ├── account_spec.rb ├── authorization_spec.rb ├── challenge_spec.rb ├── fixtures │ └── certificate_chain.pem ├── jwk_spec.rb ├── order_spec.rb └── certificate_request_spec.rb ├── Gemfile ├── LICENSE.txt ├── acmev2-client.gemspec ├── .rubocop.yml └── CHANGELOG.md /lib/acmev2-client.rb: -------------------------------------------------------------------------------- 1 | require 'acmev2/client' 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --order rand 4 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'acme-client' 5 | 6 | require 'pry' 7 | Pry.start 8 | -------------------------------------------------------------------------------- /lib/acmev2/client/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AcmeV2 4 | class Client 5 | VERSION = '2.0.7'.freeze 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/challenges/unsupported_challenge.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::Client::Resources::Challenges::Unsupported < AcmeV2::Client::Resources::Challenges::Base 2 | end 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /vendor/bundle 11 | /.idea/ 12 | .tool-versions -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new 8 | 9 | task default: [:spec, :rubocop] 10 | -------------------------------------------------------------------------------- /lib/acmev2/client/certificate_request/ec_key_patch.rb: -------------------------------------------------------------------------------- 1 | # Class to handle bug # 2 | class AcmeV2::Client::CertificateRequest::ECKeyPatch < OpenSSL::PKey::EC 3 | alias private? private_key? 4 | alias public? public_key? 5 | end 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - 2.5 5 | - 2.6 6 | - 2.7 7 | 8 | before_install: 9 | - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true 10 | - gem install bundler -v '< 2' 11 | -------------------------------------------------------------------------------- /spec/support/asn1_helper.rb: -------------------------------------------------------------------------------- 1 | module Asn1Helper 2 | def asn1_dig(attribute) 3 | if OpenSSL::VERSION < '2.0.0' 4 | attribute.value.first.first.value 5 | else 6 | attribute.value.value.first.value.first.value 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/retry_helper.rb: -------------------------------------------------------------------------------- 1 | module RetryHelper 2 | def retry_until(condition:, retry_count: 3, wait: 0.25) 3 | count = 0 4 | until condition.call 5 | yield 6 | raise 'timed out' if count > retry_count 7 | count = + 1 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources.rb: -------------------------------------------------------------------------------- 1 | module AcmeV2::Client::Resources; end 2 | 3 | require 'acmev2/client/resources/directory' 4 | require 'acmev2/client/resources/account' 5 | require 'acmev2/client/resources/order' 6 | require 'acmev2/client/resources/authorization' 7 | require 'acmev2/client/resources/challenges' 8 | -------------------------------------------------------------------------------- /spec/support/profile_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ruby-prof' 2 | 3 | RSpec.configure do |c| 4 | c.before(:suite) do 5 | RubyProf.start 6 | end 7 | 8 | c.after(:suite) do 9 | result = RubyProf.stop 10 | printer = RubyProf::FlatPrinter.new(result) 11 | printer.print(STDOUT) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development, :test do 6 | gem 'pry' 7 | gem 'rubocop', '~> 0.49.0' 8 | gem 'ruby-prof', require: false 9 | 10 | if Gem::Version.new(RUBY_VERSION) <= Gem::Version.new('2.2.2') 11 | gem 'activesupport', '~> 4.2.6' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/challenges/http01.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::Resources::Challenges::HTTP01 < AcmeV2::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 | key_authorization 13 | end 14 | 15 | def filename 16 | ".well-known/acme-challenge/#{token}" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/chain_identifier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::ChainIdentifier do 4 | let(:pem) { open('./spec/fixtures/certificate_chain.pem').read } 5 | let(:issuer_name) { 'Pebble Root CA' } 6 | 7 | subject { AcmeV2::Client::ChainIdentifier.new(pem) } 8 | it 'matches certificate by name' do 9 | expect(subject).to be_a_match_name(issuer_name) 10 | end 11 | 12 | it 'fail non matching certificate name' do 13 | expect(subject).not_to be_a_match_name('foo') 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/util_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::JWK do 4 | context '#decode_link_headers' do 5 | let(:example) do 6 | '; rel="up", ; rel="alt", ; rel="alt"' 7 | end 8 | 9 | it 'extract link value and in a hash with rel as they key' do 10 | links = AcmeV2::Client::Util.decode_link_headers(example) 11 | expect(links).to be_a(Hash) 12 | expect(links['up']).to eq(['uri1']) 13 | expect(links['alt'].sort).to eq(%w(uri2 uri3).sort) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/challenges/dns01.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::Resources::Challenges::DNS01 < AcmeV2::Client::Resources::Challenges::Base 4 | CHALLENGE_TYPE = 'dns-01'.freeze 5 | RECORD_NAME = '_acme-challenge'.freeze 6 | RECORD_TYPE = 'TXT'.freeze 7 | DIGEST = OpenSSL::Digest::SHA256 8 | 9 | def record_name 10 | RECORD_NAME 11 | end 12 | 13 | def record_type 14 | RECORD_TYPE 15 | end 16 | 17 | def record_content 18 | AcmeV2::Client::Util.urlsafe_base64(DIGEST.digest(key_authorization)) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'acme-client' 5 | 6 | version = AcmeV2::Client::VERSION 7 | 8 | def `(command) 9 | puts super(command) 10 | 11 | if $?.exitstatus > 0 12 | fail("failed at: #{command}") 13 | end 14 | end 15 | 16 | `git add CHANGELOG.md` 17 | `git add lib/acme/client/version.rb` 18 | `git commit -m "bump to #{version}"` 19 | `git pull --rebase origin master` 20 | `git tag 'v#{version}'` 21 | `git push --tags origin master` 22 | `gem build acme-client.gemspec` 23 | `gem push acme-client-#{version}.gem` 24 | `rm acme-client-#{version}.gem` 25 | -------------------------------------------------------------------------------- /spec/support/http_helper.rb: -------------------------------------------------------------------------------- 1 | require 'webrick' 2 | 3 | module HttpHelper 4 | def serve_once(body) 5 | return yield unless VCR.real_http_connections_allowed? 6 | 7 | dev_null = WEBrick::BasicLog.new(StringIO.new) 8 | server = WEBrick::HTTPServer.new(Port: 5002, Logger: dev_null, AccessLog: dev_null) 9 | 10 | begin 11 | thread = Thread.new do 12 | server.mount_proc('/') do |_, response| 13 | response.body = body 14 | end 15 | server.start 16 | end 17 | 18 | yield 19 | sleep(1) 20 | ensure 21 | server.shutdown 22 | thread.join(5) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/challenges.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AcmeV2::Client::Resources::Challenges 4 | require 'acmev2/client/resources/challenges/base' 5 | require 'acmev2/client/resources/challenges/http01' 6 | require 'acmev2/client/resources/challenges/dns01' 7 | require 'acmev2/client/resources/challenges/unsupported_challenge' 8 | 9 | CHALLENGE_TYPES = { 10 | 'http-01' => AcmeV2::Client::Resources::Challenges::HTTP01, 11 | 'dns-01' => AcmeV2::Client::Resources::Challenges::DNS01 12 | } 13 | 14 | def self.new(client, type:, **arguments) 15 | CHALLENGE_TYPES.fetch(type, Unsupported).new(client, **arguments) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/acmev2/client/jwk.rb: -------------------------------------------------------------------------------- 1 | module AcmeV2::Client::JWK 2 | # Make a JWK from a private key. 3 | # 4 | # private_key - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance. 5 | # 6 | # Returns a JWK::Base subclass instance. 7 | def self.from_private_key(private_key) 8 | case private_key 9 | when OpenSSL::PKey::RSA 10 | AcmeV2::Client::JWK::RSA.new(private_key) 11 | when OpenSSL::PKey::EC 12 | AcmeV2::Client::JWK::ECDSA.new(private_key) 13 | else 14 | raise ArgumentError, 'private_key must be EC or RSA' 15 | end 16 | end 17 | end 18 | 19 | require 'acmev2/client/jwk/base' 20 | require 'acmev2/client/jwk/rsa' 21 | require 'acmev2/client/jwk/ecdsa' 22 | -------------------------------------------------------------------------------- /spec/dns01_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe AcmeV2::Client::Resources::Challenges::DNS01 do 6 | let(:private_key) { generate_private_key } 7 | let(:client) do 8 | AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 9 | end 10 | let(:attributes) do 11 | { status: 'pending', url: 'https://example.com/foo/bar', token: 'example_token' } 12 | end 13 | let(:dns01) do 14 | AcmeV2::Client::Resources::Challenges::DNS01.new(client, **attributes) 15 | end 16 | 17 | it { expect(dns01.record_name).to eq('_acme-challenge') } 18 | it { expect(dns01.record_type).to eq('TXT') } 19 | it { expect(dns01.record_content).to be_a(String) } 20 | end 21 | -------------------------------------------------------------------------------- /spec/support/tls_helper.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | 3 | module TlsHelper 4 | def listen_tls(certificate, private_key) 5 | return yield unless VCR.real_http_connections_allowed? 6 | server = tls_server(certificate, private_key) 7 | 8 | begin 9 | thread = Thread.new do 10 | server.accept 11 | end 12 | 13 | yield 14 | ensure 15 | server.close 16 | thread.join(5) 17 | end 18 | end 19 | 20 | def tls_server(certificate, private_key) 21 | server = TCPServer.new(5001) 22 | ssl_context = OpenSSL::SSL::SSLContext.new 23 | ssl_context.cert = certificate 24 | ssl_context.key = private_key 25 | OpenSSL::SSL::SSLServer.new(server, ssl_context) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/acmev2/client/chain_identifier.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::Client 2 | class ChainIdentifier 3 | def initialize(pem_certificate_chain) 4 | @pem_certificate_chain = pem_certificate_chain 5 | end 6 | 7 | def match_name?(name) 8 | issuers.any? do |issuer| 9 | issuer.include?(name) 10 | end 11 | end 12 | 13 | private 14 | 15 | def issuers 16 | x509_certificates.map(&:issuer).map(&:to_s) 17 | end 18 | 19 | def x509_certificates 20 | @x509_certificates ||= splitted_pem_certificates.map { |pem| OpenSSL::X509::Certificate.new(pem) } 21 | end 22 | 23 | def splitted_pem_certificates 24 | @pem_certificate_chain.each_line.slice_after(/END CERTIFICATE/).map(&:join) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/http01_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe AcmeV2::Client::Resources::Challenges::HTTP01 do 6 | let(:private_key) { generate_private_key } 7 | let(:client) do 8 | AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 9 | end 10 | let(:attributes) do 11 | { status: 'pending', url: 'https://example.com/foo/bar', token: 'example_token' } 12 | end 13 | let(:http01) do 14 | AcmeV2::Client::Resources::Challenges::HTTP01.new(client, **attributes) 15 | end 16 | 17 | context 'file_content' do 18 | it 'match the key_authorization' do 19 | expect(http01.file_content).to eq(http01.key_authorization) 20 | end 21 | end 22 | 23 | context 'filename' do 24 | it 'returns the filename path for the http challenge' do 25 | expect(http01.filename).to eq('.well-known/acme-challenge/example_token') 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/directory_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::Resources::Directory do 4 | let(:directory) { AcmeV2::Client::Resources::Directory.new(DIRECTORY_URL, {}) } 5 | 6 | context 'endpoint_for', vcr: { cassette_name: 'directory_endpoint_for' } do 7 | it { expect(directory.endpoint_for(:new_nonce)).to be_a_kind_of(URI) } 8 | it { expect(directory.endpoint_for(:new_account)).to be_a_kind_of(URI) } 9 | it { expect(directory.endpoint_for(:new_order)).to be_a_kind_of(URI) } 10 | it { expect(directory.endpoint_for(:revoke_certificate)).to be_a_kind_of(URI) } 11 | it { expect(directory.endpoint_for(:key_change)).to be_a_kind_of(URI) } 12 | end 13 | 14 | context 'meta', vcr: { cassette_name: 'directory_meta' } do 15 | it { expect(directory.meta).to be_a(Hash) } 16 | it { expect(directory.terms_of_service).to be_a(String) } 17 | it { expect(directory.external_account_required).to be_nil } 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/acmev2/client/util.rb: -------------------------------------------------------------------------------- 1 | module AcmeV2::Client::Util 2 | def urlsafe_base64(data) 3 | Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '') 4 | end 5 | 6 | LINK_MATCH = /<(.*?)>\s?;\s?rel="([\w-]+)"/ 7 | 8 | # See RFC 8288 - https://tools.ietf.org/html/rfc8288#section-3 9 | def decode_link_headers(link_header) 10 | link_header.split(',').each_with_object({}) { |entry, hash| 11 | _, link, name = *entry.match(LINK_MATCH) 12 | hash[name] ||= [] 13 | hash[name].push(link) 14 | } 15 | end 16 | 17 | # Sets public key on CSR or cert. 18 | # 19 | # obj - An OpenSSL::X509::Certificate or OpenSSL::X509::Request instance. 20 | # priv - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance. 21 | # 22 | # Returns nothing. 23 | def set_public_key(obj, priv) 24 | case priv 25 | when OpenSSL::PKey::EC 26 | obj.public_key = priv 27 | when OpenSSL::PKey::RSA 28 | obj.public_key = priv.public_key 29 | else 30 | raise ArgumentError, 'priv must be EC or RSA' 31 | end 32 | end 33 | 34 | extend self 35 | end 36 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/challenges/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::Resources::Challenges::Base 4 | attr_reader :status, :url, :token, :error 5 | 6 | def initialize(client, **arguments) 7 | @client = client 8 | assign_attributes(**arguments) 9 | end 10 | 11 | def challenge_type 12 | self.class::CHALLENGE_TYPE 13 | end 14 | 15 | def key_authorization 16 | "#{token}.#{@client.jwk.thumbprint}" 17 | end 18 | 19 | def reload 20 | assign_attributes(**@client.challenge(url: url).to_h) 21 | true 22 | end 23 | 24 | def request_validation 25 | assign_attributes(**send_challenge_validation( 26 | url: url 27 | )) 28 | true 29 | end 30 | 31 | def to_h 32 | { status: status, url: url, token: token, error: error } 33 | end 34 | 35 | private 36 | 37 | def send_challenge_validation(url:) 38 | @client.request_challenge_validation( 39 | url: url 40 | ).to_h 41 | end 42 | 43 | def assign_attributes(status:, url:, token:, error: nil) 44 | @status = status 45 | @url = url 46 | @token = token 47 | @error = error 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. 22 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/account.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::Resources::Account 4 | attr_reader :url, :status, :contact, :term_of_service, :orders_url 5 | 6 | def initialize(client, **arguments) 7 | @client = client 8 | assign_attributes(**arguments) 9 | end 10 | 11 | def kid 12 | url 13 | end 14 | 15 | def update(contact: nil, terms_of_service_agreed: nil) 16 | assign_attributes(**@client.account_update( 17 | contact: contact, terms_of_service_agreed: term_of_service 18 | ).to_h) 19 | true 20 | end 21 | 22 | def deactivate 23 | assign_attributes(**@client.account_deactivate.to_h) 24 | true 25 | end 26 | 27 | def reload 28 | assign_attributes(**@client.account.to_h) 29 | true 30 | end 31 | 32 | def to_h 33 | { 34 | url: url, 35 | term_of_service: term_of_service, 36 | status: status, 37 | contact: contact 38 | } 39 | end 40 | 41 | private 42 | 43 | def assign_attributes(url:, term_of_service:, status:, contact:) 44 | @url = url 45 | @term_of_service = term_of_service 46 | @status = status 47 | @contact = Array(contact) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.join(__dir__, '../lib') 2 | $LOAD_PATH.unshift File.join(__dir__, 'support') 3 | 4 | require 'openssl' 5 | 6 | DIRECTORY_URL = ENV['ACME_DIRECTORY_URL'] || 'https://127.0.0.1/directory' 7 | 8 | require 'acmev2/client' 9 | 10 | require 'rspec' 11 | require 'vcr' 12 | require 'webmock/rspec' 13 | 14 | require 'asn1_helper' 15 | require 'http_helper' 16 | require 'retry_helper' 17 | require 'ssl_helper' 18 | require 'tls_helper' 19 | require 'profile_helper' if ENV['RUBY_PROF'] 20 | 21 | RSpec.configure do |c| 22 | c.include Asn1Helper 23 | c.include HttpHelper 24 | c.include TlsHelper 25 | c.include RetryHelper 26 | c.include SSLHelper 27 | end 28 | 29 | VCR.configure do |c| 30 | c.cassette_library_dir = 'spec/cassettes' 31 | c.configure_rspec_metadata! 32 | c.hook_into :webmock 33 | c.ignore_localhost = false 34 | c.default_cassette_options = { record: :once, match_requests_on: [:method, :path, :query] } 35 | c.allow_http_connections_when_no_cassette = false 36 | c.filter_sensitive_data('') do 37 | DIRECTORY_URL 38 | end 39 | c.filter_sensitive_data('') do 40 | DIRECTORY_URL.gsub(%r[\/[^\/]+$], '') 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /acmev2-client.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'acmev2/client/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'acmev2-client' 7 | spec.version = AcmeV2::Client::VERSION 8 | spec.authors = ['Charles Barbier'] 9 | spec.email = ['unixcharles@gmail.com'] 10 | spec.summary = 'Client for the ACME protocol.' 11 | spec.homepage = 'http://github.com/unixcharles/acme-client' 12 | spec.license = 'MIT' 13 | 14 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 15 | spec.require_paths = ['lib'] 16 | 17 | spec.required_ruby_version = '>= 2.1.0' 18 | 19 | spec.add_development_dependency 'bundler', '>= 1.17.3' 20 | if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2') 21 | spec.add_development_dependency 'rake', '>= 12.0' 22 | else 23 | spec.add_development_dependency 'rake', '~> 13.0' 24 | end 25 | spec.add_development_dependency 'rspec', '~> 3.9' 26 | spec.add_development_dependency 'vcr', '~> 2.9' 27 | spec.add_development_dependency 'webmock', '~> 3.8' 28 | 29 | spec.add_runtime_dependency 'faraday', '>= 0.11', '< 2.0.0' 30 | end 31 | -------------------------------------------------------------------------------- /spec/cassettes/directory_meta.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:14 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 44 | recorded_with: VCR 2.9.3 45 | -------------------------------------------------------------------------------- /spec/cassettes/directory_endpoint_for.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:14 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 44 | recorded_with: VCR 2.9.3 45 | -------------------------------------------------------------------------------- /lib/acmev2/client/jwk/rsa.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::Client::JWK::RSA < AcmeV2::Client::JWK::Base 2 | # Digest algorithm to use when signing. 3 | DIGEST = OpenSSL::Digest::SHA256 4 | 5 | # Instantiate a new RSA JWK. 6 | # 7 | # private_key - A OpenSSL::PKey::RSA instance. 8 | # 9 | # Returns nothing. 10 | def initialize(private_key) 11 | unless private_key.is_a?(OpenSSL::PKey::RSA) 12 | raise ArgumentError, 'private_key must be a OpenSSL::PKey::RSA' 13 | end 14 | 15 | @private_key = private_key 16 | end 17 | 18 | # Get this JWK as a Hash for JSON serialization. 19 | # 20 | # Returns a Hash. 21 | def to_h 22 | { 23 | e: AcmeV2::Client::Util.urlsafe_base64(public_key.e.to_s(2)), 24 | kty: 'RSA', 25 | n: AcmeV2::Client::Util.urlsafe_base64(public_key.n.to_s(2)) 26 | } 27 | end 28 | 29 | # Sign a message with the private key. 30 | # 31 | # message - A String message to sign. 32 | # 33 | # Returns a String signature. 34 | def sign(message) 35 | @private_key.sign(DIGEST.new, message) 36 | end 37 | 38 | # The name of the algorithm as needed for the `alg` member of a JWS object. 39 | # 40 | # Returns a String. 41 | def jwa_alg 42 | # https://tools.ietf.org/html/rfc7518#section-3.1 43 | # RSASSA-PKCS1-v1_5 using SHA-256 44 | 'RS256' 45 | end 46 | 47 | private 48 | 49 | def public_key 50 | @private_key.public_key 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/self_sign_certificate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::SelfSignCertificate do 4 | it 'generate a self sign certificate' do 5 | self_sign_certificate = AcmeV2::Client::SelfSignCertificate.new(subject_alt_names: ['test.example.org']) 6 | expect(self_sign_certificate.certificate).to be_a(OpenSSL::X509::Certificate) 7 | expect(self_sign_certificate.private_key).to be_a(OpenSSL::PKey::RSA) 8 | end 9 | 10 | it 'generate a self sign certificate from a provided private key' do 11 | private_key = generate_private_key 12 | self_sign_certificate = AcmeV2::Client::SelfSignCertificate.new(private_key: private_key, subject_alt_names: ['test.example.org']) 13 | expect(self_sign_certificate.certificate).to be_a(OpenSSL::X509::Certificate) 14 | expect(self_sign_certificate.private_key).to be(private_key) 15 | end 16 | 17 | it 'sets the certificates version' do 18 | private_key = generate_private_key 19 | self_sign_certificate = AcmeV2::Client::SelfSignCertificate.new(private_key: private_key, subject_alt_names: ['test.example.org']) 20 | expect(self_sign_certificate.certificate.version).to eql(2) 21 | end 22 | 23 | it 'sets the certificates serial number' do 24 | private_key = generate_private_key 25 | self_sign_certificate = AcmeV2::Client::SelfSignCertificate.new(private_key: private_key, subject_alt_names: ['test.example.org']) 26 | expect(self_sign_certificate.certificate.serial.to_i).to eql(1) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::Resources::Account do 4 | let(:private_key) { generate_private_key } 5 | let(:unregistered_client) do 6 | client = AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 7 | client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true) 8 | client 9 | end 10 | let(:client) do 11 | client = AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 12 | client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true) 13 | client 14 | end 15 | let(:account) do 16 | client.account 17 | end 18 | 19 | context 'status' do 20 | it 'send the agreement for the terms', vcr: { cassette_name: 'registration_agree_terms' } do 21 | expect(account.status).to eq('valid') 22 | end 23 | end 24 | 25 | context 'update' do 26 | it 'update contact information', vcr: { cassette_name: 'account_update' } do 27 | expect(account.contact).to eq(['mailto:info@example.com']) 28 | account.update(contact: 'mailto:updated@example.com') 29 | expect(account.contact).to eq(['mailto:updated@example.com']) 30 | end 31 | end 32 | 33 | context 'deactivate' do 34 | it 'deactivate account', vcr: { cassette_name: 'account_deactivate' } do 35 | expect(account.status).to eq('valid') 36 | account.deactivate 37 | expect(account.status).to eq('deactivated') 38 | end 39 | end 40 | 41 | context 'reload', vcr: { cassette_name: 'account_reload' } do 42 | it 'reload the account' do 43 | expect { account.reload }.not_to raise_error 44 | expect(account.url).not_to be_nil 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/order.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::Resources::Order 4 | attr_reader :url, :status, :contact, :finalize_url, :identifiers, :authorization_urls, :expires, :certificate_url 5 | 6 | def initialize(client, **arguments) 7 | @client = client 8 | assign_attributes(**arguments) 9 | end 10 | 11 | def reload 12 | assign_attributes(**@client.order(url: url).to_h) 13 | true 14 | end 15 | 16 | def authorizations 17 | @authorization_urls.map do |authorization_url| 18 | @client.authorization(url: authorization_url) 19 | end 20 | end 21 | 22 | def finalize(csr:) 23 | assign_attributes(**@client.finalize(url: finalize_url, csr: csr).to_h) 24 | true 25 | end 26 | 27 | def certificate(force_chain: nil) 28 | if certificate_url 29 | @client.certificate(url: certificate_url, force_chain: force_chain) 30 | else 31 | raise AcmeV2::Client::Error::CertificateNotReady, 'No certificate_url to collect the order' 32 | end 33 | end 34 | 35 | def to_h 36 | { 37 | url: url, 38 | status: status, 39 | expires: expires, 40 | finalize_url: finalize_url, 41 | authorization_urls: authorization_urls, 42 | identifiers: identifiers, 43 | certificate_url: certificate_url 44 | } 45 | end 46 | 47 | private 48 | 49 | def assign_attributes(url:, status:, expires:, finalize_url:, authorization_urls:, identifiers:, certificate_url: nil) 50 | @url = url 51 | @status = status 52 | @expires = expires 53 | @finalize_url = finalize_url 54 | @authorization_urls = authorization_urls 55 | @identifiers = identifiers 56 | @certificate_url = certificate_url 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/authorization_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe AcmeV2::Client::Resources::Authorization do 6 | let(:private_key) { generate_private_key } 7 | let(:client) do 8 | client = AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 9 | client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true) 10 | client 11 | end 12 | 13 | let(:order) do 14 | client.new_order(identifiers: [{ type: 'dns', value: 'example.com' }]) 15 | end 16 | let(:authorization) { client.authorization(url: order.authorization_urls.first) } 17 | 18 | context 'deactivate' do 19 | it 'successfully deactive the authorization', vcr: { cassette_name: 'authorization_deactivate' } do 20 | expect(authorization.status).to eq('pending') 21 | expect { authorization.deactivate }.not_to raise_error 22 | expect(authorization.status).to eq('deactivated') 23 | end 24 | end 25 | 26 | context 'challenges' do 27 | it 'returns the challenges', vcr: { cassette_name: 'authorization_challenges' } do 28 | expect(authorization.challenges).to all(be_kind_of(AcmeV2::Client::Resources::Challenges::Base)) 29 | end 30 | 31 | it 'returns the HTTP challenge', vcr: { cassette_name: 'authorization_http_challenge' } do 32 | expect(authorization.http).to be_a(AcmeV2::Client::Resources::Challenges::HTTP01) 33 | end 34 | 35 | it 'returns the DNS challenge', vcr: { cassette_name: 'authorization_dns_challenge' } do 36 | expect(authorization.dns).to be_a(AcmeV2::Client::Resources::Challenges::DNS01) 37 | end 38 | end 39 | 40 | context 'reload' do 41 | it 'successfully reload the authorization', vcr: { cassette_name: 'authorization_reload' } do 42 | expect { authorization.reload }.not_to raise_error 43 | expect(authorization.url).not_to be_nil 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/acmev2/client/self_sign_certificate.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::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 | AcmeV2::Client::Util.set_public_key(certificate, private_key) 50 | certificate.version = 2 51 | certificate.serial = 1 52 | certificate 53 | end 54 | 55 | def generate_extension_factory(certificate) 56 | extension_factory = OpenSSL::X509::ExtensionFactory.new 57 | extension_factory.subject_certificate = certificate 58 | extension_factory.issuer_certificate = certificate 59 | extension_factory 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/authorization.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::Resources::Authorization 4 | attr_reader :url, :identifier, :domain, :expires, :status, :wildcard 5 | 6 | def initialize(client, **arguments) 7 | @client = client 8 | assign_attributes(**arguments) 9 | end 10 | 11 | def deactivate 12 | assign_attributes(**@client.deactivate_authorization(url: url).to_h) 13 | true 14 | end 15 | 16 | def reload 17 | assign_attributes(**@client.authorization(url: url).to_h) 18 | true 19 | end 20 | 21 | def challenges 22 | @challenges.map do |challenge| 23 | initialize_challenge(challenge) 24 | end 25 | end 26 | 27 | def http01 28 | @http01 ||= challenges.find { |challenge| 29 | challenge.is_a?(AcmeV2::Client::Resources::Challenges::HTTP01) 30 | } 31 | end 32 | alias_method :http, :http01 33 | 34 | def dns01 35 | @dns01 ||= challenges.find { |challenge| 36 | challenge.is_a?(AcmeV2::Client::Resources::Challenges::DNS01) 37 | } 38 | end 39 | alias_method :dns, :dns01 40 | 41 | def to_h 42 | { 43 | url: url, 44 | identifier: identifier, 45 | status: status, 46 | expires: expires, 47 | challenges: @challenges, 48 | wildcard: wildcard 49 | } 50 | end 51 | 52 | private 53 | 54 | def initialize_challenge(attributes) 55 | arguments = { 56 | type: attributes.fetch('type'), 57 | status: attributes.fetch('status'), 58 | url: attributes.fetch('url'), 59 | token: attributes.fetch('token'), 60 | error: attributes['error'] 61 | } 62 | AcmeV2::Client::Resources::Challenges.new(@client, **arguments) 63 | end 64 | 65 | def assign_attributes(url:, status:, expires:, challenges:, identifier:, wildcard: false) 66 | @url = url 67 | @identifier = identifier 68 | @domain = identifier.fetch('value') 69 | @status = status 70 | @expires = expires 71 | @challenges = challenges 72 | @wildcard = wildcard 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/cassettes/get_nonce.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:14 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - hpHeIywErqvdFwQwSQbM2w 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:14 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 73 | recorded_with: VCR 2.9.3 74 | -------------------------------------------------------------------------------- /lib/acmev2/client/resources/directory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::Resources::Directory 4 | DIRECTORY_RESOURCES = { 5 | new_nonce: 'newNonce', 6 | new_account: 'newAccount', 7 | new_order: 'newOrder', 8 | new_authz: 'newAuthz', 9 | revoke_certificate: 'revokeCert', 10 | key_change: 'keyChange' 11 | } 12 | 13 | DIRECTORY_META = { 14 | terms_of_service: 'termsOfService', 15 | website: 'website', 16 | caa_identities: 'caaIdentities', 17 | external_account_required: 'externalAccountRequired' 18 | } 19 | 20 | def initialize(url, connection_options) 21 | @url, @connection_options = url, connection_options 22 | end 23 | 24 | def endpoint_for(key) 25 | directory.fetch(key) do |missing_key| 26 | raise AcmeV2::Client::Error::UnsupportedOperation, 27 | "Directory at #{@url} does not include `#{missing_key}`" 28 | end 29 | end 30 | 31 | def terms_of_service 32 | meta[DIRECTORY_META[:terms_of_service]] 33 | end 34 | 35 | def website 36 | meta[DIRECTORY_META[:website]] 37 | end 38 | 39 | def caa_identities 40 | meta[DIRECTORY_META[:caa_identities]] 41 | end 42 | 43 | def external_account_required 44 | meta[DIRECTORY_META[:external_account_required]] 45 | end 46 | 47 | def meta 48 | directory[:meta] 49 | end 50 | 51 | private 52 | 53 | def directory 54 | @directory ||= load_directory 55 | end 56 | 57 | def load_directory 58 | body = fetch_directory 59 | result = {} 60 | result[:meta] = body.delete('meta') 61 | DIRECTORY_RESOURCES.each do |key, entry| 62 | result[key] = URI(body[entry]) if body[entry] 63 | end 64 | result 65 | rescue JSON::ParserError => exception 66 | raise AcmeV2::Client::Error::InvalidDirectory, 67 | "Invalid directory url\n#{@directory} did not return a valid directory\n#{exception.inspect}" 68 | end 69 | 70 | def fetch_directory 71 | connection = Faraday.new(url: @directory, **@connection_options) 72 | connection.headers[:user_agent] = AcmeV2::Client::USER_AGENT 73 | response = connection.get(@url) 74 | JSON.parse(response.body) 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/acmev2/client/jwk/base.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::Client::JWK::Base 2 | THUMBPRINT_DIGEST = OpenSSL::Digest::SHA256 3 | 4 | # Initialize a new JWK. 5 | # 6 | # Returns nothing. 7 | def initialize 8 | raise NotImplementedError 9 | end 10 | 11 | # Generate a JWS JSON web signature. 12 | # 13 | # header - A Hash of extra header fields to include. 14 | # payload - A Hash of payload data. 15 | # 16 | # Returns a JSON String. 17 | def jws(header: {}, payload:) 18 | header = jws_header(header) 19 | encoded_header = AcmeV2::Client::Util.urlsafe_base64(header.to_json) 20 | encoded_payload = AcmeV2::Client::Util.urlsafe_base64(payload.nil? ? '' : payload.to_json) 21 | 22 | signature_data = "#{encoded_header}.#{encoded_payload}" 23 | signature = sign(signature_data) 24 | encoded_signature = AcmeV2::Client::Util.urlsafe_base64(signature) 25 | 26 | { 27 | protected: encoded_header, 28 | payload: encoded_payload, 29 | signature: encoded_signature 30 | }.to_json 31 | end 32 | 33 | # Serialize this JWK as JSON. 34 | # 35 | # Returns a JSON string. 36 | def to_json 37 | to_h.to_json 38 | end 39 | 40 | # Get this JWK as a Hash for JSON serialization. 41 | # 42 | # Returns a Hash. 43 | def to_h 44 | raise NotImplementedError 45 | end 46 | 47 | # JWK thumbprint as used for key authorization. 48 | # 49 | # Returns a String. 50 | def thumbprint 51 | AcmeV2::Client::Util.urlsafe_base64(THUMBPRINT_DIGEST.digest(to_json)) 52 | end 53 | 54 | # Header fields for a JSON web signature. 55 | # 56 | # typ: - Value for the `typ` field. Default 'JWT'. 57 | # 58 | # Returns a Hash. 59 | def jws_header(header) 60 | jws = { 61 | typ: 'JWT', 62 | alg: jwa_alg 63 | }.merge(header) 64 | jws[:jwk] = to_h if header[:kid].nil? 65 | jws 66 | end 67 | 68 | # The name of the algorithm as needed for the `alg` member of a JWS object. 69 | # 70 | # Returns a String. 71 | def jwa_alg 72 | raise NotImplementedError 73 | end 74 | 75 | # Sign a message with the private key. 76 | # 77 | # message - A String message to sign. 78 | # 79 | # Returns a String signature. 80 | # rubocop:disable Lint/UnusedMethodArgument 81 | def sign(message) 82 | raise NotImplementedError 83 | end 84 | # rubocop:enable Lint/UnusedMethodArgument 85 | end 86 | -------------------------------------------------------------------------------- /spec/challenge_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::Resources::Challenges do 4 | let(:private_key) { generate_private_key } 5 | let(:client) do 6 | client = AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 7 | client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true) 8 | client 9 | end 10 | 11 | let(:order) do 12 | client.new_order(identifiers: [{ type: 'dns', value: 'example.com' }]) 13 | end 14 | let(:authorization) { client.authorization(url: order.authorization_urls.first) } 15 | let(:http01) { authorization.http01 } 16 | 17 | context 'reload', vcr: { cassette_name: 'challenge_reload' } do 18 | it 'reload reload the challenge' do 19 | expect { http01.reload }.not_to raise_error 20 | expect(http01.url).not_to be_nil 21 | end 22 | end 23 | 24 | context 'key_authorization', vcr: { cassette_name: 'challenge_key_authorization' } do 25 | it 'returns a key authorization' do 26 | token, jwk_thumbprint = http01.key_authorization.split('.') 27 | expect(token).to eq(http01.token) 28 | expect(jwk_thumbprint).to be_a(String) 29 | end 30 | end 31 | 32 | context 'request_validation' do 33 | it 'successfully verify the challenge', vcr: { cassette_name: 'challenge_verify_success' } do 34 | serve_once(http01.file_content) do 35 | expect { 36 | expect(http01.request_validation).to be(true) 37 | }.to_not raise_error 38 | 39 | expect { 40 | retry_until(condition: lambda { http01.status != 'pending' }) do 41 | http01.reload 42 | end 43 | }.to_not raise_error 44 | 45 | expect(http01.status).to eq('valid') 46 | end 47 | end 48 | 49 | it 'fail to verify the challenge and return the status', vcr: { cassette_name: 'challenge_verify_failure' } do 50 | serve_once("#{http01.file_content}-oops") do 51 | expect { 52 | expect(http01.request_validation).to be(true) 53 | }.to_not raise_error 54 | 55 | expect { 56 | retry_until(condition: lambda { http01.status != 'pending' }) do 57 | http01.reload 58 | end 59 | }.to_not raise_error 60 | 61 | expect(http01.status).to eq('invalid') 62 | expect(http01.error).to_not be_empty 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/fixtures/certificate_chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDWTCCAkGgAwIBAgIIa2EsI7kDF8kwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE 3 | AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA2Nzk0NDAwHhcNMTkxMDEwMDMwODEx 4 | WhcNMjQxMDEwMDMwODExWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJ 5 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBALvgcRqd814+yUb/7ZOHchX0xuiBiH89 6 | YhDiBAEFuW5gOxYXAAVY1b9qAdrHnPEBtvEGqlvMnvDP7hkar7JA69rT6kbt9Psz 7 | 2gARJKrprx3d3Oep8IzS1HFEIqaYddkDoFdSwNpKIy43qmJGpF+ADMwkkmQ0+ICC 8 | Ri3rlidFlW41UT/cIDMbZqgtN51PFW0RAA1OI5NHKCtWCqsI/a7lLAT4u6u0hIK7 9 | zUVr/fPKeT4pkkusVyklEETSW1y+SwLeflOn7SPEBMPTizKr44Et0l4PXva+lfvw 10 | gLbiDg6xqgPwmiq3yt7BzHx/aR/Kc9jwtsImCthleDydDNEoO5WhaqECAwEAAaOB 11 | mDCBlTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF 12 | BwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFDDuGNC3NaAvNCEXcSEBbLaz/NDg 13 | MB8GA1UdIwQYMBaAFIiN1hLypsnm8KWVi3ItK0Unjae0MBYGA1UdEQQPMA2CC2V4 14 | YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBmqbvbeCWhmTJrtt+q5Zlz5C9z 15 | ftpuUIMhAo8iTCOlXASlQ1G/6JmUv1SY/tp93rGIzFPU/wYKZsbw/qzu4eTmS6DZ 16 | q3U1cvb2zjUH7aRAl7etu1H0KVQG4gDw961zM4eUuHSybIgp1FKo+J6X3hHHVM6u 17 | sUAgYvLyyrVYpZxMKP/UCvbA4hZ/sbLJGfI2wzU/FKQjt41W3b+zpEEw8VcFkOfs 18 | eQ5YeUVIHAZ3HFNz0QB/2RTmKLtjZ7UpnmxZUeVMlJXKrYjhAcCBR8vb72BDdaWk 19 | fA/1pSyT7mbXyuxDJCvrwzzdHWw7PL2wzaCbultcuDn4oT15Hih28HjorEtU 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- 22 | MIIDTjCCAjagAwIBAgIIH8F+H5a3JeIwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE 23 | AxMVUGViYmxlIFJvb3QgQ0EgM2E1OTY4MB4XDTE5MTAxMDAzMDU0MloXDTQ5MTAx 24 | MDAzMDU0MlowKDEmMCQGA1UEAxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA2Nzk0 25 | NDAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1TkMbn1tCv0QXFen5 26 | Was596VScGyD0ifqoq4/kb4WQ/debH8coZzz9pVKTPbZySY/X1ltStYBI/2GdIZg 27 | t/uw9hOW7ko2Agy4bcd6tD9S+eZK4F3rH1EJutUjuq9WLmmckT8PJEbToazvDTL2 28 | v9YGcf56v0SCuCYQshNlx5ECKb7kWWqy5D6OzJPB24BRFX7MTD8ckkdhQmzafyI7 29 | vVkDrHFUZslx1BUQ6lfJEy0mi/UmTgxrjPwTfe5UWF6N3AZDEUng8eWAnKW9px2g 30 | 5qTUShGMZG6E6OkFHX6rmT3MVuO+rWVpyW1zKbWxvlcAyyBR91Ju6tsJFF/1Y9t+ 31 | +XHPAgMBAAGjgYMwgYAwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUF 32 | BwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSIjdYS8qbJ 33 | 5vCllYtyLStFJ42ntDAfBgNVHSMEGDAWgBQECikuQVoCg4iOTz+f+0K8SqRdsDAN 34 | BgkqhkiG9w0BAQsFAAOCAQEAeyRyz24bmQqWGYXkM02ZWHlom1FNXVtfNOlzgzjC 35 | 8sUbFYJDaXefS7x1KJwGparNWmn0jopBrSjOJLCDDphqk1DcS2YPu+7CcyodXyGw 36 | CxRSe7gYGx6I0Z/wReeb5rscJ21kJK30Tt3z2hLXWBkxfdcHit3JNLn3HA5HQRph 37 | HUr3EUzoFHfNCBecY8lWkmLeN39I0MMCis49DTact6M1jT+q1c+MzjuWO+Bst6mr 38 | 6RebdTn4monMOHvmbaVR7+klY05dMjatBF5gV7sf8mPJwUZP5NnEcTLe6zgb+dh4 39 | kDc7qJ6UvkuoDJmbHeSd27GdCJfK+nwc49UG/Fmd8+Zeyg== 40 | -----END CERTIFICATE----- 41 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.1 3 | Exclude: 4 | - 'bin/*' 5 | - 'vendor/**/*' 6 | 7 | Rails: 8 | Enabled: false 9 | 10 | Layout/AlignParameters: 11 | EnforcedStyle: with_fixed_indentation 12 | 13 | Layout/ElseAlignment: 14 | Enabled: false 15 | 16 | Layout/FirstParameterIndentation: 17 | EnforcedStyle: consistent 18 | 19 | Layout/IndentationWidth: 20 | Enabled: false 21 | 22 | Layout/MultilineOperationIndentation: 23 | Enabled: false 24 | 25 | Layout/SpaceInsideBlockBraces: 26 | Enabled: false 27 | 28 | Lint/AmbiguousOperator: 29 | Enabled: false 30 | 31 | Lint/AssignmentInCondition: 32 | Enabled: false 33 | 34 | Lint/EndAlignment: 35 | Enabled: false 36 | 37 | Lint/UnusedMethodArgument: 38 | AllowUnusedKeywordArguments: true 39 | 40 | Metrics/AbcSize: 41 | Enabled: false 42 | 43 | Metrics/BlockLength: 44 | Enabled: false 45 | 46 | Metrics/ClassLength: 47 | Enabled: false 48 | 49 | Metrics/CyclomaticComplexity: 50 | Enabled: false 51 | 52 | Metrics/LineLength: 53 | Max: 140 54 | 55 | Metrics/MethodLength: 56 | Max: 15 57 | Enabled: false 58 | 59 | Metrics/ParameterLists: 60 | Max: 5 61 | CountKeywordArgs: false 62 | 63 | Metrics/PerceivedComplexity: 64 | Enabled: false 65 | 66 | Security/JSONLoad: 67 | Enabled: false 68 | 69 | Style/AccessorMethodName: 70 | Enabled: false 71 | 72 | Style/Alias: 73 | Enabled: false 74 | 75 | Style/BlockDelimiters: 76 | EnforcedStyle: semantic 77 | 78 | Style/ClassAndModuleChildren: 79 | Enabled: false 80 | 81 | Style/Documentation: 82 | Enabled: false 83 | 84 | Style/DoubleNegation: 85 | Enabled: false 86 | 87 | Style/FileName: 88 | Exclude: 89 | - 'lib/acme-client.rb' 90 | 91 | Style/GlobalVars: 92 | Enabled: false 93 | 94 | Style/GuardClause: 95 | Enabled: false 96 | 97 | Style/IfUnlessModifier: 98 | Enabled: false 99 | 100 | Style/Lambda: 101 | Enabled: false 102 | 103 | Style/ModuleFunction: 104 | Enabled: false 105 | 106 | Style/MultilineBlockChain: 107 | Enabled: false 108 | 109 | Style/MultipleComparison: 110 | Enabled: false 111 | 112 | Style/MutableConstant: 113 | Enabled: false 114 | 115 | Style/ParallelAssignment: 116 | Enabled: false 117 | 118 | Style/PercentLiteralDelimiters: 119 | Enabled: false 120 | 121 | Style/SignalException: 122 | EnforcedStyle: only_raise 123 | 124 | Style/SymbolArray: 125 | Enabled: false 126 | 127 | Style/StringLiterals: 128 | Enabled: single_quotes 129 | 130 | Style/TrailingCommaInArguments: 131 | Enabled: false 132 | 133 | Style/TrivialAccessors: 134 | AllowPredicates: true 135 | -------------------------------------------------------------------------------- /lib/acmev2/client/error.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::Client::Error < StandardError 2 | class Timeout < AcmeV2::Client::Error; end 3 | 4 | class ClientError < AcmeV2::Client::Error; end 5 | class InvalidDirectory < ClientError; end 6 | class UnsupportedOperation < ClientError; end 7 | class UnsupportedChallengeType < ClientError; end 8 | class NotFound < ClientError; end 9 | class CertificateNotReady < ClientError; end 10 | class ForcedChainNotFound < ClientError; end 11 | 12 | class ServerError < AcmeV2::Client::Error; end 13 | class BadCSR < ServerError; end 14 | class BadNonce < ServerError; end 15 | class BadSignatureAlgorithm < ServerError; end 16 | class InvalidContact < ServerError; end 17 | class UnsupportedContact < ServerError; end 18 | class ExternalAccountRequired < ServerError; end 19 | class AccountDoesNotExist < ServerError; end 20 | class Malformed < ServerError; end 21 | class RateLimited < ServerError; end 22 | class RejectedIdentifier < ServerError; end 23 | class ServerInternal < ServerError; end 24 | class Unauthorized < ServerError; end 25 | class UnsupportedIdentifier < ServerError; end 26 | class UserActionRequired < ServerError; end 27 | class BadRevocationReason < ServerError; end 28 | class Caa < ServerError; end 29 | class Dns < ServerError; end 30 | class Connection < ServerError; end 31 | class Tls < ServerError; end 32 | class IncorrectResponse < ServerError; end 33 | 34 | ACME_ERRORS = { 35 | 'urn:ietf:params:acme:error:badCSR' => BadCSR, 36 | 'urn:ietf:params:acme:error:badNonce' => BadNonce, 37 | 'urn:ietf:params:acme:error:badSignatureAlgorithm' => BadSignatureAlgorithm, 38 | 'urn:ietf:params:acme:error:invalidContact' => InvalidContact, 39 | 'urn:ietf:params:acme:error:unsupportedContact' => UnsupportedContact, 40 | 'urn:ietf:params:acme:error:externalAccountRequired' => ExternalAccountRequired, 41 | 'urn:ietf:params:acme:error:accountDoesNotExist' => AccountDoesNotExist, 42 | 'urn:ietf:params:acme:error:malformed' => Malformed, 43 | 'urn:ietf:params:acme:error:rateLimited' => RateLimited, 44 | 'urn:ietf:params:acme:error:rejectedIdentifier' => RejectedIdentifier, 45 | 'urn:ietf:params:acme:error:serverInternal' => ServerInternal, 46 | 'urn:ietf:params:acme:error:unauthorized' => Unauthorized, 47 | 'urn:ietf:params:acme:error:unsupportedIdentifier' => UnsupportedIdentifier, 48 | 'urn:ietf:params:acme:error:userActionRequired' => UserActionRequired, 49 | 'urn:ietf:params:acme:error:badRevocationReason' => BadRevocationReason, 50 | 'urn:ietf:params:acme:error:caa' => Caa, 51 | 'urn:ietf:params:acme:error:dns' => Dns, 52 | 'urn:ietf:params:acme:error:connection' => Connection, 53 | 'urn:ietf:params:acme:error:tls' => Tls, 54 | 'urn:ietf:params:acme:error:incorrectResponse' => IncorrectResponse 55 | } 56 | end 57 | -------------------------------------------------------------------------------- /lib/acmev2/client/jwk/ecdsa.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::Client::JWK::ECDSA < AcmeV2::Client::JWK::Base 2 | # JWA parameters for supported OpenSSL curves. 3 | # https://tools.ietf.org/html/rfc7518#section-3.1 4 | KNOWN_CURVES = { 5 | 'prime256v1' => { 6 | jwa_crv: 'P-256', 7 | jwa_alg: 'ES256', 8 | digest: OpenSSL::Digest::SHA256 9 | }.freeze, 10 | 'secp384r1' => { 11 | jwa_crv: 'P-384', 12 | jwa_alg: 'ES384', 13 | digest: OpenSSL::Digest::SHA384 14 | }.freeze, 15 | 'secp521r1' => { 16 | jwa_crv: 'P-521', 17 | jwa_alg: 'ES512', 18 | digest: OpenSSL::Digest::SHA512 19 | }.freeze 20 | }.freeze 21 | 22 | # Instantiate a new ECDSA JWK. 23 | # 24 | # private_key - A OpenSSL::PKey::EC instance. 25 | # 26 | # Returns nothing. 27 | def initialize(private_key) 28 | unless private_key.is_a?(OpenSSL::PKey::EC) 29 | raise ArgumentError, 'private_key must be a OpenSSL::PKey::EC' 30 | end 31 | 32 | unless @curve_params = KNOWN_CURVES[private_key.group.curve_name] 33 | raise ArgumentError, 'Unknown EC curve' 34 | end 35 | 36 | @private_key = private_key 37 | end 38 | 39 | # The name of the algorithm as needed for the `alg` member of a JWS object. 40 | # 41 | # Returns a String. 42 | def jwa_alg 43 | @curve_params[:jwa_alg] 44 | end 45 | 46 | # Get this JWK as a Hash for JSON serialization. 47 | # 48 | # Returns a Hash. 49 | def to_h 50 | { 51 | crv: @curve_params[:jwa_crv], 52 | kty: 'EC', 53 | x: AcmeV2::Client::Util.urlsafe_base64(coordinates[:x].to_s(2)), 54 | y: AcmeV2::Client::Util.urlsafe_base64(coordinates[:y].to_s(2)) 55 | } 56 | end 57 | 58 | # Sign a message with the private key. 59 | # 60 | # message - A String message to sign. 61 | # 62 | # Returns a String signature. 63 | def sign(message) 64 | # DER encoded ASN.1 signature 65 | der = @private_key.sign(@curve_params[:digest].new, message) 66 | 67 | # ASN.1 SEQUENCE 68 | seq = OpenSSL::ASN1.decode(der) 69 | 70 | # ASN.1 INTs 71 | ints = seq.value 72 | 73 | # BigNumbers 74 | bns = ints.map(&:value) 75 | 76 | # Binary R/S values 77 | r, s = bns.map { |bn| [bn.to_s(16)].pack('H*') } 78 | 79 | # JWS wants raw R/S concatenated. 80 | [r, s].join 81 | end 82 | 83 | private 84 | 85 | def coordinates 86 | @coordinates ||= begin 87 | hex = public_key.to_bn.to_s(16) 88 | data_len = hex.length - 2 89 | hex_x = hex[2, data_len / 2] 90 | hex_y = hex[2 + data_len / 2, data_len / 2] 91 | 92 | { 93 | x: OpenSSL::BN.new([hex_x].pack('H*'), 2), 94 | y: OpenSSL::BN.new([hex_y].pack('H*'), 2) 95 | } 96 | end 97 | end 98 | 99 | def public_key 100 | @private_key.public_key 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/acmev2/client/faraday_middleware.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AcmeV2::Client::FaradayMiddleware < Faraday::Middleware 4 | attr_reader :env, :response, :client 5 | 6 | CONTENT_TYPE = 'application/jose+json' 7 | 8 | def initialize(app, client:, mode:) 9 | super(app) 10 | @client = client 11 | @mode = mode 12 | end 13 | 14 | def call(env) 15 | @env = env 16 | @env[:request_headers]['User-Agent'] = AcmeV2::Client::USER_AGENT 17 | @env[:request_headers]['Content-Type'] = CONTENT_TYPE 18 | 19 | if @env.method != :get 20 | @env.body = client.jwk.jws(header: jws_header, payload: env.body) 21 | end 22 | 23 | @app.call(env).on_complete { |response_env| on_complete(response_env) } 24 | rescue Faraday::TimeoutError, Faraday::ConnectionFailed 25 | raise AcmeV2::Client::Error::Timeout 26 | end 27 | 28 | def on_complete(env) 29 | @env = env 30 | 31 | raise_on_not_found! 32 | store_nonce 33 | env.body = decode_body 34 | env.response_headers['Link'] = decode_link_headers 35 | 36 | return if env.success? 37 | 38 | raise_on_error! 39 | end 40 | 41 | private 42 | 43 | def jws_header 44 | headers = { nonce: pop_nonce, url: env.url.to_s } 45 | headers[:kid] = client.kid if @mode == :kid 46 | headers 47 | end 48 | 49 | def raise_on_not_found! 50 | raise AcmeV2::Client::Error::NotFound, env.url.to_s if env.status == 404 51 | end 52 | 53 | def raise_on_error! 54 | raise error_class, error_message 55 | end 56 | 57 | def error_message 58 | if env.body.is_a? Hash 59 | env.body['detail'] 60 | else 61 | "Error message: #{env.body}" 62 | end 63 | end 64 | 65 | def error_class 66 | AcmeV2::Client::Error::ACME_ERRORS.fetch(error_name, AcmeV2::Client::Error) 67 | end 68 | 69 | def error_name 70 | return unless env.body.is_a?(Hash) 71 | return unless env.body.key?('type') 72 | env.body['type'] 73 | end 74 | 75 | def decode_body 76 | content_type = env.response_headers['Content-Type'].to_s 77 | 78 | if content_type.start_with?('application/json', 'application/problem+json') 79 | JSON.load(env.body) 80 | else 81 | env.body 82 | end 83 | end 84 | 85 | def decode_link_headers 86 | return unless env.response_headers.key?('Link') 87 | link_header = env.response_headers['Link'] 88 | AcmeV2::Client::Util.decode_link_headers(link_header) 89 | end 90 | 91 | def store_nonce 92 | nonce = env.response_headers['replay-nonce'] 93 | nonces << nonce if nonce 94 | end 95 | 96 | def pop_nonce 97 | if nonces.empty? 98 | get_nonce 99 | end 100 | 101 | nonces.pop 102 | end 103 | 104 | def get_nonce 105 | client.get_nonce 106 | end 107 | 108 | def nonces 109 | client.nonces 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/support/ssl_helper.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module SSLHelper 4 | class KeyStash 5 | KEYSTASH_PATH = File.join(__dir__, '../fixtures/keystash.yml') 6 | 7 | def initialize 8 | @keystash = load 9 | @iter = @keystash.each 10 | end 11 | 12 | def next 13 | @iter.next 14 | rescue StopIteration 15 | @keystash << generate_key 16 | save 17 | @keystash.last 18 | end 19 | 20 | def generate_key 21 | case (rand * 4).to_i 22 | when 0 23 | OpenSSL::PKey::RSA.new(2048) 24 | when 1 25 | generate_ecdsa_key('prime256v1') 26 | when 2 27 | generate_ecdsa_key('secp384r1') 28 | when 3 29 | OpenSSL::PKey::RSA.new(2048) 30 | # TODO: ECDSA curve P-521 not allowed at the moment. 31 | # generate_ecdsa_key('secp521r1') 32 | end 33 | end 34 | 35 | def generate_ecdsa_key(curve) 36 | k = OpenSSL::PKey::EC.new(curve) 37 | k.generate_key 38 | AcmeV2::Client::CertificateRequest::ECKeyPatch.new(k) 39 | end 40 | 41 | private 42 | 43 | def load 44 | if File.exist?(KEYSTASH_PATH) 45 | YAML.load_file(KEYSTASH_PATH).shuffle.map do |pem| 46 | begin 47 | OpenSSL::PKey::RSA.new(pem) 48 | rescue StandardError 49 | AcmeV2::Client::CertificateRequest::ECKeyPatch.new(pem) 50 | end 51 | end 52 | else 53 | [] 54 | end 55 | end 56 | 57 | def save 58 | File.write(KEYSTASH_PATH, YAML.dump(@keystash.map(&:to_pem))) 59 | end 60 | end 61 | 62 | KEYSTASH = KeyStash.new 63 | 64 | def generate_private_key 65 | KEYSTASH.next 66 | end 67 | 68 | def generate_csr(common_name, private_key) 69 | request = OpenSSL::X509::Request.new 70 | request.subject = OpenSSL::X509::Name.new( 71 | [ 72 | [ 73 | 'CN', 74 | common_name, 75 | OpenSSL::ASN1::UTF8STRING 76 | ] 77 | ] 78 | ) 79 | 80 | AcmeV2::Client::Util.set_public_key(request, private_key) 81 | request.sign(private_key, OpenSSL::Digest::SHA256.new) 82 | request 83 | end 84 | 85 | # Verify a CSR's signature. 86 | # 87 | # csr - A OpenSSL::X509::Request instance. 88 | # priv - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance. 89 | # 90 | # Returns boolean. 91 | def verify_csr(csr, priv) 92 | case priv 93 | when OpenSSL::PKey::EC 94 | csr.verify(priv) 95 | when OpenSSL::PKey::RSA 96 | csr.verify(priv.public_key) 97 | else 98 | raise ArgumentError, 'priv must be EC or RSA' 99 | end 100 | end 101 | 102 | # Export a private key's public key in DER format. 103 | # 104 | # priv - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance. 105 | # 106 | # Returns a String. 107 | def public_key_to_pem(private_key) 108 | case private_key 109 | when OpenSSL::PKey::EC 110 | dup = OpenSSL::PKey::EC.new(private_key.to_der) 111 | dup.private_key = nil 112 | dup.to_pem 113 | when OpenSSL::PKey::RSA 114 | private_key.public_key.to_pem 115 | else 116 | raise ArgumentError, 'private_key must be EC or RSA' 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /spec/jwk_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::JWK do 4 | let(:private_key) { generate_key(key_class) } 5 | 6 | subject { described_class.new(private_key) } 7 | 8 | describe described_class::RSA do 9 | let(:key_class) { OpenSSL::PKey::RSA } 10 | 11 | describe '#to_json' do 12 | it 'returns a String' do 13 | expect(subject.to_json).to be_a(String) 14 | end 15 | end 16 | 17 | describe '#to_h' do 18 | it 'returns a Hash' do 19 | expect(subject.to_h).to be_a(Hash) 20 | end 21 | end 22 | 23 | describe '#sign' do 24 | let(:message) { 'hello, world' } 25 | 26 | it 'returns a String' do 27 | expect(subject.sign(message)).to be_a(String) 28 | end 29 | end 30 | 31 | describe '#jwa_alg' do 32 | it 'returns a String' do 33 | expect(subject.jwa_alg).to be_a(String) 34 | end 35 | end 36 | 37 | describe '#jwt' do 38 | it 'generates a valid JWT' do 39 | jws_s = subject.jws(header: { 'a-header' => 'header-value' }, payload: { 'some' => 'data' }) 40 | jws = JSON.parse(jws_s) 41 | header = JSON.parse(Base64.decode64(jws['protected'])) 42 | payload = JSON.parse(Base64.decode64(jws['payload'])) 43 | 44 | expect(header).to include('a-header' => 'header-value') 45 | expect(header['typ']).to eq('JWT') 46 | expect(header['alg']).to eq('RS256') 47 | expect(header['jwk']['kty']).to eq('RSA') 48 | expect(payload).to include('some' => 'data') 49 | end 50 | end 51 | end 52 | 53 | describe described_class::ECDSA do 54 | let(:key_class) { OpenSSL::PKey::EC } 55 | 56 | describe '#to_json' do 57 | it 'returns a String' do 58 | expect(subject.to_json).to be_a(String) 59 | end 60 | end 61 | 62 | describe '#to_h' do 63 | it 'returns a Hash' do 64 | expect(subject.to_h).to be_a(Hash) 65 | end 66 | end 67 | 68 | describe '#sign' do 69 | let(:message) { 'hello, world' } 70 | 71 | it 'returns a String' do 72 | expect(subject.sign(message)).to be_a(String) 73 | end 74 | 75 | it 'is not ASN.1 encoded' do 76 | expect { 77 | OpenSSL::ASN1.decode(subject.sign(message)) 78 | }.to raise_error(OpenSSL::ASN1::ASN1Error) 79 | end 80 | end 81 | 82 | describe '#jwa_alg' do 83 | it 'returns a String' do 84 | expect(subject.jwa_alg).to be_a(String) 85 | end 86 | end 87 | 88 | describe '#jwt' do 89 | it 'generates a valid JWT' do 90 | jws_s = subject.jws(header: { 'a-header' => 'header-value' }, payload: { 'some' => 'data' }) 91 | jws = JSON.parse(jws_s) 92 | header = JSON.parse(Base64.decode64(jws['protected'])) 93 | payload = JSON.parse(Base64.decode64(jws['payload'])) 94 | 95 | expect(header).to include('a-header' => 'header-value') 96 | expect(header['typ']).to eq('JWT') 97 | expect(%w(ES256 ES384 ES512)).to include(header['alg']) 98 | expect(header['jwk']['kty']).to eq('EC') 99 | expect(payload).to include('some' => 'data') 100 | end 101 | end 102 | end 103 | 104 | def generate_key(klass) 105 | while k = generate_private_key 106 | return k if k.is_a?(klass) 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /spec/order_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::Resources::Order do 4 | let(:private_key) { generate_private_key } 5 | let(:unregistered_client) do 6 | client = AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 7 | client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true) 8 | client 9 | end 10 | 11 | let(:client) do 12 | client = AcmeV2::Client.new(private_key: private_key, directory: DIRECTORY_URL) 13 | client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true) 14 | client 15 | end 16 | 17 | let(:order) do 18 | client.new_order(identifiers: [{ type: 'dns', value: 'example.com' }]) 19 | end 20 | 21 | context 'status' do 22 | it 'send the agreement for the terms', vcr: { cassette_name: 'order_status' } do 23 | expect(order.status).to eq('pending') 24 | end 25 | end 26 | 27 | context 'finalize' do 28 | let(:authorization) { order.authorizations.first } 29 | let(:challenge) { authorization.http01 } 30 | 31 | it 'call client finalize failure', vcr: { cassette_name: 'order_finalize_fail' } do 32 | csr = AcmeV2::Client::CertificateRequest.new(names: %w[example.com]) 33 | expect { order.finalize(csr: csr) }.to raise_error(AcmeV2::Client::Error::Unauthorized) 34 | end 35 | 36 | it 'call client finalize sucess', vcr: { cassette_name: 'order_finalize_sucess' } do 37 | serve_once(challenge.file_content) do 38 | challenge.request_validation 39 | end 40 | 41 | csr = AcmeV2::Client::CertificateRequest.new(names: %w[example.com]) 42 | expect { order.finalize(csr: csr) }.not_to raise_error 43 | end 44 | end 45 | 46 | context 'certificate' do 47 | let(:authorization) { order.authorizations.first } 48 | let(:challenge) { authorization.http01 } 49 | let(:finalized_order) do 50 | serve_once(challenge.file_content) do 51 | challenge.request_validation 52 | end 53 | 54 | csr = AcmeV2::Client::CertificateRequest.new(names: %w[example.com]) 55 | order.finalize(csr: csr) 56 | order.reload 57 | order 58 | end 59 | 60 | it 'call client certificate sucess', vcr: { cassette_name: 'order_certificate_download_sucess' } do 61 | certificate = finalized_order.certificate 62 | 63 | expect { OpenSSL::X509::Certificate.new(certificate) }.not_to raise_error 64 | end 65 | 66 | it 'call client certificate fail', vcr: { cassette_name: 'order_certificate_download_fail' } do 67 | expect { order.certificate }.to raise_error(AcmeV2::Client::Error::CertificateNotReady) 68 | end 69 | 70 | it 'call client certificate with a force_chain', vcr: { cassette_name: 'order_certificate_download_preferred_chain' } do 71 | force_chain_name = 'Pebble Root CA 769220' 72 | 73 | expect { finalized_order.certificate(force_chain: force_chain_name) }.not_to raise_error 74 | end 75 | 76 | it 'call client certificate with an unmatched force_chain', vcr: { cassette_name: 'order_certificate_download_preferred_chain' } do 77 | force_chain_name = 'Fail Test CA' 78 | 79 | expect { 80 | finalized_order.certificate(force_chain: force_chain_name) 81 | }.to raise_error(AcmeV2::Client::Error::ForcedChainNotFound) 82 | end 83 | end 84 | 85 | context 'reload' do 86 | it 'reload a update attributes', vcr: { cassette_name: 'order_reload' } do 87 | expect { order.reload }.not_to raise_error 88 | expect(order.url).not_to be_nil 89 | end 90 | end 91 | 92 | context 'authorizations' do 93 | it 'load authorizations', vcr: { cassette_name: 'order_authorizations' } do 94 | authorizations = order.authorizations 95 | expect(authorizations).to all(be_a(AcmeV2::Client::Resources::Authorization)) 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/cassettes/nonce_fail.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:15 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - Nr5JbswLJWa8FwbDW5spdg 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:15 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm5vbmNlIjoiTnI1SmJzd0xKV2E4RndiRFc1c3BkZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiS0dhMzVHSFBuN1owWkgycjAyUE9ZcVBzVmVoLTlEaW5GSUZVZlNKeUFMRSIsInkiOiJYa2V1MmRXTGo0SmhJakhXMThWSnFIbnBuMjZiWE52SEJYT3FTOG5oTHhNIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl19","signature":"Bpn4xFJAMVIplSaZw6b42B1WD1tieAGNeAqFA1kl9npDkPfUG1vqO86xelHQZsRxrA0T-yWYGRXz2bu3w1gYiw"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 403 91 | message: Forbidden 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/problem+json; charset=utf-8 97 | Link: 98 | - ;rel="terms-of-service" 99 | - <>;rel="index" 100 | Replay-Nonce: 101 | - nA9fzQ2E9Chs1lpyLoX-2Q 102 | Date: 103 | - Thu, 10 Oct 2019 03:08:15 GMT 104 | Content-Length: 105 | - '150' 106 | body: 107 | encoding: UTF-8 108 | string: |- 109 | { 110 | "type": "urn:ietf:params:acme:error:badNonce", 111 | "detail": "JWS has an invalid anti-replay nonce: 063ziGbuwwDIznTSUpOaSQ", 112 | "status": 400 113 | } 114 | http_version: 115 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 116 | recorded_with: VCR 2.9.3 117 | -------------------------------------------------------------------------------- /lib/acmev2/client/certificate_request.rb: -------------------------------------------------------------------------------- 1 | class AcmeV2::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 | if @private_key.is_a?(OpenSSL::PKey::EC) && RbConfig::CONFIG['MAJOR'] == '2' && 83 | RbConfig::CONFIG['MINOR'].to_i < 4 84 | # OpenSSL::PKey::EC does not respect classic PKey interface (as defined by 85 | # PKey::RSA and PKey::DSA) until ruby 2.4. 86 | # Supporting this interface needs monkey patching of OpenSSL:PKey::EC, or 87 | # subclassing it. Here, use a subclass. 88 | @private_key = ECKeyPatch.new(@private_key) 89 | end 90 | csr.public_key = @private_key 91 | csr.subject = generate_subject 92 | csr.version = 2 93 | add_extension(csr) 94 | csr.sign @private_key, @digest 95 | end 96 | end 97 | 98 | def generate_subject 99 | OpenSSL::X509::Name.new( 100 | @subject.map {|name, value| 101 | [name, value, SUBJECT_TYPES[name]] 102 | } 103 | ) 104 | end 105 | 106 | def add_extension(csr) 107 | extension = OpenSSL::X509::ExtensionFactory.new.create_extension( 108 | 'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false 109 | ) 110 | csr.add_attribute( 111 | OpenSSL::X509::Attribute.new( 112 | 'extReq', 113 | OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])]) 114 | ) 115 | ) 116 | end 117 | end 118 | 119 | require 'acmev2/client/certificate_request/ec_key_patch' 120 | -------------------------------------------------------------------------------- /spec/cassettes/client_meta.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:14 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - BjoPGx1wSsVSQ-fM-SVjNw 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:14 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiQmpvUEd4MXdTc1ZTUS1mTS1TVmpOdyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiQXlXcUttek9sSGFxR2pYZ2hzcWNNQ1Y3U0ZqYU5DeXpacWhxcFgyRlFTMEg2MXBGZlltRGtuZndVTDF3b1JURCIsInkiOiJ2VnAwblhyTjZMdGkteVFma3hodGJ6WmhrTThtbWM1LUhlVGY4TE9mU2c0SV9QbEtTODF1cWdrdW9WdzY4dHJWIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"aLQuJR9XxuEQYCi3aQtMRTRzXAPBz4pG_zSE8tb1PE9d4wUbmUXB6aK7aH1JWMoDfARuPr3Z7DvUi6BDHtnB4eBYu_IT_etc73318an3WNUICPzkBuGVVZm3oo9Ru2v3"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/55" 101 | Replay-Nonce: 102 | - ONNsf6redbHHNON11yBGdA 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:14 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/55", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "AyWqKmzOlHaqGjXghsqcMCV7SFjaNCyzZqhqpX2FQS0H61pFfYmDknfwUL1woRTD", 120 | "y": "vVp0nXrN6Lti-yQfkxhtbzZhkM8mmc5-HeTf8LOfSg4I_PlKS81uqgkuoVw68trV" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 125 | recorded_with: VCR 2.9.3 126 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## `2.0.7` 2 | 3 | * Add support for alternate certificate chain 4 | * Change `Link` headers parsing to return array of value. This add support multiple entries at the same `rel` 5 | 6 | ## `2.0.6` 7 | 8 | * Allow Faraday up to `< 2.0` 9 | 10 | ## `2.0.5` 11 | 12 | * Use post-as-get 13 | * Remove deprecated keyAuthorization 14 | 15 | ## `2.0.4` 16 | 17 | * Add an option to retry bad nonce errors 18 | 19 | ## `2.0.3` 20 | 21 | * Do not try to set the body on GET request 22 | 23 | ## `2.0.2` 24 | 25 | * Fix constant lookup on InvalidDirectory 26 | * Forward connection options when fetching nonce 27 | * Fix splats without parenthesis warning 28 | 29 | ## `2.0.1` 30 | 31 | * Properly require URI 32 | 33 | ## `2.0.0` 34 | 35 | * Release of the `ACMEv2` branch 36 | 37 | ## `1.0.0` 38 | 39 | * Development for `ACMEv1` moved into `1.0.x` 40 | 41 | ## `0.6.3` 42 | 43 | * Handle Faraday::ConnectionFailed errors as Timeout error. 44 | 45 | ## `0.6.2` 46 | 47 | * Do not cache error type 48 | 49 | ## `0.6.1` 50 | 51 | * Fix typo in ECDSA curves 52 | 53 | ## `0.6.0` 54 | 55 | * Support external account keys 56 | 57 | ## `0.5.5` 58 | 59 | * Release script fixes. 60 | 61 | ## `0.5.4` 62 | 63 | * Enable ECDSA certificates 64 | 65 | ## `0.5.3` 66 | 67 | * Build release script 68 | 69 | ## `0.5.2` 70 | 71 | * Fix acme error names 72 | * ASN1 parsing improvements 73 | 74 | ## `0.5.1` 75 | 76 | * Set serial number of self-signed certificate 77 | 78 | ## `0.5.0` 79 | 80 | * Allow access to `AcmeV2::Client#endpoint` and `AcmeV2::Client#directory_uri` 81 | * Add `AcmeV2::Client#fetch_authorization` 82 | * Setup cyclic dependency between challenges and their authorization for easier access of either with the other. 83 | * Drop `AcmeV2::Client#challenge_from_hash` and `AcmeV2::Client::Resources::Challenges::Base#to_h` in favor of the new API. 84 | * Delegate `AcmeV2::Client::Resources::Challenges::Base#verify_status` to `AcmeV2::Client::Resources::Authorization#verify_status` and make it update existing challenge objects. This makes it so that whichever is called, the correct status is reflected everywhere. 85 | * Add `Authorization#verify_status` - Recent versions of boulder will no longer process a challenge if the associated authorization is already valid, that is another challenge was previously solved. This means we need to allow people to poll on the authorizations status rather than the challenge status so they don't have to poll on the status of all challenges of an authorization all the time. See https://community.letsencrypt.org/t/upcoming-change-valid-authz-reuse/16982 and https://github.com/letsencrypt/boulder/issues/2057 86 | 87 | ## `0.4.1` 88 | 89 | * Set the X509 version of the self-signed certificate 90 | * Fix requiring of time standard library 91 | 92 | ## `0.4.0` 93 | 94 | * Drop json-jwt dependency, implement JWS on our own 95 | * Drop ActiveSupport dependency 96 | 97 | ## `0.3.7` 98 | 99 | * Simplify internal `require` statements 100 | * Fix usage of json-jwt return value 101 | * Remove usage of deprecated `qualified_const_defined?` 102 | * Add user agent to upstream calls 103 | * Fix gem requiring 104 | * Set CSR version 105 | 106 | ## `0.3.6` 107 | 108 | * Handle non-json errors better 109 | 110 | ## `0.3.5` 111 | 112 | * Handle non protocol related server error 113 | 114 | ## `0.3.4` 115 | 116 | * Make `AcmeV2::Client#challenge_from_hash` more strict with the arguments it receives 117 | 118 | ## `0.3.3` 119 | 120 | * Add new `unsupportedIdentifier` error from acme protocol 121 | 122 | ## `0.3.2` 123 | 124 | * Adds `rejectedIdentifier` error 125 | * Adds `RateLimited` error class 126 | * Clean up gem loading 127 | * Make client connection options configurable 128 | * Add URL to certificate 129 | 130 | ## `0.3.1` 131 | 132 | * Add ability to serialize challenges 133 | 134 | ## `0.3.0` 135 | 136 | * Use ISO8601 format for time parsing 137 | * Expose the authorization expiration timestamp. The ACME server returns an optional timestamp that signifies the expiration date of the domain authorization challenge. The time format is RFC3339 and can be parsed by Time#parse. See: https://letsencrypt.github.io/acme-spec/ Section 5.3 - expires 138 | * Update dns-01 record content to comply with ACME spec 139 | * Fix `SelfSignCertificate#default_not_before` 140 | 141 | ## `0.2.4` 142 | 143 | * Support tls-sni-01 144 | 145 | ## `0.2.3` 146 | 147 | * Support certificate revocation 148 | * Move everything under the `AcmeV2::Client` namespace 149 | * Improved errors 150 | -------------------------------------------------------------------------------- /spec/cassettes/new_account_refuse_terms.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:16 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:16 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - fllAWa2hcoxWw_V2i5yY2Q 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:16 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:16 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiZmxsQVdhMmhjb3hXd19WMmk1eVkyUSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6Ind6WG1pbWJ3cElfdFhva2lqNTNpNTlvYVRTTks2QnVPLWt0cDhwLXFBVG1YanBUdUxFRUF6WVFxVVIyUncwZmJ6TG13bk1qazFLVFJ1azZjdllFREhJd1VFdUU4Y2VUaVg5THdnTzBzenpLemNMMGdtZERDZnc1WVFfcjRlenJ0VC0wTmlMdEdkT2JLZEZ1b1VGN0UtUzk4UC1OOW0yZU5CSU1xVDdXQmw1RVRPeFh6OTRjUXhHNy13aFhhWEdlMUdpM3V6SGlxaHVBcTVZd1YyX1JROW0xd2VxUmdpbUV5NGc3NzNOSXpTMTZQOTMxRW04alFLYTRSQVBZWHY5SVVyOC1VMVpydVVTUWk4WGQzZnBNYVhhMnBYREtXWjc5dnM4UDBycVFpMFBCZ0tLaXBRQ1lXZS1pVDljMlh6YUR0WE1MSzVWUEY3dTNDLWowLUlkaXRDUSJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl19","signature":"WmC99gSTvwIILpo7vzgQX6RY15lQrwQ5_GiqLJWkI7yPpnDjFM7RzTg-fdDIdEBKUO_SmZquyq6DeWTGeK5Sv05pa35TzY7RG8KeUVUBn2TtNmvksBvufvzzSZkZtwyiYOpaGS9wtmVyl5K92trzgQKfEbhbmkX1G2C48FErcIPASExxRXGrO7Ow8xKVWqIjNzdIcB_xaNIF6cqoDIaQT0QbtCLcMDum732CWbGtGfyNcbXwzFfu5g9X2qUSCs6YsNa1ScljowKLFAxf-fa8F7ztkUkAUh68LS-oNY2MkXjYHdwtmXZTMlC46nPUz8yoAExlewpeKPrJBWEsY_mY5g"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 403 91 | message: Forbidden 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/problem+json; charset=utf-8 97 | Link: 98 | - ;rel="terms-of-service" 99 | - <>;rel="index" 100 | Replay-Nonce: 101 | - s652EIBYhKFgo3BZDe-88w 102 | Date: 103 | - Thu, 10 Oct 2019 03:08:16 GMT 104 | Content-Length: 105 | - '150' 106 | body: 107 | encoding: UTF-8 108 | string: |- 109 | { 110 | "type": "urn:ietf:params:acme:error:agreementRequired", 111 | "detail": "Provided account did not agree to the terms of service", 112 | "status": 403 113 | } 114 | http_version: 115 | recorded_at: Thu, 10 Oct 2019 03:08:16 GMT 116 | recorded_with: VCR 2.9.3 117 | -------------------------------------------------------------------------------- /spec/cassettes/new_account_agree_terms.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:16 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:16 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - yFZPHjvjLZq2aBwFbGoOKw 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:16 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:16 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoieUZaUEhqdmpMWnEyYUJ3RmJHb09LdyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6IjRSeEQ1UnBMT2JjNURRcHp0UlZ3YkNsaWdWWmJnQktoaWx5MFRXbTJpTUZYc0ZRRENlanJ3bHhqLS1nUXQ1UndXX3EyT0FvRVpNMHBHVjN1eWN3SVlsM2VmYVYyMVpjVHA1QVM2OHptdllEQkoxTFhoVGtwdkJxOHEwMmNJd0tuOGxTaExHdUpWTFl1YmMzZ0lyU3Q3ck5jckJzNEh6cThqZ3VRaHF1c2xRWkRBS2tILS02em1Oc0t6M084UU1rYmNqLWx1Z2xEUFcxcU91SlpRUWpoX2U1WXludmtqR3I0cjcxNHdVbjhKcEJfRWl3TG5BMkM3MVM1ZmRCeWN0QjNPcG4zald5ZTJlRGlLZmJqX1JSOEZ0czIzcHQ5QXUtTGtDMVdVdEZ2X3pGaXA4Qkp4WWNjd0ZPSGhWYjl3b2hDWV8yMG9DT3NERkhhcHJWV3p4M0s5dyJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"HYzqegvAYrBt13T11BB832WldifTAONqmZB4xRqoI9xcsMVDDTICqJo8FUJpiSvqn6jKN8yBkW8_OEbiuZKe3Ob1wQ0EGL0O0HX16kHjkS0qkrMVVDfSuxOM5LT4CrpbnqbiU0tAifD9tl2_S98nOAM2Oln6xJAjZt49gJAJCSvmOyei42XnZZjFPVPQOsrusZzliEPNZHokgNxqn4TPmVSImqQykJluV_tZaKOUVYJxzG36twPNMOqz2EjVVxxUmgFmDGIpE8qQf3voRddA2Okb9gdcb6GPRMNH1oQmlDmrq1SzQxXE34uRl1dXWf3oIh4tApTVoGC6FJwqqcfMYQ"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/61" 101 | Replay-Nonce: 102 | - _ClcKhAQfQ3NUANcKCCKfA 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:16 GMT 105 | Content-Length: 106 | - '550' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/61", 116 | "key": { 117 | "kty": "RSA", 118 | "n": "4RxD5RpLObc5DQpztRVwbCligVZbgBKhily0TWm2iMFXsFQDCejrwlxj--gQt5RwW_q2OAoEZM0pGV3uycwIYl3efaV21ZcTp5AS68zmvYDBJ1LXhTkpvBq8q02cIwKn8lShLGuJVLYubc3gIrSt7rNcrBs4Hzq8jguQhquslQZDAKkH--6zmNsKz3O8QMkbcj-luglDPW1qOuJZQQjh_e5YynvkjGr4r714wUn8JpB_EiwLnA2C71S5fdByctB3Opn3jWye2eDiKfbj_RR8Fts23pt9Au-LkC1WUtFv_zFip8BJxYccwFOHhVb9wohCY_20oCOsDFHaprVWzx3K9w", 119 | "e": "AQAB" 120 | } 121 | } 122 | http_version: 123 | recorded_at: Thu, 10 Oct 2019 03:08:16 GMT 124 | recorded_with: VCR 2.9.3 125 | -------------------------------------------------------------------------------- /spec/certificate_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AcmeV2::Client::CertificateRequest do 4 | let(:test_key) { generate_private_key } 5 | 6 | it 'reads the common name from the subject' do 7 | request = AcmeV2::Client::CertificateRequest.new(private_key: test_key, subject: { common_name: 'example.org' }) 8 | 9 | expect(request.common_name).to eq('example.org') 10 | 11 | request = AcmeV2::Client::CertificateRequest.new(private_key: test_key, subject: { 'CN' => 'example.org' }) 12 | 13 | expect(request.common_name).to eq('example.org') 14 | end 15 | 16 | it "doesn't modify the given subject" do 17 | subject = { common_name: 'example.org' } 18 | original = subject.dup 19 | AcmeV2::Client::CertificateRequest.new(private_key: test_key, subject: subject) 20 | 21 | expect(subject).to eq(original) 22 | end 23 | 24 | it 'normalizes the subject to OpenSSL short names' do 25 | subject = AcmeV2::Client::CertificateRequest::SUBJECT_KEYS.each_with_object({}) {|(key, _), hash| 26 | hash[key] = 'example' 27 | } 28 | request = AcmeV2::Client::CertificateRequest.new(private_key: test_key, subject: subject) 29 | 30 | subject = AcmeV2::Client::CertificateRequest::SUBJECT_KEYS.each_with_object({}) {|(_, short_name), hash| 31 | hash[short_name] = 'example' 32 | } 33 | expect(request.subject).to eq(subject) 34 | end 35 | 36 | it 'sets the subject common name from the parameter' do 37 | request = AcmeV2::Client::CertificateRequest.new(common_name: 'example.org', private_key: test_key) 38 | 39 | expect(request.subject['CN']).to eq('example.org') 40 | end 41 | 42 | it 'adds the common name to the names' do 43 | request = AcmeV2::Client::CertificateRequest.new(common_name: 'example.org', private_key: test_key) 44 | 45 | expect(request.names).to eq(%w(example.org)) 46 | end 47 | 48 | it 'picks a single domain as the common name' do 49 | request = AcmeV2::Client::CertificateRequest.new(names: %w(example.org), private_key: test_key) 50 | 51 | expect(request.common_name).to eq('example.org') 52 | expect(request.subject['CN']).to eq('example.org') 53 | expect(request.names).to eq(%w(example.org)) 54 | end 55 | 56 | it 'picks the common name from the names' do 57 | request = AcmeV2::Client::CertificateRequest.new(names: %w(example.org www.example.org), private_key: test_key) 58 | 59 | expect(request.common_name).to eq('example.org') 60 | expect(request.subject['CN']).to eq('example.org') 61 | expect(request.names).to eq(%w(example.org www.example.org)) 62 | end 63 | 64 | it 'expects a domain' do 65 | expect { 66 | AcmeV2::Client::CertificateRequest.new(private_key: test_key) 67 | }.to raise_error(ArgumentError, /No common name/) 68 | end 69 | 70 | it 'disallows arbitrary subject keys' do 71 | expect { 72 | AcmeV2::Client::CertificateRequest.new( 73 | common_name: 'example.org', 74 | private_key: test_key, 75 | subject: { :milk => 'yes', 'serialNumber' => 123 } 76 | ) 77 | }.to raise_error(ArgumentError, /Unexpected subject attributes/) 78 | end 79 | 80 | it 'checks consistency of given common names' do 81 | expect { 82 | AcmeV2::Client::CertificateRequest.new( 83 | common_name: 'example.org', 84 | private_key: test_key, 85 | subject: { common_name: 'example.net' } 86 | ) 87 | }.to raise_error(ArgumentError, /Conflicting common name/) 88 | 89 | expect { 90 | AcmeV2::Client::CertificateRequest.new( 91 | common_name: 'example.org', 92 | private_key: test_key, 93 | subject: { 'CN' => 'example.net' } 94 | ) 95 | }.to raise_error(ArgumentError, /Conflicting common name/) 96 | end 97 | 98 | it 'assigns the public key' do 99 | request = AcmeV2::Client::CertificateRequest.new(common_name: 'example.org', private_key: test_key) 100 | 101 | expect(public_key_to_pem(request.csr.public_key)).to eq(public_key_to_pem(test_key)) 102 | expect(request.csr.verify(request.csr.public_key)).to be(true) 103 | end 104 | 105 | it 'adds the common name to the subject' do 106 | request = AcmeV2::Client::CertificateRequest.new(common_name: 'example.org', private_key: test_key) 107 | 108 | subject = request.csr.subject.to_a.map { |name, value, _| [name, value] }.to_h 109 | expect(subject['CN']).to eq('example.org') 110 | end 111 | 112 | it 'adds other valid attributes to the subject' do 113 | subject_keys = AcmeV2::Client::CertificateRequest::SUBJECT_KEYS 114 | subject = subject_keys.each_with_object({}) {|(_, short_name), hash| 115 | hash[short_name] = 'example' 116 | } 117 | request = AcmeV2::Client::CertificateRequest.new(private_key: test_key, subject: subject) 118 | 119 | csr_subject = request.csr.subject.to_a.map { |name, value, _| [name, value] }.to_h 120 | expect(csr_subject).to eq(subject) 121 | end 122 | 123 | it 'creates a subjectAltName extension with multiple names' do 124 | request = AcmeV2::Client::CertificateRequest.new(names: %w(example.org www.example.org), private_key: test_key) 125 | 126 | extension = request.csr.attributes.find { |attribute| 127 | asn1_dig(attribute).first.value == 'subjectAltName' 128 | } 129 | expect(extension).not_to be_nil 130 | value = asn1_dig(extension).last.value 131 | expect(value).to include('example.org') 132 | expect(value).to include('www.example.org') 133 | end 134 | 135 | it 'signs the request with the private key' do 136 | request = AcmeV2::Client::CertificateRequest.new(common_name: 'example.org', private_key: test_key) 137 | 138 | expect(verify_csr(request.csr, test_key)).to be(true) 139 | end 140 | 141 | it 'supports ECDSA keys' do 142 | ec_key = OpenSSL::PKey::EC.new('secp384r1') 143 | ec_key.generate_key 144 | request = AcmeV2::Client::CertificateRequest.new(common_name: 'example.org', 145 | private_key: ec_key) 146 | 147 | expect(request.csr.verify(ec_key)).to be(true) 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /spec/cassettes/registration_agree_terms.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:21 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - 2ypJmsMoJqIBfkTovMJ6CA 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:21 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiMnlwSm1zTW9KcUlCZmtUb3ZNSjZDQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiSV9pVy1fd3hGZS1NY1VZWU1TN3RuVklUMnZCenY0QWdhNHluXzE3QjhFMW05SzdGaUs3Wkg5YVhDX0pRakF4LSIsInkiOiJ3RWxVUEJmS1owSkJscHVFbkVMdGJFYTRTTnl1VmhObVNwTTVXZUJGZmpObUFIay1tLTI5aGxHTFBWdWpQdVR4In19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"dcY7ek129KQXqER1JrSltKapkGiP6IEvcQ59Zck8z1RazEgjD7NIFkl25xNXf0hs_3q601DOT5iEX8t7c8WvWm3SpqbsjU0lLY61s1PofqEoFIGjIiaj0OIOCQLXgqTU"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/74" 101 | Replay-Nonce: 102 | - 9C8wBG9HcZeC-7pFjabZAw 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:21 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/74", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "I_iW-_wxFe-McUYYMS7tnVIT2vBzv4Aga4yn_17B8E1m9K7FiK7ZH9aXC_JQjAx-", 120 | "y": "wElUPBfKZ0JBlpuEnELtbEa4SNyuVhNmSpM5WeBFfjNmAHk-m-29hlGLPVujPuTx" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 125 | - request: 126 | method: post 127 | uri: "/my-account/74" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiOUM4d0JHOUhjWmVDLTdwRmphYlpBdyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50Lzc0Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzQifQ","payload":"","signature":"QnlziDAwDfycTS2RpoZRqQzZwnVqwraV9qH0eDkgDsn-awJIFwRDlhLKsR3Aeuflj0yAstSPpZK9AEgmBQhD8isFqSJ32IdxwBKwYUKLWIhoyi_eekxaWX9gYjgtlTuz"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 200 143 | message: OK 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Replay-Nonce: 152 | - LsGGR_TRtJ2fycbdvHvJ6Q 153 | Date: 154 | - Thu, 10 Oct 2019 03:08:21 GMT 155 | Content-Length: 156 | - '353' 157 | body: 158 | encoding: UTF-8 159 | string: |- 160 | { 161 | "status": "valid", 162 | "contact": [ 163 | "mailto:info@example.com" 164 | ], 165 | "orders": "/list-orderz/74", 166 | "key": { 167 | "kty": "EC", 168 | "crv": "P-384", 169 | "x": "I_iW-_wxFe-McUYYMS7tnVIT2vBzv4Aga4yn_17B8E1m9K7FiK7ZH9aXC_JQjAx-", 170 | "y": "wElUPBfKZ0JBlpuEnELtbEa4SNyuVhNmSpM5WeBFfjNmAHk-m-29hlGLPVujPuTx" 171 | } 172 | } 173 | http_version: 174 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 175 | recorded_with: VCR 2.9.3 176 | -------------------------------------------------------------------------------- /spec/cassettes/order_status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:12 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:12 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - iX8A1ZF--iuYUR9_hREK-w 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:12 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:12 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm5vbmNlIjoiaVg4QTFaRi0taXVZVVI5X2hSRUstdyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoicWQyRVB2N0ZKTnpJcko4MVlFcFh0RWM3ODNfVGtWRk9sRlNHMlpraldRSSIsInkiOiJXRVlYUHFkdlhmLXJGVk4wSjJKbzZWOXNfem1HejkxNDlQdVZTX0tndllJIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"gs03FTPoqqRRGSGZUqN4DWPC8fw--mEEGr4CJ4F4zvDxjzijvIJqIZFk-gad4Iw-vnoYgqVnfEMxYLpBQybrwQ"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/50" 101 | Replay-Nonce: 102 | - CWTu9fWwwRSx6S0rkmrlgg 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:12 GMT 105 | Content-Length: 106 | - '311' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/50", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-256", 119 | "x": "qd2EPv7FJNzIrJ81YEpXtEc783_TkVFOlFSG2ZkjWQI", 120 | "y": "WEYXPqdvXf-rFVN0J2Jo6V9s_zmGz9149PuVS_KgvYI" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:12 GMT 125 | - request: 126 | method: post 127 | uri: "/order-plz" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm5vbmNlIjoiQ1dUdTlmV3d3UlN4NlMwcmttcmxnZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC81MCJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"PYswmghKoB1wfPrqYItB_XmUKElIW_1t8Ui_FOvEQezc_H8_s2c8VRCD4cEnT0UczLSNHmwCg8KH-HO9GlN18Q"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 201 143 | message: Created 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Location: 152 | - "/my-order/pF2bujKOeAqmFbop2GQgy3GA0AahpSPSMRGikDcCjNw" 153 | Replay-Nonce: 154 | - Vrl3vsP3I1GJ6JGkHffvOw 155 | Date: 156 | - Thu, 10 Oct 2019 03:08:12 GMT 157 | Content-Length: 158 | - '382' 159 | body: 160 | encoding: UTF-8 161 | string: |- 162 | { 163 | "status": "pending", 164 | "expires": "2019-10-11T03:08:12Z", 165 | "identifiers": [ 166 | { 167 | "type": "dns", 168 | "value": "example.com" 169 | } 170 | ], 171 | "finalize": "/finalize-order/pF2bujKOeAqmFbop2GQgy3GA0AahpSPSMRGikDcCjNw", 172 | "authorizations": [ 173 | "/authZ/K0e3DRXwAH_XLhLKQSByTFf3JjEcbMMEFJrrA4XPUkE" 174 | ] 175 | } 176 | http_version: 177 | recorded_at: Thu, 10 Oct 2019 03:08:12 GMT 178 | recorded_with: VCR 2.9.3 179 | -------------------------------------------------------------------------------- /spec/cassettes/new_order.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:20 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - Oro0Rhbjrz15xEYyE3bWYg 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:20 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiT3JvMFJoYmpyejE1eEVZeUUzYldZZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiZW1CYXd1bF9lRkFqeXZGZ2ZRU2JlQUpHTTZnbDVFZ2tDQkdHSzk3RjV1c3FSZjZXWmtJLS1KQjgyWXFBNDJDeSIsInkiOiJTb1VaMzVOTnBNVG9XdTJZMWdkVjgwUHJBZ3UxVFRnR2s4eDVTSXZ1WmNUMFl0bllDNkIyVHpmdGg4di1VX2hPIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"ZhYDAXx1fRqjgQhlSqq2vWfE0Tu8GIBx5VED5AgsTny0LFxr0VTTcfV8g7qsavCoGipRRZcrNfCL326kh5mq1XPHzXdG0iNWwtGHRrbMB4XrsaTC4E5js3RMtxF9CKxc"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/69" 101 | Replay-Nonce: 102 | - fSZVUdObtWiKkOeoBuQDPg 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:20 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/69", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "emBawul_eFAjyvFgfQSbeAJGM6gl5EgkCBGGK97F5usqRf6WZkI--JB82YqA42Cy", 120 | "y": "SoUZ35NNpMToWu2Y1gdV80PrAgu1TTgGk8x5SIvuZcT0YtnYC6B2Tzfth8v-U_hO" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 125 | - request: 126 | method: post 127 | uri: "/order-plz" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiZlNaVlVkT2J0V2lLa09lb0J1UURQZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC82OSJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"_Xt5PHogrITBJFl7q8DHi6Mzl3nMr-K43QNQshDVmmcdVp98BF8xQr3XM39RH_hNhze5awagBqVrA50ELUJSW6XG3LBCR5N6fyhsVbWoDnx8rCwfY0F0YqGzTZacSjlA"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 201 143 | message: Created 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Location: 152 | - "/my-order/6H2x8Bin-qxPnVerW-k4C-2nlYzbW3SWpBlw-o--_Kg" 153 | Replay-Nonce: 154 | - BoTL51TdVcVh6iD_cLVPNw 155 | Date: 156 | - Thu, 10 Oct 2019 03:08:20 GMT 157 | Content-Length: 158 | - '382' 159 | body: 160 | encoding: UTF-8 161 | string: |- 162 | { 163 | "status": "pending", 164 | "expires": "2019-10-11T03:08:20Z", 165 | "identifiers": [ 166 | { 167 | "type": "dns", 168 | "value": "example.com" 169 | } 170 | ], 171 | "finalize": "/finalize-order/6H2x8Bin-qxPnVerW-k4C-2nlYzbW3SWpBlw-o--_Kg", 172 | "authorizations": [ 173 | "/authZ/AnWQqpbBeqh-Vm-5BAEWTRnb3lKyEtfJUxdTllcdGbo" 174 | ] 175 | } 176 | http_version: 177 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 178 | recorded_with: VCR 2.9.3 179 | -------------------------------------------------------------------------------- /spec/cassettes/nonce_retry.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:15 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 44 | - request: 45 | method: post 46 | uri: "/sign-me-up" 47 | body: 48 | encoding: UTF-8 49 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiaW52YWxpZF9ub25jZSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6InluSkVQN2JyQi1sMlRXQUtxSkVOemM4U1NNRnJpTXhKTjRRU05qcF9zeFEtVlZzZmZkbXRFNk1aNDdEVmI5cEo3c0ZoN1p0bmNkckdhX3BheWdmci00aUg5MGRCeUpNeDhBWTJDUmxQZHFlR3ozLXNEc2ItOU5OQWkySnhPaXl5RUxtUXk2aHNXcWVhdkJfSnpsRlk4YTJMbFRzM1NOQml5amVBOVhwWnlGR0dFNzNoNkFnNFRfOHZzWWRINFpTaWVMVVhPMC1FWnNTdnA2aXhUN0VjVTVLUlJpWUhGNGVzZk9zTm1ja0Rna3VhcEh3S2hua1Q1VmlKY1k1clVZVF9kZHZ0M2NZZktGeDNxRFcteUdGUHVFcVlHZHlmaUllTWJOdURPUGdfdVNlOWN4aFJvWnRtS1lDYUJtaDFZLXRXNG5vX1M3ektvYmJNQnU2NHpwYjNLdyJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"tp6zGSHndIi8tqYSy_IgI9xzs_y1wTsWdpBrtlk7vqSt8ZUX16VsTtev7hwoykOSM26Tj-M1yiTX5M_dGQET4F6HNaq3J9V8JE-rUT7go_ybf-q7JE33W1OTU7JvCayRaYSvxG9sPDthtrUFQnOqGja2EzNYRPcFpXVEHnMil71oaAxYGpBi1BSeHL4yM1b3JRW28LUHhtm4fS4E32o9GL891y6MinO7yVbwwnS_EW12kP61kV_qcOWgLF_EIaCzhrs-_3rflEXTT1b1Dta0FdIA6oyi0j_GBc5SFL1eGAnJmgsj9y7t_34Unva4ZUZwQwLepA9RIHRTOBUtwcAeYQ"}' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Content-Type: 54 | - application/jose+json 55 | Accept-Encoding: 56 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 57 | Accept: 58 | - "*/*" 59 | response: 60 | status: 61 | code: 400 62 | message: Bad Request 63 | headers: 64 | Cache-Control: 65 | - public, max-age=0, no-cache 66 | Content-Type: 67 | - application/problem+json; charset=utf-8 68 | Link: 69 | - <>;rel="index" 70 | Replay-Nonce: 71 | - X2B9trMSP5OkXY_arjvAGw 72 | Date: 73 | - Thu, 10 Oct 2019 03:08:15 GMT 74 | Content-Length: 75 | - '138' 76 | body: 77 | encoding: UTF-8 78 | string: |- 79 | { 80 | "type": "urn:ietf:params:acme:error:badNonce", 81 | "detail": "JWS has an invalid anti-replay nonce: invalid_nonce", 82 | "status": 400 83 | } 84 | http_version: 85 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 86 | - request: 87 | method: post 88 | uri: "/sign-me-up" 89 | body: 90 | encoding: UTF-8 91 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiWDJCOXRyTVNQNU9rWFlfYXJqdkFHdyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6InluSkVQN2JyQi1sMlRXQUtxSkVOemM4U1NNRnJpTXhKTjRRU05qcF9zeFEtVlZzZmZkbXRFNk1aNDdEVmI5cEo3c0ZoN1p0bmNkckdhX3BheWdmci00aUg5MGRCeUpNeDhBWTJDUmxQZHFlR3ozLXNEc2ItOU5OQWkySnhPaXl5RUxtUXk2aHNXcWVhdkJfSnpsRlk4YTJMbFRzM1NOQml5amVBOVhwWnlGR0dFNzNoNkFnNFRfOHZzWWRINFpTaWVMVVhPMC1FWnNTdnA2aXhUN0VjVTVLUlJpWUhGNGVzZk9zTm1ja0Rna3VhcEh3S2hua1Q1VmlKY1k1clVZVF9kZHZ0M2NZZktGeDNxRFcteUdGUHVFcVlHZHlmaUllTWJOdURPUGdfdVNlOWN4aFJvWnRtS1lDYUJtaDFZLXRXNG5vX1M3ektvYmJNQnU2NHpwYjNLdyJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"feFWQErflM26yux443Mb0WAzP1_meo-SpdwGiXsQ35D8nFAZo6LcjowluK7vDwKpSJkPxb7Pgy3fEKqr4CvZ70G8HFAUbYaT2PkrTw1jIBxJCcK2O6iQ-e4ZmEAR0gz1HNb-QI9KEO3lrBbNvZtoswZXv4yA2vzvSwugXZKB8jTfNMUJjAMLs0WCBqoRgSfNeVGd9Hty0CDknK97jm4tYmxStYYpXZQ9pYSEq5uWAFCjvjdQ1yiQPdEV8YjSTN6agYVx670kgt1TVZIORhGwOOwQwWXK5-BhI6h24uFAzKHWCLBYWj54Fjod-DsrS1Z9-dTZN5IBodE9Lz4l9suOXg"}' 92 | headers: 93 | User-Agent: 94 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 95 | Content-Type: 96 | - application/jose+json 97 | Accept-Encoding: 98 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 99 | Accept: 100 | - "*/*" 101 | response: 102 | status: 103 | code: 201 104 | message: Created 105 | headers: 106 | Cache-Control: 107 | - public, max-age=0, no-cache 108 | Content-Type: 109 | - application/json; charset=utf-8 110 | Link: 111 | - <>;rel="index" 112 | Location: 113 | - "/my-account/56" 114 | Replay-Nonce: 115 | - IKA3e6VXcs73cg6wwe4q0w 116 | Date: 117 | - Thu, 10 Oct 2019 03:08:15 GMT 118 | Content-Length: 119 | - '550' 120 | body: 121 | encoding: UTF-8 122 | string: |- 123 | { 124 | "status": "valid", 125 | "contact": [ 126 | "mailto:info@example.com" 127 | ], 128 | "orders": "/list-orderz/56", 129 | "key": { 130 | "kty": "RSA", 131 | "n": "ynJEP7brB-l2TWAKqJENzc8SSMFriMxJN4QSNjp_sxQ-VVsffdmtE6MZ47DVb9pJ7sFh7ZtncdrGa_paygfr-4iH90dByJMx8AY2CRlPdqeGz3-sDsb-9NNAi2JxOiyyELmQy6hsWqeavB_JzlFY8a2LlTs3SNBiyjeA9XpZyFGGE73h6Ag4T_8vsYdH4ZSieLUXO0-EZsSvp6ixT7EcU5KRRiYHF4esfOsNmckDgkuapHwKhnkT5ViJcY5rUYT_ddvt3cYfKFx3qDW-yGFPuEqYGdyfiIeMbNuDOPg_uSe9cxhRoZtmKYCaBmh1Y-tW4no_S7zKobbMBu64zpb3Kw", 132 | "e": "AQAB" 133 | } 134 | } 135 | http_version: 136 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 137 | recorded_with: VCR 2.9.3 138 | -------------------------------------------------------------------------------- /spec/cassettes/simpler_identifiers_order.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:20 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - zXfGZ7qMCxysCz_4Qg-DYA 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:20 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoielhmR1o3cU1DeHlzQ3pfNFFnLURZQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6IndsV2ZQcFJwUlRsbUs3anpHYjFRUzRCYnV5RU9zVU83ZlFGbFRMRFRIblVIWG5ld0I3VDFVTXpqNjNFZEJyLXdDQ0lJNEk3ZWptcVNRbmZZeHlObGlDNkIxVEUzTE1WZEFyZG8wSmdHMF9sLUlmSnpnT3pFWVdVRHlPWTY5Mnh4ajdsRVBTVzEzYUpFNTJSVHg2cDRyZmJYYXQ1UEV1bnk2VElxSmV6SVI4YmlCVjdmRG9KcEtSWVdvMElrT1V1MkpfZjJaVmxIZ05ucU5zWnVISEgtYThFNG5YZUM1ZUo4Nm5IbFdINVJFb2k1N1lhbm9ZOFpabU5LSGQxNU9RTEJ0UnZrNGwwTkp2cU5sd3VaX0g3NDVPTl9ET3VPYWZ5NGxucGpOaDJQMklpeWNaZWM4NHNxZ1l6WEt6MjlrMmlPNHVZel8zMG54OVN3bWphT3c1TF91USJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"V8BzJKt4lURIVcblFYT1w4Rx7CvIoM04NlJSNlXyQqmMUmhJk9BlyWD9J3hna25Wavpki6yMskLmtn7r72lIuuOcbCavAzLSit5ATtusuIg9KymQ0yjjupyaViUd0-xdhmkq8F39GazoCk1QMQpXCT7g_j58uUDIX3tuMcaCR0Rbv-ay0vKvjssdRr96K5CmWeWddqEjNxiLjMXOw5CBTKXe_lrsX-UhUtrsykGjDwbbWVu4IZGSpIPsQWery_Whr1AhZ-0zf6diPAYBJhGyPi7VAo4t6hHT-NQVsnX9G3wo4wmwGVQ-fFQSKzdU2PLk8Lre60nPBmvUKYpzJG7XZw"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/68" 101 | Replay-Nonce: 102 | - bYhTDj52cvycxsqvSYlUYQ 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:20 GMT 105 | Content-Length: 106 | - '550' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/68", 116 | "key": { 117 | "kty": "RSA", 118 | "n": "wlWfPpRpRTlmK7jzGb1QS4BbuyEOsUO7fQFlTLDTHnUHXnewB7T1UMzj63EdBr-wCCII4I7ejmqSQnfYxyNliC6B1TE3LMVdArdo0JgG0_l-IfJzgOzEYWUDyOY692xxj7lEPSW13aJE52RTx6p4rfbXat5PEuny6TIqJezIR8biBV7fDoJpKRYWo0IkOUu2J_f2ZVlHgNnqNsZuHHH-a8E4nXeC5eJ86nHlWH5REoi57YanoY8ZZmNKHd15OQLBtRvk4l0NJvqNlwuZ_H745ON_DOuOafy4lnpjNh2P2IiycZec84sqgYzXKz29k2iO4uYz_30nx9SwmjaOw5L_uQ", 119 | "e": "AQAB" 120 | } 121 | } 122 | http_version: 123 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 124 | - request: 125 | method: post 126 | uri: "/order-plz" 127 | body: 128 | encoding: UTF-8 129 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiYlloVERqNTJjdnljeHNxdlNZbFVZUSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC82OCJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"SHqLSudSSQg_y-gYfZ1maiukI4hVThXgc5fWOqZAxwvfdcc0tTdNrwABeGQjL4def9JuHzCDDof2Pg43yhYpKZsUjfv41dKLTSB_Y3Q887cZeIw8wh6l4L44QTpp-trkLN4tC4ZpHpel8Y7yAsOB5-5Yx3QetA4TsSnxXJuVZx-2lJE8D5B81XFwszOJH-ngMnE3ap-WazG1R_DFu7VNl6UEUFH3wZW_YS-WTaE57pCpFBaJZ3CbaYITGRzrA0WmfMUI90vGwVFrhzrywFC191pIcXPqO_2dN8gwPC4yjVwyeMkWYPy7SqpWPPvX3ZfpoYptFPK5jnxodVNjsh7Wuw"}' 130 | headers: 131 | User-Agent: 132 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 133 | Content-Type: 134 | - application/jose+json 135 | Accept-Encoding: 136 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 137 | Accept: 138 | - "*/*" 139 | response: 140 | status: 141 | code: 201 142 | message: Created 143 | headers: 144 | Cache-Control: 145 | - public, max-age=0, no-cache 146 | Content-Type: 147 | - application/json; charset=utf-8 148 | Link: 149 | - <>;rel="index" 150 | Location: 151 | - "/my-order/YkH7utCEsPA9QutahVZh5Osw9Sg72w0HxJG1nnN04IQ" 152 | Replay-Nonce: 153 | - QOwgE1D5hYw0laVP8Re8ig 154 | Date: 155 | - Thu, 10 Oct 2019 03:08:20 GMT 156 | Content-Length: 157 | - '382' 158 | body: 159 | encoding: UTF-8 160 | string: |- 161 | { 162 | "status": "pending", 163 | "expires": "2019-10-11T03:08:20Z", 164 | "identifiers": [ 165 | { 166 | "type": "dns", 167 | "value": "example.com" 168 | } 169 | ], 170 | "finalize": "/finalize-order/YkH7utCEsPA9QutahVZh5Osw9Sg72w0HxJG1nnN04IQ", 171 | "authorizations": [ 172 | "/authZ/xtMqT6H0eM9s5PX4NJtzOx4DDBs1zO4wtbxLM-PkZy0" 173 | ] 174 | } 175 | http_version: 176 | recorded_at: Thu, 10 Oct 2019 03:08:20 GMT 177 | recorded_with: VCR 2.9.3 178 | -------------------------------------------------------------------------------- /spec/cassettes/account_contact_deactivate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:15 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - jlqzVngJd_Dr9JEngxlYOw 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:15 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiamxxelZuZ0pkX0RyOUpFbmd4bFlPdyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6InJKN0Q2cV9fS0FPM2Q2M3RtbHk3eXdjTlhabWhlRl8zc3JlcFpMaHdKbW94VUtfWnJWeVQ4VHpndmxwLVZYTVZHeThvaEt6MUZBektKVVYwYUVFWG5CNzZSbVpSVTU3b2RGanZkeWZfOG82UWNMUER3c21STFFvR0JDNmlFSjZyX3ZCb3RKZHE2ZXdtdF9nZ0ZTa2lRNU1Gb2twNnNsTktSSTJKRUZ5NFkyTmtfa19SSlRxNm5ER2pwOXhpdnR4STctc0Fuak15V19jaVZ1MHFnTUtKcy0xYmQ5bm83TnkwR1NjbmJKNk1yY2kwTTFBcVNPSnJiY3h5TWdhbFZCZV9TVU9vMm9DdC1Vb2FEVF9xM0lhVDNLNURETlF0M01yWFhsOXNmTDFKQ2ZWSW5Fc1JqeVRULWl5WGZoQ011M0h6enppZHMwNGwxZXRhRmFFbUhjM1BJdyJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"dm_ch6ybg9na20Ygn_gOkjE5zyte8MzcK-vsaJU-EMrLnoo3aR7opx3sYnIyAStVxycepXC3-w_sLc_g7QPZ7u6DVPEekMbRlCMP-e7ENhs2H05BvmuXG0unfyMGCX6RNoXOxFq6Sckm9pM69RJzsaIQe-M1mF8RDMnaFIyq2hwjoHMxvn93F1UOATgVBzrfM7oG9uMyUFUP8QbPsdWLt4B8A9avIEbamN9XR_lA9vlaZpdDyVLN7vPi-gjUFdV6u0AzeHwiYG68xqwABftWOLq2oinVIDyfJfdMZ6QEf_LDAqInzTjthTfJbATjtstKFxZJGHDzZl4S8c75KyPs4A"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/57" 101 | Replay-Nonce: 102 | - Hk_U6WiUO_c_nk94sAQ6RA 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:15 GMT 105 | Content-Length: 106 | - '550' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/57", 116 | "key": { 117 | "kty": "RSA", 118 | "n": "rJ7D6q__KAO3d63tmly7ywcNXZmheF_3srepZLhwJmoxUK_ZrVyT8Tzgvlp-VXMVGy8ohKz1FAzKJUV0aEEXnB76RmZRU57odFjvdyf_8o6QcLPDwsmRLQoGBC6iEJ6r_vBotJdq6ewmt_ggFSkiQ5MFokp6slNKRI2JEFy4Y2Nk_k_RJTq6nDGjp9xivtxI7-sAnjMyW_ciVu0qgMKJs-1bd9no7Ny0GScnbJ6Mrci0M1AqSOJrbcxyMgalVBe_SUOo2oCt-UoaDT_q3IaT3K5DDNQt3MrXXl9sfL1JCfVInEsRjyTT-iyXfhCMu3Hzzzids04l1etaFaEmHc3PIw", 119 | "e": "AQAB" 120 | } 121 | } 122 | http_version: 123 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 124 | - request: 125 | method: post 126 | uri: "/my-account/57" 127 | body: 128 | encoding: UTF-8 129 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiSGtfVTZXaVVPX2Nfbms5NHNBUTZSQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50LzU3Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNTcifQ","payload":"eyJzdGF0dXMiOiJkZWFjdGl2YXRlZCJ9","signature":"UI1GQGQqmlZhoZiFWqQXdn4ZvnH3WI6Ik3sze3aAX1CZnjxi4pcVtYjobN0R73QkRnwduS1by32siYG76GtQmUyTMKIShfk2xbILsEPwQHGxgnAe6Yy8lmjPv3o7sGcdyfp1K5CrW5qrOrycb16EaSgxQQEFNNU4-_3vjXgHO62uS7c7NW84pkZB5uELDuNyDWnC1eMD_AmmGmVZvzCOy1WJheUWTwccTuGf04mycGkHbOB15IenV_OBKnrzxJrHvLZPyX0HS5f2Ay6NGLRHjsNr82qJlP-Qfxdrg2deRQqltg7k3cxwEpFN9wx9LLczfPJKynT_XzrI8zP3Ja0Cgg"}' 130 | headers: 131 | User-Agent: 132 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 133 | Content-Type: 134 | - application/jose+json 135 | Accept-Encoding: 136 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 137 | Accept: 138 | - "*/*" 139 | response: 140 | status: 141 | code: 200 142 | message: OK 143 | headers: 144 | Cache-Control: 145 | - public, max-age=0, no-cache 146 | Content-Type: 147 | - application/json; charset=utf-8 148 | Link: 149 | - <>;rel="index" 150 | Replay-Nonce: 151 | - STgLjfCPc5lz9rj3MAk1Iw 152 | Date: 153 | - Thu, 10 Oct 2019 03:08:15 GMT 154 | Content-Length: 155 | - '556' 156 | body: 157 | encoding: UTF-8 158 | string: |- 159 | { 160 | "status": "deactivated", 161 | "contact": [ 162 | "mailto:info@example.com" 163 | ], 164 | "orders": "/list-orderz/57", 165 | "key": { 166 | "kty": "RSA", 167 | "n": "rJ7D6q__KAO3d63tmly7ywcNXZmheF_3srepZLhwJmoxUK_ZrVyT8Tzgvlp-VXMVGy8ohKz1FAzKJUV0aEEXnB76RmZRU57odFjvdyf_8o6QcLPDwsmRLQoGBC6iEJ6r_vBotJdq6ewmt_ggFSkiQ5MFokp6slNKRI2JEFy4Y2Nk_k_RJTq6nDGjp9xivtxI7-sAnjMyW_ciVu0qgMKJs-1bd9no7Ny0GScnbJ6Mrci0M1AqSOJrbcxyMgalVBe_SUOo2oCt-UoaDT_q3IaT3K5DDNQt3MrXXl9sfL1JCfVInEsRjyTT-iyXfhCMu3Hzzzids04l1etaFaEmHc3PIw", 168 | "e": "AQAB" 169 | } 170 | } 171 | http_version: 172 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 173 | recorded_with: VCR 2.9.3 174 | -------------------------------------------------------------------------------- /spec/cassettes/order_certificate_download_fail.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:11 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:11 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - IY6aQ3HI_w0wWQfW_25iog 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:11 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:11 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiSVk2YVEzSElfdzB3V1FmV18yNWlvZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6InpoT0F1eUZJdDNkWFVETi1tU1BQQWhxcjdmLU1JMnpORmtDRzgzSDhsaU13MFhOelVQaW9HOVlySm1HRF9GYy1CcHd4M25yRjlmOWZWaWlRUnp6SlZGNEYybVd4TEx2RGU2UlVNRmVqVktzcXU4M2lpUk55TmZhXzBvcTUybHFtQjZIU0g0bEtQLWRIc0doOEtIOTFKMFdLYUtGeTNWQi1DSTlFZmtrZl9sNS11eFRrdGQ4Rjhjby1LRXpnLUp4YUJfQlZSa0FMQU5TNTVPRzN5b3hzLThfdEFaeDBuYXlYSXJpZ1IzSUNzVTFKWG5IVnRvT3lWZHpLNlluaTkyQzY5b1VvdTdtcUtoWlJtMk1OaXhPb1ZWZ01IbWFIYUNuWUQ4ejBlVGJuTHRIak5IaVNsSlk3YzlMNXI5QUJHZi1GemlRa2hPVWVTUDF6M0l3UmNQN3A2USJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"oFydbJkpA4pTvrCflffk6djUj-_p0D64-HlPX6UtdzfF4kiIOPlzm-s-4RQlEdqq7xb9D3WhdYxmePcVrvu8ktQ-laRE67f6egLQ_vZNdFsYXyOMYpwu-vdo0f7BIHCBYIEFPoE47myhva-kIarQEFwKXrM0sNQkulF99UNhKbbwDWd-ec5rpArm6KajbthZcG80DTuqgP-x1TmANlT4ukWLEReuvbHYQpRcyxXGWPb66xg_9MoHwPRdkKwDAam4n4qMZLOVe9c-DmmubBpNeTsqBfWZfa4rZTiTG3cfW3zVOchney9Q_1nOQ7t8QodpizQIs4aSWsABpjzh5nvwcg"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/49" 101 | Replay-Nonce: 102 | - olgUCT19BcyB-N9AhU06Pg 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:12 GMT 105 | Content-Length: 106 | - '550' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/49", 116 | "key": { 117 | "kty": "RSA", 118 | "n": "zhOAuyFIt3dXUDN-mSPPAhqr7f-MI2zNFkCG83H8liMw0XNzUPioG9YrJmGD_Fc-Bpwx3nrF9f9fViiQRzzJVF4F2mWxLLvDe6RUMFejVKsqu83iiRNyNfa_0oq52lqmB6HSH4lKP-dHsGh8KH91J0WKaKFy3VB-CI9Efkkf_l5-uxTktd8F8co-KEzg-JxaB_BVRkALANS55OG3yoxs-8_tAZx0nayXIrigR3ICsU1JXnHVtoOyVdzK6Yni92C69oUou7mqKhZRm2MNixOoVVgMHmaHaCnYD8z0eTbnLtHjNHiSlJY7c9L5r9ABGf-FziQkhOUeSP1z3IwRcP7p6Q", 119 | "e": "AQAB" 120 | } 121 | } 122 | http_version: 123 | recorded_at: Thu, 10 Oct 2019 03:08:12 GMT 124 | - request: 125 | method: post 126 | uri: "/order-plz" 127 | body: 128 | encoding: UTF-8 129 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoib2xnVUNUMTlCY3lCLU45QWhVMDZQZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC80OSJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"qeUAzjDdaQ6xi6s-0AziJX3ofrpcS73wpX0skKzASiKgA8X5ghEyUgdauDW5rJY-7ApAZg11TyGud_eInlGzuhJwt4zCFSnJn10-14crfZ3DB27JpSCvT2HgEaWAuUnZypbNpybHh4cR7LsPS7H-RynaSCmxtaGFOsbzs43pu1_Y2hGY0Oc6j66qMtgD2jgcO-N34UU2iqVvfRJAsbrSIf4dB7CJDo1rLZYGgRv1NelSOUkpeZ18oeMNyoWdAwR_XRIKjmlhr8weOlzVorhpOLOBqCEXeCq-qstzWRrgfoA3WWZEs9Bny0YKbzMpbRFkRNm2k3pmE_dCONbpjwGHzQ"}' 130 | headers: 131 | User-Agent: 132 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 133 | Content-Type: 134 | - application/jose+json 135 | Accept-Encoding: 136 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 137 | Accept: 138 | - "*/*" 139 | response: 140 | status: 141 | code: 201 142 | message: Created 143 | headers: 144 | Cache-Control: 145 | - public, max-age=0, no-cache 146 | Content-Type: 147 | - application/json; charset=utf-8 148 | Link: 149 | - <>;rel="index" 150 | Location: 151 | - "/my-order/XojARKX5NhF7DEEikmTCSu_9DUOUwQslNkSBZ6ziOic" 152 | Replay-Nonce: 153 | - fAmGEaMoBNwP3lyXEUAWeg 154 | Date: 155 | - Thu, 10 Oct 2019 03:08:12 GMT 156 | Content-Length: 157 | - '382' 158 | body: 159 | encoding: UTF-8 160 | string: |- 161 | { 162 | "status": "pending", 163 | "expires": "2019-10-11T03:08:12Z", 164 | "identifiers": [ 165 | { 166 | "type": "dns", 167 | "value": "example.com" 168 | } 169 | ], 170 | "finalize": "/finalize-order/XojARKX5NhF7DEEikmTCSu_9DUOUwQslNkSBZ6ziOic", 171 | "authorizations": [ 172 | "/authZ/KZs2V-vGigo8huJ_tGXFn0GFzgKMi-T_diKGttOXx5c" 173 | ] 174 | } 175 | http_version: 176 | recorded_at: Thu, 10 Oct 2019 03:08:12 GMT 177 | recorded_with: VCR 2.9.3 178 | -------------------------------------------------------------------------------- /spec/cassettes/account_reload.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:22 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - CrP1QhrQkN_FZt2m-polYg 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:22 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiQ3JQMVFoclFrTl9GWnQybS1wb2xZZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiOXVIeFRrOVpaWTZyaXJ4NTBDcGRfSThqZEFfVG5INXBfajZSX0psbUFzemozR1pSR3ZoZzJWU2ZfWFRsbFpSeSIsInkiOiJKNlFERzViZmFkWFlzZFdUQmJSTHZIelp2UmxOTk56NU92dmVGUmVGQnZfT2pONkt5Y1NGMVU4Z2VaWnA2Q1k0In19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"2rmE6GLVep_jRdwswxg5-ek-BxtODUvZTps7W7DNV7DEsurABCAI-8I2K8xbPnDdm4U73m3tzgjm43MQRUmM2_EU4HD57BHHL8e0WK5maHX9y0DaLJkr_xzXDTnf3B8z"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/76" 101 | Replay-Nonce: 102 | - Yf6AZXBXl4zg8fMuXti1MA 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:22 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/76", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "9uHxTk9ZZY6rirx50Cpd_I8jdA_TnH5p_j6R_JlmAszj3GZRGvhg2VSf_XTllZRy", 120 | "y": "J6QDG5bfadXYsdWTBbRLvHzZvRlNNNz5OvveFReFBv_OjN6KycSF1U8geZZp6CY4" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 125 | - request: 126 | method: post 127 | uri: "/my-account/76" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiWWY2QVpYQlhsNHpnOGZNdVh0aTFNQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50Lzc2Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzYifQ","payload":"","signature":"xasz4afk9YAM5PfzqKuxR5yYc2DXk8wkEAVxNOvgSJnKTgeO03T1aBhpjn_u8egRc-fw8r5Xlrit066ro-vvtF1L_nqaOi1XR_LoII1tI1paLgqq6x32_FIhqLRxRmFv"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 200 143 | message: OK 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Replay-Nonce: 152 | - cQnjWF-IKQXo0LiTpvqaKQ 153 | Date: 154 | - Thu, 10 Oct 2019 03:08:22 GMT 155 | Content-Length: 156 | - '353' 157 | body: 158 | encoding: UTF-8 159 | string: |- 160 | { 161 | "status": "valid", 162 | "contact": [ 163 | "mailto:info@example.com" 164 | ], 165 | "orders": "/list-orderz/76", 166 | "key": { 167 | "kty": "EC", 168 | "crv": "P-384", 169 | "x": "9uHxTk9ZZY6rirx50Cpd_I8jdA_TnH5p_j6R_JlmAszj3GZRGvhg2VSf_XTllZRy", 170 | "y": "J6QDG5bfadXYsdWTBbRLvHzZvRlNNNz5OvveFReFBv_OjN6KycSF1U8geZZp6CY4" 171 | } 172 | } 173 | http_version: 174 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 175 | - request: 176 | method: post 177 | uri: "/my-account/76" 178 | body: 179 | encoding: UTF-8 180 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiY1FualdGLUlLUVhvMExpVHB2cWFLUSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50Lzc2Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzYifQ","payload":"","signature":"KsQdo-oGEps9I5XYRd9a2YvGGPyigCDU2a6VDQImsSlOQD7vHLriJXoFCj4Bp35gmzKH7BRfEhdIIl9_CQ-AvRuMAQiYZxCSjvVs556fPla-c6o3yQDWN4IFHnz70Dtn"}' 181 | headers: 182 | User-Agent: 183 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 184 | Content-Type: 185 | - application/jose+json 186 | Accept-Encoding: 187 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 188 | Accept: 189 | - "*/*" 190 | response: 191 | status: 192 | code: 200 193 | message: OK 194 | headers: 195 | Cache-Control: 196 | - public, max-age=0, no-cache 197 | Content-Type: 198 | - application/json; charset=utf-8 199 | Link: 200 | - <>;rel="index" 201 | Replay-Nonce: 202 | - 4hysQxoLLL4LjDYY9tRQWg 203 | Date: 204 | - Thu, 10 Oct 2019 03:08:22 GMT 205 | Content-Length: 206 | - '353' 207 | body: 208 | encoding: UTF-8 209 | string: |- 210 | { 211 | "status": "valid", 212 | "contact": [ 213 | "mailto:info@example.com" 214 | ], 215 | "orders": "/list-orderz/76", 216 | "key": { 217 | "kty": "EC", 218 | "crv": "P-384", 219 | "x": "9uHxTk9ZZY6rirx50Cpd_I8jdA_TnH5p_j6R_JlmAszj3GZRGvhg2VSf_XTllZRy", 220 | "y": "J6QDG5bfadXYsdWTBbRLvHzZvRlNNNz5OvveFReFBv_OjN6KycSF1U8geZZp6CY4" 221 | } 222 | } 223 | http_version: 224 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 225 | recorded_with: VCR 2.9.3 226 | -------------------------------------------------------------------------------- /spec/cassettes/account_deactivate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:22 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - aIpDHONQ5aIE4y_5eCwLTw 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:22 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiYUlwREhPTlE1YUlFNHlfNWVDd0xUdyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiMmRzeVlnSTFtclFpZlgwd2VUeWlqcHVma3lqYUZIOVQteGhhMkNNNm5KWkxYaFV5RXNTaFNLT2JKb2NEZW93eiIsInkiOiJMQTVkTFg0cE1hU3JqWllfd0l1emYtSFZfZGotd04tUHYySzFESE1feTlyVmg4RnA2bXR6aDQ0a0doNGVKZEc2In19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"k-449-HjTBfetN7UdOPnx3d1BY5EFzNmA4ATwlPsY63VwiAMqUiczo_rPRGe8gzq5eqn38zaqrdFq4IOlvsQaZohryeo4NXDxfQJVVs2D6W-ljaxrEFreZP9mI2oK-qb"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/77" 101 | Replay-Nonce: 102 | - OxD0J0u_iL0-5vwUS4D5QA 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:22 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/77", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "2dsyYgI1mrQifX0weTyijpufkyjaFH9T-xha2CM6nJZLXhUyEsShSKObJocDeowz", 120 | "y": "LA5dLX4pMaSrjZY_wIuzf-HV_dj-wN-Pv2K1DHM_y9rVh8Fp6mtzh44kGh4eJdG6" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 125 | - request: 126 | method: post 127 | uri: "/my-account/77" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiT3hEMEowdV9pTDAtNXZ3VVM0RDVRQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50Lzc3Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzcifQ","payload":"","signature":"sF5Zsax1s_OAgCPiZCOW473jl1E0fzeAS8v7fF90IyeUBcS6Ag9nh0pwpBfDFmfab8a_GuqW7ipdcmBKfWJaNSmdEBw_aC4x3KXW0lpE3etcJ19AheuPwvnkqn7eSSBw"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 200 143 | message: OK 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Replay-Nonce: 152 | - tTu9SWOMZL9kvmLYxPiL7w 153 | Date: 154 | - Thu, 10 Oct 2019 03:08:22 GMT 155 | Content-Length: 156 | - '353' 157 | body: 158 | encoding: UTF-8 159 | string: |- 160 | { 161 | "status": "valid", 162 | "contact": [ 163 | "mailto:info@example.com" 164 | ], 165 | "orders": "/list-orderz/77", 166 | "key": { 167 | "kty": "EC", 168 | "crv": "P-384", 169 | "x": "2dsyYgI1mrQifX0weTyijpufkyjaFH9T-xha2CM6nJZLXhUyEsShSKObJocDeowz", 170 | "y": "LA5dLX4pMaSrjZY_wIuzf-HV_dj-wN-Pv2K1DHM_y9rVh8Fp6mtzh44kGh4eJdG6" 171 | } 172 | } 173 | http_version: 174 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 175 | - request: 176 | method: post 177 | uri: "/my-account/77" 178 | body: 179 | encoding: UTF-8 180 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoidFR1OVNXT01aTDlrdm1MWXhQaUw3dyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50Lzc3Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzcifQ","payload":"eyJzdGF0dXMiOiJkZWFjdGl2YXRlZCJ9","signature":"W5vxTjc-GkIN3UVsKwuh6Cf8v-gEBQ8CywzO8MgRnPBJMniMYbnNtMkDEBXNYBVjgRbWaKv97L9SiHHGRD1Ts9Dz2Ng4bLqbioOASbvIjSLllb_Fmc8D9pearT6ByZRc"}' 181 | headers: 182 | User-Agent: 183 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 184 | Content-Type: 185 | - application/jose+json 186 | Accept-Encoding: 187 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 188 | Accept: 189 | - "*/*" 190 | response: 191 | status: 192 | code: 200 193 | message: OK 194 | headers: 195 | Cache-Control: 196 | - public, max-age=0, no-cache 197 | Content-Type: 198 | - application/json; charset=utf-8 199 | Link: 200 | - <>;rel="index" 201 | Replay-Nonce: 202 | - O8Wm_mcioRoVMlJplEZ9oQ 203 | Date: 204 | - Thu, 10 Oct 2019 03:08:22 GMT 205 | Content-Length: 206 | - '359' 207 | body: 208 | encoding: UTF-8 209 | string: |- 210 | { 211 | "status": "deactivated", 212 | "contact": [ 213 | "mailto:info@example.com" 214 | ], 215 | "orders": "/list-orderz/77", 216 | "key": { 217 | "kty": "EC", 218 | "crv": "P-384", 219 | "x": "2dsyYgI1mrQifX0weTyijpufkyjaFH9T-xha2CM6nJZLXhUyEsShSKObJocDeowz", 220 | "y": "LA5dLX4pMaSrjZY_wIuzf-HV_dj-wN-Pv2K1DHM_y9rVh8Fp6mtzh44kGh4eJdG6" 221 | } 222 | } 223 | http_version: 224 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 225 | recorded_with: VCR 2.9.3 226 | -------------------------------------------------------------------------------- /spec/cassettes/account_update.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:22 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - Z1KBXkieCGEvGQzNEZBsKA 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:22 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiWjFLQlhraWVDR0V2R1F6TkVaQnNLQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiVjVWaHhrVHotYkNaTklJc2xrYUY3ZUV1UTFkLWh4RTJoUUlyeTZwWl9vY01zWmYyMklPWE53dHJXOHJPbVFaTyIsInkiOiJNa2dCNkY2R1M0MUtiR2dpRTlVSTROeDdLUmVmY0xlaWZvTlpsZFJRaTdQMUdQY1V0azRtR2ViRFlQTTdEZ0lPIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"NjWzzv87jKOF0ZsbL6qCdzqEEji7ip-XLnsSdwOH2n9EppdVmwZdweNI1WwcROAbIJcSHN5qwplXIW454oI9lkHzB3GV0bTSLVGcgxBE7jVHlb6CoElqzSSrvCnTylcZ"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/75" 101 | Replay-Nonce: 102 | - UEgPK0rQckOGL31ajF6_vQ 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:22 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/75", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "V5VhxkTz-bCZNIIslkaF7eEuQ1d-hxE2hQIry6pZ_ocMsZf22IOXNwtrW8rOmQZO", 120 | "y": "MkgB6F6GS41KbGgiE9UI4Nx7KRefcLeifoNZldRQi7P1GPcUtk4mGebDYPM7DgIO" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 125 | - request: 126 | method: post 127 | uri: "/my-account/75" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiVUVnUEswclFja09HTDMxYWpGNl92USIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50Lzc1Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzUifQ","payload":"","signature":"fOKByeKmX9j5VxHiH10UFfp-QcfXU4IsFTYKzJ-4iMqN_OVZ7uBWBZpynM9EzuzOWqo-bbesA8eladGOr9XWn5uOYCOU8bSvatl-exAMWkJTPJ4oAhZvrHD4y7S0Ejm9"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 200 143 | message: OK 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Replay-Nonce: 152 | - WpFzVcHnnxQNyJDv2T17BQ 153 | Date: 154 | - Thu, 10 Oct 2019 03:08:22 GMT 155 | Content-Length: 156 | - '353' 157 | body: 158 | encoding: UTF-8 159 | string: |- 160 | { 161 | "status": "valid", 162 | "contact": [ 163 | "mailto:info@example.com" 164 | ], 165 | "orders": "/list-orderz/75", 166 | "key": { 167 | "kty": "EC", 168 | "crv": "P-384", 169 | "x": "V5VhxkTz-bCZNIIslkaF7eEuQ1d-hxE2hQIry6pZ_ocMsZf22IOXNwtrW8rOmQZO", 170 | "y": "MkgB6F6GS41KbGgiE9UI4Nx7KRefcLeifoNZldRQi7P1GPcUtk4mGebDYPM7DgIO" 171 | } 172 | } 173 | http_version: 174 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 175 | - request: 176 | method: post 177 | uri: "/my-account/75" 178 | body: 179 | encoding: UTF-8 180 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiV3BGelZjSG5ueFFOeUpEdjJUMTdCUSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50Lzc1Iiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzUifQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzp1cGRhdGVkQGV4YW1wbGUuY29tIl19","signature":"AxHnABRLi0AecBE2Cd49fgm4dMsrcF0fyMP-D29fxP337Hc9lNDqnZReVr3mOc02ezy6gujNaa1qFaMwdCVM4UU9x-EM6x7mlz0sGFGWdbSY55aBfeiRmC7U7Q-nkR2U"}' 181 | headers: 182 | User-Agent: 183 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 184 | Content-Type: 185 | - application/jose+json 186 | Accept-Encoding: 187 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 188 | Accept: 189 | - "*/*" 190 | response: 191 | status: 192 | code: 200 193 | message: OK 194 | headers: 195 | Cache-Control: 196 | - public, max-age=0, no-cache 197 | Content-Type: 198 | - application/json; charset=utf-8 199 | Link: 200 | - <>;rel="index" 201 | Replay-Nonce: 202 | - hSWCipH97jCluQOKpsP26A 203 | Date: 204 | - Thu, 10 Oct 2019 03:08:22 GMT 205 | Content-Length: 206 | - '356' 207 | body: 208 | encoding: UTF-8 209 | string: |- 210 | { 211 | "status": "valid", 212 | "contact": [ 213 | "mailto:updated@example.com" 214 | ], 215 | "orders": "/list-orderz/75", 216 | "key": { 217 | "kty": "EC", 218 | "crv": "P-384", 219 | "x": "V5VhxkTz-bCZNIIslkaF7eEuQ1d-hxE2hQIry6pZ_ocMsZf22IOXNwtrW8rOmQZO", 220 | "y": "MkgB6F6GS41KbGgiE9UI4Nx7KRefcLeifoNZldRQi7P1GPcUtk4mGebDYPM7DgIO" 221 | } 222 | } 223 | http_version: 224 | recorded_at: Thu, 10 Oct 2019 03:08:22 GMT 225 | recorded_with: VCR 2.9.3 226 | -------------------------------------------------------------------------------- /spec/cassettes/load_account_valid_kid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:15 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - CW3g31Wdrs6FPgvGtFB8Ag 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:15 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiQ1czZzMxV2RyczZGUGd2R3RGQjhBZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4Ijoid093WVBBUjc5c2plVldYc1kwaExFQmVZdkRhbTJVbmRrN1pGOGtQMy10VWE1eS1aSlJqaG54NmpYZi13eEhwSSIsInkiOiJfSl9WMW84blREcnJjZUI2U2VWand0SFl5azl4VTF4MjdCeGVhU3BDU0JtdkpkZ3k2M1I3d1VOcGhzd2gtZm93In19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"nT_BdozJyFM3mphoiJgR0aptn2nYjyhNUtNNyNGCIJCWYI8L54zmo31LM_N31trdZzMKcIUzis_pXBz8yStPRXcQqJWpU6xkK-Jv0VT2whlHbApCLxSGzhwhWcVFux-w"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/60" 101 | Replay-Nonce: 102 | - zCX0kliAkU080IxqGv0beA 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:15 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/60", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "wOwYPAR79sjeVWXsY0hLEBeYvDam2Undk7ZF8kP3-tUa5y-ZJRjhnx6jXf-wxHpI", 120 | "y": "_J_V1o8nTDrrceB6SeVjwtHYyk9xU1x27BxeaSpCSBmvJdgy63R7wUNphswh-fow" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 125 | - request: 126 | method: get 127 | uri: "" 128 | body: 129 | encoding: US-ASCII 130 | string: '' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Accept-Encoding: 135 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 136 | Accept: 137 | - "*/*" 138 | response: 139 | status: 140 | code: 200 141 | message: OK 142 | headers: 143 | Cache-Control: 144 | - public, max-age=0, no-cache 145 | Content-Type: 146 | - application/json; charset=utf-8 147 | Date: 148 | - Thu, 10 Oct 2019 03:08:15 GMT 149 | Content-Length: 150 | - '386' 151 | body: 152 | encoding: UTF-8 153 | string: |- 154 | { 155 | "keyChange": "/rollover-account-key", 156 | "meta": { 157 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 158 | }, 159 | "newAccount": "/sign-me-up", 160 | "newNonce": "/nonce-plz", 161 | "newOrder": "/order-plz", 162 | "revokeCert": "/revoke-cert" 163 | } 164 | http_version: 165 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 166 | - request: 167 | method: head 168 | uri: "/nonce-plz" 169 | body: 170 | encoding: US-ASCII 171 | string: '' 172 | headers: 173 | User-Agent: 174 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 175 | Accept: 176 | - "*/*" 177 | response: 178 | status: 179 | code: 200 180 | message: OK 181 | headers: 182 | Cache-Control: 183 | - public, max-age=0, no-cache 184 | Link: 185 | - <>;rel="index" 186 | Replay-Nonce: 187 | - xZU2IbAoSub6VCYLIpNiGg 188 | Date: 189 | - Thu, 10 Oct 2019 03:08:15 GMT 190 | body: 191 | encoding: UTF-8 192 | string: '' 193 | http_version: 194 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 195 | - request: 196 | method: post 197 | uri: "/my-account/60" 198 | body: 199 | encoding: UTF-8 200 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoieFpVMkliQW9TdWI2VkNZTElwTmlHZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1hY2NvdW50LzYwIiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNjAifQ","payload":"","signature":"aW_inVRhJIr5nLM9jIZ6kRNhPuMGa-UFm9tslXNSBvQNLX9PYQYdzFYUUiJaibmhkSvAY9K-UPO8BLW3fUcSu_bH6uXJcz8xU_H78gCCA8Jt5S_GCGI3SRxM7tk6Npa5"}' 201 | headers: 202 | User-Agent: 203 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 204 | Content-Type: 205 | - application/jose+json 206 | Accept-Encoding: 207 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 208 | Accept: 209 | - "*/*" 210 | response: 211 | status: 212 | code: 200 213 | message: OK 214 | headers: 215 | Cache-Control: 216 | - public, max-age=0, no-cache 217 | Content-Type: 218 | - application/json; charset=utf-8 219 | Link: 220 | - <>;rel="index" 221 | Replay-Nonce: 222 | - 7E9OXm-F9tEelxKaca2cyQ 223 | Date: 224 | - Thu, 10 Oct 2019 03:08:15 GMT 225 | Content-Length: 226 | - '353' 227 | body: 228 | encoding: UTF-8 229 | string: |- 230 | { 231 | "status": "valid", 232 | "contact": [ 233 | "mailto:info@example.com" 234 | ], 235 | "orders": "/list-orderz/60", 236 | "key": { 237 | "kty": "EC", 238 | "crv": "P-384", 239 | "x": "wOwYPAR79sjeVWXsY0hLEBeYvDam2Undk7ZF8kP3-tUa5y-ZJRjhnx6jXf-wxHpI", 240 | "y": "_J_V1o8nTDrrceB6SeVjwtHYyk9xU1x27BxeaSpCSBmvJdgy63R7wUNphswh-fow" 241 | } 242 | } 243 | http_version: 244 | recorded_at: Thu, 10 Oct 2019 03:08:15 GMT 245 | recorded_with: VCR 2.9.3 246 | -------------------------------------------------------------------------------- /spec/cassettes/fail_fetch_order.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:21 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - iiMrZxEMH1Vb92DQIn7ftw 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:21 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiaWlNclp4RU1IMVZiOTJEUUluN2Z0dyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6InZxNGhFWWM4YnpmSnJZdzFlNXJVS0VIT3VNcGMwY0xoenhRSjhjOXM3V2k1QnJ2b0oyckxYNURGdnRISzdCSjhKeVpIV0NQMFcyanVYNlFVOUJ6WEJJNUZCNFg5eHE0cllwaEhvbDRYcVF3dmljejFEUHVlT0RRU2hNY3hSajh0bS1qdEJoSE1NSUlxQW5tUkluN0I2UkN0QU1UYWNkeXdEU1dzbllONm10TTcwVEhWMkNBeExlS202a0V3a2N6S0M0ZGV1ZFd1MzUwMTFacVNUbHNhN0dFZmU4RDdDSHZIRlFKLWREVGE1eHdGelFVekx6TlJjMDI2X1BNandXczJyc3ZadTFpaEsySmdfVUN6SENHcUY2eHd5WlR5MU1PX0hLTERRMVNLOG54aVJWNkQwUk1mX3EtMUMwN0FiMTJPdmlfdmM4MVBiMVpkV1NkV0NrOXJndyJ9fQ","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"BX8St2zGJ1f7zlhDyuzBBXMAWzHhwYcsGjyWrNIrQIy3s-fEpoepGtyq7UmSEUfxRVf-tsT_aI6pJJYNMkC12M9TEwcbYkW9Jg46kGlapjGe5NJDokYdEtcvGtk2kpDEWlejiu_QibBDGFB4uEO5DnVx8c9PTXnhlqkjqnZO74lbsn3fYDgU1J41st40AMuYp2oV-4xdgiXg7bWqFsiuMtkB9jFVN1V43kutpRRLNvinpBgN-sR6IvgGptPLHT3B9s-Eu6h8pj-k3t91ZEam8NHWMlLzuWu8y_kbWhDeGSvrVvVmUJyI3WWprRyvLfvOg4vKPK-8yiXWC9HwSmlZCA"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/72" 101 | Replay-Nonce: 102 | - wea9niuVeVkDp9KU7uc0LA 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:21 GMT 105 | Content-Length: 106 | - '550' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/72", 116 | "key": { 117 | "kty": "RSA", 118 | "n": "vq4hEYc8bzfJrYw1e5rUKEHOuMpc0cLhzxQJ8c9s7Wi5BrvoJ2rLX5DFvtHK7BJ8JyZHWCP0W2juX6QU9BzXBI5FB4X9xq4rYphHol4XqQwvicz1DPueODQShMcxRj8tm-jtBhHMMIIqAnmRIn7B6RCtAMTacdywDSWsnYN6mtM70THV2CAxLeKm6kEwkczKC4deudWu35011ZqSTlsa7GEfe8D7CHvHFQJ-dDTa5xwFzQUzLzNRc026_PMjwWs2rsvZu1ihK2Jg_UCzHCGqF6xwyZTy1MO_HKLDQ1SK8nxiRV6D0RMf_q-1C07Ab12Ovi_vc81Pb1ZdWSdWCk9rgw", 119 | "e": "AQAB" 120 | } 121 | } 122 | http_version: 123 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 124 | - request: 125 | method: post 126 | uri: "/order-plz" 127 | body: 128 | encoding: UTF-8 129 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoid2VhOW5pdVZlVmtEcDlLVTd1YzBMQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC83MiJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"MBhk5DBtDLhQqQTq6JRqA4qafxm1IjDXAHEAQGyR6tzXbH6T2T0xNn8Kng3N-_dS3w_CCGdtBZVY8LK2EbLZi6npnJ_h_VGbA6vcd8ncwsJSyx6_AoZnqEgO56I_dv3aX0oVREkw3aerG8j0K1_mJSXh1mLhMZby4CdtXe0d0CU_EJdOpiOfZ4K6WRyYZJT1g1M99xm0DAV-oDWp9xVPPV7ZJk64qqWeOOyRya6wFtTq1Hc36eXR2eCv9KXtu9JrVSdjjLsaEjYq-ijuQSEKPxYH3l-bS7A8kmapTg-9xYBJ6h3ITb7dNdE5rgEdUJ_cbLtHD_j_MAQtlt1Yr98ePg"}' 130 | headers: 131 | User-Agent: 132 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 133 | Content-Type: 134 | - application/jose+json 135 | Accept-Encoding: 136 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 137 | Accept: 138 | - "*/*" 139 | response: 140 | status: 141 | code: 201 142 | message: Created 143 | headers: 144 | Cache-Control: 145 | - public, max-age=0, no-cache 146 | Content-Type: 147 | - application/json; charset=utf-8 148 | Link: 149 | - <>;rel="index" 150 | Location: 151 | - "/my-order/75D9-Axgm0UbKkiEyBsFLILuyj6YZ64qevtW7G23FYM" 152 | Replay-Nonce: 153 | - 5z7Fgov4rxzd6bRtvY-eoA 154 | Date: 155 | - Thu, 10 Oct 2019 03:08:21 GMT 156 | Content-Length: 157 | - '382' 158 | body: 159 | encoding: UTF-8 160 | string: |- 161 | { 162 | "status": "pending", 163 | "expires": "2019-10-11T03:08:21Z", 164 | "identifiers": [ 165 | { 166 | "type": "dns", 167 | "value": "example.com" 168 | } 169 | ], 170 | "finalize": "/finalize-order/75D9-Axgm0UbKkiEyBsFLILuyj6YZ64qevtW7G23FYM", 171 | "authorizations": [ 172 | "/authZ/UE9i-tkywhNUx-fwdKH4iJpZ5PqIQ3YsNc3HjHfe_bA" 173 | ] 174 | } 175 | http_version: 176 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 177 | - request: 178 | method: post 179 | uri: "/my-order/75D9-Axgm0UbKkiEyBsFLILuyj6YZ64qevtW7G23FYMerr" 180 | body: 181 | encoding: UTF-8 182 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiNXo3RmdvdjRyeHpkNmJSdHZZLWVvQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9teS1vcmRlci83NUQ5LUF4Z20wVWJLa2lFeUJzRkxJTHV5ajZZWjY0cWV2dFc3RzIzRllNZXJyIiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzIifQ","payload":"","signature":"ZpN4SvsXhFU6Usb0x3Xe1Faj9i-7a5BHc0pfMcqoSx_7QCHmXPlxJCJPLPFY5HxKlNAsBAscluxxEjPfkhCKPfhGBagtFge-M0P7ZxCAPDuxAPOhqDW4eSWa7Uk4Gpqi65siZTb4bVA_0AwrIWXzgxH3vMFFx-Hfvc2fI-CGQWwBO3K5GaSG7eR_Y5uUTxJ_9mROHbL8LWoTzegW5Gno3WAAynQtWIXRip1AiOXpzwA4VKjtjx3LE3BKDN46Vsj49Xd0ds2EEIgf7LSD4MumSb-qd7T7f_FyOSdul08Nl1e0zdD2hbqRD18V8V_llUYnnO-CdHtHLz3wj9IGouR7Gg"}' 183 | headers: 184 | User-Agent: 185 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 186 | Content-Type: 187 | - application/jose+json 188 | Accept-Encoding: 189 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 190 | Accept: 191 | - "*/*" 192 | response: 193 | status: 194 | code: 404 195 | message: Not Found 196 | headers: 197 | Cache-Control: 198 | - public, max-age=0, no-cache 199 | Link: 200 | - <>;rel="index" 201 | Replay-Nonce: 202 | - A1RKyffeQ5VEkjtzYu6z8Q 203 | Date: 204 | - Thu, 10 Oct 2019 03:08:21 GMT 205 | Content-Length: 206 | - '0' 207 | body: 208 | encoding: UTF-8 209 | string: '' 210 | http_version: 211 | recorded_at: Thu, 10 Oct 2019 03:08:21 GMT 212 | recorded_with: VCR 2.9.3 213 | -------------------------------------------------------------------------------- /spec/cassettes/finalize_incomplete_challenge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:19 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:19 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - K6Keaf_hlKfQ82HWm-G-Ag 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:19 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:19 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm5vbmNlIjoiSzZLZWFmX2hsS2ZRODJIV20tRy1BZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiMUNsRExlQWU2dGJucDVtYnZJd3l3bWNLQ2lmYnFRLW8tUjZTQUNVaTFlbyIsInkiOiJnQXBrN2hfR0ZVTjRFOHYwR3BhRTA3R0d2c3AzaUtZS3pxeWYzWE9RUnNrIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"HWsjD7veqG5m1w9_bZphxrG7-fUw_pD9W0TWhmnADTIui1HyzO3pEkTLJzvuohT8ku-nEiv_5vQ8kZ0FA5ZUIg"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/65" 101 | Replay-Nonce: 102 | - NJ-NKUczfaAvKEiLFEyONQ 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:19 GMT 105 | Content-Length: 106 | - '311' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/65", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-256", 119 | "x": "1ClDLeAe6tbnp5mbvIwywmcKCifbqQ-o-R6SACUi1eo", 120 | "y": "gApk7h_GFUN4E8v0GpaE07GGvsp3iKYKzqyf3XOQRsk" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:19 GMT 125 | - request: 126 | method: post 127 | uri: "/order-plz" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm5vbmNlIjoiTkotTktVY3pmYUF2S0VpTEZFeU9OUSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC82NSJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"tOiiCTnwVkE3GOfBrnyYu4MXBgFFc2K7Q8WVySB8O_4SwBcL29jBInRt42oZh3T5hWP2OsnvmLKIwYUH7aNDZA"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 201 143 | message: Created 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Location: 152 | - "/my-order/OIYUGTYBfQtcj2teUTSC9awjYnmCESsv4C3PIHLvOFM" 153 | Replay-Nonce: 154 | - RC_YXTj817HrScAGJDCBwQ 155 | Date: 156 | - Thu, 10 Oct 2019 03:08:19 GMT 157 | Content-Length: 158 | - '382' 159 | body: 160 | encoding: UTF-8 161 | string: |- 162 | { 163 | "status": "pending", 164 | "expires": "2019-10-11T03:08:19Z", 165 | "identifiers": [ 166 | { 167 | "type": "dns", 168 | "value": "example.com" 169 | } 170 | ], 171 | "finalize": "/finalize-order/OIYUGTYBfQtcj2teUTSC9awjYnmCESsv4C3PIHLvOFM", 172 | "authorizations": [ 173 | "/authZ/2HeBGz9uoauxqW-o2jP9UBBw9kMdhf_nwzkkJ9v538U" 174 | ] 175 | } 176 | http_version: 177 | recorded_at: Thu, 10 Oct 2019 03:08:19 GMT 178 | - request: 179 | method: post 180 | uri: "/finalize-order/OIYUGTYBfQtcj2teUTSC9awjYnmCESsv4C3PIHLvOFM" 181 | body: 182 | encoding: UTF-8 183 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm5vbmNlIjoiUkNfWVhUajgxN0hyU2NBR0pEQ0J3USIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9maW5hbGl6ZS1vcmRlci9PSVlVR1RZQmZRdGNqMnRlVVRTQzlhd2pZbm1DRVNzdjRDM1BJSEx2T0ZNIiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNjUifQ","payload":"eyJjc3IiOiJNSUlDaERDQ0FXd0NBUUl3RmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEUnQ2RHhVVE0tdzBsWUV3RHFJVnFMNUxMT3QyWFY0Y0FOTy1oWXJsb3FhR2RBU3hzRWhSTlJyWmxlRk1KWnU4bFVVbFhUX290LUZmMXo3OUxqdjVWNUstYWJhRWc1VzFzZGNpelExai1zQ0J0MDB0Y24xQ2lLWHRHdEtrbHpnT0RFMlFBWDl4ZlZNaU5XaHdhNzE3RmFvRW5LMTIxMFozd1hJVEh6RW12UjYxVXRyRXBOQjJSNWZReG01WWdnQ0FlMjFZdUtpZEo3SHJkUzdEM2ZGeFhaTWlpcEJrWGJoTE0xMkpIcnQ3OVlaTmpFdWdCR3Z6OGRUbGdqOHFNcnNXaGRXYXhVVldjNGpZTWN1b0swTzJGSWY2U3NYbktoSDUtbjI4Q2JmcTNYMmhST2RQZkkwV3pEYm9QZFM4dXNIRUJGZ2VZYU9jdUFXZTJjQlZlbXRKUzNBZ01CQUFHZ0tUQW5CZ2txaGtpRzl3MEJDUTR4R2pBWU1CWUdBMVVkRVFRUE1BMkNDMlY0WVcxd2JHVXVZMjl0TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFCWnBvajZmWFlsekdCNzlaeVBGalFWM19JWF8tcDdNN0lXQ2haWkx1YXQ1SVVNM3N6cFpfWFJZaG5GcVNOUFRUMkV6RWdONHBuNnZZZ0tZQVd3RmF5d19OTExoQ2E1MVlUSEtITHFZME5JYzVjLXN2cTRTblBoYVpyNnBHSTF3MW1sdEFzZ3I1RFJ5bVZjazA2M3BXaS1vMGx3LUdmNDJndWxuZHE0UERHd2lTR0FrbElqXzI4b2tYNWFWVDlmX01xVlZGYkVNSk9FSjhjOVE5Mzdlc09CZmJyc1g3a3VTd3BNMjhENlJsLWhZLXZtdEQyRVc0V0wxOEV5YkhFWFFrblN0ellWSnA3R09nNGpUR0lMbWhUbjEwUTlSQzlfT1NLUENZQXJreXlYd2ROeHVoZ1Zzd0NEa3dHYlk4dUpCZElyVmpCSE5Id0RhWDgweTEzcV85SGgifQ","signature":"kevRne3gCeUE-C0CjutYtXZE7RX2ZEvhDh8FQGiKpCF8uFjBtsd-jaR8bA8qVw7e7V_MbTw9iWCJREenK8rQ9g"}' 184 | headers: 185 | User-Agent: 186 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 187 | Content-Type: 188 | - application/jose+json 189 | Accept-Encoding: 190 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 191 | Accept: 192 | - "*/*" 193 | response: 194 | status: 195 | code: 403 196 | message: Forbidden 197 | headers: 198 | Cache-Control: 199 | - public, max-age=0, no-cache 200 | Content-Type: 201 | - application/problem+json; charset=utf-8 202 | Link: 203 | - <>;rel="index" 204 | Replay-Nonce: 205 | - Gh2bkhOhgRLNv2BrfHz4jQ 206 | Date: 207 | - Thu, 10 Oct 2019 03:08:19 GMT 208 | Content-Length: 209 | - '134' 210 | body: 211 | encoding: UTF-8 212 | string: |- 213 | { 214 | "type": "urn:ietf:params:acme:error:unauthorized", 215 | "detail": "Authorization for \"example.com\" is not status valid", 216 | "status": 403 217 | } 218 | http_version: 219 | recorded_at: Thu, 10 Oct 2019 03:08:19 GMT 220 | recorded_with: VCR 2.9.3 221 | -------------------------------------------------------------------------------- /spec/cassettes/authorization_http_challenge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:09 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:09 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - ENbDqNTO3oKl2BbwCTj_kA 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:09 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:09 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiRU5iRHFOVE8zb0tsMkJid0NUal9rQSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoibjlrYjl4WFFjOHBTZ2EwZFpoYkRqWGR1Y0UyUmkyVGI4c3hraXJ1bGFxbWozNzdZLTR5MTliaEFiaGQ3YjBkUiIsInkiOiJBd0RqRGdzc0Z1LWpZaGs5bGhubXgtaDdYZjZxSU1PSEhPT1FxOTVVdjJmcUtBajBQdmd4ZUZYeVc3TG10VUJsIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"DoCH-M7eQyOqoLbGyYYYHTYwHnX3Zuge-nCtacDAIJuBO-rWfmiuvYbn-fmyPnFSzsiuj3aPkOi4Lp6jCLXY5rLqQCiTD9gGWK6ELp7laSX3kyzfGXhm-pZCAQl_AUBv"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/44" 101 | Replay-Nonce: 102 | - 3HJxdw2_lVu74rrkFv40Ag 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:09 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/44", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "n9kb9xXQc8pSga0dZhbDjXducE2Ri2Tb8sxkirulaqmj377Y-4y19bhAbhd7b0dR", 120 | "y": "AwDjDgssFu-jYhk9lhnmx-h7Xf6qIMOHHOOQq95Uv2fqKAj0PvgxeFXyW7LmtUBl" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:09 GMT 125 | - request: 126 | method: post 127 | uri: "/order-plz" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiM0hKeGR3Ml9sVnU3NHJya0Z2NDBBZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC80NCJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"lZFkbf9XlWJgr8iQo1xVVmWMOnoSeLmNcdrDMk_xxlMMEnw_keHWr5_t9OPajzJwz8rcicxgB_7cPnY1z4Xh-m7S20rXJS8aVZQQyuChuphFXwj6uDL1YS8Nc2w0Mr5q"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 201 143 | message: Created 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Location: 152 | - "/my-order/I780HO_BH9VzHd34b2dLlpL-ff6ikkMD0vUfXOup57I" 153 | Replay-Nonce: 154 | - STgKfagvlS0bJp7m5oQ35g 155 | Date: 156 | - Thu, 10 Oct 2019 03:08:09 GMT 157 | Content-Length: 158 | - '382' 159 | body: 160 | encoding: UTF-8 161 | string: |- 162 | { 163 | "status": "pending", 164 | "expires": "2019-10-11T03:08:09Z", 165 | "identifiers": [ 166 | { 167 | "type": "dns", 168 | "value": "example.com" 169 | } 170 | ], 171 | "finalize": "/finalize-order/I780HO_BH9VzHd34b2dLlpL-ff6ikkMD0vUfXOup57I", 172 | "authorizations": [ 173 | "/authZ/WJA4LOg7RQ30FIpZBVKEIlsrTcHSHjCwGrTmZKdiKj0" 174 | ] 175 | } 176 | http_version: 177 | recorded_at: Thu, 10 Oct 2019 03:08:09 GMT 178 | - request: 179 | method: post 180 | uri: "/authZ/WJA4LOg7RQ30FIpZBVKEIlsrTcHSHjCwGrTmZKdiKj0" 181 | body: 182 | encoding: UTF-8 183 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiU1RnS2ZhZ3ZsUzBiSnA3bTVvUTM1ZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9hdXRoWi9XSkE0TE9nN1JRMzBGSXBaQlZLRUlsc3JUY0hTSGpDd0dyVG1aS2RpS2owIiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNDQifQ","payload":"","signature":"zF0sGePGN2Y0VDkyBi7vYJW4YlMBBHMIPe_b6Ia-PDlz1WIa9s0xYdUXVBr5NoERzF5Fe1fpychAOlCfR0Kg4YAxQy_HTlss7cJh6jY2ZZdTj6ifJpQzv7krsZoHAD5l"}' 184 | headers: 185 | User-Agent: 186 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 187 | Content-Type: 188 | - application/jose+json 189 | Accept-Encoding: 190 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 191 | Accept: 192 | - "*/*" 193 | response: 194 | status: 195 | code: 200 196 | message: OK 197 | headers: 198 | Cache-Control: 199 | - public, max-age=0, no-cache 200 | Content-Type: 201 | - application/json; charset=utf-8 202 | Link: 203 | - <>;rel="index" 204 | Replay-Nonce: 205 | - hjORs94px5RbAEwVjWryJA 206 | Date: 207 | - Thu, 10 Oct 2019 03:08:09 GMT 208 | Content-Length: 209 | - '874' 210 | body: 211 | encoding: UTF-8 212 | string: |- 213 | { 214 | "status": "pending", 215 | "identifier": { 216 | "type": "dns", 217 | "value": "example.com" 218 | }, 219 | "challenges": [ 220 | { 221 | "type": "dns-01", 222 | "url": "/chalZ/2fYgNoiFwVWcqTULeSqdN1ggZt4cYuCVAfVEdsEJaH4", 223 | "token": "DtYTgFfXRLwl8v_d7IVgsAprLTTwROdJ_TAR1dTW4cw", 224 | "status": "pending" 225 | }, 226 | { 227 | "type": "tls-alpn-01", 228 | "url": "/chalZ/bB4q9IzLykUE1Kt6GvkGRa3Xo7X7rTYopVTz4HgtOtE", 229 | "token": "i4kRIFQPxVg_0oT8YJ5umFdeH6wqBf16oUCksoAUWUw", 230 | "status": "pending" 231 | }, 232 | { 233 | "type": "http-01", 234 | "url": "/chalZ/0LKVDwWPXZzqegCfss6nLTVTJcqUMi6DW6TL6hyztXc", 235 | "token": "YS78P5G5H-FKLYzJML10rkHt7gPpIrEl-NC-etZ6T5A", 236 | "status": "pending" 237 | } 238 | ], 239 | "expires": "2019-10-10T04:08:09Z" 240 | } 241 | http_version: 242 | recorded_at: Thu, 10 Oct 2019 03:08:09 GMT 243 | recorded_with: VCR 2.9.3 244 | -------------------------------------------------------------------------------- /spec/cassettes/challenge_key_authorization.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 12 | Accept-Encoding: 13 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 14 | Accept: 15 | - "*/*" 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Cache-Control: 22 | - public, max-age=0, no-cache 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Date: 26 | - Thu, 10 Oct 2019 03:08:25 GMT 27 | Content-Length: 28 | - '386' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | { 33 | "keyChange": "/rollover-account-key", 34 | "meta": { 35 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 36 | }, 37 | "newAccount": "/sign-me-up", 38 | "newNonce": "/nonce-plz", 39 | "newOrder": "/order-plz", 40 | "revokeCert": "/revoke-cert" 41 | } 42 | http_version: 43 | recorded_at: Thu, 10 Oct 2019 03:08:25 GMT 44 | - request: 45 | method: head 46 | uri: "/nonce-plz" 47 | body: 48 | encoding: US-ASCII 49 | string: '' 50 | headers: 51 | User-Agent: 52 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 53 | Accept: 54 | - "*/*" 55 | response: 56 | status: 57 | code: 200 58 | message: OK 59 | headers: 60 | Cache-Control: 61 | - public, max-age=0, no-cache 62 | Link: 63 | - <>;rel="index" 64 | Replay-Nonce: 65 | - 1uet0IOxo1fmy8mHSH9h4A 66 | Date: 67 | - Thu, 10 Oct 2019 03:08:25 GMT 68 | body: 69 | encoding: UTF-8 70 | string: '' 71 | http_version: 72 | recorded_at: Thu, 10 Oct 2019 03:08:25 GMT 73 | - request: 74 | method: post 75 | uri: "/sign-me-up" 76 | body: 77 | encoding: UTF-8 78 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiMXVldDBJT3hvMWZteThtSFNIOWg0QSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiU3dibGZpLXRsQ29uVGdETGowd1U2eWY2VGNZV1NZVjZQSElCR2JVWVdHWktmbnZlcXRBSXNaTnNheUgxS3lINiIsInkiOiJQR3FIMHpKaFRaLWZQS1pab0RXS3lSdFVQS285d2l1ODc1blBDNll6LVZqTVphdmxJbnlZcXViYTZRZFloZXJqIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"19RIwlHj0OByZABM57jm-ap5P64UpD991D0Vtwr4ZECNKjc2HjjCce4H1GxMv9D9XkNJ1iOFy2dfmu9ind8awluNGsxzM2hQVgoU9udfTKQoTNjjsmKHW7of4IsMSY42"}' 79 | headers: 80 | User-Agent: 81 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 82 | Content-Type: 83 | - application/jose+json 84 | Accept-Encoding: 85 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 86 | Accept: 87 | - "*/*" 88 | response: 89 | status: 90 | code: 201 91 | message: Created 92 | headers: 93 | Cache-Control: 94 | - public, max-age=0, no-cache 95 | Content-Type: 96 | - application/json; charset=utf-8 97 | Link: 98 | - <>;rel="index" 99 | Location: 100 | - "/my-account/78" 101 | Replay-Nonce: 102 | - HH3i7erQdQtYF4uZ-Bwd1Q 103 | Date: 104 | - Thu, 10 Oct 2019 03:08:25 GMT 105 | Content-Length: 106 | - '353' 107 | body: 108 | encoding: UTF-8 109 | string: |- 110 | { 111 | "status": "valid", 112 | "contact": [ 113 | "mailto:info@example.com" 114 | ], 115 | "orders": "/list-orderz/78", 116 | "key": { 117 | "kty": "EC", 118 | "crv": "P-384", 119 | "x": "Swblfi-tlConTgDLj0wU6yf6TcYWSYV6PHIBGbUYWGZKfnveqtAIsZNsayH1KyH6", 120 | "y": "PGqH0zJhTZ-fPKZZoDWKyRtUPKo9wiu875nPC6Yz-VjMZavlInyYquba6QdYherj" 121 | } 122 | } 123 | http_version: 124 | recorded_at: Thu, 10 Oct 2019 03:08:25 GMT 125 | - request: 126 | method: post 127 | uri: "/order-plz" 128 | body: 129 | encoding: UTF-8 130 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiSEgzaTdlclFkUXRZRjR1Wi1Cd2QxUSIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9vcmRlci1wbHoiLCJraWQiOiJodHRwczovLzE5Mi4xNjguNTYuOTM6MTQwMDAvbXktYWNjb3VudC83OCJ9","payload":"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoiZXhhbXBsZS5jb20ifV19","signature":"fpepD5SuO7amQBZlPfqQVdGotg3sbChsfzP6gpEwy9YSnbgzE4DanMx6rRIEtWXjHa8_LDu3dAkyPEBK1Dv65sD9spGtLKsknR9HBOjvfrYrG3gSOewCCnwFp1W5ThOQ"}' 131 | headers: 132 | User-Agent: 133 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 134 | Content-Type: 135 | - application/jose+json 136 | Accept-Encoding: 137 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 138 | Accept: 139 | - "*/*" 140 | response: 141 | status: 142 | code: 201 143 | message: Created 144 | headers: 145 | Cache-Control: 146 | - public, max-age=0, no-cache 147 | Content-Type: 148 | - application/json; charset=utf-8 149 | Link: 150 | - <>;rel="index" 151 | Location: 152 | - "/my-order/z2hgt4gxxh4BXCK-dfSBZbGUypaBwkNgcCAWymVOGgA" 153 | Replay-Nonce: 154 | - gJZR_q0rpplZmWoRMZb1-g 155 | Date: 156 | - Thu, 10 Oct 2019 03:08:25 GMT 157 | Content-Length: 158 | - '382' 159 | body: 160 | encoding: UTF-8 161 | string: |- 162 | { 163 | "status": "pending", 164 | "expires": "2019-10-11T03:08:25Z", 165 | "identifiers": [ 166 | { 167 | "type": "dns", 168 | "value": "example.com" 169 | } 170 | ], 171 | "finalize": "/finalize-order/z2hgt4gxxh4BXCK-dfSBZbGUypaBwkNgcCAWymVOGgA", 172 | "authorizations": [ 173 | "/authZ/JClXa41a_GoUO7fIdWgcZgUAeXqFQg5NPRPAPlmjubU" 174 | ] 175 | } 176 | http_version: 177 | recorded_at: Thu, 10 Oct 2019 03:08:25 GMT 178 | - request: 179 | method: post 180 | uri: "/authZ/JClXa41a_GoUO7fIdWgcZgUAeXqFQg5NPRPAPlmjubU" 181 | body: 182 | encoding: UTF-8 183 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiZ0paUl9xMHJwcGxabVdvUk1aYjEtZyIsInVybCI6Imh0dHBzOi8vMTkyLjE2OC41Ni45MzoxNDAwMC9hdXRoWi9KQ2xYYTQxYV9Hb1VPN2ZJZFdnY1pnVUFlWHFGUWc1TlBSUEFQbG1qdWJVIiwia2lkIjoiaHR0cHM6Ly8xOTIuMTY4LjU2LjkzOjE0MDAwL215LWFjY291bnQvNzgifQ","payload":"","signature":"dmRnymnQlLJ8I1e1HcS0jJ28kj3NF0k6pgtomUBtfvBE65CGzdHVBAMFaODwydIvgFBGG1aOKpCBmD-_VYycZPxue5vGy4DbVb9GwQt16R4S6rhAyh5D5fYEMKW4YrTY"}' 184 | headers: 185 | User-Agent: 186 | - AcmeV2::Client v2.0.4 (https://github.com/unixcharles/acme-client) 187 | Content-Type: 188 | - application/jose+json 189 | Accept-Encoding: 190 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 191 | Accept: 192 | - "*/*" 193 | response: 194 | status: 195 | code: 200 196 | message: OK 197 | headers: 198 | Cache-Control: 199 | - public, max-age=0, no-cache 200 | Content-Type: 201 | - application/json; charset=utf-8 202 | Link: 203 | - <>;rel="index" 204 | Replay-Nonce: 205 | - j57YiNaq0dB580yTxcKM9g 206 | Date: 207 | - Thu, 10 Oct 2019 03:08:25 GMT 208 | Content-Length: 209 | - '874' 210 | body: 211 | encoding: UTF-8 212 | string: |- 213 | { 214 | "status": "pending", 215 | "identifier": { 216 | "type": "dns", 217 | "value": "example.com" 218 | }, 219 | "challenges": [ 220 | { 221 | "type": "tls-alpn-01", 222 | "url": "/chalZ/wex9skdk0z1o9Q8JhdqF7OEgznaAuzCcBWcjf8pbUEE", 223 | "token": "iqJmhmRdreeAdLrBNiFoMsidzOICIkcz6WXyxx7bJWY", 224 | "status": "pending" 225 | }, 226 | { 227 | "type": "http-01", 228 | "url": "/chalZ/rbfVhgYP1XAEUnfA7Bv6M8RaHJvacY8_YD937Wjh4gk", 229 | "token": "epEnxX7EFrmkgIXf0TceX7-6KUqmIfMvn3vBzDTXGUA", 230 | "status": "pending" 231 | }, 232 | { 233 | "type": "dns-01", 234 | "url": "/chalZ/lxlgaZmQ-nTpXB2yVqJ8deHx29z0LRVPgd9z17mlys4", 235 | "token": "HpKtXPjeoM1d3sWl7KIde9eR6huX03DWxz2Tx8Z4H08", 236 | "status": "pending" 237 | } 238 | ], 239 | "expires": "2019-10-10T04:08:25Z" 240 | } 241 | http_version: 242 | recorded_at: Thu, 10 Oct 2019 03:08:25 GMT 243 | recorded_with: VCR 2.9.3 244 | --------------------------------------------------------------------------------