-
-
Save saumilsdk/1e17e30e33d0a18f44ce4e2b5841b281 to your computer and use it in GitHub Desktop.
/** | |
* Utility class to read encrypted PEM files and generate a SSL Socket Factory based on the provided certificates. | |
* This utility also support an option to enable and disable server hostname verification. | |
* | |
* Note: Java use jks (Java KeyStore) format, but openssl usual use pem format. We can convert it by keytool (jdk build-in tool), or use | |
* BouncyCastle library to handle pem. | |
* | |
* The original code is by Sharon Asher (link below). I have modified it to use a newer version of the BouncyCastle Library (v1.68) | |
* Add below dependencies to work with this util. | |
* org.bouncycastle:bcpkix-jdk15on:1.68 | |
* | |
* Reference - https://gist.github.com/sharonbn/4104301 and https://gist.github.com/10gic/b204dd9016f0b348797d94861d7962b9 | |
* @author - Saumil, Kapadia (https://github.com/saumilsdk) | |
* @version - 3.0 | |
*/ | |
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; | |
import org.bouncycastle.cert.X509CertificateHolder; | |
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | |
import org.bouncycastle.jce.provider.BouncyCastleProvider; | |
import org.bouncycastle.openssl.PEMDecryptorProvider; | |
import org.bouncycastle.openssl.PEMEncryptedKeyPair; | |
import org.bouncycastle.openssl.PEMKeyPair; | |
import org.bouncycastle.openssl.PEMParser; | |
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; | |
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; | |
import java.io.FileReader; | |
import java.io.IOException; | |
import java.net.Socket; | |
import java.security.KeyManagementException; | |
import java.security.KeyPair; | |
import java.security.KeyStore; | |
import java.security.KeyStoreException; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.PrivateKey; | |
import java.security.Security; | |
import java.security.UnrecoverableKeyException; | |
import java.security.cert.Certificate; | |
import java.security.cert.CertificateException; | |
import java.security.cert.X509Certificate; | |
import javax.net.ssl.KeyManagerFactory; | |
import javax.net.ssl.SSLContext; | |
import javax.net.ssl.SSLEngine; | |
import javax.net.ssl.SSLSocketFactory; | |
import javax.net.ssl.TrustManager; | |
import javax.net.ssl.TrustManagerFactory; | |
import javax.net.ssl.X509ExtendedTrustManager; | |
import javax.net.ssl.X509TrustManager; | |
public class SslUtil { | |
private SslUtil() { | |
} | |
/** | |
* Create an SslSocketFactory using PEM encrypted certificate files. Mutual SSL | |
* Authentication is NOT supported. | |
* | |
* @param caCrtFile CA certificate of remote server. | |
* @param serverHostnameVerification Enable/disable verification of server | |
* certificate DNS and hostname. | |
* @return | |
* @throws CertificateException | |
* @throws IOException | |
* @throws KeyStoreException | |
* @throws NoSuchAlgorithmException | |
* @throws KeyManagementException | |
*/ | |
public static SSLSocketFactory getSSLSocketFactory(final String caCrtFile, boolean serverHostnameVerification) | |
throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, | |
KeyManagementException { | |
/** | |
* Add BouncyCastle as a Security Provider | |
*/ | |
Security.addProvider(new BouncyCastleProvider()); | |
JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter().setProvider("BC"); | |
/** | |
* Load Certificate Authority (CA) certificate | |
*/ | |
X509CertificateHolder caCertHolder = (X509CertificateHolder) readPEMFile(caCrtFile); | |
X509Certificate caCert = certificateConverter.getCertificate(caCertHolder); | |
/** | |
* CA certificate is used to authenticate server | |
*/ | |
KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
caKeyStore.load(null, null); | |
caKeyStore.setCertificateEntry("ca-certificate", caCert); | |
/** | |
* Create SSL socket factory | |
*/ | |
SSLContext context = SSLContext.getInstance("TLSv1.2"); | |
context.init(null, | |
serverHostnameVerification ? getTrustManagers(caKeyStore) : getUnsafeTrustManagers(caKeyStore), null); | |
/** | |
* Return the newly created socket factory object | |
*/ | |
return context.getSocketFactory(); | |
} | |
/** | |
* Create an SslSocketFactory using PEM encrypted certificate files. Mutual SSL | |
* Authentication is supported. | |
* | |
* @param caCrtFile CA certificate of remote server. | |
* @param crtFile certificate file of client. | |
* @param keyFile key file of client. | |
* @param password password of key file. | |
* @param serverHostnameVerification Enable/disable verification of server | |
* certificate DNS and hostname. | |
* @return | |
* @throws CertificateException | |
* @throws IOException | |
* @throws KeyStoreException | |
* @throws NoSuchAlgorithmException | |
* @throws KeyManagementException | |
* @throws UnrecoverableKeyException | |
*/ | |
public static SSLSocketFactory getSSLSocketFactory(final String caCrtFile, final String crtFile, | |
final String keyFile, final String password, boolean serverHostnameVerification) | |
throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, | |
KeyManagementException, UnrecoverableKeyException { | |
/** | |
* Add BouncyCastle as a Security Provider | |
*/ | |
Security.addProvider(new BouncyCastleProvider()); | |
JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter().setProvider("BC"); | |
/** | |
* Load Certificate Authority (CA) certificate | |
*/ | |
X509CertificateHolder caCertHolder = (X509CertificateHolder) readPEMFile(caCrtFile); | |
X509Certificate caCert = certificateConverter.getCertificate(caCertHolder); | |
/** | |
* Load client certificate | |
*/ | |
X509CertificateHolder certHolder = (X509CertificateHolder) readPEMFile(crtFile); | |
X509Certificate cert = certificateConverter.getCertificate(certHolder); | |
/** | |
* Load client private key | |
*/ | |
Object keyObject = readPEMFile(keyFile); | |
JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter().setProvider("BC"); | |
PrivateKey privateKey = null; | |
if (keyObject instanceof PEMEncryptedKeyPair) { | |
PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); | |
KeyPair keyPair = keyConverter.getKeyPair(((PEMEncryptedKeyPair) keyObject).decryptKeyPair(provider)); | |
privateKey = keyPair.getPrivate(); | |
} else if (keyObject instanceof PEMKeyPair) { | |
KeyPair keyPair = keyConverter.getKeyPair((PEMKeyPair) keyObject); | |
privateKey = keyPair.getPrivate(); | |
} else if (keyObject instanceof PrivateKeyInfo) { | |
privateKey = keyConverter.getPrivateKey((PrivateKeyInfo) keyObject); | |
} else { | |
throw new IOException(String.format("Unsported type of keyFile %s", keyFile)); | |
} | |
/** | |
* CA certificate is used to authenticate server | |
*/ | |
KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
caKeyStore.load(null, null); | |
caKeyStore.setCertificateEntry("ca-certificate", caCert); | |
/** | |
* Client key and certificates are sent to server so it can authenticate the | |
* client. (server send CertificateRequest message in TLS handshake step). | |
*/ | |
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
clientKeyStore.load(null, null); | |
clientKeyStore.setCertificateEntry("certificate", cert); | |
clientKeyStore.setKeyEntry("private-key", privateKey, password.toCharArray(), new Certificate[] { cert }); | |
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); | |
keyManagerFactory.init(clientKeyStore, password.toCharArray()); | |
/** | |
* Create SSL socket factory | |
*/ | |
SSLContext context = SSLContext.getInstance("TLSv1.2"); | |
context.init(keyManagerFactory.getKeyManagers(), | |
serverHostnameVerification ? getTrustManagers(caKeyStore) : getUnsafeTrustManagers(caKeyStore), null); | |
/** | |
* Return the newly created socket factory object | |
*/ | |
return context.getSocketFactory(); | |
} | |
private static Object readPEMFile(String filePath) throws IOException { | |
try (PEMParser reader = new PEMParser(new FileReader(filePath))) { | |
return reader.readObject(); | |
} | |
} | |
private static TrustManager[] getTrustManagers(KeyStore caKeyStore) | |
throws NoSuchAlgorithmException, KeyStoreException { | |
TrustManagerFactory trustManagerFactory = TrustManagerFactory | |
.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | |
trustManagerFactory.init(caKeyStore); | |
return trustManagerFactory.getTrustManagers(); | |
} | |
/** | |
* This method checks server and client certificates but overrides server hostname verification. | |
* @param caKeyStore | |
* @return | |
* @throws NoSuchAlgorithmException | |
* @throws KeyStoreException | |
' */ | |
private static TrustManager[] getUnsafeTrustManagers(KeyStore caKeyStore) | |
throws NoSuchAlgorithmException, KeyStoreException { | |
X509TrustManager standardTrustManager = (X509TrustManager) getTrustManagers(caKeyStore)[0]; | |
return new TrustManager[] { new X509ExtendedTrustManager() { | |
@Override | |
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { | |
standardTrustManager.checkClientTrusted(chain, authType); | |
} | |
@Override | |
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { | |
standardTrustManager.checkServerTrusted(chain, authType); | |
} | |
@Override | |
public X509Certificate[] getAcceptedIssuers() { | |
return standardTrustManager.getAcceptedIssuers(); | |
} | |
@Override | |
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) | |
throws CertificateException { | |
standardTrustManager.checkClientTrusted(chain, authType); | |
} | |
@Override | |
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) | |
throws CertificateException { | |
standardTrustManager.checkServerTrusted(chain, authType); | |
} | |
@Override | |
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) | |
throws CertificateException { | |
standardTrustManager.checkClientTrusted(chain, authType); | |
} | |
@Override | |
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) | |
throws CertificateException { | |
standardTrustManager.checkServerTrusted(chain, authType); | |
} | |
} }; | |
} | |
} |
Hi,
What location are using for placing the certificate and key files in the android project?
The following path shows the file not found exception.
I placed the files in the assets folder and specified the path as caCertFile = "file:///android_asset/" + "AmazonRootCA1.pem";private static Object readPEMFile(String filePath) throws IOException { try (PEMParser reader = new PEMParser(new FileReader(filePath))) { return reader.readObject(); } }
Please specify absolute path without
file:///
Thanks, I found another method instead of specifying the path.
I am using the assetManager
Hi,
What location are using for placing the certificate and key files in the android project?
The following path shows the file not found exception.
I placed the files in the assets folder and specified the path as caCertFile = "file:///android_asset/" + "AmazonRootCA1.pem";private static Object readPEMFile(String filePath) throws IOException { try (PEMParser reader = new PEMParser(new FileReader(filePath))) { return reader.readObject(); } }
Please specify absolute path without
file:///
Thanks, I found another method instead of specifying the path.
I am using the assetManager
Great. Can you put that snippet so we can also use that?
I get this error when giving the absolute path : String caCrtFile = "Users/test/AndroidStudioProjects/MyApplication/app/src/main/res/raw/ca.crt";
java.io.FileNotFoundException: Users/Steve/AndroidStudioProjects/MyApplication/app/src/main/res/raw/ca.crt: open failed: ENOENT (No such file or directory)
2023-09-15 17:04:01.212 24260-24260 System.err com.example.myapplication W at libcore.io.IoBridge.open(IoBridge.java:574)
@Deazyhome were you able to figure this out? Basically you need to keep the ca.crt at some path and use that path in the code.
@saumilsdk No I still haven't solved my problem, I read in an article that it's not possible to use an absolute path in Android Studio. Where should I place my Ca, key and crt certificates. Can you please show me an example, because I'm really lost, I've been trying for 2 weeks but I can't read the certificates.
@ssmr1982 you can share with us the method that allows you to feed the method getSSLSocketFactory
Please specify absolute path without
file:///