From 08a3670ce5ae678a9c619616c68d2816f5ee51dc Mon Sep 17 00:00:00 2001
From: "eugene.livis" <elivis@basistech.com>
Date: Wed, 18 Oct 2023 10:23:20 -0400
Subject: [PATCH] Changes to support SSL connection to Postgres server

---
 .../datamodel/CaseDatabaseFactory.java        |  6 ++
 .../datamodel/CaseDbConnectionInfo.java       | 40 +++++++++-
 .../sleuthkit/datamodel/SleuthkitCase.java    | 76 +++++++++++--------
 3 files changed, 91 insertions(+), 31 deletions(-)

diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
index f1dc2cfbe..da21a76cf 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
@@ -713,6 +713,12 @@ Connection getConnection(String databaseName) throws TskCoreException {
 				.append('/') // NON-NLS
 				.append(encodedDbName);
 			
+			if (info.isSslEnabled()) {
+				// ssl=true: enables SSL encryption. 
+				// NonValidatingFactory avoids hostname verification.
+				url.append("?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory");
+			}
+			
 			Connection conn;
 			try {
 				Properties props = new Properties();
diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDbConnectionInfo.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDbConnectionInfo.java
index fd4cb12c5..2fc9d3ec4 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CaseDbConnectionInfo.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDbConnectionInfo.java
@@ -34,6 +34,7 @@ public class CaseDbConnectionInfo {
 	private String userName;
 	private String password;
 	private DbType dbType;
+	private boolean sslEnabled = false;
 
 	/**
 	 * The intent of this class is to hold any information needed to connect to
@@ -54,12 +55,41 @@ public CaseDbConnectionInfo(String hostNameOrIP, String portNumber, String userN
 		this.portNumber = portNumber;
 		this.userName = userName;
 		this.password = password;
+		this.sslEnabled = false;
 		if (dbType == DbType.SQLITE) {
 			throw new IllegalArgumentException("SQLite database type invalid for CaseDbConnectionInfo. CaseDbConnectionInfo should be used only for remote database types.");
 		}
 		this.dbType = dbType;
 	}
-
+	 
+	/**
+	 * The intent of this class is to hold any information needed to connect to
+	 * a remote database server, except for the actual database name. This constructor 
+	 * allows user to specify whether to use SSL to connect to database. This does
+	 * not hold information to connect to a local database such as SQLite.
+	 *
+	 * It can be used generically to hold remote database connection
+	 * information.
+	 *
+	 * @param hostNameOrIP the host name
+	 * @param portNumber   the port number
+	 * @param userName     the user name
+	 * @param password     the password
+	 * @param dbType       the database type
+	 * @param sslEnabled   a flag whether SSL is enabled
+	 */
+	public CaseDbConnectionInfo(String hostNameOrIP, String portNumber, String userName, String password, DbType dbType, boolean sslEnabled) {
+		this.hostNameOrIP = hostNameOrIP;
+		this.portNumber = portNumber;
+		this.userName = userName;
+		this.password = password;
+		this.sslEnabled = sslEnabled;
+		if (dbType == DbType.SQLITE) {
+			throw new IllegalArgumentException("SQLite database type invalid for CaseDbConnectionInfo. CaseDbConnectionInfo should be used only for remote database types.");
+		}
+		this.dbType = dbType;
+	}
+	
 	public DbType getDbType() {
 		return this.dbType;
 	}
@@ -99,4 +129,12 @@ public void setUserName(String user) {
 	public void setPassword(String pass) {
 		this.password = pass;
 	}
+
+	public boolean isSslEnabled() {
+		return sslEnabled;
+	}
+
+	public void setSslEnabled(boolean sslEnabled) {
+		this.sslEnabled = sslEnabled;
+	}
 }
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index aec77700f..78484e88f 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -112,6 +112,7 @@ public class SleuthkitCase {
 	private static final ResourceBundle bundle = ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle");
 	private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
 	private static final String SQL_ERROR_CONNECTION_GROUP = "08";
+	private static final String SQL_CONNECTION_REJECTED = "08006";
 	private static final String SQL_ERROR_AUTHENTICATION_GROUP = "28";
 	private static final String SQL_ERROR_PRIVILEGE_GROUP = "42";
 	private static final String SQL_ERROR_RESOURCE_GROUP = "53";
@@ -289,7 +290,13 @@ public static void tryConnect(CaseDbConnectionInfo info) throws TskCoreException
 
 		try {
 			Class.forName("org.postgresql.Driver"); //NON-NLS
-			Connection conn = DriverManager.getConnection("jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres", info.getUserName(), info.getPassword()); //NON-NLS
+			String connectionURL = "jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres";
+			if (info.isSslEnabled()) {
+				// ssl=true: enables SSL encryption. 
+				// NonValidatingFactory avoids hostname verification.
+				connectionURL += "?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory";
+			}
+			Connection conn = DriverManager.getConnection(connectionURL, info.getUserName(), info.getPassword()); //NON-NLS
 			if (conn != null) {
 				conn.close();
 			}
@@ -297,16 +304,25 @@ public static void tryConnect(CaseDbConnectionInfo info) throws TskCoreException
 			String result;
 			String sqlState = ex.getSQLState().toLowerCase();
 			if (sqlState.startsWith(SQL_ERROR_CONNECTION_GROUP)) {
-				try {
-					if (InetAddress.getByName(info.getHost()).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
-						// if we can reach the host, then it's probably port problem
-						result = bundle.getString("DatabaseConnectionCheck.Port"); //NON-NLS
+				
+				if (SQL_CONNECTION_REJECTED.equals(ex.getSQLState())) {
+					if (info.isSslEnabled()) {
+						result = "Server rejected the SSL connection attempt. Check SSL configuration.";
 					} else {
-						result = bundle.getString("DatabaseConnectionCheck.HostnameOrPort"); //NON-NLS
+						result = "Server rejected the connection attempt. Check server configuration.";
+					}					
+				} else {
+					try {
+						if (InetAddress.getByName(info.getHost()).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
+							// if we can reach the host, then it's probably port problem
+							result = bundle.getString("DatabaseConnectionCheck.Port"); //NON-NLS
+						} else {
+							result = bundle.getString("DatabaseConnectionCheck.HostnameOrPort"); //NON-NLS
+						}
+					} catch (IOException | MissingResourceException any) {
+						// it may be anything
+						result = bundle.getString("DatabaseConnectionCheck.Everything"); //NON-NLS
 					}
-				} catch (IOException | MissingResourceException any) {
-					// it may be anything
-					result = bundle.getString("DatabaseConnectionCheck.Everything"); //NON-NLS
 				}
 			} else if (sqlState.startsWith(SQL_ERROR_AUTHENTICATION_GROUP)) {
 				result = bundle.getString("DatabaseConnectionCheck.Authentication"); //NON-NLS
@@ -357,27 +373,20 @@ private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbTyp
 	/**
 	 * Private constructor, clients must use newCase() or openCase() method to
 	 * create an instance of this class.
-	 *
-	 * @param host        The PostgreSQL database server.
-	 * @param port        The port to use connect to the PostgreSQL database
-	 *                    server.
+	 * 
+	 * @param info		  CaseDbConnectionInfo object with database connection info
 	 * @param dbName      The name of the case database.
-	 * @param userName    The user name to use to connect to the case database.
-	 * @param password    The password to use to connect to the case database.
 	 * @param caseHandle  A handle to a case database object in the native code
-	 * @param dbType      The type of database we're dealing with SleuthKit
-	 *                    layer.
 	 * @param caseDirPath The path to the root case directory.
 	 * @param contentProvider Custom provider for file content (can be null).
-	 *
-	 * @throws Exception
+	 * @throws Exception 
 	 */
-	private SleuthkitCase(String host, int port, String dbName, String userName, String password, SleuthkitJNI.CaseDbHandle caseHandle, String caseDirPath, DbType dbType, ContentStreamProvider contentProvider) throws Exception {
+	private SleuthkitCase(CaseDbConnectionInfo info, String dbName, SleuthkitJNI.CaseDbHandle caseHandle, String caseDirPath, ContentStreamProvider contentProvider) throws Exception {
 		this.dbPath = "";
 		this.databaseName = dbName;
-		this.dbType = dbType;
+		this.dbType = info.getDbType();
 		this.caseDirPath = caseDirPath;
-		this.connections = new PostgreSQLConnections(host, port, dbName, userName, password);
+		this.connections = new PostgreSQLConnections(info, dbName);
 		this.caseHandle = caseHandle;
 		this.caseHandleIdentifier = caseHandle.getCaseDbIdentifier();
 		this.contentProvider = contentProvider;
@@ -3057,7 +3066,7 @@ public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo i
 			 * are able, but do not lose any information if unable.
 			 */
 			final SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(databaseName, info);
-			return new SleuthkitCase(info.getHost(), Integer.parseInt(info.getPort()), databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDir, info.getDbType(), contentProvider);
+			return new SleuthkitCase(info, databaseName, caseHandle, caseDir, contentProvider);
 		} catch (PropertyVetoException exp) {
 			// In this case, the JDBC driver doesn't support PostgreSQL. Use the generic message here.
 			throw new TskCoreException(exp.getMessage(), exp);
@@ -3162,8 +3171,7 @@ public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info,
 			factory.createCaseDatabase();
 
 			final SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(databaseName, info);
-			return new SleuthkitCase(info.getHost(), Integer.parseInt(info.getPort()),
-					databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDirPath, info.getDbType(), contentProvider);
+			return new SleuthkitCase(info, databaseName, caseHandle, caseDirPath, contentProvider);
 		} catch (PropertyVetoException exp) {
 			// In this case, the JDBC driver doesn't support PostgreSQL. Use the generic message here.
 			throw new TskCoreException(exp.getMessage(), exp);
@@ -13384,13 +13392,21 @@ public CaseDbConnection getPooledConnection() throws SQLException {
 	 */
 	private final class PostgreSQLConnections extends ConnectionPool {
 
-		PostgreSQLConnections(String host, int port, String dbName, String userName, String password) throws PropertyVetoException, UnsupportedEncodingException {
+		PostgreSQLConnections(CaseDbConnectionInfo info, String dbName) throws PropertyVetoException, UnsupportedEncodingException {
+			
 			ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
 			comboPooledDataSource.setDriverClass("org.postgresql.Driver"); //loads the jdbc driver
-			comboPooledDataSource.setJdbcUrl("jdbc:postgresql://" + host + ":" + port + "/"
-					+ URLEncoder.encode(dbName, StandardCharsets.UTF_8.toString()));
-			comboPooledDataSource.setUser(userName);
-			comboPooledDataSource.setPassword(password);
+			
+			String connectionURL = "jdbc:postgresql://" + info.getHost() + ":" + Integer.valueOf(info.getPort()) + "/"
+					+ URLEncoder.encode(dbName, StandardCharsets.UTF_8.toString());
+			if (info.isSslEnabled()) {
+				// ssl=true: enables SSL encryption. 
+				// NonValidatingFactory avoids hostname verification.
+				connectionURL += "?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory";
+			}
+			comboPooledDataSource.setJdbcUrl(connectionURL);
+			comboPooledDataSource.setUser(info.getUserName());
+			comboPooledDataSource.setPassword(info.getPassword());
 			comboPooledDataSource.setAcquireIncrement(2);
 			comboPooledDataSource.setInitialPoolSize(5);
 			comboPooledDataSource.setMinPoolSize(5);
-- 
GitLab