From a0955bdf935113abbb28d7c7011abc04027be2b0 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 22 Aug 2023 08:39:39 -0400 Subject: [PATCH] fix(NODE-5548): ensure that tlsCertificateKeyFile maps to cert and key (#3819) Co-authored-by: Durran Jordan --- src/mongo_client.ts | 15 +++++++---- test/manual/tls_support.test.ts | 47 ++++++++++++++++++++++++++++++++- test/unit/mongo_client.test.js | 1 + 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index b35d2811bf..1e0971cb8c 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -435,10 +435,14 @@ export class MongoClient extends TypedEventEmitter { if (options.tls) { if (typeof options.tlsCAFile === 'string') { - options.ca ??= await fs.readFile(options.tlsCAFile, { encoding: 'utf8' }); + options.ca ??= await fs.readFile(options.tlsCAFile); } if (typeof options.tlsCertificateKeyFile === 'string') { - options.key ??= await fs.readFile(options.tlsCertificateKeyFile, { encoding: 'utf8' }); + if (!options.key || !options.cert) { + const contents = await fs.readFile(options.tlsCertificateKeyFile); + options.key ??= contents; + options.cert ??= contents; + } } } if (typeof options.srvHost === 'string') { @@ -787,6 +791,7 @@ export interface MongoOptions * |:----------------------|:----------------------------------------------|:-------------------| * | `ca` | `tlsCAFile` | `string` | * | `crl` | N/A | `string` | + * | `cert` | `tlsCertificateKeyFile` | `string` | * | `key` | `tlsCertificateKeyFile` | `string` | * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | @@ -804,9 +809,9 @@ export interface MongoOptions * * The files specified by the paths passed in to the `tlsCAFile` and `tlsCertificateKeyFile` fields * are read lazily on the first call to `MongoClient.connect`. Once these files have been read and - * the `ca` and `key` fields are populated, they will not be read again on subsequent calls to - * `MongoClient.connect`. As a result, until the first call to `MongoClient.connect`, the `ca` - * and `key` fields will be undefined. + * the `ca`, `cert` and `key` fields are populated, they will not be read again on subsequent calls to + * `MongoClient.connect`. As a result, until the first call to `MongoClient.connect`, the `ca`, + * `cert` and `key` fields will be undefined. */ tls: boolean; diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index 179527b1c4..f50c43a48d 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -1,7 +1,12 @@ import { expect } from 'chai'; import { promises as fs } from 'fs'; -import { LEGACY_HELLO_COMMAND, MongoClient, type MongoClientOptions } from '../mongodb'; +import { + LEGACY_HELLO_COMMAND, + MongoClient, + type MongoClientOptions, + MongoServerSelectionError +} from '../mongodb'; const REQUIRED_ENV = ['MONGODB_URI', 'SSL_KEY_FILE', 'SSL_CA_FILE']; @@ -51,11 +56,13 @@ describe('TLS Support', function () { expect(client.options).property('tlsCertificateKeyFile', TLS_CERT_KEY_FILE); expect(client.options).not.have.property('ca'); expect(client.options).not.have.property('key'); + expect(client.options).not.have.property('cert'); await client.connect(); expect(client.options).property('ca').to.exist; expect(client.options).property('key').to.exist; + expect(client.options).property('cert').to.exist; }); context('when client has been opened and closed more than once', function () { @@ -106,6 +113,44 @@ describe('TLS Support', function () { }); }); }); + + context('when tlsCertificateKeyFile is provided, but tlsCAFile is missing', () => { + let client: MongoClient; + beforeEach(() => { + client = new MongoClient(CONNECTION_STRING, { + tls: true, + tlsCertificateKeyFile: TLS_CERT_KEY_FILE, + serverSelectionTimeoutMS: 5000, + connectTimeoutMS: 5000 + }); + }); + afterEach(async () => { + if (client) await client.close(); + }); + + it('throws a MongoServerSelectionError', async () => { + const err = await client.connect().catch(e => e); + expect(err).to.be.instanceOf(MongoServerSelectionError); + }); + }); + + context('when tlsCAFile is provided, but tlsCertificateKeyFile is missing', () => { + let client: MongoClient; + beforeEach(() => { + client = new MongoClient(CONNECTION_STRING, { + tls: true, + tlsCAFile: TLS_CA_FILE + }); + }); + afterEach(async () => { + if (client) await client.close(); + }); + + it('connects without error', async () => { + const clientOrError = await client.connect().catch(e => e); + expect(clientOrError).to.be.instanceOf(MongoClient); + }); + }); }); function makeConnectionTest(connectionString: string, clientOptions?: MongoClientOptions) { diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 64ec92e2fe..c43a8a17ba 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -58,6 +58,7 @@ describe('MongoOptions', function () { expect(options).to.not.have.property('tlsCertificateKeyFilePassword'); expect(options).to.not.have.property('key'); expect(options).to.not.have.property('ca'); + expect(options).to.not.have.property('cert'); expect(options).to.have.property('tlsCertificateKeyFile', filename); expect(options).to.have.property('tlsCAFile', filename); expect(options).has.property('passphrase', 'tlsCertificateKeyFilePassword');