Thread: SSL - Providing client certificates
Hello,
http://jdbc.postgresql.org/documentation/83/ssl-factory.html states :
"Specifically it would be nice to be able to provide client certificates to be validated by the server."
http://jdbc.postgresql.org/documentation/83/ssl-client.html refers to validating the server's identity only, using javax.net.ssl.trustStore JVM-wide setting.
However, if we set javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword, the SSL connection is established and the client certificate is verified.
This seems contradictory with the documntation, or I am missing something.
I would be happy to read your comments.
Thank you.
On Fri, 20 Feb 2009, Saleem EDAH-TALLY wrote: > However, if we set javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword, > the SSL connection is established and the client certificate is verified. > When the code was first written, this wasn't tested and it was just assumed that it wouldn't work. Recently we got a report that it did work, but the documentation was not updated. What I don't understand is how it selects the certificate to send. If you have multiple keys in your keystore, how do you indicate which one to use? Kris Jurka
Kris Jurka <books 'at' ejurka.com> writes: > On Fri, 20 Feb 2009, Saleem EDAH-TALLY wrote: > >> However, if we set javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword, >> the SSL connection is established and the client certificate is verified. >> > > When the code was first written, this wasn't tested and it was just > assumed that it wouldn't work. Recently we got a report that it did > work, but the documentation was not updated. > > What I don't understand is how it selects the certificate to send. If > you have multiple keys in your keystore, how do you indicate which one > to use? My quite limited understanding of the behaviour of SSL client authentication may potentially help a little: You initially send a certificate signing request to the admin/owner of the server (signed with your private key); when you receive the certificate reply (signed with their private key), you can build a chain of trust between you and the server, your keystore will look like: Entry type: PrivateKeyEntry Certificate chain length: 2 Certificate[1]: Owner: <you> Issuer: <server> Certificate[2]: Owner: <server> Issuer: <server> Then at the SSL handshake time, first the server presents his certificate, second it asks for a client certificate, at that time you are able to present the certificate belonging to the chain of trust containing the server certificate on top. -- Guillaume Cottenceau
Regarding Kris Jurka's question :
> > What I don't understand is how it selects the certificate to send. If
> > you have multiple keys in your keystore, how do you indicate which one
> > to use?
http://java.sun.com/javase/6/docs/api/javax/net/ssl/SSLContext.html#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom)
states
<>
Only the first instance of a particular key and/or trust manager implementation type in the array is used...
</>
Hence it's not necessary to overload a keystore or a truststore.
And who knows how the entries in the keystore are sorted an presented to SSLContext.init. My guess they are sorted in ascending order of the aliases, but it's just a guess.
As an add-on to this thread, the following class allows to build a SSLSocketFactory that is not JVM wide but specific to a connection. It can be passed as sslfactory, and a label passed as sslfactoryarg.
It may be adapted and included in the jdbc-driver if found useful.
**********************************************************************
public class SSLBoth extends javax.net.ssl.SSLSocketFactory{
private String ksFile = null;
private String ksType = null;
private String ksPwd = null;
private String tsFile = null;
private String tsType = null;
private String tsPwd = null;
private javax.net.ssl.SSLSocketFactory ssf = null;
public SSLBoth(String label) {
setParams(label);
javax.net.ssl.KeyManagerFactory kmf = makeKMF();
javax.net.ssl.TrustManagerFactory tmf = makeTMF();
javax.net.ssl.SSLContext sslc = makeSSLContext(kmf.getKeyManagers(), tmf.getTrustManagers());
ssf = sslc.getSocketFactory();
}
private void setParams(String label) {
ksFile = System.getProperty("KEYSTORE_FILE_" + label);
ksType = System.getProperty("KEYSTORE_TYPE_" + label);
ksPwd = System.getProperty("KEYSTORE_PWD_" + label);
tsFile = System.getProperty("TRUSTSTORE_FILE_" + label);
tsType = System.getProperty("TRUSTSTORE_TYPE_" + label);
tsPwd = System.getProperty("TRUSTSTORE_PWD_" + label);
}
private javax.net.ssl.KeyManagerFactory makeKMF() {
java.security.KeyStore ks = loadKeyStore(ksFile, ksType, ksPwd);
if (ks != null) {
try {
javax.net.ssl.KeyManagerFactory kmf = javax.net.ssl.KeyManagerFactory.getInstance(javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, ksPwd.toCharArray());
return kmf;
} catch (java.security.KeyStoreException ex) {
ex.printStackTrace();
} catch (java.security.UnrecoverableKeyException ex) {
ex.printStackTrace();
} catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
}
return null;
}
private javax.net.ssl.TrustManagerFactory makeTMF() {
java.security.KeyStore ts = loadKeyStore(tsFile, tsType, tsPwd);
if (ts != null) {
try {
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance(javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
return tmf;
} catch (java.security.KeyStoreException ex) {
ex.printStackTrace();
} catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
}
return null;
}
private javax.net.ssl.SSLContext makeSSLContext(javax.net.ssl.KeyManager[] km, javax.net.ssl.TrustManager[] tm) {
try {
javax.net.ssl.SSLContext sslc = javax.net.ssl.SSLContext.getInstance("TLS");
sslc.init(km, tm, new java.security.SecureRandom());
return sslc;
} catch (java.security.KeyManagementException ex) {
ex.printStackTrace();
} catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
return null;
}
public static java.security.KeyStore loadKeyStore(String ksPath, String type, String pwd) {
try {
java.security.KeyStore ks = java.security.KeyStore.getInstance(type);
java.io.FileInputStream fis = new java.io.FileInputStream(ksPath);
ks.load(fis, pwd.toCharArray());
fis.close();
return ks;
}
catch (java.security.KeyStoreException ex) {
ex.printStackTrace();
}
catch (java.io.FileNotFoundException ex) {
ex.printStackTrace();
}
catch (java.io.IOException ex) {
ex.printStackTrace();
}
catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
catch (java.security.cert.CertificateException ex) {
ex.printStackTrace();
}
return null;
}
@Override
public String[] getDefaultCipherSuites() {
return ssf.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return ssf.getSupportedCipherSuites();
}
@Override
public java.net.Socket createSocket(java.net.Socket arg0, String arg1, int arg2, boolean arg3) throws java.io.IOException {
return ssf.createSocket(arg0, arg1, arg2, arg3);
}
@Override
public java.net.Socket createSocket(String arg0, int arg1) throws java.io.IOException, java.net.UnknownHostException {
return ssf.createSocket(arg0, arg1);
}
@Override
public java.net.Socket createSocket(String arg0, int arg1, java.net.InetAddress arg2, int arg3) throws java.io.IOException, java.net.UnknownHostException {
return ssf.createSocket(arg0, arg1, arg2, arg3);
}
@Override
public java.net.Socket createSocket(java.net.InetAddress arg0, int arg1) throws java.io.IOException {
return ssf.createSocket(arg0, arg1);
}
@Override
public java.net.Socket createSocket(java.net.InetAddress arg0, int arg1, java.net.InetAddress arg2, int arg3) throws java.io.IOException {
return ssf.createSocket(arg0, arg1, arg2, arg3);
}
}
**********************************************************************
> > What I don't understand is how it selects the certificate to send. If
> > you have multiple keys in your keystore, how do you indicate which one
> > to use?
http://java.sun.com/javase/6/docs/api/javax/net/ssl/SSLContext.html#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom)
states
<>
Only the first instance of a particular key and/or trust manager implementation type in the array is used...
</>
Hence it's not necessary to overload a keystore or a truststore.
And who knows how the entries in the keystore are sorted an presented to SSLContext.init. My guess they are sorted in ascending order of the aliases, but it's just a guess.
As an add-on to this thread, the following class allows to build a SSLSocketFactory that is not JVM wide but specific to a connection. It can be passed as sslfactory, and a label passed as sslfactoryarg.
It may be adapted and included in the jdbc-driver if found useful.
**********************************************************************
public class SSLBoth extends javax.net.ssl.SSLSocketFactory{
private String ksFile = null;
private String ksType = null;
private String ksPwd = null;
private String tsFile = null;
private String tsType = null;
private String tsPwd = null;
private javax.net.ssl.SSLSocketFactory ssf = null;
public SSLBoth(String label) {
setParams(label);
javax.net.ssl.KeyManagerFactory kmf = makeKMF();
javax.net.ssl.TrustManagerFactory tmf = makeTMF();
javax.net.ssl.SSLContext sslc = makeSSLContext(kmf.getKeyManagers(), tmf.getTrustManagers());
ssf = sslc.getSocketFactory();
}
private void setParams(String label) {
ksFile = System.getProperty("KEYSTORE_FILE_" + label);
ksType = System.getProperty("KEYSTORE_TYPE_" + label);
ksPwd = System.getProperty("KEYSTORE_PWD_" + label);
tsFile = System.getProperty("TRUSTSTORE_FILE_" + label);
tsType = System.getProperty("TRUSTSTORE_TYPE_" + label);
tsPwd = System.getProperty("TRUSTSTORE_PWD_" + label);
}
private javax.net.ssl.KeyManagerFactory makeKMF() {
java.security.KeyStore ks = loadKeyStore(ksFile, ksType, ksPwd);
if (ks != null) {
try {
javax.net.ssl.KeyManagerFactory kmf = javax.net.ssl.KeyManagerFactory.getInstance(javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, ksPwd.toCharArray());
return kmf;
} catch (java.security.KeyStoreException ex) {
ex.printStackTrace();
} catch (java.security.UnrecoverableKeyException ex) {
ex.printStackTrace();
} catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
}
return null;
}
private javax.net.ssl.TrustManagerFactory makeTMF() {
java.security.KeyStore ts = loadKeyStore(tsFile, tsType, tsPwd);
if (ts != null) {
try {
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance(javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
return tmf;
} catch (java.security.KeyStoreException ex) {
ex.printStackTrace();
} catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
}
return null;
}
private javax.net.ssl.SSLContext makeSSLContext(javax.net.ssl.KeyManager[] km, javax.net.ssl.TrustManager[] tm) {
try {
javax.net.ssl.SSLContext sslc = javax.net.ssl.SSLContext.getInstance("TLS");
sslc.init(km, tm, new java.security.SecureRandom());
return sslc;
} catch (java.security.KeyManagementException ex) {
ex.printStackTrace();
} catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
return null;
}
public static java.security.KeyStore loadKeyStore(String ksPath, String type, String pwd) {
try {
java.security.KeyStore ks = java.security.KeyStore.getInstance(type);
java.io.FileInputStream fis = new java.io.FileInputStream(ksPath);
ks.load(fis, pwd.toCharArray());
fis.close();
return ks;
}
catch (java.security.KeyStoreException ex) {
ex.printStackTrace();
}
catch (java.io.FileNotFoundException ex) {
ex.printStackTrace();
}
catch (java.io.IOException ex) {
ex.printStackTrace();
}
catch (java.security.NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
catch (java.security.cert.CertificateException ex) {
ex.printStackTrace();
}
return null;
}
@Override
public String[] getDefaultCipherSuites() {
return ssf.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return ssf.getSupportedCipherSuites();
}
@Override
public java.net.Socket createSocket(java.net.Socket arg0, String arg1, int arg2, boolean arg3) throws java.io.IOException {
return ssf.createSocket(arg0, arg1, arg2, arg3);
}
@Override
public java.net.Socket createSocket(String arg0, int arg1) throws java.io.IOException, java.net.UnknownHostException {
return ssf.createSocket(arg0, arg1);
}
@Override
public java.net.Socket createSocket(String arg0, int arg1, java.net.InetAddress arg2, int arg3) throws java.io.IOException, java.net.UnknownHostException {
return ssf.createSocket(arg0, arg1, arg2, arg3);
}
@Override
public java.net.Socket createSocket(java.net.InetAddress arg0, int arg1) throws java.io.IOException {
return ssf.createSocket(arg0, arg1);
}
@Override
public java.net.Socket createSocket(java.net.InetAddress arg0, int arg1, java.net.InetAddress arg2, int arg3) throws java.io.IOException {
return ssf.createSocket(arg0, arg1, arg2, arg3);
}
}
**********************************************************************