From 219edf756b4427daab097318daba3d42ed23143c Mon Sep 17 00:00:00 2001 From: Phil Barber Date: Tue, 1 Apr 2025 20:46:43 -0400 Subject: [PATCH] Can now specify SSL Protocol when creating a CertificateAuthContext. --- .../client/DatabaseClientFactory.java | 88 ++++++++++++++++--- .../impl/DatabaseClientPropertySource.java | 13 +-- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientFactory.java b/marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientFactory.java index f6ed16e99..b6b36024a 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientFactory.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientFactory.java @@ -913,8 +913,9 @@ public Map toOptions() { } public static class CertificateAuthContext extends AuthContext { - String certFile; - String certPassword; + private static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2"; + String certFile; + String certPassword; /** * Creates a CertificateAuthContext by initializing the SSLContext of the @@ -973,13 +974,39 @@ public CertificateAuthContext(SSLContext context, SSLHostnameVerifier verifier, * UnrecoverableKeyException. */ public CertificateAuthContext(String certFile, X509TrustManager trustManager) - throws CertificateException, IOException, - UnrecoverableKeyException, KeyManagementException { - this.certFile = certFile; - this.trustManager = trustManager; - this.certPassword = ""; - this.sslContext = createSSLContext(); - } + throws CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { + this(certFile, "", trustManager, null); + } + + /** + * Creates a CertificateAuthContext with a PKCS12 file. The SSLContext is + * created from the information in the PKCS12 file. This constructor should + * be called when the export password of the PKCS12 file is empty. + * + * @param certFile the p12 file which contains the client's private key and + * the client's certificate chain + * @param trustManager the X509TrustManager object which is responsible for + * deciding if a credential should be trusted or not. + * @param sslProtocol the version of the SSL protocol to use. + * @throws CertificateException if any of the certificates in the certFile + * cannot be loaded + * @throws UnrecoverableKeyException if the certFile has an export password + * @throws KeyManagementException if initializing the SSLContext with the + * KeyManager fails + * @throws IOException if there is an I/O or format problem with the + * keystore data, if a password is required but not given, or if + * the given password was incorrect or if the certFile path is + * invalid or if the file is not found If the error is due to a + * wrong password, the cause of the IOException should be an + * UnrecoverableKeyException. + */ + public CertificateAuthContext(String certFile, X509TrustManager trustManager, String sslProtocol) + throws CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { + this.certFile = certFile; + this.trustManager = trustManager; + this.certPassword = ""; + this.sslContext = createSSLContext(sslProtocol); + } /** * Creates a CertificateAuthContext with a PKCS12 file. The SSLContext @@ -1000,15 +1027,46 @@ public CertificateAuthContext(String certFile, X509TrustManager trustManager) * should be an UnrecoverableKeyException. */ public CertificateAuthContext(String certFile, String certPassword, X509TrustManager trustManager) - throws CertificateException, IOException, - UnrecoverableKeyException, KeyManagementException { + throws CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { this.certFile = certFile; this.certPassword = certPassword; this.trustManager = trustManager; this.sslContext = createSSLContext(); - } + } + + /** + * Creates a CertificateAuthContext with a PKCS12 file. The SSLContext + * is created from the information in the PKCS12 file. This constructor + * should be called when the export password of the PKCS12 file is non-empty. + * @param certFile the p12 file which contains the client's private key + * and the client's certificate chain + * @param trustManager the X509TrustManager object which is responsible for + * deciding if a credential should be trusted or not. + * @param certPassword the export password of the p12 file + * @param sslProtocol the version of the SSL protocol to use. + * @throws CertificateException if any of the certificates in the certFile cannot be loaded + * @throws UnrecoverableKeyException if the certFile has an export password + * @throws KeyManagementException if initializing the SSLContext with the KeyManager fails + * @throws IOException if there is an I/O or format problem with the keystore data, + * if a password is required but not given, or if the given password was + * incorrect or if the certFile path is invalid or if the file is not found + * If the error is due to a wrong password, the cause of the IOException + * should be an UnrecoverableKeyException. + */ + public CertificateAuthContext(String certFile, String certPassword, X509TrustManager trustManager, String sslProtocol) + throws CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { + this.certFile = certFile; + this.certPassword = certPassword; + this.trustManager = trustManager; + this.sslContext = createSSLContext(sslProtocol); + } private SSLContext createSSLContext() + throws UnrecoverableKeyException, CertificateException, IOException, KeyManagementException { + return createSSLContext(DEFAULT_SSL_PROTOCOL); + } + + private SSLContext createSSLContext(String sslProtocol) throws CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { if(certPassword == null) { throw new IllegalArgumentException("Certificate export password must not be null"); @@ -1037,7 +1095,11 @@ private SSLContext createSSLContext() } keyManagerFactory.init(keyStore, certPassword.toCharArray()); keyMgr = keyManagerFactory.getKeyManagers(); - sslContext = SSLContext.getInstance("TLSv1.2"); + if (sslProtocol == null) { + sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); + } else { + sslContext = SSLContext.getInstance(sslProtocol); + } } catch (NoSuchAlgorithmException | KeyStoreException e) { throw new IllegalStateException("The certificate algorithm used or the Key store " + "Service provider Implementaion (SPI) is invalid. CertificateAuthContext " 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 c0e8b09bb..f84c57304 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 @@ -159,7 +159,8 @@ private DatabaseClientFactory.SecurityContext newSecurityContext() { final String authType = determineAuthType(connectionString); final SSLUtil.SSLInputs sslInputs = buildSSLInputs(authType); - DatabaseClientFactory.SecurityContext securityContext = newSecurityContext(authType, connectionString, sslInputs); + String sslProcotolForCertificateAuth = getSSLProtocol(authType); + DatabaseClientFactory.SecurityContext securityContext = newSecurityContext(authType, connectionString, sslInputs, sslProcotolForCertificateAuth); if (sslInputs.getSslContext() != null) { securityContext.withSSLContext(sslInputs.getSslContext(), sslInputs.getTrustManager()); } @@ -178,7 +179,7 @@ private String determineAuthType(ConnectionString connectionString) { return (String) value; } - private DatabaseClientFactory.SecurityContext newSecurityContext(String type, ConnectionString connectionString, SSLUtil.SSLInputs sslInputs) { + private DatabaseClientFactory.SecurityContext newSecurityContext(String type, ConnectionString connectionString, SSLUtil.SSLInputs sslInputs, String sslProtocol) { switch (type.toLowerCase()) { case DatabaseClientBuilder.AUTH_TYPE_BASIC: return newBasicAuthContext(connectionString); @@ -189,7 +190,7 @@ private DatabaseClientFactory.SecurityContext newSecurityContext(String type, Co case DatabaseClientBuilder.AUTH_TYPE_KERBEROS: return newKerberosAuthContext(); case DatabaseClientBuilder.AUTH_TYPE_CERTIFICATE: - return newCertificateAuthContext(sslInputs); + return newCertificateAuthContext(sslInputs, sslProtocol); case DatabaseClientBuilder.AUTH_TYPE_SAML: return newSAMLAuthContext(); case DatabaseClientBuilder.AUTH_TYPE_OAUTH: @@ -261,15 +262,15 @@ private DatabaseClientFactory.SecurityContext newCloudAuthContext() { return new DatabaseClientFactory.MarkLogicCloudAuthContext(apiKey, duration); } - private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLUtil.SSLInputs sslInputs) { + private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLUtil.SSLInputs sslInputs, String sslProtocol) { String file = getNullableStringValue("certificate.file"); String password = getNullableStringValue("certificate.password"); if (file != null && file.trim().length() > 0) { try { if (password != null && password.trim().length() > 0) { - return new DatabaseClientFactory.CertificateAuthContext(file, password, sslInputs.getTrustManager()); + return new DatabaseClientFactory.CertificateAuthContext(file, password, sslInputs.getTrustManager(), sslProtocol); } - return new DatabaseClientFactory.CertificateAuthContext(file, sslInputs.getTrustManager()); + return new DatabaseClientFactory.CertificateAuthContext(file, sslInputs.getTrustManager(), sslProtocol); } catch (Exception e) { throw new RuntimeException("Unable to create CertificateAuthContext; cause " + e.getMessage(), e); }