Skip to content

Commit 94d0eb2

Browse files
committed
Client Side Encryption bypassAutoEncryption fix
If auto encryption is bypassed then the driver no longer tries to spawn mongocryptd. JAVA-3554
1 parent 12d45e9 commit 94d0eb2

File tree

10 files changed

+450
-78
lines changed

10 files changed

+450
-78
lines changed

driver-core/src/main/com/mongodb/internal/async/client/CommandMarker.java

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,27 @@ class CommandMarker implements Closeable {
3838
private AsyncMongoClient client;
3939
private final ProcessBuilder processBuilder;
4040

41-
CommandMarker(final Map<String, Object> options) {
42-
String connectionString;
41+
CommandMarker(final boolean isBypassAutoEncryption, final Map<String, Object> options) {
4342

44-
if (options.containsKey("mongocryptdURI")) {
45-
connectionString = (String) options.get("mongocryptdURI");
43+
if (isBypassAutoEncryption) {
44+
processBuilder = null;
45+
client = null;
4646
} else {
47-
connectionString = "mongodb://localhost:27020";
48-
}
47+
if (!options.containsKey("mongocryptdBypassSpawn") || !((Boolean) options.get("mongocryptdBypassSpawn"))) {
48+
processBuilder = new ProcessBuilder(createMongocryptdSpawnArgs(options));
49+
startProcess();
50+
} else {
51+
processBuilder = null;
52+
}
4953

50-
if (!options.containsKey("mongocryptdBypassSpawn") || !((Boolean) options.get("mongocryptdBypassSpawn"))) {
51-
processBuilder = new ProcessBuilder(createMongocryptdSpawnArgs(options));
52-
startProcess();
53-
} else {
54-
processBuilder = null;
55-
}
54+
String connectionString;
55+
if (options.containsKey("mongocryptdURI")) {
56+
connectionString = (String) options.get("mongocryptdURI");
57+
} else {
58+
connectionString = "mongodb://localhost:27020";
59+
}
5660

57-
client = AsyncMongoClients.create(MongoClientSettings.builder()
61+
client = AsyncMongoClients.create(MongoClientSettings.builder()
5862
.applyConnectionString(new ConnectionString(connectionString))
5963
.applyToClusterSettings(new Block<ClusterSettings.Builder>() {
6064
@Override
@@ -63,45 +67,53 @@ public void apply(final ClusterSettings.Builder builder) {
6367
}
6468
})
6569
.build());
70+
71+
}
6672
}
6773

6874
void mark(final String databaseName, final RawBsonDocument command, final SingleResultCallback<RawBsonDocument> callback) {
69-
final SingleResultCallback<RawBsonDocument> wrappedCallback = new SingleResultCallback<RawBsonDocument>() {
70-
@Override
71-
public void onResult(final RawBsonDocument result, final Throwable t) {
72-
if (t != null) {
73-
callback.onResult(null, new MongoClientException("Exception in encryption library: " + t.getMessage(), t));
74-
} else {
75-
callback.onResult(result, null);
75+
if (client != null) {
76+
final SingleResultCallback<RawBsonDocument> wrappedCallback = new SingleResultCallback<RawBsonDocument>() {
77+
@Override
78+
public void onResult(final RawBsonDocument result, final Throwable t) {
79+
if (t != null) {
80+
callback.onResult(null, new MongoClientException("Exception in encryption library: " + t.getMessage(), t));
81+
} else {
82+
callback.onResult(result, null);
83+
}
7684
}
77-
}
78-
};
79-
runCommand(databaseName, command, new SingleResultCallback<RawBsonDocument>() {
80-
@Override
81-
public void onResult(final RawBsonDocument result, final Throwable t) {
82-
if (t == null) {
83-
wrappedCallback.onResult(result, null);
84-
} else if (t instanceof MongoTimeoutException && processBuilder != null) {
85-
startProcessAndContinue(new SingleResultCallback<Void>() {
86-
@Override
87-
public void onResult(final Void result, final Throwable t) {
88-
if (t != null) {
89-
callback.onResult(null, t);
90-
} else {
91-
runCommand(databaseName, command, wrappedCallback);
85+
};
86+
runCommand(databaseName, command, new SingleResultCallback<RawBsonDocument>() {
87+
@Override
88+
public void onResult(final RawBsonDocument result, final Throwable t) {
89+
if (t == null) {
90+
wrappedCallback.onResult(result, null);
91+
} else if (t instanceof MongoTimeoutException && processBuilder != null) {
92+
startProcessAndContinue(new SingleResultCallback<Void>() {
93+
@Override
94+
public void onResult(final Void result, final Throwable t) {
95+
if (t != null) {
96+
callback.onResult(null, t);
97+
} else {
98+
runCommand(databaseName, command, wrappedCallback);
99+
}
92100
}
93-
}
94-
});
95-
} else {
96-
wrappedCallback.onResult(null, t);
101+
});
102+
} else {
103+
wrappedCallback.onResult(null, t);
104+
}
97105
}
98-
}
99-
});
106+
});
107+
} else {
108+
callback.onResult(command, null);
109+
}
100110
}
101111

102112
@Override
103113
public void close() {
104-
client.close();
114+
if (client != null) {
115+
client.close();
116+
}
105117
}
106118

107119
private void runCommand(final String databaseName, final RawBsonDocument command,

driver-core/src/main/com/mongodb/internal/async/client/Crypts.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public final class Crypts {
3333
public static Crypt createCrypt(final AsyncMongoClient client, final AutoEncryptionSettings options) {
3434
return new Crypt(MongoCrypts.create(createMongoCryptOptions(options.getKmsProviders(), options.getSchemaMap())),
3535
new CollectionInfoRetriever(client),
36-
new CommandMarker(options.getExtraOptions()),
36+
new CommandMarker(options.isBypassAutoEncryption(), options.getExtraOptions()),
3737
createKeyRetriever(client, options.getKeyVaultMongoClientSettings(), options.getKeyVaultNamespace()),
3838
createKeyManagementService(),
3939
options.isBypassAutoEncryption());

driver-core/src/test/functional/com/mongodb/internal/async/client/Fixture.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public static synchronized AsyncMongoClient getMongoClient() {
5454
return mongoClient;
5555
}
5656

57+
public static MongoClientSettings.Builder getMongoClientSettingsBuilder() {
58+
return getMongoClientBuilderFromConnectionString();
59+
}
60+
5761
public static MongoClientSettings getMongoClientSettings() {
5862
return getMongoClientBuilderFromConnectionString().build();
5963
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.reactivestreams.client;
18+
19+
import com.mongodb.AutoEncryptionSettings;
20+
import com.mongodb.ClientEncryptionSettings;
21+
import com.mongodb.MongoClientSettings;
22+
import com.mongodb.MongoNamespace;
23+
import com.mongodb.client.model.vault.DataKeyOptions;
24+
import com.mongodb.client.model.vault.EncryptOptions;
25+
import com.mongodb.client.result.InsertOneResult;
26+
import com.mongodb.reactivestreams.client.vault.ClientEncryption;
27+
import com.mongodb.reactivestreams.client.vault.ClientEncryptions;
28+
import org.bson.BsonBinary;
29+
import org.bson.BsonString;
30+
import org.bson.Document;
31+
import org.junit.After;
32+
import org.junit.Before;
33+
import org.junit.Test;
34+
import static reactivestreams.helpers.SubscriberHelpers.ObservableSubscriber;
35+
import static reactivestreams.helpers.SubscriberHelpers.OperationSubscriber;
36+
37+
import java.security.SecureRandom;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
41+
42+
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
43+
import static org.junit.Assert.assertEquals;
44+
import static org.junit.Assume.assumeTrue;
45+
46+
public class ClientSideEncryptionBypassAutoEncryptionTest {
47+
private MongoClient clientEncrypted;
48+
private ClientEncryption clientEncryption;
49+
50+
@Before
51+
public void setUp() throws Throwable {
52+
assumeTrue(serverVersionAtLeast(4, 1));
53+
54+
MongoClient mongoClient = Fixture.getMongoClient();
55+
56+
final byte[] localMasterKey = new byte[96];
57+
new SecureRandom().nextBytes(localMasterKey);
58+
59+
Map<String, Map<String, Object>> kmsProviders = new HashMap<String, Map<String, Object>>() {{
60+
put("local", new HashMap<String, Object>() {{
61+
put("key", localMasterKey);
62+
}});
63+
}};
64+
65+
66+
MongoNamespace keyVaultNamespace = new MongoNamespace(Fixture.getDefaultDatabaseName(), "testKeyVault");
67+
68+
Fixture.dropDatabase(Fixture.getDefaultDatabaseName());
69+
70+
ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder()
71+
.keyVaultMongoClientSettings(Fixture.getMongoClientSettings())
72+
.keyVaultNamespace(keyVaultNamespace.getFullName())
73+
.kmsProviders(kmsProviders)
74+
.build();
75+
76+
clientEncryption = ClientEncryptions.create(clientEncryptionSettings);
77+
78+
AutoEncryptionSettings autoEncryptionSettings = AutoEncryptionSettings.builder()
79+
.keyVaultNamespace(keyVaultNamespace.getFullName())
80+
.kmsProviders(kmsProviders)
81+
.bypassAutoEncryption(true)
82+
.build();
83+
84+
MongoClientSettings clientSettings = Fixture.getMongoClientSettingsBuilder()
85+
.autoEncryptionSettings(autoEncryptionSettings)
86+
.build();
87+
clientEncrypted = MongoClients.create(clientSettings);
88+
}
89+
90+
@Test
91+
public void shouldAutoDecryptManuallyEncryptedData() {
92+
String fieldValue = "123456789";
93+
94+
ObservableSubscriber<BsonBinary> binarySubscriber = new OperationSubscriber<>();
95+
clientEncryption.createDataKey("local", new DataKeyOptions()).subscribe(binarySubscriber);
96+
BsonBinary dataKeyId = binarySubscriber.get().get(0);
97+
98+
binarySubscriber = new OperationSubscriber<>();
99+
clientEncryption.encrypt(new BsonString(fieldValue),
100+
new EncryptOptions("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic").keyId(dataKeyId))
101+
.subscribe(binarySubscriber);
102+
BsonBinary encryptedFieldValue = binarySubscriber.get().get(0);
103+
104+
MongoCollection<Document> collection = clientEncrypted.getDatabase(Fixture.getDefaultDatabaseName()).getCollection("test");
105+
106+
ObservableSubscriber<InsertOneResult> insertSubscriber = new OperationSubscriber<>();
107+
collection.insertOne(new Document("encryptedField", encryptedFieldValue)).subscribe(insertSubscriber);
108+
insertSubscriber.await();
109+
110+
ObservableSubscriber<Document> resultSubscriber = new OperationSubscriber<>();
111+
collection.find().first().subscribe(resultSubscriber);
112+
113+
assertEquals(fieldValue, resultSubscriber.get().get(0).getString("encryptedField"));
114+
}
115+
116+
@After
117+
public void after() throws Throwable {
118+
if (clientEncrypted != null) {
119+
Fixture.dropDatabase(Fixture.getDefaultDatabaseName());
120+
clientEncrypted.close();
121+
}
122+
}
123+
}

driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/Fixture.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.mongodb.reactivestreams.client;
1818

1919
import com.mongodb.ClusterFixture;
20+
import com.mongodb.MongoClientSettings;
2021
import com.mongodb.MongoCommandException;
2122
import com.mongodb.MongoNamespace;
2223
import com.mongodb.MongoTimeoutException;
@@ -48,14 +49,22 @@ private Fixture() {
4849

4950
public static synchronized MongoClient getMongoClient() {
5051
if (mongoClient == null) {
51-
mongoClient = MongoClients.create(ClusterFixture.getConnectionString());
52+
mongoClient = MongoClients.create(getMongoClientSettings());
5253
serverVersion = getServerVersion();
5354
clusterType = getClusterType();
5455
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
5556
}
5657
return mongoClient;
5758
}
5859

60+
public static MongoClientSettings getMongoClientSettings() {
61+
return getMongoClientSettingsBuilder().build();
62+
}
63+
64+
public static MongoClientSettings.Builder getMongoClientSettingsBuilder() {
65+
return MongoClientSettings.builder().applyConnectionString(ClusterFixture.getConnectionString());
66+
}
67+
5968
public static String getDefaultDatabaseName() {
6069
return ClusterFixture.getDefaultDatabaseName();
6170
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.mongodb.scala
18+
19+
import java.security.SecureRandom
20+
21+
import org.mongodb.scala.MongoClient.DEFAULT_CODEC_REGISTRY
22+
import org.mongodb.scala.bson.BsonString
23+
import org.mongodb.scala.model.vault.{ DataKeyOptions, EncryptOptions }
24+
import org.mongodb.scala.vault.ClientEncryptions
25+
26+
import scala.collection.JavaConverters._
27+
28+
class ClientSideEncryptionBypassAutoEncryptionSpec extends RequiresMongoDBISpec with FuturesSpec {
29+
30+
"ClientSideEncryption" should "be able to bypass auto encryption" in withDatabase { db =>
31+
assume(serverVersionAtLeast(List(4, 1, 0)))
32+
33+
val localMasterKey = new Array[Byte](96)
34+
new SecureRandom().nextBytes(localMasterKey)
35+
36+
val kmsProviders = Map("local" -> Map[String, AnyRef]("key" -> localMasterKey).asJava).asJava
37+
38+
val keyVaultNamespace: MongoNamespace = new MongoNamespace(databaseName, "testKeyVault")
39+
40+
db.drop().futureValue
41+
42+
val clientEncryptionSettings: ClientEncryptionSettings = ClientEncryptionSettings
43+
.builder()
44+
.keyVaultMongoClientSettings(mongoClientSettings)
45+
.keyVaultNamespace(keyVaultNamespace.getFullName)
46+
.kmsProviders(kmsProviders)
47+
.build()
48+
49+
val clientEncryption = ClientEncryptions.create(clientEncryptionSettings)
50+
51+
val autoEncryptionSettings: AutoEncryptionSettings = AutoEncryptionSettings
52+
.builder()
53+
.keyVaultNamespace(keyVaultNamespace.getFullName)
54+
.kmsProviders(kmsProviders)
55+
.bypassAutoEncryption(true)
56+
.build()
57+
58+
val clientSettings: MongoClientSettings = mongoClientSettingsBuilder
59+
.autoEncryptionSettings(autoEncryptionSettings)
60+
.codecRegistry(DEFAULT_CODEC_REGISTRY)
61+
.build
62+
63+
val clientEncrypted = MongoClient(clientSettings)
64+
65+
val fieldValue = BsonString("123456789")
66+
67+
val dataKeyId = clientEncryption.createDataKey("local", DataKeyOptions()).head().futureValue
68+
69+
val encryptedFieldValue = clientEncryption
70+
.encrypt(fieldValue, EncryptOptions("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic").keyId(dataKeyId))
71+
.head()
72+
.futureValue
73+
74+
val collection: MongoCollection[Document] =
75+
clientEncrypted.getDatabase(databaseName).getCollection[Document]("test")
76+
77+
collection.insertOne(Document("encryptedField" -> encryptedFieldValue)).futureValue
78+
79+
val result = collection.find().first().head().futureValue
80+
81+
result.get[BsonString]("encryptedField") should equal(Some(fieldValue))
82+
}
83+
84+
}

0 commit comments

Comments
 (0)