|
20 | 20 | import com.mongodb.MongoClientException; |
21 | 21 | import com.mongodb.client.model.vault.DataKeyOptions; |
22 | 22 | import com.mongodb.client.vault.ClientEncryption; |
| 23 | +import com.mongodb.fixture.EncryptionFixture; |
23 | 24 | import com.mongodb.lang.NonNull; |
24 | 25 | import com.mongodb.lang.Nullable; |
25 | 26 | import org.bson.BsonDocument; |
26 | 27 | import org.junit.jupiter.api.Test; |
27 | 28 |
|
28 | 29 | import javax.net.ssl.SSLContext; |
| 30 | +import javax.net.ssl.SSLServerSocket; |
| 31 | +import javax.net.ssl.SSLServerSocketFactory; |
29 | 32 | import javax.net.ssl.TrustManager; |
30 | 33 | import javax.net.ssl.X509TrustManager; |
| 34 | +import java.io.IOException; |
| 35 | +import java.net.Socket; |
31 | 36 | import java.security.KeyManagementException; |
32 | 37 | import java.security.NoSuchAlgorithmException; |
33 | 38 | import java.security.cert.CertificateException; |
34 | 39 | import java.security.cert.CertificateExpiredException; |
35 | 40 | import java.security.cert.X509Certificate; |
36 | 41 | import java.util.HashMap; |
37 | 42 | import java.util.Map; |
| 43 | +import java.util.concurrent.CompletableFuture; |
| 44 | +import java.util.concurrent.TimeUnit; |
38 | 45 |
|
39 | 46 | import static com.mongodb.ClusterFixture.getEnv; |
40 | 47 | import static com.mongodb.ClusterFixture.hasEncryptionTestsEnabled; |
41 | 48 | import static com.mongodb.client.Fixture.getMongoClientSettings; |
42 | 49 | import static java.util.Objects.requireNonNull; |
| 50 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
43 | 51 | import static org.junit.jupiter.api.Assertions.assertNotNull; |
44 | 52 | import static org.junit.jupiter.api.Assertions.assertThrows; |
45 | 53 | import static org.junit.jupiter.api.Assertions.assertTrue; |
46 | 54 | import static org.junit.jupiter.api.Assertions.fail; |
47 | 55 | import static org.junit.jupiter.api.Assumptions.assumeTrue; |
| 56 | + |
48 | 57 | public abstract class AbstractClientSideEncryptionKmsTlsTest { |
49 | 58 |
|
50 | 59 | private static final String SYSTEM_PROPERTY_KEY = "org.mongodb.test.kms.tls.error.type"; |
@@ -128,7 +137,7 @@ public void testInvalidKmsCertificate() { |
128 | 137 | * See <a href="https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#11-kms-tls-options-tests"> |
129 | 138 | * 11. KMS TLS Options Tests</a>. |
130 | 139 | */ |
131 | | - @Test() |
| 140 | + @Test |
132 | 141 | public void testThatCustomSslContextIsUsed() { |
133 | 142 | assumeTrue(hasEncryptionTestsEnabled()); |
134 | 143 |
|
@@ -165,34 +174,71 @@ public void testThatCustomSslContextIsUsed() { |
165 | 174 | } |
166 | 175 | } |
167 | 176 |
|
| 177 | + /** |
| 178 | + * Not a prose spec test. However, it is additional test case for better coverage. |
| 179 | + */ |
| 180 | + @Test |
| 181 | + public void testUnexpectedEndOfStreamFromKmsProvider() throws Exception { |
| 182 | + String kmsKeystoreLocation = System.getProperty("org.mongodb.test.kms.keystore.location"); |
| 183 | + assumeTrue(kmsKeystoreLocation != null && !kmsKeystoreLocation.isEmpty(), |
| 184 | + "System property org.mongodb.test.kms.keystore.location is not set"); |
| 185 | + |
| 186 | + int kmsPort = 5555; |
| 187 | + ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() |
| 188 | + .keyVaultMongoClientSettings(getMongoClientSettings()) |
| 189 | + .keyVaultNamespace("keyvault.datakeys") |
| 190 | + .kmsProviders(new HashMap<String, Map<String, Object>>() {{ |
| 191 | + put("kmip", new HashMap<String, Object>() {{ |
| 192 | + put("endpoint", "localhost:" + kmsPort); |
| 193 | + }}); |
| 194 | + }}) |
| 195 | + .build(); |
| 196 | + |
| 197 | + Thread serverThread = null; |
| 198 | + try (ClientEncryption clientEncryption = getClientEncryption(clientEncryptionSettings)) { |
| 199 | + serverThread = startKmsServerSimulatingEof(EncryptionFixture.buildSslContextFromKeyStore( |
| 200 | + kmsKeystoreLocation, |
| 201 | + "server.p12"), kmsPort); |
| 202 | + |
| 203 | + MongoClientException mongoException = assertThrows(MongoClientException.class, |
| 204 | + () -> clientEncryption.createDataKey("kmip", new DataKeyOptions())); |
| 205 | + assertEquals("Exception in encryption library: Unexpected end of stream from KMS provider kmip", |
| 206 | + mongoException.getMessage()); |
| 207 | + } finally { |
| 208 | + if (serverThread != null) { |
| 209 | + serverThread.interrupt(); |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | + |
168 | 214 | private HashMap<String, Map<String, Object>> getKmsProviders() { |
169 | 215 | return new HashMap<String, Map<String, Object>>() {{ |
170 | | - put("aws", new HashMap<String, Object>() {{ |
| 216 | + put("aws", new HashMap<String, Object>() {{ |
171 | 217 | put("accessKeyId", getEnv("AWS_ACCESS_KEY_ID")); |
172 | 218 | put("secretAccessKey", getEnv("AWS_SECRET_ACCESS_KEY")); |
173 | 219 | }}); |
174 | | - put("aws:named", new HashMap<String, Object>() {{ |
| 220 | + put("aws:named", new HashMap<String, Object>() {{ |
175 | 221 | put("accessKeyId", getEnv("AWS_ACCESS_KEY_ID")); |
176 | 222 | put("secretAccessKey", getEnv("AWS_SECRET_ACCESS_KEY")); |
177 | 223 | }}); |
178 | | - put("azure", new HashMap<String, Object>() {{ |
| 224 | + put("azure", new HashMap<String, Object>() {{ |
179 | 225 | put("tenantId", getEnv("AZURE_TENANT_ID")); |
180 | 226 | put("clientId", getEnv("AZURE_CLIENT_ID")); |
181 | 227 | put("clientSecret", getEnv("AZURE_CLIENT_SECRET")); |
182 | 228 | put("identityPlatformEndpoint", "login.microsoftonline.com:443"); |
183 | 229 | }}); |
184 | | - put("azure:named", new HashMap<String, Object>() {{ |
| 230 | + put("azure:named", new HashMap<String, Object>() {{ |
185 | 231 | put("tenantId", getEnv("AZURE_TENANT_ID")); |
186 | 232 | put("clientId", getEnv("AZURE_CLIENT_ID")); |
187 | 233 | put("clientSecret", getEnv("AZURE_CLIENT_SECRET")); |
188 | 234 | put("identityPlatformEndpoint", "login.microsoftonline.com:443"); |
189 | 235 | }}); |
190 | | - put("gcp", new HashMap<String, Object>() {{ |
| 236 | + put("gcp", new HashMap<String, Object>() {{ |
191 | 237 | put("email", getEnv("GCP_EMAIL")); |
192 | 238 | put("privateKey", getEnv("GCP_PRIVATE_KEY")); |
193 | 239 | put("endpoint", "oauth2.googleapis.com:443"); |
194 | 240 | }}); |
195 | | - put("gcp:named", new HashMap<String, Object>() {{ |
| 241 | + put("gcp:named", new HashMap<String, Object>() {{ |
196 | 242 | put("email", getEnv("GCP_EMAIL")); |
197 | 243 | put("privateKey", getEnv("GCP_PRIVATE_KEY")); |
198 | 244 | put("endpoint", "oauth2.googleapis.com:443"); |
@@ -257,5 +303,29 @@ public void checkServerTrusted(final X509Certificate[] certs, final String authT |
257 | 303 | throw new RuntimeException(e); |
258 | 304 | } |
259 | 305 | } |
| 306 | + |
| 307 | + private Thread startKmsServerSimulatingEof(final SSLContext sslContext, final int kmsPort) |
| 308 | + throws Exception { |
| 309 | + CompletableFuture<Void> confirmListening = new CompletableFuture<>(); |
| 310 | + Thread serverThread = new Thread(() -> { |
| 311 | + try { |
| 312 | + SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); |
| 313 | + try (SSLServerSocket sslServerSocket = (SSLServerSocket) serverSocketFactory.createServerSocket(kmsPort)) { |
| 314 | + sslServerSocket.setNeedClientAuth(false); |
| 315 | + confirmListening.complete(null); |
| 316 | + try (Socket accept = sslServerSocket.accept()) { |
| 317 | + accept.setSoTimeout(10000); |
| 318 | + accept.getInputStream().read(); |
| 319 | + } |
| 320 | + } |
| 321 | + } catch (IOException e) { |
| 322 | + throw new RuntimeException(e); |
| 323 | + } |
| 324 | + }, "KMIP-EOF-Fake-Server"); |
| 325 | + serverThread.setDaemon(true); |
| 326 | + serverThread.start(); |
| 327 | + confirmListening.get(TimeUnit.SECONDS.toMillis(10), TimeUnit.MILLISECONDS); |
| 328 | + return serverThread; |
| 329 | + } |
260 | 330 | } |
261 | 331 |
|
0 commit comments