unixcharles / acme-client

A Ruby client for the letsencrypt's ACME protocol.
MIT License
495 stars 116 forks source link

Returned certificate doesn't match private key #154

Closed mikefogg closed 5 years ago

mikefogg commented 5 years ago

Hey guys,

Banging my head against the wall here trying to figure this out.

Can anyone shed some light into why this isn't returning true? I'm passing the private key object directly through to my Acme Client and then attempting to upload the private/public pair to Heroku... but somehow they don't match (even though I literally just made it).

(I've ignored the uploading to heroku part since the "verify" method fails regardless)

# Initially creating and storing this
key = OpenSSL::PKey::RSA.new(4096).to_s

# Then later using this to generate the certificate
key_object = OpenSSL::PKey::RSA.new(key)
client = Acme::Client.new(private_key: key_object)
order = client.new_order(identifiers: domain_names)

#
# Run through successful authorizations...
#

# Finalize the cert
csr = Acme::Client::CertificateRequest.new(
    common_name: domain_names[0],
    names: domain_names
)
order.finalize(csr: csr)

# Up to this point, everything looks great! order.certificate is a valid-looking certificate
# with the correct data (domain names, expiration, etc.)
cert = OpenSSL::X509::Certificate.new(order.certificate)

# False! (and fails for the same reason uploading to heroku)
cert.verify(key_object) 
cpu commented 5 years ago

Hi @mikefogg,

I believe when you're calling Acme::Client:CertificateRequest.new() without proving an explicit private key a new one is generated for you because the default private_key argument is generate_private_key: https://github.com/unixcharles/acme-client/blob/38f4e17bc9e4ec21c3a417a085d843d1fb7f8b4d/lib/acme/client/certificate_request.rb#L28

As you discovered since the account key doesn't match the key in the CSR you aren't able to verify the certificate signature with it and neither is Heroku.

It would be tempting to fix that by providing your key_object to the CSR initializer but that won't work because you're already using key_object as the ACME account key in Acme:Client.New() and Let's Encrypt specifically forbids reusing your ACME account public key as a certificate subject public key.

I think the correct solution is for you to either:

  1. Retrieve the private key that acme-client generates for you as a result of Acme::Client::CertificateRequest.new() and upload that key to Heroku
  2. Explicitly generate a second certificate private key, use it to construct a CSR, and then upload that 2nd key to Heroku.

(FWIW I'm not a Ruby dev or an acme-client dev I just know my way around ACME so definitely take my help with a grain of salt!)

unixcharles commented 5 years ago

Yep, @cpu is right.