From 69308ed152b8fbf8e46b9eb499da9d056d082dee Mon Sep 17 00:00:00 2001 From: Dana Keeler Date: Thu, 9 Apr 2020 00:54:11 +0000 Subject: [PATCH] Bug 1627756 - implement enterprise roots for android r=snorp Differential Revision: https://phabricator.services.mozilla.com/D69855 --HG-- extra : moz-landing-system : lando --- .../org/mozilla/gecko/EnterpriseRoots.java | 101 ++++++++++++++++++ security/manager/ssl/EnterpriseRoots.cpp | 29 +++++ 2 files changed, 130 insertions(+) create mode 100644 mobile/android/geckoview/src/main/java/org/mozilla/gecko/EnterpriseRoots.java diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EnterpriseRoots.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EnterpriseRoots.java new file mode 100644 index 000000000000..864b06b3ab53 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EnterpriseRoots.java @@ -0,0 +1,101 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +import java.io.IOException; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; + +import java.util.ArrayList; +import java.util.Enumeration; + +import org.mozilla.gecko.annotation.WrapForJNI; + +import android.util.Log; + +// This class implements the functionality needed to find third-party root +// certificates that have been added to the android CA store. +public class EnterpriseRoots { + private static final String LOGTAG = "EnterpriseRoots"; + + // Gecko calls this function from C++ to find third-party root certificates + // it can use as trust anchors for TLS connections. + @WrapForJNI + private static byte[][] gatherEnterpriseRoots() { + + // The KeyStore "AndroidCAStore" contains the certificates we're + // interested in. + KeyStore ks; + try { + ks = KeyStore.getInstance("AndroidCAStore"); + } catch (KeyStoreException kse) { + Log.e(LOGTAG, "getInstance() failed", kse); + return new byte[0][0]; + } + try { + ks.load(null); + } catch (CertificateException ce) { + Log.e(LOGTAG, "load() failed", ce); + return new byte[0][0]; + } catch (IOException ioe) { + Log.e(LOGTAG, "load() failed", ioe); + return new byte[0][0]; + } catch (NoSuchAlgorithmException nsae) { + Log.e(LOGTAG, "load() failed", nsae); + return new byte[0][0]; + } + // Given the KeyStore, we get an identifier for each object in it. For + // each one that is a Certificate, we try to distinguish between + // entries that shipped with the OS and entries that were added by the + // user or an administrator. The former we ignore and the latter we + // collect in an array of byte arrays and return. + Enumeration aliases; + try { + aliases = ks.aliases(); + } catch (KeyStoreException kse) { + Log.e(LOGTAG, "aliases() failed", kse); + return new byte[0][0]; + } + ArrayList roots = new ArrayList(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + boolean isCertificate; + try { + isCertificate = ks.isCertificateEntry(alias); + } catch (KeyStoreException kse) { + Log.e(LOGTAG, "isCertificateEntry() failed", kse); + continue; + } + // Built-in certificate aliases start with "system:", whereas + // 3rd-party certificate aliases start with "user:". It's + // unfortunate to be relying on this implementation detail, but + // there appears to be no other way to differentiate between the + // two. + if (isCertificate && alias.startsWith("user:")) { + Certificate certificate; + try { + certificate = ks.getCertificate(alias); + } catch (KeyStoreException kse) { + Log.e(LOGTAG, "getCertificate() failed", kse); + continue; + } + try { + roots.add(certificate.getEncoded()); + } catch (CertificateEncodingException cee) { + Log.e(LOGTAG, "getEncoded() failed", cee); + } + } + } + Log.d(LOGTAG, "found " + roots.size() + " enterprise roots"); + return roots.toArray(new byte[0][0]); + } +} diff --git a/security/manager/ssl/EnterpriseRoots.cpp b/security/manager/ssl/EnterpriseRoots.cpp index 1fb456529ecb..100f9d04dbbe 100644 --- a/security/manager/ssl/EnterpriseRoots.cpp +++ b/security/manager/ssl/EnterpriseRoots.cpp @@ -12,6 +12,10 @@ #include "mozpkix/Result.h" #include "nsThreadUtils.h" +#ifdef ANDROID +# include "GeneratedJNIWrappers.h" +#endif // ANDROID + #ifdef XP_MACOSX # include # include "KeychainSecret.h" // for ScopedCFType @@ -326,6 +330,28 @@ OSStatus GatherEnterpriseCertsMacOS(Vector& certs) { } #endif // XP_MACOSX +#ifdef ANDROID +void GatherEnterpriseCertsAndroid(Vector& certs) { + if (!jni::IsAvailable()) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("JNI not available")); + return; + } + jni::ObjectArray::LocalRef roots = + java::EnterpriseRoots::GatherEnterpriseRoots(); + for (size_t i = 0; i < roots->Length(); i++) { + jni::ByteArray::LocalRef root = roots->GetElement(i); + EnterpriseCert cert; + // Currently we treat all certificates gleaned from the Android + // CA store as roots. + if (NS_SUCCEEDED(cert.Init( + reinterpret_cast(root->GetElements().Elements()), + root->Length(), true))) { + Unused << certs.append(std::move(cert)); + } + } +} +#endif // ANDROID + nsresult GatherEnterpriseCerts(Vector& certs) { MOZ_ASSERT(!NS_IsMainThread()); if (NS_IsMainThread()) { @@ -342,5 +368,8 @@ nsresult GatherEnterpriseCerts(Vector& certs) { return NS_ERROR_FAILURE; } #endif // XP_MACOSX +#ifdef ANDROID + GatherEnterpriseCertsAndroid(certs); +#endif // ANDROID return NS_OK; }