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