Skip to content

Commit 184167e

Browse files
addaleaxnbbeeken
authored andcommitted
feat(NODE-5875): provide native crypto hooks for OpenSSL 3 (#25)
Co-authored-by: Neal Beeken <[email protected]>
1 parent 3e83400 commit 184167e

15 files changed

+978
-74
lines changed

Diff for: .github/docker/Dockerfile.glibc

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
FROM ubuntu:bionic AS build
1+
ARG UBUNTU_VERSION=bionic
2+
FROM ubuntu:${UBUNTU_VERSION} AS build
23

4+
ARG NODE_VERSION=16.20.1
35
# Possible values: s390x, arm64, x64
46
ARG NODE_ARCH
5-
ADD https://nodejs.org/dist/v16.20.1/node-v16.20.1-linux-${NODE_ARCH}.tar.gz /
6-
RUN mkdir -p /nodejs && tar -xzf /node-v16.20.1-linux-${NODE_ARCH}.tar.gz --strip-components=1 -C /nodejs
7+
ADD https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz /
8+
RUN mkdir -p /nodejs && tar -xzf /node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz --strip-components=1 -C /nodejs
79
ENV PATH=$PATH:/nodejs/bin
810

911
WORKDIR /mongodb-client-encryption
1012
COPY . .
1113

1214
RUN apt-get -qq update && apt-get -qq install -y python3 build-essential && ldd --version
1315

14-
RUN npm run install:libmongocrypt && npm run test
16+
RUN npm run install:libmongocrypt
17+
18+
ARG RUN_TEST
19+
RUN [ -n "$RUN_TEST" ] && npm run test || echo 'skipping testing!'
1520

1621
FROM scratch
1722

Diff for: .github/workflows/build.yml

+7-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_dispatch: {}
55
workflow_call: {}
66

7-
name: Build and Test
7+
name: Build
88

99
permissions:
1010
contents: write
@@ -24,10 +24,6 @@ jobs:
2424
run: node .github/scripts/libmongocrypt.mjs ${{ runner.os == 'Windows' && '--build' || '' }}
2525
shell: bash
2626

27-
- name: Test ${{ matrix.os }}
28-
shell: bash
29-
run: npm run test
30-
3127
- id: upload
3228
name: Upload prebuild
3329
uses: actions/upload-artifact@v4
@@ -57,7 +53,12 @@ jobs:
5753
- name: Run Buildx
5854
run: |
5955
docker buildx create --name builder --bootstrap --use
60-
docker buildx build --platform linux/${{ matrix.linux_arch }} --build-arg NODE_ARCH=${{ matrix.linux_arch == 'amd64' && 'x64' || matrix.linux_arch }} --output type=local,dest=./prebuilds,platform-split=false -f ./.github/docker/Dockerfile.glibc .
56+
docker buildx build \
57+
--platform linux/${{ matrix.linux_arch }} \
58+
--build-arg="NODE_ARCH=${{ matrix.linux_arch == 'amd64' && 'x64' || matrix.linux_arch }}" \
59+
--output type=local,dest=./prebuilds,platform-split=false \
60+
-f ./.github/docker/Dockerfile.glibc \
61+
.
6162
6263
- id: upload
6364
name: Upload prebuild

Diff for: .github/workflows/test.yml

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
on:
2+
push:
3+
branches: [main]
4+
pull_request:
5+
branches: [main]
6+
workflow_dispatch: {}
7+
8+
name: Test
9+
10+
jobs:
11+
host_tests:
12+
strategy:
13+
matrix:
14+
os: [macos-latest, windows-2019]
15+
node: [16.x, 18.x, 20.x, 22.x]
16+
runs-on: ${{ matrix.os }}
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: ${{ matrix.node }}
23+
cache: 'npm'
24+
registry-url: 'https://registry.npmjs.org'
25+
26+
- name: Build with Node.js ${{ matrix.node }} on ${{ matrix.os }}
27+
run: node .github/scripts/libmongocrypt.mjs ${{ runner.os == 'Windows' && '--build' || '' }}
28+
shell: bash
29+
30+
- name: Test ${{ matrix.os }}
31+
shell: bash
32+
run: npm run test
33+
34+
container_tests:
35+
runs-on: ubuntu-latest
36+
strategy:
37+
matrix:
38+
linux_arch: [s390x, arm64, amd64]
39+
node: [16.x, 18.x, 20.x, 22.x]
40+
steps:
41+
- uses: actions/checkout@v4
42+
43+
- uses: actions/setup-node@v4
44+
with:
45+
node-version: ${{ matrix.node }}
46+
47+
- name: Get Full Node.js Version
48+
id: get_nodejs_version
49+
shell: bash
50+
run: |
51+
echo "version=$(node --print 'process.version.slice(1)')" >> "$GITHUB_OUTPUT"
52+
echo "ubuntu_version=$(node --print '(+process.version.slice(1).split(`.`).at(0)) > 16 ? `noble` : `bionic`')" >> "$GITHUB_OUTPUT"
53+
54+
- name: Set up QEMU
55+
uses: docker/setup-qemu-action@v3
56+
57+
- name: Set up Docker Buildx
58+
uses: docker/setup-buildx-action@v3
59+
60+
- name: Run Buildx
61+
run: |
62+
docker buildx create --name builder --bootstrap --use
63+
docker buildx build \
64+
--platform linux/${{ matrix.linux_arch }} \
65+
--build-arg="NODE_ARCH=${{ matrix.linux_arch == 'amd64' && 'x64' || matrix.linux_arch }}" \
66+
--build-arg="NODE_VERSION=${{ steps.get_nodejs_version.outputs.version }}" \
67+
--build-arg="UBUNTU_VERSION=${{ steps.get_nodejs_version.outputs.ubuntu_version }}" \
68+
--build-arg="RUN_TEST=true" \
69+
--output type=local,dest=./prebuilds,platform-split=false \
70+
-f ./.github/docker/Dockerfile.glibc \
71+
.

Diff for: addon/mongocrypt.cc

+46-15
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ Function MongoCrypt::Init(Napi::Env env) {
120120
InstanceMethod("makeDataKeyContext", &MongoCrypt::MakeDataKeyContext),
121121
InstanceMethod("makeRewrapManyDataKeyContext", &MongoCrypt::MakeRewrapManyDataKeyContext),
122122
InstanceAccessor("status", &MongoCrypt::Status, nullptr),
123+
InstanceAccessor("cryptoHooksProvider", &MongoCrypt::CryptoHooksProvider, nullptr),
123124
InstanceAccessor(
124125
"cryptSharedLibVersionInfo", &MongoCrypt::CryptSharedLibVersionInfo, nullptr),
125126
StaticValue("libmongocryptVersion", String::New(env, mongocrypt_version(nullptr)))});
@@ -201,7 +202,7 @@ static bool aes_256_generic_hook(MongoCrypt* mongoCrypt,
201202
return true;
202203
}
203204

204-
bool MongoCrypt::setupCryptoHooks() {
205+
std::unique_ptr<CryptoHooks> MongoCrypt::createJSCryptoHooks() {
205206
auto aes_256_cbc_encrypt = [](void* ctx,
206207
mongocrypt_binary_t* key,
207208
mongocrypt_binary_t* iv,
@@ -398,26 +399,47 @@ bool MongoCrypt::setupCryptoHooks() {
398399
return true;
399400
};
400401

402+
return std::make_unique<CryptoHooks>(CryptoHooks{"js",
403+
aes_256_cbc_encrypt,
404+
aes_256_cbc_decrypt,
405+
random,
406+
hmac_sha_512,
407+
hmac_sha_256,
408+
sha_256,
409+
aes_256_ctr_encrypt,
410+
aes_256_ctr_decrypt,
411+
nullptr,
412+
sign_rsa_sha256,
413+
this});
414+
}
415+
416+
bool MongoCrypt::installCryptoHooks() {
417+
const auto& hooks = *_crypto_hooks;
401418
if (!mongocrypt_setopt_crypto_hooks(_mongo_crypt.get(),
402-
aes_256_cbc_encrypt,
403-
aes_256_cbc_decrypt,
404-
random,
405-
hmac_sha_512,
406-
hmac_sha_256,
407-
sha_256,
408-
this)) {
419+
hooks.aes_256_cbc_encrypt,
420+
hooks.aes_256_cbc_decrypt,
421+
hooks.random,
422+
hooks.hmac_sha_512,
423+
hooks.hmac_sha_256,
424+
hooks.sha_256,
425+
hooks.ctx)) {
409426
return false;
410427
}
411428

412429
// Added after `mongocrypt_setopt_crypto_hooks`, they should be treated as the same during
413430
// configuration
414431
if (!mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(
415-
_mongo_crypt.get(), sign_rsa_sha256, this)) {
432+
_mongo_crypt.get(), hooks.sign_rsa_sha256, this)) {
416433
return false;
417434
}
418435

419436
if (!mongocrypt_setopt_aes_256_ctr(
420-
_mongo_crypt.get(), aes_256_ctr_encrypt, aes_256_ctr_decrypt, this)) {
437+
_mongo_crypt.get(), hooks.aes_256_ctr_encrypt, hooks.aes_256_ctr_decrypt, hooks.ctx)) {
438+
return false;
439+
}
440+
441+
if (hooks.aes_256_ecb_encrypt &&
442+
!mongocrypt_setopt_aes_256_ecb(_mongo_crypt.get(), hooks.aes_256_ecb_encrypt, hooks.ctx)) {
421443
return false;
422444
}
423445

@@ -472,7 +494,10 @@ MongoCrypt::MongoCrypt(const CallbackInfo& info)
472494
}
473495
}
474496

475-
if (options.Has("cryptoCallbacks")) {
497+
if (!_crypto_hooks) {
498+
_crypto_hooks = opensslcrypto::createOpenSSLCryptoHooks();
499+
}
500+
if (!_crypto_hooks && options.Has("cryptoCallbacks")) {
476501
Object cryptoCallbacks = options.Get("cryptoCallbacks").ToObject();
477502

478503
SetCallback("aes256CbcEncryptHook", cryptoCallbacks["aes256CbcEncryptHook"]);
@@ -484,10 +509,10 @@ MongoCrypt::MongoCrypt(const CallbackInfo& info)
484509
SetCallback("hmacSha256Hook", cryptoCallbacks["hmacSha256Hook"]);
485510
SetCallback("sha256Hook", cryptoCallbacks["sha256Hook"]);
486511
SetCallback("signRsaSha256Hook", cryptoCallbacks["signRsaSha256Hook"]);
487-
488-
if (!setupCryptoHooks()) {
489-
throw Error::New(Env(), "unable to configure crypto hooks");
490-
}
512+
_crypto_hooks = createJSCryptoHooks();
513+
}
514+
if (_crypto_hooks && !installCryptoHooks()) {
515+
throw Error::New(Env(), "unable to configure crypto hooks");
491516
}
492517

493518
if (options.Has("cryptSharedLibSearchPaths")) {
@@ -535,6 +560,12 @@ Value MongoCrypt::CryptSharedLibVersionInfo(const CallbackInfo& info) {
535560
return ret;
536561
}
537562

563+
Value MongoCrypt::CryptoHooksProvider(const CallbackInfo& info) {
564+
if (!_crypto_hooks)
565+
return Env().Null();
566+
return String::New(Env(), _crypto_hooks->id);
567+
}
568+
538569
Value MongoCrypt::Status(const CallbackInfo& info) {
539570
std::unique_ptr<mongocrypt_status_t, MongoCryptStatusDeleter> status(mongocrypt_status_new());
540571
mongocrypt_status(_mongo_crypt.get(), status.get());

Diff for: addon/mongocrypt.h

+23-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@ extern "C" {
1919

2020
namespace node_mongocrypt {
2121

22+
struct CryptoHooks {
23+
const char* id;
24+
mongocrypt_crypto_fn aes_256_cbc_encrypt;
25+
mongocrypt_crypto_fn aes_256_cbc_decrypt;
26+
mongocrypt_random_fn random;
27+
mongocrypt_hmac_fn hmac_sha_512;
28+
mongocrypt_hmac_fn hmac_sha_256;
29+
mongocrypt_hash_fn sha_256;
30+
mongocrypt_crypto_fn aes_256_ctr_encrypt;
31+
mongocrypt_crypto_fn aes_256_ctr_decrypt;
32+
mongocrypt_crypto_fn aes_256_ecb_encrypt;
33+
mongocrypt_hmac_fn sign_rsa_sha256;
34+
void* ctx;
35+
};
36+
2237
struct MongoCryptBinaryDeleter {
2338
void operator()(mongocrypt_binary_t* binary) {
2439
mongocrypt_binary_destroy(binary);
@@ -37,6 +52,10 @@ struct MongoCryptContextDeleter {
3752
}
3853
};
3954

55+
namespace opensslcrypto {
56+
std::unique_ptr<CryptoHooks> createOpenSSLCryptoHooks();
57+
}
58+
4059
class MongoCrypt : public Napi::ObjectWrap<MongoCrypt> {
4160
public:
4261
static Napi::Function Init(Napi::Env env);
@@ -51,20 +70,23 @@ class MongoCrypt : public Napi::ObjectWrap<MongoCrypt> {
5170

5271
Napi::Value Status(const Napi::CallbackInfo& info);
5372
Napi::Value CryptSharedLibVersionInfo(const Napi::CallbackInfo& info);
73+
Napi::Value CryptoHooksProvider(const Napi::CallbackInfo& info);
5474

5575
private:
5676
friend class Napi::ObjectWrap<MongoCrypt>;
5777
Napi::Function GetCallback(const char* name);
5878
void SetCallback(const char* name, Napi::Value fn);
5979

6080
explicit MongoCrypt(const Napi::CallbackInfo& info);
61-
bool setupCryptoHooks();
81+
std::unique_ptr<CryptoHooks> createJSCryptoHooks();
82+
bool installCryptoHooks();
6283

6384
static void logHandler(mongocrypt_log_level_t level,
6485
const char* message,
6586
uint32_t message_len,
6687
void* ctx);
6788

89+
std::unique_ptr<CryptoHooks> _crypto_hooks;
6890
std::unique_ptr<mongocrypt_t, MongoCryptDeleter> _mongo_crypt;
6991
};
7092

0 commit comments

Comments
 (0)