Skip to content

Instantly share code, notes, and snippets.

@saumilsdk
Forked from rohanag12/SslUtil.java
Last active October 26, 2023 09:53
Show Gist options
  • Save saumilsdk/1e17e30e33d0a18f44ce4e2b5841b281 to your computer and use it in GitHub Desktop.
Save saumilsdk/1e17e30e33d0a18f44ce4e2b5841b281 to your computer and use it in GitHub Desktop.
Create an SslSocketFactory using PEM encrypted certificate files
/**
* 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);
}
} };
}
}
@saumilsdk
Copy link
Author

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:///

@ssmr1982
Copy link

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

@saumilsdk
Copy link
Author

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?

@Deazyhome
Copy link

I get this error when giving the absolute path : String caCrtFile = "Users/test/AndroidStudioProjects/MyApplication/app/src/main/res/raw/ca.crt";

@Deazyhome
Copy link

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)

@saumilsdk
Copy link
Author

@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.

@Deazyhome
Copy link

@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.

@Deazyhome
Copy link

@ssmr1982 you can share with us the method that allows you to feed the method getSSLSocketFactory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment