From ceb18e5e59d917195c1a4463704ca01b530f17a6 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 26 Aug 2024 22:02:55 -0700 Subject: [PATCH 01/22] Integrate mongodb-crypt module into mongo-java-driver as a new Gradle project. JAVA-5582 --- build.gradle | 1 - driver-core/build.gradle | 2 +- graalvm-native-image-app/build.gradle | 2 +- mongocrypt/.gitignore | 39 + mongocrypt/build.gradle.kts | 189 +++ .../com/mongodb/crypt/capi/BinaryHolder.java | 45 + .../src/main/com/mongodb/crypt/capi/CAPI.java | 1165 +++++++++++++++++ .../com/mongodb/crypt/capi/CAPIHelper.java | 94 ++ .../mongodb/crypt/capi/CipherCallback.java | 92 ++ .../mongodb/crypt/capi/DisposableMemory.java | 31 + .../com/mongodb/crypt/capi/JULLogger.java | 130 ++ .../main/com/mongodb/crypt/capi/Logger.java | 144 ++ .../main/com/mongodb/crypt/capi/Loggers.java | 50 + .../com/mongodb/crypt/capi/MacCallback.java | 60 + .../crypt/capi/MessageDigestCallback.java | 55 + .../capi/MongoAwsKmsProviderOptions.java | 104 ++ .../com/mongodb/crypt/capi/MongoCrypt.java | 100 ++ .../mongodb/crypt/capi/MongoCryptContext.java | 137 ++ .../crypt/capi/MongoCryptContextImpl.java | 164 +++ .../crypt/capi/MongoCryptException.java | 67 + .../mongodb/crypt/capi/MongoCryptImpl.java | 423 ++++++ .../mongodb/crypt/capi/MongoCryptOptions.java | 284 ++++ .../com/mongodb/crypt/capi/MongoCrypts.java | 38 + .../crypt/capi/MongoDataKeyOptions.java | 125 ++ .../capi/MongoExplicitEncryptOptions.java | 227 ++++ .../mongodb/crypt/capi/MongoKeyDecryptor.java | 76 ++ .../crypt/capi/MongoKeyDecryptorImpl.java | 104 ++ .../capi/MongoLocalKmsProviderOptions.java | 83 ++ .../capi/MongoRewrapManyDataKeyOptions.java | 104 ++ .../com/mongodb/crypt/capi/SLF4JLogger.java | 110 ++ .../crypt/capi/SecureRandomCallback.java | 51 + .../crypt/capi/SigningRSAESPKCSCallback.java | 73 ++ .../com/mongodb/crypt/capi/package-info.java | 18 + .../META-INF/native-image/jni-config.json | 180 +++ .../META-INF/native-image/reflect-config.json | 134 ++ settings.gradle | 1 + 36 files changed, 4699 insertions(+), 3 deletions(-) create mode 100644 mongocrypt/.gitignore create mode 100644 mongocrypt/build.gradle.kts create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/CipherCallback.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/JULLogger.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/Logger.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/Loggers.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MacCallback.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java create mode 100644 mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java create mode 100644 mongocrypt/src/main/resources/META-INF/native-image/jni-config.json create mode 100644 mongocrypt/src/main/resources/META-INF/native-image/reflect-config.json diff --git a/build.gradle b/build.gradle index 86fe2ad12d4..543e6de19ce 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,6 @@ ext { zstdVersion = '1.5.5-3' awsSdkV2Version = '2.18.9' awsSdkV1Version = '1.12.337' - mongoCryptVersion = '1.11.0' projectReactorVersion = '2022.0.0' junitBomVersion = '5.10.2' logbackVersion = '1.3.14' diff --git a/driver-core/build.gradle b/driver-core/build.gradle index 1f7d06f93f2..d8a70f10bb7 100644 --- a/driver-core/build.gradle +++ b/driver-core/build.gradle @@ -39,6 +39,7 @@ dependencies { implementation project(path: ':bson-record-codec', configuration: 'default') implementation project(path: ':bson-kotlin', configuration: 'default'), optional implementation project(path: ':bson-kotlinx', configuration: 'default'), optional + implementation project(path: ':mongocrypt', configuration: 'default'), optional implementation "com.github.jnr:jnr-unixsocket:$jnrUnixsocketVersion", optional api platform("io.netty:netty-bom:$nettyVersion") @@ -55,7 +56,6 @@ dependencies { implementation "org.xerial.snappy:snappy-java:$snappyVersion", optional implementation "com.github.luben:zstd-jni:$zstdVersion", optional - implementation "org.mongodb:mongodb-crypt:$mongoCryptVersion", optional testImplementation project(':bson').sourceSets.test.output testImplementation('org.junit.jupiter:junit-jupiter-api') diff --git a/graalvm-native-image-app/build.gradle b/graalvm-native-image-app/build.gradle index c34d8623b15..f17244b3561 100644 --- a/graalvm-native-image-app/build.gradle +++ b/graalvm-native-image-app/build.gradle @@ -82,12 +82,12 @@ dependencies { implementation project(path:':driver-sync', configuration:'archives') implementation project(path:':driver-reactive-streams', configuration:'archives') implementation project(path:':driver-legacy', configuration:'archives') + implementation project(path: ':mongocrypt', configuration: 'archives') // note that as a result of these `sourceSets` dependencies, `driver-sync/src/test/resources/logback-test.xml` is used implementation project(':driver-core').sourceSets.test.output implementation project(':driver-sync').sourceSets.test.output implementation project(':driver-legacy').sourceSets.test.output implementation project(':driver-reactive-streams').sourceSets.test.output - implementation "org.mongodb:mongodb-crypt:$mongoCryptVersion" implementation 'org.slf4j:slf4j-api:2.0.12' implementation "ch.qos.logback:logback-classic:$logbackVersion" implementation platform("io.projectreactor:reactor-bom:$projectReactorVersion") diff --git a/mongocrypt/.gitignore b/mongocrypt/.gitignore new file mode 100644 index 00000000000..64e5adbd91a --- /dev/null +++ b/mongocrypt/.gitignore @@ -0,0 +1,39 @@ +*~ +.#* +.git +*# + +# os x stuff +*Thumbs.db* +*.DS_Store + +# Build artifacts +build +out + +# Eclipse files +.classpath +.project +.settings + +# Intellij IDEA files +*.ipr +*.iws +*.iml +*.idea +workspace.xml +atlassian-ide-plugin.xml + +# gradle +.gradle + +# local settings +**/gradle.properties +local.properties + +# jenv +.java-version + +# bin +/bin +/benchmarks/bin diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts new file mode 100644 index 00000000000..b27d42ecada --- /dev/null +++ b/mongocrypt/build.gradle.kts @@ -0,0 +1,189 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import de.undercouch.gradle.tasks.download.Download +import java.io.ByteArrayOutputStream + +buildscript { + repositories { + mavenCentral() + google() + } + dependencies { + "classpath"(group = "net.java.dev.jna", name = "jna", version = "5.11.0") + } +} + +plugins { + // Needed to download libmongocrypt from s3. + id("de.undercouch.download") version "5.6.0" +} + +group = "org.mongodb" +base.archivesBaseName = "mongodb-crypt" +description = "MongoDB client-side crypto support" + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + api(project(path = ":bson", configuration = "default")) + api("net.java.dev.jna:jna:5.11.0") + + // Tests + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("ch.qos.logback:logback-classic:1.2.11") +} + +/* + * Git version information + */ +// Returns a String representing the output of `git describe` +val gitDescribe by lazy { + val describeStdOut = ByteArrayOutputStream() + exec { + commandLine = listOf("git", "describe", "--tags", "--always", "--dirty") + standardOutput = describeStdOut + } + describeStdOut.toString().trim() +} + +val isJavaTag by lazy { gitDescribe.startsWith("java") } +val gitVersion by lazy { gitDescribe.subSequence(gitDescribe.toCharArray().indexOfFirst { it.isDigit() }, gitDescribe.length).toString() } + +val defaultDownloadRevision = "9a88ac5698e8e3ffcd6580b98c247f0126f26c40" // r.1.11.0 + +/* + * Jna copy or download resources + */ +val jnaDownloadsDir = "$buildDir/jnaLibs/downloads/" +val jnaResourcesDir = "$buildDir/jnaLibs/resources/" +val jnaLibPlatform: String = if (com.sun.jna.Platform.RESOURCE_PREFIX.startsWith("darwin")) "darwin" else com.sun.jna.Platform.RESOURCE_PREFIX +val jnaLibsPath: String = System.getProperty("jnaLibsPath", "${jnaResourcesDir}${jnaLibPlatform}") +val jnaResources: String = System.getProperty("jna.library.path", jnaLibsPath) + +// Download jnaLibs that match the git to jnaResourcesBuildDir +val downloadRevision: String = System.getProperties().computeIfAbsent("gitRevision") { k -> defaultDownloadRevision }.toString() +val binariesArchiveName = "libmongocrypt-java.tar.gz" + +val downloadUrl: String = "https://mciuploads.s3.amazonaws.com/libmongocrypt/java/$downloadRevision/$binariesArchiveName" + +val jnaMapping: Map = mapOf( + "rhel-62-64-bit" to "linux-x86-64", + "rhel72-zseries-test" to "linux-s390x", + "rhel-71-ppc64el" to "linux-ppc64le", + "ubuntu1604-arm64" to "linux-aarch64", + "windows-test" to "win32-x86-64", + "macos" to "darwin" +) + +sourceSets { + main { + java { + resources { + srcDirs(jnaResourcesDir) + } + } + } +} + +tasks.register("downloadJava") { + src(downloadUrl) + dest("${jnaDownloadsDir}/$binariesArchiveName") + overwrite(true) +} + +// The `processResources` task (defined by the `java-library` plug-in) consumes files in the main source set. +// Add a dependency on `unzipJava`. `unzipJava` adds libmongocrypt libraries to the main source set. +tasks.processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + mustRunAfter(tasks.named("unzipJava")) +} + +tasks.register("unzipJava") { + outputs.upToDateWhen { false } + from(tarTree(resources.gzip("${jnaDownloadsDir}/$binariesArchiveName"))) + include(jnaMapping.keys.flatMap { + listOf("${it}/nocrypto/**/libmongocrypt.so", "${it}/lib/**/libmongocrypt.dylib", "${it}/bin/**/mongocrypt.dll" ) + }) + eachFile { + path = "${jnaMapping[path.substringBefore("/")]}/${name}" + } + into(jnaResourcesDir) + mustRunAfter("downloadJava") + + doLast { + println("jna.library.path contents: \n ${fileTree(jnaResourcesDir).files.joinToString(",\n ")}") + } +} + +tasks.register("downloadJnaLibs") { + dependsOn("downloadJava", "unzipJava") +} + +tasks.test { + systemProperty("jna.debug_load", "true") + systemProperty("jna.library.path", jnaResources) + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } + + doFirst { + println("jna.library.path contents:") + println(fileTree(jnaResources) { + this.setIncludes(listOf("*.*")) + }.files.joinToString(",\n ", " ")) + } + dependsOn("downloadJnaLibs", "downloadJava", "unzipJava") +} + +tasks.withType { + description = """$description + | System properties: + | ================= + | + | jnaLibsPath : Custom local JNA library path for inclusion into the build (rather than downloading from s3) + | gitRevision : Optional Git Revision to download the built resources for from s3. + """.trimMargin() +} + +tasks.jar { + manifest { + attributes( + "-exportcontents" to "com.mongodb.crypt.capi.*;-noimport:=true", + "Automatic-Module-Name" to "com.mongodb.crypt.capi", + "Import-Package" to "org.bson.*", + "Build-Version" to gitVersion, + "Bundle-Version" to gitVersion, + "Bundle-Name" to "MongoCrypt", + "Bundle-SymbolicName" to "com.mongodb.crypt.capi", + "Private-Package" to "" + ) + } + + //NOTE this enables depending on the mongocrypt from driver-core + dependsOn("downloadJnaLibs") +} + +tasks.javadoc { + if (JavaVersion.current().isJava9Compatible) { + (options as StandardJavadocDocletOptions).addBooleanOption("html5", true) + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java b/mongocrypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java new file mode 100644 index 00000000000..60570bd1180 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; + +import static com.mongodb.crypt.capi.CAPI.mongocrypt_binary_destroy; + +// Wrap JNA memory and a mongocrypt_binary_t that references that memory, in order to ensure that the JNA Memory is not GC'd before the +// mongocrypt_binary_t is destroyed +class BinaryHolder implements AutoCloseable { + + private final DisposableMemory memory; + private final mongocrypt_binary_t binary; + + BinaryHolder(final DisposableMemory memory, final mongocrypt_binary_t binary) { + this.memory = memory; + this.binary = binary; + } + + mongocrypt_binary_t getBinary() { + return binary; + } + + @Override + public void close() { + mongocrypt_binary_destroy(binary); + memory.dispose(); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java new file mode 100644 index 00000000000..999190b4357 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java @@ -0,0 +1,1165 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.sun.jna.Callback; +import com.sun.jna.Memory; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.PointerType; +import com.sun.jna.ptr.PointerByReference; + +//CHECKSTYLE:OFF + +/** + * For internal use only. Not part of the public API. + */ +@SuppressWarnings("WeakerAccess") +public class CAPI { + + public static class cstring extends PointerType { + public cstring() { + super(); + } + + public cstring(String string) { + Pointer m = new Memory(string.length() + 1); + m.setString(0, string); + setPointer(m); + } + + public String toString() { + return getPointer().getString(0); + } + } + + + /** + * Indicates success or contains error information. + *

+ * Functions like @ref mongocrypt_ctx_encrypt_init follow a pattern to expose a + * status. A boolean is returned. True indicates success, and false indicates + * failure. On failure a status on the handle is set, and is accessible with a + * corresponding status function. E.g. @ref mongocrypt_ctx_status. + */ + public static class mongocrypt_status_t extends PointerType { + } + + /** + * Contains all options passed on initialization of a @ref mongocrypt_ctx_t. + */ + public static class mongocrypt_opts_t extends PointerType { + } + + /** + * A non-owning view of a byte buffer. + *

+ * Functions returning a mongocrypt_binary_t* expect it to be destroyed with + * mongocrypt_binary_destroy. + */ + public static class mongocrypt_binary_t extends PointerType { + // The `mongocrypt_binary_t` struct layout is part of libmongocrypt's ABI: + // typedef struct _mongocrypt_binary_t { + // void *data; + // uint32_t len; + // } mongocrypt_binary_t; + // To improve performance, fields are read directly using `getPointer` and `getInt`. + // This results in observed performance improvements over using of `mongocrypt_binary_data` and `mongocrypt_binary_len`. Refer: MONGOCRYPT-589. + public mongocrypt_binary_t() { + super(); + } + public Pointer data() { + return this.getPointer().getPointer(0); + } + public int len() { + int len = this.getPointer().getInt(Native.POINTER_SIZE); + // mongocrypt_binary_t represents length as an unsigned `uint32_t`. + // Representing `uint32_t` values greater than INT32_MAX is represented as a negative `int`. + // Throw an exception. mongocrypt_binary_t is not expected to use lengths greater than INT32_MAX. + if (len < 0) { + throw new AssertionError( + String.format("Expected mongocrypt_binary_t length to be non-negative, got: %d", len)); + } + return len; + + } + } + + /** + * The top-level handle to libmongocrypt. + *

+ * Create a mongocrypt_t handle to perform operations within libmongocrypt: + * encryption, decryption, registering log callbacks, etc. + *

+ * Functions on a mongocrypt_t are thread safe, though functions on derived + * handle (e.g. mongocrypt_encryptor_t) are not and must be owned by a single + * thread. See each handle's documentation for thread-safety considerations. + *

+ * Multiple mongocrypt_t handles may be created. + */ + public static class mongocrypt_t extends PointerType { + } + + /** + * Manages the state machine for encryption or decryption. + */ + public static class mongocrypt_ctx_t extends PointerType { + } + + /** + * Manages a single KMS HTTP request/response. + */ + public static class mongocrypt_kms_ctx_t extends PointerType { + } + + /** + * Returns the version string x.y.z for libmongocrypt. + * + * @param len an optional length of the returned string. May be NULL. + * @return the version string x.y.z for libmongocrypt. + */ + public static native cstring + mongocrypt_version(Pointer len); + + + /** + * Create a new non-owning view of a buffer (data + length). + *

+ * Use this to create a mongocrypt_binary_t used for output parameters. + * + * @return A new mongocrypt_binary_t. + */ + public static native mongocrypt_binary_t + mongocrypt_binary_new(); + + + /** + * Create a new non-owning view of a buffer (data + length). + * + * @param data A pointer to an array of bytes. This is not copied. data must outlive the binary object. + * @param len The length of the @p data byte array. + * @return A new mongocrypt_binary_t. + */ + public static native mongocrypt_binary_t + mongocrypt_binary_new_from_data(Pointer data, int len); + + + /** + * Get a pointer to the referenced data. + * + * @param binary The @ref mongocrypt_binary_t. + * @return A pointer to the referenced data. + */ + public static native Pointer + mongocrypt_binary_data(mongocrypt_binary_t binary); + + + /** + * Get the length of the referenced data. + * + * @param binary The @ref mongocrypt_binary_t. + * @return The length of the referenced data. + */ + public static native int + mongocrypt_binary_len(mongocrypt_binary_t binary); + + + /** + * Free the @ref mongocrypt_binary_t. + *

+ * This does not free the referenced data. Refer to individual function + * documentation to determine the lifetime guarantees of the underlying + * data. + * + * @param binary The mongocrypt_binary_t destroy. + */ + public static native void + mongocrypt_binary_destroy(mongocrypt_binary_t binary); + + + public static final int MONGOCRYPT_STATUS_OK = 0; + public static final int MONGOCRYPT_STATUS_ERROR_CLIENT = 1; + public static final int MONGOCRYPT_STATUS_ERROR_KMS = 2; + + /** + * Create a new status object. + *

+ * Use a new status object to retrieve the status from a handle by passing + * this as an out-parameter to functions like @ref mongocrypt_ctx_status. + * When done, destroy it with @ref mongocrypt_status_destroy. + * + * @return A new status object. + */ + public static native mongocrypt_status_t + mongocrypt_status_new(); + + /** + * Set a status object with message, type, and code. + *

+ * Use this to set the mongocrypt_status_t given in the crypto hooks. + * + * @param status The status. + * @param type The status type. + * @param code The status code. + * @param message The message. + * @param message_len The length of @p message. Pass -1 to determine the * string length with strlen (must * be NULL terminated). + */ + public static native void + mongocrypt_status_set(mongocrypt_status_t status, + int type, + int code, + cstring message, + int message_len); + + /** + * Indicates success or the type of error. + * + * @param status The status object. + * @return A @ref mongocrypt_status_type_t. + */ + + public static native int + mongocrypt_status_type(mongocrypt_status_t status); + + + /** + * Get an error code or 0. + * + * @param status The status object. + * @return An error code. + */ + public static native int + mongocrypt_status_code(mongocrypt_status_t status); + + + /** + * Get the error message associated with a status, or an empty string. + * + * @param status The status object. + * @param len an optional length of the returned string. May be NULL. + * @return An error message or an empty string. + */ + public static native cstring + mongocrypt_status_message(mongocrypt_status_t status, Pointer len); + + + /** + * Returns true if the status indicates success. + * + * @param status The status to check. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_status_ok(mongocrypt_status_t status); + + + /** + * Free the memory for a status object. + * + * @param status The status to destroy. + */ + public static native void + mongocrypt_status_destroy(mongocrypt_status_t status); + + + public static final int MONGOCRYPT_LOG_LEVEL_FATAL = 0; + public static final int MONGOCRYPT_LOG_LEVEL_ERROR = 1; + public static final int MONGOCRYPT_LOG_LEVEL_WARNING = 2; + public static final int MONGOCRYPT_LOG_LEVEL_INFO = 3; + public static final int MONGOCRYPT_LOG_LEVEL_TRACE = 4; + + + /** + * A log callback function. Set a custom log callback with mongocrypt_setopt_log_handler. + */ + public interface mongocrypt_log_fn_t extends Callback { + void log(int level, cstring message, int message_len, Pointer ctx); + } + + public interface mongocrypt_crypto_fn extends Callback { + boolean crypt(Pointer ctx, mongocrypt_binary_t key, mongocrypt_binary_t iv, mongocrypt_binary_t in, + mongocrypt_binary_t out, Pointer bytesWritten, mongocrypt_status_t status); + } + + public interface mongocrypt_hmac_fn extends Callback { + boolean hmac(Pointer ctx, mongocrypt_binary_t key, mongocrypt_binary_t in, mongocrypt_binary_t out, + mongocrypt_status_t status); + } + + public interface mongocrypt_hash_fn extends Callback { + boolean hash(Pointer ctx, mongocrypt_binary_t in, mongocrypt_binary_t out, mongocrypt_status_t status); + } + + public interface mongocrypt_random_fn extends Callback { + boolean random(Pointer ctx, mongocrypt_binary_t out, int count, mongocrypt_status_t status); + } + + /** + * Allocate a new @ref mongocrypt_t object. + *

+ * Initialize with @ref mongocrypt_init. When done, free with @ref + * mongocrypt_destroy. + * + * @return A new @ref mongocrypt_t object. + */ + public static native mongocrypt_t + mongocrypt_new(); + + /** + * Set a handler to get called on every log message. + * + * @param crypt The @ref mongocrypt_t object. + * @param log_fn The log callback. + * @param log_ctx A context passed as an argument to the log callback every + * invokation. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_setopt_log_handler(mongocrypt_t crypt, + mongocrypt_log_fn_t log_fn, + Pointer log_ctx); + + + public static native boolean + mongocrypt_setopt_crypto_hooks(mongocrypt_t crypt, + 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, + Pointer ctx); + + /** + * Set a crypto hook for the AES256-CTR operations. + * + * @param crypt The @ref mongocrypt_t object. + * @param aes_256_ctr_encrypt The crypto callback function for encrypt + * operation. + * @param aes_256_ctr_decrypt The crypto callback function for decrypt + * operation. + * @param ctx A context passed as an argument to the crypto callback + * every invocation. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_status + * + */ + public static native boolean + mongocrypt_setopt_aes_256_ctr (mongocrypt_t crypt, + mongocrypt_crypto_fn aes_256_ctr_encrypt, + mongocrypt_crypto_fn aes_256_ctr_decrypt, + Pointer ctx); + + /** + * Set a crypto hook for the RSASSA-PKCS1-v1_5 algorithm with a SHA-256 hash. + * + *

See: https://tools.ietf.org/html/rfc3447#section-8.2

+ * + *

Note: this function has the wrong name. It should be: + * mongocrypt_setopt_crypto_hook_sign_rsassa_pkcs1_v1_5

+ * + * @param crypt The @ref mongocrypt_t object. + * @param sign_rsaes_pkcs1_v1_5 The crypto callback function. + * @param sign_ctx A context passed as an argument to the crypto callback + * every invocation. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_status + */ + public static native boolean + mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5( + mongocrypt_t crypt, + mongocrypt_hmac_fn sign_rsaes_pkcs1_v1_5, + Pointer sign_ctx); + + /** + * Set a handler to get called on every log message. + * + * @param crypt The @ref mongocrypt_t object. + * @param aws_access_key_id The AWS access key ID used to generate KMS + * messages. + * @param aws_access_key_id_len The string length (in bytes) of @p + * * aws_access_key_id. Pass -1 to determine the string length with strlen (must + * * be NULL terminated). + * @param aws_secret_access_key The AWS secret access key used to generate + * KMS messages. + * @param aws_secret_access_key_len The string length (in bytes) of @p + * aws_secret_access_key. Pass -1 to determine the string length with strlen + * (must be NULL terminated). + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_setopt_kms_provider_aws(mongocrypt_t crypt, + cstring aws_access_key_id, + int aws_access_key_id_len, + cstring aws_secret_access_key, + int aws_secret_access_key_len); + + /** + * Configure a local KMS provider on the @ref mongocrypt_t object. + * + * @param crypt The @ref mongocrypt_t object. + * @param key A 64 byte master key used to encrypt and decrypt key vault keys. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_setopt_kms_provider_local(mongocrypt_t crypt, + mongocrypt_binary_t key); + + /** + * Configure KMS providers with a BSON document. + * + * @param crypt The @ref mongocrypt_t object. + * @param kms_providers A BSON document mapping the KMS provider names to credentials. + * @return A boolean indicating success. If false, an error status is set. + * @since 1.1 + */ + public static native boolean + mongocrypt_setopt_kms_providers(mongocrypt_t crypt, + mongocrypt_binary_t kms_providers); + + /** + * Set a local schema map for encryption. + * + * @param crypt The @ref mongocrypt_t object. + * @param schema_map A BSON document representing the schema map supplied by + * the user. The keys are collection namespaces and values are JSON schemas. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_status + */ + public static native boolean + mongocrypt_setopt_schema_map (mongocrypt_t crypt, mongocrypt_binary_t schema_map); + + /** + * Opt-into setting KMS providers before each KMS request. + * + * If set, before entering the MONGOCRYPT_CTX_NEED_KMS state, + * contexts will enter the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state + * and then wait for credentials to be supplied through @ref mongocrypt_ctx_provide_kms_providers. + * + * @param crypt The @ref mongocrypt_t object to update + */ + public static native void + mongocrypt_setopt_use_need_kms_credentials_state (mongocrypt_t crypt); + + + /** + * Set a local EncryptedFieldConfigMap for encryption. + * + * @param crypt The @ref mongocrypt_t object. + * @param encryptedFieldConfigMap A BSON document representing the EncryptedFieldConfigMap + * supplied by the user. The keys are collection namespaces and values are + * EncryptedFieldConfigMap documents. The viewed data copied. It is valid to + * destroy @p efc_map with @ref mongocrypt_binary_destroy immediately after. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_status + */ + public static native boolean + mongocrypt_setopt_encrypted_field_config_map (mongocrypt_t crypt, mongocrypt_binary_t encryptedFieldConfigMap); + + /** + * Opt-into skipping query analysis. + * + *

If opted in: + *

    + *
  • The crypt_shared shared library will not attempt to be loaded.
  • + *
  • A mongocrypt_ctx_t will never enter the MONGOCRYPT_CTX_NEED_MARKINGS state.
  • + *
+ * + * @param crypt The @ref mongocrypt_t object to update + * @since 1.5 + */ + public static native void + mongocrypt_setopt_bypass_query_analysis (mongocrypt_t crypt); + + /** + * Set the contention factor used for explicit encryption. + * The contention factor is only used for indexed Queryable Encryption. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param contention_factor the contention factor + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status. + * @since 1.5 + */ + public static native boolean + mongocrypt_ctx_setopt_contention_factor (mongocrypt_ctx_t ctx, long contention_factor); + + /** + * Set the index key id to use for Queryable Encryption explicit encryption. + * + * If the index key id not set, the key id from @ref mongocrypt_ctx_setopt_key_id is used. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param key_id The binary corresponding to the _id (a UUID) of the data key to use from + * the key vault collection. Note, the UUID must be encoded with RFC-4122 byte order. + * The viewed data is copied. It is valid to destroy key_id with @ref mongocrypt_binary_destroy immediately after. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status + * @since 1.5 + */ + public static native boolean + mongocrypt_ctx_setopt_index_key_id (mongocrypt_ctx_t ctx, mongocrypt_binary_t key_id); + + /** + * Append an additional search directory to the search path for loading + * the crypt_shared dynamic library. + * + * @param crypt The @ref mongocrypt_t object to update + * @param path A null-terminated sequence of bytes for the search path. On + * some filesystems, this may be arbitrary bytes. On other filesystems, this may + * be required to be a valid UTF-8 code unit sequence. If the leading element of + * the path is the literal string "$ORIGIN", that substring will be replaced + * with the directory path containing the executable libmongocrypt module. If + * the path string is literal "$SYSTEM", then libmongocrypt will defer to the + * system's library resolution mechanism to find the crypt_shared library. + * + *

If no crypt_shared dynamic library is found in any of the directories + * specified by the search paths loaded here, @ref mongocrypt_init() will still + * succeed and continue to operate without crypt_shared.

+ * + *

The search paths are searched in the order that they are appended. This + * allows one to provide a precedence in how the library will be discovered. For + * example, appending known directories before appending "$SYSTEM" will allow + * one to supersede the system's installed library, but still fall-back to it if + * the library wasn't found otherwise. If one does not ever append "$SYSTEM", + * then the system's library-search mechanism will never be consulted.

+ * + *

If an absolute path to the library is specified using @ref mongocrypt_setopt_set_crypt_shared_lib_path_override, + * then paths appended here will have no effect.

+ * @since 1.5 + */ + public static native void + mongocrypt_setopt_append_crypt_shared_lib_search_path (mongocrypt_t crypt, cstring path); + + /** + * Set a single override path for loading the crypt_shared dynamic library. + * @param crypt The @ref mongocrypt_t object to update + * @param path A null-terminated sequence of bytes for a path to the crypt_shared + * dynamic library. On some filesystems, this may be arbitrary bytes. On other + * filesystems, this may be required to be a valid UTF-8 code unit sequence. If + * the leading element of the path is the literal string `$ORIGIN`, that + * substring will be replaced with the directory path containing the executable + * libmongocrypt module. + * + *

This function will do no IO nor path validation. All validation will + * occur during the call to @ref mongocrypt_init.

+ *

If a crypt_shared library path override is specified here, then no paths given + * to @ref mongocrypt_setopt_append_crypt_shared_lib_search_path will be consulted when + * opening the crypt_shared library.

+ *

If a path is provided via this API and @ref mongocrypt_init fails to + * initialize a valid crypt_shared library instance for the path specified, then + * the initialization of mongocrypt_t will fail with an error.

+ * @since 1.5 + */ + public static native void + mongocrypt_setopt_set_crypt_shared_lib_path_override(mongocrypt_t crypt, cstring path); + + /** + * Set the query type to use for Queryable Encryption explicit encryption. + * The query type is only used for indexed Queryable Encryption. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param query_type the query type + * @param len the length + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status + */ + public static native boolean + mongocrypt_ctx_setopt_query_type (mongocrypt_ctx_t ctx, cstring query_type, int len); + + /** + * Set options for explicit encryption with the "range" algorithm. + * NOTE: "range" is currently unstable API and subject to backwards breaking changes. + * + * opts is a BSON document of the form: + * { + * "min": Optional<BSON value>, + * "max": Optional<BSON value>, + * "sparsity": Int64, + * "precision": Optional<Int32> + * "trimFactor": Optional<Int32> + * } + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param opts BSON. + * @return A boolean indicating success. If false, an error status is set. + * @since 1.7 + */ + public static native boolean + mongocrypt_ctx_setopt_algorithm_range (mongocrypt_ctx_t ctx, mongocrypt_binary_t opts); + + /** + * Initialize new @ref mongocrypt_t object. + * + * @param crypt The @ref mongocrypt_t object. + * @return A boolean indicating success. Failure may occur if previously set options are invalid. + */ + public static native boolean + mongocrypt_init(mongocrypt_t crypt); + + + /** + * Get the status associated with a @ref mongocrypt_t object. + * + * @param crypt The @ref mongocrypt_t object. + * @param status Receives the status. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_status(mongocrypt_t crypt, mongocrypt_status_t status); + + /** + * Returns true if libmongocrypt was built with native crypto support. + * + *

+ * If libmongocrypt was not built with native crypto support, setting crypto hooks is required. + *

+ * + * @return true if libmongocrypt was built with native crypto support + */ + public static native boolean + mongocrypt_is_crypto_available(); + + /** + * Destroy the @ref mongocrypt_t object. + * + * @param crypt The @ref mongocrypt_t object to destroy. + */ + public static native void + mongocrypt_destroy(mongocrypt_t crypt); + + /** + * Obtain a nul-terminated version string of the loaded crypt_shared dynamic library, + * if available. + * + * If no crypt_shared was successfully loaded, this function returns NULL. + * + * @param crypt The mongocrypt_t object after a successful call to mongocrypt_init. + * @param len an optional length of the returned string. May be NULL. + * + * @return A nul-terminated string of the dynamically loaded crypt_shared library. + * @since 1.5 + */ + public static native cstring + mongocrypt_crypt_shared_lib_version_string (mongocrypt_t crypt, Pointer len); + + /** + * Call in response to the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state + * to set per-context KMS provider settings. These follow the same format + * as @ref mongocrypt_setopt_kms_providers. If no keys are present in the + * BSON input, the KMS provider settings configured for the @ref mongocrypt_t + * at initialization are used. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param kms_providers A BSON document mapping the KMS provider names + * to credentials. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status. + */ + public static native boolean + mongocrypt_ctx_provide_kms_providers (mongocrypt_ctx_t ctx, + mongocrypt_binary_t kms_providers); + + /** + * Set the key id to use for explicit encryption. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param key_id The key_id to use. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_setopt_key_id (mongocrypt_ctx_t ctx, + mongocrypt_binary_t key_id); + + /** + * Set the keyAltName to use for explicit encryption. + * keyAltName should be a binary encoding a bson document + * with the following format: { "keyAltName" : >BSON UTF8 value< } + * + *

It is an error to set both this and the key id.

+ * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param key_alt_name The name to use. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status + */ + public static native boolean + mongocrypt_ctx_setopt_key_alt_name (mongocrypt_ctx_t ctx, + mongocrypt_binary_t key_alt_name); + + /** + * Set the keyMaterial to use for encrypting data. + * + *

+ * Pass the binary encoding of a BSON document like the following: + * { "keyMaterial" : (BSON BINARY value) } + *

+ * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param key_material The data encryption key to use. The viewed data is + * copied. It is valid to destroy @p key_material with @ref + * mongocrypt_binary_destroy immediately after. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status + */ + public static native boolean + mongocrypt_ctx_setopt_key_material (mongocrypt_ctx_t ctx, mongocrypt_binary_t key_material); + + /** + * Set the algorithm used for encryption to either + * deterministic or random encryption. This value + * should only be set when using explicit encryption. + * + * If -1 is passed in for "len", then "algorithm" is + * assumed to be a null-terminated string. + * + * Valid values for algorithm are: + * "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + * "AEAD_AES_256_CBC_HMAC_SHA_512-Randomized" + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param algorithm A string specifying the algorithm to + * use for encryption. + * @param len The length of the algorithm string. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_setopt_algorithm (mongocrypt_ctx_t ctx, + cstring algorithm, + int len); + + + /** + * Create a new uninitialized @ref mongocrypt_ctx_t. + *

+ * Initialize the context with functions like @ref mongocrypt_ctx_encrypt_init. + * When done, destroy it with @ref mongocrypt_ctx_destroy. + * + * @param crypt The @ref mongocrypt_t object. + * @return A new context. + */ + public static native mongocrypt_ctx_t + mongocrypt_ctx_new(mongocrypt_t crypt); + + + /** + * Get the status associated with a @ref mongocrypt_ctx_t object. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param status Receives the status. + * @return A boolean indicating success. + */ + + public static native boolean + mongocrypt_ctx_status(mongocrypt_ctx_t ctx, mongocrypt_status_t status); + + + /** + * Identify the AWS KMS master key to use for creating a data key. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param region The AWS region. + * @param region_len The string length of @p region. Pass -1 to determine + * the string length with strlen (must be NULL terminated). + * @param cmk The Amazon Resource Name (ARN) of the customer master key + * (CMK). + * @param cmk_len The string length of @p cmk_len. Pass -1 to determine the + * string length with strlen (must be NULL terminated). + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_setopt_masterkey_aws (mongocrypt_ctx_t ctx, + cstring region, + int region_len, + cstring cmk, + int cmk_len); + + /** + * Identify a custom AWS endpoint when creating a data key. + * This is used internally to construct the correct HTTP request + * (with the Host header set to this endpoint). This endpoint + * is persisted in the new data key, and will be returned via + * mongocrypt_kms_ctx_endpoint. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param endpoint The endpoint. + * @param endpoint_len The string length of @p endpoint. Pass -1 to + * determine the string length with strlen (must be NULL terminated). + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status + */ + public static native boolean + mongocrypt_ctx_setopt_masterkey_aws_endpoint (mongocrypt_ctx_t ctx, + cstring endpoint, + int endpoint_len); + + + /** + * Set the master key to "local" for creating a data key. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_setopt_masterkey_local (mongocrypt_ctx_t ctx); + + /** + * Set key encryption key document for creating a data key. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param keyDocument BSON representing the key encryption key document. + * @return A boolean indicating success. If false, and error status is set. + * @since 1.1 + */ + public static native boolean + mongocrypt_ctx_setopt_key_encryption_key(mongocrypt_ctx_t ctx, + mongocrypt_binary_t keyDocument); + + /** + * Initialize a context to create a data key. + * + * Set options before using @ref mongocrypt_ctx_setopt_masterkey_aws and + * mongocrypt_ctx_setopt_masterkey_local. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @return A boolean indicating success. + * + * Assumes a master key option has been set, and an associated KMS provider + * has been set on the parent @ref mongocrypt_t. + */ + public static native boolean + mongocrypt_ctx_datakey_init (mongocrypt_ctx_t ctx); + + /** + * Initialize a context for encryption. + * + * Associated options: + * - @ref mongocrypt_ctx_setopt_cache_noblock + * - @ref mongocrypt_ctx_setopt_schema + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param db The database name. + * @param db_len The byte length of @p db. Pass -1 to determine the string length with strlen (must be NULL terminated). + * @param cmd The BSON command to be encrypted. + * @return A boolean indicating success. If false, an error status is set. + * Retrieve it with @ref mongocrypt_ctx_status + */ + public static native boolean + mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t ctx, + cstring db, + int db_len, + mongocrypt_binary_t cmd); + + /** + * Explicit helper method to encrypt a single BSON object. Contexts + * created for explicit encryption will not go through mongocryptd. + * + * To specify a key_id, algorithm, or iv to use, please use the + * corresponding mongocrypt_setopt methods before calling this. + * + * This method expects the passed-in BSON to be of the form: + * { "v" : BSON value to encrypt } + * + * @param ctx A @ref mongocrypt_ctx_t. + * @param msg A @ref mongocrypt_binary_t the plaintext BSON value. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_explicit_encrypt_init (mongocrypt_ctx_t ctx, + mongocrypt_binary_t msg); + + /** + * Explicit helper method to encrypt a Match Expression or Aggregate Expression. + * Contexts created for explicit encryption will not go through mongocryptd. + * Requires query_type to be "range". + * NOTE: "range" is currently unstable API and subject to backwards breaking changes. + * + * This method expects the passed-in BSON to be of the form: + * { "v" : FLE2RangeFindDriverSpec } + * + * FLE2RangeFindDriverSpec is a BSON document with one of these forms: + * + * 1. A Match Expression of this form: + * {$and: [{<field>: {<op>: <value1>, {<field>: {<op>: <value2> }}]} + * 2. An Aggregate Expression of this form: + * {$and: [{<op>: [<fieldpath>, <value1>]}, {<op>: [<fieldpath>, <value2>]}] + * + * may be $lt, $lte, $gt, or $gte. + * + * The value of "v" is expected to be the BSON value passed to a driver + * ClientEncryption.encryptExpression helper. + * + * Associated options for FLE 1: + * - @ref mongocrypt_ctx_setopt_key_id + * - @ref mongocrypt_ctx_setopt_key_alt_name + * - @ref mongocrypt_ctx_setopt_algorithm + * + * Associated options for Queryable Encryption: + * - @ref mongocrypt_ctx_setopt_key_id + * - @ref mongocrypt_ctx_setopt_index_key_id + * - @ref mongocrypt_ctx_setopt_contention_factor + * - @ref mongocrypt_ctx_setopt_query_type + * - @ref mongocrypt_ctx_setopt_algorithm_range + * + * An error is returned if FLE 1 and Queryable Encryption incompatible options + * are set. + * + * @param ctx A @ref mongocrypt_ctx_t. + * @param msg A @ref mongocrypt_binary_t the plaintext BSON value. + * @return A boolean indicating success. + * @since 1.7 + */ + public static native boolean + mongocrypt_ctx_explicit_encrypt_expression_init (mongocrypt_ctx_t ctx, + mongocrypt_binary_t msg); + + /** + * Initialize a context for decryption. + * + * @param ctx The mongocrypt_ctx_t object. + * @param doc The document to be decrypted. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_decrypt_init(mongocrypt_ctx_t ctx, mongocrypt_binary_t doc); + + + /** + * Explicit helper method to decrypt a single BSON object. + * + * @param ctx A @ref mongocrypt_ctx_t. + * @param msg A @ref mongocrypt_binary_t the encrypted BSON. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_explicit_decrypt_init (mongocrypt_ctx_t ctx, + mongocrypt_binary_t msg); + + /** + * Initialize a context to rewrap datakeys. + * + * Associated options {@link #mongocrypt_ctx_setopt_key_encryption_key(mongocrypt_ctx_t, mongocrypt_binary_t)} + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param filter The filter to use for the find command on the key vault collection to retrieve datakeys to rewrap. + * @return A boolean indicating success. If false, and error status is set. + * @since 1.5 + */ + public static native boolean + mongocrypt_ctx_rewrap_many_datakey_init (mongocrypt_ctx_t ctx, mongocrypt_binary_t filter); + + + public static final int MONGOCRYPT_CTX_ERROR = 0; + public static final int MONGOCRYPT_CTX_NEED_MONGO_COLLINFO = 1; /* run on main MongoClient */ + public static final int MONGOCRYPT_CTX_NEED_MONGO_MARKINGS = 2; /* run on mongocryptd. */ + public static final int MONGOCRYPT_CTX_NEED_MONGO_KEYS = 3; /* run on key vault */ + public static final int MONGOCRYPT_CTX_NEED_KMS = 4; + public static final int MONGOCRYPT_CTX_READY = 5; /* ready for encryption/decryption */ + public static final int MONGOCRYPT_CTX_DONE = 6; + public static final int MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS = 7; /* fetch/renew KMS credentials */ + + public static final int MONGOCRYPT_INDEX_TYPE_NONE = 1; + public static final int MONGOCRYPT_INDEX_TYPE_EQUALITY = 2; + public static final int MONGOCRYPT_QUERY_TYPE_EQUALITY = 1; + + /** + * Get the current state of a context. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @return A @ref mongocrypt_ctx_state_t. + */ + public static native int + mongocrypt_ctx_state(mongocrypt_ctx_t ctx); + + + /** + * Get BSON necessary to run the mongo operation when mongocrypt_ctx_t + * is in MONGOCRYPT_CTX_NEED_MONGO_* states. + * + *

+ * op_bson is a BSON document to be used for the operation. + * - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO it is a listCollections filter. + * - For MONGOCRYPT_CTX_NEED_MONGO_KEYS it is a find filter. + * - For MONGOCRYPT_CTX_NEED_MONGO_MARKINGS it is a JSON schema to append. + *

+ * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param op_bson A BSON document for the MongoDB operation. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_mongo_op(mongocrypt_ctx_t ctx, mongocrypt_binary_t op_bson); + + + /** + * Feed a BSON reply or result when when mongocrypt_ctx_t is in + * MONGOCRYPT_CTX_NEED_MONGO_* states. This may be called multiple times + * depending on the operation. + *

+ * op_bson is a BSON document to be used for the operation. + * - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO it is a doc from a listCollections + * cursor. + * - For MONGOCRYPT_CTX_NEED_MONGO_KEYS it is a doc from a find cursor. + * - For MONGOCRYPT_CTX_NEED_MONGO_MARKINGS it is a reply from mongocryptd. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @param reply A BSON document for the MongoDB operation. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_mongo_feed(mongocrypt_ctx_t ctx, mongocrypt_binary_t reply); + + + /** + * Call when done feeding the reply (or replies) back to the context. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @return A boolean indicating success. + */ + + public static native boolean + mongocrypt_ctx_mongo_done(mongocrypt_ctx_t ctx); + + /** + * Get the next KMS handle. + *

+ * Multiple KMS handles may be retrieved at once. Drivers may do this to fan + * out multiple concurrent KMS HTTP requests. Feeding multiple KMS requests + * is thread-safe. + *

+ * Is KMS handles are being handled synchronously, the driver can reuse the same + * TLS socket to send HTTP requests and receive responses. + * + * @param ctx A @ref mongocrypt_ctx_t. + * @return a new @ref mongocrypt_kms_ctx_t or NULL. + */ + public static native mongocrypt_kms_ctx_t + mongocrypt_ctx_next_kms_ctx(mongocrypt_ctx_t ctx); + + /** + * Get the KMS provider identifier associated with this KMS request. + * + * This is used to conditionally configure TLS connections based on the KMS + * request. It is useful for KMIP, which authenticates with a client + * certificate. + * + * @param kms The mongocrypt_kms_ctx_t object. + * @param len Receives the length of the returned string. + * + * @return The name of the KMS provider + */ + public static native cstring + mongocrypt_kms_ctx_get_kms_provider(mongocrypt_kms_ctx_t kms, + Pointer len); + + /** + * Get the HTTP request message for a KMS handle. + * + * @param kms A @ref mongocrypt_kms_ctx_t. + * @param msg The HTTP request to send to KMS. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_kms_ctx_message(mongocrypt_kms_ctx_t kms, + mongocrypt_binary_t msg); + + /** + * Get the hostname from which to connect over TLS. + *

+ * The storage for @p endpoint is not owned by the caller, but + * is valid until calling @ref mongocrypt_ctx_kms_done on the + * parent @ref mongocrypt_ctx_t. + * + * @param kms A @ref mongocrypt_kms_ctx_t. + * @param endpoint The output hostname. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_kms_ctx_endpoint(mongocrypt_kms_ctx_t kms, PointerByReference endpoint); + + /** + * Indicates how many bytes to feed into @ref mongocrypt_kms_ctx_feed. + * + * @param kms The @ref mongocrypt_kms_ctx_t. + * @return The number of requested bytes. + */ + public static native int + mongocrypt_kms_ctx_bytes_needed(mongocrypt_kms_ctx_t kms); + + + /** + * Feed bytes from the HTTP response. + *

+ * Feeding more bytes than what has been returned in @ref + * mongocrypt_kms_ctx_bytes_needed is an error. + * + * @param kms The @ref mongocrypt_kms_ctx_t. + * @param bytes The bytes to feed. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_kms_ctx_feed(mongocrypt_kms_ctx_t kms, mongocrypt_binary_t bytes); + + + /** + * Get the status associated with a @ref mongocrypt_kms_ctx_t object. + * + * @param kms The @ref mongocrypt_kms_ctx_t object. + * @param status Receives the status. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_kms_ctx_status(mongocrypt_kms_ctx_t kms, + mongocrypt_status_t status); + + + /** + * Call when done handling all KMS contexts. + * + * @param ctx The @ref mongocrypt_ctx_t object. + * @return A boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_kms_done(mongocrypt_ctx_t ctx); + + + /** + * Perform the final encryption or decryption. + * + * @param ctx A @ref mongocrypt_ctx_t. + * @param out The final BSON to send to the server. + * @return a boolean indicating success. + */ + public static native boolean + mongocrypt_ctx_finalize(mongocrypt_ctx_t ctx, mongocrypt_binary_t out); + + + /** + * Destroy and free all memory associated with a @ref mongocrypt_ctx_t. + * + * @param ctx A @ref mongocrypt_ctx_t. + */ + public static native void + mongocrypt_ctx_destroy(mongocrypt_ctx_t ctx); + + static final String NATIVE_LIBRARY_NAME = "mongocrypt"; + + static { + Native.register(CAPI.class, NATIVE_LIBRARY_NAME); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java new file mode 100644 index 00000000000..9ba223645cf --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.sun.jna.Pointer; +import org.bson.BsonBinaryWriter; +import org.bson.BsonDocument; +import org.bson.RawBsonDocument; +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.Codec; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.io.BasicOutputBuffer; + +import java.nio.ByteBuffer; + +import static com.mongodb.crypt.capi.CAPI.mongocrypt_binary_new_from_data; +import static java.lang.String.format; + +final class CAPIHelper { + + private static final CodecRegistry CODEC_REGISTRY = CodecRegistries.fromProviders(new BsonValueCodecProvider()); + + @SuppressWarnings("unchecked") + static BinaryHolder toBinary(final BsonDocument document) { + BasicOutputBuffer buffer = new BasicOutputBuffer(); + BsonBinaryWriter writer = new BsonBinaryWriter(buffer); + ((Codec) CODEC_REGISTRY.get(document.getClass())).encode(writer, document, EncoderContext.builder().build()); + + DisposableMemory memory = new DisposableMemory(buffer.size()); + memory.write(0, buffer.getInternalBuffer(), 0, buffer.size()); + + return new BinaryHolder(memory, mongocrypt_binary_new_from_data(memory, buffer.getSize())); + } + + static RawBsonDocument toDocument(final mongocrypt_binary_t binary) { + ByteBuffer byteBuffer = toByteBuffer(binary); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return new RawBsonDocument(bytes); + } + + static BinaryHolder toBinary(final ByteBuffer buffer) { + byte[] message = new byte[buffer.remaining()]; + buffer.get(message, 0, buffer.remaining()); + + DisposableMemory memory = new DisposableMemory(message.length); + memory.write(0, message, 0, message.length); + + return new BinaryHolder(memory, mongocrypt_binary_new_from_data(memory, message.length)); + } + + static ByteBuffer toByteBuffer(final mongocrypt_binary_t binary) { + Pointer pointer = binary.data(); + int length = binary.len(); + return pointer.getByteBuffer(0, length); + } + + static byte[] toByteArray(final mongocrypt_binary_t binary) { + ByteBuffer byteBuffer = toByteBuffer(binary); + byte[] byteArray = new byte[byteBuffer.remaining()]; + byteBuffer.get(byteArray); + return byteArray; + } + + static void writeByteArrayToBinary(final mongocrypt_binary_t binary, byte[] bytes) { + if (binary.len() < bytes.length) { + throw new IllegalArgumentException(format("mongocrypt binary of length %d is not large enough to hold %d bytes", + binary.len(), bytes.length)); + } + Pointer outPointer = binary.data(); + outPointer.write(0, bytes, 0, bytes.length); + } + + private CAPIHelper() { + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CipherCallback.java b/mongocrypt/src/main/com/mongodb/crypt/capi/CipherCallback.java new file mode 100644 index 00000000000..b10c0f21c67 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/CipherCallback.java @@ -0,0 +1,92 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.cstring; +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_crypto_fn; +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import com.sun.jna.Pointer; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.ConcurrentLinkedDeque; + +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_STATUS_ERROR_CLIENT; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_set; +import static com.mongodb.crypt.capi.CAPIHelper.toByteArray; +import static com.mongodb.crypt.capi.CAPIHelper.writeByteArrayToBinary; + +class CipherCallback implements mongocrypt_crypto_fn { + private final String algorithm; + private final String transformation; + private final int mode; + private final CipherPool cipherPool; + + CipherCallback(final String algorithm, final String transformation, final int mode) { + this.algorithm = algorithm; + this.transformation = transformation; + this.mode = mode; + this.cipherPool = new CipherPool(); + } + + @Override + public boolean crypt(final Pointer ctx, final mongocrypt_binary_t key, final mongocrypt_binary_t iv, + final mongocrypt_binary_t in, final mongocrypt_binary_t out, + final Pointer bytesWritten, final mongocrypt_status_t status) { + Cipher cipher = null; + try { + IvParameterSpec ivParameterSpec = new IvParameterSpec(toByteArray(iv)); + SecretKeySpec secretKeySpec = new SecretKeySpec(toByteArray(key), algorithm); + cipher = cipherPool.get(); + cipher.init(mode, secretKeySpec, ivParameterSpec); + + byte[] result = cipher.doFinal(toByteArray(in)); + writeByteArrayToBinary(out, result); + bytesWritten.setInt(0, result.length); + + return true; + } catch (Exception e) { + mongocrypt_status_set(status, MONGOCRYPT_STATUS_ERROR_CLIENT, 0, new cstring(e.toString()), -1); + return false; + } finally { + if (cipher != null) { + cipherPool.release(cipher); + } + } + } + + private class CipherPool { + private final ConcurrentLinkedDeque available = new ConcurrentLinkedDeque<>(); + + Cipher get() throws NoSuchAlgorithmException, NoSuchPaddingException { + Cipher cipher = available.pollLast(); + if (cipher != null) { + return cipher; + } + return Cipher.getInstance(transformation); + } + + void release(final Cipher cipher) { + available.addLast(cipher); + } + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java b/mongocrypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java new file mode 100644 index 00000000000..fdcfb268fea --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.sun.jna.Memory; + +// Subclass of JNA's Memory class so that we can call its protected dispose method +class DisposableMemory extends Memory { + DisposableMemory(final int size) { + super(size); + } + + public void dispose() { + super.dispose(); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/JULLogger.java b/mongocrypt/src/main/com/mongodb/crypt/capi/JULLogger.java new file mode 100644 index 00000000000..9a53e850d15 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/JULLogger.java @@ -0,0 +1,130 @@ + +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import java.util.logging.Level; + +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.FINER; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.SEVERE; +import static java.util.logging.Level.WARNING; + +class JULLogger implements Logger { + + private final java.util.logging.Logger delegate; + + JULLogger(final String name) { + this.delegate = java.util.logging.Logger.getLogger(name); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean isTraceEnabled() { + return isEnabled(FINER); + } + + @Override + public void trace(final String msg) { + log(FINER, msg); + } + + @Override + public void trace(final String msg, final Throwable t) { + log(FINER, msg, t); + } + + @Override + public boolean isDebugEnabled() { + return isEnabled(FINE); + } + + @Override + public void debug(final String msg) { + log(FINE, msg); + } + + @Override + public void debug(final String msg, final Throwable t) { + log(FINE, msg, t); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isLoggable(INFO); + } + + @Override + public void info(final String msg) { + log(INFO, msg); + } + + @Override + public void info(final String msg, final Throwable t) { + log(INFO, msg, t); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isLoggable(WARNING); + } + + @Override + public void warn(final String msg) { + log(WARNING, msg); + } + + @Override + public void warn(final String msg, final Throwable t) { + log(WARNING, msg, t); + } + + + @Override + public boolean isErrorEnabled() { + return delegate.isLoggable(SEVERE); + } + + @Override + public void error(final String msg) { + log(SEVERE, msg); + } + + @Override + public void error(final String msg, final Throwable t) { + log(SEVERE, msg, t); + } + + + private boolean isEnabled(final Level level) { + return delegate.isLoggable(level); + } + + private void log(final Level level, final String msg) { + delegate.log(level, msg); + } + + public void log(final Level level, final String msg, final Throwable t) { + delegate.log(level, msg, t); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/Logger.java b/mongocrypt/src/main/com/mongodb/crypt/capi/Logger.java new file mode 100644 index 00000000000..38e82c235b8 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/Logger.java @@ -0,0 +1,144 @@ + +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +/** + * Not part of the public API + */ +public interface Logger { + /** + * Return the name of this Logger instance. + * + * @return name of this logger instance + */ + String getName(); + + /** + * Is the logger instance enabled for the TRACE level? + * + * @return True if this Logger is enabled for the TRACE level, false otherwise. + */ + boolean isTraceEnabled(); + + /** + * Log a message at the TRACE level. + * + * @param msg the message string to be logged + */ + void trace(String msg); + + /** + * Log an exception (throwable) at the TRACE level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void trace(String msg, Throwable t); + + /** + * Is the logger instance enabled for the DEBUG level? + * + * @return True if this Logger is enabled for the DEBUG level, false otherwise. + */ + boolean isDebugEnabled(); + + + /** + * Log a message at the DEBUG level. + * + * @param msg the message string to be logged + */ + void debug(String msg); + + + /** + * Log an exception (throwable) at the DEBUG level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void debug(String msg, Throwable t); + + /** + * Is the logger instance enabled for the INFO level? + * + * @return True if this Logger is enabled for the INFO level, false otherwise. + */ + boolean isInfoEnabled(); + + + /** + * Log a message at the INFO level. + * + * @param msg the message string to be logged + */ + void info(String msg); + + /** + * Log an exception (throwable) at the INFO level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void info(String msg, Throwable t); + + /** + * Is the logger instance enabled for the WARN level? + * + * @return True if this Logger is enabled for the WARN level, false otherwise. + */ + boolean isWarnEnabled(); + + /** + * Log a message at the WARN level. + * + * @param msg the message string to be logged + */ + void warn(String msg); + + /** + * Log an exception (throwable) at the WARN level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void warn(String msg, Throwable t); + + /** + * Is the logger instance enabled for the ERROR level? + * + * @return True if this Logger is enabled for the ERROR level, false otherwise. + */ + boolean isErrorEnabled(); + + /** + * Log a message at the ERROR level. + * + * @param msg the message string to be logged + */ + void error(String msg); + + /** + * Log an exception (throwable) at the ERROR level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void error(String msg, Throwable t); +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/Loggers.java b/mongocrypt/src/main/com/mongodb/crypt/capi/Loggers.java new file mode 100644 index 00000000000..c57cd3994e4 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/Loggers.java @@ -0,0 +1,50 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +/** + * This class is not part of the public API. + */ +public final class Loggers { + private static final String NAME = "org.mongodb.driver.crypt"; + + private static final boolean USE_SLF4J = shouldUseSLF4J(); + + /** + * @return the logger + */ + public static Logger getLogger() { + if (USE_SLF4J) { + return new SLF4JLogger(NAME); + } else { + return new JULLogger(NAME); + } + } + + private Loggers() { + } + + private static boolean shouldUseSLF4J() { + try { + Class.forName("org.slf4j.Logger"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MacCallback.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MacCallback.java new file mode 100644 index 00000000000..2ea09550bb4 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MacCallback.java @@ -0,0 +1,60 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.cstring; +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_hmac_fn; +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import com.sun.jna.Pointer; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_STATUS_ERROR_CLIENT; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_set; +import static com.mongodb.crypt.capi.CAPIHelper.toByteArray; +import static com.mongodb.crypt.capi.CAPIHelper.writeByteArrayToBinary; + +class MacCallback implements mongocrypt_hmac_fn { + private final String algorithm; + + MacCallback(final String algorithm) { + this.algorithm = algorithm; + } + + @Override + public boolean hmac(final Pointer ctx, final mongocrypt_binary_t key, final mongocrypt_binary_t in, + final mongocrypt_binary_t out, final mongocrypt_status_t status) { + try { + Mac mac = Mac.getInstance(algorithm); + SecretKeySpec keySpec = new SecretKeySpec(toByteArray(key), algorithm); + mac.init(keySpec); + + mac.update(toByteArray(in)); + + byte[] result = mac.doFinal(); + writeByteArrayToBinary(out, result); + + return true; + } catch (Exception e) { + mongocrypt_status_set(status, MONGOCRYPT_STATUS_ERROR_CLIENT, 0, new cstring(e.toString()), -1); + return false; + } + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java new file mode 100644 index 00000000000..861290d0a8f --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java @@ -0,0 +1,55 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.cstring; +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_hash_fn; +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import com.sun.jna.Pointer; + +import java.security.MessageDigest; + +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_STATUS_ERROR_CLIENT; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_set; +import static com.mongodb.crypt.capi.CAPIHelper.toByteArray; +import static com.mongodb.crypt.capi.CAPIHelper.writeByteArrayToBinary; + +class MessageDigestCallback implements mongocrypt_hash_fn { + + private final String algorithm; + + MessageDigestCallback(final String algorithm) { + this.algorithm = algorithm; + } + + @Override + public boolean hash(final Pointer ctx, final mongocrypt_binary_t in, final mongocrypt_binary_t out, + final mongocrypt_status_t status) { + try { + MessageDigest messageDigest = MessageDigest.getInstance(algorithm); + messageDigest.update(toByteArray(in)); + byte[] digest = messageDigest.digest(); + writeByteArrayToBinary(out, digest); + return true; + } catch (Exception e) { + mongocrypt_status_set(status, MONGOCRYPT_STATUS_ERROR_CLIENT, 0, new cstring(e.toString()), -1); + return false; + } + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java new file mode 100644 index 00000000000..aa8c0bffaa6 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import static org.bson.assertions.Assertions.notNull; + +/** + * The options for configuring the AWS KMS provider. + */ +public class MongoAwsKmsProviderOptions { + + private final String accessKeyId; + private final String secretAccessKey; + + /** + * Construct a builder for the options + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the access key id + * + * @return the access key id, which may not be null + */ + public String getAccessKeyId() { + return accessKeyId; + } + + /** + * Gets the secret access key + * + * @return the secret access key, which may not be null + */ + public String getSecretAccessKey() { + return secretAccessKey; + } + + + /** + * The builder for the options + */ + public static class Builder { + private String accessKeyId; + private String secretAccessKey; + + private Builder() { + } + + /** + * Sets the access key id. + * + * @param accessKeyId the access key id + * @return this + */ + public Builder accessKeyId(final String accessKeyId) { + this.accessKeyId = accessKeyId; + return this; + } + + /** + * Sets the secret access key. + * + * @param secretAccessKey the secret access key + * @return this + */ + public Builder secretAccessKey(final String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + return this; + } + + /** + * Build the options. + * + * @return the options + */ + public MongoAwsKmsProviderOptions build() { + return new MongoAwsKmsProviderOptions(this); + } + } + + private MongoAwsKmsProviderOptions(final Builder builder) { + this.accessKeyId = notNull("AWS KMS provider accessKeyId", builder.accessKeyId); + this.secretAccessKey = notNull("AWS KMS provider secretAccessKey", builder.secretAccessKey); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java new file mode 100644 index 00000000000..5022f2fb6a2 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.mongodb.crypt.capi; + +import org.bson.BsonDocument; + +import java.io.Closeable; + +/** + * A context for encryption/decryption operations. + */ +public interface MongoCrypt extends Closeable { + + /** + * Create a context to use for encryption + * + * @param database the namespace + * @param command the document representing the command to encrypt + * @return the context + */ + MongoCryptContext createEncryptionContext(String database, final BsonDocument command); + + /** + * Create a context to use for decryption + * + * @param document the document to decrypt + * @return the context + */ + MongoCryptContext createDecryptionContext(BsonDocument document); + + /** + * Create a context to use for creating a data key + * @param kmsProvider the KMS provider + * @param options the data key options + * @return the context + */ + MongoCryptContext createDataKeyContext(String kmsProvider, MongoDataKeyOptions options); + + /** + * Create a context to use for encryption + * + * @param document the document to encrypt, which must be in the form { "v" : BSON value to encrypt } + * @param options the explicit encryption options + * @return the context + */ + MongoCryptContext createExplicitEncryptionContext(BsonDocument document, MongoExplicitEncryptOptions options); + + /** + * Create a context to use for encryption + * + * @param document the document to encrypt, which must be in the form { "v" : BSON value to encrypt } + * @param options the expression encryption options + * @return the context + * @since 1.7 + */ + MongoCryptContext createEncryptExpressionContext(BsonDocument document, MongoExplicitEncryptOptions options); + + /** + * Create a context to use for encryption + * + * @param document the document to decrypt,which must be in the form { "v" : encrypted BSON value } + * @return the context + */ + MongoCryptContext createExplicitDecryptionContext(BsonDocument document); + + /** + * Create a context to use for encryption + * + * @param filter The filter to use for the find command on the key vault collection to retrieve datakeys to rewrap. + * @param options the rewrap many data key options + * @return the context + * @since 1.5 + */ + MongoCryptContext createRewrapManyDatakeyContext(BsonDocument filter, MongoRewrapManyDataKeyOptions options); + + /** + * @return the version string of the loaded crypt shared dynamic library if available or null + * @since 1.5 + */ + String getCryptSharedLibVersionString(); + + @Override + void close(); +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java new file mode 100644 index 00000000000..cd466c4ccee --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java @@ -0,0 +1,137 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import org.bson.BsonDocument; +import org.bson.RawBsonDocument; + +import java.io.Closeable; + +/** + * An interface representing the lifecycle of an encryption or decryption request. It's modelled as a state machine. + */ +public interface MongoCryptContext extends Closeable { + + /** + * The possible states. + */ + enum State { + /** + * Needs collection information from the cluster encrypting to + */ + NEED_MONGO_COLLINFO(CAPI.MONGOCRYPT_CTX_NEED_MONGO_COLLINFO), + + /** + * Need to mark command with encryption markers + */ + NEED_MONGO_MARKINGS(CAPI.MONGOCRYPT_CTX_NEED_MONGO_MARKINGS), + + /** + * Need keys from the key vault + */ + NEED_MONGO_KEYS(CAPI.MONGOCRYPT_CTX_NEED_MONGO_KEYS), + + /** + * Need the key management service + */ + NEED_KMS(CAPI.MONGOCRYPT_CTX_NEED_KMS), + + /** + * Need to fetch/renew KMS credentials + * @since 1.4 + */ + NEED_KMS_CREDENTIALS(CAPI.MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS), + + /** + * Ready for encryption/decryption + */ + READY(CAPI.MONGOCRYPT_CTX_READY), + + /** + * Done + */ + DONE(CAPI.MONGOCRYPT_CTX_DONE); + + private final int index; + + State(final int index) { + this.index = index; + } + + static State fromIndex(final int index) { + for (State state : State.values()) { + if (state.index == index) { + return state; + } + } + throw new MongoCryptException("Unknown context state " + index); + } + } + + /** + * Gets the current state. + * + * @return the current state + */ + State getState(); + + /** + * + * @return the operation to execute + */ + RawBsonDocument getMongoOperation(); + + /** + * + * @param document a result of the operation + */ + void addMongoOperationResult(BsonDocument document); + + /** + * Signal completion of the operation + */ + void completeMongoOperation(); + + /** + * Provide KMS credentials on demand, in response to NEED_KMS_CREDENTIALS state + * + * @param credentialsDocument document containing all credentials + * @since 1.4 + */ + void provideKmsProviderCredentials(BsonDocument credentialsDocument); + + /** + * + * @return the next key decryptor, or null if there are no more + */ + MongoKeyDecryptor nextKeyDecryptor(); + + /** + * Indicate that all key decryptors have been completed + */ + void completeKeyDecryptors(); + + /** + * + * @return the encrypted or decrypted document + */ + RawBsonDocument finish(); + + @Override + void close(); +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java new file mode 100644 index 00000000000..4df0fa06928 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java @@ -0,0 +1,164 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_t; +import org.bson.BsonDocument; +import org.bson.RawBsonDocument; + +import static com.mongodb.crypt.capi.CAPI.mongocrypt_binary_destroy; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_binary_new; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_destroy; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_finalize; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_kms_done; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_mongo_done; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_mongo_feed; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_mongo_op; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_next_kms_ctx; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_provide_kms_providers; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_state; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_status; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_destroy; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_new; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import static com.mongodb.crypt.capi.CAPIHelper.toBinary; +import static com.mongodb.crypt.capi.CAPIHelper.toDocument; +import static org.bson.assertions.Assertions.isTrue; +import static org.bson.assertions.Assertions.notNull; + +class MongoCryptContextImpl implements MongoCryptContext { + private final mongocrypt_ctx_t wrapped; + private volatile boolean closed; + + MongoCryptContextImpl(final mongocrypt_ctx_t wrapped) { + notNull("wrapped", wrapped); + this.wrapped = wrapped; + } + + @Override + public State getState() { + isTrue("open", !closed); + return State.fromIndex(mongocrypt_ctx_state(wrapped)); + } + + @Override + public RawBsonDocument getMongoOperation() { + isTrue("open", !closed); + mongocrypt_binary_t binary = mongocrypt_binary_new(); + + try { + boolean success = mongocrypt_ctx_mongo_op(wrapped, binary); + if (!success) { + throwExceptionFromStatus(); + } + return toDocument(binary); + } finally { + mongocrypt_binary_destroy(binary); + } + } + + @Override + public void addMongoOperationResult(final BsonDocument document) { + isTrue("open", !closed); + + try (BinaryHolder binaryHolder = toBinary(document)) { + boolean success = mongocrypt_ctx_mongo_feed(wrapped, binaryHolder.getBinary()); + if (!success) { + throwExceptionFromStatus(); + } + } + } + + @Override + public void completeMongoOperation() { + isTrue("open", !closed); + boolean success = mongocrypt_ctx_mongo_done(wrapped); + if (!success) { + throwExceptionFromStatus(); + } + } + + @Override + public void provideKmsProviderCredentials(final BsonDocument credentialsDocument) { + try (BinaryHolder binaryHolder = toBinary(credentialsDocument)) { + boolean success = mongocrypt_ctx_provide_kms_providers(wrapped, binaryHolder.getBinary()); + if (!success) { + throwExceptionFromStatus(); + } + } + } + + @Override + public MongoKeyDecryptor nextKeyDecryptor() { + isTrue("open", !closed); + + mongocrypt_kms_ctx_t kmsContext = mongocrypt_ctx_next_kms_ctx(wrapped); + if (kmsContext == null) { + return null; + } + return new MongoKeyDecryptorImpl(kmsContext); + } + + @Override + public void completeKeyDecryptors() { + isTrue("open", !closed); + + boolean success = mongocrypt_ctx_kms_done(wrapped); + if (!success) { + throwExceptionFromStatus(); + } + + } + + @Override + public RawBsonDocument finish() { + isTrue("open", !closed); + + mongocrypt_binary_t binary = mongocrypt_binary_new(); + + try { + boolean success = mongocrypt_ctx_finalize(wrapped, binary); + if (!success) { + throwExceptionFromStatus(); + } + return toDocument(binary); + } finally { + mongocrypt_binary_destroy(binary); + } + } + + @Override + public void close() { + mongocrypt_ctx_destroy(wrapped); + closed = true; + } + + static void throwExceptionFromStatus(final mongocrypt_ctx_t wrapped) { + mongocrypt_status_t status = mongocrypt_status_new(); + mongocrypt_ctx_status(wrapped, status); + MongoCryptException e = new MongoCryptException(status); + mongocrypt_status_destroy(status); + throw e; + } + + private void throwExceptionFromStatus() { + throwExceptionFromStatus(wrapped); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java new file mode 100644 index 00000000000..ccde7c88baa --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + + +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; + +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_code; +import static org.bson.assertions.Assertions.isTrue; + +/** + * Top level Exception for all Mongo Crypt CAPI exceptions + */ +public class MongoCryptException extends RuntimeException { + private static final long serialVersionUID = -5524416583514807953L; + private final int code; + + /** + * @param msg the message + */ + public MongoCryptException(final String msg) { + super(msg); + this.code = -1; + } + + /** + * @param msg the message + * @param cause the cause + */ + public MongoCryptException(final String msg, Throwable cause) { + super(msg, cause); + this.code = -1; + } + + /** + * Construct an instance from a {@code mongocrypt_status_t}. + * + * @param status the status + */ + MongoCryptException(final mongocrypt_status_t status) { + super(CAPI.mongocrypt_status_message(status, null).toString()); + isTrue("status not ok", !CAPI.mongocrypt_status_ok(status)); + code = mongocrypt_status_code(status); + } + + /** + * @return the error code for the exception. + */ + public int getCode() { + return code; + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java new file mode 100644 index 00000000000..2132f3c682c --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java @@ -0,0 +1,423 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.cstring; +import com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_log_fn_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_t; +import com.sun.jna.Pointer; +import org.bson.BsonBinary; +import org.bson.BsonDocument; +import org.bson.BsonString; + +import javax.crypto.Cipher; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_LOG_LEVEL_ERROR; +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_LOG_LEVEL_FATAL; +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_LOG_LEVEL_INFO; +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_LOG_LEVEL_TRACE; +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_LOG_LEVEL_WARNING; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_crypt_shared_lib_version_string; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_datakey_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_decrypt_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_encrypt_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_explicit_decrypt_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_explicit_encrypt_expression_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_explicit_encrypt_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_new; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_rewrap_many_datakey_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_algorithm; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_algorithm_range; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_contention_factor; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_key_alt_name; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_key_encryption_key; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_key_id; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_key_material; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_ctx_setopt_query_type; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_destroy; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_init; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_is_crypto_available; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_new; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_aes_256_ctr; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_append_crypt_shared_lib_search_path; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_bypass_query_analysis; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_crypto_hooks; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_encrypted_field_config_map; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_kms_provider_aws; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_kms_provider_local; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_kms_providers; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_log_handler; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_schema_map; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_set_crypt_shared_lib_path_override; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_setopt_use_need_kms_credentials_state; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_destroy; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_new; +import static com.mongodb.crypt.capi.CAPIHelper.toBinary; +import static org.bson.assertions.Assertions.isTrue; +import static org.bson.assertions.Assertions.notNull; + +class MongoCryptImpl implements MongoCrypt { + private static final Logger LOGGER = Loggers.getLogger(); + private final mongocrypt_t wrapped; + + // Keep a strong reference to all the callbacks so that they don't get garbage collected + @SuppressWarnings("FieldCanBeLocal") + private final LogCallback logCallback; + + @SuppressWarnings("FieldCanBeLocal") + private final CipherCallback aesCBC256EncryptCallback; + @SuppressWarnings("FieldCanBeLocal") + private final CipherCallback aesCBC256DecryptCallback; + @SuppressWarnings("FieldCanBeLocal") + private final CipherCallback aesCTR256EncryptCallback; + @SuppressWarnings("FieldCanBeLocal") + private final CipherCallback aesCTR256DecryptCallback; + @SuppressWarnings("FieldCanBeLocal") + private final MacCallback hmacSha512Callback; + @SuppressWarnings("FieldCanBeLocal") + private final MacCallback hmacSha256Callback; + @SuppressWarnings("FieldCanBeLocal") + private final MessageDigestCallback sha256Callback; + @SuppressWarnings("FieldCanBeLocal") + private final SecureRandomCallback secureRandomCallback; + @SuppressWarnings("FieldCanBeLocal") + private final SigningRSAESPKCSCallback signingRSAESPKCSCallback; + + private final AtomicBoolean closed; + + MongoCryptImpl(final MongoCryptOptions options) { + closed = new AtomicBoolean(); + wrapped = mongocrypt_new(); + if (wrapped == null) { + throw new MongoCryptException("Unable to create new mongocrypt object"); + } + + logCallback = new LogCallback(); + + configure(() -> mongocrypt_setopt_log_handler(wrapped, logCallback, null)); + + if (mongocrypt_is_crypto_available()) { + LOGGER.debug("libmongocrypt is compiled with cryptography support, so not registering Java callbacks"); + aesCBC256EncryptCallback = null; + aesCBC256DecryptCallback = null; + aesCTR256EncryptCallback = null; + aesCTR256DecryptCallback = null; + hmacSha512Callback = null; + hmacSha256Callback = null; + sha256Callback = null; + secureRandomCallback = null; + signingRSAESPKCSCallback = null; + } else { + LOGGER.debug("libmongocrypt is compiled without cryptography support, so registering Java callbacks"); + // We specify NoPadding here because the underlying C library is responsible for padding prior + // to executing the callback + aesCBC256EncryptCallback = new CipherCallback("AES", "AES/CBC/NoPadding", Cipher.ENCRYPT_MODE); + aesCBC256DecryptCallback = new CipherCallback("AES", "AES/CBC/NoPadding", Cipher.DECRYPT_MODE); + aesCTR256EncryptCallback = new CipherCallback("AES", "AES/CTR/NoPadding", Cipher.ENCRYPT_MODE); + aesCTR256DecryptCallback = new CipherCallback("AES", "AES/CTR/NoPadding", Cipher.DECRYPT_MODE); + + hmacSha512Callback = new MacCallback("HmacSHA512"); + hmacSha256Callback = new MacCallback("HmacSHA256"); + sha256Callback = new MessageDigestCallback("SHA-256"); + secureRandomCallback = new SecureRandomCallback(new SecureRandom()); + + configure(() -> mongocrypt_setopt_crypto_hooks(wrapped, aesCBC256EncryptCallback, aesCBC256DecryptCallback, + secureRandomCallback, hmacSha512Callback, hmacSha256Callback, + sha256Callback, null)); + + signingRSAESPKCSCallback = new SigningRSAESPKCSCallback(); + configure(() -> mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5(wrapped, signingRSAESPKCSCallback, null)); + configure(() -> mongocrypt_setopt_aes_256_ctr(wrapped, aesCTR256EncryptCallback, aesCTR256DecryptCallback, null)); + } + + if (options.getLocalKmsProviderOptions() != null) { + try (BinaryHolder localMasterKeyBinaryHolder = toBinary(options.getLocalKmsProviderOptions().getLocalMasterKey())) { + configure(() -> mongocrypt_setopt_kms_provider_local(wrapped, localMasterKeyBinaryHolder.getBinary())); + } + } + + if (options.getAwsKmsProviderOptions() != null) { + configure(() -> mongocrypt_setopt_kms_provider_aws(wrapped, + new cstring(options.getAwsKmsProviderOptions().getAccessKeyId()), -1, + new cstring(options.getAwsKmsProviderOptions().getSecretAccessKey()), -1)); + } + + if (options.isNeedsKmsCredentialsStateEnabled()) { + mongocrypt_setopt_use_need_kms_credentials_state(wrapped); + } + + if (options.getKmsProviderOptions() != null) { + try (BinaryHolder binaryHolder = toBinary(options.getKmsProviderOptions())) { + configure(() -> mongocrypt_setopt_kms_providers(wrapped, binaryHolder.getBinary())); + } + } + + if (options.getLocalSchemaMap() != null) { + BsonDocument localSchemaMapDocument = new BsonDocument(); + localSchemaMapDocument.putAll(options.getLocalSchemaMap()); + + try (BinaryHolder localSchemaMapBinaryHolder = toBinary(localSchemaMapDocument)) { + configure(() -> mongocrypt_setopt_schema_map(wrapped, localSchemaMapBinaryHolder.getBinary())); + } + } + + if (options.isBypassQueryAnalysis()) { + mongocrypt_setopt_bypass_query_analysis(wrapped); + } + + if (options.getEncryptedFieldsMap() != null) { + BsonDocument localEncryptedFieldsMap = new BsonDocument(); + localEncryptedFieldsMap.putAll(options.getEncryptedFieldsMap()); + + try (BinaryHolder localEncryptedFieldsMapHolder = toBinary(localEncryptedFieldsMap)) { + configure(() -> mongocrypt_setopt_encrypted_field_config_map(wrapped, localEncryptedFieldsMapHolder.getBinary())); + } + } + + options.getSearchPaths().forEach(p -> mongocrypt_setopt_append_crypt_shared_lib_search_path(wrapped, new cstring(p))); + if (options.getExtraOptions().containsKey("cryptSharedLibPath")) { + mongocrypt_setopt_set_crypt_shared_lib_path_override(wrapped, new cstring(options.getExtraOptions().getString("cryptSharedLibPath").getValue())); + } + + configure(() -> mongocrypt_init(wrapped)); + } + + @Override + public MongoCryptContext createEncryptionContext(final String database, final BsonDocument commandDocument) { + isTrue("open", !closed.get()); + notNull("database", database); + notNull("commandDocument", commandDocument); + mongocrypt_ctx_t context = mongocrypt_ctx_new(wrapped); + if (context == null) { + throwExceptionFromStatus(); + } + + try (BinaryHolder commandDocumentBinaryHolder = toBinary(commandDocument)) { + configure(() -> mongocrypt_ctx_encrypt_init(context, new cstring(database), -1, + commandDocumentBinaryHolder.getBinary()), context); + return new MongoCryptContextImpl(context); + } + } + + @Override + public MongoCryptContext createDecryptionContext(final BsonDocument document) { + isTrue("open", !closed.get()); + mongocrypt_ctx_t context = mongocrypt_ctx_new(wrapped); + if (context == null) { + throwExceptionFromStatus(); + } + try (BinaryHolder documentBinaryHolder = toBinary(document)){ + configure(() -> mongocrypt_ctx_decrypt_init(context, documentBinaryHolder.getBinary()), context); + } + return new MongoCryptContextImpl(context); + } + + @Override + public MongoCryptContext createDataKeyContext(final String kmsProvider, final MongoDataKeyOptions options) { + isTrue("open", !closed.get()); + mongocrypt_ctx_t context = mongocrypt_ctx_new(wrapped); + if (context == null) { + throwExceptionFromStatus(); + } + + BsonDocument keyDocument = new BsonDocument("provider", new BsonString(kmsProvider)); + BsonDocument masterKey = options.getMasterKey(); + if (masterKey != null) { + masterKey.forEach(keyDocument::append); + } + try (BinaryHolder masterKeyHolder = toBinary(keyDocument)) { + configure(() -> mongocrypt_ctx_setopt_key_encryption_key(context, masterKeyHolder.getBinary()), context); + } + + if (options.getKeyAltNames() != null) { + for (String cur : options.getKeyAltNames()) { + try (BinaryHolder keyAltNameBinaryHolder = toBinary(new BsonDocument("keyAltName", new BsonString(cur)))) { + configure(() -> mongocrypt_ctx_setopt_key_alt_name(context, keyAltNameBinaryHolder.getBinary()), context); + } + } + } + + if (options.getKeyMaterial() != null) { + try (BinaryHolder keyMaterialBinaryHolder = toBinary(new BsonDocument("keyMaterial", new BsonBinary(options.getKeyMaterial())))) { + configure(() -> mongocrypt_ctx_setopt_key_material(context, keyMaterialBinaryHolder.getBinary()), context); + } + } + + if (!mongocrypt_ctx_datakey_init(context)) { + MongoCryptContextImpl.throwExceptionFromStatus(context); + } + return new MongoCryptContextImpl(context); + } + + @Override + public MongoCryptContext createExplicitEncryptionContext(final BsonDocument document, final MongoExplicitEncryptOptions options) { + isTrue("open", !closed.get()); + mongocrypt_ctx_t context = configureExplicitEncryption(options); + + try (BinaryHolder documentBinaryHolder = toBinary(document)) { + configure(() -> mongocrypt_ctx_explicit_encrypt_init(context, documentBinaryHolder.getBinary()), context); + } + + return new MongoCryptContextImpl(context); + } + + @Override + public MongoCryptContext createEncryptExpressionContext(final BsonDocument document, final MongoExplicitEncryptOptions options) { + isTrue("open", !closed.get()); + mongocrypt_ctx_t context = configureExplicitEncryption(options); + + try (BinaryHolder documentBinaryHolder = toBinary(document)) { + configure(() -> mongocrypt_ctx_explicit_encrypt_expression_init(context, documentBinaryHolder.getBinary()), context); + } + return new MongoCryptContextImpl(context); + } + + @Override + public MongoCryptContext createExplicitDecryptionContext(final BsonDocument document) { + isTrue("open", !closed.get()); + mongocrypt_ctx_t context = mongocrypt_ctx_new(wrapped); + if (context == null) { + throwExceptionFromStatus(); + } + try (BinaryHolder binaryHolder = toBinary(document)) { + configure(() -> mongocrypt_ctx_explicit_decrypt_init(context, binaryHolder.getBinary()), context); + } + return new MongoCryptContextImpl(context); + } + + @Override + public MongoCryptContext createRewrapManyDatakeyContext(final BsonDocument filter, final MongoRewrapManyDataKeyOptions options) { + isTrue("open", !closed.get()); + mongocrypt_ctx_t context = mongocrypt_ctx_new(wrapped); + if (context == null) { + throwExceptionFromStatus(); + } + + if (options != null && options.getProvider() != null) { + BsonDocument keyDocument = new BsonDocument("provider", new BsonString(options.getProvider())); + BsonDocument masterKey = options.getMasterKey(); + if (masterKey != null) { + masterKey.forEach(keyDocument::append); + } + try (BinaryHolder binaryHolder = toBinary(keyDocument)) { + configure(() -> mongocrypt_ctx_setopt_key_encryption_key(context, binaryHolder.getBinary()), context); + } + } + + try (BinaryHolder binaryHolder = toBinary(filter)) { + configure(() -> mongocrypt_ctx_rewrap_many_datakey_init(context, binaryHolder.getBinary()), context); + } + return new MongoCryptContextImpl(context); + } + + @Override + public String getCryptSharedLibVersionString() { + cstring versionString = mongocrypt_crypt_shared_lib_version_string(wrapped, null); + return versionString == null ? null : versionString.toString(); + } + + @Override + public void close() { + if (!closed.getAndSet(true)) { + mongocrypt_destroy(wrapped); + } + } + + private mongocrypt_ctx_t configureExplicitEncryption(final MongoExplicitEncryptOptions options) { + mongocrypt_ctx_t context = mongocrypt_ctx_new(wrapped); + if (context == null) { + throwExceptionFromStatus(); + } + + if (options.getKeyId() != null) { + try (BinaryHolder keyIdBinaryHolder = toBinary(ByteBuffer.wrap(options.getKeyId().getData()))) { + configure(() -> mongocrypt_ctx_setopt_key_id(context, keyIdBinaryHolder.getBinary()), context); + } + } else if (options.getKeyAltName() != null) { + try (BinaryHolder keyAltNameBinaryHolder = toBinary(new BsonDocument("keyAltName", new BsonString(options.getKeyAltName())))) { + configure(() -> mongocrypt_ctx_setopt_key_alt_name(context, keyAltNameBinaryHolder.getBinary()), context); + } + } + + if (options.getAlgorithm() != null) { + configure(() -> mongocrypt_ctx_setopt_algorithm(context, new cstring(options.getAlgorithm()), -1), context); + } + if (options.getQueryType() != null) { + configure(() -> mongocrypt_ctx_setopt_query_type(context, new cstring(options.getQueryType()), -1), context); + } + if (options.getContentionFactor() != null) { + configure(() -> mongocrypt_ctx_setopt_contention_factor(context, options.getContentionFactor()), context); + } + if (options.getRangeOptions() != null) { + try (BinaryHolder rangeOptionsHolder = toBinary(options.getRangeOptions())) { + configure(() -> mongocrypt_ctx_setopt_algorithm_range(context, rangeOptionsHolder.getBinary()), context); + } + } + return context; + } + + + private void configure(final Supplier successSupplier) { + if (!successSupplier.get()) { + throwExceptionFromStatus(); + } + } + + private void configure(final Supplier successSupplier, final mongocrypt_ctx_t context) { + if (!successSupplier.get()) { + MongoCryptContextImpl.throwExceptionFromStatus(context); + } + } + + private void throwExceptionFromStatus() { + mongocrypt_status_t status = mongocrypt_status_new(); + mongocrypt_status(wrapped, status); + MongoCryptException e = new MongoCryptException(status); + mongocrypt_status_destroy(status); + throw e; + } + + static class LogCallback implements mongocrypt_log_fn_t { + @Override + public void log(final int level, final cstring message, final int messageLength, final Pointer ctx) { + if (level == MONGOCRYPT_LOG_LEVEL_FATAL) { + LOGGER.error(message.toString()); + } + if (level == MONGOCRYPT_LOG_LEVEL_ERROR) { + LOGGER.error(message.toString()); + } + if (level == MONGOCRYPT_LOG_LEVEL_WARNING) { + LOGGER.warn(message.toString()); + } + if (level == MONGOCRYPT_LOG_LEVEL_INFO) { + LOGGER.info(message.toString()); + } + if (level == MONGOCRYPT_LOG_LEVEL_TRACE) { + LOGGER.trace(message.toString()); + } + } + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java new file mode 100644 index 00000000000..a35af7638e6 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java @@ -0,0 +1,284 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import org.bson.BsonDocument; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static org.bson.assertions.Assertions.isTrue; + +/** + * The options for configuring MongoCrypt. + */ +public class MongoCryptOptions { + + private final MongoAwsKmsProviderOptions awsKmsProviderOptions; + private final MongoLocalKmsProviderOptions localKmsProviderOptions; + private final BsonDocument kmsProviderOptions; + private final Map localSchemaMap; + private final boolean needsKmsCredentialsStateEnabled; + private final Map encryptedFieldsMap; + private final BsonDocument extraOptions; + private final boolean bypassQueryAnalysis; + private final List searchPaths; + + + /** + * Construct a builder for the options + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the AWS KMS provider options. + * + * @return the AWS KMS provider options, which may be null + */ + public MongoAwsKmsProviderOptions getAwsKmsProviderOptions() { + return awsKmsProviderOptions; + } + + /** + * Gets the local KMS provider options. + * + * @return the local KMS provider options, which may be null + */ + public MongoLocalKmsProviderOptions getLocalKmsProviderOptions() { + return localKmsProviderOptions; + } + + /** + * Returns the KMS provider options. + * + * @return the KMS provider options, which may be null + * @since 1.1 + */ + public BsonDocument getKmsProviderOptions() { + return kmsProviderOptions; + } + + /** + * Gets the local schema map. + * + * @return the local schema map + */ + public Map getLocalSchemaMap() { + return localSchemaMap; + } + + /** + * Gets whether the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS is enabled. Defaults to false + * + * @return whether the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS is enabled + * @since 1.4 + */ + public boolean isNeedsKmsCredentialsStateEnabled() { + return needsKmsCredentialsStateEnabled; + } + + /** + * Gets the encrypted fields map. + * + * @since 1.5 + * @return the encrypted fields map + */ + public Map getEncryptedFieldsMap() { + return encryptedFieldsMap; + } + + /** + * Gets whether automatic analysis of outgoing commands should be disabled. + * + * @since 1.5 + * @return true if bypassing query analysis + */ + public boolean isBypassQueryAnalysis() { + return bypassQueryAnalysis; + } + + /** + * The extraOptions that relate to the mongocryptd process or shared library. + * @return the extra options + * @since 1.5 + */ + public BsonDocument getExtraOptions() { + return extraOptions; + } + + /** + * Gets the search paths + * @return this + * @since 1.5 + */ + public List getSearchPaths() { + return searchPaths; + } + + /** + * The builder for the options + */ + public static class Builder { + private MongoAwsKmsProviderOptions awsKmsProviderOptions; + private MongoLocalKmsProviderOptions localKmsProviderOptions; + private BsonDocument kmsProviderOptions = null; + private Map localSchemaMap = null; + private boolean needsKmsCredentialsStateEnabled; + private Map encryptedFieldsMap = null; + private boolean bypassQueryAnalysis; + private BsonDocument extraOptions = new BsonDocument(); + private List searchPaths = emptyList(); + + private Builder() { + } + + /** + * Sets the AWS KMS provider options. + * + * @param awsKmsProviderOptions the AWS KMS provider options + * @return this + */ + public Builder awsKmsProviderOptions(final MongoAwsKmsProviderOptions awsKmsProviderOptions) { + this.awsKmsProviderOptions = awsKmsProviderOptions; + return this; + } + + /** + * Sets the local KMS provider options. + * + * @param localKmsProviderOptions the local KMS provider options + * @return this + */ + public Builder localKmsProviderOptions(final MongoLocalKmsProviderOptions localKmsProviderOptions) { + this.localKmsProviderOptions = localKmsProviderOptions; + return this; + } + + /** + * Sets the KMS provider options. + * + * @param kmsProviderOptions the KMS provider options document + * @return this + * @since 1.1 + */ + public Builder kmsProviderOptions(final BsonDocument kmsProviderOptions) { + this.kmsProviderOptions = kmsProviderOptions; + return this; + } + + /** + * Sets the local schema map. + * + * @param localSchemaMap local schema map + * @return this + */ + public Builder localSchemaMap(final Map localSchemaMap) { + this.localSchemaMap = localSchemaMap; + return this; + } + + /** + * Sets whether the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS is enabled. Defaults to false + * + * @param needsKmsCredentialsStateEnabled whether the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS is enabled + * @return this + * @since 1.4 + */ + public Builder needsKmsCredentialsStateEnabled(final boolean needsKmsCredentialsStateEnabled) { + this.needsKmsCredentialsStateEnabled = needsKmsCredentialsStateEnabled; + return this; + } + + /** + * Sets the encrypted fields map. + * + * @param encryptedFieldsMap the encrypted fields map + * @since 1.5 + * @return this + */ + public Builder encryptedFieldsMap(final Map encryptedFieldsMap) { + this.encryptedFieldsMap = encryptedFieldsMap; + return this; + } + + /** + * Sets whether automatic analysis of outgoing commands should be disabled. + * + *

Set bypassQueryAnalysis to true to use explicit encryption on indexed fields + * without the MongoDB Enterprise Advanced licensed crypt shared library.

+ * + * @param bypassQueryAnalysis whether the analysis of outgoing commands should be disabled. + * @since 1.5 + * @return this + */ + public Builder bypassQueryAnalysis(final boolean bypassQueryAnalysis) { + this.bypassQueryAnalysis = bypassQueryAnalysis; + return this; + } + + /** + * The extraOptions that relate to the mongocryptd process or shared library. + * @param extraOptions the extraOptions + * @return this + * @since 1.5 + */ + public Builder extraOptions(final BsonDocument extraOptions) { + this.extraOptions = extraOptions; + return this; + } + + /** + * Sets search paths + * @param searchPaths sets search path + * @return this + * @since 1.5 + */ + public Builder searchPaths(final List searchPaths) { + this.searchPaths = searchPaths; + return this; + } + + /** + * Build the options. + * + * @return the options + */ + public MongoCryptOptions build() { + return new MongoCryptOptions(this); + } + } + + private MongoCryptOptions(final Builder builder) { + isTrue("at least one KMS provider is configured", + builder.awsKmsProviderOptions != null || builder.localKmsProviderOptions != null + || builder.kmsProviderOptions != null ); + this.awsKmsProviderOptions = builder.awsKmsProviderOptions; + this.localKmsProviderOptions = builder.localKmsProviderOptions; + this.kmsProviderOptions = builder.kmsProviderOptions; + this.localSchemaMap = builder.localSchemaMap; + this.needsKmsCredentialsStateEnabled = builder.needsKmsCredentialsStateEnabled; + this.encryptedFieldsMap = builder.encryptedFieldsMap; + this.bypassQueryAnalysis = builder.bypassQueryAnalysis; + this.extraOptions = builder.extraOptions; + this.searchPaths = builder.searchPaths; + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java new file mode 100644 index 00000000000..8316f16b981 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +/** + * The entry point to the MongoCrypt library. + */ +public class MongoCrypts { + + /** + * Create a {@code MongoCrypt} instance. + * + *

+ * Make sure that JNA is able to find the shared library, most likely by setting the jna.library.path system property + *

+ * + * @param options the options + * @return the instance + */ + public static MongoCrypt create(MongoCryptOptions options) { + return new MongoCryptImpl(options); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java new file mode 100644 index 00000000000..a21d715411d --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java @@ -0,0 +1,125 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import org.bson.BsonDocument; + +import java.util.List; + +/** + * The options for creation of a data key + */ +public class MongoDataKeyOptions { + private final List keyAltNames; + private final BsonDocument masterKey; + private final byte[] keyMaterial; + + /** + * Options builder + */ + public static class Builder { + private List keyAltNames; + private BsonDocument masterKey; + private byte[] keyMaterial; + + /** + * Add alternate key names + * @param keyAltNames the alternate key names + * @return this + */ + public Builder keyAltNames(final List keyAltNames) { + this.keyAltNames = keyAltNames; + return this; + } + + /** + * Add the master key. + * + * @param masterKey the master key + * @return this + */ + public Builder masterKey(final BsonDocument masterKey) { + this.masterKey = masterKey; + return this; + } + + /** + * Add the key material + * + * @param keyMaterial the optional custom key material for the data key + * @return this + * @since 1.5 + */ + public Builder keyMaterial(final byte[] keyMaterial) { + this.keyMaterial = keyMaterial; + return this; + } + + /** + * Build the options. + * + * @return the options + */ + public MongoDataKeyOptions build() { + return new MongoDataKeyOptions(this); + } + } + + /** + * Create a builder for the options. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the alternate key names for the data key. + * + * @return the alternate key names + */ + public List getKeyAltNames() { + return keyAltNames; + } + + /** + * Gets the master key for the data key. + * + * @return the master key + */ + public BsonDocument getMasterKey() { + return masterKey; + } + + /** + * Gets the custom key material if set. + * + * @return the custom key material for the data key or null + * @since 1.5 + */ + public byte[] getKeyMaterial() { + return keyMaterial; + } + + private MongoDataKeyOptions(final Builder builder) { + keyAltNames = builder.keyAltNames; + masterKey = builder.masterKey; + keyMaterial = builder.keyMaterial; + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java new file mode 100644 index 00000000000..e96808e8451 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java @@ -0,0 +1,227 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import org.bson.BsonBinary; +import org.bson.BsonDocument; + +import java.util.Objects; + +/** + * Options for explicit encryption. + */ +public class MongoExplicitEncryptOptions { + private final BsonBinary keyId; + private final String keyAltName; + private final String algorithm; + private final Long contentionFactor; + private final String queryType; + private final BsonDocument rangeOptions; + + /** + * The builder for the options + */ + public static class Builder { + private BsonBinary keyId; + private String keyAltName; + private String algorithm; + private Long contentionFactor; + private String queryType; + private BsonDocument rangeOptions; + + private Builder() { + } + + /** + * Add the key identifier. + * + * @param keyId the key idenfifier + * @return this + */ + public Builder keyId(final BsonBinary keyId) { + this.keyId = keyId; + return this; + } + + /** + * Add the key alternative name. + * + * @param keyAltName the key alternative name + * @return this + */ + public Builder keyAltName(final String keyAltName) { + this.keyAltName = keyAltName; + return this; + } + + /** + * Add the encryption algorithm. + * + *

To insert or query with an "Indexed" encrypted payload, use a MongoClient configured with {@code AutoEncryptionSettings}. + * {@code AutoEncryptionSettings.bypassQueryAnalysis} may be true. + * {@code AutoEncryptionSettings.bypassAutoEncryption must be false}.

+ * + * @param algorithm the encryption algorithm + * @return this + */ + public Builder algorithm(final String algorithm) { + this.algorithm = algorithm; + return this; + } + + /** + * The contention factor. + * + *

It is an error to set contentionFactor when algorithm is not "Indexed". + * @param contentionFactor the contention factor + * @return this + * @since 1.5 + */ + public Builder contentionFactor(final Long contentionFactor) { + this.contentionFactor = contentionFactor; + return this; + } + + /** + * The QueryType. + * + *

It is an error to set queryType when algorithm is not "Indexed".

+ * + * @param queryType the query type + * @return this + * @since 1.5 + */ + public Builder queryType(final String queryType) { + this.queryType = queryType; + return this; + } + + /** + * The Range Options. + * + *

It is an error to set rangeOptions when the algorithm is not "range".

+ * + * @param rangeOptions the range options + * @return this + * @since 1.7 + */ + public Builder rangeOptions(final BsonDocument rangeOptions) { + this.rangeOptions = rangeOptions; + return this; + } + + /** + * Build the options. + * + * @return the options + */ + public MongoExplicitEncryptOptions build() { + return new MongoExplicitEncryptOptions(this); + } + } + + /** + * Create a builder for the options. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the key identifier + * @return the key identifier + */ + public BsonBinary getKeyId() { + return keyId; + } + + /** + * Gets the key alternative name + * @return the key alternative name + */ + public String getKeyAltName() { + return keyAltName; + } + + /** + * Gets the encryption algorithm + * @return the encryption algorithm + */ + public String getAlgorithm() { + return algorithm; + } + + /** + * Gets the contention factor + * @return the contention factor + * @since 1.5 + */ + public Long getContentionFactor() { + return contentionFactor; + } + + /** + * Gets the query type + * @return the query type + * @since 1.5 + */ + public String getQueryType() { + return queryType; + } + + /** + * Gets the range options + * @return the range options + * @since 1.7 + */ + public BsonDocument getRangeOptions() { + return rangeOptions; + } + + private MongoExplicitEncryptOptions(Builder builder) { + this.keyId = builder.keyId; + this.keyAltName = builder.keyAltName; + this.algorithm = builder.algorithm; + this.contentionFactor = builder.contentionFactor; + this.queryType = builder.queryType; + this.rangeOptions = builder.rangeOptions; + if (!(Objects.equals(algorithm, "Indexed") || Objects.equals(algorithm, "Range"))) { + if (contentionFactor != null) { + throw new IllegalStateException( + "Invalid configuration, contentionFactor can only be set if algorithm is 'Indexed' or 'Range'"); + } else if (queryType != null) { + throw new IllegalStateException( + "Invalid configuration, queryType can only be set if algorithm is 'Indexed' or 'Range'"); + } + } + } + + @Override + public String toString() { + return "MongoExplicitEncryptOptions{" + + "keyId=" + keyId + + ", keyAltName='" + keyAltName + '\'' + + ", algorithm='" + algorithm + '\'' + + ", contentionFactor=" + contentionFactor + + ", queryType='" + queryType + '\'' + + ", rangeOptions=" + rangeOptions + + '}'; + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java new file mode 100644 index 00000000000..8ba0cc209e0 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import java.nio.ByteBuffer; + +/** + * An interface representing a key decryption operation using a key management service. + */ +public interface MongoKeyDecryptor { + + /** + * Gets the name of the KMS provider, e.g. "aws" or "kmip" + * + * @return the KMS provider name + */ + String getKmsProvider(); + + /** + * Gets the host name of the key management service. + * + * @return the host name + */ + String getHostName(); + + /** + * Gets the message to send to the key management service. + * + *

+ * Clients should call this method first, and send the message on a TLS connection to a configured KMS server. + *

+ * + * @return the message to send + */ + ByteBuffer getMessage(); + + /** + * Gets the number of bytes that should be received from the KMS server. + * + *

+ * After sending the message to the KMS server, clients should call this method in a loop, receiving {@code bytesNeeded} from + * the KMS server and feeding those bytes to this decryptor, until {@code bytesNeeded} is 0. + *

+ * + * @return the actual number of bytes that clients should be prepared receive + */ + int bytesNeeded(); + + /** + * Feed the received bytes to the decryptor. + * + *

+ * After sending the message to the KMS server, clients should call this method in a loop, receiving the number of bytes indicated by + * a call to {@link #bytesNeeded()} from the KMS server and feeding those bytes to this decryptor, until {@link #bytesNeeded()} + * returns 0. + *

+ * + * @param bytes the received bytes + */ + void feed(ByteBuffer bytes); +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java new file mode 100644 index 00000000000..c149757cc6c --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.PointerByReference; + +import java.nio.ByteBuffer; + +import static com.mongodb.crypt.capi.CAPI.mongocrypt_binary_destroy; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_binary_new; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_bytes_needed; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_endpoint; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_feed; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_get_kms_provider; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_message; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_kms_ctx_status; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_destroy; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_new; +import static com.mongodb.crypt.capi.CAPIHelper.toBinary; +import static com.mongodb.crypt.capi.CAPIHelper.toByteBuffer; +import static org.bson.assertions.Assertions.notNull; + +class MongoKeyDecryptorImpl implements MongoKeyDecryptor { + private final mongocrypt_kms_ctx_t wrapped; + + MongoKeyDecryptorImpl(final mongocrypt_kms_ctx_t wrapped) { + notNull("wrapped", wrapped); + this.wrapped = wrapped; + } + + @Override + public String getKmsProvider() { + return mongocrypt_kms_ctx_get_kms_provider(wrapped, null).toString(); + } + + @Override + public String getHostName() { + PointerByReference hostNamePointerByReference = new PointerByReference(); + boolean success = mongocrypt_kms_ctx_endpoint(wrapped, hostNamePointerByReference); + if (!success) { + throwExceptionFromStatus(); + } + Pointer hostNamePointer = hostNamePointerByReference.getValue(); + return hostNamePointer.getString(0); + } + + @Override + public ByteBuffer getMessage() { + mongocrypt_binary_t binary = mongocrypt_binary_new(); + + try { + boolean success = mongocrypt_kms_ctx_message(wrapped, binary); + if (!success) { + throwExceptionFromStatus(); + } + return toByteBuffer(binary); + } finally { + mongocrypt_binary_destroy(binary); + } + } + + @Override + public int bytesNeeded() { + return mongocrypt_kms_ctx_bytes_needed(wrapped); + } + + @Override + public void feed(final ByteBuffer bytes) { + try (BinaryHolder binaryHolder = toBinary(bytes)) { + boolean success = mongocrypt_kms_ctx_feed(wrapped, binaryHolder.getBinary()); + if (!success) { + throwExceptionFromStatus(); + } + } + } + + private void throwExceptionFromStatus() { + mongocrypt_status_t status = mongocrypt_status_new(); + mongocrypt_kms_ctx_status(wrapped, status); + MongoCryptException e = new MongoCryptException(status); + mongocrypt_status_destroy(status); + throw e; + } + +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java new file mode 100644 index 00000000000..790fbfff00c --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java @@ -0,0 +1,83 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import java.nio.ByteBuffer; + +import static org.bson.assertions.Assertions.notNull; + +/** + * The options for configuring a local KMS provider. + */ +public class MongoLocalKmsProviderOptions { + + private final ByteBuffer localMasterKey; + + /** + * Construct a builder for the options + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the local master key + * + * @return the local master key + */ + public ByteBuffer getLocalMasterKey() { + return localMasterKey; + } + + /** + * The builder for the options + */ + public static class Builder { + private ByteBuffer localMasterKey; + + private Builder() { + } + + /** + * Sets the local master key. + * + * @param localMasterKey the local master key + * @return this + */ + public Builder localMasterKey(final ByteBuffer localMasterKey) { + this.localMasterKey = localMasterKey; + return this; + } + + /** + * Build the options. + * + * @return the options + */ + public MongoLocalKmsProviderOptions build() { + return new MongoLocalKmsProviderOptions(this); + } + } + + private MongoLocalKmsProviderOptions(final Builder builder) { + this.localMasterKey = notNull("Local KMS provider localMasterKey", builder.localMasterKey); + + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java new file mode 100644 index 00000000000..d875c8d795d --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java @@ -0,0 +1,104 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.crypt.capi; + +import org.bson.BsonDocument; + +/** + * The rewrap many data key options + * + *

+ * The masterKey document MUST have the fields corresponding to the given provider as specified in masterKey. + *

+ * + * @since 1.5 + */ +public class MongoRewrapManyDataKeyOptions { + + private final String provider; + private final BsonDocument masterKey; + + /** + * Options builder + */ + public static class Builder { + private String provider; + private BsonDocument masterKey; + + /** + * The provider + * + * @param provider the provider + * @return this + */ + public Builder provider(final String provider) { + this.provider = provider; + return this; + } + + /** + * Add the master key. + * + * @param masterKey the master key + * @return this + */ + public Builder masterKey(final BsonDocument masterKey) { + this.masterKey = masterKey; + return this; + } + + /** + * Build the options. + * + * @return the options + */ + public MongoRewrapManyDataKeyOptions build() { + return new MongoRewrapManyDataKeyOptions(this); + } + } + + /** + * Create a builder for the options. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * @return the provider name + */ + public String getProvider() { + return provider; + } + + /** + * Gets the master key for the data key. + * + * @return the master key + */ + public BsonDocument getMasterKey() { + return masterKey; + } + + private MongoRewrapManyDataKeyOptions(final Builder builder) { + provider = builder.provider; + masterKey = builder.masterKey; + } +} + diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java b/mongocrypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java new file mode 100644 index 00000000000..23064f8bf85 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java @@ -0,0 +1,110 @@ + +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import org.slf4j.LoggerFactory; + +class SLF4JLogger implements Logger { + + private final org.slf4j.Logger delegate; + + SLF4JLogger(final String name) { + this.delegate = LoggerFactory.getLogger(name); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean isTraceEnabled() { + return delegate.isTraceEnabled(); + } + + @Override + public void trace(final String msg) { + delegate.trace(msg); + } + + @Override + public void trace(final String msg, final Throwable t) { + delegate.trace(msg, t); + } + + @Override + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + @Override + public void debug(final String msg) { + delegate.debug(msg); + } + + @Override + public void debug(final String msg, final Throwable t) { + delegate.debug(msg, t); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + @Override + public void info(final String msg) { + delegate.info(msg); + } + + @Override + public void info(final String msg, final Throwable t) { + delegate.info(msg, t); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isWarnEnabled(); + } + + @Override + public void warn(final String msg) { + delegate.warn(msg); + } + + @Override + public void warn(final String msg, final Throwable t) { + delegate.warn(msg, t); + } + + @Override + public boolean isErrorEnabled() { + return delegate.isErrorEnabled(); + } + + @Override + public void error(final String msg) { + delegate.error(msg); + } + + @Override + public void error(final String msg, final Throwable t) { + delegate.error(msg, t); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java b/mongocrypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java new file mode 100644 index 00000000000..0a2a83c02f7 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java @@ -0,0 +1,51 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.cstring; +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_random_fn; +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import com.sun.jna.Pointer; + +import java.security.SecureRandom; + +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_STATUS_ERROR_CLIENT; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_set; +import static com.mongodb.crypt.capi.CAPIHelper.writeByteArrayToBinary; + +class SecureRandomCallback implements mongocrypt_random_fn { + private final SecureRandom secureRandom; + + SecureRandomCallback(final SecureRandom secureRandom) { + this.secureRandom = secureRandom; + } + + @Override + public boolean random(final Pointer ctx, final mongocrypt_binary_t out, final int count, final mongocrypt_status_t status) { + try { + byte[] randomBytes = new byte[count]; + secureRandom.nextBytes(randomBytes); + writeByteArrayToBinary(out, randomBytes); + return true; + } catch (Exception e) { + mongocrypt_status_set(status, MONGOCRYPT_STATUS_ERROR_CLIENT, 0, new cstring(e.toString()), -1); + return false; + } + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java b/mongocrypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java new file mode 100644 index 00000000000..a5b7ac9f050 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java @@ -0,0 +1,73 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.CAPI.cstring; +import com.mongodb.crypt.capi.CAPI.mongocrypt_binary_t; +import com.mongodb.crypt.capi.CAPI.mongocrypt_hmac_fn; +import com.mongodb.crypt.capi.CAPI.mongocrypt_status_t; +import com.sun.jna.Pointer; + +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; + +import static com.mongodb.crypt.capi.CAPI.MONGOCRYPT_STATUS_ERROR_CLIENT; +import static com.mongodb.crypt.capi.CAPI.mongocrypt_status_set; +import static com.mongodb.crypt.capi.CAPIHelper.toByteArray; +import static com.mongodb.crypt.capi.CAPIHelper.writeByteArrayToBinary; + +class SigningRSAESPKCSCallback implements mongocrypt_hmac_fn { + + private static final String KEY_ALGORITHM = "RSA"; + private static final String SIGN_ALGORITHM = "SHA256withRSA"; + + SigningRSAESPKCSCallback() { + } + + @Override + public boolean hmac(final Pointer ctx, final mongocrypt_binary_t key, final mongocrypt_binary_t in, + final mongocrypt_binary_t out, final mongocrypt_status_t status) { + try { + byte[] result = getSignature(toByteArray(key), toByteArray(in)); + writeByteArrayToBinary(out, result); + return true; + } catch (Exception e) { + mongocrypt_status_set(status, MONGOCRYPT_STATUS_ERROR_CLIENT, 0, new cstring(e.toString()), -1); + return false; + } + } + + static byte[] getSignature(final byte[] privateKeyBytes, final byte[] dataToSign) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { + KeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + + Signature privateSignature = Signature.getInstance(SIGN_ALGORITHM); + privateSignature.initSign(privateKey); + privateSignature.update(dataToSign); + + return privateSignature.sign(); + } +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java b/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java new file mode 100644 index 00000000000..50f94177634 --- /dev/null +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; diff --git a/mongocrypt/src/main/resources/META-INF/native-image/jni-config.json b/mongocrypt/src/main/resources/META-INF/native-image/jni-config.json new file mode 100644 index 00000000000..44e398cb556 --- /dev/null +++ b/mongocrypt/src/main/resources/META-INF/native-image/jni-config.json @@ -0,0 +1,180 @@ +[ +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_crypto_fn", + "methods":[{"name":"crypt","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hash_fn", + "methods":[{"name":"hash","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hmac_fn", + "methods":[{"name":"hmac","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_log_fn_t", + "methods":[{"name":"log","parameterTypes":["int","com.mongodb.crypt.capi.CAPI$cstring","int","com.sun.jna.Pointer"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_random_fn", + "methods":[{"name":"random","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","int","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }] +}, +{ + "name":"com.sun.jna.Callback" +}, +{ + "name":"com.sun.jna.CallbackReference", + "methods":[{"name":"getCallback","parameterTypes":["java.lang.Class","com.sun.jna.Pointer","boolean"] }, {"name":"getFunctionPointer","parameterTypes":["com.sun.jna.Callback","boolean"] }, {"name":"getNativeString","parameterTypes":["java.lang.Object","boolean"] }, {"name":"initializeThread","parameterTypes":["com.sun.jna.Callback","com.sun.jna.CallbackReference$AttachOptions"] }] +}, +{ + "name":"com.sun.jna.CallbackReference$AttachOptions" +}, +{ + "name":"com.sun.jna.FromNativeConverter", + "methods":[{"name":"nativeType","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.IntegerType", + "fields":[{"name":"value"}] +}, +{ + "name":"com.sun.jna.JNIEnv" +}, +{ + "name":"com.sun.jna.Native", + "methods":[{"name":"dispose","parameterTypes":[] }, {"name":"fromNative","parameterTypes":["com.sun.jna.FromNativeConverter","java.lang.Object","java.lang.reflect.Method"] }, {"name":"fromNative","parameterTypes":["java.lang.Class","java.lang.Object"] }, {"name":"fromNative","parameterTypes":["java.lang.reflect.Method","java.lang.Object"] }, {"name":"nativeType","parameterTypes":["java.lang.Class"] }, {"name":"toNative","parameterTypes":["com.sun.jna.ToNativeConverter","java.lang.Object"] }] +}, +{ + "name":"com.sun.jna.Native$ffi_callback", + "methods":[{"name":"invoke","parameterTypes":["long","long","long"] }] +}, +{ + "name":"com.sun.jna.NativeMapped", + "methods":[{"name":"toNative","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.Pointer", + "fields":[{"name":"peer"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"com.sun.jna.PointerType", + "fields":[{"name":"pointer"}] +}, +{ + "name":"com.sun.jna.Structure", + "fields":[{"name":"memory"}, {"name":"typeInfo"}], + "methods":[{"name":"autoRead","parameterTypes":[] }, {"name":"autoWrite","parameterTypes":[] }, {"name":"getTypeInfo","parameterTypes":[] }, {"name":"newInstance","parameterTypes":["java.lang.Class","long"] }] +}, +{ + "name":"com.sun.jna.Structure$ByValue" +}, +{ + "name":"com.sun.jna.Structure$FFIType$FFITypes", + "fields":[{"name":"ffi_type_double"}, {"name":"ffi_type_float"}, {"name":"ffi_type_longdouble"}, {"name":"ffi_type_pointer"}, {"name":"ffi_type_sint16"}, {"name":"ffi_type_sint32"}, {"name":"ffi_type_sint64"}, {"name":"ffi_type_sint8"}, {"name":"ffi_type_uint16"}, {"name":"ffi_type_uint32"}, {"name":"ffi_type_uint64"}, {"name":"ffi_type_uint8"}, {"name":"ffi_type_void"}] +}, +{ + "name":"com.sun.jna.WString", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Boolean", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["boolean"] }, {"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Byte", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["byte"] }] +}, +{ + "name":"java.lang.Character", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["char"] }] +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"getComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.Double", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["double"] }] +}, +{ + "name":"java.lang.Float", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["float"] }] +}, +{ + "name":"java.lang.Integer", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.Long", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"java.lang.Object", + "methods":[{"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.Short", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["short"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"","parameterTypes":["byte[]"] }, {"name":"","parameterTypes":["byte[]","java.lang.String"] }, {"name":"getBytes","parameterTypes":[] }, {"name":"getBytes","parameterTypes":["java.lang.String"] }, {"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }, {"name":"toCharArray","parameterTypes":[] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"java.lang.UnsatisfiedLinkError", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Void", + "fields":[{"name":"TYPE"}] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getParameterTypes","parameterTypes":[] }, {"name":"getReturnType","parameterTypes":[] }] +}, +{ + "name":"java.nio.Buffer", + "methods":[{"name":"position","parameterTypes":[] }] +}, +{ + "name":"java.nio.ByteBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.CharBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.DoubleBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.FloatBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.IntBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.LongBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.ShortBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +} +] diff --git a/mongocrypt/src/main/resources/META-INF/native-image/reflect-config.json b/mongocrypt/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 00000000000..4187c0e8eab --- /dev/null +++ b/mongocrypt/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,134 @@ +[ +{ + "name":"com.mongodb.crypt.capi.CAPI", + "allPublicFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$cstring", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_crypto_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_ctx_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hash_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hmac_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_kms_ctx_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_log_fn_t", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_random_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_status_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.CallbackProxy", + "methods":[{"name":"callback","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"com.sun.jna.Pointer", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"com.sun.jna.Structure$FFIType", + "allDeclaredFields":true, + "queryAllPublicConstructors":true, + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.Structure$FFIType$size_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.ptr.PointerByReference", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"boolean", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"int", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"java.lang.Throwable", + "methods":[{"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"isVarArgs","parameterTypes":[] }] +}, +{ + "name":"java.nio.Buffer" +}, +{ + "name":"long", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"void", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"org.slf4j.Logger" +} +] diff --git a/settings.gradle b/settings.gradle index ab252727079..92cf08421f7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -29,6 +29,7 @@ include ':driver-kotlin-sync' include ':driver-kotlin-coroutine' include ':bson-scala' include ':driver-scala' +include ':mongocrypt' include 'util:spock' include 'util:taglets' include ':graalvm-native-image-app' From fb7c733ac6a4cc908bf020bb2c1801c0662dd301 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 26 Aug 2024 22:04:20 -0700 Subject: [PATCH 02/22] Integrate mongocrypt tests. JAVA-5582 --- .../mongodb/crypt/capi/MongoCryptTest.java | 388 ++++++++++++++++++ .../src/test/resources/collection-info.json | 37 ++ .../src/test/resources/command-reply.json | 13 + mongocrypt/src/test/resources/command.json | 6 + .../resources/encrypted-command-reply.json | 16 + .../src/test/resources/encrypted-command.json | 11 + .../src/test/resources/encrypted-value.json | 6 + .../int32/encrypted-payload.json | 26 ++ .../int32/key-filter.json | 19 + .../int32/rangeopts.json | 14 + .../int32/value-to-encrypt.json | 20 + .../src/test/resources/json-schema.json | 15 + .../src/test/resources/key-document.json | 36 ++ .../test/resources/key-filter-keyAltName.json | 14 + mongocrypt/src/test/resources/key-filter.json | 19 + ...3498761234123456789012-local-document.json | 30 ++ mongocrypt/src/test/resources/kms-reply.txt | 6 + .../resources/list-collections-filter.json | 3 + .../test/resources/mongocryptd-command.json | 22 + .../src/test/resources/mongocryptd-reply.json | 18 + 20 files changed, 719 insertions(+) create mode 100644 mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java create mode 100644 mongocrypt/src/test/resources/collection-info.json create mode 100644 mongocrypt/src/test/resources/command-reply.json create mode 100644 mongocrypt/src/test/resources/command.json create mode 100644 mongocrypt/src/test/resources/encrypted-command-reply.json create mode 100644 mongocrypt/src/test/resources/encrypted-command.json create mode 100644 mongocrypt/src/test/resources/encrypted-value.json create mode 100644 mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json create mode 100644 mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json create mode 100644 mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json create mode 100644 mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json create mode 100644 mongocrypt/src/test/resources/json-schema.json create mode 100644 mongocrypt/src/test/resources/key-document.json create mode 100644 mongocrypt/src/test/resources/key-filter-keyAltName.json create mode 100644 mongocrypt/src/test/resources/key-filter.json create mode 100644 mongocrypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json create mode 100644 mongocrypt/src/test/resources/kms-reply.txt create mode 100644 mongocrypt/src/test/resources/list-collections-filter.json create mode 100644 mongocrypt/src/test/resources/mongocryptd-command.json create mode 100644 mongocrypt/src/test/resources/mongocryptd-reply.json diff --git a/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java b/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java new file mode 100644 index 00000000000..53e67f645fb --- /dev/null +++ b/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java @@ -0,0 +1,388 @@ +/* + * Copyright 2019-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.crypt.capi; + +import com.mongodb.crypt.capi.MongoCryptContext.State; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.RawBsonDocument; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +@SuppressWarnings("SameParameterValue") +public class MongoCryptTest { + @Test + public void testEncrypt() throws URISyntaxException, IOException { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + MongoCryptContext encryptor = mongoCrypt.createEncryptionContext("test", getResourceAsDocument("command.json")); + + assertEquals(State.NEED_MONGO_COLLINFO, encryptor.getState()); + + BsonDocument listCollectionsFilter = encryptor.getMongoOperation(); + assertEquals(getResourceAsDocument("list-collections-filter.json"), listCollectionsFilter); + + encryptor.addMongoOperationResult(getResourceAsDocument("collection-info.json")); + encryptor.completeMongoOperation(); + assertEquals(State.NEED_MONGO_MARKINGS, encryptor.getState()); + + BsonDocument jsonSchema = encryptor.getMongoOperation(); + assertEquals(getResourceAsDocument("mongocryptd-command.json"), jsonSchema); + + encryptor.addMongoOperationResult(getResourceAsDocument("mongocryptd-reply.json")); + encryptor.completeMongoOperation(); + assertEquals(State.NEED_MONGO_KEYS, encryptor.getState()); + + testKeyDecryptor(encryptor); + + assertEquals(State.READY, encryptor.getState()); + + RawBsonDocument encryptedDocument = encryptor.finish(); + assertEquals(State.DONE, encryptor.getState()); + assertEquals(getResourceAsDocument("encrypted-command.json"), encryptedDocument); + + encryptor.close(); + + mongoCrypt.close(); + } + + + @Test + public void testDecrypt() throws IOException, URISyntaxException { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + MongoCryptContext decryptor = mongoCrypt.createDecryptionContext(getResourceAsDocument("encrypted-command-reply.json")); + + assertEquals(State.NEED_MONGO_KEYS, decryptor.getState()); + + testKeyDecryptor(decryptor); + + assertEquals(State.READY, decryptor.getState()); + + RawBsonDocument decryptedDocument = decryptor.finish(); + assertEquals(State.DONE, decryptor.getState()); + assertEquals(getResourceAsDocument("command-reply.json"), decryptedDocument); + + decryptor.close(); + + mongoCrypt.close(); + } + + @Test + public void testEmptyAwsCredentials() throws URISyntaxException, IOException { + MongoCrypt mongoCrypt = MongoCrypts.create(MongoCryptOptions + .builder() + .kmsProviderOptions(new BsonDocument("aws", new BsonDocument())) + .needsKmsCredentialsStateEnabled(true) + .build()); + + MongoCryptContext decryptor = mongoCrypt.createDecryptionContext(getResourceAsDocument("encrypted-command-reply.json")); + + assertEquals(State.NEED_KMS_CREDENTIALS, decryptor.getState()); + + BsonDocument awsCredentials = new BsonDocument(); + awsCredentials.put("accessKeyId", new BsonString("example")); + awsCredentials.put("secretAccessKey", new BsonString("example")); + + decryptor.provideKmsProviderCredentials(new BsonDocument("aws", awsCredentials)); + + assertEquals(State.NEED_MONGO_KEYS, decryptor.getState()); + + mongoCrypt.close(); + } + + @Test + public void testMultipleCloseCalls() { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + mongoCrypt.close(); + mongoCrypt.close(); + } + + @Test + public void testDataKeyCreation() { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + List keyAltNames = Arrays.asList("first", "second"); + MongoCryptContext dataKeyContext = mongoCrypt.createDataKeyContext("local", + MongoDataKeyOptions.builder().masterKey(new BsonDocument()) + .keyAltNames(keyAltNames) + .build()); + assertEquals(State.READY, dataKeyContext.getState()); + + RawBsonDocument dataKeyDocument = dataKeyContext.finish(); + assertEquals(State.DONE, dataKeyContext.getState()); + assertNotNull(dataKeyDocument); + + List actualKeyAltNames = dataKeyDocument.getArray("keyAltNames").stream() + .map(bsonValue -> bsonValue.asString().getValue()) + .sorted() + .collect(Collectors.toList()); + assertIterableEquals(keyAltNames, actualKeyAltNames); + dataKeyContext.close(); + mongoCrypt.close(); + } + + @Test + public void testExplicitEncryptionDecryption() { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + BsonDocument documentToEncrypt = new BsonDocument("v", new BsonString("hello")); + MongoExplicitEncryptOptions options = MongoExplicitEncryptOptions.builder() + .keyId(new BsonBinary(BsonBinarySubType.UUID_STANDARD, Base64.getDecoder().decode("YWFhYWFhYWFhYWFhYWFhYQ=="))) + .algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") + .build(); + MongoCryptContext encryptor = mongoCrypt.createExplicitEncryptionContext(documentToEncrypt, options); + assertEquals(State.NEED_MONGO_KEYS, encryptor.getState()); + + testKeyDecryptor(encryptor); + + assertEquals(State.READY, encryptor.getState()); + + RawBsonDocument encryptedDocument = encryptor.finish(); + assertEquals(State.DONE, encryptor.getState()); + assertEquals(getResourceAsDocument("encrypted-value.json"), encryptedDocument); + + MongoCryptContext decryptor = mongoCrypt.createExplicitDecryptionContext(encryptedDocument); + + assertEquals(State.READY, decryptor.getState()); + + RawBsonDocument decryptedDocument = decryptor.finish(); + assertEquals(State.DONE, decryptor.getState()); + assertEquals(documentToEncrypt, decryptedDocument); + + encryptor.close(); + + mongoCrypt.close(); + } + + + @Test + public void testExplicitExpressionEncryption() { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + BsonDocument valueToEncrypt = getResourceAsDocument("fle2-find-range-explicit-v2/int32/value-to-encrypt.json"); + BsonDocument rangeOptions = getResourceAsDocument("fle2-find-range-explicit-v2/int32/rangeopts.json"); + BsonDocument expectedEncryptedPayload = getResourceAsDocument("fle2-find-range-explicit-v2/int32/encrypted-payload.json"); + + MongoExplicitEncryptOptions options = MongoExplicitEncryptOptions.builder() + .keyId(new BsonBinary(BsonBinarySubType.UUID_STANDARD, Base64.getDecoder().decode("q83vqxI0mHYSNBI0VniQEg=="))) + .algorithm("Range") + .queryType("range") + .contentionFactor(4L) + .rangeOptions(rangeOptions) + .build(); + MongoCryptContext encryptor = mongoCrypt.createEncryptExpressionContext(valueToEncrypt, options); + assertEquals(State.NEED_MONGO_KEYS, encryptor.getState()); + + testKeyDecryptor(encryptor, "fle2-find-range-explicit-v2/int32/key-filter.json", "keys/ABCDEFAB123498761234123456789012-local-document.json"); + + assertEquals(State.READY, encryptor.getState()); + + RawBsonDocument actualEncryptedPayload = encryptor.finish(); + assertEquals(State.DONE, encryptor.getState()); + assertEquals(expectedEncryptedPayload, actualEncryptedPayload); + + encryptor.close(); + mongoCrypt.close(); + } + + @Test + public void testRangePreviewQueryTypeIsNotSupported() { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + BsonDocument valueToEncrypt = getResourceAsDocument("fle2-find-range-explicit-v2/int32/value-to-encrypt.json"); + BsonDocument rangeOptions = getResourceAsDocument("fle2-find-range-explicit-v2/int32/rangeopts.json"); + + MongoExplicitEncryptOptions options = MongoExplicitEncryptOptions.builder() + .keyId(new BsonBinary(BsonBinarySubType.UUID_STANDARD, Base64.getDecoder().decode("q83vqxI0mHYSNBI0VniQEg=="))) + .algorithm("Range") + .queryType("rangePreview") + .contentionFactor(4L) + .rangeOptions(rangeOptions) + .build(); + + MongoCryptException exp = assertThrows(MongoCryptException.class, () -> mongoCrypt.createEncryptExpressionContext(valueToEncrypt, options)); + assertEquals("Query type 'rangePreview' is deprecated, please use 'range'", exp.getMessage()); + mongoCrypt.close(); + } + + @Test + public void testRangePreviewAlgorithmIsNotSupported() { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + BsonDocument rangeOptions = getResourceAsDocument("fle2-find-range-explicit-v2/int32/rangeopts.json"); + + IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, () -> MongoExplicitEncryptOptions.builder() + .keyId(new BsonBinary(BsonBinarySubType.UUID_STANDARD, Base64.getDecoder().decode("q83vqxI0mHYSNBI0VniQEg=="))) + .algorithm("RangePreview") + .queryType("range") + .contentionFactor(4L) + .rangeOptions(rangeOptions) + .build()); + + assertEquals("Invalid configuration, contentionFactor can only be set if algorithm is 'Indexed' or 'Range'", + illegalStateException.getMessage()); + mongoCrypt.close(); + } + + @Test + public void testExplicitEncryptionDecryptionKeyAltName() throws IOException, URISyntaxException { + MongoCrypt mongoCrypt = createMongoCrypt(); + assertNotNull(mongoCrypt); + + BsonDocument documentToEncrypt = new BsonDocument("v", new BsonString("hello")); + MongoExplicitEncryptOptions options = MongoExplicitEncryptOptions.builder() + .keyAltName("altKeyName") + .algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") + .build(); + MongoCryptContext encryptor = mongoCrypt.createExplicitEncryptionContext(documentToEncrypt, options); + + assertEquals(State.NEED_MONGO_KEYS, encryptor.getState()); + testKeyDecryptor(encryptor, "key-filter-keyAltName.json", "key-document.json"); + + assertEquals(State.READY, encryptor.getState()); + + RawBsonDocument encryptedDocument = encryptor.finish(); + assertEquals(State.DONE, encryptor.getState()); + assertEquals(getResourceAsDocument("encrypted-value.json"), encryptedDocument); + + MongoCryptContext decryptor = mongoCrypt.createExplicitDecryptionContext(encryptedDocument); + + assertEquals(State.READY, decryptor.getState()); + + RawBsonDocument decryptedDocument = decryptor.finish(); + assertEquals(State.DONE, decryptor.getState()); + assertEquals(documentToEncrypt, decryptedDocument); + + encryptor.close(); + + mongoCrypt.close(); + } + + private void testKeyDecryptor(final MongoCryptContext context) { + testKeyDecryptor(context, "key-filter.json", "key-document.json"); + } + + private void testKeyDecryptor(final MongoCryptContext context, final String keyFilterPath, final String keyDocumentPath) { + BsonDocument keyFilter = context.getMongoOperation(); + assertEquals(getResourceAsDocument(keyFilterPath), keyFilter); + context.addMongoOperationResult(getResourceAsDocument(keyDocumentPath)); + context.completeMongoOperation(); + if (context.getState() == State.READY) { + return; + } + + assertEquals(State.NEED_KMS, context.getState()); + + MongoKeyDecryptor keyDecryptor = context.nextKeyDecryptor(); + assertEquals("aws", keyDecryptor.getKmsProvider()); + assertEquals("kms.us-east-1.amazonaws.com:443", keyDecryptor.getHostName()); + + ByteBuffer keyDecryptorMessage = keyDecryptor.getMessage(); + assertEquals(790, keyDecryptorMessage.remaining()); + + int bytesNeeded = keyDecryptor.bytesNeeded(); + assertEquals(1024, bytesNeeded); + + keyDecryptor.feed(getHttpResourceAsByteBuffer("kms-reply.txt")); + bytesNeeded = keyDecryptor.bytesNeeded(); + assertEquals(0, bytesNeeded); + + assertNull(context.nextKeyDecryptor()); + + context.completeKeyDecryptors(); + } + + private MongoCrypt createMongoCrypt() { + return MongoCrypts.create(MongoCryptOptions + .builder() + .awsKmsProviderOptions(MongoAwsKmsProviderOptions.builder() + .accessKeyId("example") + .secretAccessKey("example") + .build()) + .localKmsProviderOptions(MongoLocalKmsProviderOptions.builder() + .localMasterKey(ByteBuffer.wrap(new byte[96])) + .build()) + .build()); + } + + private static BsonDocument getResourceAsDocument(final String fileName) { + return BsonDocument.parse(getFileAsString(fileName, System.getProperty("line.separator"))); + } + + private static ByteBuffer getHttpResourceAsByteBuffer(final String fileName) { + return ByteBuffer.wrap(getFileAsString(fileName, "\r\n").getBytes(StandardCharsets.UTF_8)); + } + + private static String getFileAsString(final String fileName, String lineSeparator) { + try { + URL resource = MongoCryptTest.class.getResource("/" + fileName); + if (resource == null) { + throw new RuntimeException("Could not find file " + fileName); + } + File file = new File(resource.toURI()); + StringBuilder stringBuilder = new StringBuilder(); + String line; + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(Files.newInputStream(file.toPath()), StandardCharsets.UTF_8))) { + boolean first = true; + while ((line = reader.readLine()) != null) { + if (!first) { + stringBuilder.append(lineSeparator); + } + first = false; + stringBuilder.append(line); + } + } + return stringBuilder.toString(); + } catch (Throwable t) { + throw new RuntimeException("Could not parse file " + fileName, t); + } + } +} diff --git a/mongocrypt/src/test/resources/collection-info.json b/mongocrypt/src/test/resources/collection-info.json new file mode 100644 index 00000000000..3b9660938a3 --- /dev/null +++ b/mongocrypt/src/test/resources/collection-info.json @@ -0,0 +1,37 @@ +{ + "type": "collection", + "name": "test", + "idIndex": { + "ns": "test.test", + "name": "_id_", + "key": { + "_id": { + "$numberInt": "1" + } + }, + "v": { + "$numberInt": "2" + } + }, + "options": { + "validator": { + "$jsonSchema": { + "properties": { + "ssn": { + "encrypt": { + "keyId": { + "$binary": { + "base64": "YWFhYWFhYWFhYWFhYWFhYQ==", + "subType": "04" + } + }, + "type": "string", + "algorithm": "AEAD_AES_CBC_HMAC_SHA512-Deterministic" + } + } + }, + "bsonType": "object" + } + } + } +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/command-reply.json b/mongocrypt/src/test/resources/command-reply.json new file mode 100644 index 00000000000..c110f737f45 --- /dev/null +++ b/mongocrypt/src/test/resources/command-reply.json @@ -0,0 +1,13 @@ +{ + "cursor": { + "firstBatch": [ + { + "_id": 1, + "ssn": "457-55-5462" + } + ], + "id": 0, + "ns": "test.test" + }, + "ok": 1 +} diff --git a/mongocrypt/src/test/resources/command.json b/mongocrypt/src/test/resources/command.json new file mode 100644 index 00000000000..d04bf7799ad --- /dev/null +++ b/mongocrypt/src/test/resources/command.json @@ -0,0 +1,6 @@ +{ + "find": "test", + "filter": { + "ssn": "457-55-5462" + } +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/encrypted-command-reply.json b/mongocrypt/src/test/resources/encrypted-command-reply.json new file mode 100644 index 00000000000..73d4d3427ee --- /dev/null +++ b/mongocrypt/src/test/resources/encrypted-command-reply.json @@ -0,0 +1,16 @@ +{ + "cursor" : { + "firstBatch" : [ + { + "_id": 1, + "ssn": { + "$binary": "AWFhYWFhYWFhYWFhYWFhYWECRTOW9yZzNDn5dGwuqsrJQNLtgMEKaujhs9aRWRp+7Yo3JK8N8jC8P0Xjll6C1CwLsE/iP5wjOMhVv1KMMyOCSCrHorXRsb2IKPtzl2lKTqQ=", + "$type": "06" + } + } + ], + "id" : 0, + "ns" : "test.test" + }, + "ok" : 1 +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/encrypted-command.json b/mongocrypt/src/test/resources/encrypted-command.json new file mode 100644 index 00000000000..8b8cfaa27ee --- /dev/null +++ b/mongocrypt/src/test/resources/encrypted-command.json @@ -0,0 +1,11 @@ +{ + "filter": { + "ssn": { + "$binary": { + "base64": "AWFhYWFhYWFhYWFhYWFhYWECRTOW9yZzNDn5dGwuqsrJQNLtgMEKaujhs9aRWRp+7Yo3JK8N8jC8P0Xjll6C1CwLsE/iP5wjOMhVv1KMMyOCSCrHorXRsb2IKPtzl2lKTqQ=", + "subType": "06" + } + } + }, + "find": "test" +} diff --git a/mongocrypt/src/test/resources/encrypted-value.json b/mongocrypt/src/test/resources/encrypted-value.json new file mode 100644 index 00000000000..e1a832b5ecb --- /dev/null +++ b/mongocrypt/src/test/resources/encrypted-value.json @@ -0,0 +1,6 @@ +{ + "v": { + "$binary": "AWFhYWFhYWFhYWFhYWFhYWECW+zDjR/69eS6VtuMD5+O2lZw6JyiWOw3avI7mnUkdpKzPfvy8F/nlZrgZa2cGmQsb0TmLZuk5trldosnGKD91w==", + "$type": "06" + } +} diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json new file mode 100644 index 00000000000..7db5540ca1b --- /dev/null +++ b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json @@ -0,0 +1,26 @@ +{ + "v": { + "$and": [ + { + "age": { + "$gte": { + "$binary": { + "base64": "DQECAAADcGF5bG9hZACZAQAABGcAhQEAAAMwAH0AAAAFZAAgAAAAAInd0noBhIiJMv8QTjcfgRqnnVhxRJRRACLfvgT+CTR/BXMAIAAAAADm0EjqF/T4EmR6Dw6NaPLrL0OuzS4AFvm90czFluAAygVsACAAAAAA5MXcYWjYlzhPFUDebBEa17B5z2bupmaW9uCdtLjc7RkAAzEAfQAAAAVkACAAAAAA7lkNtT6RLw91aJ07K/blwlFs5wi9pQjqUXDcaCTxe98FcwAgAAAAAPwySffuLQihmF70Ot93KtaUMNU8KpmA+niyPRcvarNMBWwAIAAAAACDv6fJXXwRqwZH3O2kO+hdeLZ36U6bMZSui8kv0PsPtAADMgB9AAAABWQAIAAAAACcMWVTbZC4ox5VdjWeYKLgf4oBjpPlbTTAkucm9JPK0wVzACAAAAAA3tIww4ZTytkxFsUKyJbc3zwQ2w7DhkOqaNvX9g8pi3gFbAAgAAAAAGs9XR3Q1JpxV+HPW8P2GvCuCBF5bGZ8Kl1zHqzZcd5/AAASY20ABAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAgAAABBzZWNvbmRPcGVyYXRvcgAEAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + }, + { + "age": { + "$lte": { + "$binary": { + "base64": "DTsAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgACAAAAEHNlY29uZE9wZXJhdG9yAAQAAAAA", + "subType": "06" + } + } + } + } + ] + } +} diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json new file mode 100644 index 00000000000..897364761c7 --- /dev/null +++ b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json @@ -0,0 +1,19 @@ +{ + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": "q83vqxI0mHYSNBI0VniQEg==", + "$type": "04" + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json new file mode 100644 index 00000000000..2e1407fe4e6 --- /dev/null +++ b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json @@ -0,0 +1,14 @@ +{ + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + }, + "sparsity": { + "$numberLong": "1" + }, + "trimFactor": { + "$numberInt": "1" + } +} diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json new file mode 100644 index 00000000000..4c294e887e6 --- /dev/null +++ b/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json @@ -0,0 +1,20 @@ +{ + "v": { + "$and": [ + { + "age": { + "$gte": { + "$numberInt": "23" + } + } + }, + { + "age": { + "$lte": { + "$numberInt": "35" + } + } + } + ] + } +} diff --git a/mongocrypt/src/test/resources/json-schema.json b/mongocrypt/src/test/resources/json-schema.json new file mode 100644 index 00000000000..059373d9ca1 --- /dev/null +++ b/mongocrypt/src/test/resources/json-schema.json @@ -0,0 +1,15 @@ +{ + "properties": { + "ssn": { + "encrypt": { + "keyId": { + "$binary": "YWFhYWFhYWFhYWFhYWFhYQ==", + "$type": "04" + }, + "type": "string", + "algorithm": "AEAD_AES_CBC_HMAC_SHA512-Deterministic" + } + } + }, + "bsonType": "object" +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/key-document.json b/mongocrypt/src/test/resources/key-document.json new file mode 100644 index 00000000000..5414072596d --- /dev/null +++ b/mongocrypt/src/test/resources/key-document.json @@ -0,0 +1,36 @@ +{ + "status": { + "$numberInt": "1" + }, + "_id": { + "$binary": { + "base64": "YWFhYWFhYWFhYWFhYWFhYQ==", + "subType": "04" + } + }, + "masterKey": { + "region": "us-east-1", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "provider": "aws" + }, + "updateDate": { + "$date": { + "$numberLong": "1557827033449" + } + }, + "keyMaterial": { + "$binary": { + "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1557827033449" + } + }, + "keyAltNames": [ + "altKeyName", + "another_altname" + ] +} diff --git a/mongocrypt/src/test/resources/key-filter-keyAltName.json b/mongocrypt/src/test/resources/key-filter-keyAltName.json new file mode 100644 index 00000000000..eb53a142a14 --- /dev/null +++ b/mongocrypt/src/test/resources/key-filter-keyAltName.json @@ -0,0 +1,14 @@ +{ + "$or": [ + { + "_id": { + "$in": [] + } + }, + { + "keyAltNames": { + "$in": ["altKeyName"] + } + } + ] +} diff --git a/mongocrypt/src/test/resources/key-filter.json b/mongocrypt/src/test/resources/key-filter.json new file mode 100644 index 00000000000..9ad7c70e5a7 --- /dev/null +++ b/mongocrypt/src/test/resources/key-filter.json @@ -0,0 +1,19 @@ +{ + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": "YWFhYWFhYWFhYWFhYWFhYQ==", + "$type": "04" + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json b/mongocrypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json new file mode 100644 index 00000000000..e5d1a3f7661 --- /dev/null +++ b/mongocrypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json @@ -0,0 +1,30 @@ +{ + "_id": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "27OBvUqHAuYFy60nwCdvq2xmZ4kFzVySphXzBGq+HEot13comCoydEfnltBzLTuXLbV9cnREFJIO5f0jMqrlkxIuvAV8yO84p5VJTEa8j/xSNe7iA594rx7UeKT0fOt4VqM47fht8h+8PZYc5JVezvEMvwk115IBCwENxDjLtT0g+y8Hf+aTUEGtxrYToH8zf1/Y7S16mHiIc4jK3/vxHw==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648915408923" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648915408923" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } +} diff --git a/mongocrypt/src/test/resources/kms-reply.txt b/mongocrypt/src/test/resources/kms-reply.txt new file mode 100644 index 00000000000..c2c52e38413 --- /dev/null +++ b/mongocrypt/src/test/resources/kms-reply.txt @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +x-amzn-RequestId: deeb35e5-4ecb-4bf1-9af5-84a54ff0af0e +Content-Type: application/x-amz-json-1.1 +Content-Length: 233 + +{"KeyId": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", "Plaintext": "TqhXy3tKckECjy4/ZNykMWG8amBF46isVPzeOgeusKrwheBmYaU8TMG5AHR/NeUDKukqo8hBGgogiQOVpLPkqBQHD8YkLsNbDmHoGOill5QAHnniF/Lz405bGucB5TfR"} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/list-collections-filter.json b/mongocrypt/src/test/resources/list-collections-filter.json new file mode 100644 index 00000000000..2f37dc5b093 --- /dev/null +++ b/mongocrypt/src/test/resources/list-collections-filter.json @@ -0,0 +1,3 @@ +{ + "name": "test" +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/mongocryptd-command.json b/mongocrypt/src/test/resources/mongocryptd-command.json new file mode 100644 index 00000000000..2ec0612d7e9 --- /dev/null +++ b/mongocrypt/src/test/resources/mongocryptd-command.json @@ -0,0 +1,22 @@ +{ + "find": "test", + "filter": { + "ssn": "457-55-5462" + }, + "jsonSchema": { + "properties": { + "ssn": { + "encrypt": { + "keyId": { + "$binary": "YWFhYWFhYWFhYWFhYWFhYQ==", + "$type": "04" + }, + "type": "string", + "algorithm": "AEAD_AES_CBC_HMAC_SHA512-Deterministic" + } + } + }, + "bsonType": "object" + }, + "isRemoteSchema": true +} \ No newline at end of file diff --git a/mongocrypt/src/test/resources/mongocryptd-reply.json b/mongocrypt/src/test/resources/mongocryptd-reply.json new file mode 100644 index 00000000000..0d1873de7e2 --- /dev/null +++ b/mongocrypt/src/test/resources/mongocryptd-reply.json @@ -0,0 +1,18 @@ +{ + "schemaRequiresEncryption": true, + "ok": { + "$numberInt": "1" + }, + "result": { + "filter": { + "ssn": { + "$binary": { + "base64": "ADgAAAAQYQABAAAABWtpABAAAAAEYWFhYWFhYWFhYWFhYWFhYQJ2AAwAAAA0NTctNTUtNTQ2MgAA", + "subType": "06" + } + } + }, + "find": "test" + }, + "hasEncryptedPlaceholders": true +} \ No newline at end of file From 0750e208d2d349c547963c189f036deab17a1b1d Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 27 Aug 2024 16:29:43 -0700 Subject: [PATCH 03/22] Prevent downloading binaries on each local run. JAVA-5582 --- mongocrypt/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index b27d42ecada..8e3457a7de7 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -107,6 +107,8 @@ tasks.register("downloadJava") { src(downloadUrl) dest("${jnaDownloadsDir}/$binariesArchiveName") overwrite(true) + /* To make sure we don't download archive with binaries if it already exists or if it hasn't changed in S3 bucket.*/ + onlyIfModified(true) } // The `processResources` task (defined by the `java-library` plug-in) consumes files in the main source set. From 4d967e366cd19c174fd889ff88cfd5bdced6618c Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 27 Aug 2024 22:48:12 -0700 Subject: [PATCH 04/22] Update manifest. JAVA-5582 --- mongocrypt/build.gradle.kts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 8e3457a7de7..19adc50d061 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -36,6 +36,7 @@ plugins { group = "org.mongodb" base.archivesBaseName = "mongodb-crypt" description = "MongoDB client-side crypto support" +ext.set("pomName", "MongoCrypt") java { sourceCompatibility = JavaVersion.VERSION_1_8 @@ -167,19 +168,6 @@ tasks.withType { } tasks.jar { - manifest { - attributes( - "-exportcontents" to "com.mongodb.crypt.capi.*;-noimport:=true", - "Automatic-Module-Name" to "com.mongodb.crypt.capi", - "Import-Package" to "org.bson.*", - "Build-Version" to gitVersion, - "Bundle-Version" to gitVersion, - "Bundle-Name" to "MongoCrypt", - "Bundle-SymbolicName" to "com.mongodb.crypt.capi", - "Private-Package" to "" - ) - } - //NOTE this enables depending on the mongocrypt from driver-core dependsOn("downloadJnaLibs") } @@ -189,3 +177,20 @@ tasks.javadoc { (options as StandardJavadocDocletOptions).addBooleanOption("html5", true) } } + +afterEvaluate { + tasks.jar { + manifest { + attributes( + "-exportcontents" to "com.mongodb.crypt.capi.*;-noimport:=true", + "Automatic-Module-Name" to "com.mongodb.crypt.capi", + "Import-Package" to "org.bson.*", + "Build-Version" to gitVersion, + "Bundle-Version" to gitVersion, + "Bundle-Name" to "MongoCrypt", + "Bundle-SymbolicName" to "com.mongodb.crypt.capi", + "Private-Package" to "" + ) + } + } +} \ No newline at end of file From b71f62178dde393cf6b6092f3c06e192591dba5a Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 30 Aug 2024 15:26:44 -0700 Subject: [PATCH 05/22] Add slf4j to manifest. JAVA-5582 --- mongocrypt/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 19adc50d061..672b983b49b 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -184,7 +184,7 @@ afterEvaluate { attributes( "-exportcontents" to "com.mongodb.crypt.capi.*;-noimport:=true", "Automatic-Module-Name" to "com.mongodb.crypt.capi", - "Import-Package" to "org.bson.*", + "Import-Package" to "org.slf4j.*;resolution:=optional,org.bson.*", "Build-Version" to gitVersion, "Bundle-Version" to gitVersion, "Bundle-Name" to "MongoCrypt", From 1233c1b68e2ca939b1b6c20726aa4f5b6aa97733 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 30 Aug 2024 15:31:28 -0700 Subject: [PATCH 06/22] Remove gitignore due to having one in root directory. JAVA-5582 --- mongocrypt/.gitignore | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 mongocrypt/.gitignore diff --git a/mongocrypt/.gitignore b/mongocrypt/.gitignore deleted file mode 100644 index 64e5adbd91a..00000000000 --- a/mongocrypt/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -*~ -.#* -.git -*# - -# os x stuff -*Thumbs.db* -*.DS_Store - -# Build artifacts -build -out - -# Eclipse files -.classpath -.project -.settings - -# Intellij IDEA files -*.ipr -*.iws -*.iml -*.idea -workspace.xml -atlassian-ide-plugin.xml - -# gradle -.gradle - -# local settings -**/gradle.properties -local.properties - -# jenv -.java-version - -# bin -/bin -/benchmarks/bin From cc32a177a1e95ebcd3e7aedd462bcd52a0308766 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 9 Sep 2024 16:33:48 -0700 Subject: [PATCH 07/22] - Fix checkstyle issues. - Remove redundant git function from build script. - Integrate MongoCrypt benchmarks. JAVA-5582 --- driver-benchmarks/build.gradle | 1 + .../benchmark/benchmarks/BenchmarkSuite.java | 16 ++ .../framework/BenchmarkResultWriter.java | 2 + .../EvergreenBenchmarkResultWriter.java | 26 ++ ...MinimalTextBasedBenchmarkResultWriter.java | 6 + .../framework/MongoCryptBenchmarkRunner.java | 224 ++++++++++++++++++ .../framework/MongocryptBecnhmarkResult.java | 84 +++++++ .../TextBasedBenchmarkResultWriter.java | 14 ++ .../src/resources/keyDocument.json | 24 ++ mongocrypt/build.gradle.kts | 18 -- .../src/main/com/mongodb/crypt/capi/CAPI.java | 2 +- .../com/mongodb/crypt/capi/CAPIHelper.java | 4 +- .../capi/MongoAwsKmsProviderOptions.java | 6 +- .../com/mongodb/crypt/capi/MongoCrypt.java | 4 +- .../mongodb/crypt/capi/MongoCryptContext.java | 2 +- .../crypt/capi/MongoCryptContextImpl.java | 2 +- .../crypt/capi/MongoCryptException.java | 4 +- .../mongodb/crypt/capi/MongoCryptImpl.java | 2 +- .../mongodb/crypt/capi/MongoCryptOptions.java | 8 +- .../com/mongodb/crypt/capi/MongoCrypts.java | 10 +- .../crypt/capi/MongoDataKeyOptions.java | 4 +- .../capi/MongoExplicitEncryptOptions.java | 22 +- .../mongodb/crypt/capi/MongoKeyDecryptor.java | 2 +- .../crypt/capi/MongoKeyDecryptorImpl.java | 2 +- .../capi/MongoLocalKmsProviderOptions.java | 6 +- .../capi/MongoRewrapManyDataKeyOptions.java | 4 +- .../com/mongodb/crypt/capi/package-info.java | 5 +- 27 files changed, 445 insertions(+), 59 deletions(-) create mode 100644 driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongoCryptBenchmarkRunner.java create mode 100644 driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongocryptBecnhmarkResult.java create mode 100644 driver-benchmarks/src/resources/keyDocument.json diff --git a/driver-benchmarks/build.gradle b/driver-benchmarks/build.gradle index 960674011eb..adac283f28d 100644 --- a/driver-benchmarks/build.gradle +++ b/driver-benchmarks/build.gradle @@ -31,6 +31,7 @@ sourceSets { dependencies { api project(':driver-sync') + api project(':mongocrypt') implementation "ch.qos.logback:logback-classic:$logbackVersion" } diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java index 08dce238b70..ab41fcdf12f 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java @@ -22,6 +22,8 @@ import com.mongodb.benchmark.framework.BenchmarkResultWriter; import com.mongodb.benchmark.framework.BenchmarkRunner; import com.mongodb.benchmark.framework.EvergreenBenchmarkResultWriter; +import com.mongodb.benchmark.framework.MongoCryptBenchmarkRunner; +import com.mongodb.benchmark.framework.MongocryptBecnhmarkResult; import org.bson.Document; import org.bson.codecs.Codec; @@ -85,6 +87,20 @@ private static void runBenchmarks() runBenchmark(new MultiFileExportBenchmark()); runBenchmark(new GridFSMultiFileUploadBenchmark()); runBenchmark(new GridFSMultiFileDownloadBenchmark()); + runBenchmark(new GridFSMultiFileDownloadBenchmark()); + + runMongoCryptBenchMarks(); + } + + private static void runMongoCryptBenchMarks() throws InterruptedException { + // This runner has been migrated from libmongocrypt as it is. + List results = new MongoCryptBenchmarkRunner().run(); + + for (BenchmarkResultWriter writer : WRITERS) { + for (MongocryptBecnhmarkResult result : results) { + writer.write(result); + } + } } private static void runBenchmark(final Benchmark benchmark) throws Exception { diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/BenchmarkResultWriter.java b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/BenchmarkResultWriter.java index d7f4a4701ce..26828a5a75f 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/BenchmarkResultWriter.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/BenchmarkResultWriter.java @@ -21,4 +21,6 @@ public interface BenchmarkResultWriter extends Closeable { void write(BenchmarkResult benchmarkResult); + + void write(MongocryptBecnhmarkResult result); } diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/EvergreenBenchmarkResultWriter.java b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/EvergreenBenchmarkResultWriter.java index 719bf269163..f1e5361ffeb 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/EvergreenBenchmarkResultWriter.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/EvergreenBenchmarkResultWriter.java @@ -65,6 +65,32 @@ public void write(final BenchmarkResult benchmarkResult) { jsonWriter.writeEndDocument(); } + @Override + public void write(final MongocryptBecnhmarkResult result) { + jsonWriter.writeStartDocument(); + + jsonWriter.writeStartDocument("info"); + jsonWriter.writeString("test_name", result.getTestName()); + + jsonWriter.writeStartDocument("args"); + jsonWriter.writeInt32("threads", result.getThreadCount()); + jsonWriter.writeEndDocument(); + jsonWriter.writeEndDocument(); + + jsonWriter.writeString("created_at", result.getCreatedAt()); + jsonWriter.writeString("completed_at", result.getCompletedAt()); + jsonWriter.writeStartArray("metrics"); + + jsonWriter.writeStartDocument(); + jsonWriter.writeString("name", result.getMetricName()); + jsonWriter.writeString("type", result.getMetricType()); + jsonWriter.writeDouble("value", result.getMedianOpsPerSec()); + jsonWriter.writeEndDocument(); + + jsonWriter.writeEndArray(); + jsonWriter.writeEndDocument(); + } + @Override public void close() throws IOException { jsonWriter.writeEndArray(); diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MinimalTextBasedBenchmarkResultWriter.java b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MinimalTextBasedBenchmarkResultWriter.java index 73f85697f33..b5ed85f1f2e 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MinimalTextBasedBenchmarkResultWriter.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MinimalTextBasedBenchmarkResultWriter.java @@ -34,6 +34,12 @@ public void write(final BenchmarkResult benchmarkResult) { benchmarkResult.getElapsedTimeNanosAtPercentile(50) / ONE_BILLION); } + @Override + public void write(final MongocryptBecnhmarkResult result) { + printStream.printf("%s: %d%n", result.getTestName(), + result.getMedianOpsPerSec()); + } + @Override public void close() { } diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongoCryptBenchmarkRunner.java b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongoCryptBenchmarkRunner.java new file mode 100644 index 00000000000..33b6c0ad102 --- /dev/null +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongoCryptBenchmarkRunner.java @@ -0,0 +1,224 @@ +package com.mongodb.benchmark.framework; + +/* + * Copyright 2023-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import com.mongodb.crypt.capi.CAPI; +import com.mongodb.crypt.capi.MongoCrypt; +import com.mongodb.crypt.capi.MongoCryptContext; +import com.mongodb.crypt.capi.MongoCryptOptions; +import com.mongodb.crypt.capi.MongoCrypts; +import com.mongodb.crypt.capi.MongoExplicitEncryptOptions; +import com.mongodb.crypt.capi.MongoLocalKmsProviderOptions; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.RawBsonDocument; + +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class MongoCryptBenchmarkRunner { + static final int NUM_FIELDS = 1500; + static final int NUM_WARMUP_SECS = 2; + static final int NUM_SECS = 10; + static final byte[] LOCAL_MASTER_KEY = new byte[]{ + -99, -108, 75, 13, -109, -48, -59, 68, -91, 114, -3, 50, 27, -108, 48, -112, 35, 53, + 115, 124, -16, -10, -62, -12, -38, 35, 86, -25, -113, 4, -52, -6, -34, 117, -76, 81, + -121, -13, -117, -105, -41, 75, 68, 59, -84, 57, -94, -58, 77, -111, 0, 62, -47, -6, 74, + 48, -63, -46, -58, 94, -5, -84, 65, -14, 72, 19, 60, -101, 80, -4, -89, 36, 122, 46, 2, + 99, -93, -58, 22, 37, 81, 80, 120, 62, 15, -40, 110, -124, -90, -20, -115, 45, 36, 71, + -27, -81 + }; + + private static String getFileAsString(final String fileName) { + try { + URL resource = BenchmarkRunner.class.getResource("/" + fileName); + if (resource == null) { + throw new RuntimeException("Could not find file " + fileName); + } + return new String(Files.readAllBytes(Paths.get(resource.toURI()))); + } catch (Throwable t) { + throw new RuntimeException("Could not parse file " + fileName, t); + } + } + + private static BsonDocument getResourceAsDocument(final String fileName) { + return BsonDocument.parse(getFileAsString(fileName)); + } + + private static MongoCrypt createMongoCrypt() { + return MongoCrypts.create(MongoCryptOptions + .builder() + .localKmsProviderOptions(MongoLocalKmsProviderOptions.builder() + .localMasterKey(ByteBuffer.wrap(LOCAL_MASTER_KEY)) + .build()) + .build()); + } + + // DecryptTask decrypts a document repeatedly for a specified number of seconds and records ops/sec. + private static class DecryptTask implements Runnable { + public DecryptTask(MongoCrypt mongoCrypt, BsonDocument toDecrypt, int numSecs, CountDownLatch doneSignal) { + this.mongoCrypt = mongoCrypt; + this.toDecrypt = toDecrypt; + this.opsPerSecs = new ArrayList(numSecs); + this.numSecs = numSecs; + this.doneSignal = doneSignal; + } + + public void run() { + for (int i = 0; i < numSecs; i++) { + long opsPerSec = 0; + long start = System.nanoTime(); + // Run for one second. + while (System.nanoTime() - start < 1_000_000_000) { + try (MongoCryptContext ctx = mongoCrypt.createDecryptionContext(toDecrypt)) { + assert ctx.getState() == MongoCryptContext.State.READY; + ctx.finish(); + opsPerSec++; + } + } + opsPerSecs.add(opsPerSec); + } + doneSignal.countDown(); + } + + public long getMedianOpsPerSecs() { + if (opsPerSecs.size() == 0) { + throw new IllegalStateException("opsPerSecs is empty. Was `run` called?"); + } + Collections.sort(opsPerSecs); + return opsPerSecs.get(numSecs / 2); + } + + private MongoCrypt mongoCrypt; + private BsonDocument toDecrypt; + private ArrayList opsPerSecs; + private int numSecs; + private CountDownLatch doneSignal; + } + + public List run() throws InterruptedException { + System.out.printf("BenchmarkRunner is using libmongocrypt version=%s, NUM_WARMUP_SECS=%d, NUM_SECS=%d%n", + CAPI.mongocrypt_version(null).toString(), NUM_WARMUP_SECS, NUM_SECS); + // `keyDocument` is a Data Encryption Key (DEK) encrypted with the Key Encryption Key (KEK) `LOCAL_MASTER_KEY`. + BsonDocument keyDocument = getResourceAsDocument("keyDocument.json"); + try (MongoCrypt mongoCrypt = createMongoCrypt()) { + // `encrypted` will contain encrypted fields. + BsonDocument encrypted = new BsonDocument(); + { + for (int i = 0; i < NUM_FIELDS; i++) { + MongoExplicitEncryptOptions options = MongoExplicitEncryptOptions.builder() + .keyId(new BsonBinary(BsonBinarySubType.UUID_STANDARD, Base64.getDecoder().decode("YWFhYWFhYWFhYWFhYWFhYQ=="))) + .algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") + .build(); + BsonDocument toEncrypt = new BsonDocument("v", new BsonString(String.format("value %04d", i))); + try (MongoCryptContext ctx = mongoCrypt.createExplicitEncryptionContext(toEncrypt, options)) { + // If mongocrypt_t has not yet cached the DEK, supply it. + if (MongoCryptContext.State.NEED_MONGO_KEYS == ctx.getState()) { + ctx.addMongoOperationResult(keyDocument); + ctx.completeMongoOperation(); + } + assert ctx.getState() == MongoCryptContext.State.READY; + RawBsonDocument result = ctx.finish(); + BsonValue encryptedValue = result.get("v"); + String key = String.format("key%04d", i); + encrypted.append(key, encryptedValue); + } + } + } + + // Warm up benchmark and discard the result. + DecryptTask warmup = new DecryptTask(mongoCrypt, encrypted, NUM_WARMUP_SECS, new CountDownLatch(1)); + warmup.run(); + + // Decrypt `encrypted` and measure ops/sec. + // Check with varying thread counts to measure impact of a shared pool of Cipher instances. + int[] threadCounts = {1, 2, 8, 64}; + ArrayList totalMedianOpsPerSecs = new ArrayList(threadCounts.length); + ArrayList createdAts = new ArrayList(threadCounts.length); + ArrayList completedAts = new ArrayList(threadCounts.length); + + for (int threadCount : threadCounts) { + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch doneSignal = new CountDownLatch(threadCount); + ArrayList decryptTasks = new ArrayList(threadCount); + createdAts.add(ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT)); + + for (int i = 0; i < threadCount; i++) { + DecryptTask decryptTask = new DecryptTask(mongoCrypt, encrypted, NUM_SECS, doneSignal); + decryptTasks.add(decryptTask); + executorService.submit(decryptTask); + } + + // Await completion of all tasks. Tasks are expected to complete shortly after NUM_SECS. Time out `await` if time exceeds 2 * NUM_SECS. + boolean ok = doneSignal.await(NUM_SECS * 2, TimeUnit.SECONDS); + assert ok; + completedAts.add(ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT)); + // Sum the median ops/secs of all tasks to get total throughput. + long totalMedianOpsPerSec = 0; + for (DecryptTask decryptTask : decryptTasks) { + totalMedianOpsPerSec += decryptTask.getMedianOpsPerSecs(); + } + System.out.printf("threadCount=%d. Decrypting 1500 fields median ops/sec : %d%n", threadCount, totalMedianOpsPerSec); + totalMedianOpsPerSecs.add(totalMedianOpsPerSec); + executorService.shutdown(); + ok = executorService.awaitTermination(NUM_SECS * 2, TimeUnit.SECONDS); + assert ok; + } + + // Print the results in JSON that can be accepted by the `perf.send` command. + // See https://docs.devprod.prod.corp.mongodb.com/evergreen/Project-Configuration/Project-Commands#perfsend for the expected `perf.send` input. + List results = new ArrayList<>(threadCounts.length); + for (int i = 0; i < threadCounts.length; i++) { + int threadCount = threadCounts[i]; + long totalMedianOpsPerSec = totalMedianOpsPerSecs.get(i); + String createdAt = createdAts.get(i); + String completedAt = completedAts.get(i); + + MongocryptBecnhmarkResult result = new MongocryptBecnhmarkResult( + "java_decrypt_1500", + threadCount, + totalMedianOpsPerSec, + createdAt, + completedAt, + "medianOpsPerSec", + "THROUGHPUT"); + + results.add(result); + } + System.out.println("Results: " + results); + return results; + } + } +} + diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongocryptBecnhmarkResult.java b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongocryptBecnhmarkResult.java new file mode 100644 index 00000000000..92ef999bee2 --- /dev/null +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/MongocryptBecnhmarkResult.java @@ -0,0 +1,84 @@ +package com.mongodb.benchmark.framework; +/* + * Copyright 2016-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +public class MongocryptBecnhmarkResult { + private final String testName; + private final int threadCount; + private final long medianOpsPerSec; + private final String createdAt; + private final String completedAt; + private final String metricName; + private final String metricType; + + public MongocryptBecnhmarkResult(final String testName, + final int threadCount, + final long medianOpsPerSec, + final String createdAt, + final String completedAt, + final String metricName, + final String metricType) { + this.testName = testName; + this.threadCount = threadCount; + this.medianOpsPerSec = medianOpsPerSec; + this.createdAt = createdAt; + this.completedAt = completedAt; + this.metricName = metricName; + this.metricType = metricType; + } + + public String getTestName() { + return testName; + } + + public int getThreadCount() { + return threadCount; + } + + public long getMedianOpsPerSec() { + return medianOpsPerSec; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getCompletedAt() { + return completedAt; + } + + public String getMetricName() { + return metricName; + } + + public String getMetricType() { + return metricType; + } + + @Override + public String toString() { + return "MongocryptBecnhmarkResult{" + + "testName='" + testName + '\'' + + ", threadCount=" + threadCount + + ", medianOpsPerSec=" + medianOpsPerSec + + ", createdAt=" + createdAt + + ", completedAt=" + completedAt + + ", metricName=" + metricName + + ", metricType=" + metricType + + '}'; + } +} diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/TextBasedBenchmarkResultWriter.java b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/TextBasedBenchmarkResultWriter.java index 185e518c3a0..9a29c9bd621 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/framework/TextBasedBenchmarkResultWriter.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/framework/TextBasedBenchmarkResultWriter.java @@ -82,6 +82,20 @@ public void write(final BenchmarkResult benchmarkResult) { printStream.println(); } + @Override + public void write(final MongocryptBecnhmarkResult result) { + printStream.println(result.getTestName()); + + printStream.println("CreatedAt: " + result.getCreatedAt()); + printStream.println("CompletedAt: " + result.getCompletedAt()); + printStream.println("ThreadCount: " + result.getThreadCount()); + printStream.println("MedianOpsPerSec: " + result.getMedianOpsPerSec()); + printStream.println("MetricType: " + result.getMetricType()); + + printStream.println(); + printStream.println(); + } + @Override public void close() { } diff --git a/driver-benchmarks/src/resources/keyDocument.json b/driver-benchmarks/src/resources/keyDocument.json new file mode 100644 index 00000000000..20d631db86c --- /dev/null +++ b/driver-benchmarks/src/resources/keyDocument.json @@ -0,0 +1,24 @@ +{ + "_id": { + "$binary": { + "base64": "YWFhYWFhYWFhYWFhYWFhYQ==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "ACR7Hm33dDOAAD7l2ubZhSpSUWK8BkALUY+qW3UgBAEcTV8sBwZnaAWnzDsmrX55dgmYHWfynDlJogC/e33u6pbhyXvFTs5ow9OLCuCWBJ39T/Ivm3kMaZJybkejY0V+uc4UEdHvVVz/SbitVnzs2WXdMGmo1/HmDRrxGYZjewFslquv8wtUHF5pyB+QDlQBd/al9M444/8bJZFbMSmtIg==", + "subType": "00" + } + }, + "creationDate": { + "$date": "2023-08-21T14:28:20.875Z" + }, + "updateDate": { + "$date": "2023-08-21T14:28:20.875Z" + }, + "status": 0, + "masterKey": { + "provider": "local" + } +} \ No newline at end of file diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 672b983b49b..12af6b93627 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -52,22 +52,6 @@ dependencies { testRuntimeOnly("ch.qos.logback:logback-classic:1.2.11") } -/* - * Git version information - */ -// Returns a String representing the output of `git describe` -val gitDescribe by lazy { - val describeStdOut = ByteArrayOutputStream() - exec { - commandLine = listOf("git", "describe", "--tags", "--always", "--dirty") - standardOutput = describeStdOut - } - describeStdOut.toString().trim() -} - -val isJavaTag by lazy { gitDescribe.startsWith("java") } -val gitVersion by lazy { gitDescribe.subSequence(gitDescribe.toCharArray().indexOfFirst { it.isDigit() }, gitDescribe.length).toString() } - val defaultDownloadRevision = "9a88ac5698e8e3ffcd6580b98c247f0126f26c40" // r.1.11.0 /* @@ -185,8 +169,6 @@ afterEvaluate { "-exportcontents" to "com.mongodb.crypt.capi.*;-noimport:=true", "Automatic-Module-Name" to "com.mongodb.crypt.capi", "Import-Package" to "org.slf4j.*;resolution:=optional,org.bson.*", - "Build-Version" to gitVersion, - "Bundle-Version" to gitVersion, "Bundle-Name" to "MongoCrypt", "Bundle-SymbolicName" to "com.mongodb.crypt.capi", "Private-Package" to "" diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java index 999190b4357..d6567bdaf7c 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java index 9ba223645cf..c1de63e8c8c 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,7 +80,7 @@ static byte[] toByteArray(final mongocrypt_binary_t binary) { return byteArray; } - static void writeByteArrayToBinary(final mongocrypt_binary_t binary, byte[] bytes) { + static void writeByteArrayToBinary(final mongocrypt_binary_t binary, final byte[] bytes) { if (binary.len() < bytes.length) { throw new IllegalArgumentException(format("mongocrypt binary of length %d is not large enough to hold %d bytes", binary.len(), bytes.length)); diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java index aa8c0bffaa6..4824197510d 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * The options for configuring the AWS KMS provider. */ -public class MongoAwsKmsProviderOptions { +public final class MongoAwsKmsProviderOptions { private final String accessKeyId; private final String secretAccessKey; @@ -58,7 +58,7 @@ public String getSecretAccessKey() { /** * The builder for the options */ - public static class Builder { + public static final class Builder { private String accessKeyId; private String secretAccessKey; diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java index 5022f2fb6a2..74816dbe42c 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public interface MongoCrypt extends Closeable { * @param command the document representing the command to encrypt * @return the context */ - MongoCryptContext createEncryptionContext(String database, final BsonDocument command); + MongoCryptContext createEncryptionContext(String database, BsonDocument command); /** * Create a context to use for decryption diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java index cd466c4ccee..2c3aa250b87 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java index 4df0fa06928..34aaafe7344 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java index ccde7c88baa..63074e20bc9 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ public MongoCryptException(final String msg) { * @param msg the message * @param cause the cause */ - public MongoCryptException(final String msg, Throwable cause) { + public MongoCryptException(final String msg, final Throwable cause) { super(msg, cause); this.code = -1; } diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java index 2132f3c682c..2949e2a11e4 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java index a35af7638e6..dc65bbdd9ae 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ /** * The options for configuring MongoCrypt. */ -public class MongoCryptOptions { +public final class MongoCryptOptions { private final MongoAwsKmsProviderOptions awsKmsProviderOptions; private final MongoLocalKmsProviderOptions localKmsProviderOptions; @@ -137,7 +137,7 @@ public List getSearchPaths() { /** * The builder for the options */ - public static class Builder { + public static final class Builder { private MongoAwsKmsProviderOptions awsKmsProviderOptions; private MongoLocalKmsProviderOptions localKmsProviderOptions; private BsonDocument kmsProviderOptions = null; @@ -270,7 +270,7 @@ public MongoCryptOptions build() { private MongoCryptOptions(final Builder builder) { isTrue("at least one KMS provider is configured", builder.awsKmsProviderOptions != null || builder.localKmsProviderOptions != null - || builder.kmsProviderOptions != null ); + || builder.kmsProviderOptions != null); this.awsKmsProviderOptions = builder.awsKmsProviderOptions; this.localKmsProviderOptions = builder.localKmsProviderOptions; this.kmsProviderOptions = builder.kmsProviderOptions; diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java index 8316f16b981..683dcdf90f1 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,11 @@ /** * The entry point to the MongoCrypt library. */ -public class MongoCrypts { +public final class MongoCrypts { + + private MongoCrypts() { + //NOP + } /** * Create a {@code MongoCrypt} instance. @@ -32,7 +36,7 @@ public class MongoCrypts { * @param options the options * @return the instance */ - public static MongoCrypt create(MongoCryptOptions options) { + public static MongoCrypt create(final MongoCryptOptions options) { return new MongoCryptImpl(options); } } diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java index a21d715411d..27f62514aeb 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java @@ -24,7 +24,7 @@ /** * The options for creation of a data key */ -public class MongoDataKeyOptions { +public final class MongoDataKeyOptions { private final List keyAltNames; private final BsonDocument masterKey; private final byte[] keyMaterial; @@ -32,7 +32,7 @@ public class MongoDataKeyOptions { /** * Options builder */ - public static class Builder { + public static final class Builder { private List keyAltNames; private BsonDocument masterKey; private byte[] keyMaterial; diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java index e96808e8451..2dad2182e7d 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java @@ -25,7 +25,7 @@ /** * Options for explicit encryption. */ -public class MongoExplicitEncryptOptions { +public final class MongoExplicitEncryptOptions { private final BsonBinary keyId; private final String keyAltName; private final String algorithm; @@ -36,7 +36,7 @@ public class MongoExplicitEncryptOptions { /** * The builder for the options */ - public static class Builder { + public static final class Builder { private BsonBinary keyId; private String keyAltName; private String algorithm; @@ -195,7 +195,7 @@ public BsonDocument getRangeOptions() { return rangeOptions; } - private MongoExplicitEncryptOptions(Builder builder) { + private MongoExplicitEncryptOptions(final Builder builder) { this.keyId = builder.keyId; this.keyAltName = builder.keyAltName; this.algorithm = builder.algorithm; @@ -215,13 +215,13 @@ private MongoExplicitEncryptOptions(Builder builder) { @Override public String toString() { - return "MongoExplicitEncryptOptions{" + - "keyId=" + keyId + - ", keyAltName='" + keyAltName + '\'' + - ", algorithm='" + algorithm + '\'' + - ", contentionFactor=" + contentionFactor + - ", queryType='" + queryType + '\'' + - ", rangeOptions=" + rangeOptions + - '}'; + return "MongoExplicitEncryptOptions{" + + "keyId=" + keyId + + ", keyAltName='" + keyAltName + '\'' + + ", algorithm='" + algorithm + '\'' + + ", contentionFactor=" + contentionFactor + + ", queryType='" + queryType + '\'' + + ", rangeOptions=" + rangeOptions + + '}'; } } diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java index 8ba0cc209e0..43a724348d6 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java index c149757cc6c..cef14bf855f 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java index 790fbfff00c..be8eef09573 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * The options for configuring a local KMS provider. */ -public class MongoLocalKmsProviderOptions { +public final class MongoLocalKmsProviderOptions { private final ByteBuffer localMasterKey; @@ -49,7 +49,7 @@ public ByteBuffer getLocalMasterKey() { /** * The builder for the options */ - public static class Builder { + public static final class Builder { private ByteBuffer localMasterKey; private Builder() { diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java index d875c8d795d..0bfc6defa63 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java @@ -27,7 +27,7 @@ * * @since 1.5 */ -public class MongoRewrapManyDataKeyOptions { +public final class MongoRewrapManyDataKeyOptions { private final String provider; private final BsonDocument masterKey; @@ -35,7 +35,7 @@ public class MongoRewrapManyDataKeyOptions { /** * Options builder */ - public static class Builder { + public static final class Builder { private String provider; private BsonDocument masterKey; diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java b/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java index 50f94177634..93211933e76 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,7 @@ * */ +/** + * The mongocrpyt API package + */ package com.mongodb.crypt.capi; From b8bad4e294d75208813133fde45194eff7b771b3 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 9 Sep 2024 16:38:32 -0700 Subject: [PATCH 08/22] Remove redundant benchmark. JAVA-5582 --- .../main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java index ab41fcdf12f..5ff153743f0 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java @@ -87,8 +87,6 @@ private static void runBenchmarks() runBenchmark(new MultiFileExportBenchmark()); runBenchmark(new GridFSMultiFileUploadBenchmark()); runBenchmark(new GridFSMultiFileDownloadBenchmark()); - runBenchmark(new GridFSMultiFileDownloadBenchmark()); - runMongoCryptBenchMarks(); } From ee7d96c496046d2663a1afdd713e10436787565f Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 9 Sep 2024 17:33:01 -0700 Subject: [PATCH 09/22] Fix checkstyle issues. JAVA-5582 --- .../src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java b/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java index 53e67f645fb..87fbab2e82f 100644 --- a/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java +++ b/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -360,7 +360,7 @@ private static ByteBuffer getHttpResourceAsByteBuffer(final String fileName) { return ByteBuffer.wrap(getFileAsString(fileName, "\r\n").getBytes(StandardCharsets.UTF_8)); } - private static String getFileAsString(final String fileName, String lineSeparator) { + private static String getFileAsString(final String fileName, final String lineSeparator) { try { URL resource = MongoCryptTest.class.getResource("/" + fileName); if (resource == null) { From 1e38914f303b9117562b753e6b5dead5f5a04180 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 9 Sep 2024 21:10:25 -0700 Subject: [PATCH 10/22] Ensure incremental builds for unzip and downloadJava tasks. JAVA-5582 --- mongocrypt/build.gradle.kts | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 12af6b93627..39e886552ef 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -16,7 +16,6 @@ */ import de.undercouch.gradle.tasks.download.Download -import java.io.ByteArrayOutputStream buildscript { repositories { @@ -67,6 +66,13 @@ val jnaResources: String = System.getProperty("jna.library.path", jnaLibsPath) val downloadRevision: String = System.getProperties().computeIfAbsent("gitRevision") { k -> defaultDownloadRevision }.toString() val binariesArchiveName = "libmongocrypt-java.tar.gz" +/** + * The name of the archive includes defaultDownloadRevision to ensure that: + * - the archive is downloaded if the revision changes. + * - the archive is not downloaded if the revision is the same and archive had already been saved in build output. + */ +val localBinariesArchiveName = "libmongocrypt-java-$defaultDownloadRevision.tar.gz" + val downloadUrl: String = "https://mciuploads.s3.amazonaws.com/libmongocrypt/java/$downloadRevision/$binariesArchiveName" val jnaMapping: Map = mapOf( @@ -90,9 +96,9 @@ sourceSets { tasks.register("downloadJava") { src(downloadUrl) - dest("${jnaDownloadsDir}/$binariesArchiveName") + dest("${jnaDownloadsDir}/$localBinariesArchiveName") overwrite(true) - /* To make sure we don't download archive with binaries if it already exists or if it hasn't changed in S3 bucket.*/ + /* To make sure we don't download archive with binaries if it hasn't been changed in S3 bucket since last download.*/ onlyIfModified(true) } @@ -104,8 +110,23 @@ tasks.processResources { } tasks.register("unzipJava") { - outputs.upToDateWhen { false } - from(tarTree(resources.gzip("${jnaDownloadsDir}/$binariesArchiveName"))) + /* + Clean up the directory first if the task is not UP-TO-DATE. + This can happen if the download revision has been changed and the archive is downloaded again. + */ + doFirst { + println("Cleaning up $jnaResourcesDir") + delete(fileTree(jnaResourcesDir).matching { + include(jnaMapping.keys.flatMap { + listOf( + "${it}/nocrypto/**/libmongocrypt.so", + "${it}/lib/**/libmongocrypt.dylib", + "${it}/bin/**/mongocrypt.dll" + ) + }) + }) + } + from(tarTree(resources.gzip("${jnaDownloadsDir}/$localBinariesArchiveName"))) include(jnaMapping.keys.flatMap { listOf("${it}/nocrypto/**/libmongocrypt.so", "${it}/lib/**/libmongocrypt.dylib", "${it}/bin/**/mongocrypt.dll" ) }) From a99def1932e90a0c827693b69019744bd27dee38 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 11 Sep 2024 11:23:13 -0700 Subject: [PATCH 11/22] Remove redundant configs. JAVA-5582 --- mongocrypt/build.gradle.kts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 39e886552ef..3cac2c661fb 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -51,7 +51,6 @@ dependencies { testRuntimeOnly("ch.qos.logback:logback-classic:1.2.11") } -val defaultDownloadRevision = "9a88ac5698e8e3ffcd6580b98c247f0126f26c40" // r.1.11.0 /* * Jna copy or download resources @@ -62,16 +61,16 @@ val jnaLibPlatform: String = if (com.sun.jna.Platform.RESOURCE_PREFIX.startsWith val jnaLibsPath: String = System.getProperty("jnaLibsPath", "${jnaResourcesDir}${jnaLibPlatform}") val jnaResources: String = System.getProperty("jna.library.path", jnaLibsPath) -// Download jnaLibs that match the git to jnaResourcesBuildDir -val downloadRevision: String = System.getProperties().computeIfAbsent("gitRevision") { k -> defaultDownloadRevision }.toString() +// Download jnaLibs that match the git tag or revision to jnaResourcesBuildDir +val downloadRevision = "9a88ac5698e8e3ffcd6580b98c247f0126f26c40" // r.1.11.0 val binariesArchiveName = "libmongocrypt-java.tar.gz" /** - * The name of the archive includes defaultDownloadRevision to ensure that: + * The name of the archive includes downloadRevision to ensure that: * - the archive is downloaded if the revision changes. * - the archive is not downloaded if the revision is the same and archive had already been saved in build output. */ -val localBinariesArchiveName = "libmongocrypt-java-$defaultDownloadRevision.tar.gz" +val localBinariesArchiveName = "libmongocrypt-java-$downloadRevision.tar.gz" val downloadUrl: String = "https://mciuploads.s3.amazonaws.com/libmongocrypt/java/$downloadRevision/$binariesArchiveName" @@ -105,7 +104,6 @@ tasks.register("downloadJava") { // The `processResources` task (defined by the `java-library` plug-in) consumes files in the main source set. // Add a dependency on `unzipJava`. `unzipJava` adds libmongocrypt libraries to the main source set. tasks.processResources { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE mustRunAfter(tasks.named("unzipJava")) } From d390438e6db86a656de799ef8dd107d8bdc29829 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 11 Sep 2024 11:28:07 -0700 Subject: [PATCH 12/22] Format code. JAVA-5582 --- mongocrypt/build.gradle.kts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 3cac2c661fb..7f3ac174bbf 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -101,12 +101,6 @@ tasks.register("downloadJava") { onlyIfModified(true) } -// The `processResources` task (defined by the `java-library` plug-in) consumes files in the main source set. -// Add a dependency on `unzipJava`. `unzipJava` adds libmongocrypt libraries to the main source set. -tasks.processResources { - mustRunAfter(tasks.named("unzipJava")) -} - tasks.register("unzipJava") { /* Clean up the directory first if the task is not UP-TO-DATE. @@ -139,6 +133,12 @@ tasks.register("unzipJava") { } } +// The `processResources` task (defined by the `java-library` plug-in) consumes files in the main source set. +// Add a dependency on `unzipJava`. `unzipJava` adds libmongocrypt libraries to the main source set. +tasks.processResources { + mustRunAfter(tasks.named("unzipJava")) +} + tasks.register("downloadJnaLibs") { dependsOn("downloadJava", "unzipJava") } From 070fba8c0151b78be50b48aabc275b671b9646fd Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 11 Sep 2024 16:48:35 -0700 Subject: [PATCH 13/22] Move mongocrypt benchmark. JAVA-5582 --- .../main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java index 5ff153743f0..2260e0ed80a 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java @@ -58,6 +58,7 @@ public static void main(String[] args) throws Exception { private static void runBenchmarks() throws Exception { + runMongoCryptBenchMarks(); runBenchmark(new BsonEncodingBenchmark<>("Flat", "extended_bson/flat_bson.json", DOCUMENT_CODEC)); runBenchmark(new BsonEncodingBenchmark<>("Deep", "extended_bson/deep_bson.json", DOCUMENT_CODEC)); runBenchmark(new BsonEncodingBenchmark<>("Full", "extended_bson/full_bson.json", DOCUMENT_CODEC)); @@ -87,7 +88,6 @@ private static void runBenchmarks() runBenchmark(new MultiFileExportBenchmark()); runBenchmark(new GridFSMultiFileUploadBenchmark()); runBenchmark(new GridFSMultiFileDownloadBenchmark()); - runMongoCryptBenchMarks(); } private static void runMongoCryptBenchMarks() throws InterruptedException { From 5495133adbb9eeeacc7ae991c46c241bc29ecc97 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 12 Sep 2024 14:53:35 -0700 Subject: [PATCH 14/22] Delete whole resources dir instead of individual files. JAVA-5582 --- mongocrypt/build.gradle.kts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 7f3ac174bbf..597f0b87ebe 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -108,15 +108,7 @@ tasks.register("unzipJava") { */ doFirst { println("Cleaning up $jnaResourcesDir") - delete(fileTree(jnaResourcesDir).matching { - include(jnaMapping.keys.flatMap { - listOf( - "${it}/nocrypto/**/libmongocrypt.so", - "${it}/lib/**/libmongocrypt.dylib", - "${it}/bin/**/mongocrypt.dll" - ) - }) - }) + delete(jnaResourcesDir) } from(tarTree(resources.gzip("${jnaDownloadsDir}/$localBinariesArchiveName"))) include(jnaMapping.keys.flatMap { @@ -126,7 +118,7 @@ tasks.register("unzipJava") { path = "${jnaMapping[path.substringBefore("/")]}/${name}" } into(jnaResourcesDir) - mustRunAfter("downloadJava") + dependsOn("downloadJava") doLast { println("jna.library.path contents: \n ${fileTree(jnaResourcesDir).files.joinToString(",\n ")}") From b0438fdd9094159019427ebe4d34a63152fffd23 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 16 Sep 2024 17:10:18 -0700 Subject: [PATCH 15/22] Add runtimeElemens to GraalVM script, as this configuration is meant to be used by consumers, to retrieve all the elements necessary to run against this library immitating trasative dependency resolution. JAVA-5582 --- graalvm-native-image-app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/graalvm-native-image-app/build.gradle b/graalvm-native-image-app/build.gradle index f17244b3561..1bc4c0da293 100644 --- a/graalvm-native-image-app/build.gradle +++ b/graalvm-native-image-app/build.gradle @@ -83,6 +83,7 @@ dependencies { implementation project(path:':driver-reactive-streams', configuration:'archives') implementation project(path:':driver-legacy', configuration:'archives') implementation project(path: ':mongocrypt', configuration: 'archives') + implementation project(path: ':mongocrypt', configuration: 'runtimeElements') // note that as a result of these `sourceSets` dependencies, `driver-sync/src/test/resources/logback-test.xml` is used implementation project(':driver-core').sourceSets.test.output implementation project(':driver-sync').sourceSets.test.output From 10f3cf52c87d09f795a242990477f3b4518d15f3 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 17 Sep 2024 12:47:30 +0100 Subject: [PATCH 16/22] PR nits --- mongocrypt/build.gradle.kts | 9 ++++----- .../src/main/com/mongodb/crypt/capi/package-info.java | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index 597f0b87ebe..c94d8bd1943 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2019-present MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,10 +48,9 @@ dependencies { // Tests testImplementation("org.junit.jupiter:junit-jupiter") - testRuntimeOnly("ch.qos.logback:logback-classic:1.2.11") + testRuntimeOnly("ch.qos.logback:logback-classic:1.2.13") } - /* * Jna copy or download resources */ @@ -62,7 +61,7 @@ val jnaLibsPath: String = System.getProperty("jnaLibsPath", "${jnaResourcesDir}$ val jnaResources: String = System.getProperty("jna.library.path", jnaLibsPath) // Download jnaLibs that match the git tag or revision to jnaResourcesBuildDir -val downloadRevision = "9a88ac5698e8e3ffcd6580b98c247f0126f26c40" // r.1.11.0 +val downloadRevision = "9a88ac5698e8e3ffcd6580b98c247f0126f26c40" // r1.11.0 val binariesArchiveName = "libmongocrypt-java.tar.gz" /** @@ -186,4 +185,4 @@ afterEvaluate { ) } } -} \ No newline at end of file +} diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java b/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java index 93211933e76..c1c9060de33 100644 --- a/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java +++ b/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java @@ -16,6 +16,6 @@ */ /** - * The mongocrpyt API package + * The mongocrypt API package */ package com.mongodb.crypt.capi; From 7b5738b5b9a6c50d411f35f600c642509954acac Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 17 Sep 2024 13:04:36 +0100 Subject: [PATCH 17/22] Spotbugs naming convention fix --- config/spotbugs/exclude.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index fedf0c72566..9ce5b944cb4 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -260,4 +260,10 @@ + + + + + + From 204330b7a48273fa6015167c98797ff31f8e31ce Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 18 Sep 2024 17:25:47 -0700 Subject: [PATCH 18/22] Remove logback from dependencies. JAVA-5582 --- mongocrypt/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/mongocrypt/build.gradle.kts b/mongocrypt/build.gradle.kts index c94d8bd1943..7daf829db91 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongocrypt/build.gradle.kts @@ -48,7 +48,6 @@ dependencies { // Tests testImplementation("org.junit.jupiter:junit-jupiter") - testRuntimeOnly("ch.qos.logback:logback-classic:1.2.13") } /* From a8cee8d3ef4e65151fb54383129052430105804a Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 19 Sep 2024 16:37:53 +0100 Subject: [PATCH 19/22] Fix driver-core pom optional. --- driver-core/build.gradle | 8 +++++++- gradle/publish.gradle | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/driver-core/build.gradle b/driver-core/build.gradle index d8a70f10bb7..1ad2bbbaa2d 100644 --- a/driver-core/build.gradle +++ b/driver-core/build.gradle @@ -33,13 +33,19 @@ configurations { } } +java { + registerFeature("mongoCryptSupport") { + usingSourceSet(sourceSets["main"]) + } +} + def classifiers = ["linux-x86_64", "linux-aarch_64", "osx-x86_64", "osx-aarch_64", "windows-x86_64"] dependencies { api project(path: ':bson', configuration: 'default') implementation project(path: ':bson-record-codec', configuration: 'default') implementation project(path: ':bson-kotlin', configuration: 'default'), optional implementation project(path: ':bson-kotlinx', configuration: 'default'), optional - implementation project(path: ':mongocrypt', configuration: 'default'), optional + mongoCryptSupportImplementation project(path: ':mongocrypt', configuration: 'default') implementation "com.github.jnr:jnr-unixsocket:$jnrUnixsocketVersion", optional api platform("io.netty:netty-bom:$nettyVersion") diff --git a/gradle/publish.gradle b/gradle/publish.gradle index 25edda53f49..dc5b997aeca 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -102,6 +102,9 @@ configure(javaProjects) { project -> suppressPomMetadataWarningsFor("dateTimeSupportApiElements") suppressPomMetadataWarningsFor("dateTimeRuntimeElements") + + suppressPomMetadataWarningsFor("mongoCryptSupportApiElements") + suppressPomMetadataWarningsFor("mongoCryptSupportRuntimeElements") } } From 28c5b8367f1c41eb2400998d9889d8e842c21375 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 19 Sep 2024 16:59:37 +0100 Subject: [PATCH 20/22] Add mongocrypt support to driver-sync and reactive streams driver --- driver-reactive-streams/build.gradle | 7 +++++++ driver-sync/build.gradle | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/driver-reactive-streams/build.gradle b/driver-reactive-streams/build.gradle index 5a08997e6e8..1bb47b5917a 100644 --- a/driver-reactive-streams/build.gradle +++ b/driver-reactive-streams/build.gradle @@ -17,9 +17,16 @@ description = "A Reactive Streams implementation of the MongoDB Java driver" archivesBaseName = 'mongodb-driver-reactivestreams' +java { + registerFeature("mongoCryptSupport") { + usingSourceSet(sourceSets["main"]) + } +} + dependencies { api project(path: ':bson', configuration: 'default') api project(path: ':driver-core', configuration: 'default') + mongoCryptSupportImplementation project(path: ':mongocrypt', configuration: 'default') api 'org.reactivestreams:reactive-streams:1.0.4' implementation platform("io.projectreactor:reactor-bom:$projectReactorVersion") diff --git a/driver-sync/build.gradle b/driver-sync/build.gradle index eb10ef62ebf..e504110cd9c 100644 --- a/driver-sync/build.gradle +++ b/driver-sync/build.gradle @@ -20,9 +20,16 @@ ext { pomName = 'MongoDB Driver' } +java { + registerFeature("mongoCryptSupport") { + usingSourceSet(sourceSets["main"]) + } +} + dependencies { api project(path: ':bson', configuration: 'default') api project(path: ':driver-core', configuration: 'default') + mongoCryptSupportImplementation project(path: ':mongocrypt', configuration: 'default') testImplementation project(':bson').sourceSets.test.output testImplementation project(':driver-core').sourceSets.test.output From 6e3e7068f1b0f2648a2fa0610e2c13a3f1dd2e3f Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 19 Sep 2024 15:04:20 -0700 Subject: [PATCH 21/22] Changed the project name in the configuration to align with the artifactId, ensuring that the 'optional' plugin functions correctly. JAVA-5582 --- driver-benchmarks/build.gradle | 2 +- driver-core/build.gradle | 2 +- driver-reactive-streams/build.gradle | 7 ------- driver-sync/build.gradle | 7 ------- graalvm-native-image-app/build.gradle | 4 ++-- settings.gradle | 2 ++ 6 files changed, 6 insertions(+), 18 deletions(-) diff --git a/driver-benchmarks/build.gradle b/driver-benchmarks/build.gradle index adac283f28d..91d979cff68 100644 --- a/driver-benchmarks/build.gradle +++ b/driver-benchmarks/build.gradle @@ -31,7 +31,7 @@ sourceSets { dependencies { api project(':driver-sync') - api project(':mongocrypt') + api project(':mongodb-crypt') implementation "ch.qos.logback:logback-classic:$logbackVersion" } diff --git a/driver-core/build.gradle b/driver-core/build.gradle index 1ad2bbbaa2d..04290df8878 100644 --- a/driver-core/build.gradle +++ b/driver-core/build.gradle @@ -45,7 +45,7 @@ dependencies { implementation project(path: ':bson-record-codec', configuration: 'default') implementation project(path: ':bson-kotlin', configuration: 'default'), optional implementation project(path: ':bson-kotlinx', configuration: 'default'), optional - mongoCryptSupportImplementation project(path: ':mongocrypt', configuration: 'default') + implementation project(path: ':mongodb-crypt', configuration: 'default'), optional implementation "com.github.jnr:jnr-unixsocket:$jnrUnixsocketVersion", optional api platform("io.netty:netty-bom:$nettyVersion") diff --git a/driver-reactive-streams/build.gradle b/driver-reactive-streams/build.gradle index 1bb47b5917a..5a08997e6e8 100644 --- a/driver-reactive-streams/build.gradle +++ b/driver-reactive-streams/build.gradle @@ -17,16 +17,9 @@ description = "A Reactive Streams implementation of the MongoDB Java driver" archivesBaseName = 'mongodb-driver-reactivestreams' -java { - registerFeature("mongoCryptSupport") { - usingSourceSet(sourceSets["main"]) - } -} - dependencies { api project(path: ':bson', configuration: 'default') api project(path: ':driver-core', configuration: 'default') - mongoCryptSupportImplementation project(path: ':mongocrypt', configuration: 'default') api 'org.reactivestreams:reactive-streams:1.0.4' implementation platform("io.projectreactor:reactor-bom:$projectReactorVersion") diff --git a/driver-sync/build.gradle b/driver-sync/build.gradle index e504110cd9c..eb10ef62ebf 100644 --- a/driver-sync/build.gradle +++ b/driver-sync/build.gradle @@ -20,16 +20,9 @@ ext { pomName = 'MongoDB Driver' } -java { - registerFeature("mongoCryptSupport") { - usingSourceSet(sourceSets["main"]) - } -} - dependencies { api project(path: ':bson', configuration: 'default') api project(path: ':driver-core', configuration: 'default') - mongoCryptSupportImplementation project(path: ':mongocrypt', configuration: 'default') testImplementation project(':bson').sourceSets.test.output testImplementation project(':driver-core').sourceSets.test.output diff --git a/graalvm-native-image-app/build.gradle b/graalvm-native-image-app/build.gradle index 1bc4c0da293..d6bc5a7b6cb 100644 --- a/graalvm-native-image-app/build.gradle +++ b/graalvm-native-image-app/build.gradle @@ -82,8 +82,8 @@ dependencies { implementation project(path:':driver-sync', configuration:'archives') implementation project(path:':driver-reactive-streams', configuration:'archives') implementation project(path:':driver-legacy', configuration:'archives') - implementation project(path: ':mongocrypt', configuration: 'archives') - implementation project(path: ':mongocrypt', configuration: 'runtimeElements') + implementation project(path: ':mongodb-crypt', configuration: 'archives') + implementation project(path: ':mongodb-crypt', configuration: 'runtimeElements') // note that as a result of these `sourceSets` dependencies, `driver-sync/src/test/resources/logback-test.xml` is used implementation project(':driver-core').sourceSets.test.output implementation project(':driver-sync').sourceSets.test.output diff --git a/settings.gradle b/settings.gradle index 92cf08421f7..f9524ff6437 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,6 +30,8 @@ include ':driver-kotlin-coroutine' include ':bson-scala' include ':driver-scala' include ':mongocrypt' +// Project name is different from the folder name because "optional plugin" needs project name to match the artifactId. +project(":mongocrypt").name = "mongodb-crypt" include 'util:spock' include 'util:taglets' include ':graalvm-native-image-app' From a8699de1a4da012f097eb1b82ea92e4c884cf018 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Fri, 20 Sep 2024 10:03:56 +0100 Subject: [PATCH 22/22] Move mongocrypt to mongodb-crypt to work around optional project name issue --- driver-core/build.gradle | 6 ------ {mongocrypt => mongodb-crypt}/build.gradle.kts | 2 +- .../src/main/com/mongodb/crypt/capi/BinaryHolder.java | 0 .../src/main/com/mongodb/crypt/capi/CAPI.java | 0 .../src/main/com/mongodb/crypt/capi/CAPIHelper.java | 0 .../src/main/com/mongodb/crypt/capi/CipherCallback.java | 0 .../src/main/com/mongodb/crypt/capi/DisposableMemory.java | 0 .../src/main/com/mongodb/crypt/capi/JULLogger.java | 0 .../src/main/com/mongodb/crypt/capi/Logger.java | 0 .../src/main/com/mongodb/crypt/capi/Loggers.java | 0 .../src/main/com/mongodb/crypt/capi/MacCallback.java | 0 .../main/com/mongodb/crypt/capi/MessageDigestCallback.java | 0 .../com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java | 0 .../src/main/com/mongodb/crypt/capi/MongoCrypt.java | 0 .../src/main/com/mongodb/crypt/capi/MongoCryptContext.java | 0 .../main/com/mongodb/crypt/capi/MongoCryptContextImpl.java | 0 .../main/com/mongodb/crypt/capi/MongoCryptException.java | 0 .../src/main/com/mongodb/crypt/capi/MongoCryptImpl.java | 0 .../src/main/com/mongodb/crypt/capi/MongoCryptOptions.java | 0 .../src/main/com/mongodb/crypt/capi/MongoCrypts.java | 0 .../main/com/mongodb/crypt/capi/MongoDataKeyOptions.java | 0 .../com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java | 0 .../src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java | 0 .../main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java | 0 .../mongodb/crypt/capi/MongoLocalKmsProviderOptions.java | 0 .../mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java | 0 .../src/main/com/mongodb/crypt/capi/SLF4JLogger.java | 0 .../main/com/mongodb/crypt/capi/SecureRandomCallback.java | 0 .../com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java | 0 .../src/main/com/mongodb/crypt/capi/package-info.java | 0 .../main/resources/META-INF/native-image/jni-config.json | 0 .../resources/META-INF/native-image/reflect-config.json | 0 .../test/java/com/mongodb/crypt/capi/MongoCryptTest.java | 0 .../src/test/resources/collection-info.json | 0 .../src/test/resources/command-reply.json | 0 .../src/test/resources/command.json | 0 .../src/test/resources/encrypted-command-reply.json | 0 .../src/test/resources/encrypted-command.json | 0 .../src/test/resources/encrypted-value.json | 0 .../int32/encrypted-payload.json | 0 .../fle2-find-range-explicit-v2/int32/key-filter.json | 0 .../fle2-find-range-explicit-v2/int32/rangeopts.json | 0 .../fle2-find-range-explicit-v2/int32/value-to-encrypt.json | 0 .../src/test/resources/json-schema.json | 0 .../src/test/resources/key-document.json | 0 .../src/test/resources/key-filter-keyAltName.json | 0 .../src/test/resources/key-filter.json | 0 .../ABCDEFAB123498761234123456789012-local-document.json | 0 .../src/test/resources/kms-reply.txt | 0 .../src/test/resources/list-collections-filter.json | 0 .../src/test/resources/mongocryptd-command.json | 0 .../src/test/resources/mongocryptd-reply.json | 0 settings.gradle | 4 +--- 53 files changed, 2 insertions(+), 10 deletions(-) rename {mongocrypt => mongodb-crypt}/build.gradle.kts (99%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/BinaryHolder.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/CAPI.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/CAPIHelper.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/CipherCallback.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/DisposableMemory.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/JULLogger.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/Logger.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/Loggers.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MacCallback.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoCrypt.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoCryptContext.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoCryptException.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoCrypts.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/SLF4JLogger.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/com/mongodb/crypt/capi/package-info.java (100%) rename {mongocrypt => mongodb-crypt}/src/main/resources/META-INF/native-image/jni-config.json (100%) rename {mongocrypt => mongodb-crypt}/src/main/resources/META-INF/native-image/reflect-config.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/collection-info.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/command-reply.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/command.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/encrypted-command-reply.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/encrypted-command.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/encrypted-value.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/json-schema.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/key-document.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/key-filter-keyAltName.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/key-filter.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/kms-reply.txt (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/list-collections-filter.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/mongocryptd-command.json (100%) rename {mongocrypt => mongodb-crypt}/src/test/resources/mongocryptd-reply.json (100%) diff --git a/driver-core/build.gradle b/driver-core/build.gradle index 04290df8878..78ab607cc23 100644 --- a/driver-core/build.gradle +++ b/driver-core/build.gradle @@ -33,12 +33,6 @@ configurations { } } -java { - registerFeature("mongoCryptSupport") { - usingSourceSet(sourceSets["main"]) - } -} - def classifiers = ["linux-x86_64", "linux-aarch_64", "osx-x86_64", "osx-aarch_64", "windows-x86_64"] dependencies { api project(path: ':bson', configuration: 'default') diff --git a/mongocrypt/build.gradle.kts b/mongodb-crypt/build.gradle.kts similarity index 99% rename from mongocrypt/build.gradle.kts rename to mongodb-crypt/build.gradle.kts index 7daf829db91..bf2fef544ff 100644 --- a/mongocrypt/build.gradle.kts +++ b/mongodb-crypt/build.gradle.kts @@ -33,7 +33,7 @@ plugins { } group = "org.mongodb" -base.archivesBaseName = "mongodb-crypt" +base.archivesName.set("mongodb-crypt") description = "MongoDB client-side crypto support" ext.set("pomName", "MongoCrypt") diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/BinaryHolder.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/CAPI.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/CAPI.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/CAPI.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/CAPIHelper.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/CipherCallback.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/CipherCallback.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/CipherCallback.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/CipherCallback.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/DisposableMemory.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/JULLogger.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/JULLogger.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/JULLogger.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/JULLogger.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/Logger.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/Logger.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/Logger.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/Logger.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/Loggers.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/Loggers.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/Loggers.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/Loggers.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MacCallback.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MacCallback.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MacCallback.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MacCallback.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MessageDigestCallback.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoAwsKmsProviderOptions.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCrypt.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptContext.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptContextImpl.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptException.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptImpl.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCryptOptions.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoCrypts.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoDataKeyOptions.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoExplicitEncryptOptions.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptor.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoKeyDecryptorImpl.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoLocalKmsProviderOptions.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/MongoRewrapManyDataKeyOptions.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/SLF4JLogger.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/SecureRandomCallback.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/SigningRSAESPKCSCallback.java diff --git a/mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java b/mongodb-crypt/src/main/com/mongodb/crypt/capi/package-info.java similarity index 100% rename from mongocrypt/src/main/com/mongodb/crypt/capi/package-info.java rename to mongodb-crypt/src/main/com/mongodb/crypt/capi/package-info.java diff --git a/mongocrypt/src/main/resources/META-INF/native-image/jni-config.json b/mongodb-crypt/src/main/resources/META-INF/native-image/jni-config.json similarity index 100% rename from mongocrypt/src/main/resources/META-INF/native-image/jni-config.json rename to mongodb-crypt/src/main/resources/META-INF/native-image/jni-config.json diff --git a/mongocrypt/src/main/resources/META-INF/native-image/reflect-config.json b/mongodb-crypt/src/main/resources/META-INF/native-image/reflect-config.json similarity index 100% rename from mongocrypt/src/main/resources/META-INF/native-image/reflect-config.json rename to mongodb-crypt/src/main/resources/META-INF/native-image/reflect-config.json diff --git a/mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java b/mongodb-crypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java similarity index 100% rename from mongocrypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java rename to mongodb-crypt/src/test/java/com/mongodb/crypt/capi/MongoCryptTest.java diff --git a/mongocrypt/src/test/resources/collection-info.json b/mongodb-crypt/src/test/resources/collection-info.json similarity index 100% rename from mongocrypt/src/test/resources/collection-info.json rename to mongodb-crypt/src/test/resources/collection-info.json diff --git a/mongocrypt/src/test/resources/command-reply.json b/mongodb-crypt/src/test/resources/command-reply.json similarity index 100% rename from mongocrypt/src/test/resources/command-reply.json rename to mongodb-crypt/src/test/resources/command-reply.json diff --git a/mongocrypt/src/test/resources/command.json b/mongodb-crypt/src/test/resources/command.json similarity index 100% rename from mongocrypt/src/test/resources/command.json rename to mongodb-crypt/src/test/resources/command.json diff --git a/mongocrypt/src/test/resources/encrypted-command-reply.json b/mongodb-crypt/src/test/resources/encrypted-command-reply.json similarity index 100% rename from mongocrypt/src/test/resources/encrypted-command-reply.json rename to mongodb-crypt/src/test/resources/encrypted-command-reply.json diff --git a/mongocrypt/src/test/resources/encrypted-command.json b/mongodb-crypt/src/test/resources/encrypted-command.json similarity index 100% rename from mongocrypt/src/test/resources/encrypted-command.json rename to mongodb-crypt/src/test/resources/encrypted-command.json diff --git a/mongocrypt/src/test/resources/encrypted-value.json b/mongodb-crypt/src/test/resources/encrypted-value.json similarity index 100% rename from mongocrypt/src/test/resources/encrypted-value.json rename to mongodb-crypt/src/test/resources/encrypted-value.json diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json b/mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json similarity index 100% rename from mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json rename to mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/encrypted-payload.json diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json b/mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json similarity index 100% rename from mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json rename to mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/key-filter.json diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json b/mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json similarity index 100% rename from mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json rename to mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/rangeopts.json diff --git a/mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json b/mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json similarity index 100% rename from mongocrypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json rename to mongodb-crypt/src/test/resources/fle2-find-range-explicit-v2/int32/value-to-encrypt.json diff --git a/mongocrypt/src/test/resources/json-schema.json b/mongodb-crypt/src/test/resources/json-schema.json similarity index 100% rename from mongocrypt/src/test/resources/json-schema.json rename to mongodb-crypt/src/test/resources/json-schema.json diff --git a/mongocrypt/src/test/resources/key-document.json b/mongodb-crypt/src/test/resources/key-document.json similarity index 100% rename from mongocrypt/src/test/resources/key-document.json rename to mongodb-crypt/src/test/resources/key-document.json diff --git a/mongocrypt/src/test/resources/key-filter-keyAltName.json b/mongodb-crypt/src/test/resources/key-filter-keyAltName.json similarity index 100% rename from mongocrypt/src/test/resources/key-filter-keyAltName.json rename to mongodb-crypt/src/test/resources/key-filter-keyAltName.json diff --git a/mongocrypt/src/test/resources/key-filter.json b/mongodb-crypt/src/test/resources/key-filter.json similarity index 100% rename from mongocrypt/src/test/resources/key-filter.json rename to mongodb-crypt/src/test/resources/key-filter.json diff --git a/mongocrypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json b/mongodb-crypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json similarity index 100% rename from mongocrypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json rename to mongodb-crypt/src/test/resources/keys/ABCDEFAB123498761234123456789012-local-document.json diff --git a/mongocrypt/src/test/resources/kms-reply.txt b/mongodb-crypt/src/test/resources/kms-reply.txt similarity index 100% rename from mongocrypt/src/test/resources/kms-reply.txt rename to mongodb-crypt/src/test/resources/kms-reply.txt diff --git a/mongocrypt/src/test/resources/list-collections-filter.json b/mongodb-crypt/src/test/resources/list-collections-filter.json similarity index 100% rename from mongocrypt/src/test/resources/list-collections-filter.json rename to mongodb-crypt/src/test/resources/list-collections-filter.json diff --git a/mongocrypt/src/test/resources/mongocryptd-command.json b/mongodb-crypt/src/test/resources/mongocryptd-command.json similarity index 100% rename from mongocrypt/src/test/resources/mongocryptd-command.json rename to mongodb-crypt/src/test/resources/mongocryptd-command.json diff --git a/mongocrypt/src/test/resources/mongocryptd-reply.json b/mongodb-crypt/src/test/resources/mongocryptd-reply.json similarity index 100% rename from mongocrypt/src/test/resources/mongocryptd-reply.json rename to mongodb-crypt/src/test/resources/mongocryptd-reply.json diff --git a/settings.gradle b/settings.gradle index f9524ff6437..b1c5e185d37 100644 --- a/settings.gradle +++ b/settings.gradle @@ -29,9 +29,7 @@ include ':driver-kotlin-sync' include ':driver-kotlin-coroutine' include ':bson-scala' include ':driver-scala' -include ':mongocrypt' -// Project name is different from the folder name because "optional plugin" needs project name to match the artifactId. -project(":mongocrypt").name = "mongodb-crypt" +include ':mongodb-crypt' include 'util:spock' include 'util:taglets' include ':graalvm-native-image-app'