| 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; |
| } |
| } |