Post

Node.js Driver failing to connect due to "unsafe legacy renegotiation disabled"

Overview

In this community forum post there was a report of the MongoDB Node.js driver failing to connect with the following error:

MongoServerSelectionError: C8320000:error:0A000152:SSL routines:final_renegotiate:unsafe legacy renegotiation disabled:c:\ws\deps\openssl\openssl\ssl\statem\extensions.c:922

This error doesn’t smell like a MongoDB-specific error, so digging into final_renegotiate:unsafe legacy renegotiation disabled specifically lead to this openssl issue that looks to elaborate on the meaning of the error message:

TLSv1.2 (and earlier) support the concept of renegotiation. In 2009 (i.e. after the TLSv1.2 RFC was published), a flaw was discovered with how renegotiation works that could lead to an attack. After the attack was discovered a fix was deployed to all TLS libraries. In order for the fixed version of renegotiation to work both the client and the server need to support it.

The original (unfixed) version of renegotiation is known as “unsafe legacy renegotiation” in OpenSSL. The fixed version is known as “secure renegotiation”. So either a peer does not have the fix, in which case it will be using “unsafe legacy renegotiation”, or it does have the fix in which case it will be using “secure renegotiation”.

So it seems that the error originated from OpenSSL, and “that flaw” they’re alluding to was likely CVE-2009-3555. What was particularly interesting about this issue is that it only occurred when the application was run using Node.js 20, while Node.js 16 didn’t exhibit any issues - so what’s different between those two versions? One notable change is that Node.js 17+ use OpenSSL 3.0 by default - and starting with 3.0 secure negotiation support is required by default.

For more information on secure server-side renegotiation I’d highly recommend this discussion.

Configuring OpenSSL via the Node.js Driver

A similar issue was reported on Stack Overflow for the axios library, and the solution there was to pass secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT during request creation. As secureOptions is an option passed to Node’s tls.createSecureContext API (which MongoDB documents an example of using) it should be possible to do something similar with the Node.js driver.

1
2
3
4
5
6
7
8
import { MongoClient } from 'mongodb';
import { * as crypto } from 'crypto';

const client = new MongoClient("mongodb+srv://...", {
  secureContext: {
    secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT
  }
});

SUCCESS! The above example allows a SecureContext object to be created with the secureOptions selected from the enumerated OpenSSL options Node.js has defined.

Though the Node.js driver allows direct configuration of the SecureContext object, as other MongoDB drivers may not, DRIVERS-2823 is being considered to ensure this type of configuration is available.

Alternative Configuration

Configuring the MongoDB Node.js driver’s OpenSSL options directly is likely the preferred approach, but the Node runtime can also be configured (via --openssl-config=file). In this, when the node process is executed the path to a custom OpenSSL configuration file could be provided as follows:

1
node --openssl-config=/path/to/openssl.conf

Where openssl.conf is setup similar to the example below:

1
2
3
4
5
6
7
8
9
10
nodejs_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyRenegotiation
This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.