diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index 7de253baac6..b0772260acd 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -764,6 +764,16 @@ functions: set +o xtrace MONGODB_URI="${MONGODB_URI}" KMS_TLS_ERROR_TYPE=${KMS_TLS_ERROR_TYPE} .evergreen/run-kms-tls-tests.sh + "run-kms-retry-test": + - command: shell.exec + type: "test" + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + set +o xtrace + MONGODB_URI="${MONGODB_URI}" .evergreen/run-kms-retry-tests.sh + "run-csfle-aws-from-environment-test": - command: shell.exec type: "test" @@ -1632,6 +1642,17 @@ tasks: AUTH: "noauth" SSL: "nossl" + - name: "test-kms-retry-task" + tags: [ "kms-retry" ] + commands: + - func: "start-mongo-orchestration" + vars: + TOPOLOGY: "server" + AUTH: "noauth" + SSL: "nossl" + - func: "start-csfle-servers" + - func: "run-kms-retry-test" + - name: "test-csfle-aws-from-environment-task" tags: [ "csfle-aws-from-environment" ] commands: @@ -2528,6 +2549,12 @@ buildvariants: tasks: - name: ".kms-tls" + - matrix_name: "kms-retry-test" + matrix_spec: { os: "linux", version: [ "5.0" ], topology: [ "standalone" ] } + display_name: "CSFLE KMS Retry" + tasks: + - name: ".kms-retry" + - matrix_name: "csfle-aws-from-environment-test" matrix_spec: { os: "linux", version: [ "5.0" ], topology: [ "standalone" ] } display_name: "CSFLE AWS From Environment" diff --git a/.evergreen/run-kms-retry-tests.sh b/.evergreen/run-kms-retry-tests.sh new file mode 100755 index 00000000000..c9a96ddafa2 --- /dev/null +++ b/.evergreen/run-kms-retry-tests.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Don't trace since the URI contains a password that shouldn't show up in the logs +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# MONGODB_URI Set the suggested connection MONGODB_URI (including credentials and topology info) + +############################################ +# Main Program # +############################################ +RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE:-$0}")" +. "${RELATIVE_DIR_PATH}/setup-env.bash" +echo "Running KMS Retry tests" + +cp ${JAVA_HOME}/lib/security/cacerts mongo-truststore +${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -keystore mongo-truststore -storepass changeit -storetype JKS -noprompt + +export GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.trustStoreType=jks -Pssl.trustStore=`pwd`/mongo-truststore -Pssl.trustStorePassword=changeit" + +./gradlew -version + +# Disable errexit so both suites run and their exit codes can be captured below. +set +o errexit + +./gradlew --stacktrace --info ${GRADLE_EXTRA_VARS} -Dorg.mongodb.test.uri=${MONGODB_URI} \ + -Dorg.mongodb.test.kms.retry.ca.path="${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem" \ + driver-sync:cleanTest driver-sync:test --tests ClientSideEncryptionKmsRetryProseTest +first=$? +echo $first + +./gradlew --stacktrace --info ${GRADLE_EXTRA_VARS} -Dorg.mongodb.test.uri=${MONGODB_URI} \ + -Dorg.mongodb.test.kms.retry.ca.path="${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem" \ + driver-reactive-streams:cleanTest driver-reactive-streams:test --tests ClientSideEncryptionKmsRetryProseTest +second=$? +echo $second + +if [ $first -ne 0 ]; then + exit $first +elif [ $second -ne 0 ]; then + exit $second +else + exit 0 +fi diff --git a/driver-core/src/main/com/mongodb/internal/connection/tlschannel/impl/TlsChannelImpl.java b/driver-core/src/main/com/mongodb/internal/connection/tlschannel/impl/TlsChannelImpl.java index 20bc69e81f0..2daec943e5d 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/tlschannel/impl/TlsChannelImpl.java +++ b/driver-core/src/main/com/mongodb/internal/connection/tlschannel/impl/TlsChannelImpl.java @@ -236,10 +236,13 @@ public long read(ByteBufferSet dest) throws IOException { case NOT_HANDSHAKING: case FINISHED: UnwrapResult res = readAndUnwrap(Optional.of(dest)); + bytesToReturn = res.bytesProduced; if (res.wasClosed) { - return -1; + // JAVA-5391: return any bytes produced alongside close_notify; the next read + // sees shutdownReceived and returns -1. Fixed in upstream marianobarrios/tls-channel; + // this is the minimal patch until the vendored snapshot is refreshed. + return bytesToReturn > 0 ? bytesToReturn : -1; } - bytesToReturn = res.bytesProduced; handshakeStatus = res.lastHandshakeStatus; break; case NEED_TASK: diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java index 67ebf421c9c..076c8743f1b 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java @@ -48,10 +48,12 @@ import java.io.Closeable; import java.nio.channels.CompletionHandler; import java.nio.channels.InterruptedByTimeoutException; +import java.time.Duration; import java.util.List; import java.util.Map; import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.bson.assertions.Assertions.assertTrue; @@ -74,6 +76,29 @@ public void close() { } Mono decryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Timeout operationTimeout) { + return Mono.defer(() -> { + long sleepMicros = keyDecryptor.sleepMicroseconds(); + if (sleepMicros > 0 && operationTimeout != null) { + operationTimeout.run(MICROSECONDS, + () -> { }, + remainingMicros -> { + if (remainingMicros < sleepMicros) { + throw TimeoutContext.createMongoTimeoutException(TIMEOUT_ERROR_MESSAGE); + } + }, + () -> { + throw TimeoutContext.createMongoTimeoutException(TIMEOUT_ERROR_MESSAGE); + }); + } + Mono attempt = sleepMicros > 0 + ? Mono.delay(Duration.ofNanos(MICROSECONDS.toNanos(sleepMicros))) + .then(attemptDecryptKey(keyDecryptor, operationTimeout)) + : attemptDecryptKey(keyDecryptor, operationTimeout); + return attempt; + }).onErrorMap(this::unWrapException); + } + + private Mono attemptDecryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Timeout operationTimeout) { SocketSettings socketSettings = SocketSettings.builder() .connectTimeout(timeoutMillis, MILLISECONDS) .readTimeout(timeoutMillis, MILLISECONDS) @@ -85,84 +110,119 @@ Mono decryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Time LOGGER.info("Connecting to KMS server at " + serverAddress); - return Mono.create(sink -> { - Stream stream = streamFactory.create(serverAddress); + return Mono.create(sink -> { OperationContext operationContext = createOperationContext(operationTimeout, socketSettings); + Stream stream = streamFactory.create(serverAddress); stream.openAsync(operationContext, new AsyncCompletionHandler() { @Override public void completed(@Nullable final Void ignored) { - streamWrite(stream, keyDecryptor, operationContext, sink); + try { + streamWrite(stream, keyDecryptor, operationContext, sink); + } catch (Throwable t) { + stream.close(); + sink.error(t); + } } @Override public void failed(final Throwable t) { stream.close(); - handleError(t, operationContext, sink); + failOrHandleError(t, keyDecryptor, operationContext, sink); } }); - }).onErrorMap(this::unWrapException); + }).flatMap(shouldRetry -> { + if (shouldRetry) { + return decryptKey(keyDecryptor, operationTimeout); + } + return Mono.empty(); + }); } private void streamWrite(final Stream stream, final MongoKeyDecryptor keyDecryptor, - final OperationContext operationContext, final MonoSink sink) { + final OperationContext operationContext, final MonoSink sink) { List byteBufs = singletonList(new ByteBufNIO(keyDecryptor.getMessage())); stream.writeAsync(byteBufs, operationContext, new AsyncCompletionHandler() { @Override public void completed(@Nullable final Void aVoid) { - streamRead(stream, keyDecryptor, operationContext, sink); + try { + int bytesNeeded = keyDecryptor.bytesNeeded(); + int readSize = bytesNeeded > 0 ? bytesNeeded : MongoKeyDecryptor.DEFAULT_KMS_READ_SIZE; + streamRead(stream, keyDecryptor, operationContext, sink, readSize); + } catch (Throwable t) { + stream.close(); + sink.error(t); + } } @Override public void failed(final Throwable t) { stream.close(); - handleError(t, operationContext, sink); + failOrHandleError(t, keyDecryptor, operationContext, sink); } }); } private void streamRead(final Stream stream, final MongoKeyDecryptor keyDecryptor, - final OperationContext operationContext, final MonoSink sink) { - int bytesNeeded = keyDecryptor.bytesNeeded(); - if (bytesNeeded > 0) { - AsynchronousChannelStream asyncStream = (AsynchronousChannelStream) stream; - ByteBuf buffer = asyncStream.getBuffer(bytesNeeded); - long readTimeoutMS = operationContext.getTimeoutContext().getReadTimeoutMS(); - asyncStream.getChannel().read(buffer.asNIO(), readTimeoutMS, MILLISECONDS, null, - new CompletionHandler() { - - @Override - public void completed(final Integer integer, final Void aVoid) { - if (integer == -1) { - sink.error(new MongoException( - "Unexpected end of stream from KMS provider " + keyDecryptor.getKmsProvider())); - return; - } - buffer.flip(); - try { - keyDecryptor.feed(buffer.asNIO()); - buffer.release(); - streamRead(stream, keyDecryptor, operationContext, sink); - } catch (Throwable t) { - sink.error(t); - } - } - - @Override - public void failed(final Throwable t, final Void aVoid) { - buffer.release(); - stream.close(); - handleError(t, operationContext, sink); - } - }); - } else { + final OperationContext operationContext, final MonoSink sink, + final int readSize) { + if (readSize <= 0) { stream.close(); - sink.success(); + sink.success(false); + return; } + AsynchronousChannelStream asyncStream = (AsynchronousChannelStream) stream; + ByteBuf buffer = asyncStream.getBuffer(readSize); + long readTimeoutMS = operationContext.getTimeoutContext().getReadTimeoutMS(); + asyncStream.getChannel().read(buffer.asNIO(), readTimeoutMS, MILLISECONDS, null, + new CompletionHandler() { + + @Override + public void completed(final Integer integer, final Void aVoid) { + try { + if (integer == -1) { + buffer.release(); + stream.close(); + MongoException eof = new MongoException("Unexpected end of stream from KMS provider " + + keyDecryptor.getKmsProvider()); + failOrHandleError(eof, keyDecryptor, operationContext, sink); + return; + } + buffer.flip(); + boolean shouldRetry; + try { + shouldRetry = keyDecryptor.feedAndRetry(buffer.asNIO()); + } finally { + buffer.release(); + } + if (shouldRetry) { + stream.close(); + sink.success(true); + } else { + streamRead(stream, keyDecryptor, operationContext, sink, + keyDecryptor.bytesNeeded()); + } + } catch (Throwable t) { + stream.close(); + sink.error(t); + } + } + + @Override + public void failed(final Throwable t, final Void aVoid) { + buffer.release(); + stream.close(); + failOrHandleError(t, keyDecryptor, operationContext, sink); + } + }); } - private static void handleError(final Throwable t, final OperationContext operationContext, final MonoSink sink) { + private static void failOrHandleError(final Throwable t, final MongoKeyDecryptor keyDecryptor, + final OperationContext operationContext, final MonoSink sink) { if (isTimeoutException(t) && operationContext.getTimeoutContext().hasTimeoutMS()) { sink.error(TimeoutContext.createMongoTimeoutException(TIMEOUT_ERROR_MESSAGE, t)); + } else if (keyDecryptor.fail()) { + LOGGER.debug("Retrying KMS request after transient error", t); + sink.success(true); } else { sink.error(t); } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionKmsRetryProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionKmsRetryProseTest.java new file mode 100644 index 00000000000..1dd4f0f833d --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionKmsRetryProseTest.java @@ -0,0 +1,30 @@ +/* + * 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.reactivestreams.client; + +import com.mongodb.ClientEncryptionSettings; +import com.mongodb.client.AbstractClientSideEncryptionKmsRetryProseTest; +import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.reactivestreams.client.syncadapter.SyncClientEncryption; +import com.mongodb.reactivestreams.client.vault.ClientEncryptions; + +public class ClientSideEncryptionKmsRetryProseTest extends AbstractClientSideEncryptionKmsRetryProseTest { + @Override + public ClientEncryption getClientEncryption(final ClientEncryptionSettings settings) { + return new SyncClientEncryption(ClientEncryptions.create(settings)); + } +} diff --git a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java index 67fac13770c..6039d64ae98 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java +++ b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java @@ -24,8 +24,11 @@ import com.mongodb.client.model.vault.EncryptOptions; import com.mongodb.client.model.vault.RewrapManyDataKeyOptions; import com.mongodb.crypt.capi.MongoCryptException; +import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.capi.MongoCryptHelper; import com.mongodb.internal.crypt.capi.MongoCrypt; +import com.mongodb.internal.diagnostics.logging.Logger; +import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.crypt.capi.MongoCryptContext; import com.mongodb.internal.crypt.capi.MongoDataKeyOptions; import com.mongodb.internal.crypt.capi.MongoKeyDecryptor; @@ -38,11 +41,13 @@ import org.bson.RawBsonDocument; import java.io.Closeable; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import static com.mongodb.assertions.Assertions.assertNotNull; @@ -56,6 +61,8 @@ */ public class Crypt implements Closeable { + private static final Logger LOGGER = Loggers.getLogger("client"); + private static final String TIMEOUT_ERROR_MESSAGE = "KMS key decryption exceeded the timeout limit."; private static final RawBsonDocument EMPTY_RAW_BSON_DOCUMENT = RawBsonDocument.parse("{}"); private final MongoCrypt mongoCrypt; private final Map> kmsProviders; @@ -361,19 +368,83 @@ private void decryptKeys(final MongoCryptContext cryptContext, @Nullable final T } } - private void decryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Timeout operationTimeout) throws IOException { - try (InputStream inputStream = keyManagementService.stream(keyDecryptor.getKmsProvider(), keyDecryptor.getHostName(), - keyDecryptor.getMessage(), operationTimeout)) { - int bytesNeeded = keyDecryptor.bytesNeeded(); + private void decryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Timeout operationTimeout) + throws IOException, InterruptedException { + while (true) { + long sleepMicros = keyDecryptor.sleepMicroseconds(); + if (sleepMicros > 0) { + if (operationTimeout != null) { + operationTimeout.run(TimeUnit.MICROSECONDS, + () -> { }, + remainingMicros -> { + if (remainingMicros < sleepMicros) { + throw TimeoutContext.createMongoTimeoutException( + TIMEOUT_ERROR_MESSAGE); + } + }, + () -> { + throw TimeoutContext.createMongoTimeoutException( + TIMEOUT_ERROR_MESSAGE); + }); + } + TimeUnit.MICROSECONDS.sleep(sleepMicros); + } + boolean shouldRetry; + try { + shouldRetry = attemptDecryptKey(keyDecryptor, operationTimeout); + } catch (IOException e) { + if (!keyDecryptor.fail()) { + throw e; + } + LOGGER.debug("Retrying KMS request after transient error", e); + continue; + } + if (!shouldRetry) { + return; + } + } + } - while (bytesNeeded > 0) { - byte[] bytes = new byte[bytesNeeded]; + private boolean attemptDecryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Timeout operationTimeout) + throws IOException { + Timeout.onExistsAndExpired(operationTimeout, () -> { + throw TimeoutContext.createMongoTimeoutException(TIMEOUT_ERROR_MESSAGE); + }); + // After a fail()-triggered retry, bytesNeeded() may return 0 until feedAndRetry() clears + // the flag, so the do-while guarantees at least one read using DEFAULT_KMS_READ_SIZE. + InputStream inputStream = keyManagementService.stream(keyDecryptor.getKmsProvider(), keyDecryptor.getHostName(), + keyDecryptor.getMessage(), operationTimeout); + Throwable primary = null; + try { + int bytesNeeded = keyDecryptor.bytesNeeded(); + int readSize = bytesNeeded > 0 ? bytesNeeded : MongoKeyDecryptor.DEFAULT_KMS_READ_SIZE; + do { + byte[] bytes = new byte[readSize]; int bytesRead = inputStream.read(bytes, 0, bytes.length); if (bytesRead == -1) { - throw new MongoException("Unexpected end of stream from KMS provider " + keyDecryptor.getKmsProvider()); + throw new EOFException("Unexpected end of stream from KMS provider " + keyDecryptor.getKmsProvider()); + } + if (keyDecryptor.feedAndRetry(ByteBuffer.wrap(bytes, 0, bytesRead))) { + return true; + } + readSize = keyDecryptor.bytesNeeded(); + } while (readSize > 0); + return false; + } catch (Throwable t) { + primary = t; + throw t; + } finally { + // If the feed loop succeeded, suppress close() failures so they do not trigger a retry + // of an already-complete KMS exchange. If the feed loop threw, preserve the primary + // exception and attach any close() failure as suppressed — matching try-with-resources. + try { + inputStream.close(); + } catch (IOException closeException) { + if (primary != null) { + primary.addSuppressed(closeException); + } else { + LOGGER.debug("Ignoring close() failure after successful KMS exchange", closeException); } - keyDecryptor.feed(ByteBuffer.wrap(bytes, 0, bytesRead)); - bytesNeeded = keyDecryptor.bytesNeeded(); } } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionKmsRetryProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionKmsRetryProseTest.java new file mode 100644 index 00000000000..e7aa620d8dd --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionKmsRetryProseTest.java @@ -0,0 +1,282 @@ +/* + * 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.client; + +import com.mongodb.ClientEncryptionSettings; +import com.mongodb.MongoClientException; +import com.mongodb.MongoOperationTimeoutException; +import com.mongodb.client.model.vault.DataKeyOptions; +import com.mongodb.client.model.vault.EncryptOptions; +import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.lang.NonNull; +import com.mongodb.lang.Nullable; +import org.bson.BsonBinary; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static com.mongodb.ClusterFixture.getEnv; +import static com.mongodb.ClusterFixture.hasEncryptionTestsEnabled; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.client.Fixture.getMongoClientSettings; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * See + * 24. KMS Retry Tests. + * + *

Requires the {@code org.mongodb.test.kms.retry.ca.path} system property pointing to the CA cert for the + * failpoint server. + */ +public abstract class AbstractClientSideEncryptionKmsRetryProseTest { + + private static final String FAILPOINT_SERVER_ADDRESS = "127.0.0.1:9003"; + private static final String FAILPOINT_URL_BASE = "https://" + FAILPOINT_SERVER_ADDRESS; + + @NonNull + protected abstract ClientEncryption getClientEncryption(ClientEncryptionSettings settings); + + @BeforeEach + public void setUp() { + assumeTrue(System.getProperty("org.mongodb.test.kms.retry.ca.path") != null, + "org.mongodb.test.kms.retry.ca.path system property is not set"); + } + + /** + * Case 1: createDataKey and encrypt with TCP retry. + */ + @ParameterizedTest(name = "Case 1: TCP retry with {0}") + @ValueSource(strings = {"aws", "azure", "gcp"}) + public void testCreateDataKeyAndEncryptWithTcpRetry(final String provider) { + assumeTrue(hasEncryptionTestsEnabled()); + assumeTrue(serverVersionAtLeast(4, 2)); + + try (ClientEncryption clientEncryption = createClientEncryptionForRetryTest()) { + setFailpoint("network", 1); + BsonBinary keyId = assertDoesNotThrow( + () -> clientEncryption.createDataKey(provider, getDataKeyOptions(provider))); + + setFailpoint("network", 1); + assertDoesNotThrow( + () -> clientEncryption.encrypt(new BsonInt32(123), + new EncryptOptions("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic").keyId(keyId))); + } + } + + /** + * Case 2: createDataKey and encrypt with HTTP retry. + */ + @ParameterizedTest(name = "Case 2: HTTP retry with {0}") + @ValueSource(strings = {"aws", "azure", "gcp"}) + public void testCreateDataKeyAndEncryptWithHttpRetry(final String provider) { + assumeTrue(hasEncryptionTestsEnabled()); + assumeTrue(serverVersionAtLeast(4, 2)); + + try (ClientEncryption clientEncryption = createClientEncryptionForRetryTest()) { + setFailpoint("http", 1); + BsonBinary keyId = assertDoesNotThrow( + () -> clientEncryption.createDataKey(provider, getDataKeyOptions(provider))); + + setFailpoint("http", 1); + assertDoesNotThrow( + () -> clientEncryption.encrypt(new BsonInt32(123), + new EncryptOptions("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic").keyId(keyId))); + } + } + + /** + * Case 3: createDataKey fails after too many retries. + */ + @ParameterizedTest(name = "Case 3: Exhausted retries with {0}") + @ValueSource(strings = {"aws", "azure", "gcp"}) + public void testCreateDataKeyFailsAfterTooManyRetries(final String provider) { + assumeTrue(hasEncryptionTestsEnabled()); + assumeTrue(serverVersionAtLeast(4, 2)); + + try (ClientEncryption clientEncryption = createClientEncryptionForRetryTest()) { + setFailpoint("network", 4); + assertThrows(MongoClientException.class, + () -> clientEncryption.createDataKey(provider, getDataKeyOptions(provider))); + } + } + + /** + * Prose test: createDataKey surfaces MongoOperationTimeoutException when the operation timeout expires + * mid-retry. Configures a 100ms operation timeout and a failpoint that triggers repeated network errors; + * the cumulative retry backoff must push the operation past its deadline, and the expiry check at the + * top of each retry iteration must surface MongoOperationTimeoutException rather than MongoClientException. + */ + @Test + public void testCreateDataKeyTimesOutDuringRetry() { + assumeTrue(hasEncryptionTestsEnabled()); + assumeTrue(serverVersionAtLeast(4, 2)); + + try (ClientEncryption clientEncryption = createClientEncryptionForRetryTest(100L)) { + setFailpoint("network", 4); + assertThrows(MongoOperationTimeoutException.class, + () -> clientEncryption.createDataKey("aws", getDataKeyOptions("aws"))); + } + } + + private ClientEncryption createClientEncryptionForRetryTest() { + return createClientEncryptionForRetryTest(null); + } + + private ClientEncryption createClientEncryptionForRetryTest(@Nullable final Long timeoutMS) { + Map> kmsProviders = getKmsProvidersForRetryTest(); + SSLContext failpointSslContext = createFailpointSslContext(); + Map kmsProviderSslContextMap = new HashMap<>(); + kmsProviderSslContextMap.put("aws", failpointSslContext); + kmsProviderSslContextMap.put("azure", failpointSslContext); + kmsProviderSslContextMap.put("gcp", failpointSslContext); + + ClientEncryptionSettings.Builder builder = ClientEncryptionSettings.builder() + .keyVaultMongoClientSettings(getMongoClientSettings()) + .keyVaultNamespace("keyvault.datakeys") + .kmsProviders(kmsProviders) + .kmsProviderSslContextMap(kmsProviderSslContextMap); + if (timeoutMS != null) { + builder.timeout(timeoutMS, TimeUnit.MILLISECONDS); + } + + return getClientEncryption(builder.build()); + } + + private static Map> getKmsProvidersForRetryTest() { + return new HashMap>() {{ + put("aws", new HashMap() {{ + put("accessKeyId", getEnv("AWS_ACCESS_KEY_ID")); + put("secretAccessKey", getEnv("AWS_SECRET_ACCESS_KEY")); + }}); + put("azure", new HashMap() {{ + put("tenantId", getEnv("AZURE_TENANT_ID")); + put("clientId", getEnv("AZURE_CLIENT_ID")); + put("clientSecret", getEnv("AZURE_CLIENT_SECRET")); + put("identityPlatformEndpoint", FAILPOINT_SERVER_ADDRESS); + }}); + put("gcp", new HashMap() {{ + put("email", getEnv("GCP_EMAIL")); + put("privateKey", getEnv("GCP_PRIVATE_KEY")); + put("endpoint", FAILPOINT_SERVER_ADDRESS); + }}); + }}; + } + + private static DataKeyOptions getDataKeyOptions(final String provider) { + BsonDocument masterKey; + switch (provider) { + case "aws": + masterKey = new BsonDocument() + .append("region", new BsonString("foo")) + .append("key", new BsonString("bar")) + .append("endpoint", new BsonString(FAILPOINT_SERVER_ADDRESS)); + break; + case "azure": + masterKey = new BsonDocument() + .append("keyVaultEndpoint", new BsonString(FAILPOINT_SERVER_ADDRESS)) + .append("keyName", new BsonString("foo")); + break; + case "gcp": + masterKey = new BsonDocument() + .append("projectId", new BsonString("foo")) + .append("location", new BsonString("bar")) + .append("keyRing", new BsonString("baz")) + .append("keyName", new BsonString("qux")) + .append("endpoint", new BsonString(FAILPOINT_SERVER_ADDRESS)); + break; + default: + throw new UnsupportedOperationException("Unsupported KMS provider: " + provider); + } + return new DataKeyOptions().masterKey(masterKey); + } + + private static void setFailpoint(final String failpointType, final int count) { + try { + SSLContext sslContext = createFailpointSslContext(); + URL url = new URL(FAILPOINT_URL_BASE + "/set_failpoint/" + failpointType); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + try { + connection.setConnectTimeout(10_000); + connection.setReadTimeout(10_000); + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + connection.setHostnameVerifier((hostname, session) -> true); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + + byte[] body = ("{\"count\": " + count + "}").getBytes(StandardCharsets.UTF_8); + connection.setRequestProperty("Content-Length", String.valueOf(body.length)); + + try (OutputStream os = connection.getOutputStream()) { + os.write(body); + } + + int responseCode = connection.getResponseCode(); + assertEquals(200, responseCode, "Failed to set KMS failpoint, HTTP status: " + responseCode); + } finally { + connection.disconnect(); + } + } catch (Exception e) { + throw new RuntimeException("Failed to set KMS failpoint", e); + } + } + + private static SSLContext createFailpointSslContext() { + try { + String caCertPath = System.getProperty("org.mongodb.test.kms.retry.ca.path"); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate caCert; + try (FileInputStream fis = new FileInputStream(caCertPath)) { + caCert = (X509Certificate) cf.generateCertificate(fis); + } + + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + trustStore.setCertificateEntry("ca", caCert); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + return sslContext; + } catch (Exception e) { + throw new RuntimeException("Failed to create SSL context for failpoint server", e); + } + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionKmsRetryProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionKmsRetryProseTest.java new file mode 100644 index 00000000000..7b51bf915e5 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionKmsRetryProseTest.java @@ -0,0 +1,28 @@ +/* + * 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.client; + +import com.mongodb.ClientEncryptionSettings; +import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.client.vault.ClientEncryptions; + +public class ClientSideEncryptionKmsRetryProseTest extends AbstractClientSideEncryptionKmsRetryProseTest { + @Override + public ClientEncryption getClientEncryption(final ClientEncryptionSettings settings) { + return ClientEncryptions.create(settings); + } +} diff --git a/mongodb-crypt/AGENTS.md b/mongodb-crypt/AGENTS.md index bf9737febf7..03381f1572b 100644 --- a/mongodb-crypt/AGENTS.md +++ b/mongodb-crypt/AGENTS.md @@ -15,6 +15,17 @@ Client-side field-level encryption (CSFLE) support via JNA bindings to libmongoc review** - `com.mongodb.internal.crypt.capi` — Internal encryption state management +## CAPI.java — JNA Binding Declarations + +`CAPI.java` declares the JNA native method signatures for libmongocrypt. Javadoc on each method +must match the documentation in [mongocrypt.h](https://github.com/mongodb/libmongocrypt/blob/master/src/mongocrypt.h). + +When adding or updating bindings: +- Copy the doc comment from `mongocrypt.h` verbatim +- Replace Doxygen `@ref type_name` with Javadoc `{@link type_name}` (for types) or + `{@link #function_name}` (for functions in the same class) +- Replace `@p param_name` with `{@code param_name}` + ## Build & Test ```bash diff --git a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java index 41cc8ced31b..8e8be93501f 100644 --- a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java +++ b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java @@ -22,6 +22,7 @@ import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.PointerType; +import com.sun.jna.ptr.ByteByReference; import com.sun.jna.ptr.PointerByReference; //CHECKSTYLE:OFF @@ -52,16 +53,16 @@ public String toString() { /** * Indicates success or contains error information. *

- * Functions like @ref mongocrypt_ctx_encrypt_init follow a pattern to expose a + * Functions like {@link #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. + * corresponding (handle)_status function. E.g. {@link #mongocrypt_ctx_status}. */ public static class mongocrypt_status_t extends PointerType { } /** - * Contains all options passed on initialization of a @ref mongocrypt_ctx_t. + * Contains all options passed on initialization of a {@link mongocrypt_ctx_t}. */ public static class mongocrypt_opts_t extends PointerType { } @@ -69,8 +70,19 @@ 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. + * When constructing a {@link mongocrypt_binary_t} it is the responsibility of the + * caller to maintain the lifetime of the viewed data. However, all public + * functions that take a {@link mongocrypt_binary_t} as an argument will make a copy of + * the viewed data. + *

+ * Functions with a {@link mongocrypt_binary_t}* out guarantee the lifetime of the + * viewed data to live as long as the parent object. For example, + * {@link #mongocrypt_ctx_mongo_op} guarantees that the viewed data of + * {@link mongocrypt_binary_t} is valid until the parent ctx is destroyed with + * {@link #mongocrypt_ctx_destroy}. + *

+ * The {@code mongocrypt_binary_t} struct definition is public. + * Consumers may rely on the struct layout. */ public static class mongocrypt_binary_t extends PointerType { // The `mongocrypt_binary_t` struct layout is part of libmongocrypt's ABI: @@ -83,9 +95,11 @@ public static class mongocrypt_binary_t extends PointerType { 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`. @@ -107,7 +121,7 @@ public int len() { * 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 + * handles (e.g. mongocrypt_ctx_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. @@ -152,7 +166,7 @@ public static class mongocrypt_kms_ctx_t extends PointerType { * 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. + * @param len The length of the {@code data} byte array. * @return A new mongocrypt_binary_t. */ public static native mongocrypt_binary_t @@ -160,31 +174,29 @@ public static class mongocrypt_kms_ctx_t extends PointerType { /** - * Get a pointer to the referenced data. + * Get a pointer to the viewed data. * - * @param binary The @ref mongocrypt_binary_t. - * @return A pointer to the referenced data. + * @param binary The {@link mongocrypt_binary_t}. + * @return A pointer to the viewed data. */ public static native Pointer mongocrypt_binary_data(mongocrypt_binary_t binary); /** - * Get the length of the referenced data. + * Get the length of the viewed data. * - * @param binary The @ref mongocrypt_binary_t. - * @return The length of the referenced data. + * @param binary The {@link mongocrypt_binary_t}. + * @return The length of the viewed data. */ public static native int mongocrypt_binary_len(mongocrypt_binary_t binary); /** - * Free the @ref mongocrypt_binary_t. + * Free the {@link mongocrypt_binary_t}. *

- * This does not free the referenced data. Refer to individual function - * documentation to determine the lifetime guarantees of the underlying - * data. + * This does not free the viewed data. * * @param binary The mongocrypt_binary_t destroy. */ @@ -200,8 +212,8 @@ public static class mongocrypt_kms_ctx_t extends PointerType { * 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. + * this as an out-parameter to functions like {@link #mongocrypt_ctx_status}. + * When done, destroy it with {@link #mongocrypt_status_destroy}. * * @return A new status object. */ @@ -217,20 +229,23 @@ public static class mongocrypt_kms_ctx_t extends PointerType { * @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). + * @param message_len Due to historical behavior, pass 1 + the string length + * of {@code message} (which differs from other functions accepting string + * arguments). Alternatively, if message is NULL terminated this may be -1 to + * tell mongocrypt to determine the string's length with strlen. */ public static native void mongocrypt_status_set(mongocrypt_status_t status, - int type, - int code, - cstring message, - int message_len); + 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. + * @return A mongocrypt_status_type_t. */ public static native int @@ -248,11 +263,12 @@ public static class mongocrypt_kms_ctx_t extends PointerType { /** - * Get the error message associated with a status, or an empty string. + * Get the error message associated with a status or NULL. * * @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. + * @param len An optional length of the returned string (excluding the + * trailing NULL byte). May be NULL. + * @return A NULL terminated error message or NULL. */ public static native cstring mongocrypt_status_message(mongocrypt_status_t status, Pointer len); @@ -310,12 +326,12 @@ public interface mongocrypt_random_fn extends Callback { } /** - * Allocate a new @ref mongocrypt_t object. + * Allocate a new {@link mongocrypt_t} object. *

- * Initialize with @ref mongocrypt_init. When done, free with @ref - * mongocrypt_destroy. + * Set options using mongocrypt_setopt_* functions, then initialize with {@link #mongocrypt_init}. + * When done with the {@link mongocrypt_t}, free with {@link #mongocrypt_destroy}. * - * @return A new @ref mongocrypt_t object. + * @return A new {@link mongocrypt_t} object. */ public static native mongocrypt_t mongocrypt_new(); @@ -323,7 +339,7 @@ public interface mongocrypt_random_fn extends Callback { /** * Set a handler to get called on every log message. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link mongocrypt_t} object. * @param log_fn The log callback. * @param log_ctx A context passed as an argument to the log callback every * invokation. @@ -348,19 +364,18 @@ public interface mongocrypt_random_fn extends Callback { /** * Set a crypto hook for the AES256-CTR operations. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link mongocrypt_t} object. * @param aes_256_ctr_encrypt The crypto callback function for encrypt - * operation. + * 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 + * 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 {@link #mongocrypt_status} * */ public static native boolean - mongocrypt_setopt_aes_256_ctr (mongocrypt_t crypt, + mongocrypt_setopt_aes_256_ctr(mongocrypt_t crypt, mongocrypt_crypto_fn aes_256_ctr_encrypt, mongocrypt_crypto_fn aes_256_ctr_decrypt, Pointer ctx); @@ -373,12 +388,11 @@ public interface mongocrypt_random_fn extends Callback { *

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 crypt The {@link 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 + * @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 {@link #mongocrypt_status} */ public static native boolean mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5( @@ -387,20 +401,22 @@ public interface mongocrypt_random_fn extends Callback { Pointer sign_ctx); /** - * Set a handler to get called on every log message. + * Configure an AWS KMS provider on the {@link mongocrypt_t} object. * - * @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. + *

This has been superseded by the more flexible: + * {@link #mongocrypt_setopt_kms_providers}. + * + * @param crypt The {@link 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 {@code 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 {@code aws_secret_access_key}. + * 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 {@link #mongocrypt_ctx_status} */ public static native boolean mongocrypt_setopt_kms_provider_aws(mongocrypt_t crypt, @@ -410,11 +426,16 @@ public interface mongocrypt_random_fn extends Callback { int aws_secret_access_key_len); /** - * Configure a local KMS provider on the @ref mongocrypt_t object. + * Configure a local KMS provider on the {@link 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. + *

This has been superseded by the more flexible: + * {@link #mongocrypt_setopt_kms_providers}. + * + * @param crypt The {@link mongocrypt_t} object. + * @param key A 96 byte master key used to encrypt and decrypt key vault + * keys. The viewed data is copied. It is valid to destroy {@code key} with + * {@link #mongocrypt_binary_destroy} immediately after. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean mongocrypt_setopt_kms_provider_local(mongocrypt_t crypt, @@ -423,10 +444,9 @@ public interface mongocrypt_random_fn extends Callback { /** * Configure KMS providers with a BSON document. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link 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, @@ -435,41 +455,44 @@ public interface mongocrypt_random_fn extends Callback { /** * Set a local schema map for encryption. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link 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 + * 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 {@link #mongocrypt_status} */ public static native boolean - mongocrypt_setopt_schema_map (mongocrypt_t crypt, mongocrypt_binary_t schema_map); + mongocrypt_setopt_schema_map(mongocrypt_t crypt, mongocrypt_binary_t schema_map); /** - * Opt-into setting KMS providers before each KMS request. + * Opt-into handling the {@link #MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS} state. + * + *

If set, before entering the {@link #MONGOCRYPT_CTX_NEED_KMS} state, + * contexts may enter the {@link #MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS} state + * and then wait for credentials to be supplied through + * {@link #mongocrypt_ctx_provide_kms_providers}. * - * 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. + *

A context will only enter {@link #MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS} + * if an empty document was set for a KMS provider in + * {@link #mongocrypt_setopt_kms_providers}. * - * @param crypt The @ref mongocrypt_t object to update + * @param crypt The {@link mongocrypt_t} object to update */ public static native void - mongocrypt_setopt_use_need_kms_credentials_state (mongocrypt_t crypt); + mongocrypt_setopt_use_need_kms_credentials_state(mongocrypt_t crypt); /** * Set a local EncryptedFieldConfigMap for encryption. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link 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 + * supplied by the user. The keys are collection namespaces and values are + * EncryptedFieldConfigMap documents. The viewed data copied. It is valid to + * destroy {@code efc_map} with {@link #mongocrypt_binary_destroy} immediately after. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_status} */ public static native boolean - mongocrypt_setopt_encrypted_field_config_map (mongocrypt_t crypt, mongocrypt_binary_t encryptedFieldConfigMap); + mongocrypt_setopt_encrypted_field_config_map(mongocrypt_t crypt, mongocrypt_binary_t encryptedFieldConfigMap); /** * Opt-into skipping query analysis. @@ -477,113 +500,118 @@ public interface mongocrypt_random_fn extends Callback { *

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.
  • + *
  • A mongocrypt_ctx_t will never enter the {@link #MONGOCRYPT_CTX_NEED_MONGO_MARKINGS} state.
  • *
* - * @param crypt The @ref mongocrypt_t object to update - * @since 1.5 + * @param crypt The {@link mongocrypt_t} object to update */ public static native void - mongocrypt_setopt_bypass_query_analysis (mongocrypt_t crypt); + mongocrypt_setopt_bypass_query_analysis(mongocrypt_t crypt); + + /** + * Enable or disable KMS retry behavior. + * + *

Requires that {@link #mongocrypt_init} has not been called on crypt. + * + * @param crypt The {@link mongocrypt_t} object. + * @param enable A boolean indicating whether to retry operations. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} + */ + public static native boolean + mongocrypt_setopt_retry_kms(mongocrypt_t crypt, boolean enable); /** * Set the expiration time for the data encryption key cache. Defaults to 60 seconds if not set. * - * @param crypt The @ref mongocrypt_t object to update + * @param crypt The {@link mongocrypt_t} object to update * @param cache_expiration_ms if 0 the cache never expires * @return A boolean indicating success. If false, an error status is set. - * @since 5.4 */ public static native boolean - mongocrypt_setopt_key_expiration (mongocrypt_t crypt, long cache_expiration_ms); + mongocrypt_setopt_key_expiration(mongocrypt_t crypt, long cache_expiration_ms); /** * Opt-into enabling sending multiple collection info documents. * - * @param crypt The @ref mongocrypt_t object to update + * @param crypt The {@link mongocrypt_t} object to update */ public static native void - mongocrypt_setopt_enable_multiple_collinfo (mongocrypt_t crypt); + mongocrypt_setopt_enable_multiple_collinfo(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 ctx The {@link 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 + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status}. */ public static native boolean - mongocrypt_ctx_setopt_contention_factor (mongocrypt_ctx_t ctx, long contention_factor); + 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 {@link #mongocrypt_ctx_setopt_key_id} is used. * - * 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 ctx The {@link 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 + * The viewed data is copied. It is valid to destroy key_id with {@link #mongocrypt_binary_destroy} immediately after. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean - mongocrypt_ctx_setopt_index_key_id (mongocrypt_ctx_t ctx, mongocrypt_binary_t key_id); + 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 + * @param crypt The {@link 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, {@link #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 {@link #mongocrypt_setopt_set_crypt_shared_lib_path_override}, + * then paths appended here will have no effect.

*/ public static native void - mongocrypt_setopt_append_crypt_shared_lib_search_path (mongocrypt_t crypt, cstring path); + 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 + * + * @param crypt The {@link 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 {@link #mongocrypt_init}.

+ *

If a crypt_shared library path override is specified here, then no paths given + * to {@link #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 {@link #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.

*/ public static native void mongocrypt_setopt_set_crypt_shared_lib_path_override(mongocrypt_t crypt, cstring path); @@ -592,80 +620,76 @@ public interface mongocrypt_random_fn extends Callback { * 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 ctx The {@link 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 + * @param len the length + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean - mongocrypt_ctx_setopt_query_type (mongocrypt_ctx_t ctx, cstring query_type, int len); + 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> + * "min": Optional<BSON value>, + * "max": Optional<BSON value>, + * "sparsity": Optional<Int64>, + * "precision": Optional<Int32> + * "trimFactor": Optional<Int32> * } * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link 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); + mongocrypt_ctx_setopt_algorithm_range(mongocrypt_ctx_t ctx, mongocrypt_binary_t opts); /** * Set options for explicit encryption with the "textPreview" algorithm. "prefix" and "suffix" can both be set. * NOTE: "textPreview" is experimental only and may be removed in a future non-major release. * opts is a BSON document of the form: - * + *

* { - * "caseSensitive": bool, - * "diacriticSensitive": bool, - * "prefix": Optional{ - * "strMaxQueryLength": Int32, - * "strMinQueryLength": Int32, - * }, - * "suffix": Optional{ - * "strMaxQueryLength": Int32, - * "strMinQueryLength": Int32, - * }, - * "substring": Optional{ - * "strMaxLength": Int32, - * "strMaxQueryLength": Int32, - * "strMinQueryLength": Int32, - * }, + * "caseSensitive": bool, + * "diacriticSensitive": bool, + * "prefix": Optional{ + * "strMaxQueryLength": Int32, + * "strMinQueryLength": Int32, + * }, + * "suffix": Optional{ + * "strMaxQueryLength": Int32, + * "strMinQueryLength": Int32, + * }, + * "substring": Optional{ + * "strMaxLength": Int32, + * "strMaxQueryLength": Int32, + * "strMinQueryLength": Int32, + * }, * } * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link mongocrypt_ctx_t} object. * @param opts BSON. * @return A boolean indicating success. If false, an error status is set. - * @since 5.6 */ public static native boolean mongocrypt_ctx_setopt_algorithm_text(mongocrypt_ctx_t ctx, mongocrypt_binary_t opts); /** - * Initialize new @ref mongocrypt_t object. + * Initialize new {@link mongocrypt_t} object. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link 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. + * Get the status associated with a {@link mongocrypt_t} object. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link mongocrypt_t} object. * @param status Receives the status. * @return A boolean indicating success. */ @@ -685,9 +709,9 @@ public interface mongocrypt_random_fn extends Callback { mongocrypt_is_crypto_available(); /** - * Destroy the @ref mongocrypt_t object. + * Destroy the {@link mongocrypt_t} object. * - * @param crypt The @ref mongocrypt_t object to destroy. + * @param crypt The {@link mongocrypt_t} object to destroy. */ public static native void mongocrypt_destroy(mongocrypt_t crypt); @@ -695,45 +719,42 @@ public interface mongocrypt_random_fn extends Callback { /** * 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. - * + * @param crypt The {@link mongocrypt_t} object after a successful call to {@link #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); + mongocrypt_crypt_shared_lib_version_string(mongocrypt_t crypt, Pointer len); /** - * Call in response to the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state + * Call in response to the {@link #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 + * as {@link #mongocrypt_setopt_kms_providers}. If no keys are present in the + * BSON input, the KMS provider settings configured for the {@link mongocrypt_t} * at initialization are used. * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link 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. + * to credentials. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status}. */ public static native boolean - mongocrypt_ctx_provide_kms_providers (mongocrypt_ctx_t ctx, - mongocrypt_binary_t kms_providers); + 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 ctx The {@link 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); + mongocrypt_ctx_setopt_key_id(mongocrypt_ctx_t ctx, + mongocrypt_binary_t key_id); /** * Set the keyAltName to use for explicit encryption. @@ -742,14 +763,13 @@ public interface mongocrypt_random_fn extends Callback { * *

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

* - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link 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 + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean - mongocrypt_ctx_setopt_key_alt_name (mongocrypt_ctx_t ctx, - mongocrypt_binary_t key_alt_name); + mongocrypt_ctx_setopt_key_alt_name(mongocrypt_ctx_t ctx, + mongocrypt_binary_t key_alt_name); /** * Set the keyMaterial to use for encrypting data. @@ -759,32 +779,30 @@ public interface mongocrypt_random_fn extends Callback { * { "keyMaterial" : (BSON BINARY value) } *

* - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link 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 + * copied. It is valid to destroy {@code key_material} with {@link #mongocrypt_binary_destroy} immediately after. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean - mongocrypt_ctx_setopt_key_material (mongocrypt_ctx_t ctx, mongocrypt_binary_t key_material); + 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" + * "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + * "AEAD_AES_256_CBC_HMAC_SHA_512-Random" * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link mongocrypt_ctx_t} object. * @param algorithm A string specifying the algorithm to - * use for encryption. - * @param len The length of the algorithm string. + * use for encryption. + * @param len The length of the algorithm string. * @return A boolean indicating success. */ public static native boolean @@ -794,12 +812,12 @@ public interface mongocrypt_random_fn extends Callback { /** - * Create a new uninitialized @ref mongocrypt_ctx_t. + * Create a new uninitialized {@link mongocrypt_ctx_t}. *

- * Initialize the context with functions like @ref mongocrypt_ctx_encrypt_init. - * When done, destroy it with @ref mongocrypt_ctx_destroy. + * Initialize the context with functions like {@link #mongocrypt_ctx_encrypt_init}. + * When done, destroy it with {@link #mongocrypt_ctx_destroy}. * - * @param crypt The @ref mongocrypt_t object. + * @param crypt The {@link mongocrypt_t} object. * @return A new context. */ public static native mongocrypt_ctx_t @@ -807,9 +825,9 @@ public interface mongocrypt_random_fn extends Callback { /** - * Get the status associated with a @ref mongocrypt_ctx_t object. + * Get the status associated with a {@link mongocrypt_ctx_t} object. * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link mongocrypt_ctx_t} object. * @param status Receives the status. * @return A boolean indicating success. */ @@ -821,14 +839,14 @@ public interface mongocrypt_random_fn extends Callback { /** * 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). + * @param ctx The {@link mongocrypt_ctx_t} object. + * @param region The AWS region. + * @param region_len The string length of {@code 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 {@code cmk_len}. Pass -1 to determine the + * string length with strlen (must be NULL terminated). * @return A boolean indicating success. */ public static native boolean @@ -845,12 +863,11 @@ public interface mongocrypt_random_fn extends Callback { * 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 + * @param ctx The {@link mongocrypt_ctx_t} object. + * @param endpoint The endpoint. + * @param endpoint_len The string length of {@code 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 {@link #mongocrypt_ctx_status} */ public static native boolean mongocrypt_ctx_setopt_masterkey_aws_endpoint (mongocrypt_ctx_t ctx, @@ -861,52 +878,47 @@ public interface mongocrypt_random_fn extends Callback { /** * Set the master key to "local" for creating a data key. * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link mongocrypt_ctx_t} object. * @return A boolean indicating success. */ public static native boolean - mongocrypt_ctx_setopt_masterkey_local (mongocrypt_ctx_t ctx); + 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 ctx The {@link 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); + mongocrypt_binary_t keyDocument); /** * Initialize a context to create a data key. - * - * Set options before using @ref mongocrypt_ctx_setopt_masterkey_aws and + *

+ * Set options before using {@link #mongocrypt_ctx_setopt_masterkey_aws} and * mongocrypt_ctx_setopt_masterkey_local. * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link 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. + * has been set on the parent {@link mongocrypt_t}. */ public static native boolean - mongocrypt_ctx_datakey_init (mongocrypt_ctx_t ctx); + 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 + * @param ctx The {@link mongocrypt_ctx_t} object. + * @param db The database name. + * @param db_len The byte length of {@code db}. Pass -1 to determine the string length with strlen (must be NULL terminated). + * @param cmd The BSON command to be encrypted. The viewed data is copied. + * It is valid to destroy {@code cmd} with {@link #mongocrypt_binary_destroy} immediately after. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t ctx, @@ -917,65 +929,67 @@ public interface mongocrypt_random_fn extends Callback { /** * 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. + * @param ctx A {@link mongocrypt_ctx_t}. + * @param msg A {@link 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); + 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 } + * {@code { "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> }}]}
+     *    {$and: [{: {: , {: {:  }}]}
      * 2. An Aggregate Expression of this form:
-     *    {$and: [{<op>: [<fieldpath>, <value1>]}, {<op>: [<fieldpath>, <value2>]}]
+     *    {$and: [{: [, ]}, {: [, ]}]
      *
      * 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 - * + * - {@link #mongocrypt_ctx_setopt_key_id} + * - {@link #mongocrypt_ctx_setopt_key_alt_name} + * - {@link #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 - * + * - {@link #mongocrypt_ctx_setopt_key_id} + * - {@link #mongocrypt_ctx_setopt_index_key_id} + * - {@link #mongocrypt_ctx_setopt_contention_factor} + * - {@link #mongocrypt_ctx_setopt_query_type} + * - {@link #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. + * @param ctx A {@link mongocrypt_ctx_t}. + * @param msg A {@link 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); + mongocrypt_ctx_explicit_encrypt_expression_init(mongocrypt_ctx_t ctx, mongocrypt_binary_t msg); /** * Initialize a context for decryption. @@ -991,26 +1005,24 @@ public interface mongocrypt_random_fn extends Callback { /** * 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. + * @param ctx A {@link mongocrypt_ctx_t}. + * @param msg A {@link 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); + 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 ctx The {@link 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); + mongocrypt_ctx_rewrap_many_datakey_init(mongocrypt_ctx_t ctx, mongocrypt_binary_t filter); public static final int MONGOCRYPT_CTX_ERROR = 0; @@ -1029,8 +1041,8 @@ public interface mongocrypt_random_fn extends Callback { /** * Get the current state of a context. * - * @param ctx The @ref mongocrypt_ctx_t object. - * @return A @ref mongocrypt_ctx_state_t. + * @param ctx The {@link mongocrypt_ctx_t} object. + * @return A mongocrypt_ctx_state_t. */ public static native int mongocrypt_ctx_state(mongocrypt_ctx_t ctx); @@ -1041,15 +1053,20 @@ public interface mongocrypt_random_fn extends Callback { * 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. + * {@code op_bson} is a BSON document to be used for the operation. + * - For {@link #MONGOCRYPT_CTX_NEED_MONGO_COLLINFO}(_WITH_DB) it is a listCollections filter. + * - For {@link #MONGOCRYPT_CTX_NEED_MONGO_KEYS} it is a find filter. + * - For {@link #MONGOCRYPT_CTX_NEED_MONGO_MARKINGS} it is a command to send to mongocryptd. *

* - * @param ctx The @ref mongocrypt_ctx_t object. - * @param op_bson A BSON document for the MongoDB operation. - * @return A boolean indicating success. + *

The lifetime of {@code op_bson} is tied to the lifetime of {@code ctx}. It is valid + * until {@link #mongocrypt_ctx_destroy} is called. + * + * @param ctx The {@link mongocrypt_ctx_t} object. + * @param op_bson A BSON document for the MongoDB operation. The data + * viewed by {@code op_bson} is guaranteed to be valid until {@code ctx} is destroyed with + * {@link #mongocrypt_ctx_destroy}. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean mongocrypt_ctx_mongo_op(mongocrypt_ctx_t ctx, mongocrypt_binary_t op_bson); @@ -1061,12 +1078,12 @@ public interface mongocrypt_random_fn extends Callback { * 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 + * - For {@link #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. + * - For {@link #MONGOCRYPT_CTX_NEED_MONGO_KEYS} it is a doc from a find cursor. + * - For {@link #MONGOCRYPT_CTX_NEED_MONGO_MARKINGS} it is a reply from mongocryptd. * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link mongocrypt_ctx_t} object. * @param reply A BSON document for the MongoDB operation. * @return A boolean indicating success. */ @@ -1077,7 +1094,7 @@ public interface mongocrypt_random_fn extends Callback { /** * Call when done feeding the reply (or replies) back to the context. * - * @param ctx The @ref mongocrypt_ctx_t object. + * @param ctx The {@link mongocrypt_ctx_t} object. * @return A boolean indicating success. */ @@ -1091,35 +1108,36 @@ public interface mongocrypt_random_fn extends Callback { * 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 + * If KMS handles are being handled synchronously, the driver can reuse the same * TLS socket to send HTTP requests and receive responses. + *

+ * The returned KMS handle does not outlive {@code ctx}. * - * @param ctx A @ref mongocrypt_ctx_t. - * @return a new @ref mongocrypt_kms_ctx_t or NULL. + * @param ctx A {@link mongocrypt_ctx_t}. + * @return a new {@link 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); + Pointer len); /** * Get the HTTP request message for a KMS handle. * - * @param kms A @ref mongocrypt_kms_ctx_t. + * @param kms A {@link mongocrypt_kms_ctx_t}. * @param msg The HTTP request to send to KMS. * @return A boolean indicating success. */ @@ -1130,11 +1148,11 @@ public interface mongocrypt_random_fn extends Callback { /** * 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. + * The storage for {@code endpoint} is not owned by the caller, but + * is valid until calling {@link #mongocrypt_ctx_kms_done} on the + * parent {@link mongocrypt_ctx_t}. * - * @param kms A @ref mongocrypt_kms_ctx_t. + * @param kms A {@link mongocrypt_kms_ctx_t}. * @param endpoint The output hostname. * @return A boolean indicating success. */ @@ -1142,9 +1160,9 @@ public interface mongocrypt_random_fn extends Callback { mongocrypt_kms_ctx_endpoint(mongocrypt_kms_ctx_t kms, PointerByReference endpoint); /** - * Indicates how many bytes to feed into @ref mongocrypt_kms_ctx_feed. + * Indicates how many bytes to feed into {@link #mongocrypt_kms_ctx_feed}. * - * @param kms The @ref mongocrypt_kms_ctx_t. + * @param kms The {@link mongocrypt_kms_ctx_t}. * @return The number of requested bytes. */ public static native int @@ -1154,34 +1172,78 @@ public interface mongocrypt_random_fn extends Callback { /** * Feed bytes from the HTTP response. *

- * Feeding more bytes than what has been returned in @ref - * mongocrypt_kms_ctx_bytes_needed is an error. + * Feeding more bytes than what has been returned in + * {@link #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. + * @param kms The {@link mongocrypt_kms_ctx_t}. + * @param bytes The bytes to feed. The viewed data is copied. It is valid to + * destroy bytes with {@link #mongocrypt_binary_destroy} immediately after. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_kms_ctx_status} */ public static native boolean mongocrypt_kms_ctx_feed(mongocrypt_kms_ctx_t kms, mongocrypt_binary_t bytes); + /** + * Indicates how long to sleep before sending this request. + * + *

Requires {@link #mongocrypt_setopt_retry_kms} to be enabled. + * + * @param kms The {@link mongocrypt_kms_ctx_t}. + * @return How long to sleep in microseconds. + */ + public static native long + mongocrypt_kms_ctx_usleep(mongocrypt_kms_ctx_t kms); + + /** + * Feed bytes from the KMS response. + * + *

Feeding more bytes than what has been returned in + * {@link #mongocrypt_kms_ctx_bytes_needed} is an error. + * + *

Requires {@link #mongocrypt_setopt_retry_kms} to be enabled. + * + * @param kms The {@link mongocrypt_kms_ctx_t}. + * @param bytes The bytes to feed. The viewed data is copied. It is valid to + * destroy bytes with {@link #mongocrypt_binary_destroy} immediately after. + * @param should_retry Receives whether the KMS request should be retried. Retry in-place + * without calling {@link #mongocrypt_kms_ctx_fail}. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_kms_ctx_status} + */ + public static native boolean + mongocrypt_kms_ctx_feed_with_retry(mongocrypt_kms_ctx_t kms, + mongocrypt_binary_t bytes, + ByteByReference should_retry); + + /** + * Indicate a network error. Discards all data fed to this KMS context with + * {@link #mongocrypt_kms_ctx_feed}. The {@link mongocrypt_kms_ctx_t} may be reused. + * + *

Requires {@link #mongocrypt_setopt_retry_kms} to be enabled. + * + * @param kms The {@link mongocrypt_kms_ctx_t}. + * @return A boolean indicating whether the failed request may be retried. + */ + public static native boolean + mongocrypt_kms_ctx_fail(mongocrypt_kms_ctx_t kms); + /** - * Get the status associated with a @ref mongocrypt_kms_ctx_t object. + * Get the status associated with a {@link mongocrypt_kms_ctx_t} object. * - * @param kms The @ref mongocrypt_kms_ctx_t object. + * @param kms The {@link 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); + mongocrypt_status_t status); /** * Call when done handling all KMS contexts. * - * @param ctx The @ref mongocrypt_ctx_t object. - * @return A boolean indicating success. + * @param ctx The {@link mongocrypt_ctx_t} object. + * @return A boolean indicating success. If false, an error status is set. Retrieve it with {@link #mongocrypt_ctx_status} */ public static native boolean mongocrypt_ctx_kms_done(mongocrypt_ctx_t ctx); @@ -1190,7 +1252,7 @@ public interface mongocrypt_random_fn extends Callback { /** * Perform the final encryption or decryption. * - * @param ctx A @ref mongocrypt_ctx_t. + * @param ctx A {@link mongocrypt_ctx_t}. * @param out The final BSON to send to the server. * @return a boolean indicating success. */ @@ -1199,9 +1261,9 @@ public interface mongocrypt_random_fn extends Callback { /** - * Destroy and free all memory associated with a @ref mongocrypt_ctx_t. + * Destroy and free all memory associated with a {@link mongocrypt_ctx_t}. * - * @param ctx A @ref mongocrypt_ctx_t. + * @param ctx A {@link mongocrypt_ctx_t}. */ public static native void mongocrypt_ctx_destroy(mongocrypt_ctx_t ctx); diff --git a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java index 774b9e718cb..5731ca20689 100644 --- a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java +++ b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java @@ -73,6 +73,7 @@ import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_kms_provider_local; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_kms_providers; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_log_handler; +import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_retry_kms; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_schema_map; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_set_crypt_shared_lib_path_override; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_use_need_kms_credentials_state; @@ -198,6 +199,8 @@ class MongoCryptImpl implements MongoCrypt { mongocrypt_setopt_use_need_kms_credentials_state(wrapped); } + configure(() -> mongocrypt_setopt_retry_kms(wrapped, true)); + if (options.getKmsProviderOptions() != null) { withBinaryHolder(options.getKmsProviderOptions(), binary -> configure(() -> mongocrypt_setopt_kms_providers(wrapped, binary))); diff --git a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptor.java b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptor.java index 9b0eae6776f..a8470433fe5 100644 --- a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptor.java +++ b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptor.java @@ -24,6 +24,15 @@ */ public interface MongoKeyDecryptor { + /** + * Initial read size after re-sending a KMS request. Matches libmongocrypt's DEFAULT_MAX_KMS_BYTE_REQUEST + * and is used when {@link #bytesNeeded()} still returns 0 because libmongocrypt's should_retry flag has + * not yet been cleared by {@link #feedAndRetry}. + * + * @since 5.8 + */ + int DEFAULT_KMS_READ_SIZE = 1024; + /** * Gets the name of the KMS provider, e.g. "aws" or "kmip" * @@ -73,4 +82,29 @@ public interface MongoKeyDecryptor { * @param bytes the received bytes */ void feed(ByteBuffer bytes); + + /** + * Gets the number of microseconds to sleep before sending the next KMS request. + * + * @return the number of microseconds to sleep, or 0 if no delay is needed + * @since 5.8 + */ + long sleepMicroseconds(); + + /** + * Feed the received bytes to the decryptor, with retry support. + * + * @param bytes the received bytes + * @return true if the KMS request should be retried + * @since 5.8 + */ + boolean feedAndRetry(ByteBuffer bytes); + + /** + * Signal to libmongocrypt that a network error occurred on this KMS request. + * + * @return true if the request should be retried, false if retries are exhausted + * @since 5.8 + */ + boolean fail(); } diff --git a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptorImpl.java b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptorImpl.java index 1411adffc21..f1ab70c3dbc 100644 --- a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptorImpl.java +++ b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoKeyDecryptorImpl.java @@ -22,6 +22,7 @@ import com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_t; import com.mongodb.internal.crypt.capi.CAPI.mongocrypt_status_t; import com.sun.jna.Pointer; +import com.sun.jna.ptr.ByteByReference; import com.sun.jna.ptr.PointerByReference; import java.nio.ByteBuffer; @@ -30,9 +31,12 @@ import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_binary_new; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_bytes_needed; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_endpoint; +import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_fail; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_feed; +import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_feed_with_retry; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_get_kms_provider; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_message; +import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_usleep; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_kms_ctx_status; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_status_code; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_status_destroy; @@ -42,6 +46,11 @@ import static com.mongodb.internal.crypt.capi.CAPIHelper.toByteBuffer; import static org.bson.assertions.Assertions.notNull; +/** + * Note: Not thread-safe: methods mutate the underlying native {@code mongocrypt_kms_ctx_t} and must be invoked serially. + * Callers perform retries sequentially — the sync driver in a {@code while} loop and the reactive driver via + * {@code Mono.flatMap} — so no external synchronization is required. + */ class MongoKeyDecryptorImpl implements MongoKeyDecryptor { private final mongocrypt_kms_ctx_t wrapped; @@ -96,6 +105,29 @@ public void feed(final ByteBuffer bytes) { } } + @Override + public long sleepMicroseconds() { + return mongocrypt_kms_ctx_usleep(wrapped); + } + + @Override + public boolean feedAndRetry(final ByteBuffer bytes) { + try (BinaryHolder binaryHolder = toBinary(bytes)) { + // Default 0 means "do not retry"; libmongocrypt writes 1 only when the driver should retry. + ByteByReference shouldRetry = new ByteByReference(); + boolean success = mongocrypt_kms_ctx_feed_with_retry(wrapped, binaryHolder.getBinary(), shouldRetry); + if (!success) { + throwExceptionFromStatus(); + } + return shouldRetry.getValue() != 0; + } + } + + @Override + public boolean fail() { + return mongocrypt_kms_ctx_fail(wrapped); + } + private void throwExceptionFromStatus() { mongocrypt_status_t status = mongocrypt_status_new(); mongocrypt_kms_ctx_status(wrapped, status);