node icon indicating copy to clipboard operation
node copied to clipboard

intermediate CA certificate can be not trusted.

Open kadinwu opened this issue 4 years ago • 8 comments

  • Version: 12.18.2
  • Platform: Linux
  • Subsystem:

What steps will reproduce the bug?

  1. Only add server intermediate CA certificate to trusted certs.
  2. Trying to Connect to this server over tls, but got below error:

Error: unable to get issuer certificate at TLSSocket.onConnectSecure (_tls_wrap.js:1496:34) at TLSSocket.emit (events.js:315:20) at TLSSocket._finishInit (_tls_wrap.js:938:8) at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:696:12) { code: 'UNABLE_TO_GET_ISSUER_CERT' }

if add root CA certificate to trusted certs, then can connect to server.

How often does it reproduce? Is there a required condition?

What is the expected behavior?

Able to connect server over tls with server intermediate CA certificate trusted. Or provider any option to support openssl partial_chain verify: https://www.openssl.org/docs/manmaster/man1/openssl-verification-options.html

What do you see instead?

Additional information

kadinwu avatar Dec 09 '20 13:12 kadinwu

@nodejs/crypto

Trott avatar Dec 11 '20 13:12 Trott

was bitten by this at work for couple months (during which disabled NODE_TLS_REJECT_UNAUTHORIZED=0 when i didn't know better).

eventually stumbled on solution trying to get curl to work as well. not sure whether this should be fixed or not, b/c curl has the same behavior (although both nodejs and curl need better documentation telling users they need to include the root-certifcate as well).

kaizhu256 avatar Dec 11 '20 17:12 kaizhu256

This is expected behaviour and not a Node.js decision, but an OpenSSL one. OpenSSL expects trust anchors to be self-signed certificates and ignores intermediate ones. It can be changed with the X509_V_FLAG_PARTIAL_CHAIN flag which was added in v1.1.0:

The X509_V_FLAG_PARTIAL_CHAIN flag causes non-self-signed certificates in the trust store to be treated as trust anchors, in the same way as self-signed root CA certificates. This makes it possible to trust self-issued certificates as well as certificates issued by an intermediate CA without having to trust their ancestor root CA. With OpenSSL 1.1.0 and later and X509_V_FLAG_PARTIAL_CHAIN set, chain construction stops as soon as the first certificate contained in the trust store is added to the chain, whether that certificate is a self-signed "root" certificate or a not self-signed "intermediate" or self-issued certificate. Thus, when an intermediate certificate is found in the trust store, the verified chain passed to callbacks may be shorter than it otherwise would be without the X509_V_FLAG_PARTIAL_CHAIN flag.

Curl decided to enable it by default a year ago, see curl/curl#4655 for discussion. For curl it makes more sense because it's more agnostic of the TLS implementation, and the other backends do support intermediate certs as trust anchors.

Node.js is tied to OpenSSL, and changing a setting like this would have security implications, it would have to be semver-major at least IMHO.

mildsunrise avatar Dec 11 '20 22:12 mildsunrise

This is expected behaviour and not a Node.js decision, but an OpenSSL one. OpenSSL expects trust anchors to be self-signed certificates and ignores intermediate ones. It can be changed with the X509_V_FLAG_PARTIAL_CHAIN flag which was added in v1.1.0:

The X509_V_FLAG_PARTIAL_CHAIN flag causes non-self-signed certificates in the trust store to be treated as trust anchors, in the same way as self-signed root CA certificates. This makes it possible to trust self-issued certificates as well as certificates issued by an intermediate CA without having to trust their ancestor root CA. With OpenSSL 1.1.0 and later and X509_V_FLAG_PARTIAL_CHAIN set, chain construction stops as soon as the first certificate contained in the trust store is added to the chain, whether that certificate is a self-signed "root" certificate or a not self-signed "intermediate" or self-issued certificate. Thus, when an intermediate certificate is found in the trust store, the verified chain passed to callbacks may be shorter than it otherwise would be without the X509_V_FLAG_PARTIAL_CHAIN flag.

Curl decided to enable it by default a year ago, see curl/curl#4655 for discussion. For curl it makes more sense because it's more agnostic of the TLS implementation, and the other backends do support intermediate certs as trust anchors.

Node.js is tied to OpenSSL, and changing a setting like this would have security implications, it would have to be semver-major at least IMHO.

Thanks for the quick reply. By Default, X509_V_FLAG_PARTIAL_CHAIN flag is disabled is make sense for me. It is more secure, but it doesn't mean enable it is not secure. I just want nodejs can expose a way to set this flag. just to confirm, in latest nodejs, there is no way to configure X509_V_FLAG_PARTIAL_CHAIN flag and pass to openssl, right? If so, do you have any plan to support it?

kadinwu avatar Dec 12 '20 05:12 kadinwu

Hello, I just bumped into this issue when I was trying to discover why my nodejs apps throw "UNABLE_TO_GET_ISSUER_CERT" with my certificate design (CA / Client Cert / Server Cert)

So to just ask agin the question of @kadinwu , will be any way of passing this the X509_V_FLAG_PARTIAL_CHAIN flag in a future release of nodejs ?

I did not find anything mentionning that in the documentation, but may be I missed it ?

Regards,

zipou avatar Jun 15 '23 17:06 zipou

AFAIK we have never exposed options for certificate verification (X509_VERIFY_PARAM) such as the flags in this case. a priori I see it feasible and simple enough to do, but I haven't been very active at the project and I'm worried if it hasn't been done yet there's a reason for it. I couldn't find issues or PRs talking about it, so maybe there's just not been enough demand.

mildsunrise avatar Jun 16 '23 22:06 mildsunrise

One reason is that it's practically guaranteed people are going to misuse it: google error message, copy first stack overflow answer, move on, herp derp, not understanding or caring about the security implications.

I mean, look at how often people abuse NODE_TLS_REJECT_UNAUTHORIZED to get around certificate errors. Adding it as an escape hatch when I turned on TLS verify-by-default is probably the biggest regret I have over the last 12-13 years.

bnoordhuis avatar Jun 17 '23 06:06 bnoordhuis

understandable, but by that rule we also shouldn't have exposed SSL options, protocol version, global keylog, etc. I'm not sure to what extent it's good to prevent perfectly legitimate use cases to protect users from themselves... maybe a middle ground here would be to only expose flag constants that have reduced potential for misuse, such as CRL_CHECK*, X509_STRICT, CHECK_SS_SIGNATURE, PARTIAL_CHAIN, but users would probably hardcode other constants anyway.

mildsunrise avatar Jun 17 '23 17:06 mildsunrise

+1 to expose an option that enables setting this flag for allowing trusting intermediate CAs

galaxyfeeder avatar Sep 17 '23 21:09 galaxyfeeder

This is very much needed in our product as well. We're making use of different TLS implementations. Looks like in all our cases we can configure a non-root certificate as a trust anchor (e.g. JSSE from Java allows this, the crypto/tls package in golang seems to support this, etc.) but nodejs doesn't.

+1 for exposing a parameter or a way to configure nodejs (snd with that openssl underneath) to accept non-root certificates as trust anchors.

schaefec avatar Sep 21 '23 16:09 schaefec

I need to have deeper look into it and try some things, however, based on openssl documentation, a trust anchor can be configured in multiple ways and setting the certificates as TRUSTED CERTIFICATE in the PEM format should allow adding an intermediate CA and validating against it, being present in the trust store. All without the need of setting the X509_V_FLAG_PARTIAL_CHAIN flag.

Based on the following parts of the linked openssl docs:

From the OpenSSL perspective, a trust anchor is a certificate that should be augmented with an explicit designation for which uses of a target certificate the certificate may serve as a trust anchor. In PEM encoding, this is indicated by the TRUSTED CERTIFICATE string. Such a designation provides a set of positive trust attributes explicitly stating trust for the listed purposes and/or a set of negative trust attributes explicitly rejecting the use for the listed purposes. The purposes are encoded using the values defined for the extended key usages (EKUs) that may be given in X.509 extensions of end-entity certificates. See also the "Extended Key Usage" section below.

A certificate, which may be CA certificate or an end-entity certificate, is considered a trust anchor for the given use if and only if all the following conditions hold:

  • It is an an element of the trust store.
  • It does not have a negative trust attribute rejecting the given use.
  • It has a positive trust attribute accepting the given use or (by default) one of the following compatibility conditions apply: It is self-signed or the -partial_chain option is given (which corresponds to the X509_V_FLAG_PARTIAL_CHAIN flag being set).

galaxyfeeder avatar Sep 21 '23 16:09 galaxyfeeder

Thanks a lot for the hint @galaxyfeeder!

What I did to test that I can add an intermediate certificate as a trust is the following:

  1. Add trust attribute to my intermediate certificate using openssl x509

openssl x509 -in <path>/intermediate.crt -addtrust serverAuth -out <path>/intermediate-trusted.crt

  1. Starting a simple http server using openssl s_server which returns only the end entity certificate and the intermeidate certificate in the TLS handshake

openssl s_server -no-CAfile -no-CApath -no-CAstore -CAfile intermediate.crt -cert cert.crt -key key.key -www

  1. Executing the following code to check if my intermediate certificate is treated as a trust anchor in node now:
const https = require('node:https');
var fs = require('fs');

var https_options = {
  ca: [
          fs.readFileSync('<path>/intermediate-trusted.crt')
       ]
};

https.get('https://<my hostname>:4433/', https_options , (res) => {
  console.log('statusCode:', res.statusCode);
  console.log('headers:', res.headers);

  res.on('data', (d) => {
    process.stdout.write(d);
  });

}).on('error', (e) => {
  console.error(e);
});

This works well. However, I was not able to add my end entity certificate as a trust anchor by setting the trust attributes similar as I did for the intermediate certificate although this should work according to openssl's documentation

A certificate, which may be CA certificate or an end-entity certificate, is considered a trust anchor for the given use if and only if all the following conditions hold:

It is an an element of the trust store. It does not have a negative trust attribute rejecting the given use. It has a positive trust attribute accepting the given use or (by default) one of the following compatibility conditions apply: It is self-signed or the -partial_chain option is given (which corresponds to the X509_V_FLAG_PARTIAL_CHAIN flag being set).

It's good to be able to establish trust to an intermediate certificate (which is what is mentioned in the title of this issue), however, I would also be interested into establishing a trust to an end-entity in some situations. If anyone succeeeds to do this, please let us know.

schaefec avatar Sep 26 '23 15:09 schaefec