denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
98.26k stars 5.41k forks source link

rustls refuses to establish connections to self signed TLS certificates #10312

Open lucacasonato opened 3 years ago

lucacasonato commented 3 years ago

Reproduction

The key chain this uses is made up of two parts. The CA (./test_util/wpt/tools/certs/cacert.pem) and the end entity certificate (./test_util/wpt/tools/certs/web-platform.test.pem).

To start the testing server:

deno run -A --unstable ./tools/wpt.ts setup
cd ./test_util/wpt
./wpt serve

See that curling the endpoint with the correct CA cert in the roots works, and without doesn't:

$ curl -v https://web-platform.test:8444/ --cacert ./test_util/wpt/tools/certs/cacert.pem
...
* Server certificate:
*  subject: CN=web-platform.test
*  start date: Apr 12 02:33:49 2021 GMT
*  expire date: Apr 12 02:33:49 2022 GMT
*  subjectAltName: host "web-platform.test" matched cert's "web-platform.test"
*  issuer: CN=web-platform-tests
*  SSL certificate verify ok.
...
<li class="dir"><a href="xslt/">xslt</a></li>
</ul>
* Connection #0 to host web-platform.test left intact

$ curl -v https://web-platform.test:8444/ 
* SSL certificate problem: unable to get local issuer certificate
* Closing connection 0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

See that it doesn't work with Deno in any configuration:

$ deno eval --cert ./test_util/wpt/tools/certs/cacert.pem -Ldebug 'console.log(await fetch("https://web-platform.test:8444/"))'
Sending fatal alert BadCertificate
error: Uncaught (in promise) TypeError: error sending request for url (https://web-platform.test:8444/): error trying to connect: invalid certificate: UnknownIssuer
console.log(await fetch("https://web-platform.test:8444/"))
            ^
    at unwrapOpResult (deno:core/core.js:100:13)
    at async mainFetch (deno:op_crates/fetch/26_fetch.js:165:18)
    at async fetch (deno:op_crates/fetch/26_fetch.js:295:22)
    at async file:///mnt/starship/Projects/github.com/denoland/deno/$deno$eval.ts:1:13

$ deno eval -Ldebug 'console.log(await fetch("https://web-platform.test:8444/"))'
Sending fatal alert BadCertificate
error: Uncaught (in promise) TypeError: error sending request for url (https://web-platform.test:8444/): error trying to connect: invalid certificate: UnknownIssuer
console.log(await fetch("https://web-platform.test:8444/"))
            ^
    at unwrapOpResult (deno:core/core.js:100:13)
    at async mainFetch (deno:op_crates/fetch/26_fetch.js:165:18)
    at async fetch (deno:op_crates/fetch/26_fetch.js:295:22)
    at async file:///mnt/starship/Projects/github.com/denoland/deno/$deno$eval.ts:1:13

This same issue is reproducible with WebSocket, but for now lets only use fetch because it is easier to test with.

lucacasonato commented 3 years ago

Analysis so far:

CA cert is being added to rustls here: https://github.com/denoland/deno/blob/21ab4d94c0edca94cb71c5e444f93005074b17f2/op_crates/fetch/lib.rs#L497-L498

Error originates at https://github.com/briansmith/webpki/blob/e51f215d2ea45940d98db21b24f3a8be02f7606d/src/verify_cert.rs#L55

Cert doesnt actually end up in webpki trust anchors list. Don't know why.

piscisaureus commented 3 years ago

Multiple copies of webpki/webpki-roots?

lucacasonato commented 3 years ago

Multiple copies of webpki/webpki-roots?

Nope

bnoordhuis commented 3 years ago

webpki 0.21.4 doesn't like the ca cert. It looks like either webpki or the untrusted crate chokes on the humongous X509v3 Name Constraints field in test_util/wpt/tools/certs/cacert.pem, it's almost 8K big!

lucacasonato commented 3 years ago

Ah great... so I guess this should be reported upstream? OpenSSL can do it, so I think rustls should too. Thanks for digging into this.

bnoordhuis commented 3 years ago

I've opened https://github.com/briansmith/webpki/pull/226 with a failing regression test, to solicit feedback.

bnoordhuis commented 3 years ago

Maybe this isn't a webpki issue after all. See the linked pull request for more details but tl;dr the DER encoding in the CA certificate looks suspect. - edit: likely a webpki issue after all. The workaround still works though.

There's a workaround: apply this patch and regenerate the certificates with wpt serve --config tools/certs/config.json:

diff --git a/tools/wptserve/wptserve/sslutils/openssl.py b/tools/wptserve/wptserve/sslutils/openssl.py
index 87a8cc9cc7..bbf500d8ca 100644
--- a/tools/wptserve/wptserve/sslutils/openssl.py
+++ b/tools/wptserve/wptserve/sslutils/openssl.py
@@ -216,7 +216,7 @@ basicConstraints = CA:true
 subjectKeyIdentifier=hash
 authorityKeyIdentifier=keyid:always,issuer:always
 keyUsage = keyCertSign
-%(constraints_line)s
+#%(constraints_line)s
 """ % {"root_dir": root_dir,
        "san_line": san_line,
        "duration": duration,

https://github.com/web-platform-tests/wpt/issues/11075 indicates the nameConstraints field was added to make the certificates safe(r) to use in browsers so I don't know if they're open to removing it again.

bnoordhuis commented 3 years ago

Turns out it was indeed a webpki bug. Submitted a fix.

lucacasonato commented 3 years ago

I'm going to reopen this, as the root issue has not been resolved. We only mitigated it for the specific usecase it was causing an issue.

cryptogohan commented 3 years ago

I think I'm running into this with a Postgres DB.

Sending fatal alert BadCertificate
error: Uncaught (in promise) InvalidData: invalid certificate: UnknownIssuer

But a ca cert is provided like so:

deno run --unstable -A --cert ca_cert.pem connect.ts

Does a provided ca cert still need a valid issuer? Because then the --cert flag is just not what I need.

lucacasonato commented 3 years ago

No it doesn't need to be valid, but the CA cert and the leaf cert used for TLS may not be the same thing. I will take a look to see if there are any misconfigurations in our TLS ops.

cryptogohan commented 3 years ago

@lucacasonato do you have a reproducible example already? I wouldn't want you to waste time. I have a pg server that I hit this issue with but I definitely can't rule out the certificate having issues somehow. The more I dive into this the more I'm .. 'impressed' 😅 , with the complexity of TLS.

Besides, I've moved on to a different hosting provider that lets me make unsecured private connections to the DB. It offers secured with self-signed cert too, but only with an IP (Google Cloud) and that seems to be unsupported by rustls.

If I can help by providing a server and cert for example you can reach me at https://t.me/cryptogohan

lndgalante commented 3 years ago

Don't know if it helps a lot but having the same issue as @cryptogohan with a Postgres database in Heroku, both locally and in the prod deploy over heroku.

lucacasonato commented 3 years ago

still not fixed, so reopening

cryptogohan commented 3 years ago

Hmm, I think I can step-out here. For my case it seems self-signed certificates are working fine, I've managed to narrow down the issues to different things. There is the Digital Ocean case I discuss over here that hits a InvalidData: invalid certificate: BadDER error but I doubt that is this issue.

justinmchase commented 3 years ago

There was a change made in this area recently, in addition to adding support for loading certs from the certificate store it also did a bit of refactoring which unified a couple places in code to all call a common function.

https://github.com/denoland/deno/pull/11491

It may be worth trying again with the latest version (when it lands with this change, or build from main) and see if it still reproduces.

bartlomieju commented 3 years ago

@lucacasonato can this be closed given that DENO_TLS_CA_STORE has been added in v1.13?

lucacasonato commented 3 years ago

No, this specific case is a bug in rustls/webpki (https://github.com/briansmith/webpki/pull/226)

FrankReh commented 1 year ago

Just for general information. I was able to get rustls to work with a self-signed certificate building things from the shell script found in https://stackoverflow.com/questions/76049656/unexpected-notvalidforname-with-rusts-tonic-with-tls

The server side was PostgreSQL, not rustls, so this may not at all be relevant.