Friday, October 22, 2021

KeyStore / TrustStore Password and '?' is interpreted as $ORACLE_HOME

One way to connect to ATP using JDBC thin is to load key/trust store password and locations using an ojdbc.properties file.
While atempting to connect to a customer ATP using given crednetails resulted in an error similar to below.
java.sql.SQLException: the connection properties file contains an invalid expression in the value of: javax.net.ssl.keyStorePassword

The customer given credentail for the ATP wallet (which is also the passwords for key/trust store) contained the character "?".
After some investigations it was found out that the JDBC driver treats the "?" as a subtitue for $ORACLE_HOME and complains that this is not set.
Caused by: java.io.IOException: Environment variable is not set: ORACLE_HOME. ('?' is interpreted as $ORACLE_HOME)
	at oracle.jdbc.driver.PropertiesFileUtil$Interpreter.readQuestionMark(PropertiesFileUtil.java:702)
	at oracle.jdbc.driver.PropertiesFileUtil$Interpreter.interpret(PropertiesFileUtil.java:669)
	at oracle.jdbc.driver.PropertiesFileUtil$Interpreter.access$000(PropertiesFileUtil.java:622)
	at oracle.jdbc.driver.PropertiesFileUtil.processExpressions(PropertiesFileUtil.java:591)

Similar to keystore, the truststore also result in an error. Since JDBC driver code load keystore password first and as it result in an error the trusture password doesn't get picked. But if truststore password contains ? and keystore didn't then error would indicate the same with regard to truststore.
java.sql.SQLException: the connection properties file contains an invalid expression in the value of: javax.net.ssl.trustStorePassword

This could affect not just ATP but any DB. Infact to recreate the problem don't even need a DB to connect to. Simply loading a ojdbc.properties file with either keystore or truststore password containing "?" character in them would result in the error. Below is an example java class with minimum number of lines of code needed to recreate the issue. The DB URL need not be valid as error occurs before URL is validated.
public class KeyStorePwd2 {

    public static void main(String[] args) throws Exception {

        PoolDataSource ds = PoolDataSourceFactory.getPoolDataSource();
        ds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
        ds.setURL("jdbc:oracle:thin:@test:1512/test");
        Connection con = ds.getConnection();

    }

}

The ojdbc.properties contain the following
javax.net.ssl.trustStore=C:\\Asanga\\java\\keystoretest\\truststore.jks
javax.net.ssl.trustStorePassword=test_123?4_ABC
javax.net.ssl.keyStore=C:\\Asanga\\java\\keystoretest\\keystore.jks
javax.net.ssl.keyStorePassword=test_123?4_ABC
Compile and run giving the ojdbc.properties file location in the tns_admin JVM option.
java -Doracle.net.tns_admin=. KeyStorePwd2
This result in a run time error and stack trace is shown below.
Exception in thread "main" java.sql.SQLException: Unable to start the Universal Connection Pool: oracle.ucp.UniversalConnectionPoolException: Cannot get Connection from Datasource: java.sql.SQLException: the connection properties file contains an invalid expression in the value of: javax.net.ssl.keyStorePassword
	at oracle.ucp.util.UCPErrorHandler.newSQLException(UCPErrorHandler.java:456)
	at oracle.ucp.util.UCPErrorHandler.throwSQLException(UCPErrorHandler.java:133)
	at oracle.ucp.jdbc.PoolDataSourceImpl.startPool(PoolDataSourceImpl.java:928)
	at oracle.ucp.jdbc.PoolDataSourceImpl.getConnection(PoolDataSourceImpl.java:1961)
	at oracle.ucp.jdbc.PoolDataSourceImpl.access$400(PoolDataSourceImpl.java:201)
	at oracle.ucp.jdbc.PoolDataSourceImpl$31.build(PoolDataSourceImpl.java:4279)
	at oracle.ucp.jdbc.PoolDataSourceImpl.getConnection(PoolDataSourceImpl.java:1917)
	at oracle.ucp.jdbc.PoolDataSourceImpl.getConnection(PoolDataSourceImpl.java:1880)
	at oracle.ucp.jdbc.PoolDataSourceImpl.getConnection(PoolDataSourceImpl.java:1865)
	at KeyStorePwd2.main(KeyStorePwd2.java:18)
Caused by: oracle.ucp.UniversalConnectionPoolException: Cannot get Connection from Datasource: java.sql.SQLException: the connection properties file contains an invalid expression in the value of: javax.net.ssl.keyStorePassword
	at oracle.ucp.util.UCPErrorHandler.newUniversalConnectionPoolException(UCPErrorHandler.java:336)
	at oracle.ucp.util.UCPErrorHandler.throwUniversalConnectionPoolException(UCPErrorHandler.java:59)
	at oracle.ucp.jdbc.oracle.OracleDataSourceConnectionFactoryAdapter.createConnection(OracleDataSourceConnectionFactoryAdapter.java:134)
	at oracle.ucp.common.Database.createPooledConnection(Database.java:256)
	at oracle.ucp.common.Topology.start(Topology.java:247)
	at oracle.ucp.common.Core.start(Core.java:2361)
	at oracle.ucp.common.UniversalConnectionPoolBase.start(UniversalConnectionPoolBase.java:690)
	at oracle.ucp.jdbc.oracle.OracleJDBCConnectionPool.start(OracleJDBCConnectionPool.java:129)
	at oracle.ucp.jdbc.PoolDataSourceImpl.startPool(PoolDataSourceImpl.java:924)
	... 7 more
Caused by: java.sql.SQLException: the connection properties file contains an invalid expression in the value of: javax.net.ssl.keyStorePassword
	at oracle.jdbc.driver.PropertiesFileUtil.processExpressions(PropertiesFileUtil.java:596)
	at oracle.jdbc.driver.PropertiesFileUtil.loadDefaultFiles(PropertiesFileUtil.java:221)
	at oracle.jdbc.driver.PropertiesFileUtil.loadPropertiesFromFile(PropertiesFileUtil.java:139)
	at oracle.jdbc.driver.PhysicalConnection.getConnectionPropertiesFromFile(PhysicalConnection.java:10210)
	at oracle.jdbc.driver.PhysicalConnection.readConnectionProperties(PhysicalConnection.java:1049)
	at oracle.jdbc.driver.PhysicalConnection.init>(PhysicalConnection.java:747)
	at oracle.jdbc.driver.T4CConnection.<init>(T4CConnection.java:502)
	at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:56)
	at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:747)
	at oracle.jdbc.pool.OracleDataSource.getPhysicalConnection(OracleDataSource.java:413)
	at oracle.jdbc.pool.OracleDataSource.getConnection(OracleDataSource.java:298)
	at oracle.jdbc.pool.OracleDataSource$1.build(OracleDataSource.java:1730)
	at oracle.jdbc.pool.OracleDataSource$1.build(OracleDataSource.java:1716)
	at oracle.ucp.jdbc.oracle.OracleDataSourceConnectionFactoryAdapter.createConnection(OracleDataSourceConnectionFactoryAdapter.java:103)
	... 13 more
Caused by: java.io.IOException: Environment variable is not set: ORACLE_HOME. ('?' is interpreted as $ORACLE_HOME)
	at oracle.jdbc.driver.PropertiesFileUtil$Interpreter.readQuestionMark(PropertiesFileUtil.java:702)
	at oracle.jdbc.driver.PropertiesFileUtil$Interpreter.interpret(PropertiesFileUtil.java:669)
	at oracle.jdbc.driver.PropertiesFileUtil$Interpreter.access$000(PropertiesFileUtil.java:622)
	at oracle.jdbc.driver.PropertiesFileUtil.processExpressions(PropertiesFileUtil.java:591)
	... 26 more

It makes no sense to treat "?" in a password field as a directory location i.e $ORACLE_HOME.



The issue is not there if the password doesn't contain "?". In case of ATP its just a matter of downloading a new wallet and giving it a password that doesn't contain "?" character in it.
However, if this is not possible there are several workarounds to overcome this.
One solution is to specify the keystore and trusttore password as JVM options instead of using ojdbc.properties file (key/trust sotre file location could still be loaded from ojdbc.properteis). Taking the previous example this would look like as below (it's assume password related lines are commented in the ojdbc.properties)
java -Doracle.net.tns_admin=. -Djavax.net.ssl.keyStorePassword=test_123?4_ABC -Djavax.net.ssl.trustStorePassword=test_123?4_ABC KeyStorePwd2

Other solution is to specify it as a connection pool property. Example sinppet shown below.
	PoolDataSource ds = PoolDataSourceFactory.getPoolDataSource();
        ds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");

        Properties p = new Properties();
        p.put(CONNECTION_PROPERTY_THIN_JAVAX_NET_SSL_TRUSTSTOREPASSWORD, "hello_DB?A_1234");
        p.put(CONNECTION_PROPERTY_THIN_JAVAX_NET_SSL_KEYSTOREPASSWORD, "hello_DB?A_1234");
        ds.setConnectionProperties(p);

SR has resulted in intenral bug 33473422.