├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── .rspec ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── acme-client.gemspec ├── bin ├── console ├── generate_keystash ├── release └── setup ├── lib ├── acme-client.rb └── acme │ ├── client.rb │ └── client │ ├── certificate_request.rb │ ├── certificate_request │ └── ec_key_patch.rb │ ├── chain_identifier.rb │ ├── error.rb │ ├── http_client.rb │ ├── jwk.rb │ ├── jwk │ ├── base.rb │ ├── ecdsa.rb │ ├── hmac.rb │ └── rsa.rb │ ├── resources.rb │ ├── resources │ ├── account.rb │ ├── authorization.rb │ ├── challenges.rb │ ├── challenges │ │ ├── base.rb │ │ ├── dns01.rb │ │ ├── http01.rb │ │ └── unsupported_challenge.rb │ ├── directory.rb │ └── order.rb │ ├── self_sign_certificate.rb │ ├── util.rb │ └── version.rb └── spec ├── account_spec.rb ├── authorization_spec.rb ├── cassettes ├── account_contact_deactivate.yml ├── account_contact_update.yml ├── account_deactivate.yml ├── account_key_change.yml ├── account_reload.yml ├── account_update.yml ├── authorization_challenges.yml ├── authorization_deactivate.yml ├── authorization_dns_challenge.yml ├── authorization_http_challenge.yml ├── authorization_reload.yml ├── certificate_download.yml ├── certificate_download_with_alternative.yml ├── challenge_key_authorization.yml ├── challenge_reload.yml ├── challenge_verify_failure.yml ├── challenge_verify_success.yml ├── client_meta.yml ├── deactivate_authorization.yml ├── directory_endpoint_for.yml ├── directory_meta.yml ├── directory_ratelimit.yml ├── fail_fetch_order.yml ├── fetch_authorization.yml ├── fetch_challenge.yml ├── fetch_order.yml ├── finalize_csr_mismatch.yml ├── finalize_incomplete_challenge.yml ├── finalize_succeed.yml ├── get_nonce.yml ├── load_account_unkown_kid.yml ├── load_account_valid_kid.yml ├── new_account_agree_terms.yml ├── new_account_invalid_external_binding.yml ├── new_account_refuse_terms.yml ├── new_account_valid_external_binding.yml ├── new_order.yml ├── nonce_fail.yml ├── nonce_retry.yml ├── order_authorizations.yml ├── order_certificate_download_fail.yml ├── order_certificate_download_preferred_chain.yml ├── order_certificate_download_sucess.yml ├── order_finalize_fail.yml ├── order_finalize_sucess.yml ├── order_reload.yml ├── order_status.yml ├── registration_agree_terms.yml ├── request_validation.yml └── simpler_identifiers_order.yml ├── certificate_request_spec.rb ├── chain_identifier_spec.rb ├── challenge_spec.rb ├── client_spec.rb ├── directory_spec.rb ├── dns01_spec.rb ├── fixtures ├── certificate_chain.pem └── keystash.yml ├── http01_spec.rb ├── jwk_spec.rb ├── order_spec.rb ├── self_sign_certificate_spec.rb ├── spec_helper.rb ├── support ├── asn1_helper.rb ├── http_helper.rb ├── profile_helper.rb ├── retry_helper.rb ├── ssl_helper.rb └── tls_helper.rb └── util_spec.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | ruby-version: ['3.0', '3.1', '3.2', '3.3', truffleruby] 15 | faraday-version: ['~> 1.10', '~> 2.9'] 16 | env: 17 | FARADAY_VERSION: ${{ matrix.faraday-version }} 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Ruby 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: ${{ matrix.ruby-version }} 24 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 25 | - name: Run tests 26 | run: bundle exec rake spec 27 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --order rand 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## `2.0.21` 2 | 3 | * Add validated attribute to challenges 4 | 5 | ## `2.0.20` 6 | 7 | * Add OrderNotReady exception 8 | 9 | ## `2.0.19` 10 | 11 | * Fix an issue CSR generation. Version should be set to zero according to the spec. It's causing issue with some ACME server implementation. 12 | 13 | ## `2.0.18` 14 | 15 | * Fix an issue public key encoding. `OpenSSL::BN` cause keys with leading zero to fail. 16 | 17 | ## `2.0.17` 18 | 19 | * Fix bug where depending on call order `jws` get generated with the wrong `kid` 20 | 21 | ## `2.0.16` 22 | 23 | * Refactor Directory 24 | * Fix an issue where the client would crash when ACME provider return nonce for directory endpoint 25 | 26 | ## `2.0.15` 27 | 28 | * Also pass connection_options to Faraday for Client#get_nonce 29 | 30 | 31 | ## `2.0.14` 32 | 33 | * Fix Faraday HTTP exceptions leaking out, always raise `Acme::Client::Error` instead 34 | 35 | ## `2.0.13` 36 | 37 | * Add support for External Account Binding 38 | 39 | ## `2.0.12` 40 | 41 | * Update test matrix to current Ruby versions (2.7 to 3.2) 42 | * Support for Faraday retry 2.x 43 | 44 | ## `2.0.11` 45 | 46 | * Add support for error code `AlreadyRevoked` and `BadPublicKey` 47 | 48 | ## `2.0.10` 49 | 50 | * Support for Faraday 1.0 / 2.0 51 | 52 | ## `2.0.9` 53 | 54 | * Support for Ruby 3.0 and Faraday 0.17.x 55 | * Raise when directory is rate limited 56 | 57 | ## `2.0.8` 58 | 59 | * Add support for the keyChange endpoint 60 | 61 | https://tools.ietf.org/html/rfc8555#section-7.3.5 62 | 63 | 64 | ## `2.0.7` 65 | 66 | * Add support for alternate certificate chain 67 | * Change `Link` headers parsing to return array of value. This add support multiple entries at the same `rel` 68 | 69 | ## `2.0.6` 70 | 71 | * Allow Faraday up to `< 2.0` 72 | 73 | ## `2.0.5` 74 | 75 | * Use post-as-get 76 | * Remove deprecated keyAuthorization 77 | 78 | ## `2.0.4` 79 | 80 | * Add an option to retry bad nonce errors 81 | 82 | ## `2.0.3` 83 | 84 | * Do not try to set the body on GET request 85 | 86 | ## `2.0.2` 87 | 88 | * Fix constant lookup on InvalidDirectory 89 | * Forward connection options when fetching nonce 90 | * Fix splats without parenthesis warning 91 | 92 | ## `2.0.1` 93 | 94 | * Properly require URI 95 | 96 | ## `2.0.0` 97 | 98 | * Release of the `ACMEv2` branch 99 | 100 | ## `1.0.0` 101 | 102 | * Development for `ACMEv1` moved into `1.0.x` 103 | 104 | ## `0.6.3` 105 | 106 | * Handle Faraday::ConnectionFailed errors as Timeout error. 107 | 108 | ## `0.6.2` 109 | 110 | * Do not cache error type 111 | 112 | ## `0.6.1` 113 | 114 | * Fix typo in ECDSA curves 115 | 116 | ## `0.6.0` 117 | 118 | * Support external account keys 119 | 120 | ## `0.5.5` 121 | 122 | * Release script fixes. 123 | 124 | ## `0.5.4` 125 | 126 | * Enable ECDSA certificates 127 | 128 | ## `0.5.3` 129 | 130 | * Build release script 131 | 132 | ## `0.5.2` 133 | 134 | * Fix acme error names 135 | * ASN1 parsing improvements 136 | 137 | ## `0.5.1` 138 | 139 | * Set serial number of self-signed certificate 140 | 141 | ## `0.5.0` 142 | 143 | * Allow access to `Acme::Client#endpoint` and `Acme::Client#directory_uri` 144 | * Add `Acme::Client#fetch_authorization` 145 | * Setup cyclic dependency between challenges and their authorization for easier access of either with the other. 146 | * Drop `Acme::Client#challenge_from_hash` and `Acme::Client::Resources::Challenges::Base#to_h` in favor of the new API. 147 | * Delegate `Acme::Client::Resources::Challenges::Base#verify_status` to `Acme::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. 148 | * 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 149 | 150 | ## `0.4.1` 151 | 152 | * Set the X509 version of the self-signed certificate 153 | * Fix requiring of time standard library 154 | 155 | ## `0.4.0` 156 | 157 | * Drop json-jwt dependency, implement JWS on our own 158 | * Drop ActiveSupport dependency 159 | 160 | ## `0.3.7` 161 | 162 | * Simplify internal `require` statements 163 | * Fix usage of json-jwt return value 164 | * Remove usage of deprecated `qualified_const_defined?` 165 | * Add user agent to upstream calls 166 | * Fix gem requiring 167 | * Set CSR version 168 | 169 | ## `0.3.6` 170 | 171 | * Handle non-json errors better 172 | 173 | ## `0.3.5` 174 | 175 | * Handle non protocol related server error 176 | 177 | ## `0.3.4` 178 | 179 | * Make `Acme::Client#challenge_from_hash` more strict with the arguments it receives 180 | 181 | ## `0.3.3` 182 | 183 | * Add new `unsupportedIdentifier` error from acme protocol 184 | 185 | ## `0.3.2` 186 | 187 | * Adds `rejectedIdentifier` error 188 | * Adds `RateLimited` error class 189 | * Clean up gem loading 190 | * Make client connection options configurable 191 | * Add URL to certificate 192 | 193 | ## `0.3.1` 194 | 195 | * Add ability to serialize challenges 196 | 197 | ## `0.3.0` 198 | 199 | * Use ISO8601 format for time parsing 200 | * 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 201 | * Update dns-01 record content to comply with ACME spec 202 | * Fix `SelfSignCertificate#default_not_before` 203 | 204 | ## `0.2.4` 205 | 206 | * Support tls-sni-01 207 | 208 | ## `0.2.3` 209 | 210 | * Support certificate revocation 211 | * Move everything under the `Acme::Client` namespace 212 | * Improved errors 213 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | if faraday_version = ENV['FARADAY_VERSION'] 6 | gem 'faraday', faraday_version 7 | end 8 | 9 | group :development, :test do 10 | gem 'pry' 11 | gem 'ruby-prof', require: false 12 | end 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: [:spec] 7 | -------------------------------------------------------------------------------- /acme-client.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'acme/client/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'acme-client' 7 | spec.version = Acme::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)/}) || f.start_with?('.') } 15 | spec.require_paths = ['lib'] 16 | 17 | spec.required_ruby_version = '>= 2.3.0' 18 | 19 | spec.add_development_dependency 'rake', '~> 13.0' 20 | spec.add_development_dependency 'rspec', '~> 3.9' 21 | spec.add_development_dependency 'vcr', '~> 2.9' 22 | spec.add_development_dependency 'webmock', '~> 3.8' 23 | spec.add_development_dependency 'webrick', '~> 1.7' 24 | 25 | spec.add_runtime_dependency 'base64', '~> 0.2.0' 26 | spec.add_runtime_dependency 'faraday', '>= 1.0', '< 3.0.0' 27 | spec.add_runtime_dependency 'faraday-retry', '>= 1.0', '< 3.0.0' 28 | end 29 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'acme-client' 5 | 6 | require 'pry' 7 | Pry.start 8 | -------------------------------------------------------------------------------- /bin/generate_keystash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'acme-client' 5 | 6 | require File.join(File.dirname(__FILE__), '../spec/support/ssl_helper') 7 | 8 | 9 | SSLHelper::KEYSTASH.generate_keystash!(size: 200) 10 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'acme-client' 5 | 6 | version = Acme::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 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | -------------------------------------------------------------------------------- /lib/acme-client.rb: -------------------------------------------------------------------------------- 1 | require 'acme/client' 2 | -------------------------------------------------------------------------------- /lib/acme/client/certificate_request.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::CertificateRequest 2 | extend Forwardable 3 | 4 | DEFAULT_KEY_LENGTH = 2048 5 | DEFAULT_DIGEST = OpenSSL::Digest::SHA256 6 | SUBJECT_KEYS = { 7 | common_name: 'CN', 8 | country_name: 'C', 9 | organization_name: 'O', 10 | organizational_unit: 'OU', 11 | state_or_province: 'ST', 12 | locality_name: 'L' 13 | }.freeze 14 | 15 | SUBJECT_TYPES = { 16 | 'CN' => OpenSSL::ASN1::UTF8STRING, 17 | 'C' => OpenSSL::ASN1::UTF8STRING, 18 | 'O' => OpenSSL::ASN1::UTF8STRING, 19 | 'OU' => OpenSSL::ASN1::UTF8STRING, 20 | 'ST' => OpenSSL::ASN1::UTF8STRING, 21 | 'L' => OpenSSL::ASN1::UTF8STRING 22 | }.freeze 23 | 24 | attr_reader :private_key, :common_name, :names, :subject 25 | 26 | def_delegators :csr, :to_pem, :to_der 27 | 28 | def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new) 29 | @digest = digest 30 | @private_key = private_key 31 | @subject = normalize_subject(subject) 32 | @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name] 33 | @names = names.to_a.dup 34 | normalize_names 35 | @subject[SUBJECT_KEYS[:common_name]] ||= @common_name 36 | validate_subject 37 | end 38 | 39 | def csr 40 | @csr ||= generate 41 | end 42 | 43 | private 44 | 45 | def generate_private_key 46 | OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH) 47 | end 48 | 49 | def normalize_subject(subject) 50 | @subject = subject.each_with_object({}) do |(key, value), hash| 51 | hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s 52 | end 53 | end 54 | 55 | def normalize_names 56 | if @common_name 57 | @names.unshift(@common_name) unless @names.include?(@common_name) 58 | else 59 | raise ArgumentError, 'No common name and no list of names given' if @names.empty? 60 | @common_name = @names.first 61 | end 62 | end 63 | 64 | def validate_subject 65 | validate_subject_attributes 66 | validate_subject_common_name 67 | end 68 | 69 | def validate_subject_attributes 70 | extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values 71 | return if extra_keys.empty? 72 | raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}" 73 | end 74 | 75 | def validate_subject_common_name 76 | return if @common_name == @subject[SUBJECT_KEYS[:common_name]] 77 | raise ArgumentError, 'Conflicting common name given in arguments and subject' 78 | end 79 | 80 | def generate 81 | OpenSSL::X509::Request.new.tap do |csr| 82 | 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 = 0 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 'acme/client/certificate_request/ec_key_patch' 120 | -------------------------------------------------------------------------------- /lib/acme/client/certificate_request/ec_key_patch.rb: -------------------------------------------------------------------------------- 1 | # Class to handle bug # 2 | class Acme::Client::CertificateRequest::ECKeyPatch < OpenSSL::PKey::EC 3 | alias private? private_key? 4 | alias public? public_key? 5 | end 6 | -------------------------------------------------------------------------------- /lib/acme/client/chain_identifier.rb: -------------------------------------------------------------------------------- 1 | class Acme::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 | -------------------------------------------------------------------------------- /lib/acme/client/error.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Error < StandardError 2 | class Timeout < Acme::Client::Error; end 3 | 4 | class ClientError < Acme::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 | class OrderNotReady < ClientError; end 12 | 13 | class ServerError < Acme::Client::Error; end 14 | class AlreadyRevoked < ServerError; end 15 | class BadCSR < ServerError; end 16 | class BadNonce < ServerError; end 17 | class BadPublicKey < ServerError; end 18 | class BadSignatureAlgorithm < ServerError; end 19 | class InvalidContact < ServerError; end 20 | class UnsupportedContact < ServerError; end 21 | class ExternalAccountRequired < ServerError; end 22 | class AccountDoesNotExist < ServerError; end 23 | class Malformed < ServerError; end 24 | class RateLimited < ServerError; end 25 | class RejectedIdentifier < ServerError; end 26 | class ServerInternal < ServerError; end 27 | class Unauthorized < ServerError; end 28 | class UnsupportedIdentifier < ServerError; end 29 | class UserActionRequired < ServerError; end 30 | class BadRevocationReason < ServerError; end 31 | class Caa < ServerError; end 32 | class Dns < ServerError; end 33 | class Connection < ServerError; end 34 | class Tls < ServerError; end 35 | class IncorrectResponse < ServerError; end 36 | 37 | ACME_ERRORS = { 38 | 'urn:ietf:params:acme:error:alreadyRevoked' => AlreadyRevoked, 39 | 'urn:ietf:params:acme:error:badCSR' => BadCSR, 40 | 'urn:ietf:params:acme:error:badNonce' => BadNonce, 41 | 'urn:ietf:params:acme:error:badPublicKey' => BadPublicKey, 42 | 'urn:ietf:params:acme:error:badSignatureAlgorithm' => BadSignatureAlgorithm, 43 | 'urn:ietf:params:acme:error:invalidContact' => InvalidContact, 44 | 'urn:ietf:params:acme:error:unsupportedContact' => UnsupportedContact, 45 | 'urn:ietf:params:acme:error:externalAccountRequired' => ExternalAccountRequired, 46 | 'urn:ietf:params:acme:error:accountDoesNotExist' => AccountDoesNotExist, 47 | 'urn:ietf:params:acme:error:malformed' => Malformed, 48 | 'urn:ietf:params:acme:error:orderNotReady' => OrderNotReady, 49 | 'urn:ietf:params:acme:error:rateLimited' => RateLimited, 50 | 'urn:ietf:params:acme:error:rejectedIdentifier' => RejectedIdentifier, 51 | 'urn:ietf:params:acme:error:serverInternal' => ServerInternal, 52 | 'urn:ietf:params:acme:error:unauthorized' => Unauthorized, 53 | 'urn:ietf:params:acme:error:unsupportedIdentifier' => UnsupportedIdentifier, 54 | 'urn:ietf:params:acme:error:userActionRequired' => UserActionRequired, 55 | 'urn:ietf:params:acme:error:badRevocationReason' => BadRevocationReason, 56 | 'urn:ietf:params:acme:error:caa' => Caa, 57 | 'urn:ietf:params:acme:error:dns' => Dns, 58 | 'urn:ietf:params:acme:error:connection' => Connection, 59 | 'urn:ietf:params:acme:error:tls' => Tls, 60 | 'urn:ietf:params:acme:error:incorrectResponse' => IncorrectResponse 61 | } 62 | end 63 | -------------------------------------------------------------------------------- /lib/acme/client/http_client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Acme::Client::HTTPClient 4 | # Creates and returns a new HTTP client, with default settings. 5 | # 6 | # @param url [URI:HTTPS] 7 | # @param options [Hash] 8 | # @return [Faraday::Connection] 9 | def self.new_connection(url:, options: {}) 10 | Faraday.new(url, options) do |configuration| 11 | configuration.use Acme::Client::HTTPClient::ErrorMiddleware 12 | 13 | yield(configuration) if block_given? 14 | 15 | configuration.headers[:user_agent] = Acme::Client::USER_AGENT 16 | configuration.adapter Faraday.default_adapter 17 | end 18 | end 19 | 20 | # Creates and returns a new HTTP client designed for the Acme-protocol, with default settings. 21 | # 22 | # @param url [URI:HTTPS] 23 | # @param client [Acme::Client] 24 | # @param mode [Symbol] 25 | # @param options [Hash] 26 | # @param bad_nonce_retry [Integer] 27 | # @return [Faraday::Connection] 28 | def self.new_acme_connection(url:, client:, mode:, options: {}, bad_nonce_retry: 0) 29 | new_connection(url: url, options: options) do |configuration| 30 | if bad_nonce_retry > 0 31 | configuration.request(:retry, 32 | max: bad_nonce_retry, 33 | methods: Faraday::Connection::METHODS, 34 | exceptions: [Acme::Client::Error::BadNonce]) 35 | end 36 | 37 | configuration.use Acme::Client::HTTPClient::AcmeMiddleware, client: client, mode: mode 38 | 39 | yield(configuration) if block_given? 40 | end 41 | end 42 | 43 | # ErrorMiddleware ensures the HTTP Client would not raise exceptions outside the Acme namespace. 44 | # 45 | # Exceptions are rescued and re-packaged as Acme exceptions. 46 | class ErrorMiddleware < Faraday::Middleware 47 | # Implements the Rack-alike Faraday::Middleware interface. 48 | def call(env) 49 | @app.call(env) 50 | rescue Faraday::TimeoutError, Faraday::ConnectionFailed 51 | raise Acme::Client::Error::Timeout 52 | end 53 | end 54 | 55 | # AcmeMiddleware implements the Acme-protocol requirements for JWK requests. 56 | class AcmeMiddleware < Faraday::Middleware 57 | attr_reader :env, :response, :client 58 | 59 | CONTENT_TYPE = 'application/jose+json' 60 | 61 | def initialize(app, options) 62 | super(app) 63 | @client = options.fetch(:client) 64 | @mode = options.fetch(:mode) 65 | end 66 | 67 | def call(env) 68 | @env = env 69 | @env[:request_headers]['Content-Type'] = CONTENT_TYPE 70 | 71 | if @env.method != :get 72 | @env.body = client.jwk.jws(header: jws_header, payload: env.body) 73 | end 74 | 75 | @app.call(env).on_complete { |response_env| on_complete(response_env) } 76 | end 77 | 78 | def on_complete(env) 79 | @env = env 80 | 81 | raise_on_not_found! 82 | store_nonce 83 | env.body = decode_body 84 | env.response_headers['Link'] = decode_link_headers 85 | 86 | return if env.success? 87 | 88 | raise_on_error! 89 | end 90 | 91 | private 92 | 93 | def jws_header 94 | headers = { nonce: pop_nonce, url: env.url.to_s } 95 | headers[:kid] = client.kid if @mode == :kid 96 | headers 97 | end 98 | 99 | def raise_on_not_found! 100 | raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404 101 | end 102 | 103 | def raise_on_error! 104 | raise error_class, error_message 105 | end 106 | 107 | def error_message 108 | if env.body.is_a? Hash 109 | env.body['detail'] 110 | else 111 | "Error message: #{env.body}" 112 | end 113 | end 114 | 115 | def error_class 116 | Acme::Client::Error::ACME_ERRORS.fetch(error_name, Acme::Client::Error) 117 | end 118 | 119 | def error_name 120 | return unless env.body.is_a?(Hash) 121 | return unless env.body.key?('type') 122 | env.body['type'] 123 | end 124 | 125 | def decode_body 126 | content_type = env.response_headers['Content-Type'].to_s 127 | 128 | if content_type.start_with?('application/json', 'application/problem+json') 129 | JSON.load(env.body) 130 | else 131 | env.body 132 | end 133 | end 134 | 135 | def decode_link_headers 136 | return unless env.response_headers.key?('Link') 137 | link_header = env.response_headers['Link'] 138 | Acme::Client::Util.decode_link_headers(link_header) 139 | end 140 | 141 | def store_nonce 142 | nonce = env.response_headers['replay-nonce'] 143 | nonces << nonce if nonce 144 | end 145 | 146 | def pop_nonce 147 | if nonces.empty? 148 | get_nonce 149 | end 150 | 151 | nonces.pop 152 | end 153 | 154 | def get_nonce 155 | client.get_nonce 156 | end 157 | 158 | def nonces 159 | client.nonces 160 | end 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /lib/acme/client/jwk.rb: -------------------------------------------------------------------------------- 1 | module Acme::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 | Acme::Client::JWK::RSA.new(private_key) 11 | when OpenSSL::PKey::EC 12 | Acme::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 'acme/client/jwk/base' 20 | require 'acme/client/jwk/rsa' 21 | require 'acme/client/jwk/ecdsa' 22 | require 'acme/client/jwk/hmac' 23 | -------------------------------------------------------------------------------- /lib/acme/client/jwk/base.rb: -------------------------------------------------------------------------------- 1 | class Acme::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 = Acme::Client::Util.urlsafe_base64(header.to_json) 20 | encoded_payload = Acme::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 = Acme::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 | Acme::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 | -------------------------------------------------------------------------------- /lib/acme/client/jwk/ecdsa.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::JWK::ECDSA < Acme::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: Acme::Client::Util.urlsafe_base64(coordinates[:x]), 54 | y: Acme::Client::Util.urlsafe_base64(coordinates[:y]) 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 | byte_size = (@private_key.group.degree + 7) / 8 77 | 78 | # Binary R/S values 79 | r, s = bns.map { |bn| bn.to_s(2).rjust(byte_size, "\x00") } 80 | 81 | # JWS wants raw R/S concatenated. 82 | [r, s].join 83 | end 84 | 85 | private 86 | 87 | def coordinates 88 | @coordinates ||= begin 89 | hex = public_key.to_bn.to_s(16) 90 | data_len = hex.length - 2 91 | hex_x = hex[2, data_len / 2] 92 | hex_y = hex[2 + data_len / 2, data_len / 2] 93 | 94 | { 95 | x: [hex_x].pack('H*'), 96 | y: [hex_y].pack('H*') 97 | } 98 | end 99 | end 100 | 101 | def public_key 102 | @private_key.public_key 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/acme/client/jwk/hmac.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::Client::JWK::HMAC < Acme::Client::JWK::Base 4 | # Instantiate a new HMAC JWS. 5 | # 6 | # key - A string. 7 | # 8 | # Returns nothing. 9 | def initialize(key) 10 | @key = key 11 | end 12 | 13 | # Sign a message with the private key. 14 | # 15 | # message - A String message to sign. 16 | # 17 | # Returns a String signature. 18 | def sign(message) 19 | OpenSSL::HMAC.digest('SHA256', @key, message) 20 | end 21 | 22 | # The name of the algorithm as needed for the `alg` member of a JWS object. 23 | # 24 | # Returns a String. 25 | def jwa_alg 26 | # https://tools.ietf.org/html/rfc7518#section-3.1 27 | # HMAC using SHA-256 28 | 'HS256' 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/acme/client/jwk/rsa.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::JWK::RSA < Acme::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: Acme::Client::Util.urlsafe_base64(public_key.e.to_s(2)), 24 | kty: 'RSA', 25 | n: Acme::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 | -------------------------------------------------------------------------------- /lib/acme/client/resources.rb: -------------------------------------------------------------------------------- 1 | module Acme::Client::Resources; end 2 | 3 | require 'acme/client/resources/directory' 4 | require 'acme/client/resources/account' 5 | require 'acme/client/resources/order' 6 | require 'acme/client/resources/authorization' 7 | require 'acme/client/resources/challenges' 8 | -------------------------------------------------------------------------------- /lib/acme/client/resources/account.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::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 | -------------------------------------------------------------------------------- /lib/acme/client/resources/authorization.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::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?(Acme::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?(Acme::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', nil), 60 | error: attributes['error'] 61 | } 62 | Acme::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 | -------------------------------------------------------------------------------- /lib/acme/client/resources/challenges.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Acme::Client::Resources::Challenges 4 | require 'acme/client/resources/challenges/base' 5 | require 'acme/client/resources/challenges/http01' 6 | require 'acme/client/resources/challenges/dns01' 7 | require 'acme/client/resources/challenges/unsupported_challenge' 8 | 9 | CHALLENGE_TYPES = { 10 | 'http-01' => Acme::Client::Resources::Challenges::HTTP01, 11 | 'dns-01' => Acme::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/acme/client/resources/challenges/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::Client::Resources::Challenges::Base 4 | attr_reader :status, :url, :token, :error, :validated 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, validated: validated } 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, validated: nil) 44 | @status = status 45 | @url = url 46 | @token = token 47 | @error = error 48 | @validated = validated 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/acme/client/resources/challenges/dns01.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base 4 | CHALLENGE_TYPE = 'dns-01'.freeze 5 | RECORD_NAME = '_acme-challenge'.freeze 6 | RECORD_TYPE = 'TXT'.freeze 7 | 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 | Acme::Client::Util.urlsafe_base64(DIGEST.digest(key_authorization)) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/acme/client/resources/challenges/http01.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base 4 | CHALLENGE_TYPE = 'http-01'.freeze 5 | CONTENT_TYPE = 'text/plain'.freeze 6 | 7 | def content_type 8 | CONTENT_TYPE 9 | end 10 | 11 | def file_content 12 | key_authorization 13 | end 14 | 15 | def filename 16 | ".well-known/acme-challenge/#{token}" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/acme/client/resources/challenges/unsupported_challenge.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::Resources::Challenges::Unsupported < Acme::Client::Resources::Challenges::Base 2 | end 3 | -------------------------------------------------------------------------------- /lib/acme/client/resources/directory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::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(client, **arguments) 21 | @client = client 22 | assign_attributes(**arguments) 23 | end 24 | 25 | def endpoint_for(key) 26 | @directory.fetch(key) do |missing_key| 27 | raise Acme::Client::Error::UnsupportedOperation, 28 | "Directory at #{@url} does not include `#{missing_key}`" 29 | end 30 | end 31 | 32 | def terms_of_service 33 | meta[DIRECTORY_META[:terms_of_service]] 34 | end 35 | 36 | def website 37 | meta[DIRECTORY_META[:website]] 38 | end 39 | 40 | def caa_identities 41 | meta[DIRECTORY_META[:caa_identities]] 42 | end 43 | 44 | def external_account_required 45 | meta[DIRECTORY_META[:external_account_required]] 46 | end 47 | 48 | def meta 49 | @directory[:meta] 50 | end 51 | 52 | private 53 | 54 | def assign_attributes(directory:) 55 | @directory = {} 56 | @directory[:meta] = directory.delete('meta') 57 | DIRECTORY_RESOURCES.each do |key, entry| 58 | @directory[key] = URI(directory[entry]) if directory[entry] 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/acme/client/resources/order.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Acme::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 Acme::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 | -------------------------------------------------------------------------------- /lib/acme/client/self_sign_certificate.rb: -------------------------------------------------------------------------------- 1 | class Acme::Client::SelfSignCertificate 2 | attr_reader :private_key, :subject_alt_names, :not_before, :not_after 3 | 4 | extend Forwardable 5 | def_delegators :certificate, :to_pem, :to_der 6 | 7 | def initialize(subject_alt_names:, not_before: default_not_before, not_after: default_not_after, private_key: generate_private_key) 8 | @private_key = private_key 9 | @subject_alt_names = subject_alt_names 10 | @not_before = not_before 11 | @not_after = not_after 12 | end 13 | 14 | def certificate 15 | @certificate ||= begin 16 | certificate = generate_certificate 17 | 18 | extension_factory = generate_extension_factory(certificate) 19 | subject_alt_name_entry = subject_alt_names.map { |d| "DNS: #{d}" }.join(',') 20 | subject_alt_name_extension = extension_factory.create_extension('subjectAltName', subject_alt_name_entry) 21 | certificate.add_extension(subject_alt_name_extension) 22 | 23 | certificate.sign(private_key, digest) 24 | end 25 | end 26 | 27 | private 28 | 29 | def generate_private_key 30 | OpenSSL::PKey::RSA.new(2048) 31 | end 32 | 33 | def default_not_before 34 | Time.now - 3600 35 | end 36 | 37 | def default_not_after 38 | Time.now + 30 * 24 * 3600 39 | end 40 | 41 | def digest 42 | OpenSSL::Digest::SHA256.new 43 | end 44 | 45 | def generate_certificate 46 | certificate = OpenSSL::X509::Certificate.new 47 | certificate.not_before = not_before 48 | certificate.not_after = not_after 49 | Acme::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/acme/client/util.rb: -------------------------------------------------------------------------------- 1 | module Acme::Client::Util 2 | extend self 3 | 4 | def urlsafe_base64(data) 5 | Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '') 6 | end 7 | 8 | LINK_MATCH = /<(.*?)>\s?;\s?rel="([\w-]+)"/ 9 | 10 | # See RFC 8288 - https://tools.ietf.org/html/rfc8288#section-3 11 | def decode_link_headers(link_header) 12 | link_header.split(',').each_with_object({}) { |entry, hash| 13 | _, link, name = *entry.match(LINK_MATCH) 14 | hash[name] ||= [] 15 | hash[name].push(link) 16 | } 17 | end 18 | 19 | # Sets public key on CSR or cert. 20 | # 21 | # obj - An OpenSSL::X509::Certificate or OpenSSL::X509::Request instance. 22 | # priv - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance. 23 | # 24 | # Returns nothing. 25 | def set_public_key(obj, priv) 26 | case priv 27 | when OpenSSL::PKey::EC 28 | obj.public_key = priv 29 | when OpenSSL::PKey::RSA 30 | obj.public_key = priv.public_key 31 | else 32 | raise ArgumentError, 'priv must be EC or RSA' 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/acme/client/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Acme 4 | class Client 5 | VERSION = '2.0.21'.freeze 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::Client::Resources::Account do 4 | let(:private_key) { generate_private_key } 5 | let(:unregistered_client) do 6 | client = Acme::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 = Acme::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 | -------------------------------------------------------------------------------- /spec/authorization_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Acme::Client::Resources::Authorization do 6 | let(:private_key) { generate_private_key } 7 | let(:client) do 8 | client = Acme::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(Acme::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(Acme::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(Acme::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 | -------------------------------------------------------------------------------- /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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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/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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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_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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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_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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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/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 | - Acme::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 | - Acme::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 | - Acme::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 | -------------------------------------------------------------------------------- /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 | - Acme::Client v2.0.15 (https://github.com/unixcharles/acme-client) 12 | Content-Type: 13 | - application/jose+json 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Cache-Control: 24 | - public, max-age=0, no-cache 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Date: 28 | - Tue, 16 Jan 2024 18:10:15 GMT 29 | Content-Length: 30 | - '396' 31 | body: 32 | encoding: UTF-8 33 | string: |- 34 | { 35 | "keyChange": "/rollover-account-key", 36 | "meta": { 37 | "externalAccountRequired": false, 38 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 39 | }, 40 | "newAccount": "/sign-me-up", 41 | "newNonce": "/nonce-plz", 42 | "newOrder": "/order-plz", 43 | "revokeCert": "/revoke-cert" 44 | } 45 | http_version: 46 | recorded_at: Tue, 16 Jan 2024 18:10:15 GMT 47 | - request: 48 | method: head 49 | uri: "/nonce-plz" 50 | body: 51 | encoding: US-ASCII 52 | string: '' 53 | headers: 54 | User-Agent: 55 | - Acme::Client v2.0.15 (https://github.com/unixcharles/acme-client) 56 | Accept-Encoding: 57 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 58 | Accept: 59 | - "*/*" 60 | response: 61 | status: 62 | code: 200 63 | message: OK 64 | headers: 65 | Cache-Control: 66 | - public, max-age=0, no-cache 67 | Link: 68 | - <>;rel="index" 69 | Replay-Nonce: 70 | - DXS4nYJl18Pid0N5tjRffw 71 | Date: 72 | - Tue, 16 Jan 2024 18:10:15 GMT 73 | body: 74 | encoding: UTF-8 75 | string: '' 76 | http_version: 77 | recorded_at: Tue, 16 Jan 2024 18:10:15 GMT 78 | - request: 79 | method: post 80 | uri: "/sign-me-up" 81 | body: 82 | encoding: UTF-8 83 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiRFhTNG5ZSmwxOFBpZDBONXRqUmZmdyIsInVybCI6Imh0dHBzOi8vMC4wLjAuMDoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiMm5NOHg0aVR4UElLcUZqR01jX3RiY2QxOW1QT1pydld1S3ZpYlU3MGsxWlZwaU9LQ2p2c3N3Yl9OT0JNX29lYiIsInkiOiJHaHFaN3d2dXhRb05VRThJclBkVF9qVkVOQmJqRThjWVBjYnIwMkdrTk0tSHNhanNJWGRrNTYyNXZsNVpVdkhNIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"_Ct6M9duFtXOxtZDWLwrxcVv49z3l2JJCDFGuQQ1HyUvbnBfoVPNuLssJEQ3bXz_Dh-F-W32DTPaOoPAvlIwWOV-WuHZEYuw1DeR8GwGS68cC-xbU2Fbs1c5Fu5bHhU-"}' 84 | headers: 85 | User-Agent: 86 | - Acme::Client v2.0.15 (https://github.com/unixcharles/acme-client) 87 | Content-Type: 88 | - application/jose+json 89 | Accept-Encoding: 90 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 91 | Accept: 92 | - "*/*" 93 | response: 94 | status: 95 | code: 201 96 | message: Created 97 | headers: 98 | Cache-Control: 99 | - public, max-age=0, no-cache 100 | Content-Type: 101 | - application/json; charset=utf-8 102 | Link: 103 | - <>;rel="index" 104 | Location: 105 | - "/my-account/3011c37ad722d86" 106 | Replay-Nonce: 107 | - "-0Wz_OugGSEpZJRBPs8X2g" 108 | Date: 109 | - Tue, 16 Jan 2024 18:10:15 GMT 110 | Content-Length: 111 | - '360' 112 | body: 113 | encoding: UTF-8 114 | string: |- 115 | { 116 | "status": "valid", 117 | "contact": [ 118 | "mailto:info@example.com" 119 | ], 120 | "orders": "/list-orderz/3011c37ad722d86", 121 | "key": { 122 | "kty": "EC", 123 | "crv": "P-384", 124 | "x": "2nM8x4iTxPIKqFjGMc_tbcd19mPOZrvWuKvibU70k1ZVpiOKCjvsswb_NOBM_oeb", 125 | "y": "GhqZ7wvuxQoNUE8IrPdT_jVENBbjE8cYPcbr02GkNM-HsajsIXdk5625vl5ZUvHM" 126 | } 127 | } 128 | http_version: 129 | recorded_at: Tue, 16 Jan 2024 18:10:15 GMT 130 | recorded_with: VCR 2.9.3 131 | -------------------------------------------------------------------------------- /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 | - Acme::Client v2.0.15 (https://github.com/unixcharles/acme-client) 12 | Content-Type: 13 | - application/jose+json 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Cache-Control: 24 | - public, max-age=0, no-cache 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Date: 28 | - Tue, 16 Jan 2024 18:10:51 GMT 29 | Content-Length: 30 | - '396' 31 | body: 32 | encoding: UTF-8 33 | string: |- 34 | { 35 | "keyChange": "/rollover-account-key", 36 | "meta": { 37 | "externalAccountRequired": false, 38 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 39 | }, 40 | "newAccount": "/sign-me-up", 41 | "newNonce": "/nonce-plz", 42 | "newOrder": "/order-plz", 43 | "revokeCert": "/revoke-cert" 44 | } 45 | http_version: 46 | recorded_at: Tue, 16 Jan 2024 18:10:51 GMT 47 | - request: 48 | method: head 49 | uri: "/nonce-plz" 50 | body: 51 | encoding: US-ASCII 52 | string: '' 53 | headers: 54 | User-Agent: 55 | - Acme::Client v2.0.15 (https://github.com/unixcharles/acme-client) 56 | Accept-Encoding: 57 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 58 | Accept: 59 | - "*/*" 60 | response: 61 | status: 62 | code: 200 63 | message: OK 64 | headers: 65 | Cache-Control: 66 | - public, max-age=0, no-cache 67 | Link: 68 | - <>;rel="index" 69 | Replay-Nonce: 70 | - Tuty2P8DM9lQfIoWYI_xIA 71 | Date: 72 | - Tue, 16 Jan 2024 18:10:51 GMT 73 | body: 74 | encoding: UTF-8 75 | string: '' 76 | http_version: 77 | recorded_at: Tue, 16 Jan 2024 18:10:51 GMT 78 | - request: 79 | method: post 80 | uri: "/sign-me-up" 81 | body: 82 | encoding: UTF-8 83 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiVHV0eTJQOERNOWxRZklvV1lJX3hJQSIsInVybCI6Imh0dHBzOi8vMC4wLjAuMDoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiNkZ2SzNfZ3Ywei1mZ250b0VRQWFmUGxpZHVxUXd0QlpsMGdqTDRzc2ltdm5Cd0M4aWxYVlBiLWNNTlBxaFEwaSIsInkiOiJla2lEa29YTVlXTmhvSWwydTRUcEg2U3oyOXlXMkZpMm1qenhBYVRzTGpQczVIMnhQWW1GYlBRRmF1N3EzejVQIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"TD8n-T2m1qE1mo9uZTl7AJax1D1LclTBixqXZmeyQi7xXQjSeGWdtBPpjvE6d0kCSpQNGv6GcBJgYHS-QoK-z-4YQnCDggCMetyn2d9MFeYuqiEhM9XrULchX58ZmiFe"}' 84 | headers: 85 | User-Agent: 86 | - Acme::Client v2.0.15 (https://github.com/unixcharles/acme-client) 87 | Content-Type: 88 | - application/jose+json 89 | Accept-Encoding: 90 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 91 | Accept: 92 | - "*/*" 93 | response: 94 | status: 95 | code: 201 96 | message: Created 97 | headers: 98 | Cache-Control: 99 | - public, max-age=0, no-cache 100 | Content-Type: 101 | - application/json; charset=utf-8 102 | Link: 103 | - <>;rel="index" 104 | Location: 105 | - "/my-account/4530875a31aa979c" 106 | Replay-Nonce: 107 | - wgZvnNCEXH7AO9RaizC3Tw 108 | Date: 109 | - Tue, 16 Jan 2024 18:10:51 GMT 110 | Content-Length: 111 | - '361' 112 | body: 113 | encoding: UTF-8 114 | string: |- 115 | { 116 | "status": "valid", 117 | "contact": [ 118 | "mailto:info@example.com" 119 | ], 120 | "orders": "/list-orderz/4530875a31aa979c", 121 | "key": { 122 | "kty": "EC", 123 | "crv": "P-384", 124 | "x": "6FvK3_gv0z-fgntoEQAafPliduqQwtBZl0gjL4ssimvnBwC8ilXVPb-cMNPqhQ0i", 125 | "y": "ekiDkoXMYWNhoIl2u4TpH6Sz29yW2Fi2mjzxAaTsLjPs5H2xPYmFbPQFau7q3z5P" 126 | } 127 | } 128 | http_version: 129 | recorded_at: Tue, 16 Jan 2024 18:10:51 GMT 130 | recorded_with: VCR 2.9.3 131 | -------------------------------------------------------------------------------- /spec/cassettes/directory_ratelimit.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 | - Acme::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: 403 19 | message: Forbidden 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 | - '212' 29 | body: 30 | encoding: UTF-8 31 | string: |- 32 | {"type": "urn:ietf:params:acme:error:rateLimited", "detail": "Your IP, 156.38.202.48, has been blocked due to extremely high traffic. Once corrected, request a review by emailing unblock-request@letsencrypt.org"} 33 | http_version: 34 | recorded_at: Thu, 10 Oct 2019 03:08:14 GMT 35 | recorded_with: VCR 2.9.3 36 | -------------------------------------------------------------------------------- /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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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/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 | - Acme::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 | - Acme::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 | -------------------------------------------------------------------------------- /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 | - Acme::Client v2.0.16 (https://github.com/unixcharles/acme-client) 12 | Content-Type: 13 | - application/jose+json 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Cache-Control: 24 | - public, max-age=0, no-cache 25 | Content-Type: 26 | - application/json; charset=utf-8 27 | Date: 28 | - Tue, 13 Feb 2024 21:25:24 GMT 29 | Content-Length: 30 | - '396' 31 | body: 32 | encoding: UTF-8 33 | string: |- 34 | { 35 | "keyChange": "/rollover-account-key", 36 | "meta": { 37 | "externalAccountRequired": false, 38 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 39 | }, 40 | "newAccount": "/sign-me-up", 41 | "newNonce": "/nonce-plz", 42 | "newOrder": "/order-plz", 43 | "revokeCert": "/revoke-cert" 44 | } 45 | http_version: 46 | recorded_at: Tue, 13 Feb 2024 21:25:24 GMT 47 | - request: 48 | method: head 49 | uri: "/nonce-plz" 50 | body: 51 | encoding: US-ASCII 52 | string: '' 53 | headers: 54 | User-Agent: 55 | - Acme::Client v2.0.16 (https://github.com/unixcharles/acme-client) 56 | Accept-Encoding: 57 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 58 | Accept: 59 | - "*/*" 60 | response: 61 | status: 62 | code: 200 63 | message: OK 64 | headers: 65 | Cache-Control: 66 | - public, max-age=0, no-cache 67 | Link: 68 | - <>;rel="index" 69 | Replay-Nonce: 70 | - Djh3JqTrVmyiJxHhtMin_Q 71 | Date: 72 | - Tue, 13 Feb 2024 21:25:25 GMT 73 | body: 74 | encoding: UTF-8 75 | string: '' 76 | http_version: 77 | recorded_at: Tue, 13 Feb 2024 21:25:25 GMT 78 | - request: 79 | method: post 80 | uri: "/sign-me-up" 81 | body: 82 | encoding: UTF-8 83 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiRGpoM0pxVHJWbXlpSnhIaHRNaW5fUSIsInVybCI6Imh0dHBzOi8vMC4wLjAuMDoxNDAwMC9zaWduLW1lLXVwIiwiandrIjp7ImNydiI6IlAtMzg0Iiwia3R5IjoiRUMiLCJ4IjoiRE9tcjhsbDJydTlvTW1DWERMeDB5b1pJbDYzN1BLM2VuMUV6enFaaFBKc3RQSnRiVWM4X2c4b1hGV0dpeTBDTiIsInkiOiJmQnBoRHA5LVZLamFubHRnMmpmSTJlWmk4NDRpMk53cmkzMjFDal9zNnJ0UVNkWnZ2R1NoOXg0YVNoWUlPZThUIn19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlfQ","signature":"aZMiCi8pWCjleLBNqs8TeFfcQuDps9WRDqQf-MAp9djKdVn70gxgKVR4YeDCHkPDghe9I_9cXQmDhGTj6FHf-Cusm7vUMA-458WnZ-VDCPxNlpQLky2Le6chPYuB4xfc"}' 84 | headers: 85 | User-Agent: 86 | - Acme::Client v2.0.16 (https://github.com/unixcharles/acme-client) 87 | Content-Type: 88 | - application/jose+json 89 | Accept-Encoding: 90 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 91 | Accept: 92 | - "*/*" 93 | response: 94 | status: 95 | code: 201 96 | message: Created 97 | headers: 98 | Cache-Control: 99 | - public, max-age=0, no-cache 100 | Content-Type: 101 | - application/json; charset=utf-8 102 | Link: 103 | - <>;rel="index" 104 | Location: 105 | - "/my-account/31c1ab2fa9778b24" 106 | Replay-Nonce: 107 | - "-XIlkoPlSCONNXRgQfgL4Q" 108 | Date: 109 | - Tue, 13 Feb 2024 21:25:25 GMT 110 | Content-Length: 111 | - '361' 112 | body: 113 | encoding: UTF-8 114 | string: |- 115 | { 116 | "status": "valid", 117 | "contact": [ 118 | "mailto:info@example.com" 119 | ], 120 | "orders": "/list-orderz/31c1ab2fa9778b24", 121 | "key": { 122 | "kty": "EC", 123 | "crv": "P-384", 124 | "x": "DOmr8ll2ru9oMmCXDLx0yoZIl637PK3en1EzzqZhPJstPJtbUc8_g8oXFWGiy0CN", 125 | "y": "fBphDp9-VKjanltg2jfI2eZi844i2Nwri321Cj_s6rtQSdZvvGSh9x4aShYIOe8T" 126 | } 127 | } 128 | http_version: 129 | recorded_at: Tue, 13 Feb 2024 21:25:25 GMT 130 | - request: 131 | method: get 132 | uri: "" 133 | body: 134 | encoding: US-ASCII 135 | string: '' 136 | headers: 137 | User-Agent: 138 | - Acme::Client v2.0.16 (https://github.com/unixcharles/acme-client) 139 | Content-Type: 140 | - application/jose+json 141 | Accept-Encoding: 142 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 143 | Accept: 144 | - "*/*" 145 | response: 146 | status: 147 | code: 200 148 | message: OK 149 | headers: 150 | Cache-Control: 151 | - public, max-age=0, no-cache 152 | Content-Type: 153 | - application/json; charset=utf-8 154 | Date: 155 | - Tue, 13 Feb 2024 21:25:25 GMT 156 | Content-Length: 157 | - '396' 158 | body: 159 | encoding: UTF-8 160 | string: |- 161 | { 162 | "keyChange": "/rollover-account-key", 163 | "meta": { 164 | "externalAccountRequired": false, 165 | "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" 166 | }, 167 | "newAccount": "/sign-me-up", 168 | "newNonce": "/nonce-plz", 169 | "newOrder": "/order-plz", 170 | "revokeCert": "/revoke-cert" 171 | } 172 | http_version: 173 | recorded_at: Tue, 13 Feb 2024 21:25:25 GMT 174 | - request: 175 | method: head 176 | uri: "/nonce-plz" 177 | body: 178 | encoding: US-ASCII 179 | string: '' 180 | headers: 181 | User-Agent: 182 | - Acme::Client v2.0.16 (https://github.com/unixcharles/acme-client) 183 | Accept-Encoding: 184 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 185 | Accept: 186 | - "*/*" 187 | response: 188 | status: 189 | code: 200 190 | message: OK 191 | headers: 192 | Cache-Control: 193 | - public, max-age=0, no-cache 194 | Link: 195 | - <>;rel="index" 196 | Replay-Nonce: 197 | - gIK0Dsp3b7wF1cLPnuUjKA 198 | Date: 199 | - Tue, 13 Feb 2024 21:25:25 GMT 200 | body: 201 | encoding: UTF-8 202 | string: '' 203 | http_version: 204 | recorded_at: Tue, 13 Feb 2024 21:25:25 GMT 205 | - request: 206 | method: post 207 | uri: "/my-account/31c1ab2fa9778b24" 208 | body: 209 | encoding: UTF-8 210 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsIm5vbmNlIjoiZ0lLMERzcDNiN3dGMWNMUG51VWpLQSIsInVybCI6Imh0dHBzOi8vMC4wLjAuMDoxNDAwMC9teS1hY2NvdW50LzMxYzFhYjJmYTk3NzhiMjQiLCJraWQiOiJodHRwczovLzAuMC4wLjA6MTQwMDAvbXktYWNjb3VudC8zMWMxYWIyZmE5Nzc4YjI0In0","payload":"","signature":"zmGzRq85n3YbFuwo9glhbUwAL_Y6k4NcCKlP1TKvXHa1haQLDH7kJFPiwempsQLIhOtvMcBJrOCOMmoe4VpiPF2pnDPY0qZs8W6YzEKe_7nMXjdnK7QAiDeccjd_bbd8"}' 211 | headers: 212 | User-Agent: 213 | - Acme::Client v2.0.16 (https://github.com/unixcharles/acme-client) 214 | Content-Type: 215 | - application/jose+json 216 | Accept-Encoding: 217 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 218 | Accept: 219 | - "*/*" 220 | response: 221 | status: 222 | code: 200 223 | message: OK 224 | headers: 225 | Cache-Control: 226 | - public, max-age=0, no-cache 227 | Content-Type: 228 | - application/json; charset=utf-8 229 | Link: 230 | - <>;rel="index" 231 | Replay-Nonce: 232 | - DQ4MtcsHzkDSLfq2yeDQsg 233 | Date: 234 | - Tue, 13 Feb 2024 21:25:25 GMT 235 | Content-Length: 236 | - '361' 237 | body: 238 | encoding: UTF-8 239 | string: |- 240 | { 241 | "status": "valid", 242 | "contact": [ 243 | "mailto:info@example.com" 244 | ], 245 | "orders": "/list-orderz/31c1ab2fa9778b24", 246 | "key": { 247 | "kty": "EC", 248 | "crv": "P-384", 249 | "x": "DOmr8ll2ru9oMmCXDLx0yoZIl637PK3en1EzzqZhPJstPJtbUc8_g8oXFWGiy0CN", 250 | "y": "fBphDp9-VKjanltg2jfI2eZi844i2Nwri321Cj_s6rtQSdZvvGSh9x4aShYIOe8T" 251 | } 252 | } 253 | http_version: 254 | recorded_at: Tue, 13 Feb 2024 21:25:25 GMT 255 | recorded_with: VCR 2.9.3 256 | -------------------------------------------------------------------------------- /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 | - Acme::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 | - Acme::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 | - Acme::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/cassettes/new_account_invalid_external_binding.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 | - Acme::Client v2.0.12 (https://github.com/unixcharles/acme-client) 12 | Content-Type: 13 | - application/jose+json 14 | Accept-Encoding: 15 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 16 | Accept: 17 | - "*/*" 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Server: 24 | - nginx 25 | Date: 26 | - Tue, 31 Jan 2023 22:10:06 GMT 27 | Content-Type: 28 | - application/json 29 | Content-Length: 30 | - '645' 31 | Connection: 32 | - keep-alive 33 | Access-Control-Allow-Origin: 34 | - "*" 35 | Strict-Transport-Security: 36 | - max-age=15724800; includeSubDomains 37 | body: 38 | encoding: UTF-8 39 | string: |- 40 | { 41 | "newNonce": "/newNonce", 42 | "newAccount": "/newAccount", 43 | "newOrder": "/newOrder", 44 | "revokeCert": "/revokeCert", 45 | "keyChange": "/keyChange", 46 | "meta": { 47 | "termsOfService": "https://secure.trust-provider.com/repository/docs/Legacy/20221001_Certificate_Subscriber_Agreement_v_2_5_click.pdf", 48 | "website": "https://zerossl.com", 49 | "caaIdentities": ["sectigo.com", "trust-provider.com", "usertrust.com", "comodoca.com", "comodo.com"], 50 | "externalAccountRequired": true 51 | } 52 | } 53 | http_version: 54 | recorded_at: Tue, 31 Jan 2023 22:10:06 GMT 55 | - request: 56 | method: head 57 | uri: "/newNonce" 58 | body: 59 | encoding: US-ASCII 60 | string: '' 61 | headers: 62 | User-Agent: 63 | - Acme::Client v2.0.12 (https://github.com/unixcharles/acme-client) 64 | Accept-Encoding: 65 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 66 | Accept: 67 | - "*/*" 68 | response: 69 | status: 70 | code: 200 71 | message: OK 72 | headers: 73 | Server: 74 | - nginx 75 | Date: 76 | - Tue, 31 Jan 2023 22:10:08 GMT 77 | Content-Type: 78 | - application/octet-stream 79 | Connection: 80 | - keep-alive 81 | Replay-Nonce: 82 | - PaUv7Esm7LPHzke0mDzpw3xD0x4NFYVXEtJws7fNolI 83 | Cache-Control: 84 | - max-age=0, no-cache, no-store 85 | Access-Control-Allow-Origin: 86 | - "*" 87 | Link: 88 | - <>;rel="index" 89 | Strict-Transport-Security: 90 | - max-age=15724800; includeSubDomains 91 | body: 92 | encoding: UTF-8 93 | string: '' 94 | http_version: 95 | recorded_at: Tue, 31 Jan 2023 22:10:08 GMT 96 | - request: 97 | method: post 98 | uri: "/newAccount" 99 | body: 100 | encoding: UTF-8 101 | string: '{"protected":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIm5vbmNlIjoiUGFVdjdFc203TFBIemtlMG1EenB3M3hEMHg0TkZZVlhFdEp3czdmTm9sSSIsInVybCI6Imh0dHBzOi8vYWNtZS56ZXJvc3NsLmNvbS92Mi9EVjkwL25ld0FjY291bnQiLCJqd2siOnsiZSI6IkFRQUIiLCJrdHkiOiJSU0EiLCJuIjoiM1lILUFIZWw2OFRZZkhwSk9NWHQ5UDh6NjVGVGhQZVh0cWNnYkFyZDBvd0V4Vkd2LXZJUjN1c0dCVjdyRDl2RmdQNmZJQlpkanBCN1JZVV94MEhVMG5qdWI1UFZ2UWVkY2FuWUdnTk9KQ3lERi1mRzBaakZDekFJYXdHb2stcGVHTEx3X2lHc3JvV0k2S0tyUDVlcXNYdDlaV2xFMEwwMUlrV3JIUFQ3Z0ZtNWlrcTM5TG54cVphZGhzOENoQmY5T1NYYlR5Z25uVnE0UHpZbXc0YVA4eTNESDk1YXAxbEF3aU05TmZfMnppN3FRQUZka2MxNWhkNTZiYWhsSl9ycVFncDN6LXI2bTV2YkJ3UVgyMzRveG5tRzg0Y1dmWnJQTmhiTkMyS2g1enk2TlRUMGZqOEFSaDBvbGJIczBHdWZILUFCdkc0Wkx6dWNZbEtvRTJiRDd3In19","payload":"eyJjb250YWN0IjpbIm1haWx0bzppbmZvQGV4YW1wbGUuY29tIl0sInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjp0cnVlLCJleHRlcm5hbEFjY291bnRCaW5kaW5nIjp7InByb3RlY3RlZCI6ImV5SmhiR2NpT2lKSVV6STFOaUlzSW10cFpDSTZJbk5zTmpGVlR6ZHNTMmRUTUZaUFUwOHlRbTVST1VFaUxDSjFjbXdpT2lKb2RIUndjem92TDJGamJXVXVlbVZ5YjNOemJDNWpiMjB2ZGpJdlJGWTVNQzl1WlhkQlkyTnZkVzUwSW4wIiwicGF5bG9hZCI6ImV5SmxJam9pUVZGQlFpSXNJbXQwZVNJNklsSlRRU0lzSW00aU9pSXpXVWd0UVVobGJEWTRWRmxtU0hCS1QwMVlkRGxRT0hvMk5VWlVhRkJsV0hSeFkyZGlRWEprTUc5M1JYaFdSM1l0ZGtsU00zVnpSMEpXTjNKRU9YWkdaMUEyWmtsQ1dtUnFjRUkzVWxsVlgzZ3dTRlV3Ym1wMVlqVlFWblpSWldSallXNVpSMmRPVDBwRGVVUkdMV1pITUZwcVJrTjZRVWxoZDBkdmF5MXdaVWRNVEhkZmFVZHpjbTlYU1RaTFMzSlFOV1Z4YzFoME9WcFhiRVV3VERBeFNXdFhja2hRVkRkblJtMDFhV3R4TXpsTWJuaHhXbUZrYUhNNFEyaENaamxQVTFoaVZIbG5ibTVXY1RSUWVsbHRkelJoVURoNU0wUklPVFZoY0RGc1FYZHBUVGxPWmw4eWVtazNjVkZCUm1Scll6RTFhR1ExTm1KaGFHeEtYM0p4VVdkd00zb3Rjalp0TlhaaVFuZFJXREl6Tkc5NGJtMUhPRFJqVjJaYWNsQk9hR0pPUXpKTGFEVjZlVFpPVkZRd1ptbzRRVkpvTUc5c1lraHpNRWQxWmtndFFVSjJSelJhVEhwMVkxbHNTMjlGTW1KRU4zY2lmUSIsInNpZ25hdHVyZSI6ImV2Um56YlBCV3JIemhIa0QzS2hndDJ2SlR5YVZoZXlkNC1NbHJRblBkMEk9In19","signature":"K0wj-1GReDCQBZXcICcLB_9jwfTIGL3-2Qe5dLBp_w7iU9GBdoP0aM2uqt6iKQD_kDrVbFfoAMMuxYL6dkNR15-G_ZFUaPQBydFvi0zF0gLK-LFUNgwa4ccSV8coFBCMlVT8zzoxwcCh4aXkb8-rYcqvRXJsPmlwYegJK3Cjcq0LriSjRgTePBks1gdJASoSqLDqWxO7BGCTHXrr0AG3V1oBcQDbYIqyvoWlT3lDzN6zfzczfS-zEU6-h9x0XMGNfLC2x_siNHNbAXBEeYW1TjNzxyu_x-E5_cQqncQafvBjvjYMQFN34M8fjuK8EiAAcXMFOXZEdLfcvRcSnwK-Vw"}' 102 | headers: 103 | User-Agent: 104 | - Acme::Client v2.0.12 (https://github.com/unixcharles/acme-client) 105 | Content-Type: 106 | - application/jose+json 107 | Accept-Encoding: 108 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 109 | Accept: 110 | - "*/*" 111 | response: 112 | status: 113 | code: 400 114 | message: Bad Request 115 | headers: 116 | Server: 117 | - nginx 118 | Date: 119 | - Tue, 31 Jan 2023 22:10:10 GMT 120 | Content-Type: 121 | - application/problem+json 122 | Content-Length: 123 | - '99' 124 | Connection: 125 | - keep-alive 126 | Replay-Nonce: 127 | - L7Ph5DdMZ31weGODBte1Bb7yP8GBBeXOeDOSNbYFm_0 128 | Cache-Control: 129 | - max-age=0, no-cache, no-store 130 | Access-Control-Allow-Origin: 131 | - "*" 132 | Link: 133 | - <>;rel="index" 134 | Strict-Transport-Security: 135 | - max-age=15724800; includeSubDomains 136 | body: 137 | encoding: UTF-8 138 | string: '{"type":"urn:ietf:params:acme:error:malformed","status":400,"detail":"[External 139 | Account Binding] "}' 140 | http_version: 141 | recorded_at: Tue, 31 Jan 2023 22:10:10 GMT 142 | recorded_with: VCR 2.9.3 143 | -------------------------------------------------------------------------------- /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 | - Acme::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 | - Acme::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 | - Acme::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_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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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_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 | - Acme::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 | - Acme::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 | - Acme::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 | -------------------------------------------------------------------------------- /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 | - Acme::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 | - Acme::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 | - Acme::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/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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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/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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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/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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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/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 | - Acme::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 | - Acme::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 | - Acme::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 | - Acme::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/certificate_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::Client::CertificateRequest do 4 | let(:test_key) { generate_private_key } 5 | 6 | it 'reads the common name from the subject' do 7 | request = Acme::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 = Acme::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 | Acme::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 = Acme::Client::CertificateRequest::SUBJECT_KEYS.each_with_object({}) {|(key, _), hash| 26 | hash[key] = 'example' 27 | } 28 | request = Acme::Client::CertificateRequest.new(private_key: test_key, subject: subject) 29 | 30 | subject = Acme::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 = Acme::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 = Acme::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 = Acme::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 = Acme::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 | Acme::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 | Acme::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 | Acme::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 | Acme::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 = Acme::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 = Acme::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 = Acme::Client::CertificateRequest::SUBJECT_KEYS 114 | subject = subject_keys.each_with_object({}) {|(_, short_name), hash| 115 | hash[short_name] = 'example' 116 | } 117 | request = Acme::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 = Acme::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 = Acme::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.generate('secp384r1') 143 | request = Acme::Client::CertificateRequest.new(common_name: 'example.org', 144 | private_key: ec_key) 145 | 146 | expect(request.csr.verify(ec_key)).to be(true) 147 | end 148 | 149 | it 'generate the request with version zero' do 150 | request = Acme::Client::CertificateRequest.new(common_name: 'example.org', private_key: test_key) 151 | 152 | expect(request.csr.version).to be_zero 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /spec/chain_identifier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::Client::ChainIdentifier do 4 | let(:pem) { open('./spec/fixtures/certificate_chain.pem').read } 5 | let(:issuer_name) { 'Pebble Root CA' } 6 | 7 | subject { Acme::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/challenge_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::Client::Resources::Challenges do 4 | let(:private_key) { generate_private_key } 5 | let(:client) do 6 | client = Acme::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 | expect(http01.validated).to be_nil 22 | end 23 | end 24 | 25 | context 'key_authorization', vcr: { cassette_name: 'challenge_key_authorization' } do 26 | it 'returns a key authorization' do 27 | token, jwk_thumbprint = http01.key_authorization.split('.') 28 | expect(token).to eq(http01.token) 29 | expect(jwk_thumbprint).to be_a(String) 30 | end 31 | end 32 | 33 | context 'request_validation' do 34 | it 'successfully verify the challenge', vcr: { cassette_name: 'challenge_verify_success' } do 35 | serve_once(http01.file_content) do 36 | expect { 37 | expect(http01.request_validation).to be(true) 38 | }.to_not raise_error 39 | 40 | expect { 41 | retry_until(condition: lambda { http01.status != 'pending' }) do 42 | http01.reload 43 | end 44 | }.to_not raise_error 45 | 46 | expect(http01.status).to eq('valid') 47 | expect(http01.validated).not_to be_nil 48 | end 49 | end 50 | 51 | it 'fail to verify the challenge and return the status', vcr: { cassette_name: 'challenge_verify_failure' } do 52 | serve_once("#{http01.file_content}-oops") do 53 | expect { 54 | expect(http01.request_validation).to be(true) 55 | }.to_not raise_error 56 | 57 | expect { 58 | retry_until(condition: lambda { http01.status != 'pending' }) do 59 | http01.reload 60 | end 61 | }.to_not raise_error 62 | 63 | expect(http01.status).to eq('invalid') 64 | expect(http01.error).to_not be_empty 65 | expect(http01.validated).not_to be_nil 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/directory_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::Client::Resources::Directory do 4 | let(:private_key) { generate_private_key } 5 | let(:client) do 6 | client = Acme::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(:directory) { client.directory } 12 | 13 | context 'endpoint_for', vcr: { cassette_name: 'directory_endpoint_for' } do 14 | it { expect(directory.endpoint_for(:new_nonce)).to be_a_kind_of(URI) } 15 | it { expect(directory.endpoint_for(:new_account)).to be_a_kind_of(URI) } 16 | it { expect(directory.endpoint_for(:new_order)).to be_a_kind_of(URI) } 17 | it { expect(directory.endpoint_for(:revoke_certificate)).to be_a_kind_of(URI) } 18 | it { expect(directory.endpoint_for(:key_change)).to be_a_kind_of(URI) } 19 | 20 | context 'when rate limited', vcr: { cassette_name: 'directory_ratelimit' } do 21 | it do 22 | expect { 23 | directory.endpoint_for(:new_order) 24 | }.to raise_error(Acme::Client::Error::RateLimited) 25 | end 26 | end 27 | end 28 | 29 | context 'meta', vcr: { cassette_name: 'directory_meta' } do 30 | it { expect(directory.meta).to be_a(Hash) } 31 | it { expect(directory.terms_of_service).to be_a(String) } 32 | it { expect(directory.external_account_required).to be false } 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/dns01_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Acme::Client::Resources::Challenges::DNS01 do 6 | let(:private_key) { generate_private_key } 7 | let(:client) do 8 | Acme::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 | Acme::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/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 | -------------------------------------------------------------------------------- /spec/http01_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Acme::Client::Resources::Challenges::HTTP01 do 6 | let(:private_key) { generate_private_key } 7 | let(:client) do 8 | Acme::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 | Acme::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/jwk_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::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 | 103 | describe 'key with leading zero bytes in the encoded public key x' do 104 | let(:private_key) { 105 | loop do 106 | key = OpenSSL::PKey::EC.generate('prime256v1') 107 | break key if key.public_key.to_bn.to_s(16)[2..3] == '00' 108 | end 109 | } 110 | 111 | it 'can be encoded correctly' do 112 | jws_s = subject.jws(header: { 'a-header' => 'header-value' }, payload: { 'some' => 'data' }) 113 | jws = JSON.parse(jws_s) 114 | 115 | b64_point_x = JSON.parse(Base64.urlsafe_decode64(jws['protected']))['jwk']['x'] 116 | hex_point_x = Base64.urlsafe_decode64(b64_point_x).unpack('H*').first 117 | 118 | expect(hex_point_x[0..1]).to eq('00') 119 | end 120 | end 121 | end 122 | 123 | def generate_key(klass) 124 | while k = generate_private_key 125 | return k if k.is_a?(klass) 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /spec/order_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::Client::Resources::Order do 4 | let(:private_key) { generate_private_key } 5 | let(:unregistered_client) do 6 | client = Acme::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 = Acme::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 = Acme::Client::CertificateRequest.new(names: %w[example.com]) 33 | expect { order.finalize(csr: csr) }.to raise_error(Acme::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 = Acme::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 = Acme::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(Acme::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(Acme::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(Acme::Client::Resources::Authorization)) 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/self_sign_certificate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::Client::SelfSignCertificate do 4 | it 'generate a self sign certificate' do 5 | self_sign_certificate = Acme::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 = Acme::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 = Acme::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 = Acme::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/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 'acme/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.shuffle 9 | @iter = @keystash.each 10 | end 11 | 12 | def next 13 | @iter.next 14 | rescue StopIteration 15 | raise "Reached the end of the keystash. #{@keystash.size} key is too small. Regenerate with ./bin/generate_keystash." 16 | end 17 | 18 | def generate_keystash!(size:) 19 | @keystash = [] 20 | size.times { @keystash << generate_key } 21 | save 22 | true 23 | end 24 | 25 | private 26 | 27 | def generate_key 28 | case (rand * 4).to_i 29 | when 0 30 | OpenSSL::PKey::RSA.new(2048) 31 | when 1 32 | generate_ecdsa_key('prime256v1') 33 | when 2 34 | generate_ecdsa_key('secp384r1') 35 | when 3 36 | OpenSSL::PKey::RSA.new(2048) 37 | # TODO: ECDSA curve P-521 not allowed at the moment. 38 | # generate_ecdsa_key('secp521r1') 39 | end 40 | end 41 | 42 | def generate_ecdsa_key(curve) 43 | k = OpenSSL::PKey::EC.generate(curve) 44 | Acme::Client::CertificateRequest::ECKeyPatch.new(k) 45 | end 46 | 47 | def load 48 | if File.exist?(KEYSTASH_PATH) 49 | YAML.load_file(KEYSTASH_PATH).shuffle.map do |pem| 50 | begin 51 | OpenSSL::PKey::RSA.new(pem) 52 | rescue StandardError 53 | Acme::Client::CertificateRequest::ECKeyPatch.new(pem) 54 | end 55 | end 56 | else 57 | [] 58 | end 59 | end 60 | 61 | def save 62 | File.write(KEYSTASH_PATH, YAML.dump(@keystash.map(&:to_pem))) 63 | end 64 | end 65 | 66 | KEYSTASH = KeyStash.new 67 | 68 | def generate_private_key 69 | KEYSTASH.next 70 | end 71 | 72 | def generate_csr(common_name, private_key) 73 | request = OpenSSL::X509::Request.new 74 | request.subject = OpenSSL::X509::Name.new( 75 | [ 76 | [ 77 | 'CN', 78 | common_name, 79 | OpenSSL::ASN1::UTF8STRING 80 | ] 81 | ] 82 | ) 83 | 84 | Acme::Client::Util.set_public_key(request, private_key) 85 | request.sign(private_key, OpenSSL::Digest::SHA256.new) 86 | request 87 | end 88 | 89 | # Verify a CSR's signature. 90 | # 91 | # csr - A OpenSSL::X509::Request instance. 92 | # priv - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance. 93 | # 94 | # Returns boolean. 95 | def verify_csr(csr, priv) 96 | case priv 97 | when OpenSSL::PKey::EC 98 | csr.verify(priv) 99 | when OpenSSL::PKey::RSA 100 | csr.verify(priv.public_key) 101 | else 102 | raise ArgumentError, 'priv must be EC or RSA' 103 | end 104 | end 105 | 106 | # Export a private key's public key in DER format. 107 | # 108 | # priv - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance. 109 | # 110 | # Returns a String. 111 | def public_key_to_pem(private_key) 112 | case private_key 113 | when OpenSSL::PKey::EC 114 | # TODO: Ruby 2.7 shenanigans 115 | if OpenSSL::PKey::EC.method_defined?(:to_pem) 116 | private_key.to_pem 117 | else 118 | dup = OpenSSL::PKey::EC.new(private_key.to_der) 119 | dup.private_key = nil 120 | dup.to_pem 121 | end 122 | when OpenSSL::PKey::RSA 123 | private_key.public_key.to_pem 124 | else 125 | raise ArgumentError, 'private_key must be EC or RSA' 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/util_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Acme::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 = Acme::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 | --------------------------------------------------------------------------------