acme-client 2.0.11 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +2 -2
- data/CHANGELOG.md +13 -0
- data/README.md +10 -3
- data/acme-client.gemspec +1 -1
- data/lib/acme/client/http_client.rb +162 -0
- data/lib/acme/client/jwk/hmac.rb +30 -0
- data/lib/acme/client/jwk.rb +1 -0
- data/lib/acme/client/resources/directory.rb +2 -7
- data/lib/acme/client/util.rb +2 -2
- data/lib/acme/client/version.rb +1 -1
- data/lib/acme/client.rb +21 -25
- metadata +17 -10
- data/lib/acme/client/faraday_middleware.rb +0 -111
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6238349e171138af08de444bf08268ab3bebab0c4d0497bee91d9d4a30d397ab
         | 
| 4 | 
            +
              data.tar.gz: 9928049d9997c284cec6d75e12bf6b5f07150459775d8f852adbc09b7c923178
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 8543f742ee8fe3822c8c989a7e5f8b1f75473f01a36d7a2218c44b43ebec55fa538beabc4545c4798a7b35005ad1fddbd0ed59dbae6942413812fae9ce625f92
         | 
| 7 | 
            +
              data.tar.gz: 77cd41773aa293bcb65ebdb9489c500a06b144e0efb2dc5af23baa8dec8c36c1af32fd9f1c97eb77e40eb4a521a9c904448d5765f64dcbe56a67f4907225aaf3
         | 
    
        data/.github/workflows/test.yml
    CHANGED
    
    | @@ -11,8 +11,8 @@ jobs: | |
| 11 11 | 
             
                runs-on: ubuntu-latest
         | 
| 12 12 | 
             
                strategy:
         | 
| 13 13 | 
             
                  matrix:
         | 
| 14 | 
            -
                    ruby-version: ['2. | 
| 15 | 
            -
                    faraday-version: ['~> 1. | 
| 14 | 
            +
                    ruby-version: ['2.7', '3.0', '3.1', '3.2']
         | 
| 15 | 
            +
                    faraday-version: ['~> 1.10', '~> 2.7']
         | 
| 16 16 | 
             
                env:
         | 
| 17 17 | 
             
                  FARADAY_VERSION: ${{ matrix.faraday-version }}
         | 
| 18 18 | 
             
                steps:
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,16 @@ | |
| 1 | 
            +
            ## `2.0.14`
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Fix Faraday HTTP exceptions leaking out, always raise `Acme::Client::Error` instead
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## `2.0.13`
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * Add support for External Account Binding
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ## `2.0.12`
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Update test matrix to current Ruby versions (2.7 to 3.2)
         | 
| 12 | 
            +
            * Support for Faraday retry 2.x
         | 
| 13 | 
            +
             | 
| 1 14 | 
             
            ## `2.0.11`
         | 
| 2 15 |  | 
| 3 16 | 
             
            * Add support for error code `AlreadyRevoked` and `BadPublicKey`
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,7 +1,5 @@ | |
| 1 1 | 
             
            # Acme::Client
         | 
| 2 2 |  | 
| 3 | 
            -
            [](https://travis-ci.org/unixcharles/acme-client)
         | 
| 4 | 
            -
             | 
| 5 3 | 
             
            `acme-client` is a client implementation of the ACMEv2 / [RFC 8555](https://tools.ietf.org/html/rfc8555) protocol in Ruby.
         | 
| 6 4 |  | 
| 7 5 | 
             
            You can find the ACME reference implementations of the [server](https://github.com/letsencrypt/boulder) in Go and the [client](https://github.com/certbot/certbot) in Python.
         | 
| @@ -108,6 +106,15 @@ client.kid | |
| 108 106 | 
             
            => "https://acme-staging-v02.api.letsencrypt.org/acme/acct/000000"
         | 
| 109 107 | 
             
            ```
         | 
| 110 108 |  | 
| 109 | 
            +
            ## External Account Binding support
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            You can use External Account Binding by providing a `external_account_binding` with a `kid` and `hmac_key`.
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            ```ruby
         | 
| 114 | 
            +
            client = Acme::Client.new(private_key: private_key, directory: 'https://acme.zerossl.com/v2/DV90')
         | 
| 115 | 
            +
            account = client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true, external_account_binding: { kid: "your kid", hmac_key: "your hmac key"})
         | 
| 116 | 
            +
            ```
         | 
| 117 | 
            +
             | 
| 111 118 | 
             
            ## Obtaining a certificate
         | 
| 112 119 | 
             
            ### Ordering a certificate
         | 
| 113 120 |  | 
| @@ -237,7 +244,7 @@ To change the key used for an account you can call `#account_key_change` with th | |
| 237 244 | 
             
            ```ruby
         | 
| 238 245 | 
             
            require 'openssl'
         | 
| 239 246 | 
             
            new_private_key = OpenSSL::PKey::RSA.new(4096)
         | 
| 240 | 
            -
            client.account_key_change( | 
| 247 | 
            +
            client.account_key_change(new_private_key: new_private_key)
         | 
| 241 248 | 
             
            ```
         | 
| 242 249 |  | 
| 243 250 | 
             
            ## Requirements
         | 
    
        data/acme-client.gemspec
    CHANGED
    
    | @@ -24,5 +24,5 @@ Gem::Specification.new do |spec| | |
| 24 24 | 
             
              spec.add_development_dependency 'webrick'
         | 
| 25 25 |  | 
| 26 26 | 
             
              spec.add_runtime_dependency 'faraday', '>= 1.0', '< 3.0.0'
         | 
| 27 | 
            -
              spec.add_runtime_dependency 'faraday-retry', ' | 
| 27 | 
            +
              spec.add_runtime_dependency 'faraday-retry', '>= 1.0', '< 3.0.0'
         | 
| 28 28 | 
             
            end
         | 
| @@ -0,0 +1,162 @@ | |
| 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
         | 
| @@ -0,0 +1,30 @@ | |
| 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
         | 
    
        data/lib/acme/client/jwk.rb
    CHANGED
    
    
| @@ -68,13 +68,8 @@ class Acme::Client::Resources::Directory | |
| 68 68 | 
             
              end
         | 
| 69 69 |  | 
| 70 70 | 
             
              def fetch_directory
         | 
| 71 | 
            -
                 | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
                  configuration.adapter Faraday.default_adapter
         | 
| 75 | 
            -
                end
         | 
| 76 | 
            -
                connection.headers[:user_agent] = Acme::Client::USER_AGENT
         | 
| 77 | 
            -
                response = connection.get(@url)
         | 
| 71 | 
            +
                http_client = Acme::Client::HTTPClient.new_acme_connection(url: @directory, options: @connection_options, client: nil, mode: nil)
         | 
| 72 | 
            +
                response = http_client.get(@url)
         | 
| 78 73 | 
             
                response.body
         | 
| 79 74 | 
             
              end
         | 
| 80 75 | 
             
            end
         | 
    
        data/lib/acme/client/util.rb
    CHANGED
    
    | @@ -1,4 +1,6 @@ | |
| 1 1 | 
             
            module Acme::Client::Util
         | 
| 2 | 
            +
              extend self
         | 
| 3 | 
            +
             | 
| 2 4 | 
             
              def urlsafe_base64(data)
         | 
| 3 5 | 
             
                Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
         | 
| 4 6 | 
             
              end
         | 
| @@ -30,6 +32,4 @@ module Acme::Client::Util | |
| 30 32 | 
             
                  raise ArgumentError, 'priv must be EC or RSA'
         | 
| 31 33 | 
             
                end
         | 
| 32 34 | 
             
              end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
              extend self
         | 
| 35 35 | 
             
            end
         | 
    
        data/lib/acme/client/version.rb
    CHANGED
    
    
    
        data/lib/acme/client.rb
    CHANGED
    
    | @@ -14,10 +14,10 @@ module Acme; end | |
| 14 14 | 
             
            class Acme::Client; end
         | 
| 15 15 |  | 
| 16 16 | 
             
            require 'acme/client/version'
         | 
| 17 | 
            +
            require 'acme/client/http_client'
         | 
| 17 18 | 
             
            require 'acme/client/certificate_request'
         | 
| 18 19 | 
             
            require 'acme/client/self_sign_certificate'
         | 
| 19 20 | 
             
            require 'acme/client/resources'
         | 
| 20 | 
            -
            require 'acme/client/faraday_middleware'
         | 
| 21 21 | 
             
            require 'acme/client/jwk'
         | 
| 22 22 | 
             
            require 'acme/client/error'
         | 
| 23 23 | 
             
            require 'acme/client/util'
         | 
| @@ -50,7 +50,8 @@ class Acme::Client | |
| 50 50 |  | 
| 51 51 | 
             
              attr_reader :jwk, :nonces
         | 
| 52 52 |  | 
| 53 | 
            -
              def new_account(contact:, terms_of_service_agreed: nil)
         | 
| 53 | 
            +
              def new_account(contact:, terms_of_service_agreed: nil, external_account_binding: nil)
         | 
| 54 | 
            +
                new_account_endpoint = endpoint_for(:new_account)
         | 
| 54 55 | 
             
                payload = {
         | 
| 55 56 | 
             
                  contact: Array(contact)
         | 
| 56 57 | 
             
                }
         | 
| @@ -59,7 +60,18 @@ class Acme::Client | |
| 59 60 | 
             
                  payload[:termsOfServiceAgreed] = terms_of_service_agreed
         | 
| 60 61 | 
             
                end
         | 
| 61 62 |  | 
| 62 | 
            -
                 | 
| 63 | 
            +
                if external_account_binding
         | 
| 64 | 
            +
                  kid, hmac_key = external_account_binding.values_at(:kid, :hmac_key)
         | 
| 65 | 
            +
                  if kid.nil? || hmac_key.nil?
         | 
| 66 | 
            +
                    raise ArgumentError, 'must specify kid and hmac_key key for external_account_binding'
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  hmac = Acme::Client::JWK::HMAC.new(Base64.urlsafe_decode64(hmac_key))
         | 
| 70 | 
            +
                  external_account_payload = hmac.jws(header: { kid: kid, url: new_account_endpoint }, payload: @jwk)
         | 
| 71 | 
            +
                  payload[:externalAccountBinding] = JSON.parse(external_account_payload)
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                response = post(new_account_endpoint, payload: payload, mode: :jws)
         | 
| 63 75 | 
             
                @kid = response.headers.fetch(:location)
         | 
| 64 76 |  | 
| 65 77 | 
             
                if response.body.nil? || response.body.empty?
         | 
| @@ -211,8 +223,8 @@ class Acme::Client | |
| 211 223 | 
             
              end
         | 
| 212 224 |  | 
| 213 225 | 
             
              def get_nonce
         | 
| 214 | 
            -
                 | 
| 215 | 
            -
                response =  | 
| 226 | 
            +
                http_client = Acme::Client::HTTPClient.new_connection(url: endpoint_for(:new_nonce))
         | 
| 227 | 
            +
                response = http_client.head(nil, nil)
         | 
| 216 228 | 
             
                nonces << response.headers['replay-nonce']
         | 
| 217 229 | 
             
                true
         | 
| 218 230 | 
             
              end
         | 
| @@ -320,28 +332,12 @@ class Acme::Client | |
| 320 332 | 
             
              def connection_for(url:, mode:)
         | 
| 321 333 | 
             
                uri = URI(url)
         | 
| 322 334 | 
             
                endpoint = "#{uri.scheme}://#{uri.hostname}:#{uri.port}"
         | 
| 335 | 
            +
             | 
| 323 336 | 
             
                @connections ||= {}
         | 
| 324 337 | 
             
                @connections[mode] ||= {}
         | 
| 325 | 
            -
                @connections[mode][endpoint] ||= new_acme_connection( | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 328 | 
            -
              def new_acme_connection(endpoint:, mode:)
         | 
| 329 | 
            -
                new_connection(endpoint: endpoint) do |configuration|
         | 
| 330 | 
            -
                  configuration.use Acme::Client::FaradayMiddleware, client: self, mode: mode
         | 
| 331 | 
            -
                end
         | 
| 332 | 
            -
              end
         | 
| 333 | 
            -
             | 
| 334 | 
            -
              def new_connection(endpoint:)
         | 
| 335 | 
            -
                Faraday.new(endpoint, **@connection_options) do |configuration|
         | 
| 336 | 
            -
                  if @bad_nonce_retry > 0
         | 
| 337 | 
            -
                    configuration.request(:retry,
         | 
| 338 | 
            -
                      max: @bad_nonce_retry,
         | 
| 339 | 
            -
                      methods: Faraday::Connection::METHODS,
         | 
| 340 | 
            -
                      exceptions: [Acme::Client::Error::BadNonce])
         | 
| 341 | 
            -
                  end
         | 
| 342 | 
            -
                  yield(configuration) if block_given?
         | 
| 343 | 
            -
                  configuration.adapter Faraday.default_adapter
         | 
| 344 | 
            -
                end
         | 
| 338 | 
            +
                @connections[mode][endpoint] ||= Acme::Client::HTTPClient.new_acme_connection(
         | 
| 339 | 
            +
                  url: URI(endpoint), mode: mode, client: self, options: @connection_options, bad_nonce_retry: @bad_nonce_retry
         | 
| 340 | 
            +
                )
         | 
| 345 341 | 
             
              end
         | 
| 346 342 |  | 
| 347 343 | 
             
              def fetch_chain(response, limit = 10)
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: acme-client
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2.0. | 
| 4 | 
            +
              version: 2.0.14
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Charles Barbier
         | 
| 8 | 
            -
            autorequire:
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2023-06-16 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -118,17 +118,23 @@ dependencies: | |
| 118 118 | 
             
              name: faraday-retry
         | 
| 119 119 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 120 120 | 
             
                requirements:
         | 
| 121 | 
            -
                - - " | 
| 121 | 
            +
                - - ">="
         | 
| 122 122 | 
             
                  - !ruby/object:Gem::Version
         | 
| 123 123 | 
             
                    version: '1.0'
         | 
| 124 | 
            +
                - - "<"
         | 
| 125 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 126 | 
            +
                    version: 3.0.0
         | 
| 124 127 | 
             
              type: :runtime
         | 
| 125 128 | 
             
              prerelease: false
         | 
| 126 129 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 127 130 | 
             
                requirements:
         | 
| 128 | 
            -
                - - " | 
| 131 | 
            +
                - - ">="
         | 
| 129 132 | 
             
                  - !ruby/object:Gem::Version
         | 
| 130 133 | 
             
                    version: '1.0'
         | 
| 131 | 
            -
             | 
| 134 | 
            +
                - - "<"
         | 
| 135 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 136 | 
            +
                    version: 3.0.0
         | 
| 137 | 
            +
            description: 
         | 
| 132 138 | 
             
            email:
         | 
| 133 139 | 
             
            - unixcharles@gmail.com
         | 
| 134 140 | 
             
            executables: []
         | 
| @@ -155,10 +161,11 @@ files: | |
| 155 161 | 
             
            - lib/acme/client/certificate_request/ec_key_patch.rb
         | 
| 156 162 | 
             
            - lib/acme/client/chain_identifier.rb
         | 
| 157 163 | 
             
            - lib/acme/client/error.rb
         | 
| 158 | 
            -
            - lib/acme/client/ | 
| 164 | 
            +
            - lib/acme/client/http_client.rb
         | 
| 159 165 | 
             
            - lib/acme/client/jwk.rb
         | 
| 160 166 | 
             
            - lib/acme/client/jwk/base.rb
         | 
| 161 167 | 
             
            - lib/acme/client/jwk/ecdsa.rb
         | 
| 168 | 
            +
            - lib/acme/client/jwk/hmac.rb
         | 
| 162 169 | 
             
            - lib/acme/client/jwk/rsa.rb
         | 
| 163 170 | 
             
            - lib/acme/client/resources.rb
         | 
| 164 171 | 
             
            - lib/acme/client/resources/account.rb
         | 
| @@ -177,7 +184,7 @@ homepage: http://github.com/unixcharles/acme-client | |
| 177 184 | 
             
            licenses:
         | 
| 178 185 | 
             
            - MIT
         | 
| 179 186 | 
             
            metadata: {}
         | 
| 180 | 
            -
            post_install_message:
         | 
| 187 | 
            +
            post_install_message: 
         | 
| 181 188 | 
             
            rdoc_options: []
         | 
| 182 189 | 
             
            require_paths:
         | 
| 183 190 | 
             
            - lib
         | 
| @@ -192,8 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 192 199 | 
             
                - !ruby/object:Gem::Version
         | 
| 193 200 | 
             
                  version: '0'
         | 
| 194 201 | 
             
            requirements: []
         | 
| 195 | 
            -
            rubygems_version: 3. | 
| 196 | 
            -
            signing_key:
         | 
| 202 | 
            +
            rubygems_version: 3.0.3.1
         | 
| 203 | 
            +
            signing_key: 
         | 
| 197 204 | 
             
            specification_version: 4
         | 
| 198 205 | 
             
            summary: Client for the ACME protocol.
         | 
| 199 206 | 
             
            test_files: []
         | 
| @@ -1,111 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            class Acme::Client::FaradayMiddleware < Faraday::Middleware
         | 
| 4 | 
            -
              attr_reader :env, :response, :client
         | 
| 5 | 
            -
             | 
| 6 | 
            -
              CONTENT_TYPE = 'application/jose+json'
         | 
| 7 | 
            -
             | 
| 8 | 
            -
              def initialize(app, options)
         | 
| 9 | 
            -
                super(app)
         | 
| 10 | 
            -
                @client = options.fetch(:client)
         | 
| 11 | 
            -
                @mode = options.fetch(:mode)
         | 
| 12 | 
            -
              end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              def call(env)
         | 
| 15 | 
            -
                @env = env
         | 
| 16 | 
            -
                @env[:request_headers]['User-Agent'] = Acme::Client::USER_AGENT
         | 
| 17 | 
            -
                @env[:request_headers]['Content-Type'] = CONTENT_TYPE
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                if @env.method != :get
         | 
| 20 | 
            -
                  @env.body = client.jwk.jws(header: jws_header, payload: env.body)
         | 
| 21 | 
            -
                end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                @app.call(env).on_complete { |response_env| on_complete(response_env) }
         | 
| 24 | 
            -
              rescue Faraday::TimeoutError, Faraday::ConnectionFailed
         | 
| 25 | 
            -
                raise Acme::Client::Error::Timeout
         | 
| 26 | 
            -
              end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
              def on_complete(env)
         | 
| 29 | 
            -
                @env = env
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                raise_on_not_found!
         | 
| 32 | 
            -
                store_nonce
         | 
| 33 | 
            -
                env.body = decode_body
         | 
| 34 | 
            -
                env.response_headers['Link'] = decode_link_headers
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                return if env.success?
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                raise_on_error!
         | 
| 39 | 
            -
              end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
              private
         | 
| 42 | 
            -
             | 
| 43 | 
            -
              def jws_header
         | 
| 44 | 
            -
                headers = { nonce: pop_nonce, url: env.url.to_s }
         | 
| 45 | 
            -
                headers[:kid] = client.kid if @mode == :kid
         | 
| 46 | 
            -
                headers
         | 
| 47 | 
            -
              end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
              def raise_on_not_found!
         | 
| 50 | 
            -
                raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
         | 
| 51 | 
            -
              end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
              def raise_on_error!
         | 
| 54 | 
            -
                raise error_class, error_message
         | 
| 55 | 
            -
              end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
              def error_message
         | 
| 58 | 
            -
                if env.body.is_a? Hash
         | 
| 59 | 
            -
                  env.body['detail']
         | 
| 60 | 
            -
                else
         | 
| 61 | 
            -
                  "Error message: #{env.body}"
         | 
| 62 | 
            -
                end
         | 
| 63 | 
            -
              end
         | 
| 64 | 
            -
             | 
| 65 | 
            -
              def error_class
         | 
| 66 | 
            -
                Acme::Client::Error::ACME_ERRORS.fetch(error_name, Acme::Client::Error)
         | 
| 67 | 
            -
              end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
              def error_name
         | 
| 70 | 
            -
                return unless env.body.is_a?(Hash)
         | 
| 71 | 
            -
                return unless env.body.key?('type')
         | 
| 72 | 
            -
                env.body['type']
         | 
| 73 | 
            -
              end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
              def decode_body
         | 
| 76 | 
            -
                content_type = env.response_headers['Content-Type'].to_s
         | 
| 77 | 
            -
             | 
| 78 | 
            -
                if content_type.start_with?('application/json', 'application/problem+json')
         | 
| 79 | 
            -
                  JSON.load(env.body)
         | 
| 80 | 
            -
                else
         | 
| 81 | 
            -
                  env.body
         | 
| 82 | 
            -
                end
         | 
| 83 | 
            -
              end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
              def decode_link_headers
         | 
| 86 | 
            -
                return unless env.response_headers.key?('Link')
         | 
| 87 | 
            -
                link_header = env.response_headers['Link']
         | 
| 88 | 
            -
                Acme::Client::Util.decode_link_headers(link_header)
         | 
| 89 | 
            -
              end
         | 
| 90 | 
            -
             | 
| 91 | 
            -
              def store_nonce
         | 
| 92 | 
            -
                nonce = env.response_headers['replay-nonce']
         | 
| 93 | 
            -
                nonces << nonce if nonce
         | 
| 94 | 
            -
              end
         | 
| 95 | 
            -
             | 
| 96 | 
            -
              def pop_nonce
         | 
| 97 | 
            -
                if nonces.empty?
         | 
| 98 | 
            -
                  get_nonce
         | 
| 99 | 
            -
                end
         | 
| 100 | 
            -
             | 
| 101 | 
            -
                nonces.pop
         | 
| 102 | 
            -
              end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
              def get_nonce
         | 
| 105 | 
            -
                client.get_nonce
         | 
| 106 | 
            -
              end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
              def nonces
         | 
| 109 | 
            -
                client.nonces
         | 
| 110 | 
            -
              end
         | 
| 111 | 
            -
            end
         |