package org.postgresql.ssl.jdbc4;
import java.io.File;
import java.io.RandomAccessFile;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.Socket;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collection;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
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;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
/**
* A Key manager that only loads the keys, if necessary.
*
*/
public class LazyKeyManager implements X509KeyManager {
private X509Certificate[] cert = null;
private PrivateKey key = null;
private String certfile;
private String keyfile;
private CallbackHandler cbh;
private boolean defaultfile;
private PSQLException error = null;
/**
* Constructor. certfile and keyfile can be null, in that case no
* certificate is presented to the server.
* @param certfile
* @param keyfile
* @param cbh
* @param defaultfile
*/
public LazyKeyManager(String certfile, String keyfile, CallbackHandler cbh, boolean defaultfile) {
this.certfile = certfile;
this.keyfile = keyfile;
this.cbh = cbh;
this.defaultfile = defaultfile;
}
/**
* getCertificateChain and getPrivateKey cannot throw exeptions,
* therefore any exception is stored in this.error and can be raised
* by this method
* @throws PSQLException
*/
public void throwKeyManagerException() throws PSQLException
{
if (error!=null) throw error;
}
public String chooseClientAlias(String[] keyType, Principal[] issuers,
Socket socket) {
if (certfile==null)
{
return null;
} else {
if (issuers==null || issuers.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 (int i=0; i<issuers.length; i++)
{
if (ourissuer.equals(issuers[i])) { found = true;}
}
return (found ? "user" : null);
}
}
}
}
public String chooseServerAlias(String keyType, Principal[] issuers,
Socket socket) {
return null; //We are not a server
}
public X509Certificate[] getCertificateChain(String alias) {
if (cert==null && certfile!=null) //If certfile is null, we do not load the certificate
{ //The certificate must be loaded
CertificateFactory cf;
try
{
cf = CertificateFactory.getInstance("X.509");
} catch (CertificateException ex)
{ //For some strange reason it throws CertificateException instead of NoSuchAlgorithmException...
error = new PSQLException(GT.tr("Could not find a java cryptographic algorithm: X.509 CertificateFactory not available.", null), PSQLState.CONNECTION_FAILURE, ex);
return null;
}
Collection certs;
try
{
certs = cf.generateCertificates(new FileInputStream(certfile));
} catch (FileNotFoundException ioex)
{
if (!defaultfile)
{ //It is not an error if there is no file at the default location
error = new PSQLException(GT.tr("Could not open SSL certificate file {0}.", new Object[]{certfile}), PSQLState.CONNECTION_FAILURE, ioex);
}
return null;
}catch (CertificateException gsex)
{
error = new PSQLException(GT.tr("Loading the SSL certificate {0} into a KeyManager failed.", new Object[]{certfile}), PSQLState.CONNECTION_FAILURE, gsex);
return null;
}
cert = (X509Certificate[]) certs.toArray(new X509Certificate[certs.size()]);
}
return cert;
}
public String[] getClientAliases(String keyType, Principal[] issuers) {
String alias = chooseClientAlias(new String[]{keyType}, issuers, (Socket)null);
return (alias==null ? new String[]{} : new String[]{alias});
}
public PrivateKey getPrivateKey(String alias) {
RandomAccessFile raf = null;
try
{
if (key==null && keyfile!=null) //If keyfile is null, we do not load the key
{ //The private key must be loaded
if (cert==null)
{ //We need the certificate for the algorithm
if(getCertificateChain("user")==null)
return null; //getCertificateChain failed...
}
try
{
raf = new RandomAccessFile(new File(keyfile), "r");
}
catch (FileNotFoundException ex)
{
if (!defaultfile)
{ //It is not an error if there is no file at the default location
throw ex;
}
return null;
}
byte[] keydata = new byte[(int)raf.length()];
raf.readFully(keydata);
raf.close();
raf = null;
KeyFactory kf = KeyFactory.getInstance(cert[0].getPublicKey().getAlgorithm());
try {
KeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec (keydata);
key = kf.generatePrivate (pkcs8KeySpec);
}
catch (InvalidKeySpecException ex) //The key might be password protected
{
EncryptedPrivateKeyInfo ePKInfo = new EncryptedPrivateKeyInfo(keydata);
Cipher cipher;
try
{
cipher = Cipher.getInstance(ePKInfo.getAlgName());
} catch (NoSuchPaddingException npex)
{ //Why is it not a subclass of NoSuchAlgorithmException?
throw new NoSuchAlgorithmException(npex.getMessage(),npex);
}
//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.", null), PSQLState.CONNECTION_FAILURE, ucex);
} else {
error = new PSQLException(GT.tr("Could not read password for SSL key file by callbackhandler {0}.", new Object[]{cbh.getClass().getName()}), PSQLState.CONNECTION_FAILURE, ucex);
}
return null;
}
try
{
PBEKeySpec pbeKeySpec = new PBEKeySpec(pwdcb.getPassword());
// Now create the Key from the PBEKeySpec
SecretKeyFactory skFac = SecretKeyFactory.getInstance(ePKInfo.getAlgName());
Key pbeKey = skFac.generateSecret(pbeKeySpec);
// Extract the iteration count and the salt
AlgorithmParameters algParams = ePKInfo.getAlgParameters();
cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
// Decrypt the encryped private key into a PKCS8EncodedKeySpec
KeySpec pkcs8KeySpec = ePKInfo.getKeySpec(cipher);
key = kf.generatePrivate (pkcs8KeySpec);
}
catch (GeneralSecurityException ikex)
{
error = new PSQLException(GT.tr("Could not decrypt SSL key file {0}.", new Object[]{keyfile}), PSQLState.CONNECTION_FAILURE, ikex);
return null;
}
}
}
}
catch (IOException ioex)
{
if (raf != null) {
try { raf.close(); } catch (IOException ex) { };
}
error = new PSQLException(GT.tr("Could not read SSL key file {0}.", new Object[]{keyfile}), PSQLState.CONNECTION_FAILURE, ioex);
}
catch(NoSuchAlgorithmException ex)
{
error = new PSQLException(GT.tr("Could not find a java cryptographic algorithm: {0}.", new Object[]{ex.getMessage()}), PSQLState.CONNECTION_FAILURE, ex);
return null;
}
return key;
}
public String[] getServerAliases(String keyType, Principal[] issuers) {
return new String[]{};
}
}