Node: NodeJs doesn't download intermediate certificate

Created on 20 Oct 2017  路  27Comments  路  Source: nodejs/node

  • 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

tls

All 27 comments

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.

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. https://github.com/mozilla/tls-observatory/issues/121

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();
});

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.)

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

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

@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.

@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.

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

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

@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.

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

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.

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. :)

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

@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

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  });

@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.

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

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

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?

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

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

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?

@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

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

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fanjunzhi picture fanjunzhi  路  3Comments

Brekmister picture Brekmister  路  3Comments

stevenvachon picture stevenvachon  路  3Comments

danielstaleiny picture danielstaleiny  路  3Comments

Icemic picture Icemic  路  3Comments