I want set up a server that behaves the same as https://valid-isrgrootx1.letsencrypt.org/ (which returns a cert that only chains up to ISRG Root X1) so that I can gain confidence that our existing android clients running an old android OS can hit an endpoint with certs issued stating in January 2020. (see this post from letsencrypt for details) The reason I want to have my own server up is that there are certain endpoints I'd like to hit and have it return 201 so subsequent calls can be made against other endpoints. Without hosting my own server, I cannot have test certain scenarios aside from "user can log in".
OS: ManjaroLinux 20.1.2
I installed certbot via pacman and the version is certbot 1.9.0. My openssl version is: OpenSSL 1.1.1h 22 Sep 2020
I ran this command:
sudo certbot certonly --manual --preferred-chain "ISRG Root X1" --preferred-challenges dns
I obtained a cert via this command:
sudo certbot certonly --manual --preferred-chain "ISRG Root X1" --preferred-challenges dns
Then I configured my DNS records so that I have an A record and a TXT record, and the challenge was completed successfully. Files of fullchain.pem / chain.pem / cert.pem / privatekey.pem were generated.
lets-encrypt-r3.pem: OK
identrust.pem: OK
isrgrootx1.pem: OK
lets-encrypt-r3-cross-signed.pem: OK
When I load a website that serves that exact same cert, I see that the chain is: <my domain> -> R3 -> DST ROOT X3
Behavior differed from what I expected because:
I expected the output of openssl verify against identrust.pem to say ERROR and lets-encrypt-r3-cross-signed.pem to also say ERROR and only OK for lets-encrypt-r3 and isrgrootx1.pem
and I expect that on firefox, the certificate chain when I go to my domain to say: <my domain> -> R3 -> ISRG Root X1
Am I configuring something incorrectly? Why does openssl verify return OK for all? Or is it impossible to get a cert that only points to ISRG Root X1 at this moment?
Does the second certificate in fullchain.pem match lets-encrypt-r3.pem? If so, you're all set.
I'm not sure what you're trying to accomplish with your openssl verify command.
Your browser may be caching a prior authorization chain if it's ever seen that domain with a DST Root X3 signed intermediate before. Looking at the chain in your browser is not a reliable method to determine what the server is currently sending.
Perhaps LE forgot to add the ISRG Root X1 signed alternative chain when they changed the intermediate signing key from X3 to R3? @lestaff any thoughts?
Also, you van manually change the intermediate provided (erroneously) to the one you want in the mean time. Just replace chain.pem (in /archive/ as the files in /live/ are just symlinks) to the one you want and als update fullchain to contain the correct intermediate (also in /archive/).
By the way, currently, your server isn't serving any intermediate certificate at the moment:
$ openssl s_client -connect api-isrg-root-x1.maishameds.org:443
CONNECTED(00000003)
depth=0 CN = api-isrg-root-x1.maishameds.org
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = api-isrg-root-x1.maishameds.org
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = api-isrg-root-x1.maishameds.org
i:C = US, O = Let's Encrypt, CN = R3
---
Kinda strange my Android 10 chooses the DST root to chain up to, perhaps it didn't cache the intermediate signed by ISRG Root X1 yet..
That is, you want to make sure that your fullchain verifies up to the CA given in the isrgrootx1 file, with the intermediate allowed for building the chain but not explicitly trusted.
(And as others are saying, you need to ensure that you're serving the fullchain.pem from your server, where the second certificate in that file should match lets-encrypt-r3.pem exactly.)
Remember that there is no such thing as "a cert that only points to ISRG Root X1". When you configure your server to present the chain Your Cert <-- R3 <-- ISRG Root X1, that acts only as a hint to browsers. Browsers are free to ignore that hint, and instead build the chain Your Cert <-- R3 <-- DST Root X3 during validation.
To double-check that your server is providing the chain you want, you can use a tool like certigo:
$ ~/go/bin/certigo connect api-isrg-root-x1.maishameds.org --verbose
** TLS Connection **
Version: TLS 1.2
Cipher Suite: ECDHE_RSA key exchange, AES_256_GCM_SHA384 cipher
** CERTIFICATE 1 **
Serial: 331862780954954513440435273324005883452576
Valid: 2020-12-03 04:09 UTC to 2021-03-03 04:09 UTC
Signature: SHA256-RSA
Subject Info:
CommonName: api-isrg-root-x1.maishameds.org
Issuer Info:
Country: US
Organization: Let's Encrypt
CommonName: R3
Subject Key ID: 49:CC:A8:EE:8A:02:54:39:36:FC:49:DD:E6:C1:59:E8:72:09:7E:9E
Authority Key ID: 14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6
Basic Constraints: CA:false
OCSP Server(s):
http://r3.o.lencr.org
Issuing Certificate URL(s):
http://r3.i.lencr.org/
Key Usage:
Digital Signature
Key Encipherment
Extended Key Usage:
Server Auth
Client Auth
DNS Names:
api-isrg-root-x1.maishameds.org
Failed to verify certificate chain:
x509: certificate signed by unknown authority
As @Osiris pointed out, it looks like you're currently not serving a chain at all; you're only serving your end-entity certificate and not providing an intermediate. You shouldn't need to download any of our certs from https://letsencrypt.org/certs/, instead simply use the fullchain.pem file which was supplied by your certbot invocation.
Thanks for the help everyone. This ended up being a config issue on the server side. I've since reconfigured our server to return what's in fullchain.pem (instead of cert.prem) and now I'm able to verify the chain via openssl s_client -connect api-isrg-root-x1.maishameds.org:443