diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/DatabaseClientPropertySource.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/DatabaseClientPropertySource.java index eb5083045..112bb304a 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/DatabaseClientPropertySource.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/DatabaseClientPropertySource.java @@ -290,8 +290,9 @@ private SSLInputs buildSSLInputs(String authType) { useNewSSLContext(sslProtocol, userTrustManager); } - // Approach 4 - no SSL connection is needed. - return new SSLInputs(null, null); + // Approach 4 - still return the user-defined TrustManager as that may be needed for certificate authentication, + // which has its own way of constructing an SSLContext from a PKCS12 file. + return new SSLInputs(null, userTrustManager); } private X509TrustManager getTrustManager() { diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/ssl/TwoWaySSLTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/ssl/TwoWaySSLTest.java index ca1870cc6..1f9cedd45 100644 --- a/marklogic-client-api/src/test/java/com/marklogic/client/test/ssl/TwoWaySSLTest.java +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/ssl/TwoWaySSLTest.java @@ -44,6 +44,7 @@ public class TwoWaySSLTest { private final static String TEST_DOCUMENT_URI = "/optic/test/musician1.json"; + private final static String KEYSTORE_PASSWORD = "password"; // Used for creating a temporary JKS (Java KeyStore) file. @TempDir @@ -52,6 +53,7 @@ public class TwoWaySSLTest { private static DatabaseClient securityClient; private static ManageClient manageClient; private static File keyStoreFile; + private static File p12File; @BeforeAll @@ -77,6 +79,7 @@ public static void setup() throws Exception { createPkcs12File(tempDir); createKeystoreFile(tempDir); keyStoreFile = new File(tempDir.toFile(), "client.jks"); + p12File = new File(tempDir.toFile(), "client.p12"); } @AfterAll @@ -126,16 +129,17 @@ void digestAuthentication() throws Exception { "Unexpected exception: " + userException.getMessage()); } + /** + * Verifies certificate authentication when a user provides their own SSLContext. + */ @Test - void certificateAuthentication() throws Exception { + void certificateAuthenticationWithSSLContext() throws Exception { if (Common.USE_REVERSE_PROXY_SERVER) { return; } + setAuthenticationToCertificate(); try { - new ServerManager(manageClient) - .save(Common.newServerPayload().put("authentication", "certificate").toString()); - SSLContext sslContext = createSSLContextWithClientCertificate(keyStoreFile); DatabaseClient client = Common.newClientBuilder() .withCertificateAuth(sslContext, RequireSSLExtension.newTrustManager()) @@ -144,11 +148,44 @@ void certificateAuthentication() throws Exception { verifyTestDocumentCanBeRead(client); } finally { - new ServerManager(manageClient) - .save(Common.newServerPayload().put("authentication", "digestbasic").toString()); + setAuthenticationToDigestbasic(); + } + } + + /** + * Verifies certificate authentication when a user provides a file and password, which must point to a PKC12 + * keystore. + */ + @Test + void certificateAuthenticationWithCertificateFileAndPassword() { + if (Common.USE_REVERSE_PROXY_SERVER) { + return; + } + + setAuthenticationToCertificate(); + try { + DatabaseClient client = Common.newClientBuilder() + .withCertificateAuth(p12File.getAbsolutePath(), KEYSTORE_PASSWORD) + .withTrustManager(RequireSSLExtension.newTrustManager()) + .withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY) + .build(); + + verifyTestDocumentCanBeRead(client); + } finally { + setAuthenticationToDigestbasic(); } } + private void setAuthenticationToCertificate() { + new ServerManager(manageClient) + .save(Common.newServerPayload().put("authentication", "certificate").toString()); + } + + private void setAuthenticationToDigestbasic() { + new ServerManager(manageClient) + .save(Common.newServerPayload().put("authentication", "digestbasic").toString()); + } + private void verifyTestDocumentCanBeRead(DatabaseClient client) { DocumentDescriptor descriptor = client.newJSONDocumentManager().exists(TEST_DOCUMENT_URI); assertNotNull(descriptor); @@ -157,9 +194,9 @@ private void verifyTestDocumentCanBeRead(DatabaseClient client) { private SSLContext createSSLContextWithClientCertificate(File keystoreFile) throws Exception { KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(new FileInputStream(keystoreFile), "password".toCharArray()); + keyStore.load(new FileInputStream(keystoreFile), KEYSTORE_PASSWORD.toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); - keyManagerFactory.init(keyStore, "password".toCharArray()); + keyManagerFactory.init(keyStore, KEYSTORE_PASSWORD.toCharArray()); SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init( keyManagerFactory.getKeyManagers(), @@ -316,7 +353,7 @@ private static void createPkcs12File(Path tempDir) throws Exception { "-in", "cert.pem", "-inkey", "client.key", "-out", "client.p12", "-name", "my-client", - "-passout", "pass:password"); + "-passout", "pass:" + KEYSTORE_PASSWORD); ExecutorService executorService = Executors.newSingleThreadExecutor(); Process process = builder.start(); @@ -330,12 +367,12 @@ private static void createKeystoreFile(Path tempDir) throws Exception { ProcessBuilder builder = new ProcessBuilder(); builder.directory(tempDir.toFile()); builder.command("keytool", "-importkeystore", - "-deststorepass", "password", - "-destkeypass", "password", + "-deststorepass", KEYSTORE_PASSWORD, + "-destkeypass", KEYSTORE_PASSWORD, "-destkeystore", "client.jks", "-srckeystore", "client.p12", "-srcstoretype", "PKCS12", - "-srcstorepass", "password", + "-srcstorepass", KEYSTORE_PASSWORD, "-alias", "my-client"); Process process = builder.start();