Skip to content

tls.connect() gets a different certificate than openssl's built-in client #26420

@cookiengineer

Description

@cookiengineer
  • Subsystem: tls
  • Platform: Arch Linux x86
  • Versions affected: 10.11.0, 11.7.0, 11.9.0 and 11.10.0 (manually installed each and verified same behaviour)

It's hard to describe what's going on, so I'll start with the setup. I know that the exact same code that I had implemented was working at least until Thursday evening (4 days ago). And I know for sure that the responded certificate was Let's Encrypt until that point in time, because my implementation logged certificate details corresponding to the requests of the program. The previous certificate was issued for the IP, too - whereas the new certificate was issued for the domain. TLS automatically downgrades/renegotiates different certificate if the .servername property is missing.

Something between then and now has changed regarding github pages' server setup; which kind of bricks node.js now and I have no clue what it is. (I'm debugging for around 8 hours now with no success)

The website is hosted on github pages, which means it's using the correct A DNS entries and they are resolved correctly and point to github.io's servers.

Visiting https://cookie.engineer in the Web Browser results in the valid received TLS 1.2 certificate by the let's encrypt X3 authority. In order to verify this, profile folders were deleted, created in /tmp and used again without any extension in both Chromium, Firefox and even WebKitGTK. Same behaviour, all get the correct certificate.

The nslookup is giving me the correct A entries for the domain; and the /etc/hosts file does not contain any custom domain mappings.

All following codes are executed on the same machine, therefore, even if DNS cache would exist, the same DNS cache would apply. In order to ensure this, I also tried out overriding the lookup() method to return an IP that was returned by Google's DNS over HTTPS server, just to be sure that some hidden DNS calls are not the cause.

However, when I connect with this simple code (without any customization) I receive a certificate of 2006; which is issued for the github.com domain which was issued by "DigiCert SHA2 High Assurance Server CA", not the Let's Encrypt certificate that was cross-signed by Digital Signature:

tls = require('tls');

socket = tls.connect({ host: 'cookie.engineer', port: 443 }, () => console.log(socket.authorized));
socket.on('error', (e) => console.log(e)); // will throw error, because all DNS entries are invalid in subjectaltname
> Thrown:
{ Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: Host: cookie.engineer. is not in the cert's altnames: DNS:*.githubassets.com, DNS:githubassets.com
    at Object.checkServerIdentity (tls.js:245:17)
    at TLSSocket.onConnectSecure (_tls_wrap.js:1191:27)
    at TLSSocket.emit (events.js:197:13)
    at TLSSocket.EventEmitter.emit (domain.js:464:23)
    at TLSSocket._finishInit (_tls_wrap.js:672:8)
  reason:
   "Host: cookie.engineer. is not in the cert's altnames: DNS:*.githubassets.com, DNS:githubassets.com",
  host: 'cookie.engineer',
  cert:
   { subject:
      [Object: null prototype] {
        C: 'US',
        ST: 'California',
        L: 'San Francisco',
        O: 'GitHub, Inc.',
        CN: '*.githubassets.com' },
     issuer:
      [Object: null prototype] {
        C: 'US',
        O: 'DigiCert Inc',
        OU: 'www.digicert.com',
        CN: 'DigiCert SHA2 High Assurance Server CA' },
     subjectaltname: 'DNS:*.githubassets.com, DNS:githubassets.com',
     infoAccess:
      [Object: null prototype] { 'OCSP - URI': [Array], 'CA Issuers - URI': [Array] },

Note that I also tried enforcing TLSv1_2_method and pretty much all other methods that are available; all have the same behaviour. Just for paranoia, I also changed the ALPNProtocols to each and all of http/2, http/1.1 and http/1.0 because I was unsure whether or not they are transmitted before the TLS handshake (and therefore a possible load balancing(?) problem).

However, when I double-check what's going on and I'm using openssl's integrated client, I get the let's encrypt certificate:

openssl s_client -connect cookie.engineer:443
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = cookie.engineer
verify return:1
(... and so on ...)

What is causing this problem, how is this even possible to receive different TLS certificates on a networked response - and how can I fix this?

Thanks in advice.

Metadata

Metadata

Assignees

No one assigned

    Labels

    tlsIssues and PRs related to the tls subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions