com.mongodb.crypt.capi.MongoCryptImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongodb-crypt Show documentation
Show all versions of mongodb-crypt Show documentation
MongoDB client-side crypto support
The newest version!
/*
* 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());
}
}
}
}