aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java267
1 files changed, 267 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java b/WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java
new file mode 100644
index 000000000..172530d51
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/networking/SelfSignedSSLCertsManager.java
@@ -0,0 +1,267 @@
+package org.wordpress.android.networking;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.http.SslCertificate;
+import android.os.Bundle;
+
+import org.wordpress.android.BuildConfig;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.ui.ActivityLauncher;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.GenericCallback;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import javax.security.auth.x500.X500Principal;
+
+public class SelfSignedSSLCertsManager {
+ private static SelfSignedSSLCertsManager sInstance;
+ private File mLocalTrustStoreFile;
+ private KeyStore mLocalKeyStore;
+ // Used to hold the last self-signed certificate chain that doesn't pass trusting
+ private X509Certificate[] mLastFailureChain;
+
+ private SelfSignedSSLCertsManager(Context ctx) throws IOException, GeneralSecurityException {
+ mLocalTrustStoreFile = new File(ctx.getFilesDir(), "self_signed_certs_truststore.bks");
+ createLocalKeyStoreFile();
+ mLocalKeyStore = loadTrustStore(ctx);
+ }
+
+ public static void askForSslTrust(final Context ctx, final GenericCallback<Void> certificateTrusted) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(ctx);
+ alert.setTitle(ctx.getString(R.string.ssl_certificate_error));
+ alert.setMessage(ctx.getString(R.string.ssl_certificate_ask_trust));
+ alert.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ SelfSignedSSLCertsManager selfSignedSSLCertsManager;
+ try {
+ selfSignedSSLCertsManager = SelfSignedSSLCertsManager.getInstance(ctx);
+ X509Certificate[] certificates = selfSignedSSLCertsManager.getLastFailureChain();
+ AppLog.i(T.NUX, "Add the following certificate to our Certificate Manager: " +
+ Arrays.toString(certificates));
+ selfSignedSSLCertsManager.addCertificates(certificates);
+ } catch (GeneralSecurityException e) {
+ AppLog.e(T.API, e);
+ } catch (IOException e) {
+ AppLog.e(T.API, e);
+ }
+ if (certificateTrusted != null) {
+ certificateTrusted.callback(null);
+ }
+ }
+ }
+ );
+ alert.setNeutralButton(R.string.ssl_certificate_details, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ ActivityLauncher.viewSSLCerts(ctx);
+ }
+ });
+ alert.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+ alert.show();
+ }
+
+ public static synchronized SelfSignedSSLCertsManager getInstance(Context ctx)
+ throws IOException, GeneralSecurityException {
+ if (sInstance == null) {
+ sInstance = new SelfSignedSSLCertsManager(ctx);
+ }
+ return sInstance;
+ }
+
+ public void addCertificates(X509Certificate[] certs) throws IOException, GeneralSecurityException {
+ if (certs == null || certs.length == 0) {
+ return;
+ }
+
+ for (X509Certificate cert : certs) {
+ String alias = hashName(cert.getSubjectX500Principal());
+ mLocalKeyStore.setCertificateEntry(alias, cert);
+ }
+ saveTrustStore();
+ // reset the Volley queue Otherwise new certs are not used
+ WordPress.setupVolleyQueue();
+ }
+
+ public void addCertificate(X509Certificate cert) throws IOException, GeneralSecurityException {
+ if (cert == null) {
+ return;
+ }
+
+ String alias = hashName(cert.getSubjectX500Principal());
+ mLocalKeyStore.setCertificateEntry(alias, cert);
+ saveTrustStore();
+ }
+
+ public KeyStore getLocalKeyStore() {
+ return mLocalKeyStore;
+ }
+
+ private KeyStore loadTrustStore(Context ctx) throws IOException, GeneralSecurityException {
+ KeyStore localTrustStore = KeyStore.getInstance("BKS");
+ InputStream in = new FileInputStream(mLocalTrustStoreFile);
+ try {
+ localTrustStore.load(in, BuildConfig.DB_SECRET.toCharArray());
+ } finally {
+ in.close();
+ }
+ return localTrustStore;
+ }
+
+ private void saveTrustStore() throws IOException, GeneralSecurityException {
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(mLocalTrustStoreFile);
+ mLocalKeyStore.store(out, BuildConfig.DB_SECRET.toCharArray());
+ } finally {
+ if (out!=null){
+ try {
+ out.close();
+ } catch (IOException e) {
+ AppLog.e(T.UTILS, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Create an empty trust store file if missing
+ */
+ private void createLocalKeyStoreFile() throws GeneralSecurityException, IOException {
+ if (!mLocalTrustStoreFile.exists()) {
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(mLocalTrustStoreFile);
+ KeyStore localTrustStore = KeyStore.getInstance("BKS");
+ localTrustStore.load(null, BuildConfig.DB_SECRET.toCharArray());
+ localTrustStore.store(out, BuildConfig.DB_SECRET.toCharArray());
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ AppLog.e(T.UTILS, e);
+ }
+ }
+ }
+ }
+ }
+
+ public void emptyLocalKeyStoreFile() {
+ if (mLocalTrustStoreFile.exists()) {
+ mLocalTrustStoreFile.delete();
+ }
+ try {
+ createLocalKeyStoreFile();
+ } catch (GeneralSecurityException e) {
+ AppLog.e(T.API, "Cannot create/initialize local Keystore", e);
+ } catch (IOException e) {
+ AppLog.e(T.API, "Cannot create/initialize local Keystore", e);
+ }
+ }
+
+ private static String hashName(X500Principal principal) {
+ try {
+ byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded());
+ String result = Integer.toString(leInt(digest), 16);
+ if (result.length() > 8) {
+ StringBuilder buff = new StringBuilder();
+ int padding = 8 - result.length();
+ for (int i = 0; i < padding; i++) {
+ buff.append("0");
+ }
+ buff.append(result);
+
+ return buff.toString();
+ }
+
+ return result;
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static int leInt(byte[] bytes) {
+ int offset = 0;
+ return ((bytes[offset++] & 0xff) << 0)
+ | ((bytes[offset++] & 0xff) << 8)
+ | ((bytes[offset++] & 0xff) << 16)
+ | ((bytes[offset] & 0xff) << 24);
+ }
+
+ public X509Certificate[] getLastFailureChain() {
+ return mLastFailureChain;
+ }
+
+ public void setLastFailureChain(X509Certificate[] lastFaiulreChain) {
+ mLastFailureChain = lastFaiulreChain;
+ }
+
+ public String getLastFailureChainDescription() {
+ return (mLastFailureChain == null || mLastFailureChain.length == 0) ? "" : mLastFailureChain[0].toString();
+ }
+
+ public boolean isCertificateTrusted(SslCertificate cert){
+ if (cert==null)
+ return false;
+
+ Bundle bundle = SslCertificate.saveState(cert);
+ X509Certificate x509Certificate;
+ byte[] bytes = bundle.getByteArray("x509-certificate");
+ if (bytes == null) {
+ AppLog.e(T.API, "Cannot load the SSLCertificate bytes from the bundle!");
+ x509Certificate = null;
+ } else {
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ Certificate certX509 = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ x509Certificate = (X509Certificate) certX509;
+ } catch (CertificateException e) {
+ AppLog.e(T.API, "Cannot generate the X509Certificate with the bytes provided", e);
+ x509Certificate = null;
+ }
+ }
+
+ return isCertificateTrusted(x509Certificate);
+ }
+
+ public boolean isCertificateTrusted(X509Certificate x509Certificate){
+ if (x509Certificate==null)
+ return false;
+
+ // Now I have an X509Certificate I can pass to an X509TrustManager for validation.
+ try {
+ String certificateAlias = this.getLocalKeyStore().getCertificateAlias(x509Certificate);
+ if(certificateAlias != null ) {
+ AppLog.w(T.API, "Current certificate " + x509Certificate.getSubjectDN().getName() +" is in KeyStore.");
+ return true;
+ }
+ } catch (KeyStoreException e) {
+ AppLog.e(T.API, "Cannot check if the certificate is in KeyStore. Seems that Keystore is not initialized.", e);
+ }
+
+ AppLog.w(T.API, "Current certificate " + x509Certificate.getSubjectDN().getName() +" is NOT in KeyStore.");
+ return false;
+ }
+}