<commit_before>require 'acmesmith/account_key'
require 'acmesmith/certificate'

require 'acme-client'

module Acmesmith
  class Client

    def initialize(config: nil)
      @config ||= config
    end

    def register(contact)
      key = AccountKey.generate
      acme = Acme::Client.new(private_key: key.private_key, endpoint: config['endpoint'])
      registration = acme.register(contact: contact)
      registration.agree_terms

      storage.put_account_key(key, account_key_passphrase)

      key
    end

    def authorize(*domains)
      targets = domains.map do |domain|
        authz = acme.authorize(domain: domain)
        challenges = [authz.http01, authz.dns01, authz.tls_sni01].compact
        challenge = nil
        responder = config.challenge_responders.find do |x|
          challenge = challenges.find { |_| x.support?(_.class::CHALLENGE_TYPE) }
        end
        {domain: domain, authz: authz, responder: responder, challenge: challenge}
      end

      begin
        targets.each do |target|
          target[:responder].respond(target[:domain], target[:challenge])
        end

        targets.each do |target|
          puts "=> Requesting verifications..."
          target[:challenge].request_verification
        end
          loop do
            all_valid = true
            targets.each do |target|
              next if target[:valid]

              status = target[:challenge].verify_status
              puts " * [#{target[:domain]}] verify_status: #{status}"

              if status == 'valid'
                target[:valid] = true
                next
              end

              all_valid = false
              if status == "invalid"
                err = target[:challenge].error
                puts " ! [#{target[:domain]}] #{err["type"]}: #{err["detail"]}"
              end
            end
            break if all_valid
            sleep 3
          end
          puts "=> Done"
      ensure
        targets.each do |target|
          target[:responder].cleanup(target[:domain], target[:challenge])
        end
      end
    end

    def request(common_name, *sans)
      csr = Acme::Client::CertificateRequest.new(common_name: common_name, names: sans)
      retried = false
      acme_cert = begin
        acme.new_certificate(csr)
      rescue Acme::Client::Error::Unauthorized => e
        raise unless config.auto_authorize_on_request
        raise if retried

        puts "=> Authorizing unauthorized domain names"
        # https://github.com/letsencrypt/boulder/blob/b9369a481415b3fe31e010b34e2ff570b89e42aa/ra/ra.go#L604
        m = e.message.match(/authorizations for these names not found or expired: ((?:[a-zA-Z0-9_.\-]+(?:,\s+|$))+)/)
        if m && m[1]
          domains = m[1].split(/,\s+/)
        else
          warn " ! Error message on certificate request was #{e.message.inspect} and acmesmith couldn't determine which domain names are unauthorized (maybe a bug)"
          warn " ! Attempting to authorize all domains in this certificate reuqest for now."
          domains = [common_name, *sans]
        end
        puts " * #{domains.join(', ')}"
        authorize(*domains)
        retried = true
        retry
      end

      cert = Certificate.from_acme_client_certificate(acme_cert)
      storage.put_certificate(cert, certificate_key_passphrase)

      execute_post_issue_hooks(common_name)
      
      cert
    end

    def post_issue_hooks(common_name)
      execute_post_issue_hooks(common_name)
    end

    def list(common_name = nil)
      if common_name
        storage.list_certificate_versions(common_name).sort
      else
        storage.list_certificates.sort
      end
    end

    def current(common_name)
      storage.get_current_certificate_version(common_name)
    end

    def get_certificate(common_name, version: 'current', type: 'text')
      cert = storage.get_certificate(common_name, version: version)

      certs = []
      case type
      when 'text'
        certs << cert.certificate.to_text
        certs << cert.certificate.to_pem
      when 'certificate'
        certs << cert.certificate.to_pem
      when 'chain'
        certs << cert.chain
      when 'fullchain'
        certs << cert.fullchain
      end

      certs
    end

    def save_certificate(common_name, version: 'current', mode: '0600', output:)
      cert = storage.get_certificate(common_name, version: version)
      File.open(output, 'w', mode.to_i(8)) do |f|
        f.puts(cert.fullchain)
      end
    end

    def get_private_key(common_name, version: 'current')
      cert = storage.get_certificate(common_name, version: version)
      cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase

      cert.private_key.to_pem
    end

    def save_private_key(common_name, version: 'current', mode: '0600', output:)
      cert = storage.get_certificate(common_name, version: version)
      cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
      File.open(output, 'w', mode.to_i(8)) do |f|
        f.puts(cert.private_key)
      end
    end

    def save_pkcs12(common_name, version: 'current', mode: '0600', output:, passphrase:)
      cert = storage.get_certificate(common_name, version: version)
      cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
      
      raise 'No Passphrase Provided' if passphrase.nil?
      
      p12 = OpenSSL::PKCS12.create(passphrase, cert.common_name, cert.private_key, cert.certificate)
      File.open(output, 'w', mode.to_i(8)) do |f|
        f.puts p12.to_der
      end
    end<commit_msg>Same error should be raised from openssl<commit_after>require 'acmesmith/account_key'
require 'acmesmith/certificate'

require 'acme-client'

module Acmesmith
  class Client
    def initialize(config: nil)
      @config ||= config
    end

    def register(contact)
      key = AccountKey.generate
      acme = Acme::Client.new(private_key: key.private_key, endpoint: config['endpoint'])
      registration = acme.register(contact: contact)
      registration.agree_terms

      storage.put_account_key(key, account_key_passphrase)

      key
    end

    def authorize(*domains)
      targets = domains.map do |domain|
        authz = acme.authorize(domain: domain)
        challenges = [authz.http01, authz.dns01, authz.tls_sni01].compact
        challenge = nil
        responder = config.challenge_responders.find do |x|
          challenge = challenges.find { |_| x.support?(_.class::CHALLENGE_TYPE) }
        end
        {domain: domain, authz: authz, responder: responder, challenge: challenge}
      end

      begin
        targets.each do |target|
          target[:responder].respond(target[:domain], target[:challenge])
        end

        targets.each do |target|
          puts "=> Requesting verifications..."
          target[:challenge].request_verification
        end
          loop do
            all_valid = true
            targets.each do |target|
              next if target[:valid]

              status = target[:challenge].verify_status
              puts " * [#{target[:domain]}] verify_status: #{status}"

              if status == 'valid'
                target[:valid] = true
                next
              end

              all_valid = false
              if status == "invalid"
                err = target[:challenge].error
                puts " ! [#{target[:domain]}] #{err["type"]}: #{err["detail"]}"
              end
            end
            break if all_valid
            sleep 3
          end
          puts "=> Done"
      ensure
        targets.each do |target|
          target[:responder].cleanup(target[:domain], target[:challenge])
        end
      end
    end

    def request(common_name, *sans)
      csr = Acme::Client::CertificateRequest.new(common_name: common_name, names: sans)
      retried = false
      acme_cert = begin
        acme.new_certificate(csr)
      rescue Acme::Client::Error::Unauthorized => e
        raise unless config.auto_authorize_on_request
        raise if retried

        puts "=> Authorizing unauthorized domain names"
        # https://github.com/letsencrypt/boulder/blob/b9369a481415b3fe31e010b34e2ff570b89e42aa/ra/ra.go#L604
        m = e.message.match(/authorizations for these names not found or expired: ((?:[a-zA-Z0-9_.\-]+(?:,\s+|$))+)/)
        if m && m[1]
          domains = m[1].split(/,\s+/)
        else
          warn " ! Error message on certificate request was #{e.message.inspect} and acmesmith couldn't determine which domain names are unauthorized (maybe a bug)"
          warn " ! Attempting to authorize all domains in this certificate reuqest for now."
          domains = [common_name, *sans]
        end
        puts " * #{domains.join(', ')}"
        authorize(*domains)
        retried = true
        retry
      end

      cert = Certificate.from_acme_client_certificate(acme_cert)
      storage.put_certificate(cert, certificate_key_passphrase)

      execute_post_issue_hooks(common_name)
      
      cert
    end

    def post_issue_hooks(common_name)
      execute_post_issue_hooks(common_name)
    end

    def list(common_name = nil)
      if common_name
        storage.list_certificate_versions(common_name).sort
      else
        storage.list_certificates.sort
      end
    end

    def current(common_name)
      storage.get_current_certificate_version(common_name)
    end

    def get_certificate(common_name, version: 'current', type: 'text')
      cert = storage.get_certificate(common_name, version: version)

      certs = []
      case type
      when 'text'
        certs << cert.certificate.to_text
        certs << cert.certificate.to_pem
      when 'certificate'
        certs << cert.certificate.to_pem
      when 'chain'
        certs << cert.chain
      when 'fullchain'
        certs << cert.fullchain
      end

      certs
    end

    def save_certificate(common_name, version: 'current', mode: '0600', output:)
      cert = storage.get_certificate(common_name, version: version)
      File.open(output, 'w', mode.to_i(8)) do |f|
        f.puts(cert.fullchain)
      end
    end

    def get_private_key(common_name, version: 'current')
      cert = storage.get_certificate(common_name, version: version)
      cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase

      cert.private_key.to_pem
    end

    def save_private_key(common_name, version: 'current', mode: '0600', output:)
      cert = storage.get_certificate(common_name, version: version)
      cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
      File.open(output, 'w', mode.to_i(8)) do |f|
        f.puts(cert.private_key)
      end
    end

    def save_pkcs12(common_name, version: 'current', mode: '0600', output:, passphrase:)
      cert = storage.get_certificate(common_name, version: version)
      cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
      
      p12 = OpenSSL::PKCS12.create(passphrase, cert.common_name, cert.private_key, cert.certificate)
      File.open(output, 'w', mode.to_i(8)) do |f|
        f.puts p12.to_der
      end
    end
{'fraction_non_alphanumeric': 0.09961479888918749, 'fraction_numerical': 0.011287288363343187, 'mean_word_length': 2.4478072884496602, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2723515', 'n_tokens_mistral': 3455, 'n_tokens_neox': 3295, 'n_words': 741}