Skip to content
Next Next commit
Add JKS keystore support to X509Authentication
- Add configureX509AuthenticationFromKeystore method for JKS keystore support
- Add createSSLContextFromKeystore helper method
- Support keystore path, password, and certificate alias parameters
- Reuse existing SSL context creation logic
- Add proper error handling for missing certificates/keys

Co-Authored-By: [email protected] <[email protected]>
  • Loading branch information
devin-ai-integration[bot] and nbagnard committed Sep 19, 2025
commit 21121a8e3ebad76b3be062bc7d06182ab05a3fab
85 changes: 85 additions & 0 deletions src/main/java/com/mongodb/jdbc/utils/X509Authentication.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
import com.mongodb.MongoException;
import com.mongodb.jdbc.logging.MongoLogger;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.security.*;
import java.security.cert.Certificate;
import java.security.UnrecoverableKeyException;
import java.util.logging.Level;
import javax.net.ssl.*;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
Expand Down Expand Up @@ -132,6 +134,53 @@ public void configureX509Authentication(
}
}

/**
* Configures X.509 authentication for MongoDB using a JKS keystore containing the private key and
* certificate.
*
* @param settingsBuilder The MongoDB client settings builder to apply the SSL configuration.
* Must not be null.
* @param keystorePath The path to the JKS keystore file containing the private key and certificate.
* Must not be null.
* @param keystorePassword The password for the keystore. Can be null if keystore is not password protected.
* @param certificateAlias The alias of the certificate to use from the keystore. Must not be null.
* @throws Exception If there is an error during configuration, keystore loading, or certificate extraction.
* @throws NullPointerException if settingsBuilder, keystorePath, or certificateAlias are null.
*/
public void configureX509AuthenticationFromKeystore(
com.mongodb.MongoClientSettings.Builder settingsBuilder,
String keystorePath,
char[] keystorePassword,
String certificateAlias)
throws Exception {

if (settingsBuilder == null) {
throw new NullPointerException("settingsBuilder cannot be null");
}
if (keystorePath == null || keystorePath.trim().isEmpty()) {
throw new NullPointerException("keystorePath cannot be null or empty");
}
if (certificateAlias == null || certificateAlias.trim().isEmpty()) {
throw new NullPointerException("certificateAlias cannot be null or empty");
}

logger.log(Level.FINE, "Using JKS keystore for X509 authentication: " + keystorePath);
logger.log(Level.FINE, "Certificate alias: " + certificateAlias);

try {
SSLContext sslContext = createSSLContextFromKeystore(keystorePath, keystorePassword, certificateAlias);

settingsBuilder.applyToSslSettings(
sslSettings -> {
sslSettings.enabled(true);
sslSettings.context(sslContext);
});
} catch (Exception e) {
logger.log(Level.SEVERE, "SSL setup failed: " + e.getMessage());
throw e;
}
}

/**
* Formats a PEM string to handle escaped newlines and ensures correct header placement. Adds
* required newlines for compatibility with Bouncy Castle PEMParser.
Expand Down Expand Up @@ -381,6 +430,42 @@ private SSLContext createSSLContextFromKeyAndCert(PrivateKey privateKey, Certifi
return sslContext;
}

private SSLContext createSSLContextFromKeystore(String keystorePath, char[] keystorePassword, String certificateAlias) throws Exception {
KeyStore keystore = KeyStore.getInstance("JKS");

try (FileInputStream keystoreStream = new FileInputStream(keystorePath)) {
keystore.load(keystoreStream, keystorePassword);
logger.log(Level.FINE, "Successfully loaded JKS keystore from: " + keystorePath);
} catch (IOException e) {
throw new MongoException("Failed to read keystore file: " + e.getMessage(), e);
} catch (Exception e) {
throw new MongoException("Failed to load keystore: " + e.getMessage(), e);
}

Certificate cert = keystore.getCertificate(certificateAlias);
if (cert == null) {
throw new MongoException("Certificate with alias '" + certificateAlias + "' not found in keystore");
}
logger.log(Level.FINE, "Found certificate with alias: " + certificateAlias);

PrivateKey privateKey;
try {
Key key = keystore.getKey(certificateAlias, keystorePassword);
if (key == null) {
throw new MongoException("Private key with alias '" + certificateAlias + "' not found in keystore");
}
if (!(key instanceof PrivateKey)) {
throw new MongoException("Key with alias '" + certificateAlias + "' is not a private key");
}
privateKey = (PrivateKey) key;
logger.log(Level.FINE, "Successfully extracted private key with alias: " + certificateAlias);
} catch (UnrecoverableKeyException e) {
throw new MongoException("Failed to extract private key with alias '" + certificateAlias + "': " + e.getMessage(), e);
}

return createSSLContextFromKeyAndCert(privateKey, cert);
}

private PemAuthenticationInput parsePemAuthenticationInput(String input) {
try {
BsonDocument doc = BsonDocument.parse(input);
Expand Down