Skip to content

fix(NODE-5548): ensure that tlsCertificateKeyFile maps to cert and key #3819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 22, 2023
Merged
15 changes: 10 additions & 5 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,10 +435,14 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {

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') {
Expand Down Expand Up @@ -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` |
Expand All @@ -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;

Expand Down
47 changes: 46 additions & 1 deletion test/manual/tls_support.test.ts
Original file line number Diff line number Diff line change
@@ -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'];

Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions test/unit/mongo_client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down