-
Notifications
You must be signed in to change notification settings - Fork 851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Windows and MacOS native certificate support #3124
base: master
Are you sure you want to change the base?
Changes from 5 commits
00bf6f6
97a9c4d
4b43fb2
3ac9f7c
7d4adac
7262faa
30087c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/** | ||
* <p>Provides a SSLSocketFactory that authenticates the remote server against | ||
* the keychain provided by MacOS.</p> | ||
* | ||
* <p>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 <code>cacerts</code>. | ||
* | ||
* <p>When multiple certificates match for the given connection, the optional | ||
* <code>sslsubject</code> 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"); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/** | ||
* <p>Provides a SSLSocketFactory that authenticates the remote server against | ||
* the local machine's certificate store provided by Windows.</p> | ||
* | ||
* <p>The remote certificate is validated against the local machine's | ||
* certificate trust store.</p> | ||
* | ||
* <p>When multiple certificates match for the given connection, the optional | ||
* <code>sslsubject</code> 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"); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/** | ||
* <p>Provides a SSLSocketFactory that authenticates the remote server against | ||
* the logged in user's certificate store provided by Windows.</p> | ||
* | ||
* <p>The remote certificate is validated against the logged in user's | ||
* certificate trust store.</p> | ||
* | ||
* <p>When multiple certificates match for the given connection, the optional | ||
* <code>sslsubject</code> 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"); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/* | ||
* 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 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 = null; | ||
final String sslsubject = PGProperty.SSL_SUBJECT.getOrDefault(info); | ||
final 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); | ||
Check failure on line 87 in pgjdbc/src/main/java/org/postgresql/ssl/SSLFactory.java GitHub Actions / CheckerFramework
|
||
|
||
} catch (NoSuchAlgorithmException ex) { | ||
throw new PSQLException(GT.tr("Could not find a java cryptographic algorithm: {0}.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java is fixed. The ex.getMessage() is there on purpose so that the whole error message is on one single line. The target audience for this patch are data scientists who aren't Java developers, nor are they experts in SSL. They see the message "Could not find a java cryptographic algorithm", they don't understand it (fair: "which crypto algorithm?"), and I have to pick it all apart for them, first helping them give me the whole exception (I'll get a screenshot of the top few lines, then I'll explain how to cut and paste), then finding the needle in the haystack of "caused by", then googling for them. With the whole message on one line, they have one line to google themselves, on the top line, and lots and lots of make work is avoided. |
||
} 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}.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java is fixed. |
||
ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java is fixed. |
||
} | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java is fixed. |
||
} | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same both here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java is fixed. |
||
} | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is intended. |
||
} | ||
|
||
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(); | ||
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting a test failure, more specifically a compilation failure that I don't understand.
keyPassphrase is a char[], the extra annotations seem sane.
Can you confirm for me if possible what specifically is wrong with this line so I can fix it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These warnings might be relevant:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick ping on this comment - I don't understand how it compiles everywhere else but not in this specific case. Is someone familiar with CheckerFramework able to confirm?
https://github.com/pgjdbc/pgjdbc/actions/runs/8323877965/job/23147420376?pr=3124