Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NodeJs doesn't download intermediate certificate #16336

Closed
magicode opened this issue Oct 20, 2017 · 27 comments
Closed

NodeJs doesn't download intermediate certificate #16336

magicode opened this issue Oct 20, 2017 · 27 comments
Labels
tls Issues and PRs related to the tls subsystem.

Comments

@magicode
Copy link

magicode commented Oct 20, 2017

  • Version: v6.10.0
  • Platform: Linux 4.4.38-std-2 x86_64 GNU/Linux
  • Subsystem:

this site https://incomplete-chain.badssl.com/ configured without intermediate certificate.

In Google Chrome it work well. because Google Chrome does download the intermediate certificate. Now, the problem is, that since Chrome is by far the most popular browser, website owners configure their website to work in Chrome.

in nodejs emit error "Error: unable to verify the first certificate"

code example:

const https = require('https');

https.get('https://incomplete-chain.badssl.com/', (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);
});

in .net framework is work well.
example: https://dotnetfiddle.net/Th5M0c (c#)

in curl have error:
curl "https://incomplete-chain.badssl.com/"

curl: (60) server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none
More details here: http://curl.haxx.se/docs/sslcerts.html

All errors occur because there is no intermediate certificate.

I download certificate from website incomplete-chain.badssl.com
$ echo -n | openssl s_client -servername incomplete-chain.badssl.com -connect incomplete-chain.badssl.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > incomplete-chain.badssl.com.crt

I see the details
$ openssl x509 -in incomplete-chain.badssl.com.crt -text -noout

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            01:f2:02:03:1d:fd:a9:8e:fd:ff:0f:72:be:51:06:0d
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=DigiCert Inc, CN=DigiCert SHA2 Secure Server CA
        Validity
            Not Before: Mar 18 00:00:00 2017 GMT
            Not After : Mar 25 12:00:00 2020 GMT
        Subject: C=US, ST=California, L=Walnut Creek, O=Lucas Garron, CN=*.badssl.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c2:04:ec:f8:8c:ee:04:c2:b3:d8:50:d5:70:58:
                    cc:93:18:eb:5c:a8:68:49:b0:22:b5:f9:95:9e:b1:
                    2b:2c:76:3e:6c:c0:4b:60:4c:4c:ea:b2:b4:c0:0f:
                    80:b6:b0:f9:72:c9:86:02:f9:5c:41:5d:13:2b:7f:
                    71:c4:4b:bc:e9:94:2e:50:37:a6:67:1c:61:8c:f6:
                    41:42:c5:46:d3:16:87:27:9f:74:eb:0a:9d:11:52:
                    26:21:73:6c:84:4c:79:55:e4:d1:6b:e8:06:3d:48:
                    15:52:ad:b3:28:db:aa:ff:6e:ff:60:95:4a:77:6b:
                    39:f1:24:d1:31:b6:dd:4d:c0:c4:fc:53:b9:6d:42:
                    ad:b5:7c:fe:ae:f5:15:d2:33:48:e7:22:71:c7:c2:
                    14:7a:6c:28:ea:37:4a:df:ea:6c:b5:72:b4:7e:5a:
                    a2:16:dc:69:b1:57:44:db:0a:12:ab:de:c3:0f:47:
                    74:5c:41:22:e1:9a:f9:1b:93:e6:ad:22:06:29:2e:
                    b1:ba:49:1c:0c:27:9e:a3:fb:8b:f7:40:72:00:ac:
                    92:08:d9:8c:57:84:53:81:05:cb:e6:fe:6b:54:98:
                    40:27:85:c7:10:bb:73:70:ef:69:18:41:07:45:55:
                    7c:f9:64:3f:3d:2c:c3:a9:7c:eb:93:1a:4c:86:d1:
                    ca:85
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier: 
                keyid:0F:80:61:1C:82:31:61:D5:2F:28:E7:8D:46:38:B4:2C:E1:C6:D9:E2

            X509v3 Subject Key Identifier: 
                9D:EE:C1:7B:81:0B:3A:47:69:71:18:7D:11:37:93:BC:A5:1B:3F:FB
            X509v3 Subject Alternative Name: 
                DNS:*.badssl.com, DNS:badssl.com
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 CRL Distribution Points: 

                Full Name:
                  URI:http://crl3.digicert.com/ssca-sha2-g5.crl

                Full Name:
                  URI:http://crl4.digicert.com/ssca-sha2-g5.crl

            X509v3 Certificate Policies: 
                Policy: 2.16.840.1.114412.1.1
                  CPS: https://www.digicert.com/CPS
                Policy: 2.23.140.1.2.3

            Authority Information Access: 
                OCSP - URI:http://ocsp.digicert.com
                CA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt

            X509v3 Basic Constraints: critical
                CA:FALSE
            CT Precertificate SCTs: 
                Signed Certificate Timestamp:
                    Version   : v1(0)
                    Log ID    : A4:B9:09:90:B4:18:58:14:87:BB:13:A2:CC:67:70:0A:
                                3C:35:98:04:F9:1B:DF:B8:E3:77:CD:0E:C8:0D:DC:10
                    Timestamp : Mar 18 04:26:01.437 2017 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:21:00:CD:1E:0A:A8:2D:33:A0:3F:05:CD:1F:
                                09:93:4C:07:DC:23:0B:7F:F8:F4:B3:FB:0C:17:24:A0:
                                E6:0B:08:3F:7A:02:20:76:92:60:34:17:B8:89:D1:26:
                                AD:56:1D:41:C8:C1:C7:6D:EC:0B:1C:64:01:56:80:46:
                                82:CE:1F:BC:F9:0B:C2
                Signed Certificate Timestamp:
                    Version   : v1(0)
                    Log ID    : 56:14:06:9A:2F:D7:C2:EC:D3:F5:E1:BD:44:B2:3E:C7:
                                46:76:B9:BC:99:11:5C:C0:EF:94:98:55:D6:89:D0:DD
                    Timestamp : Mar 18 04:26:01.736 2017 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:20:4F:B6:2D:5E:84:39:3E:61:DC:96:B3:8A:
                                A3:49:C9:B3:F6:4B:B3:4D:6A:2B:EA:34:18:93:CF:C6:
                                64:3C:11:1F:02:21:00:FB:DB:AF:6D:D5:AA:E1:DA:21:
                                DE:27:BC:11:E2:B7:B6:06:74:95:E9:BB:B1:27:51:94:
                                50:AD:4D:4F:D2:52:D5
                Signed Certificate Timestamp:
                    Version   : v1(0)
                    Log ID    : EE:4B:BD:B7:75:CE:60:BA:E1:42:69:1F:AB:E1:9E:66:
                                A3:0F:7E:5F:B0:72:D8:83:00:C4:7B:89:7A:A8:FD:CB
                    Timestamp : Mar 18 04:26:02.201 2017 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:44:02:20:54:42:47:93:36:90:56:8E:EE:3F:39:79:
                                F1:8C:E3:A5:A3:01:17:A2:CD:57:B0:8B:6A:7C:B9:1A:
                                8F:3B:FA:71:02:20:40:BC:CB:0E:14:8A:BF:15:51:36:
                                AF:F8:67:5A:DF:6E:1F:22:11:83:3A:1E:3E:76:36:93:
                                BD:F8:BD:39:EB:9A
                Signed Certificate Timestamp:
                    Version   : v1(0)
                    Log ID    : BB:D9:DF:BC:1F:8A:71:B5:93:94:23:97:AA:92:7B:47:
                                38:57:95:0A:AB:52:E8:1A:90:96:64:36:8E:1E:D1:85
                    Timestamp : Mar 18 04:26:01.622 2017 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:20:78:6A:59:18:82:34:D7:FC:87:78:B6:00:
                                3C:2C:A3:99:76:D8:51:69:F0:7E:A3:04:1A:7E:E8:1D:
                                8F:68:14:94:02:21:00:9A:4F:2F:62:58:60:68:B2:DC:
                                D0:69:1C:F8:C9:14:21:9F:6C:12:82:D1:FA:D3:85:04:
                                B6:AD:49:34:24:D4:4A
    Signature Algorithm: sha256WithRSAEncryption
         69:7a:86:5d:ec:0d:ac:58:ef:ad:9c:25:ce:5f:c4:d1:bd:29:
         cf:d0:5a:f7:f9:63:34:cb:37:12:2e:c4:47:d3:b2:3f:d9:82:
         6f:90:da:11:65:f5:e9:d8:46:3e:0c:d5:67:a8:fe:cc:3f:f0:
         9c:3d:0c:64:ae:ca:93:a2:61:70:b3:f4:89:31:d0:57:36:8f:
         f3:aa:f7:7f:49:53:9f:a8:93:50:22:90:07:97:9d:ae:03:15:
         3b:70:f0:1c:88:72:1a:d4:ea:d6:d9:6b:f2:68:71:23:34:59:
         99:7b:58:8a:e0:3a:db:f0:4f:ea:82:c8:8f:49:81:80:a7:59:
         16:cf:45:f8:59:ae:52:b1:a9:0a:70:a0:ff:be:68:55:74:1c:
         e0:51:f6:78:ff:47:38:ac:ab:c6:46:9f:b7:fa:1c:a5:71:96:
         a1:d2:94:17:61:6e:67:07:03:f9:c7:81:ba:70:53:66:e6:a2:
         78:af:9d:db:e7:a5:01:c8:f0:5c:5d:e0:1c:db:71:16:0c:f9:
         92:24:01:35:80:e7:f6:8e:bc:7f:c4:10:86:fa:93:08:a9:69:
         7b:2a:1c:b7:da:26:ad:52:63:91:b4:f7:b1:e0:58:75:82:90:
         25:03:fe:9c:57:ea:9b:13:4d:ab:ea:7e:d2:de:9d:27:7b:a6:
         22:b7:f9:fc

in first certificate have link to intermediate certificate.
CA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt

I guess. Google Chrome. or .net framework. download this intermediate certificate. and check it.
nodejs no download it.

Someone have an idea.
How to make a nodejs work like Google Chrome or c#.
or how i can to inject intermediate certificate. before node check certificate
or how i can to rewrite the function that verifies the certificate

@bnoordhuis bnoordhuis added the tls Issues and PRs related to the tls subsystem. label Oct 20, 2017
@bnoordhuis
Copy link
Member

Does Chrome actually fetch the intermediate certificate or did it see it before and cache it? Because I believe that's what Firefox does.

If you have the intermediate certificate, tls.connect({...options, ca: [rootCA, intermediateCA]}) will make Node.js use that instead.

@magicode
Copy link
Author

magicode commented Oct 21, 2017

i checked it with wireshark.

in chrome or edge in windows fetch from link with this headers

GET /DigiCertSHA2SecureServerCA.crt HTTP/1.1
Connection: Keep-Alive
Accept: */*
User-Agent: Microsoft-CryptoAPI/10.0
Host: cacerts.digicert.com

in chrome on linux

GET /DigiCertSHA2SecureServerCA.crt HTTP/1.1
Host: cacerts.digicert.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36
Accept-Encoding: gzip, deflate

this is url http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt

issue like this. mozilla/tls-observatory#121

@bnoordhuis
Copy link
Member

bnoordhuis commented Oct 21, 2017

Interesting. What you could do is connect to a host with { rejectUnauthorized: false }, check for UNABLE_TO_VERIFY_LEAF_SIGNATURE errors and obtain the issuer URLs from the certificate details. Like this:

note: example only, not for production use

// rejectUnauthorized=false makes authentication failures non-fatal
let c = tls.connect({ rejectUnauthorized: false, ...options });
c.on('secureConnect', () => {
  if (c.authorized)
    return next(c);

  if (c.authorizationError === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
    // download intermediate CA certs
    const urls = c.getPeerCertificate().infoAccess['CA Issuers - URI'];
    download(urls, function(cas) {
      // try again, this time with the certs
      c = tls.connect({ ca: cas, ...options });
      c.on('secureConnect', () => next(c));
    });
  }

  c.destroy();
});

@bnoordhuis
Copy link
Member

I'll close this out as works-as-intended. I considered tagging this feature-request but I don't think it properly belongs in the https or tls modules, and a workaround exists (the snippet I posted, which anyone is free to take and spin off into a third-party module.)

@dovidW
Copy link

dovidW commented Nov 7, 2017

@bnoordhuis your snippet workaround it's actually, a rubber stamp for every certificate, is not it?

@bnoordhuis
Copy link
Member

If you mean that it's not very sophisticated yet: no, it's not. It's an example, a starting point.

@kring
Copy link

kring commented Jan 8, 2018

@bnoordhuis I'd say that code is a lot worse than "not very sophisticated". If I'm understanding it correctly, it's saying "if the leaf cert can't be validated, then grab its issuer cert and trust that issuer as a new CA." End result: pretty much any cert with an issuer URI will be considered valid, including self-signed ones, and therefore all manner of snooping, man-in-the-middle attacks, etc. are possible. I really hope no one is using that code!

I've run into the same issue as the original poster, while connecting to a server that I don't control so I can't easily fix it on that end. Chrome, Firefox, IE11, and Edge are all ok with the certificate (they download the intermediate cert automatically), but Node.js/Request is not. I think this issue should be re-opened.

@zimbabao
Copy link
Contributor

zimbabao commented Jan 8, 2018

@kring : Intermediate certificate needs to validated before its added as new CA (or maybe just kept in separate cache). Not be blindly added as trusted CA.

@kring
Copy link

kring commented Jan 8, 2018

Yes of course, @zimbabao, but that's not what the code above is doing, right?

@zimbabao
Copy link
Contributor

zimbabao commented Jan 8, 2018

@kring : True. I think it was intended as PoC.

@kring
Copy link

kring commented Jan 8, 2018

@bnoordhuis said while closing this issue:

a workaround exists (the snippet I posted, which anyone is free to take and spin off into a third-party module.)

PoC, sure, but this is not a workaround. Turning it into an actual workaround would be relatively involved and error-prone, and because this is security-critical code, getting it wrong could mean an exploitable bug in your application.

I understand if this isn't high priority (I'm an open-source developer myself), but I think it's a legitimate feature request and should be re-opened.

But mostly I posted so that no one thinks this is an actual workaround and uses the code as-is, because that would be extremely dangerous.

@zimbabao
Copy link
Contributor

zimbabao commented Jan 8, 2018

Yes. I agree to that. The request is legitimate and your concerns are valid.

@bnoordhuis
Copy link
Member

I really hope no one is using that code!

They'd be rather stupid if they did because it should be blindingly obvious that the snippet is to showcase how to get the list of intermediate certificates, nothing more.

The reason I don't think this is a valid feature request is that it means node.js starts doing too much work ("magic") on behalf of the user, and inflexibly at that. That's okay for an end user product like a browser or a module like request but not for node.js core.

A possible way forward is to make the verification phase more pluggable so it could cover this use case but that almost certainly requires upstream changes in openssl first.

@kring
Copy link

kring commented Jan 8, 2018

They'd be rather stupid if they did because it should be blindingly obvious that the snippet is to showcase how to get the list of intermediate certificates, nothing more.

Ok, you could be right. It was indeed obvious to me. It wasn't hard to imagine, though, a developer googling their error message, seeing some code that a node.js developer describes as a "workaround", pasting it into their app, seeing that it "solves" their problem, and moving on. Anyway, thanks for your time, I'll see myself out. :)

@bnoordhuis
Copy link
Member

I've added a note to the example saying it's an, eh, example, not something to copy verbatim.

@magicode
Copy link
Author

magicode commented Jan 25, 2018

@bnoordhuis
i think if have option to set "chain cert" for connection it is good for intermediate certificate.
like this function
SSL_set1_chain_cert_store(SSL *ctx, X509_STORE *st);
to add in this line
https://github.com/nodejs/node/blob/v8.8.0/src/node_crypto.cc#L2607

@magicode
Copy link
Author

magicode commented Jan 25, 2018

i dream on this code

const tls = require("tls");


var caStore = tls.createCertStore();
caStore.addCerts([cas]);

var chainStore = tls.createCertStore();
chainStore.addCerts([cas]);

function onGetChain(cert,callback){
  download(cert.infoAccess['CA Issuers - URI'], function(cert) {
    chainStore.addCert(cert);
    callback(null,cert);
  });
}

let c = tls.connect({ caStore: caStore , chainStore: chainStore , host , port , onGetChain: onGetChain  });

@bnoordhuis
Copy link
Member

@magicode SSL_set1_chain_cert_store() doesn't work that way though. You need to call it upfront, not asynchronously while the connection has already been established.

@magicode
Copy link
Author

@bnoordhuis OK. but nodejs not have option to set chain_cert_store ,
if nodejs support chain_cert_store your code is good.

@bnoordhuis
Copy link
Member

You can already pass in a chain of certificates with { ca: [ ... ] }.

@magicode
Copy link
Author

it's not chain_store it's verify_store https://github.com/nodejs/node/blob/v8.8.0/src/node_crypto.cc#L2604
if i make PR for this option nodejs accept it?

@bnoordhuis
Copy link
Member

You'll have to explain why the .ca property doesn't work.

@magicode
Copy link
Author

if i use chain_store i not need to verify intermediate certificate. i can add to chain_store untrusted certificates.

@bnoordhuis
Copy link
Member

I think I understand what you're getting at but can you explain why verify_store isn't sufficient? OpenSSL already verifies that the intermediate certificate is signed by the root certificate, right?

@magicode
Copy link
Author

@bnoordhuis
In matters of security, I don't want to rely on myself, because I don't believe that I have deep enough understanding in the field.
I'd like to rely on the experts in the field. Therefore, when I download an intermediate certificate from an untrusted source, it has to go to the list of untrusted security certificates, which are only used to create the security chain.

This option is supported by the following functions of OPENSSL

SSL_set1_chain_cert_store
X509_STORE_CTX_set_chain

@magicode
Copy link
Author

magicode commented Feb 1, 2018

I did a module for verify certificate
this is example
https://github.com/magicode/openssl-cert/blob/master/example.js
But I do not know. Is it necessary to verify that the certificate is related to the private key of the server

@arvind-agarwal
Copy link

A possible workaround till intermediate certificate download is implemented:

node_extra_ca_certs_mozilla_bundle

It generates a PEM file that includes all root and intermediate certificates trusted by Mozilla. It uses the following environment variable

NODE_EXTRA_CA_CERTS

To generate the PEM file to use with the above environment variable. You can install the module using:

npm install --save node_extra_ca_certs_mozilla_bundle

and then launch your node script with an environment variable.

NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem node your_script.js

Other ways to use the generated PEM file are available at:
https://github.com/arvind-agarwal/node_extra_ca_certs_mozilla_bundle

NOTE: I am the author of the above module.

pirxpilot added a commit to pirxpilot/liftie that referenced this issue Dec 19, 2023
www.campofelice.it server returns incomplete certificate chain
browsers can download missing intermediary certs but node does not do it

see: nodejs/node#16336

instead we are providing missing cert in:

meta/ca/ca_intermediate_bundle.pem

please note `NODE_EXTRA_CA_CERTS` had to be configured and point to that
file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tls Issues and PRs related to the tls subsystem.
Projects
None yet
Development

No branches or pull requests

6 participants