we-promise/sure · error · EnableBankingError

invalid_certificate

invalid_certificate

Error message

Invalid private key in certificate: #{e.message}

What it means

Raised by Provider::EnableBanking#extract_private_key during initialize when OpenSSL::PKey::RSA.new rejects the client_certificate argument — the string passed as :client_certificate is not a parseable RSA private key PEM. The OpenSSL error message is preserved ('Invalid private key in certificate: ...') and logged. Every subsequent call would fail anyway, so construction aborts immediately.

Source

Thrown at app/models/provider/enable_banking.rb:247

      # Otherwise pick the first progressively-shorter window that advances the
      # window forward, skipping any window that is not newer than the current
      # date_from. Moving strictly forward guarantees progress and termination.
      FALLBACK_TRANSACTIONS_DATE_FROM_DAYS
        .map { |days| days.days.ago.to_date }
        .find { |candidate| current.nil? || candidate > current }
    end

    def safe_psu_headers(headers)
      headers.except("Authorization", :Authorization, "Accept", :Accept, "Content-Type", :"Content-Type")
    end

    def extract_private_key(certificate_pem)
      # Extract private key from PEM certificate
      OpenSSL::PKey::RSA.new(certificate_pem)
    rescue OpenSSL::PKey::RSAError => e
      Rails.logger.error "Enable Banking: Failed to parse private key: #{e.message}"
      raise EnableBankingError.new("Invalid private key in certificate: #{e.message}", :invalid_certificate)
    end

    def generate_jwt
      now = Time.current.to_i

      header = {
        typ: "JWT",
        alg: "RS256",
        kid: application_id
      }

      payload = {
        iss: "enablebanking.com",
        aud: "api.enablebanking.com",
        iat: now,
        exp: now + 3600  # 1 hour expiry
      }

View on GitHub (pinned to e69894adb9)

Solutions

  1. Read the logged OpenSSL message — 'Could not parse PKey' usually means wrong content, 'Could not find start line' means broken BEGIN/END header
  2. Re-download the RSA private key PEM from the Enable Banking application settings and pass the full '-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----' block
  3. Validate locally: openssl rsa -in key.pem -check -noout (add -passin pass:... if the key is encrypted — the client does not support passphrase-encrypted keys)
  4. Ensure the stored value keeps newlines intact (store as a single-line secret and gsub('\\n', "\n") before use, or store the multiline PEM directly)

Example fix

# before
Provider::EnableBanking.new(application_id: app_id, client_certificate: ENV["EB_CERT"]) # mangled value

# after
key_pem = ENV["EB_PRIVATE_KEY"].to_s.gsub("\\n", "\n")
raise ArgumentError, "EB_PRIVATE_KEY is not an RSA PEM" unless key_pem.include?("BEGIN")
Provider::EnableBanking.new(application_id: app_id, client_certificate: key_pem)
Defensive patterns

Strategy: validation

Validate before calling

def valid_enable_banking_rsa_key?(pem)
  OpenSSL::PKey.read(pem).is_a?(OpenSSL::PKey::RSA)
rescue OpenSSL::PKey::PKeyError
  false
end

# use before building the client:
# raise ArgumentError, "private key is not a valid RSA PEM" unless valid_enable_banking_rsa_key?(ENV["EB_PRIVATE_KEY"].to_s.gsub("\\n", "\n"))

Type guard

def eb_invalid_certificate?(error)
  error.is_a?(Provider::EnableBanking::EnableBankingError) && error.error_type == :invalid_certificate
end

Try / catch

begin
  Provider::EnableBanking.new(application_id: app_id, client_certificate: key_pem)
rescue Provider::EnableBanking::EnableBankingError => e
  raise unless e.error_type == :invalid_certificate
  raise "Enable Banking credentials misconfigured: paste the full RSA private key PEM (BEGIN/END lines included)"
end

Prevention

When it happens

Trigger: Passing the Enable Banking application's public certificate instead of its private key; a PEM block missing its BEGIN/END lines or with mangled newlines (copied through a channel that strips them); an EC or encrypted key instead of an RSA one; pasting the JSON bundle instead of the PEM.

Common situations: Copy-paste of credentials from the Enable Banking portal into .env or a settings form losing line breaks; storing the 'certificate' field when the portal's private key was downloaded separately; whitespace substitution in YAML/JSON configs.

Understand the failure class

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/1a00daaf15b44e52. Report an issue: GitHub.