The goal
I'm working on implementing communication with Client-Certificate.
Step 1: Create a PKCS#10 request (CSR) and give it to the my server for signing. The server contacts passes the CSR to the CA, and the CA signs it, and returns a PKCS#7 (with the signed PKCS#10 and the CA's certificate).
Step 2: Create a PKCS#12, store it securely on the Android device
Step 3: Create SSL connection so that the client will be authenticated according to the certificate.
Now, Step 1 works perfectly using SpongyCastle 1.50.0.0, but I'm stuck on the other steps...
I'm currently getting an SSL-Handshake exception, but I got the feeling that I should re-think on my implementation.
The question
Does anyone know how to implement the flow? How to create and store whatever's needed for a client side certificate to work well with Android's SSLContext, and how to create such SSLContext?
What I tried so far
My first attempt was to use the KeyChain, but we'd like to avoid the user interaction as described there. My second attempt was to follow Rich Freedman's steps, but I don't know how to create a PKCS#12 from the PKCS#7 and the private key. For persistence, I went over this post, but (a) it's C#, (b) it's unencrypted and (c) I think that the android platform has a better keys persistence mechanism, one that I yet know nothing about. Lastly, this code (for creating a PKCS12 from PEM and PKCS#7) didn't work as well, as I didn't know how to get a CER file and the rest of the things it needs.
Thanks!
Maybe not the best code, but it works, it does not strictly answer all you questions but maybe you will find pieces you can use.
Your flow is good, I'm doing the almost the same thing.
I'm keeping my keys in dynamically created keystore. Additionaly i have keystore with trusted certificates created with openssl tool.
For communication I've used okHttp + retrofit
https://github.com/square/okhttp
https://github.com/square/retrofit
Generate KeyPair
:
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.genKeyPair();
return keyPair;
}
Generate csr:
private static PKCS10CertificationRequest generateCSRFile(KeyPair keyPair) throws IOException, OperatorCreationException {
String principal = "CN=company1, OU=company1, O=company1, C=GB";
AsymmetricKeyParameter privateKey = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
AlgorithmIdentifier signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder()
.find("SHA1WITHRSA");
AlgorithmIdentifier digestAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find("SHA-1");
ContentSigner signer = new BcRSAContentSignerBuilder(signatureAlgorithm, digestAlgorithm).build(privateKey);
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(
principal), keyPair.getPublic());
ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
extensionsGenerator.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(true));
extensionsGenerator.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign
| KeyUsage.cRLSign));
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionsGenerator.generate());
PKCS10CertificationRequest csr = csrBuilder.build(signer);
return csr;
}
Send csr (you may need to convert it to pem format), receive certificate .
Init keystore:
KeyStore store = KeyStore.getInstance("BKS");
InputStream in;
try {
in = App.getInstance().getApplicationContext().openFileInput(filename);
try {
store.load(in, password);
} finally {
in.close();
}
} catch (FileNotFoundException e) {
//create new keystore
store.load(null, password);
}
Init truststore:
KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream in = App.getInstance().getApplicationContext().getResources().openRawResource(R.raw.truststore);
try {
trustStore.load(in, trustorePassword);
} finally {
in.close();
}
Add key to keystore (make sure your private key, and certificate match, keystore won't throw exception if they don't, and with okHttp this can cause libssl crashes (only on devices with api below 4.1):
keyStore.setKeyEntry(alias, privateKey, password, new X509Certificate[]{certificate});
Create okHttpClient with its own SSLContext
:
OkHttpClient client = new OkHttpClient();
KeyStore keyStore = App.getInstance().getKeyStoreUtil().getKeyStore();
KeyStore trustStore = App.getInstance().getKeyStoreUtil().getTrustStore();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyStorePassword);
SSLContext sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
client.setSslSocketFactory(sslCtx.getSocketFactory());
client.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
Look at Nikolay Elenkov blog, you can find many usefull informations with source code as well.
- http://nelenkov.blogspot.com/
- http://nelenkov.blogspot.com/2011/11/using-ics-keychain-api.html
- http://nelenkov.blogspot.in/2011/12/ics-trust-store-implementation.html
- http://nelenkov.blogspot.com/2011/12/using-custom-certificate-trust-store-on.html
- http://nelenkov.blogspot.com/2012/05/storing-application-secrets-in-androids.html
- http://nelenkov.blogspot.com/2013/08/credential-storage-enhancements-android-43.html
@edit
Post your exception
@edit2
In your case you need to extract your X509Certificate
from webservice response, store it in keystore with privatekey used for generating csr request and store CA cert in another keystore which will work as truststore. (It can be the same keystore, but it's not recommended).