node.jstypescripttls1.2tls1.3

How to force Nodejs v19+ to use TLS 1.2 to avoid write EPROTO SSL routines:ssl3_read_bytes:sslv3 alert handshake failure...SSL alert number 40


I am trying to get NodeJs to make get requests to a site that has disabled TLS 1.3. However, each request I make throws a handshake exception like the following:

Error: write EPROTO 00683890CA7F0000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1605:SSL alert number 40

However, if I downgrade to NodeJs v16.20.0, the request succeeds.

You can see that the site in question has disabled TLS 1.3:

https://www.ssllabs.com/ssltest/analyze.html?d=www.legislation.gov.au&s=54.66.220.183

I have used printed the ciphers available in both v19 and v16 (using tls.getCiphers()) and have confirmed that the list of available ciphers is the same in both versions.

However, I have tried various permutations of setting the min and max TLS versions, creating httpsAgents, setting ciphers to use (both as a list and as '@SECLEVEL=1'), as well as trying the Node https client, axios and got, but so far, all to no avail.

Here is an example attempt to request the URL that fails in v19 but succeeds in v16:

const https = require('https');
var options = {
    hostname: 'www.legislation.gov.au',
    port: 443,
    path: '/',
    minVersion: "TLSv1.2",
    maxVersion: "TLSv1.2"
};

https.get(options, response => {
        let body = '';
        response.on('data', d => {
                body += d;
        });
        response.on('end', () => {
                console.log(body);
        });
}).on('error', err => {
        console.error(err);
});

I would appreciate any advice that anyone may be able to provide, especially if they can provide a working sample of the request above.


Solution

  • Try ciphers: 'DEFAULT:@SECLEVEL=0'

    First, I was able to reproduce with v18.16.0 vs v16.20.0 both in docker -alpine images (which are stable only).

    Second, the problem is not version intolerance by the server. Both v16 and v18 offer 1.3 by default, and the server negotiates to 1.2 correctly as per RFCs when it doesn't reject. Moreover commandline openssl and curl also offer 1.3 by default and succeed -- but see below. The server identifies itself as IIS/8.5 which is ten years old, and normally runs only on equally old Windows (i.e. 8.1/2012), which didn't support 1.3 at all, so it's probably not 'disabled' just nonexistent.

    I find only two differences in the ClientHello between v16 and v18 (and between commandline OpenSSL 1.1.1 and 3.0.x): supported_groups includes the FFDHE groups and sigalgs does NOT include SHA1 schemes. It appears the latter is the one that matters; if I configure ciphers:'DEFAULT:@SECLEVEL=0' (or use commandline equivalent) the SHA1 signatures are added back in, and the handshake succeeds -- even though SHA1 nominally ought to qualify at SECLEVEL=1, plus the server chooses a suite with RSA key exchange which doesn't use ANY signature at all.

    Also, a warning: my investigations were delayed quite a bit because the IPv6 addresses, which some of my test systems preferred, apparently go to a quite different server which responds very differently, although also in AWS and apparently an ELB from the name given.