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
fix: Add pkcs12 key functionality #1599
Changes from all commits
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,171 @@ | ||
/* | ||
* Copyright (c) 2019, PostgreSQL Global Development Group | ||
* See the LICENSE file in the project root for more information. | ||
*/ | ||
|
||
package org.postgresql.ssl; | ||
|
||
import org.postgresql.util.GT; | ||
import org.postgresql.util.PSQLException; | ||
import org.postgresql.util.PSQLState; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.net.Socket; | ||
import java.security.KeyStore; | ||
import java.security.KeyStoreException; | ||
import java.security.Principal; | ||
import java.security.PrivateKey; | ||
import java.security.cert.Certificate; | ||
import java.security.cert.X509Certificate; | ||
|
||
import javax.net.ssl.X509KeyManager; | ||
import javax.security.auth.callback.Callback; | ||
import javax.security.auth.callback.CallbackHandler; | ||
import javax.security.auth.callback.PasswordCallback; | ||
import javax.security.auth.callback.UnsupportedCallbackException; | ||
import javax.security.auth.x500.X500Principal; | ||
|
||
public class PKCS12KeyManager implements X509KeyManager { | ||
|
||
private final CallbackHandler cbh; | ||
private PSQLException error = null; | ||
private final String keyfile; | ||
private final KeyStore keyStore; | ||
boolean keystoreLoaded = false; | ||
|
||
public PKCS12KeyManager(String pkcsFile, CallbackHandler cbh) throws PSQLException { | ||
try { | ||
keyStore = KeyStore.getInstance("pkcs12"); | ||
keyfile = pkcsFile; | ||
this.cbh = cbh; | ||
} catch ( KeyStoreException kse ) { | ||
throw new PSQLException(GT.tr( | ||
"Unable to find pkcs12 keystore."), | ||
PSQLState.CONNECTION_FAILURE, kse); | ||
} | ||
} | ||
|
||
/** | ||
* getCertificateChain and getPrivateKey cannot throw exeptions, therefore any exception is stored | ||
* in {@link #error} and can be raised by this method. | ||
* | ||
* @throws PSQLException if any exception is stored in {@link #error} and can be raised | ||
*/ | ||
public void throwKeyManagerException() throws PSQLException { | ||
if (error != null) { | ||
throw error; | ||
} | ||
} | ||
|
||
@Override | ||
public String[] getClientAliases(String keyType, Principal[] principals) { | ||
String alias = chooseClientAlias(new String[]{keyType}, principals, (Socket) null); | ||
return (alias == null ? new String[]{} : new String[]{alias}); | ||
} | ||
|
||
@Override | ||
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { | ||
if (principals == null || principals.length == 0) { | ||
// Postgres 8.4 and earlier do not send the list of accepted certificate authorities | ||
// to the client. See BUG #5468. We only hope, that our certificate will be accepted. | ||
return "user"; | ||
} else { | ||
// Sending a wrong certificate makes the connection rejected, even, if clientcert=0 in | ||
// pg_hba.conf. | ||
// therefore we only send our certificate, if the issuer is listed in issuers | ||
X509Certificate[] certchain = getCertificateChain("user"); | ||
if (certchain == null) { | ||
return null; | ||
} else { | ||
X500Principal ourissuer = certchain[certchain.length - 1].getIssuerX500Principal(); | ||
boolean found = false; | ||
for (Principal issuer : principals) { | ||
if (ourissuer.equals(issuer)) { | ||
found = true; | ||
} | ||
} | ||
return (found ? "user" : null); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public String[] getServerAliases(String s, Principal[] principals) { | ||
return new String[]{}; | ||
} | ||
|
||
@Override | ||
public String chooseServerAlias(String s, Principal[] principals, Socket socket) { | ||
// we are not a server | ||
return null; | ||
} | ||
|
||
@Override | ||
public X509Certificate[] getCertificateChain(String alias) { | ||
try { | ||
loadKeyStore(); | ||
Certificate []certs = keyStore.getCertificateChain(alias); | ||
X509Certificate [] x509Certificates = new X509Certificate[certs.length]; | ||
int i = 0; | ||
for (Certificate cert : certs) { | ||
x509Certificates[i++] = (X509Certificate)cert; | ||
} | ||
return x509Certificates; | ||
} catch (Exception kse ) { | ||
error = new PSQLException(GT.tr( | ||
"Could not find a java cryptographic algorithm: X.509 CertificateFactory not available."), | ||
PSQLState.CONNECTION_FAILURE, kse); | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public PrivateKey getPrivateKey(String s) { | ||
try { | ||
loadKeyStore(); | ||
PasswordCallback pwdcb = new PasswordCallback(GT.tr("Enter SSL password: "), false); | ||
cbh.handle(new Callback[]{pwdcb}); | ||
|
||
KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(pwdcb.getPassword()); | ||
KeyStore.PrivateKeyEntry pkEntry = | ||
(KeyStore.PrivateKeyEntry) keyStore.getEntry("user", protParam); | ||
PrivateKey myPrivateKey = pkEntry.getPrivateKey(); | ||
return myPrivateKey; | ||
} catch (Exception ioex ) { | ||
error = new PSQLException(GT.tr("Could not read SSL key file {0}.", keyfile), | ||
PSQLState.CONNECTION_FAILURE, ioex); | ||
} | ||
return null; | ||
} | ||
|
||
private synchronized void loadKeyStore() throws Exception { | ||
|
||
if (keystoreLoaded) { | ||
return; | ||
} | ||
// We call back for the password | ||
PasswordCallback pwdcb = new PasswordCallback(GT.tr("Enter SSL password: "), false); | ||
try { | ||
cbh.handle(new Callback[]{pwdcb}); | ||
} catch (UnsupportedCallbackException ucex) { | ||
if ((cbh instanceof LibPQFactory.ConsoleCallbackHandler) | ||
&& ("Console is not available".equals(ucex.getMessage()))) { | ||
error = new PSQLException(GT | ||
.tr("Could not read password for SSL key file, console is not available."), | ||
PSQLState.CONNECTION_FAILURE, ucex); | ||
} else { | ||
error = | ||
new PSQLException( | ||
GT.tr("Could not read password for SSL key file by callbackhandler {0}.", | ||
cbh.getClass().getName()), | ||
PSQLState.CONNECTION_FAILURE, ucex); | ||
} | ||
|
||
} | ||
|
||
keyStore.load(new FileInputStream(new File(keyfile)), pwdcb.getPassword()); | ||
keystoreLoaded = true; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,15 @@ | |
package org.postgresql.test.ssl; | ||
|
||
import org.postgresql.ssl.LazyKeyManager; | ||
import org.postgresql.ssl.PKCS12KeyManager; | ||
|
||
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.security.PrivateKey; | ||
import java.security.cert.X509Certificate; | ||
|
||
import javax.security.auth.callback.Callback; | ||
import javax.security.auth.callback.CallbackHandler; | ||
|
@@ -21,6 +23,16 @@ | |
|
||
public class LazyKeyManagerTest { | ||
|
||
@Test | ||
public void testLoadP12Key() throws Exception { | ||
String certdir = "../certdir/"; | ||
PKCS12KeyManager pkcs12KeyManager = new PKCS12KeyManager(certdir + "goodclient.p12", new TestCallbackHandler("sslpwd")); | ||
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. I guess it is better to take 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. agreed |
||
PrivateKey pk = pkcs12KeyManager.getPrivateKey("user"); | ||
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. It is unfortunate that 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. ya, very hard to debug |
||
Assert.assertNotNull(pk); | ||
X509Certificate[] chain = pkcs12KeyManager.getCertificateChain("user"); | ||
Assert.assertNotNull(chain); | ||
} | ||
|
||
@Test | ||
public void testLoadKey() throws Exception { | ||
String certdir = "../certdir/"; | ||
|
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.
Is there a good reason to hard-code this literal?
I think it must be moved to a static final constant with a comment then.
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.
no good reason