We have a thick java client and do SSL server and client authentication.
This scenario is on Mac OS.
Our users get a certificate with non exportable private key.
Now if i want to do SSL client authentication it fails for this certificate. Below is the code snippet which i am using`to crearte the key store( have the certificate alias with me) with which i create the KeyManager which is in turn passed to the SSLContext.
KeyStore systemKeyStore = KeyStore.getInstance("KEYCHAINSTORE");
systemKeyStore.load(null, null);
resultKeyStore = KeyStore.getInstance("JKS");
resultKeyStore.load(null, null);
if (certalias != null && !certalias.trim().equals("") && systemKeyStore.containsAlias(certalias) && systemKeyStore.isKeyEntry(certalias)) {
try {
Key key = systemKeyStore.getKey(certalias, "t".toCharArray());
Certificate certs[] = systemKeyStore.getCertificateChain(certalias);
resultKeyStore.setKeyEntry(certalias, key, "randompw".toCharArray(), certs);
} catch (Exception e) {
}
The line
Key key = systemKeyStore.getKey(certalias, "t".toCharArray()); fails as it the key object returned is null
Question is , is it possible to do SSL client authentication with a certificate with private key marked as non exportable ?
UPDATE
As mentioned below in one of my comments. I tried using the default SSL context property by passing the SSL system properties. But still it did not work. Below is my code for that
System.setProperty("javax.net.ssl.trustStore", "cacerts.jks");
System.setProperty("javax.net.ssl.keyStoreType", "KEYCHAINSTORE");
System.setProperty("javax.net.ssl.keyStore", "KEYCHAINSTORE");
if (socketFactory == null) {
// socketFactory = initializeSocketFactory(Settings.isCertificateAuth(), Settings.getClientCertAlias());
socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
}
Let me know if i need to provide any further details
Related
I am trying to setup a SSL Socket connection (and am doing the following on the client)
I generate a Certificte Signing Request to obtain a signed client certificate
Now I have a private key (used during the CSR), a signed client certificate and root certificate (obtained out of band).
I add the private key and signed client certificate to a cert chain and add that to the key manager. and the root cert to the trust manager.
But I get a bad certificate error.
I am pretty sure I am using the right certs. Should I add the signed client cert to the trust manager as well? Tried that, no luck still.
//I add the private key and the client cert to KeyStore ks
FileInputStream certificateStream = new FileInputStream(clientCertFile);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
java.security.cert.Certificate[] chain = {};
chain = certificateFactory.generateCertificates(certificateStream).toArray(chain);
certificateStream.close();
String privateKeyEntryPassword = "123";
ks.setEntry("abc", new KeyStore.PrivateKeyEntry(privateKey, chain),
new KeyStore.PasswordProtection(privateKeyEntryPassword.toCharArray()));
//Add the root certificate to keystore jks
FileInputStream is = new FileInputStream(new File(filename));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
java.security.cert.X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
System.out.println("Certificate Information: ");
System.out.println(cert.getSubjectDN().toString());
jks.setCertificateEntry(cert.getSubjectDN().toString(), cert);
//Initialize the keymanager and trustmanager and add them to the SSL context
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "123".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(jks);
Is there some sort of certificate chain that I need to create here?
I had a p12 with these components as well and upon using pretty similar code, adding the private key to the keymanager and the root cert from p12 to the trust manager I could make it work. But now I need to make it work without the p12.
EDIT: Stack trace was requested. Hope this should suffice. (NOTE: I masked the filenames)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:136)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1720)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:954)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1138)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1165)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1149)
at client.abc2.openSocketConnection(abc2.java:33)
at client.abc1.runClient(abc1.java:63)
at screens.app.abc.validateLogin(abc.java:197)
... 32 more
You need to add the root cert to the keystore as well.
I got this error when I removed these 2 lines. If you know your keystore has the right certs, make sure your code is looking at the right keystore.
System.setProperty("javax.net.ssl.keyStore", <keystorePath>));
System.setProperty("javax.net.ssl.keyStorePassword",<keystorePassword>));
I also needed this VM argument:
-Djavax.net.ssl.trustStore=/app/certs/keystore.jk
See here for more details:
https://stackoverflow.com/a/34311797/1308453
Provided that the server certificate is signed and valid, you only need to open the connection as usual:
import java.net.*;
import java.io.*;
public class URLConnectionReader {
public static void main(String[] args) throws Exception {
URL google = new URL("https://www.google.com/");
URLConnection yc = google.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(
yc.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
Note that the URL has the HTTPS schema to indicate the use of SSL.
If the server's certificate is signed but you are accessing using a different IP address/domain name than the one in the certificate, you can bypass hostname verification with this:
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName,SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
If the certificate is not signed then you need to add it to the keystore used by the JVM (useful commands).
My network code is written in NDK (cURL + OpenSSL) and I'd like to use a certificate from Android's credential storage as a client certificate for a SSL connection. Moreover, I'd like to offer a list of available certificates to the user, so he can choose the certificate for the connection. Unfortunately, I cannot obtain a certificate from the key storage.
I installed a client certificate to "Credential storage" (Settings -> Secutrity -> ...) on my Android device (5.0.2), but I'm not able to access it from Java. I tried to call following code, but the key storage is empy, athough the certificate is installed in the Credential storage:
//KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();
while(aliases.hasMoreElements()) {
String alias = (String)aliases.nextElement();
Log.i("app", "alias name: " + alias);
Certificate certificate = ks.getCertificate(alias);
Log.i("app", certificate.toString());
}
What am I doing wrong?
User credentials installed on the device are available through Android KeyChain, not Android KeyStore
The KeyChain class provides access to private keys and their corresponding certificate chains in credential storage.
Use choosePrivateKeyAlias to prompt the user for selecting the certificate. The system launches an Activity for the user to select the alias and returns it via a callback. Then use getPrivateKey and getCertificate to recover the key and the corresponding certificate chain
KeyChain.choosePrivateKeyAlias(activity, new KeyChainAliasCallback() {
public void alias(String alias) {
//do something with the selected alias
}
},
new String[] { KeyProperties.KEY_ALGORITHM_RSA, "DSA"}, // List of acceptable key types. null for any
null, // issuer, null for any
null, // host name of server requesting the cert, null if unavailable
-1, // port of server requesting the cert, -1 if unavailable
""); // alias to preselect, null if unavailable
PrivateKey privateKey = KeyChain.getPrivateKey(activity, alias);
X509Certificate chain[] = KeyChain.getCertificateChain(activity, alias);
Try something like this:
X509TrustManager manager = null;
FileInputStream fs = null;
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try
{
fs = new FileInputStream(System.getProperty("javax.net.ssl.trustStore"));
keyStore.load(fs, null);
}
finally
{
if (fs != null) { fs.close(); }
}
trustManagerFactory.init(keyStore);
TrustManager[] managers = trustManagerFactory.getTrustManagers();
for (TrustManager tm : managers)
{
if (tm instanceof X509TrustManager)
{
manager = (X509TrustManager) tm;
break;
}
}
i found some tutorial to handle with https server and a https client. i created some keystore and it works fine. But i have some question which is not clear from the tutorial.
this is my https-server
public class HTTPSServer {
private int port = 9999;
private boolean isServerDone = false;
public static void main(String[] args) {
HTTPSServer server = new HTTPSServer();
server.run();
}
HTTPSServer() {
}
HTTPSServer(int port) {
this.port = port;
}
// Create the and initialize the SSLContext
private SSLContext createSSLContext() {
try {
//Returns keystore object in definied type, here jks
KeyStore keyStore = KeyStore.getInstance("JKS");
//loads the keystore from givin input stream, and the password to unclock jks
keyStore.load(new FileInputStream("x509-ca.jks"), "password".toCharArray());
// Create key manager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, "password".toCharArray());
KeyManager[] km = keyManagerFactory.getKeyManagers();
// Create trust manager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(keyStore);
TrustManager[] tm = trustManagerFactory.getTrustManagers();
// opens a secure socket with definied protocol
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
//System.out.println(keyStore.getCertificate("root").getPublicKey());
//System.out.println(keyStore.isKeyEntry("root"));
sslContext.init(km, tm, null);
return sslContext;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
// Start to run the server
public void run() {
SSLContext sslContext = this.createSSLContext();
try {
// Create server socket factory
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
// Create server socket
SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(this.port);
System.out.println("SSL server started");
while (!isServerDone) {
SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
// Start the server thread
new ServerThread(sslSocket).start();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// Thread handling the socket from client
static class ServerThread extends Thread {
private SSLSocket sslSocket = null;
ServerThread(SSLSocket sslSocket) {
this.sslSocket = sslSocket;
}
public void run() {
sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
//System.out.println("HIER: " + sslSocket.getHandshakeSession());
//Klappt nicht, auch nicht, wenn der Client diese Zeile ebenfalls besitzt
//sslSocket.setEnabledCipherSuites(new String[]{"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"});
try {
// Start handshake
sslSocket.startHandshake();
// Get session after the connection is established
SSLSession sslSession = sslSocket.getSession();
System.out.println(sslSession.getPeerHost());
System.out.println(sslSession.getLocalCertificates());
System.out.println("\tProtocol : " + sslSession.getProtocol());
System.out.println("\tCipher suite : " + sslSession.getCipherSuite());
System.out.println("\tSession context : " + sslSession.getSessionContext());
//System.out.println("\tPeer pricipal of peer : " + sslSession.getPeerPrincipal());
// Start handling application content
InputStream inputStream = sslSocket.getInputStream();
OutputStream outputStream = sslSocket.getOutputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream));
String line = null;
String[] suites = sslSocket.getSupportedCipherSuites();
for (int i = 0; i < suites.length; i++) {
//System.out.println(suites[i]);
//System.out.println(sslSession.getCipherSuite());
}
while ((line = bufferedReader.readLine()) != null) {
System.out.println("Inut : " + line);
if (line.trim().isEmpty()) {
break;
}
}
// Write data
printWriter.print("HTTP/1.1 200\r\n");
printWriter.flush();
sslSocket.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
And this is my output:
SSL server started
127.0.0.1
null
Protocol : TLSv1.2
Cipher suite : TLS_DH_anon_WITH_AES_128_GCM_SHA256
Session context : sun.security.ssl.SSLSessionContextImpl#781df1a4
I want to know, why the line
System.out.println(sslSession.getLocalCertificates());
prints out "null"?
Thank you a lot, Mira
From the documentation:
Certificate[] getLocalCertificates()
Returns the certificate(s) that were sent to the peer during handshaking.
Note: This method is useful only when using certificate-based cipher suites.
When multiple certificates are available for use in a handshake, the implementation chooses what it considers the "best" certificate chain available, and transmits that to the other side. This method allows the caller to know which certificate chain was actually used.
Returns:
an ordered array of certificates, with the local certificate first followed by any certificate authorities. If no certificates were sent, then null is returned.
The part we care about is "Returns the certificate(s) that were sent to the peer during handshaking.", and "This method is useful only when using certificate-based cipher suites.".
Given that it is returning null, we can assume you are not sending any certificates to the client. But it's also HTTPS, so what gives? Well, it looks like you're using TLS_DH_anon_WITH_AES_128_GCM_SHA256, which is, as the name suggests, anonymous. As per the OpenSSL Wiki:
Anonymous Diffie-Hellman uses Diffie-Hellman, but without authentication. Because the keys used in the exchange are not authenticated, the protocol is susceptible to Man-in-the-Middle attacks. Note: if you use this scheme, a call to SSL_get_peer_certificate will return NULL because you have selected an anonymous protocol. This is the only time SSL_get_peer_certificate is allowed to return NULL under normal circumstances.
While this is applicable to OpenSSL, it would appear to be the same in Java - that is, you're not using a certificate-based cipher. Someone with more knowledge of TLS would need to jump in, but it looks like AES keys are generated, and they're sent to the client, but the client has no assurance those keys came from you, whereas normally you would generate the keys, and then sign / encrypt (not 100% sure) those keys with an RSA key to prove they came from you.
To fix this, I believe you would need to select a different cipher suite, e.g. TLS_RSA_WITH_AES_128_GCM_SHA256. I'm not 100% sure how you would do this, but that would appear to be the solution.
sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
You are enabling all the anonymous and low-grade cipher suites, so you are allowing the server not to send a certificate, so it doesn't send one, so it doesn't give you one in getLocalCertificates().
Remove this line.
I am trying to create SSLSocket with the following properties:
it should do SSL certificate validation.
it should allow to set custom SNI (server name indication).
From what I have understood:
SSLCertificateSocketFactory.getDefault does not do allow you to set custom SNI and but does SSL cert validation.
SSLCertificateSocketFactory.getInsecure does allow you to set custom SNI and but does not do SSL cert validation.
So the best possible solution could be to enable SSL cert validation on SSLSocket created using SSLCertificateSocketFactory.getInsecure.
I am using the following code to set custom SNI:
SSLCertificateSocketFactory ssf = (SSLCertificateSocketFactory) SSLCertificateSocketFactory
.getInsecure(Utils.getConnectTimeOutForHost(m_host),
null);
m_sslsocket = (SSLSocket) ssf.createSocket(m_socket, m_host,
m_port, false);
m_sslsocket.setEnabledProtocols(m_sslsocket.getSupportedProtocols());
String hostName = getSNIHost();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Log.i(TAG, "Setting SNI hostname");
ssf.setHostname(m_sslsocket, hostName);
} else {
try {
java.lang.reflect.Method setHostnameMethod = m_sslsocket
.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(m_sslsocket, hostName);
} catch (Exception e) {
Log.w(TAG, "SNI not useable", e);
}
}
I have created my own SSLSocketFactory as explained in this question
private SSLSocketFactory newSslSocketFactory() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get theraw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
Context context = this.activity;
Resources _resources = context.getResources();
InputStream in = _resources.openRawResource(R.raw.mystore);
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
trusted.load(in, "mypassword".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
Actually i need to set this SSLSocketFactory on the HttpsURLConnection before connecting. But when i try to set it on HttpsURLConnection by calling
(HttpsURLConnection )connection.setSSLSocketFactory(trusted);
At this point am facing cast class error between 2 packages org.apache.http.conn.ssl.SSLSocketFactory and javax.net.ssl.SSLSocketFactory.
How to solve this?
Am not getting any exception with the above piece of code.
But the problem is that, when am trying to set the SSLSocketFactory on the HttpsURLConnection using:
(HttpsURLConnection )connection.setSSLSocketFactory(trusted)
It is showing "The method setSSLSocketFactory(SSLSocketFactory) in the type HttpsURLConnection is not applicable for the arguments(SSLSocketFactory)".
Because the method setSSLSocketFactory is there in both the packages.