http icon indicating copy to clipboard operation
http copied to clipboard

Documentation: Usage of HTTPS certificates for local development

Open ThisIsMissEm opened this issue 6 months ago • 2 comments

Hi! I've been working on Mastodon which uses http.rb, and in local development, I'm using localcan to route traffic to various development servers all with auto-generated SSL certificates from a root certificate that localcan created. This means they're all self-signed.

Whilst reading the HTTPS documentation, I noticed that it was using ctx.set_params on the OpenSSL::SSL::SSLContext, I noticed a line in the documentation of:

The cert, key, and extra_chain_cert attributes are deprecated. It is recommended to use add_certificate instead.

Source: https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-cert

I'm wondering if the documentation should be updated to use that add_certificate method instead?

As I also have a Root CA certificate, as well as individual .crt and .key files per domain, I'm wondering if there's a way to tell http.rb to just add that Root CA certificate to it's trust chain, without overriding any existing certificates?

ThisIsMissEm avatar Jul 18 '25 15:07 ThisIsMissEm

I'm wondering if the documentation should be updated to use that add_certificate method instead?

Yes, updating the documentation sounds good. If you can get me a working replacement code example, I can do the update.

As I also have a Root CA certificate, as well as individual .crt and .key files per domain, I'm wondering if there's a way to tell http.rb to just add that Root CA certificate to it's trust chain, without overriding any existing certificates?

I believe that's handled by OpenSSL::X509::Store which you can access using OpenSSL::X509::SSLContext#cert_store

tarcieri avatar Jul 20 '25 12:07 tarcieri

Okay, so, yeah, from what I can tell this snippet:

HTTP.get("https://example.com", ssl_context: OpenSSL::SSL::SSLContext.new.tap do |ctx|
  ctx.set_params(
    cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
    key:  OpenSSL::PKey::RSA.new(File.read("client.key"))
  )
end)

becomes:

HTTP.get("https://example.com", ssl_context: OpenSSL::SSL::SSLContext.new.tap do |ctx|
  ctx.add_certificate(
    OpenSSL::X509::Certificate.new(File.read("client.crt")),
    OpenSSL::PKey::RSA.new(File.read("client.key"))
  )
end)

and the second snippet:

ssl_context = OpenSSL::SSL::SSLContext.new.tap do |ctx|
  ctx.set_params(
    cert: certs.shift,  # The root certificate
    key:  OpenSSL::PKey::RSA.new(File.read("path_to_your_private_key.pem")),
    extra_chain_cert: certs  # The intermediate certificates
  )
end

becomes:

bundle = File.read("path_to_your_fullchain.pem")
certificate_content_regex = /-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----/
certs = bundle.scan(certificate_content_regex).map { OpenSSL::X509::Certificate.new(_1) }

ssl_context = OpenSSL::SSL::SSLContext.new.tap do |ctx|
  ctx.add_certificate(
    certs.shift,  # The root certificate
    OpenSSL::PKey::RSA.new(File.read("path_to_your_private_key.pem")),
    certs  # The intermediate certificates
  )
end

Hope that helps. n.b., I haven't tested exactly this code, but I did do similar the other day.

ThisIsMissEm avatar Jul 20 '25 15:07 ThisIsMissEm