Be careful of bundled cert expiries

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the web-development category.

Last Updated: 2024-04-25

Ten customers did not receive their items in Oxbridge Notes one day, the biggest downtime in about years. The error in Rollbar was this:

OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate)

What does this mean? Why did it happen?

I discovered that the PayPal gem (which I still rely on for payment) included its own certs:

DigiCertHighAssuranceEVRootCA.pem
DigiCertSHA2ExtendedValidationServerCA.pem
paypal.crt

and then used these for SSL

def default_ca_file
  File.expand_path("../../../../../data/paypal.crt", __FILE__)
end

# Apply ssl configuration to http object
def configure_ssl(http)
  http.tap do |https|
    https.use_ssl = true
    https.ca_file = default_ca_file
    ...
  end
end

But then those certs in this library expired... and PayPal did not update the gem because it was deprecated. So my code, using an expired cert, failed.

What was the fix?

The quickest was to tell PayPal not to use the bundled SSL certs at all. Then it just used the standard HTTP system

PayPal::SDK.configure(
  mode: ...,
  client_id: ...,
  client_secret: ...,

  # Deliberately set ca_file to nil so the system's Cert Authority is used,
  # instead of the bundled paypal.crt file which is out-of-date due to:
  # https://www.paypal.com/va/smarthelp/article/discontinue-use-of-verisign-g5-root-certificates-ts2240
  ssl_options: { ca_file: nil }
)

Lesson

Avoid using bundled certs because they might expire.