From 8411cd82c0572e0e871c1cf93e0d4c05b35fb999 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 16 Dec 2021 23:45:41 +0100 Subject: allow to parse and handle multiple certs in a pem file --- .../bitmaskclient/base/utils/ConfigHelper.java | 32 +++++---- .../bitmaskclient/base/utils/KeyStoreHelper.java | 78 ---------------------- .../bitmaskclient/eip/VpnCertificateValidator.java | 20 ++++-- .../providersetup/ProviderApiManagerBase.java | 50 ++++++++------ .../connectivity/TLSCompatSocketFactory.java | 10 ++- 5 files changed, 75 insertions(+), 115 deletions(-) delete mode 100644 app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java (limited to 'app/src/main/java') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java index dccb5678..0a81b9cb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.os.Looper; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -37,6 +38,7 @@ import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -44,10 +46,13 @@ import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; import java.util.Calendar; +import java.util.Collection; import java.util.regex.Matcher; import java.util.regex.Pattern; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.providersetup.ProviderAPI; @@ -99,25 +104,28 @@ public class ConfigHelper { return ret; } - public static X509Certificate parseX509CertificateFromString(String certificateString) { - java.security.cert.Certificate certificate = null; + public static ArrayList parseX509CertificatesFromString(String certificateString) { + Collection certificates; CertificateFactory cf; try { cf = CertificateFactory.getInstance("X.509"); certificateString = certificateString.replaceAll("-----BEGIN CERTIFICATE-----", "").trim().replaceAll("-----END CERTIFICATE-----", "").trim(); - byte[] cert_bytes = Base64.decode(certificateString); - InputStream caInput = new ByteArrayInputStream(cert_bytes); - try { - certificate = cf.generateCertificate(caInput); - System.out.println("ca=" + ((X509Certificate) certificate).getSubjectDN()); - } finally { - caInput.close(); + byte[] certBytes = Base64.decode(certificateString); + try (InputStream caInput = new ByteArrayInputStream(certBytes)) { + certificates = cf.generateCertificates(caInput); + if (certificates != null) { + for (Certificate cert : certificates) { + System.out.println("ca=" + ((X509Certificate) cert).getSubjectDN()); + } + return (ArrayList) certificates; + } } - } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException e) { - return null; + } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException | ClassCastException e) { + e.printStackTrace(); } - return (X509Certificate) certificate; + + return null; } public static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java deleted file mode 100644 index b0b28993..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java +++ /dev/null @@ -1,78 +0,0 @@ -package se.leap.bitmaskclient.base.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -/** - * Created by cyberta on 18.03.18. - */ - -public class KeyStoreHelper { - private static KeyStore trustedKeystore; - - /** - * Adds a new X509 certificate given its input stream and its provider name - * - * @param provider used to store the certificate in the keystore - * @param inputStream from which X509 certificate must be generated. - */ - public static void addTrustedCertificate(String provider, InputStream inputStream) { - CertificateFactory cf; - try { - cf = CertificateFactory.getInstance("X.509"); - X509Certificate cert = - (X509Certificate) cf.generateCertificate(inputStream); - trustedKeystore.setCertificateEntry(provider, cert); - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (KeyStoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * Adds a new X509 certificate given in its string from and using its provider name - * - * @param provider used to store the certificate in the keystore - * @param certificate - */ - public static void addTrustedCertificate(String provider, String certificate) { - - try { - X509Certificate cert = ConfigHelper.parseX509CertificateFromString(certificate); - if (trustedKeystore == null) { - trustedKeystore = KeyStore.getInstance("BKS"); - trustedKeystore.load(null); - } - trustedKeystore.setCertificateEntry(provider, cert); - } catch (KeyStoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * @return class wide keystore - */ - public static KeyStore getKeystore() { - return trustedKeystore; - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java index c747b731..16d1c5ad 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java @@ -16,9 +16,12 @@ */ package se.leap.bitmaskclient.eip; +import androidx.annotation.VisibleForTesting; + import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -27,7 +30,7 @@ import se.leap.bitmaskclient.base.utils.ConfigHelper; public class VpnCertificateValidator { public final static String TAG = VpnCertificateValidator.class.getSimpleName(); - private String certificate; + private final String certificate; private CalendarProviderInterface calendarProvider; public VpnCertificateValidator(String certificate) { @@ -35,21 +38,30 @@ public class VpnCertificateValidator { this.calendarProvider = new CalendarProvider(); } + @VisibleForTesting public void setCalendarProvider(CalendarProviderInterface calendarProvider) { this.calendarProvider = calendarProvider; } /** * - * @return true if there's a certificate that is valid for more than 15 more days + * @return true if all certificates are valid for more than 15 more days */ public boolean isValid() { if (certificate.isEmpty()) { return false; } - X509Certificate x509Certificate = ConfigHelper.parseX509CertificateFromString(certificate); - return isValid(x509Certificate); + ArrayList x509Certificates = ConfigHelper.parseX509CertificatesFromString(certificate); + if (x509Certificates == null) { + return false; + } + for (X509Certificate cert : x509Certificates) { + if (!isValid(cert)) { + return false; + } + } + return true; } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java index 808d9e75..63cf03cf 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java @@ -849,18 +849,24 @@ public abstract class ProviderApiManagerBase { protected boolean validCertificate(Provider provider, String certString) { boolean result = false; if (!ConfigHelper.checkErroneousDownload(certString)) { - X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(certString); + ArrayList certificates = ConfigHelper.parseX509CertificatesFromString(certString); try { - if (certificate != null) { - JSONObject providerJson = provider.getDefinition(); - String fingerprint = providerJson.getString(Provider.CA_CERT_FINGERPRINT); - String encoding = fingerprint.split(":")[0]; - String expectedFingerprint = fingerprint.split(":")[1]; - String realFingerprint = getFingerprintFromCertificate(certificate, encoding); - - result = realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim()); - } else + if (certificates != null) { + if (certificates.size() == 1) { + JSONObject providerJson = provider.getDefinition(); + String fingerprint = providerJson.getString(Provider.CA_CERT_FINGERPRINT); + String encoding = fingerprint.split(":")[0]; + String expectedFingerprint = fingerprint.split(":")[1]; + String realFingerprint = getFingerprintFromCertificate(certificates.get(0), encoding); + result = realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim()); + } else { + // otherwise we assume the provider is transitioning the CA certs and thus shipping multiple CA certs + // in that case we don't do cert pinning + result = true; + } + } else { result = false; + } } catch (JSONException | NoSuchAlgorithmException | CertificateEncodingException e) { result = false; } @@ -910,18 +916,24 @@ public abstract class ProviderApiManagerBase { return result; } - X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(caCert); - if (certificate == null) { + ArrayList certificates = ConfigHelper.parseX509CertificatesFromString(caCert); + if (certificates == null) { return setErrorResult(result, warning_corrupted_provider_cert, ERROR_INVALID_CERTIFICATE.toString()); } try { - certificate.checkValidity(); String encoding = provider.getCertificatePinEncoding(); String expectedFingerprint = provider.getCertificatePin(); - String realFingerprint = getFingerprintFromCertificate(certificate, encoding); - if (!realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim())) { - return setErrorResult(result, warning_corrupted_provider_cert, ERROR_CERTIFICATE_PINNING.toString()); + // Do certificate pinning only if we have 1 cert, otherwise we assume some transitioning of + // X509 certs, therefore we cannot do cert pinning + if (certificates.size() == 1) { + String realFingerprint = getFingerprintFromCertificate(certificates.get(0), encoding); + if (!realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim())) { + return setErrorResult(result, warning_corrupted_provider_cert, ERROR_CERTIFICATE_PINNING.toString()); + } + } + for (X509Certificate certificate : certificates) { + certificate.checkValidity(); } if (!canConnect(provider, result)) { @@ -1073,9 +1085,9 @@ public abstract class ProviderApiManagerBase { keyString = Base64.encodeToString(key.getEncoded(), Base64.DEFAULT); provider.setPrivateKey( "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----"); - X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(certificateString); - certificate.checkValidity(); - certificateString = Base64.encodeToString(certificate.getEncoded(), Base64.DEFAULT); + ArrayList certificates = ConfigHelper.parseX509CertificatesFromString(certificateString); + certificates.get(0).checkValidity(); + certificateString = Base64.encodeToString(certificates.get(0).getEncoded(), Base64.DEFAULT); provider.setVpnCertificate( "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----"); result.putBoolean(BROADCAST_RESULT_KEY, true); } catch (CertificateException | NullPointerException e) { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java index 5357fd74..cc68b5a8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java @@ -12,6 +12,8 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import javax.net.ssl.SSLContext; @@ -55,8 +57,12 @@ public class TLSCompatSocketFactory extends SSLSocketFactory { KeyStore keyStore = KeyStore.getInstance(defaultType); keyStore.load(null, null); if (!TextUtils.isEmpty(trustedSelfSignedCaCert)) { - java.security.cert.Certificate provider_certificate = ConfigHelper.parseX509CertificateFromString(trustedSelfSignedCaCert); - keyStore.setCertificateEntry("provider_ca_certificate", provider_certificate); + ArrayList x509Certificates = ConfigHelper.parseX509CertificatesFromString(trustedSelfSignedCaCert); + if (x509Certificates != null) { + for (int i = 0; i < x509Certificates.size(); i++) { + keyStore.setCertificateEntry("provider_ca_certificate"+i, x509Certificates.get(i)); + } + } } // Create a TrustManager that trusts the CAs in our KeyStore -- cgit v1.2.3