Skip to content

Commit 9b910d4

Browse files
committed
chore: add crypto callbacks to src
1 parent 41c17de commit 9b910d4

File tree

3 files changed

+132
-89
lines changed

3 files changed

+132
-89
lines changed

src/crypto_callbacks.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import * as crypto from 'crypto';
2+
3+
type AES256Callback = (key: Buffer, iv: Buffer, input: Buffer, output: Buffer) => number | Error;
4+
5+
function makeAES256Hook(
6+
method: 'createCipheriv' | 'createDecipheriv',
7+
mode: 'aes-256-cbc' | 'aes-256-ctr'
8+
): AES256Callback {
9+
return function (key: Buffer, iv: Buffer, input: Buffer, output: Buffer): number | Error {
10+
let result;
11+
12+
try {
13+
const cipher = crypto[method](mode, key, iv);
14+
cipher.setAutoPadding(false);
15+
result = cipher.update(input);
16+
const final = cipher.final();
17+
if (final.length > 0) {
18+
result = Buffer.concat([result, final]);
19+
}
20+
} catch (e) {
21+
return e;
22+
}
23+
24+
result.copy(output);
25+
return result.length;
26+
};
27+
}
28+
29+
function randomHook(buffer: Buffer, count: number): number | Error {
30+
try {
31+
crypto.randomFillSync(buffer, 0, count);
32+
} catch (e) {
33+
return e;
34+
}
35+
return count;
36+
}
37+
38+
function sha256Hook(input: Buffer, output: Buffer): number | Error {
39+
let result;
40+
try {
41+
result = crypto.createHash('sha256').update(input).digest();
42+
} catch (e) {
43+
return e;
44+
}
45+
46+
result.copy(output);
47+
return result.length;
48+
}
49+
50+
type HMACHook = (key: Buffer, input: Buffer, output: Buffer) => number | Error;
51+
52+
export function makeHmacHook(algorithm: 'sha512' | 'sha256'): HMACHook {
53+
return (key: Buffer, input: Buffer, output: Buffer): number | Error => {
54+
let result;
55+
try {
56+
result = crypto.createHmac(algorithm, key).update(input).digest();
57+
} catch (e) {
58+
return e;
59+
}
60+
61+
result.copy(output);
62+
return result.length;
63+
};
64+
}
65+
66+
function signRsaSha256Hook(key: Buffer, input: Buffer, output: Buffer): number | Error {
67+
let result;
68+
try {
69+
const signer = crypto.createSign('sha256WithRSAEncryption');
70+
const privateKey = Buffer.from(
71+
`-----BEGIN PRIVATE KEY-----\n${key.toString('base64')}\n-----END PRIVATE KEY-----\n`
72+
);
73+
74+
result = signer.update(input).end().sign(privateKey);
75+
} catch (e) {
76+
return e;
77+
}
78+
79+
result.copy(output);
80+
return result.length;
81+
}
82+
83+
const aes256CbcEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-cbc');
84+
const aes256CbcDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-cbc');
85+
const aes256CtrEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-ctr');
86+
const aes256CtrDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-ctr');
87+
const hmacSha512Hook = makeHmacHook('sha512');
88+
const hmacSha256Hook = makeHmacHook('sha256');
89+
90+
/**
91+
* @public
92+
*
93+
* A JS implementation of crypto callbacks used for encryption.
94+
* These are only necessary on versions of Node.js that do not bundle OpenSSL 3.x
95+
*/
96+
export const cryptoCallbacks = {
97+
randomHook,
98+
sha256Hook,
99+
signRsaSha256Hook,
100+
aes256CbcEncryptHook,
101+
aes256CbcDecryptHook,
102+
aes256CtrEncryptHook,
103+
aes256CtrDecryptHook,
104+
hmacSha512Hook,
105+
hmacSha256Hook
106+
};

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import bindings = require('bindings');
22

33
const mc = bindings('mongocrypt');
44

5+
export { cryptoCallbacks } from './crypto_callbacks';
6+
57
export interface MongoCryptKMSRequest {
68
addResponse(response: Uint8Array): void;
79
readonly status: MongoCryptStatus;
@@ -62,7 +64,7 @@ export interface MongoCrypt {
6264
* When true, creates a `mongocrypt_ctx_explicit_encrypt_expression` context.
6365
* When false, creates a `mongocrypt_ctx_explicit_encrypt`
6466
*/
65-
expressionMode: boolean;
67+
expressionMode?: boolean;
6668
}
6769
): MongoCryptContext;
6870
makeDecryptionContext(buffer: Uint8Array): MongoCryptContext;

test/crypto.test.ts

Lines changed: 23 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,12 @@
11
import * as semver from 'semver';
22
import * as process from 'node:process';
3-
import * as crypto from 'node:crypto';
43
import * as path from 'node:path';
54
import * as fs from 'node:fs';
65
import * as sinon from 'sinon';
76
import { EJSON, BSON, Binary } from 'bson';
8-
import { MongoCrypt, MongoCryptConstructor } from '../src';
7+
import { MongoCrypt, MongoCryptConstructor, cryptoCallbacks } from '../src';
98
import { expect } from 'chai';
109

11-
function makeAES256Hook(method, mode) {
12-
return function (key, iv, input, output) {
13-
let result;
14-
try {
15-
const cipher = crypto[method](mode, key, iv);
16-
cipher.setAutoPadding(false);
17-
result = cipher.update(input);
18-
const final = cipher.final();
19-
if (final.length > 0) {
20-
result = Buffer.concat([result, final]);
21-
}
22-
} catch (e) {
23-
return e;
24-
}
25-
result.copy(output);
26-
return result.length;
27-
};
28-
}
29-
30-
function randomHook(buffer, count) {
31-
try {
32-
crypto.randomFillSync(buffer, 0, count);
33-
} catch (e) {
34-
return e;
35-
}
36-
return count;
37-
}
38-
39-
function sha256Hook(input, output) {
40-
let result;
41-
try {
42-
result = crypto.createHash('sha256').update(input).digest();
43-
} catch (e) {
44-
return e;
45-
}
46-
result.copy(output);
47-
return result.length;
48-
}
49-
50-
function makeHmacHook(algorithm) {
51-
return (key, input, output) => {
52-
let result;
53-
try {
54-
result = crypto.createHmac(algorithm, key).update(input).digest();
55-
} catch (e) {
56-
return e;
57-
}
58-
result.copy(output);
59-
return result.length;
60-
};
61-
}
62-
63-
function signRsaSha256Hook(key, input, output) {
64-
let result;
65-
try {
66-
const signer = crypto.createSign('sha256WithRSAEncryption');
67-
const privateKey = Buffer.from(
68-
`-----BEGIN PRIVATE KEY-----\n${key.toString('base64')}\n-----END PRIVATE KEY-----\n`
69-
);
70-
result = signer.update(input).end().sign(privateKey);
71-
} catch (e) {
72-
return e;
73-
}
74-
result.copy(output);
75-
return result.length;
76-
}
77-
78-
const aes256CbcEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-cbc');
79-
const aes256CbcDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-cbc');
80-
const aes256CtrEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-ctr');
81-
const aes256CtrDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-ctr');
82-
const hmacSha512Hook = makeHmacHook('sha512');
83-
const hmacSha256Hook = makeHmacHook('sha256');
84-
85-
export const cryptoCallbacks = {
86-
randomHook,
87-
sha256Hook,
88-
signRsaSha256Hook,
89-
aes256CbcEncryptHook,
90-
aes256CbcDecryptHook,
91-
aes256CtrEncryptHook,
92-
aes256CtrDecryptHook,
93-
hmacSha512Hook,
94-
hmacSha256Hook
95-
};
96-
9710
const NEED_MONGO_KEYS = 3;
9811
const READY = 5;
9912
const ERROR = 0;
@@ -154,6 +67,17 @@ describe('Crypto hooks', () => {
15467
}
15568
});
15669

70+
it('reports crypto hook provider as `native_openssl`', () => {
71+
const mongoCryptOptions: ConstructorParameters<MongoCryptConstructor>[0] = {
72+
kmsProviders: BSON.serialize(kmsProviders),
73+
cryptoCallbacks
74+
};
75+
76+
const mongoCrypt = new MongoCrypt(mongoCryptOptions);
77+
78+
expect(mongoCrypt).to.have.property('cryptoHooksProvider', 'native_openssl');
79+
});
80+
15781
it('should use native crypto hooks', async () => {
15882
const spiedCallbacks = Object.fromEntries(
15983
Object.entries(cryptoCallbacks).map(([name, hook]) => [name, sinon.spy(hook)])
@@ -184,6 +108,17 @@ describe('Crypto hooks', () => {
184108
}
185109
});
186110

111+
it('reports crypto hook provider as `js`', () => {
112+
const mongoCryptOptions: ConstructorParameters<MongoCryptConstructor>[0] = {
113+
kmsProviders: BSON.serialize(kmsProviders),
114+
cryptoCallbacks
115+
};
116+
117+
const mongoCrypt = new MongoCrypt(mongoCryptOptions);
118+
119+
expect(mongoCrypt).to.have.property('cryptoHooksProvider', 'js');
120+
});
121+
187122
it('should use js crypto hooks', async () => {
188123
const spiedCallbacks = Object.fromEntries(
189124
Object.entries(cryptoCallbacks).map(([name, hook]) => [name, sinon.spy(hook)])

0 commit comments

Comments
 (0)