Skip to content
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

feat(NODE-5875): provide native crypto hooks for OpenSSL 3 #25

Merged
merged 20 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .github/docker/Dockerfile.glibc
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
FROM ubuntu:bionic AS build
ARG UBUNTU_VERSION=bionic
FROM ubuntu:${UBUNTU_VERSION} AS build

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

WORKDIR /mongodb-client-encryption
COPY . .

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

RUN npm run install:libmongocrypt && npm run test
RUN npm run install:libmongocrypt

ARG RUN_TEST
RUN [ -n "$RUN_TEST" ] && npm run test || echo 'skipping testing!'

FROM scratch

Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch: {}
workflow_call: {}

name: Build and Test
name: Build

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

- name: Test ${{ matrix.os }}
shell: bash
run: npm run test

- id: upload
name: Upload prebuild
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -57,7 +53,12 @@ jobs:
- name: Run Buildx
run: |
docker buildx create --name builder --bootstrap --use
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 .
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 \
.

- id: upload
name: Upload prebuild
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch: {}

name: Test

jobs:
host_tests:
strategy:
matrix:
os: [macos-latest, windows-2019]
node: [16.x, 18.x, 20.x, 22.x]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm'
registry-url: 'https://registry.npmjs.org'

- name: Build with Node.js ${{ matrix.node }} on ${{ matrix.os }}
run: node .github/scripts/libmongocrypt.mjs ${{ runner.os == 'Windows' && '--build' || '' }}
shell: bash

- name: Test ${{ matrix.os }}
shell: bash
run: npm run test

container_tests:
runs-on: ubuntu-latest
strategy:
matrix:
linux_arch: [s390x, arm64, amd64]
node: [16.x, 18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- name: Get Full Node.js Version
id: get_nodejs_version
shell: bash
run: |
echo "version=$(node --print 'process.version.slice(1)')" >> "$GITHUB_OUTPUT"
echo "ubuntu_version=$(node --print '(+process.version.slice(1).split(`.`).at(0)) > 16 ? `noble` : `bionic`')" >> "$GITHUB_OUTPUT"

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Run Buildx
run: |
docker buildx create --name builder --bootstrap --use
docker buildx build \
--platform linux/${{ matrix.linux_arch }} \
--build-arg="NODE_ARCH=${{ matrix.linux_arch == 'amd64' && 'x64' || matrix.linux_arch }}" \
--build-arg="NODE_VERSION=${{ steps.get_nodejs_version.outputs.version }}" \
--build-arg="UBUNTU_VERSION=${{ steps.get_nodejs_version.outputs.ubuntu_version }}" \
--build-arg="RUN_TEST=true" \
--output type=local,dest=./prebuilds,platform-split=false \
-f ./.github/docker/Dockerfile.glibc \
.
61 changes: 46 additions & 15 deletions addon/mongocrypt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Function MongoCrypt::Init(Napi::Env env) {
InstanceMethod("makeDataKeyContext", &MongoCrypt::MakeDataKeyContext),
InstanceMethod("makeRewrapManyDataKeyContext", &MongoCrypt::MakeRewrapManyDataKeyContext),
InstanceAccessor("status", &MongoCrypt::Status, nullptr),
InstanceAccessor("cryptoHooksProvider", &MongoCrypt::CryptoHooksProvider, nullptr),
InstanceAccessor(
"cryptSharedLibVersionInfo", &MongoCrypt::CryptSharedLibVersionInfo, nullptr),
StaticValue("libmongocryptVersion", String::New(env, mongocrypt_version(nullptr)))});
Expand Down Expand Up @@ -201,7 +202,7 @@ static bool aes_256_generic_hook(MongoCrypt* mongoCrypt,
return true;
}

bool MongoCrypt::setupCryptoHooks() {
std::unique_ptr<CryptoHooks> MongoCrypt::createJSCryptoHooks() {
auto aes_256_cbc_encrypt = [](void* ctx,
mongocrypt_binary_t* key,
mongocrypt_binary_t* iv,
Expand Down Expand Up @@ -398,26 +399,47 @@ bool MongoCrypt::setupCryptoHooks() {
return true;
};

return std::make_unique<CryptoHooks>(CryptoHooks{"js",
aes_256_cbc_encrypt,
aes_256_cbc_decrypt,
random,
hmac_sha_512,
hmac_sha_256,
sha_256,
aes_256_ctr_encrypt,
aes_256_ctr_decrypt,
nullptr,
sign_rsa_sha256,
this});
}

bool MongoCrypt::installCryptoHooks() {
const auto& hooks = *_crypto_hooks;
if (!mongocrypt_setopt_crypto_hooks(_mongo_crypt.get(),
aes_256_cbc_encrypt,
aes_256_cbc_decrypt,
random,
hmac_sha_512,
hmac_sha_256,
sha_256,
this)) {
hooks.aes_256_cbc_encrypt,
hooks.aes_256_cbc_decrypt,
hooks.random,
hooks.hmac_sha_512,
hooks.hmac_sha_256,
hooks.sha_256,
hooks.ctx)) {
return false;
}

// Added after `mongocrypt_setopt_crypto_hooks`, they should be treated as the same during
// configuration
if (!mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(
_mongo_crypt.get(), sign_rsa_sha256, this)) {
_mongo_crypt.get(), hooks.sign_rsa_sha256, this)) {
return false;
}

if (!mongocrypt_setopt_aes_256_ctr(
_mongo_crypt.get(), aes_256_ctr_encrypt, aes_256_ctr_decrypt, this)) {
_mongo_crypt.get(), hooks.aes_256_ctr_encrypt, hooks.aes_256_ctr_decrypt, hooks.ctx)) {
return false;
}

if (hooks.aes_256_ecb_encrypt &&
!mongocrypt_setopt_aes_256_ecb(_mongo_crypt.get(), hooks.aes_256_ecb_encrypt, hooks.ctx)) {
return false;
baileympearson marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -472,7 +494,10 @@ MongoCrypt::MongoCrypt(const CallbackInfo& info)
}
}

if (options.Has("cryptoCallbacks")) {
if (!_crypto_hooks) {
_crypto_hooks = opensslcrypto::createOpenSSLCryptoHooks();
}
if (!_crypto_hooks && options.Has("cryptoCallbacks")) {
Object cryptoCallbacks = options.Get("cryptoCallbacks").ToObject();

SetCallback("aes256CbcEncryptHook", cryptoCallbacks["aes256CbcEncryptHook"]);
Expand All @@ -484,10 +509,10 @@ MongoCrypt::MongoCrypt(const CallbackInfo& info)
SetCallback("hmacSha256Hook", cryptoCallbacks["hmacSha256Hook"]);
SetCallback("sha256Hook", cryptoCallbacks["sha256Hook"]);
SetCallback("signRsaSha256Hook", cryptoCallbacks["signRsaSha256Hook"]);

if (!setupCryptoHooks()) {
throw Error::New(Env(), "unable to configure crypto hooks");
}
_crypto_hooks = createJSCryptoHooks();
}
if (_crypto_hooks && !installCryptoHooks()) {
throw Error::New(Env(), "unable to configure crypto hooks");
}

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

Value MongoCrypt::CryptoHooksProvider(const CallbackInfo& info) {
if (!_crypto_hooks)
return Env().Null();
return String::New(Env(), _crypto_hooks->id);
}

Value MongoCrypt::Status(const CallbackInfo& info) {
std::unique_ptr<mongocrypt_status_t, MongoCryptStatusDeleter> status(mongocrypt_status_new());
mongocrypt_status(_mongo_crypt.get(), status.get());
Expand Down
24 changes: 23 additions & 1 deletion addon/mongocrypt.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ extern "C" {

namespace node_mongocrypt {

struct CryptoHooks {
const char* id;
mongocrypt_crypto_fn aes_256_cbc_encrypt;
mongocrypt_crypto_fn aes_256_cbc_decrypt;
mongocrypt_random_fn random;
mongocrypt_hmac_fn hmac_sha_512;
mongocrypt_hmac_fn hmac_sha_256;
mongocrypt_hash_fn sha_256;
mongocrypt_crypto_fn aes_256_ctr_encrypt;
mongocrypt_crypto_fn aes_256_ctr_decrypt;
mongocrypt_crypto_fn aes_256_ecb_encrypt;
mongocrypt_hmac_fn sign_rsa_sha256;
void* ctx;
};

struct MongoCryptBinaryDeleter {
void operator()(mongocrypt_binary_t* binary) {
mongocrypt_binary_destroy(binary);
Expand All @@ -37,6 +52,10 @@ struct MongoCryptContextDeleter {
}
};

namespace opensslcrypto {
std::unique_ptr<CryptoHooks> createOpenSSLCryptoHooks();
}

class MongoCrypt : public Napi::ObjectWrap<MongoCrypt> {
public:
static Napi::Function Init(Napi::Env env);
Expand All @@ -51,20 +70,23 @@ class MongoCrypt : public Napi::ObjectWrap<MongoCrypt> {

Napi::Value Status(const Napi::CallbackInfo& info);
Napi::Value CryptSharedLibVersionInfo(const Napi::CallbackInfo& info);
Napi::Value CryptoHooksProvider(const Napi::CallbackInfo& info);

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

explicit MongoCrypt(const Napi::CallbackInfo& info);
bool setupCryptoHooks();
std::unique_ptr<CryptoHooks> createJSCryptoHooks();
bool installCryptoHooks();

static void logHandler(mongocrypt_log_level_t level,
const char* message,
uint32_t message_len,
void* ctx);

std::unique_ptr<CryptoHooks> _crypto_hooks;
std::unique_ptr<mongocrypt_t, MongoCryptDeleter> _mongo_crypt;
};

Expand Down
Loading