leap_cli 1.9 → 1.9.1
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/lib/leap_cli/commands/new.rb +2 -3
- data/lib/leap_cli/version.rb +1 -1
- metadata +6 -44
- data/vendor/acme-client/lib/acme/client/certificate.rb +0 -30
- data/vendor/acme-client/lib/acme/client/certificate_request.rb +0 -111
- data/vendor/acme-client/lib/acme/client/crypto.rb +0 -98
- data/vendor/acme-client/lib/acme/client/error.rb +0 -16
- data/vendor/acme-client/lib/acme/client/faraday_middleware.rb +0 -123
- data/vendor/acme-client/lib/acme/client/resources/authorization.rb +0 -44
- data/vendor/acme-client/lib/acme/client/resources/challenges/base.rb +0 -43
- data/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb +0 -19
- data/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb +0 -18
- data/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb +0 -24
- data/vendor/acme-client/lib/acme/client/resources/challenges.rb +0 -6
- data/vendor/acme-client/lib/acme/client/resources/registration.rb +0 -37
- data/vendor/acme-client/lib/acme/client/resources.rb +0 -5
- data/vendor/acme-client/lib/acme/client/self_sign_certificate.rb +0 -60
- data/vendor/acme-client/lib/acme/client/version.rb +0 -7
- data/vendor/acme-client/lib/acme/client.rb +0 -122
- data/vendor/acme-client/lib/acme-client.rb +0 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9e4ec8cc1569dc2e96c778ff53c1539c8e34865f
         | 
| 4 | 
            +
              data.tar.gz: 2a2a84cc1d7f152addf10af08ebb36d9f05dface
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ee2957c11ec13d6ffc44ab8b016543f7b1e37f6e9a8f8127e018878cd4db8e9494c2c2309c5ffd70ffe93b08c894fc1f90134ea7f3a3e6b9501623a81ebff757
         | 
| 7 | 
            +
              data.tar.gz: 27a85ee4027b6e79995de7896ddb10530a50abb79d94e4c5b4116445429fca04cffb7f5c6963ab614ec5689f1c8d4bc782e9f0f4767d608df57175b22c7d7b75
         | 
| @@ -84,9 +84,8 @@ module LeapCli; module Commands | |
| 84 84 | 
             
                      assert_bin! 'git'
         | 
| 85 85 | 
             
                      ensure_dir platform_dir
         | 
| 86 86 | 
             
                      Dir.chdir(platform_dir) do
         | 
| 87 | 
            -
                        log :cloning, "leap_platform into #{platform_dir}"
         | 
| 88 | 
            -
                        pty_run "git clone --branch  | 
| 89 | 
            -
                        pty_run 'git submodule update --init'
         | 
| 87 | 
            +
                        log :cloning, "leap_platform:stable into #{platform_dir}"
         | 
| 88 | 
            +
                        pty_run "git clone --branch stable #{DEFAULT_REPO} ."
         | 
| 90 89 | 
             
                      end
         | 
| 91 90 | 
             
                    else
         | 
| 92 91 | 
             
                      bail!
         | 
    
        data/lib/leap_cli/version.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            module LeapCli
         | 
| 2 2 | 
             
              unless defined?(LeapCli::VERSION)
         | 
| 3 | 
            -
                VERSION = '1.9'
         | 
| 3 | 
            +
                VERSION = '1.9.1'
         | 
| 4 4 | 
             
                COMPATIBLE_PLATFORM_VERSION = '0.9'..'0.99'
         | 
| 5 5 | 
             
                SUMMARY = 'Command line interface to the LEAP platform'
         | 
| 6 6 | 
             
                DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: leap_cli
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 1.9.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - LEAP Encryption Access Project
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2017-06-28 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: minitest
         | 
| @@ -73,39 +73,19 @@ dependencies: | |
| 73 73 | 
             
                  - !ruby/object:Gem::Version
         | 
| 74 74 | 
             
                    version: '1.11'
         | 
| 75 75 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 76 | 
            -
              name:  | 
| 76 | 
            +
              name: acme-client
         | 
| 77 77 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 78 78 | 
             
                requirements:
         | 
| 79 79 | 
             
                - - "~>"
         | 
| 80 80 | 
             
                  - !ruby/object:Gem::Version
         | 
| 81 | 
            -
                    version: '0. | 
| 81 | 
            +
                    version: '0.6'
         | 
| 82 82 | 
             
              type: :runtime
         | 
| 83 83 | 
             
              prerelease: false
         | 
| 84 84 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 85 85 | 
             
                requirements:
         | 
| 86 86 | 
             
                - - "~>"
         | 
| 87 87 | 
             
                  - !ruby/object:Gem::Version
         | 
| 88 | 
            -
                    version: '0. | 
| 89 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 90 | 
            -
              name: faraday
         | 
| 91 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 92 | 
            -
                requirements:
         | 
| 93 | 
            -
                - - "~>"
         | 
| 94 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 95 | 
            -
                    version: '0.9'
         | 
| 96 | 
            -
                - - ">="
         | 
| 97 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 98 | 
            -
                    version: 0.9.1
         | 
| 99 | 
            -
              type: :runtime
         | 
| 100 | 
            -
              prerelease: false
         | 
| 101 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 102 | 
            -
                requirements:
         | 
| 103 | 
            -
                - - "~>"
         | 
| 104 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 105 | 
            -
                    version: '0.9'
         | 
| 106 | 
            -
                - - ">="
         | 
| 107 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 108 | 
            -
                    version: 0.9.1
         | 
| 88 | 
            +
                    version: '0.6'
         | 
| 109 89 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 110 90 | 
             
              name: ya2yaml
         | 
| 111 91 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -166,23 +146,6 @@ files: | |
| 166 146 | 
             
            - lib/leap_cli/path.rb
         | 
| 167 147 | 
             
            - lib/leap_cli/util.rb
         | 
| 168 148 | 
             
            - lib/leap_cli/version.rb
         | 
| 169 | 
            -
            - vendor/acme-client/lib/acme-client.rb
         | 
| 170 | 
            -
            - vendor/acme-client/lib/acme/client.rb
         | 
| 171 | 
            -
            - vendor/acme-client/lib/acme/client/certificate.rb
         | 
| 172 | 
            -
            - vendor/acme-client/lib/acme/client/certificate_request.rb
         | 
| 173 | 
            -
            - vendor/acme-client/lib/acme/client/crypto.rb
         | 
| 174 | 
            -
            - vendor/acme-client/lib/acme/client/error.rb
         | 
| 175 | 
            -
            - vendor/acme-client/lib/acme/client/faraday_middleware.rb
         | 
| 176 | 
            -
            - vendor/acme-client/lib/acme/client/resources.rb
         | 
| 177 | 
            -
            - vendor/acme-client/lib/acme/client/resources/authorization.rb
         | 
| 178 | 
            -
            - vendor/acme-client/lib/acme/client/resources/challenges.rb
         | 
| 179 | 
            -
            - vendor/acme-client/lib/acme/client/resources/challenges/base.rb
         | 
| 180 | 
            -
            - vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb
         | 
| 181 | 
            -
            - vendor/acme-client/lib/acme/client/resources/challenges/http01.rb
         | 
| 182 | 
            -
            - vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb
         | 
| 183 | 
            -
            - vendor/acme-client/lib/acme/client/resources/registration.rb
         | 
| 184 | 
            -
            - vendor/acme-client/lib/acme/client/self_sign_certificate.rb
         | 
| 185 | 
            -
            - vendor/acme-client/lib/acme/client/version.rb
         | 
| 186 149 | 
             
            - vendor/base32/lib/base32.rb
         | 
| 187 150 | 
             
            - vendor/certificate_authority/lib/certificate_authority.rb
         | 
| 188 151 | 
             
            - vendor/certificate_authority/lib/certificate_authority/certificate.rb
         | 
| @@ -231,9 +194,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 231 194 | 
             
                  version: '0'
         | 
| 232 195 | 
             
            requirements: []
         | 
| 233 196 | 
             
            rubyforge_project: 
         | 
| 234 | 
            -
            rubygems_version: 2. | 
| 197 | 
            +
            rubygems_version: 2.5.2
         | 
| 235 198 | 
             
            signing_key: 
         | 
| 236 199 | 
             
            specification_version: 4
         | 
| 237 200 | 
             
            summary: Command line interface to the LEAP platform
         | 
| 238 201 | 
             
            test_files: []
         | 
| 239 | 
            -
            has_rdoc: 
         | 
| @@ -1,30 +0,0 @@ | |
| 1 | 
            -
            class Acme::Client::Certificate
         | 
| 2 | 
            -
              extend Forwardable
         | 
| 3 | 
            -
             | 
| 4 | 
            -
              attr_reader :x509, :x509_chain, :request, :private_key, :url
         | 
| 5 | 
            -
             | 
| 6 | 
            -
              def_delegators :x509, :to_pem, :to_der
         | 
| 7 | 
            -
             | 
| 8 | 
            -
              def initialize(certificate, url, chain, request)
         | 
| 9 | 
            -
                @x509 = certificate
         | 
| 10 | 
            -
                @url = url
         | 
| 11 | 
            -
                @x509_chain = chain
         | 
| 12 | 
            -
                @request = request
         | 
| 13 | 
            -
              end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
              def chain_to_pem
         | 
| 16 | 
            -
                x509_chain.map(&:to_pem).join
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              def x509_fullchain
         | 
| 20 | 
            -
                [x509, *x509_chain]
         | 
| 21 | 
            -
              end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              def fullchain_to_pem
         | 
| 24 | 
            -
                x509_fullchain.map(&:to_pem).join
         | 
| 25 | 
            -
              end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
              def common_name
         | 
| 28 | 
            -
                x509.subject.to_a.find { |name, _, _| name == 'CN' }[1]
         | 
| 29 | 
            -
              end
         | 
| 30 | 
            -
            end
         | 
| @@ -1,111 +0,0 @@ | |
| 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 | 
            -
                  csr.public_key = @private_key.public_key
         | 
| 83 | 
            -
                  csr.subject = generate_subject
         | 
| 84 | 
            -
                  csr.version = 2
         | 
| 85 | 
            -
                  add_extension(csr)
         | 
| 86 | 
            -
                  csr.sign @private_key, @digest
         | 
| 87 | 
            -
                end
         | 
| 88 | 
            -
              end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
              def generate_subject
         | 
| 91 | 
            -
                OpenSSL::X509::Name.new(
         | 
| 92 | 
            -
                  @subject.map {|name, value|
         | 
| 93 | 
            -
                    [name, value, SUBJECT_TYPES[name]]
         | 
| 94 | 
            -
                  }
         | 
| 95 | 
            -
                )
         | 
| 96 | 
            -
              end
         | 
| 97 | 
            -
             | 
| 98 | 
            -
              def add_extension(csr)
         | 
| 99 | 
            -
                return if @names.size <= 1
         | 
| 100 | 
            -
             | 
| 101 | 
            -
                extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
         | 
| 102 | 
            -
                  'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
         | 
| 103 | 
            -
                )
         | 
| 104 | 
            -
                csr.add_attribute(
         | 
| 105 | 
            -
                  OpenSSL::X509::Attribute.new(
         | 
| 106 | 
            -
                    'extReq',
         | 
| 107 | 
            -
                    OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])])
         | 
| 108 | 
            -
                  )
         | 
| 109 | 
            -
                )
         | 
| 110 | 
            -
              end
         | 
| 111 | 
            -
            end
         | 
| @@ -1,98 +0,0 @@ | |
| 1 | 
            -
            class Acme::Client::Crypto
         | 
| 2 | 
            -
              attr_reader :private_key
         | 
| 3 | 
            -
             | 
| 4 | 
            -
              def initialize(private_key)
         | 
| 5 | 
            -
                @private_key = private_key
         | 
| 6 | 
            -
              end
         | 
| 7 | 
            -
             | 
| 8 | 
            -
              def generate_signed_jws(header:, payload:)
         | 
| 9 | 
            -
                header = { typ: 'JWT', alg: jws_alg, jwk: jwk }.merge(header)
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                encoded_header = urlsafe_base64(header.to_json)
         | 
| 12 | 
            -
                encoded_payload = urlsafe_base64(payload.to_json)
         | 
| 13 | 
            -
                signature_data = "#{encoded_header}.#{encoded_payload}"
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                signature = private_key.sign digest, signature_data
         | 
| 16 | 
            -
                encoded_signature = urlsafe_base64(signature)
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                {
         | 
| 19 | 
            -
                  protected: encoded_header,
         | 
| 20 | 
            -
                  payload: encoded_payload,
         | 
| 21 | 
            -
                  signature: encoded_signature
         | 
| 22 | 
            -
                }.to_json
         | 
| 23 | 
            -
              end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
              def thumbprint
         | 
| 26 | 
            -
                urlsafe_base64 digest.digest(jwk.to_json)
         | 
| 27 | 
            -
              end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
              def digest
         | 
| 30 | 
            -
                OpenSSL::Digest::SHA256.new
         | 
| 31 | 
            -
              end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
              def urlsafe_base64(data)
         | 
| 34 | 
            -
                Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
         | 
| 35 | 
            -
              end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
              private
         | 
| 38 | 
            -
             | 
| 39 | 
            -
              def jws_alg
         | 
| 40 | 
            -
                { 'RSA' => 'RS256', 'EC' => 'ES256' }.fetch(jwk[:kty])
         | 
| 41 | 
            -
              end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
              def jwk
         | 
| 44 | 
            -
                @jwk ||= case private_key
         | 
| 45 | 
            -
                         when OpenSSL::PKey::RSA
         | 
| 46 | 
            -
                           rsa_jwk
         | 
| 47 | 
            -
                         when OpenSSL::PKey::EC
         | 
| 48 | 
            -
                           ec_jwk
         | 
| 49 | 
            -
                         else
         | 
| 50 | 
            -
                           raise ArgumentError, "Can't handle #{private_key} as private key, only OpenSSL::PKey::RSA and OpenSSL::PKey::EC"
         | 
| 51 | 
            -
                end
         | 
| 52 | 
            -
              end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
              def rsa_jwk
         | 
| 55 | 
            -
                {
         | 
| 56 | 
            -
                  e: urlsafe_base64(public_key.e.to_s(2)),
         | 
| 57 | 
            -
                  kty: 'RSA',
         | 
| 58 | 
            -
                  n: urlsafe_base64(public_key.n.to_s(2))
         | 
| 59 | 
            -
                }
         | 
| 60 | 
            -
              end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
              def ec_jwk
         | 
| 63 | 
            -
                {
         | 
| 64 | 
            -
                  crv: curve_name,
         | 
| 65 | 
            -
                  kty: 'EC',
         | 
| 66 | 
            -
                  x: urlsafe_base64(coordinates[:x].to_s(2)),
         | 
| 67 | 
            -
                  y: urlsafe_base64(coordinates[:y].to_s(2))
         | 
| 68 | 
            -
                }
         | 
| 69 | 
            -
              end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
              def curve_name
         | 
| 72 | 
            -
                {
         | 
| 73 | 
            -
                  'prime256v1' => 'P-256',
         | 
| 74 | 
            -
                  'secp384r1' => 'P-384',
         | 
| 75 | 
            -
                  'secp521r1' => 'P-521'
         | 
| 76 | 
            -
                }.fetch(private_key.group.curve_name) { raise ArgumentError, 'Unknown EC curve' }
         | 
| 77 | 
            -
              end
         | 
| 78 | 
            -
             | 
| 79 | 
            -
              # rubocop:disable Metrics/AbcSize
         | 
| 80 | 
            -
              def coordinates
         | 
| 81 | 
            -
                @coordinates ||= begin
         | 
| 82 | 
            -
                  hex = public_key.to_bn.to_s(16)
         | 
| 83 | 
            -
                  data_len = hex.length - 2
         | 
| 84 | 
            -
                  hex_x = hex[2, data_len / 2]
         | 
| 85 | 
            -
                  hex_y = hex[2 + data_len / 2, data_len / 2]
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                  {
         | 
| 88 | 
            -
                    x: OpenSSL::BN.new([hex_x].pack('H*'), 2),
         | 
| 89 | 
            -
                    y: OpenSSL::BN.new([hex_y].pack('H*'), 2)
         | 
| 90 | 
            -
                  }
         | 
| 91 | 
            -
                end
         | 
| 92 | 
            -
              end
         | 
| 93 | 
            -
              # rubocop:enable Metrics/AbcSize
         | 
| 94 | 
            -
             | 
| 95 | 
            -
              def public_key
         | 
| 96 | 
            -
                @public_key ||= private_key.public_key
         | 
| 97 | 
            -
              end
         | 
| 98 | 
            -
            end
         | 
| @@ -1,16 +0,0 @@ | |
| 1 | 
            -
            class Acme::Client::Error < StandardError
         | 
| 2 | 
            -
              class NotFound < Acme::Client::Error; end
         | 
| 3 | 
            -
              class BadCSR < Acme::Client::Error; end
         | 
| 4 | 
            -
              class BadNonce < Acme::Client::Error; end
         | 
| 5 | 
            -
              class Connection < Acme::Client::Error; end
         | 
| 6 | 
            -
              class Dnssec < Acme::Client::Error; end
         | 
| 7 | 
            -
              class Malformed < Acme::Client::Error; end
         | 
| 8 | 
            -
              class ServerInternal < Acme::Client::Error; end
         | 
| 9 | 
            -
              class Acme::Tls < Acme::Client::Error; end
         | 
| 10 | 
            -
              class Unauthorized < Acme::Client::Error; end
         | 
| 11 | 
            -
              class UnknownHost < Acme::Client::Error; end
         | 
| 12 | 
            -
              class Timeout < Acme::Client::Error; end
         | 
| 13 | 
            -
              class RateLimited < Acme::Client::Error; end
         | 
| 14 | 
            -
              class RejectedIdentifier < Acme::Client::Error; end
         | 
| 15 | 
            -
              class UnsupportedIdentifier < Acme::Client::Error; end
         | 
| 16 | 
            -
            end
         | 
| @@ -1,123 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            class Acme::Client::FaradayMiddleware < Faraday::Middleware
         | 
| 4 | 
            -
              attr_reader :env, :response, :client
         | 
| 5 | 
            -
             | 
| 6 | 
            -
              repo_url = 'https://github.com/unixcharles/acme-client'
         | 
| 7 | 
            -
              USER_AGENT = "Acme::Client v#{Acme::Client::VERSION} (#{repo_url})".freeze
         | 
| 8 | 
            -
             | 
| 9 | 
            -
              def initialize(app, client:)
         | 
| 10 | 
            -
                super(app)
         | 
| 11 | 
            -
                @client = client
         | 
| 12 | 
            -
              end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              def call(env)
         | 
| 15 | 
            -
                @env = env
         | 
| 16 | 
            -
                @env[:request_headers]['User-Agent'] = USER_AGENT
         | 
| 17 | 
            -
                @env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body)
         | 
| 18 | 
            -
                @app.call(env).on_complete { |response_env| on_complete(response_env) }
         | 
| 19 | 
            -
              rescue Faraday::TimeoutError
         | 
| 20 | 
            -
                raise Acme::Client::Error::Timeout
         | 
| 21 | 
            -
              end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              def on_complete(env)
         | 
| 24 | 
            -
                @env = env
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                raise_on_not_found!
         | 
| 27 | 
            -
                store_nonce
         | 
| 28 | 
            -
                env.body = decode_body
         | 
| 29 | 
            -
                env.response_headers['Link'] = decode_link_headers
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                return if env.success?
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                raise_on_error!
         | 
| 34 | 
            -
              end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
              private
         | 
| 37 | 
            -
             | 
| 38 | 
            -
              def raise_on_not_found!
         | 
| 39 | 
            -
                raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
         | 
| 40 | 
            -
              end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
              def raise_on_error!
         | 
| 43 | 
            -
                raise error_class, error_message
         | 
| 44 | 
            -
              end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
              def error_message
         | 
| 47 | 
            -
                if env.body.is_a? Hash
         | 
| 48 | 
            -
                  env.body['detail']
         | 
| 49 | 
            -
                else
         | 
| 50 | 
            -
                  "Error message: #{env.body}"
         | 
| 51 | 
            -
                end
         | 
| 52 | 
            -
              end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
              def error_class
         | 
| 55 | 
            -
                if error_name && !error_name.empty? && Acme::Client::Error.const_defined?(error_name)
         | 
| 56 | 
            -
                  Object.const_get("Acme::Client::Error::#{error_name}")
         | 
| 57 | 
            -
                else
         | 
| 58 | 
            -
                  Acme::Client::Error
         | 
| 59 | 
            -
                end
         | 
| 60 | 
            -
              end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
              def error_name
         | 
| 63 | 
            -
                @error_name ||= begin
         | 
| 64 | 
            -
                  return unless env.body.is_a?(Hash)
         | 
| 65 | 
            -
                  return unless env.body.key?('type')
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                  env.body['type'].gsub('urn:acme:error:', '').split(/[_-]/).map(&:capitalize).join
         | 
| 68 | 
            -
                end
         | 
| 69 | 
            -
              end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
              def decode_body
         | 
| 72 | 
            -
                content_type = env.response_headers['Content-Type']
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                if content_type == 'application/json' || content_type == 'application/problem+json'
         | 
| 75 | 
            -
                  JSON.load(env.body)
         | 
| 76 | 
            -
                else
         | 
| 77 | 
            -
                  env.body
         | 
| 78 | 
            -
                end
         | 
| 79 | 
            -
              end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
              LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/
         | 
| 82 | 
            -
             | 
| 83 | 
            -
              def decode_link_headers
         | 
| 84 | 
            -
                return unless env.response_headers.key?('Link')
         | 
| 85 | 
            -
                link_header = env.response_headers['Link']
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                links = link_header.split(', ').map { |entry|
         | 
| 88 | 
            -
                  _, link, name = *entry.match(LINK_MATCH)
         | 
| 89 | 
            -
                  [name, link]
         | 
| 90 | 
            -
                }
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                Hash[*links.flatten]
         | 
| 93 | 
            -
              end
         | 
| 94 | 
            -
             | 
| 95 | 
            -
              def store_nonce
         | 
| 96 | 
            -
                nonces << env.response_headers['replay-nonce']
         | 
| 97 | 
            -
              end
         | 
| 98 | 
            -
             | 
| 99 | 
            -
              def pop_nonce
         | 
| 100 | 
            -
                if nonces.empty?
         | 
| 101 | 
            -
                  get_nonce
         | 
| 102 | 
            -
                else
         | 
| 103 | 
            -
                  nonces.pop
         | 
| 104 | 
            -
                end
         | 
| 105 | 
            -
              end
         | 
| 106 | 
            -
             | 
| 107 | 
            -
              def get_nonce
         | 
| 108 | 
            -
                response = Faraday.head(env.url, nil, 'User-Agent' => USER_AGENT)
         | 
| 109 | 
            -
                response.headers['replay-nonce']
         | 
| 110 | 
            -
              end
         | 
| 111 | 
            -
             | 
| 112 | 
            -
              def nonces
         | 
| 113 | 
            -
                client.nonces
         | 
| 114 | 
            -
              end
         | 
| 115 | 
            -
             | 
| 116 | 
            -
              def private_key
         | 
| 117 | 
            -
                client.private_key
         | 
| 118 | 
            -
              end
         | 
| 119 | 
            -
             | 
| 120 | 
            -
              def crypto
         | 
| 121 | 
            -
                @crypto ||= Acme::Client::Crypto.new(private_key)
         | 
| 122 | 
            -
              end
         | 
| 123 | 
            -
            end
         | 
| @@ -1,44 +0,0 @@ | |
| 1 | 
            -
            class Acme::Client::Resources::Authorization
         | 
| 2 | 
            -
              HTTP01 = Acme::Client::Resources::Challenges::HTTP01
         | 
| 3 | 
            -
              DNS01 = Acme::Client::Resources::Challenges::DNS01
         | 
| 4 | 
            -
              TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01
         | 
| 5 | 
            -
             | 
| 6 | 
            -
              attr_reader :client, :uri, :domain, :status, :expires, :http01, :dns01, :tls_sni01
         | 
| 7 | 
            -
             | 
| 8 | 
            -
              def initialize(client, uri, response)
         | 
| 9 | 
            -
                @client = client
         | 
| 10 | 
            -
                @uri = uri
         | 
| 11 | 
            -
                assign_attributes(response.body)
         | 
| 12 | 
            -
              end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              def verify_status
         | 
| 15 | 
            -
                response = @client.connection.get(@uri)
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                assign_attributes(response.body)
         | 
| 18 | 
            -
                status
         | 
| 19 | 
            -
              end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
              private
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              def assign_attributes(body)
         | 
| 24 | 
            -
                @expires = Time.iso8601(body['expires']) if body.key? 'expires'
         | 
| 25 | 
            -
                @domain = body['identifier']['value']
         | 
| 26 | 
            -
                @status = body['status']
         | 
| 27 | 
            -
                assign_challenges(body['challenges'])
         | 
| 28 | 
            -
              end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
              def assign_challenges(challenges)
         | 
| 31 | 
            -
                challenges.each do |attributes|
         | 
| 32 | 
            -
                  challenge = case attributes.fetch('type')
         | 
| 33 | 
            -
                              when 'http-01'
         | 
| 34 | 
            -
                                @http01 ||= HTTP01.new(self)
         | 
| 35 | 
            -
                              when 'dns-01'
         | 
| 36 | 
            -
                                @dns01 ||= DNS01.new(self)
         | 
| 37 | 
            -
                              when 'tls-sni-01'
         | 
| 38 | 
            -
                                @tls_sni01 ||= TLSSNI01.new(self)
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                  challenge.assign_attributes(attributes) if challenge
         | 
| 42 | 
            -
                end
         | 
| 43 | 
            -
              end
         | 
| 44 | 
            -
            end
         | 
| @@ -1,43 +0,0 @@ | |
| 1 | 
            -
            class Acme::Client::Resources::Challenges::Base
         | 
| 2 | 
            -
              attr_reader :authorization, :status, :uri, :token, :error
         | 
| 3 | 
            -
             | 
| 4 | 
            -
              def initialize(authorization)
         | 
| 5 | 
            -
                @authorization = authorization
         | 
| 6 | 
            -
              end
         | 
| 7 | 
            -
             | 
| 8 | 
            -
              def client
         | 
| 9 | 
            -
                authorization.client
         | 
| 10 | 
            -
              end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
              def verify_status
         | 
| 13 | 
            -
                authorization.verify_status
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                status
         | 
| 16 | 
            -
              end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
              def request_verification
         | 
| 19 | 
            -
                response = client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key)
         | 
| 20 | 
            -
                response.success?
         | 
| 21 | 
            -
              end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              def assign_attributes(attributes)
         | 
| 24 | 
            -
                @status = attributes.fetch('status', 'pending')
         | 
| 25 | 
            -
                @uri = attributes.fetch('uri')
         | 
| 26 | 
            -
                @token = attributes.fetch('token')
         | 
| 27 | 
            -
                @error = attributes['error']
         | 
| 28 | 
            -
              end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
              private
         | 
| 31 | 
            -
             | 
| 32 | 
            -
              def challenge_type
         | 
| 33 | 
            -
                self.class::CHALLENGE_TYPE
         | 
| 34 | 
            -
              end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
              def authorization_key
         | 
| 37 | 
            -
                "#{token}.#{crypto.thumbprint}"
         | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
              def crypto
         | 
| 41 | 
            -
                @crypto ||= Acme::Client::Crypto.new(client.private_key)
         | 
| 42 | 
            -
              end
         | 
| 43 | 
            -
            end
         | 
| @@ -1,19 +0,0 @@ | |
| 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 | 
            -
             | 
| 8 | 
            -
              def record_name
         | 
| 9 | 
            -
                RECORD_NAME
         | 
| 10 | 
            -
              end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
              def record_type
         | 
| 13 | 
            -
                RECORD_TYPE
         | 
| 14 | 
            -
              end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
              def record_content
         | 
| 17 | 
            -
                crypto.urlsafe_base64(crypto.digest.digest(authorization_key))
         | 
| 18 | 
            -
              end
         | 
| 19 | 
            -
            end
         | 
| @@ -1,18 +0,0 @@ | |
| 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 | 
            -
                authorization_key
         | 
| 13 | 
            -
              end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
              def filename
         | 
| 16 | 
            -
                ".well-known/acme-challenge/#{token}"
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
            end
         | 
| @@ -1,24 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base
         | 
| 4 | 
            -
              CHALLENGE_TYPE = 'tls-sni-01'.freeze
         | 
| 5 | 
            -
             | 
| 6 | 
            -
              def hostname
         | 
| 7 | 
            -
                digest = crypto.digest.hexdigest(authorization_key)
         | 
| 8 | 
            -
                "#{digest[0..31]}.#{digest[32..64]}.acme.invalid"
         | 
| 9 | 
            -
              end
         | 
| 10 | 
            -
             | 
| 11 | 
            -
              def certificate
         | 
| 12 | 
            -
                self_sign_certificate.certificate
         | 
| 13 | 
            -
              end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
              def private_key
         | 
| 16 | 
            -
                self_sign_certificate.private_key
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              private
         | 
| 20 | 
            -
             | 
| 21 | 
            -
              def self_sign_certificate
         | 
| 22 | 
            -
                @self_sign_certificate ||= Acme::Client::SelfSignCertificate.new(subject_alt_names: [hostname])
         | 
| 23 | 
            -
              end
         | 
| 24 | 
            -
            end
         | 
| @@ -1,37 +0,0 @@ | |
| 1 | 
            -
            class Acme::Client::Resources::Registration
         | 
| 2 | 
            -
              attr_reader :id, :key, :contact, :uri, :next_uri, :recover_uri, :term_of_service_uri
         | 
| 3 | 
            -
             | 
| 4 | 
            -
              def initialize(client, response)
         | 
| 5 | 
            -
                @client = client
         | 
| 6 | 
            -
                @uri = response.headers['location']
         | 
| 7 | 
            -
                assign_links(response.headers['Link'])
         | 
| 8 | 
            -
                assign_attributes(response.body)
         | 
| 9 | 
            -
              end
         | 
| 10 | 
            -
             | 
| 11 | 
            -
              def get_terms
         | 
| 12 | 
            -
                return unless @term_of_service_uri
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                @client.connection.get(@term_of_service_uri).body
         | 
| 15 | 
            -
              end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
              def agree_terms
         | 
| 18 | 
            -
                return true unless @term_of_service_uri
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                response = @client.connection.post(@uri, resource: 'reg', agreement: @term_of_service_uri)
         | 
| 21 | 
            -
                response.success?
         | 
| 22 | 
            -
              end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
              private
         | 
| 25 | 
            -
             | 
| 26 | 
            -
              def assign_links(links)
         | 
| 27 | 
            -
                @next_uri = links['next']
         | 
| 28 | 
            -
                @recover_uri = links['recover']
         | 
| 29 | 
            -
                @term_of_service_uri = links['terms-of-service']
         | 
| 30 | 
            -
              end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
              def assign_attributes(body)
         | 
| 33 | 
            -
                @id = body['id']
         | 
| 34 | 
            -
                @key = body['key']
         | 
| 35 | 
            -
                @contact = body['contact']
         | 
| 36 | 
            -
              end
         | 
| 37 | 
            -
            end
         | 
| @@ -1,60 +0,0 @@ | |
| 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 | 
            -
                certificate.public_key = private_key.public_key
         | 
| 50 | 
            -
                certificate.version = 2
         | 
| 51 | 
            -
                certificate
         | 
| 52 | 
            -
              end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
              def generate_extension_factory(certificate)
         | 
| 55 | 
            -
                extension_factory = OpenSSL::X509::ExtensionFactory.new
         | 
| 56 | 
            -
                extension_factory.subject_certificate = certificate
         | 
| 57 | 
            -
                extension_factory.issuer_certificate = certificate
         | 
| 58 | 
            -
                extension_factory
         | 
| 59 | 
            -
              end
         | 
| 60 | 
            -
            end
         | 
| @@ -1,122 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require 'faraday'
         | 
| 4 | 
            -
            require 'json'
         | 
| 5 | 
            -
            require 'openssl'
         | 
| 6 | 
            -
            require 'digest'
         | 
| 7 | 
            -
            require 'forwardable'
         | 
| 8 | 
            -
            require 'base64'
         | 
| 9 | 
            -
            require 'time'
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            module Acme; end
         | 
| 12 | 
            -
            class Acme::Client; end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
            require 'acme/client/version'
         | 
| 15 | 
            -
            require 'acme/client/certificate'
         | 
| 16 | 
            -
            require 'acme/client/certificate_request'
         | 
| 17 | 
            -
            require 'acme/client/self_sign_certificate'
         | 
| 18 | 
            -
            require 'acme/client/crypto'
         | 
| 19 | 
            -
            require 'acme/client/resources'
         | 
| 20 | 
            -
            require 'acme/client/faraday_middleware'
         | 
| 21 | 
            -
            require 'acme/client/error'
         | 
| 22 | 
            -
             | 
| 23 | 
            -
            class Acme::Client
         | 
| 24 | 
            -
              DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'.freeze
         | 
| 25 | 
            -
              DIRECTORY_DEFAULT = {
         | 
| 26 | 
            -
                'new-authz' => '/acme/new-authz',
         | 
| 27 | 
            -
                'new-cert' => '/acme/new-cert',
         | 
| 28 | 
            -
                'new-reg' => '/acme/new-reg',
         | 
| 29 | 
            -
                'revoke-cert' => '/acme/revoke-cert'
         | 
| 30 | 
            -
              }.freeze
         | 
| 31 | 
            -
             | 
| 32 | 
            -
              def initialize(private_key:, endpoint: DEFAULT_ENDPOINT, directory_uri: nil, connection_options: {})
         | 
| 33 | 
            -
                @endpoint, @private_key, @directory_uri, @connection_options = endpoint, private_key, directory_uri, connection_options
         | 
| 34 | 
            -
                @nonces ||= []
         | 
| 35 | 
            -
                load_directory!
         | 
| 36 | 
            -
              end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
              attr_reader :private_key, :nonces, :operation_endpoints
         | 
| 39 | 
            -
             | 
| 40 | 
            -
              def register(contact:)
         | 
| 41 | 
            -
                payload = {
         | 
| 42 | 
            -
                  resource: 'new-reg', contact: Array(contact)
         | 
| 43 | 
            -
                }
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                response = connection.post(@operation_endpoints.fetch('new-reg'), payload)
         | 
| 46 | 
            -
                ::Acme::Client::Resources::Registration.new(self, response)
         | 
| 47 | 
            -
              end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
              def authorize(domain:)
         | 
| 50 | 
            -
                payload = {
         | 
| 51 | 
            -
                  resource: 'new-authz',
         | 
| 52 | 
            -
                  identifier: {
         | 
| 53 | 
            -
                    type: 'dns',
         | 
| 54 | 
            -
                    value: domain
         | 
| 55 | 
            -
                  }
         | 
| 56 | 
            -
                }
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                response = connection.post(@operation_endpoints.fetch('new-authz'), payload)
         | 
| 59 | 
            -
                ::Acme::Client::Resources::Authorization.new(self, response.headers['Location'], response)
         | 
| 60 | 
            -
              end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
              def fetch_authorization(uri)
         | 
| 63 | 
            -
                response = connection.get(uri)
         | 
| 64 | 
            -
                ::Acme::Client::Resources::Authorization.new(self, uri, response)
         | 
| 65 | 
            -
              end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
              def new_certificate(csr)
         | 
| 68 | 
            -
                payload = {
         | 
| 69 | 
            -
                  resource: 'new-cert',
         | 
| 70 | 
            -
                  csr: Base64.urlsafe_encode64(csr.to_der)
         | 
| 71 | 
            -
                }
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                response = connection.post(@operation_endpoints.fetch('new-cert'), payload)
         | 
| 74 | 
            -
                ::Acme::Client::Certificate.new(OpenSSL::X509::Certificate.new(response.body), response.headers['location'], fetch_chain(response), csr)
         | 
| 75 | 
            -
              end
         | 
| 76 | 
            -
             | 
| 77 | 
            -
              def revoke_certificate(certificate)
         | 
| 78 | 
            -
                payload = { resource: 'revoke-cert', certificate: Base64.urlsafe_encode64(certificate.to_der) }
         | 
| 79 | 
            -
                endpoint = @operation_endpoints.fetch('revoke-cert')
         | 
| 80 | 
            -
                response = connection.post(endpoint, payload)
         | 
| 81 | 
            -
                response.success?
         | 
| 82 | 
            -
              end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
              def self.revoke_certificate(certificate, *arguments)
         | 
| 85 | 
            -
                client = new(*arguments)
         | 
| 86 | 
            -
                client.revoke_certificate(certificate)
         | 
| 87 | 
            -
              end
         | 
| 88 | 
            -
             | 
| 89 | 
            -
              def connection
         | 
| 90 | 
            -
                @connection ||= Faraday.new(@endpoint, **@connection_options) do |configuration|
         | 
| 91 | 
            -
                  configuration.use Acme::Client::FaradayMiddleware, client: self
         | 
| 92 | 
            -
                  configuration.adapter Faraday.default_adapter
         | 
| 93 | 
            -
                end
         | 
| 94 | 
            -
              end
         | 
| 95 | 
            -
             | 
| 96 | 
            -
              private
         | 
| 97 | 
            -
             | 
| 98 | 
            -
              def fetch_chain(response, limit = 10)
         | 
| 99 | 
            -
                links = response.headers['link']
         | 
| 100 | 
            -
                if limit.zero? || links.nil? || links['up'].nil?
         | 
| 101 | 
            -
                  []
         | 
| 102 | 
            -
                else
         | 
| 103 | 
            -
                  issuer = connection.get(links['up'])
         | 
| 104 | 
            -
                  [OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)]
         | 
| 105 | 
            -
                end
         | 
| 106 | 
            -
              end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
              def load_directory!
         | 
| 109 | 
            -
                @operation_endpoints = if @directory_uri
         | 
| 110 | 
            -
                  response = connection.get(@directory_uri)
         | 
| 111 | 
            -
                  body = response.body
         | 
| 112 | 
            -
                  {
         | 
| 113 | 
            -
                    'new-reg' => body.fetch('new-reg'),
         | 
| 114 | 
            -
                    'new-authz' => body.fetch('new-authz'),
         | 
| 115 | 
            -
                    'new-cert' => body.fetch('new-cert'),
         | 
| 116 | 
            -
                    'revoke-cert' => body.fetch('revoke-cert'),
         | 
| 117 | 
            -
                  }
         | 
| 118 | 
            -
                else
         | 
| 119 | 
            -
                  DIRECTORY_DEFAULT
         | 
| 120 | 
            -
                end
         | 
| 121 | 
            -
              end
         | 
| 122 | 
            -
            end
         | 
| @@ -1 +0,0 @@ | |
| 1 | 
            -
            require 'acme/client'
         |