bug 1369911 - gather telemetry on the prevalence of 3rd party PKCS#11 modules r?Cykesiopka data-review=bsmedberg draft
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 02 Jun 2017 16:44:06 -0700
changeset 591173 beddb257de2659f04a8cb97c2de268cb4c5e3439
parent 589150 cad53f061da634a16ea75887558301b77f65745d
child 632455 34ba79a137f4b8bdec9d70d0814fce537baca464
push id62986
push userbmo:dkeeler@mozilla.com
push dateThu, 08 Jun 2017 19:13:31 +0000
reviewersCykesiopka
bugs1369911
milestone55.0a1
bug 1369911 - gather telemetry on the prevalence of 3rd party PKCS#11 modules r?Cykesiopka data-review=bsmedberg MozReview-Commit-ID: Dw99Jm64QNU
security/manager/ssl/PKCS11.cpp
security/manager/ssl/PKCS11.h
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/tests/unit/test_pkcs11_module.js
toolkit/components/telemetry/Scalars.yaml
--- a/security/manager/ssl/PKCS11.cpp
+++ b/security/manager/ssl/PKCS11.cpp
@@ -2,16 +2,18 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "PKCS11.h"
 
 #include "ScopedNSSTypes.h"
+#include "mozilla/Telemetry.h"
+#include "nsCRTGlue.h"
 #include "nsNSSComponent.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsServiceManagerUtils.h"
 
 namespace mozilla { namespace psm {
 
 NS_INTERFACE_MAP_BEGIN(PKCS11)
   NS_INTERFACE_MAP_ENTRY(nsIPKCS11)
@@ -67,16 +69,44 @@ PKCS11::DeleteModule(const nsAString& aM
   SECStatus srv = SECMOD_DeleteModule(moduleName.get(), &modType);
   if (srv != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+// Given a PKCS#11 module, determines an appropriate name to identify it for the
+// purposes of gathering telemetry. For 3rd party PKCS#11 modules, this should
+// be the name of the dynamic library that implements the module. For built-in
+// NSS modules, it will be the common name of the module.
+// Because the result will be used as a telemetry scalar key (which must be less
+// than 70 characters), this function will also truncate the result if it
+// exceeds this limit. (Note that unfortunately telemetry doesn't expose a way
+// to programmatically query the scalar key length limit, so we have to
+// hard-code the value here.)
+void
+GetModuleNameForTelemetry(/*in*/ const SECMODModule* module,
+                          /*out*/nsString& result)
+{
+  result.Truncate();
+  if (module->dllName) {
+    result.AssignWithConversion(module->dllName);
+    int32_t separatorIndex = result.RFind(FILE_PATH_SEPARATOR);
+    if (separatorIndex != kNotFound) {
+      result = Substring(result, separatorIndex + 1);
+    }
+  } else {
+    result.AssignWithConversion(module->commonName);
+  }
+  if (result.Length() >= 70) {
+    result.Truncate(69);
+  }
+}
+
 // Add a new PKCS11 module to the user's profile.
 NS_IMETHODIMP
 PKCS11::AddModule(const nsAString& aModuleName,
                   const nsAString& aLibraryFullPath,
                   int32_t aCryptoMechanismFlags,
                   int32_t aCipherFlags)
 {
   nsNSSShutDownPreventionLock locker;
@@ -95,22 +125,34 @@ PKCS11::AddModule(const nsAString& aModu
   uint32_t mechFlags = SECMOD_PubMechFlagstoInternal(aCryptoMechanismFlags);
   uint32_t cipherFlags = SECMOD_PubCipherFlagstoInternal(aCipherFlags);
   SECStatus srv = SECMOD_AddNewModule(moduleName.get(), fullPath.get(),
                                       mechFlags, cipherFlags);
   if (srv != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
 
-#ifndef MOZ_NO_SMART_CARDS
   UniqueSECMODModule module(SECMOD_FindModule(moduleName.get()));
   if (!module) {
     return NS_ERROR_FAILURE;
   }
+
+#ifndef MOZ_NO_SMART_CARDS
   nsCOMPtr<nsINSSComponent> nssComponent(
     do_GetService(PSM_COMPONENT_CONTRACTID));
   nssComponent->LaunchSmartCardThread(module.get());
 #endif
 
+  nsAutoString scalarKey;
+  GetModuleNameForTelemetry(module.get(), scalarKey);
+  // Scalar keys must be between 0 and 70 characters (exclusive).
+  // GetModuleNameForTelemetry takes care of keys that are too long.
+  // If for some reason it couldn't come up with an appropriate name and
+  // returned an empty result, however, we need to not attempt to record this
+  // (it wouldn't give us anything useful anyway).
+  if (scalarKey.Length() > 0) {
+    Telemetry::ScalarSet(Telemetry::ScalarID::SECURITY_PKCS11_MODULES_LOADED,
+                         scalarKey, true);
+  }
   return NS_OK;
 }
 
 } } // namespace mozilla::psm
--- a/security/manager/ssl/PKCS11.h
+++ b/security/manager/ssl/PKCS11.h
@@ -4,16 +4,17 @@
  * 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/. */
 #ifndef PKCS11_h
 #define PKCS11_h
 
 #include "nsIPKCS11.h"
 
 #include "nsNSSShutDown.h"
+#include "nsString.h"
 
 namespace mozilla { namespace psm {
 
 #define NS_PKCS11_CID \
   {0x74b7a390, 0x3b41, 0x11d4, { 0x8a, 0x80, 0x00, 0x60, 0x08, 0xc8, 0x44, 0xc3} }
 
 class PKCS11 : public nsIPKCS11
              , public nsNSSShutDownObject
@@ -26,11 +27,14 @@ public:
 
 protected:
   virtual ~PKCS11();
 
 private:
   virtual void virtualDestroyNSSReference() override {}
 };
 
+void GetModuleNameForTelemetry(/*in*/ const SECMODModule* module,
+                               /*out*/nsString& result);
+
 } } // namespace mozilla::psm
 
 #endif // PKCS11_h
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -3,16 +3,17 @@
  * 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/. */
 
 #include "nsNSSComponent.h"
 
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
+#include "PKCS11.h"
 #include "ScopedNSSTypes.h"
 #include "SharedSSLState.h"
 #include "cert.h"
 #include "certdb.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Preferences.h"
@@ -1944,16 +1945,35 @@ nsNSSComponent::InitializeNSS()
   LaunchSmartCardThreads();
 #endif
 
   mozilla::pkix::RegisterErrorTable();
 
   if (PK11_IsFIPS()) {
     Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true);
   }
+
+  { // Introduce scope for the AutoSECMODListReadLock.
+    AutoSECMODListReadLock lock;
+    for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list;
+         list = list->next) {
+      nsAutoString scalarKey;
+      GetModuleNameForTelemetry(list->module, scalarKey);
+      // Scalar keys must be between 0 and 70 characters (exclusive).
+      // GetModuleNameForTelemetry takes care of keys that are too long. If for
+      // some reason it couldn't come up with an appropriate name and returned
+      // an empty result, however, we need to not attempt to record this (it
+      // wouldn't give us anything useful anyway).
+      if (scalarKey.Length() > 0) {
+        Telemetry::ScalarSet(
+          Telemetry::ScalarID::SECURITY_PKCS11_MODULES_LOADED, scalarKey, true);
+      }
+    }
+  }
+
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n"));
   mNSSInitialized = true;
 
   return NS_OK;
 }
 
 void
 nsNSSComponent::ShutdownNSS()
--- a/security/manager/ssl/tests/unit/test_pkcs11_module.js
+++ b/security/manager/ssl/tests/unit/test_pkcs11_module.js
@@ -53,23 +53,54 @@ function checkTestModuleExists() {
      "Test module lib name should include lib name of 'pkcs11testmodule'");
 
   notEqual(gModuleDB.findModuleByName("PKCS11 Test Module"), null,
            "Test module should be findable by name");
 
   return testModule;
 }
 
+function checkModuleTelemetry(additionalExpectedModule = undefined) {
+  let expectedModules = [
+    "NSS Internal PKCS #11 Module",
+    `${AppConstants.DLL_PREFIX}nssckbi${AppConstants.DLL_SUFFIX}`,
+  ];
+  if (additionalExpectedModule) {
+    expectedModules.push(additionalExpectedModule);
+  }
+  expectedModules.sort();
+  let telemetryService = Cc["@mozilla.org/base/telemetry;1"]
+                           .getService(Ci.nsITelemetry);
+  let telemetry = telemetryService.snapshotKeyedScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT).parent;
+  let moduleTelemetry = telemetry["security.pkcs11_modules_loaded"];
+  let actualModules = [];
+  Object.keys(moduleTelemetry).forEach((key) => {
+    ok(moduleTelemetry[key], "each keyed scalar should be true");
+    actualModules.push(key);
+  });
+  actualModules.sort();
+  equal(actualModules.length, expectedModules.length,
+        "the number of actual and expected loaded modules should be the same");
+  for (let i in actualModules) {
+    equal(actualModules[i], expectedModules[i],
+          "actual and expected module names should match");
+  }
+}
+
 function run_test() {
   // Check that if we have never added the test module, that we don't find it
   // in the module list.
   checkTestModuleNotPresent();
+  checkModuleTelemetry();
 
   // Check that adding the test module makes it appear in the module list.
   loadPKCS11TestModule(true);
+  checkModuleTelemetry(
+    `${AppConstants.DLL_PREFIX}pkcs11testmodule${AppConstants.DLL_SUFFIX}`);
   let testModule = checkTestModuleExists();
 
   // Check that listing the slots for the test module works.
   let slots = testModule.listSlots();
   let testModuleSlotNames = [];
   while (slots.hasMoreElements()) {
     let slot = slots.getNext().QueryInterface(Ci.nsIPKCS11Slot);
     testModuleSlotNames.push(slot.name);
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -278,16 +278,32 @@ services.sync:
     kind: string
     keyed: false
     notification_emails:
       - sync-staff@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - main
 
+security:
+  pkcs11_modules_loaded:
+    bug_numbers:
+      - 1369911
+    description: >
+      A keyed boolean indicating the library names of the PKCS#11 modules that
+      have been loaded by the browser.
+    expires: "62"
+    kind: boolean
+    keyed: true
+    notification_emails:
+      - seceng-telemetry@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
 # The following section contains WebRTC nICEr scalars
 # For more info on ICE, see https://tools.ietf.org/html/rfc5245
 # For more info on STUN, see https://tools.ietf.org/html/rfc5389
 # For more info on TURN, see https://tools.ietf.org/html/rfc5766
 webrtc.nicer:
   stun_retransmits:
     bug_numbers:
       - 1325536