diff --git a/docs/content/documentation/ssl.md b/docs/content/documentation/ssl.md index 51319929ed..37724c5b4c 100644 --- a/docs/content/documentation/ssl.md +++ b/docs/content/documentation/ssl.md @@ -38,6 +38,20 @@ Information on how to actually implement such a class is beyond the scope of thi are the [JSSE Reference Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html) and the source to the `NonValidatingFactory` provided by the JDBC driver. +## Available SSLSocketFactory Implementations + +The following implementations of SSLSocketFactory are shipped with the driver. + +|sslfactory|Description| +|---|---| +|org.postgresql.ssl.DefaultJavaSSLFactory|Use the JDK default implementation.| +|org.postgresql.ssl.KeychainSSLFactory|Use certificates and keys from the MacOS keychain. If the MacOS truststore is unsupported by the JDK, we fall back to the default cacerts JKS truststore.| +|org.postgresql.ssl.LibPQFactory|Use the same certificates and keys as the libpq library. The key must be in DER encoded PKCS8 format. This is the default when sslfactory is unspecified.| +|org.postgresql.ssl.MSCAPILocalMachineSSLFactory|Use certificates and keys from the Windows local machine certificate manager.| +|org.postgresql.ssl.MSCAPISSLFactory|Use certificates and keys from the Windows certificate manager belonging to the current user.| +|org.postgresql.ssl.NonValidatingFactory|Connect to anyone without checking. No validation is performed.| +|org.postgresql.ssl.SingleCertValidatingFactory|Accept the pinned server certificate specified in the sslfactoryarg parameter.| + ## Configuring the Client There are a number of connection parameters for configuring the client for SSL. See [SSL Connection parameters](/documentation/use/#connection-parameters/) @@ -122,6 +136,10 @@ When starting your Java application you must specify this keystore and password In the event of problems extra debugging information is available by adding `-Djavax.net.debug=ssl` to your command line. +### Choosing a Specific Certificate + +When using the Keychain or MSCAPI factories, and more than one client certificate matches during SSL negotiation, the first negotiated certificate will be chosen. To control which of many negotiated certificates will be returned, the sslsubject parameter can be used to set the subject of the certificate to be chosen. Note that sslsubject chooses from the list of negotiated certificates, it does not override the negotiation mechanism. If there is no match, no certificate will be sent to the server. + ### Using SSL without Certificate Validation In some situations it may not be possible to configure your Java environment to make the server certificate available, for example in an applet. For a large scale deployment it would be best to get a certificate signed by recognized diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java index da11f66d7d..6108169675 100644 --- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java +++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java @@ -701,6 +701,14 @@ public enum PGProperty { null, "The location of the root certificate for authenticating the server."), + /** + * The subject of the desired client certificate in a store containing many certificates. + */ + SSL_SUBJECT( + "sslsubject", + null, + "The subject of the desired client certificate in a store with many certificates."), + /** * Specifies the name of the SSPI service class that forms the service class part of the SPN. The * default, {@code POSTGRES}, is almost always correct. diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java index c3b606fa88..297bbb8091 100644 --- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java +++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java @@ -774,6 +774,22 @@ public void setSslPasswordCallback(@Nullable String className) { PGProperty.SSL_PASSWORD_CALLBACK.set(properties, className); } + /** + * @return SSL subject + * @see PGProperty#SSL_SUBJECT + */ + public @Nullable String getSslSubject() { + return PGProperty.SSL_SUBJECT.getOrDefault(properties); + } + + /** + * @param file SSL subject + * @see PGProperty#SSL_SUBJECT + */ + public void setSslSubject(@Nullable String file) { + PGProperty.SSL_SUBJECT.set(properties, file); + } + /** * @param applicationName application name * @see PGProperty#APPLICATION_NAME diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/KeychainSSLFactory.java b/pgjdbc/src/main/java/org/postgresql/ssl/KeychainSSLFactory.java new file mode 100644 index 0000000000..756322f9a7 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/ssl/KeychainSSLFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.ssl; + +import org.postgresql.util.PSQLException; + +import java.util.Properties; + +/** + *

Provides a SSLSocketFactory that authenticates the remote server against + * the keychain provided by MacOS.

+ * + *

Older versions of the JDK support MacOS key stores but not trust stores. + * If the trust store implementation is not found, this factory falls back to + * the default JDK trust store in cacerts. + * + *

When multiple certificates match for the given connection, the optional + * sslsubject connection property can be used to choose the + * desired certificate from the matching set. Note that this property does not + * override the certificate selection outside of the matching set. + */ +public class KeychainSSLFactory extends SSLFactory { + + public KeychainSSLFactory(Properties info) throws PSQLException { + super(info, "TLS", "PKIX", "Apple", + "KeychainStore", + "Apple", "KeychainStore-ROOT"); + } + +} diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/MSCAPILocalMachineSSLFactory.java b/pgjdbc/src/main/java/org/postgresql/ssl/MSCAPILocalMachineSSLFactory.java new file mode 100644 index 0000000000..8c13d6182c --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/ssl/MSCAPILocalMachineSSLFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.ssl; + +import org.postgresql.util.PSQLException; + +import java.util.Properties; + +/** + *

Provides a SSLSocketFactory that authenticates the remote server against + * the local machine's certificate store provided by Windows.

+ * + *

The remote certificate is validated against the local machine's + * certificate trust store.

+ * + *

When multiple certificates match for the given connection, the optional + * sslsubject connection property can be used to choose the + * desired certificate from the matching set. Note that this property does not + * override the certificate selection outside of the matching set. + */ +public class MSCAPILocalMachineSSLFactory extends SSLFactory { + + public MSCAPILocalMachineSSLFactory(Properties info) throws PSQLException { + super(info, "TLS", "PKIX", "SunMSCAPI", + "Windows-MY-LOCALMACHINE", + "SunMSCAPI", "Windows-ROOT"); + } + +} diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/MSCAPISSLFactory.java b/pgjdbc/src/main/java/org/postgresql/ssl/MSCAPISSLFactory.java new file mode 100644 index 0000000000..31e05df699 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/ssl/MSCAPISSLFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.ssl; + +import org.postgresql.util.PSQLException; + +import java.util.Properties; + +/** + *

Provides a SSLSocketFactory that authenticates the remote server against + * the logged in user's certificate store provided by Windows.

+ * + *

The remote certificate is validated against the logged in user's + * certificate trust store.

+ * + *

When multiple certificates match for the given connection, the optional + * sslsubject connection property can be used to choose the + * desired certificate from the matching set. Note that this property does not + * override the certificate selection outside of the matching set. + */ +public class MSCAPISSLFactory extends SSLFactory { + + public MSCAPISSLFactory(Properties info) throws PSQLException { + super(info, "TLS", "PKIX", "SunMSCAPI", + "Windows-MY", + "SunMSCAPI", "Windows-ROOT"); + } + +} diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/SSLFactory.java b/pgjdbc/src/main/java/org/postgresql/ssl/SSLFactory.java new file mode 100644 index 0000000000..6b16402a40 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/ssl/SSLFactory.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.ssl; + +import org.postgresql.PGProperty; +import org.postgresql.util.GT; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Properties; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.security.auth.x500.X500Principal; + +/** + * Socket factory that uses typical "zero configuration" key and trust + * stores, like the native Windows "SunMSCAPI" or native Apple "Apple" + * providers. + */ +public class SSLFactory extends WrappedFactory { + + protected SSLFactory(Properties info, final String protocol, final String algorithm, final String keyStoreProvider, final String keyStoreType, final String trustStoreProvider, final String trustStoreType) throws PSQLException { + + SSLContext ctx; + final KeyStore keyStore; + KeyStore trustStore; + final char[] keyPassphrase = new char[0]; + final String sslsubject = PGProperty.SSL_SUBJECT.getOrDefault(info); + final @Nullable X500Principal subject; + final KeyManagerFactory keyManagerFactory; + final TrustManagerFactory trustManagerFactory; + + try { + + keyStore = KeyStore.getInstance(keyStoreType, keyStoreProvider); + + } catch (KeyStoreException ex) { + throw new PSQLException(GT.tr("SSL keystore {0} not available.", + keyStoreType), PSQLState.CONNECTION_FAILURE, ex); + } catch (NoSuchProviderException ex) { + throw new PSQLException(GT.tr("SSL keystore {0} not available.", + keyStoreType), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + keyStore.load(null, null); + + } catch (CertificateException ex) { + throw new PSQLException(GT.tr("SSL keystore {0} could not be loaded.", + keyStoreType), PSQLState.CONNECTION_FAILURE, ex); + } catch (IOException ex) { + throw new PSQLException(GT.tr("SSL keystore {0} could not be loaded.", + keyStoreType), PSQLState.CONNECTION_FAILURE, ex); + } catch (NoSuchAlgorithmException ex) { + throw new PSQLException(GT.tr("Could not find a Java cryptographic algorithm: {0}.", + ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + keyManagerFactory = KeyManagerFactory + .getInstance(algorithm); + + } catch (NoSuchAlgorithmException ex) { + throw new PSQLException(GT.tr("Could not find a Java cryptographic algorithm: {0}.", + ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + keyManagerFactory.init(keyStore, keyPassphrase); + + } catch (NoSuchAlgorithmException ex) { + throw new PSQLException(GT.tr("Could not find a Java cryptographic algorithm: {0}.", + ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); + } catch (KeyStoreException ex) { + throw new PSQLException(GT.tr("Could not initialize SSL keystore."), + PSQLState.CONNECTION_FAILURE, ex); + } catch (UnrecoverableKeyException ex) { + throw new PSQLException(GT.tr("Could not read SSL key."), + PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + trustStore = KeyStore.getInstance(trustStoreType, trustStoreProvider); + + } catch (KeyStoreException ex) { + // On the Mac, the truststore is new and won't be available + // on old versions of the JDK. In these cases fall back to + // the default truststore in cacerts. + if (ex.getCause() instanceof NoSuchAlgorithmException) { + trustStore = null; + } else { + throw new PSQLException(GT.tr("SSL truststore {0} not available.", + trustStoreType), PSQLState.CONNECTION_FAILURE, ex); + } + } catch (NoSuchProviderException ex) { + throw new PSQLException(GT.tr("SSL truststore {0} not available.", + trustStoreType), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + if (trustStore != null) { + trustStore.load(null, null); + } + + } catch (CertificateException ex) { + throw new PSQLException(GT.tr("SSL truststore {0} could not be loaded.", + trustStoreType), PSQLState.CONNECTION_FAILURE, ex); + } catch (IOException ex) { + throw new PSQLException(GT.tr("SSL truststore {0} could not be loaded.", + trustStoreType), PSQLState.CONNECTION_FAILURE, ex); + } catch (NoSuchAlgorithmException ex) { + throw new PSQLException(GT.tr("Could not find a Java cryptographic algorithm: {0}.", + ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + trustManagerFactory = TrustManagerFactory + .getInstance(algorithm); + + } catch (NoSuchAlgorithmException ex) { + throw new PSQLException(GT.tr("Could not find a Java cryptographic algorithm: {0}.", + ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + trustManagerFactory.init(trustStore); + + } catch (KeyStoreException ex) { + throw new PSQLException(GT.tr("Could not initialize SSL truststore."), + PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + ctx = SSLContext.getInstance(protocol); + + } catch (NoSuchAlgorithmException ex) { + throw new PSQLException(GT.tr("Could not find a Java cryptographic algorithm: {0}.", + ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + if (sslsubject != null && sslsubject.length() != 0) { + subject = new X500Principal(sslsubject); + } else { + subject = null; + } + + } catch (IllegalArgumentException ex) { + throw new PSQLException(GT.tr("Could not parse sslsubject {0}: {1}.", + sslsubject, ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); + } + + try { + + KeyManager km = keyManagerFactory.getKeyManagers()[0]; + + if (subject != null) { + km = new SubjectKeyManager(X509KeyManager.class.cast(km), subject); + } + + ctx.init(new KeyManager[] { km }, null, null); + + } catch (KeyManagementException ex) { + throw new PSQLException(GT.tr("Could not initialize SSL keystore/truststore."), + PSQLState.CONNECTION_FAILURE, ex); + } + + factory = ctx.getSocketFactory(); + + } +} diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/SubjectKeyManager.java b/pgjdbc/src/main/java/org/postgresql/ssl/SubjectKeyManager.java new file mode 100644 index 0000000000..bb79f753f9 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/ssl/SubjectKeyManager.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.ssl; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509KeyManager; +import javax.security.auth.x500.X500Principal; + +/** + * The SubjectKeyManager limits candidate certificates to + * the certificate subject specified in "sslsubject". + */ +public class SubjectKeyManager implements X509KeyManager { + + private final X509KeyManager km; + private final @Nullable X500Principal subject; + + /** + *

+ * Wrap the provided key manager with this one, limiting + * certificates to those matching the given subject. + *

+ * + *

+ * If subject is null, this key manager gives way to the + * wrapped key manager and does nothing. + *

+ * @param km Wrapped key manager + * @param subject Subject distinguished name of the chosen + * certificate + */ + public SubjectKeyManager(X509KeyManager km, @Nullable X500Principal subject) { + + this.km = km; + this.subject = subject; + + } + + @Override + public @Nullable String chooseClientAlias(String[] keyTypes, Principal @Nullable [] issuers, + @Nullable Socket socket) { + + if (subject == null) { + return this.km.chooseClientAlias(keyTypes, issuers, socket); + } + + for (String keyType: keyTypes) { + String[] aliases = getClientAliases(keyType, issuers); + + if (aliases == null) { + continue; + } + + for (String alias: aliases) { + + X509Certificate[] certchain = getCertificateChain(alias); + if (certchain == null || certchain.length == 0) { + continue; + } + + X509Certificate leaf = certchain[0]; + X500Principal oursubject = leaf.getSubjectX500Principal(); + if (!oursubject.equals(subject)) { + continue; + } + + return alias; + } + } + + return null; + } + + @Override + public String @Nullable [] getClientAliases(String keyType, Principal @Nullable [] issuers) { + return this.km.getClientAliases(keyType, issuers); + } + + @Override + public String @Nullable [] getServerAliases(String keyType, Principal@Nullable [] issuers) { + return this.km.getServerAliases(keyType, issuers); + } + + @Override + public @Nullable String chooseServerAlias(String keyType, Principal @Nullable [] issuers, @Nullable Socket socket) { + return this.km.chooseServerAlias(keyType, issuers, socket); + } + + @Override + public X509Certificate @Nullable[] getCertificateChain(String alias) { + return this.km.getCertificateChain(alias); + } + + @Override + public @Nullable PrivateKey getPrivateKey(String alias) { + return this.km.getPrivateKey(alias); + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java index 1d727fe46b..5cea50ebd6 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java @@ -248,6 +248,7 @@ void lowerCamelCase() { excluded.add("SSL_ROOT_CERT"); // ssl[r]oot[c]ert excluded.add("SSL_PASSWORD"); // ssl[p]assword excluded.add("SSL_PASSWORD_CALLBACK"); // ssl[p]assword[c]allback + excluded.add("SSL_SUBJECT"); // ssl[s]ubject excluded.add("APPLICATION_NAME"); // [A]pplicationName excluded.add("GSS_LIB"); // gss[l]ib excluded.add("REWRITE_BATCHED_INSERTS"); // re[W]riteBatchedInserts diff --git a/pgjdbc/src/test/java/org/postgresql/test/ssl/SSLFactoryTest.java b/pgjdbc/src/test/java/org/postgresql/test/ssl/SSLFactoryTest.java new file mode 100644 index 0000000000..5ccbfc0cb5 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/ssl/SSLFactoryTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.ssl; + +import org.postgresql.PGProperty; +import org.postgresql.core.SocketFactoryFactory; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.security.NoSuchProviderException; +import java.util.Properties; + +class SSLFactoryTest { + + @Test + void TestDefault() throws Exception { + TestUtil.assumeSslTestsEnabled(); + + Properties props = new Properties(); + PGProperty.SSL_FACTORY.set(props, "org.postgresql.ssl.DefaultJavaSSLFactory"); + + try { + SocketFactoryFactory.getSslSocketFactory(props); + } catch (Throwable t) { + /* ignore NoSuchProviderException */ + if (!(t instanceof PSQLException) + || (!(t.getCause() instanceof InvocationTargetException)) + || (!(t.getCause().getCause() instanceof PSQLException)) + || (!(t.getCause().getCause().getCause() instanceof NoSuchProviderException)) + ) { + throw t; + } + } + + } + + @Test + void TestKeychain() throws Exception { + TestUtil.assumeSslTestsEnabled(); + + Properties props = new Properties(); + PGProperty.SSL_FACTORY.set(props, "org.postgresql.ssl.KeychainSSLFactory"); + + try { + SocketFactoryFactory.getSslSocketFactory(props); + } catch (Throwable t) { + /* ignore NoSuchProviderException */ + if (!(t instanceof PSQLException) + || (!(t.getCause() instanceof InvocationTargetException)) + || (!(t.getCause().getCause() instanceof PSQLException)) + || (!(t.getCause().getCause().getCause() instanceof NoSuchProviderException)) + ) { + throw t; + } + } + + } + + @Test + void TestMSCurrentUserSSLFactory() throws Exception { + TestUtil.assumeSslTestsEnabled(); + + Properties props = new Properties(); + PGProperty.SSL_FACTORY.set(props, "org.postgresql.ssl.MSCAPISSLFactory"); + + try { + SocketFactoryFactory.getSslSocketFactory(props); + } catch (Throwable t) { + /* ignore NoSuchProviderException */ + if (!(t instanceof PSQLException) + || (!(t.getCause() instanceof InvocationTargetException)) + || (!(t.getCause().getCause() instanceof PSQLException)) + || (!(t.getCause().getCause().getCause() instanceof NoSuchProviderException)) + ) { + throw t; + } + } + + } + + @Test + void TestMSLocalMachineSSLFactory() throws Exception { + TestUtil.assumeSslTestsEnabled(); + + Properties props = new Properties(); + PGProperty.SSL_FACTORY.set(props, "org.postgresql.ssl.MSCAPILocalMachineSSLFactory"); + + try { + SocketFactoryFactory.getSslSocketFactory(props); + } catch (Throwable t) { + /* ignore NoSuchProviderException */ + if (!(t instanceof PSQLException) + || (!(t.getCause() instanceof InvocationTargetException)) + || (!(t.getCause().getCause() instanceof PSQLException)) + || (!(t.getCause().getCause().getCause() instanceof NoSuchProviderException)) + ) { + throw t; + } + } + + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java index 95e439c45e..c462081a1c 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java +++ b/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java @@ -13,7 +13,9 @@ CommonNameVerifierTest.class, LazyKeyManagerTest.class, LibPQFactoryHostNameTest.class, + SSLFactoryTest.class, SslTest.class, + SubjectKeyManagerTest.class, }) public class SslTestSuite { } diff --git a/pgjdbc/src/test/java/org/postgresql/test/ssl/SubjectKeyManagerTest.java b/pgjdbc/src/test/java/org/postgresql/test/ssl/SubjectKeyManagerTest.java new file mode 100644 index 0000000000..d5e8b87539 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/ssl/SubjectKeyManagerTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.ssl; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.postgresql.ssl.PKCS12KeyManager; +import org.postgresql.ssl.SubjectKeyManager; +import org.postgresql.test.TestUtil; +import org.postgresql.test.ssl.PKCS12KeyTest.TestCallbackHandler; + +import org.junit.jupiter.api.Test; + +import javax.security.auth.x500.X500Principal; + +class SubjectKeyManagerTest { + + @Test + void TestChooseClientAlias() throws Exception { + PKCS12KeyManager pkcs12KeyManager = new PKCS12KeyManager(TestUtil.getSslTestCertPath("goodclient.p12"), new TestCallbackHandler("sslpwd")); + X500Principal testPrincipal = new X500Principal("CN=root certificate, O=PgJdbc test, ST=CA, C=US"); + X500Principal[] issuers = new X500Principal[]{testPrincipal}; + + String validKeyType = pkcs12KeyManager.chooseClientAlias(new String[]{"RSA"}, issuers, null); + assertNotNull(validKeyType); + + X500Principal testSubject = new X500Principal("CN=test, O=PgJdbc tests, ST=CA, C=US"); + SubjectKeyManager subjectKeyManager = new SubjectKeyManager(pkcs12KeyManager, testSubject); + + String filteredKeyType = subjectKeyManager.chooseClientAlias(new String[]{"rsa"}, issuers, null); + assertNotNull(filteredKeyType); + + X500Principal badSubject = new X500Principal("CN=bad, O=PgJdbc tests, ST=CA, C=US"); + SubjectKeyManager badSubjectKeyManager = new SubjectKeyManager(pkcs12KeyManager, badSubject); + + String badKeyType = badSubjectKeyManager.chooseClientAlias(new String[]{"rsa"}, issuers, null); + assertNull(badKeyType); + } +}