Bug 1020598 - Add telemetry to measure WebCrypto usage. r=vladan, a=dougt
authorRichard Barnes <rbarnes@mozilla.com>
Mon, 09 Jun 2014 22:01:00 -0400
changeset 200158 3bd347abca17fababbdd7a326a4298f75dcb2fbc
parent 200157 9ffd2b905c8eecc2e2c529ad069bab5d40086b15
child 200159 c84a1d1b6612a1df7a6215590a22997ab21986f9
push idunknown
push userunknown
push dateunknown
reviewersvladan, dougt
bugs1020598
milestone32.0a2
Bug 1020598 - Add telemetry to measure WebCrypto usage. r=vladan, a=dougt
dom/crypto/WebCryptoTask.cpp
dom/crypto/WebCryptoTask.h
toolkit/components/telemetry/Histograms.json
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -16,19 +16,63 @@
 #include "mozilla/dom/KeyPair.h"
 #include "mozilla/dom/AesKeyAlgorithm.h"
 #include "mozilla/dom/HmacKeyAlgorithm.h"
 #include "mozilla/dom/RsaKeyAlgorithm.h"
 #include "mozilla/dom/RsaHashedKeyAlgorithm.h"
 #include "mozilla/dom/CryptoBuffer.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 
+#include "mozilla/Telemetry.h"
+
 namespace mozilla {
 namespace dom {
 
+// Pre-defined identifiers for telemetry histograms
+
+enum TelemetryMethod {
+  TM_ENCRYPT      = 0,
+  TM_DECRYPT      = 1,
+  TM_SIGN         = 2,
+  TM_VERIFY       = 3,
+  TM_DIGEST       = 4,
+  TM_GENERATEKEY  = 5,
+  TM_DERIVEKEY    = 6,
+  TM_DERIVEBITS   = 7,
+  TM_IMPORTKEY    = 8,
+  TM_EXPORTKEY    = 9,
+  TM_WRAPKEY      = 10,
+  TM_UNWRAPKEY    = 11
+};
+
+enum TelemetryAlgorithm {
+  TA_UNKNOWN         = 0,
+  // encrypt / decrypt
+  TA_AES_CBC         = 1,
+  TA_AES_CFB         = 2,
+  TA_AES_CTR         = 3,
+  TA_AES_GCM         = 4,
+  TA_RSAES_PKCS1     = 5,
+  TA_RSA_OAEP        = 6,
+  // sign/verify
+  TA_RSASSA_PKCS1    = 7,
+  TA_RSA_PSS         = 8,
+  TA_HMAC_SHA_1      = 9,
+  TA_HMAC_SHA_224    = 10,
+  TA_HMAC_SHA_256    = 11,
+  TA_HMAC_SHA_384    = 12,
+  TA_HMAC_SHA_512    = 13,
+  // digest
+  TA_SHA_1           = 14,
+  TA_SHA_224         = 15,
+  TA_SHA_256         = 16,
+  TA_SHA_384         = 17,
+  TA_SHA_512         = 18
+};
+
 // Convenience functions for extracting / converting information
 
 // OOM-safe CryptoBuffer initialization, suitable for constructors
 #define ATTEMPT_BUFFER_INIT(dst, src) \
   if (!dst.Assign(src)) { \
     mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; \
     return; \
   }
@@ -99,16 +143,70 @@ Coerce(JSContext* aCx, T& aTarget, const
   JS::RootedValue value(aCx, JS::ObjectValue(*aAlgorithm.GetAsObject()));
   if (!aTarget.Init(aCx, value)) {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
   return NS_OK;
 }
 
+// Implementation of WebCryptoTask methods
+
+void
+WebCryptoTask::FailWithError(nsresult aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, false);
+
+  // Blindly convert nsresult to DOMException
+  // Individual tasks must ensure they pass the right values
+  mResultPromise->MaybeReject(aRv);
+  // Manually release mResultPromise while we're on the main thread
+  mResultPromise = nullptr;
+  Cleanup();
+}
+
+nsresult
+WebCryptoTask::CalculateResult()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  if (NS_FAILED(mEarlyRv)) {
+    return mEarlyRv;
+  }
+
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_DOM_UNKNOWN_ERR;
+  }
+
+  return DoCrypto();
+}
+
+void
+WebCryptoTask::CallCallback(nsresult rv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (NS_FAILED(rv)) {
+    FailWithError(rv);
+    return;
+  }
+
+  nsresult rv2 = AfterCrypto();
+  if (NS_FAILED(rv2)) {
+    FailWithError(rv2);
+    return;
+  }
+
+  Resolve();
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, true);
+
+  // Manually release mResultPromise while we're on the main thread
+  mResultPromise = nullptr;
+  Cleanup();
+}
 
 // Some generic utility classes
 
 class FailureTask : public WebCryptoTask
 {
 public:
   FailureTask(nsresult rv) {
     mEarlyRv = rv;
@@ -151,28 +249,31 @@ public:
         (mSymKey.Length() != 24) &&
         (mSymKey.Length() != 32))
     {
       mEarlyRv = NS_ERROR_DOM_DATA_ERR;
       return;
     }
 
     // Cache parameters depending on the specific algorithm
+    TelemetryAlgorithm telemetryAlg;
     if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
       mMechanism = CKM_AES_CBC_PAD;
+      telemetryAlg = TA_AES_CBC;
       AesCbcParams params;
       nsresult rv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(rv) || !params.mIv.WasPassed()) {
         mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
         return;
       }
 
       ATTEMPT_BUFFER_INIT(mIv, params.mIv.Value())
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR)) {
       mMechanism = CKM_AES_CTR;
+      telemetryAlg = TA_AES_CTR;
       AesCtrParams params;
       nsresult rv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(rv) || !params.mCounter.WasPassed() ||
           !params.mLength.WasPassed()) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
 
@@ -180,16 +281,17 @@ public:
       if (mIv.Length() != 16) {
         mEarlyRv = NS_ERROR_DOM_DATA_ERR;
         return;
       }
 
       mCounterLength = params.mLength.Value();
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
       mMechanism = CKM_AES_GCM;
+      telemetryAlg = TA_AES_GCM;
       AesGcmParams params;
       nsresult rv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(rv) || !params.mIv.WasPassed()) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
 
       ATTEMPT_BUFFER_INIT(mIv, params.mIv.Value())
@@ -208,16 +310,17 @@ public:
           mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
           return;
         }
       }
     } else {
       mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
       return;
     }
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
   }
 
 private:
   CK_MECHANISM_TYPE mMechanism;
   CryptoBuffer mSymKey;
   CryptoBuffer mIv;   // Initialization vector
   CryptoBuffer mData;
   CryptoBuffer mAad;  // Additional Authenticated Data
@@ -302,16 +405,18 @@ class RsaesPkcs1Task : public ReturnArra
 public:
   RsaesPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm,
                  mozilla::dom::Key& aKey, const CryptoOperationData& aData,
                  bool aEncrypt)
     : mPrivKey(aKey.GetPrivateKey())
     , mPubKey(aKey.GetPublicKey())
     , mEncrypt(aEncrypt)
   {
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSAES_PKCS1);
+
     ATTEMPT_BUFFER_INIT(mData, aData);
 
     if (mEncrypt) {
       if (!mPubKey) {
         mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
         return;
       }
       mStrength = SECKEY_PublicKeyStrength(mPubKey);
@@ -385,16 +490,27 @@ public:
       ATTEMPT_BUFFER_INIT(mSignature, aSignature);
     }
 
     // Check that we got a symmetric key
     if (mSymKey.Length() == 0) {
       mEarlyRv = NS_ERROR_DOM_DATA_ERR;
       return;
     }
+
+    TelemetryAlgorithm telemetryAlg;
+    switch (mMechanism) {
+      case CKM_SHA_1_HMAC:  telemetryAlg = TA_HMAC_SHA_1; break;
+      case CKM_SHA224_HMAC: telemetryAlg = TA_HMAC_SHA_224; break;
+      case CKM_SHA256_HMAC: telemetryAlg = TA_HMAC_SHA_256; break;
+      case CKM_SHA384_HMAC: telemetryAlg = TA_HMAC_SHA_384; break;
+      case CKM_SHA512_HMAC: telemetryAlg = TA_HMAC_SHA_512; break;
+      default:              telemetryAlg = TA_UNKNOWN;
+    }
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
   }
 
 private:
   CK_MECHANISM_TYPE mMechanism;
   CryptoBuffer mSymKey;
   CryptoBuffer mData;
   CryptoBuffer mSignature;
   CryptoBuffer mResult;
@@ -469,16 +585,18 @@ public:
                   const CryptoOperationData& aData,
                   bool aSign)
     : mOidTag(SEC_OID_UNKNOWN)
     , mPrivKey(aKey.GetPrivateKey())
     , mPubKey(aKey.GetPublicKey())
     , mSign(aSign)
     , mVerified(false)
   {
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1);
+
     ATTEMPT_BUFFER_INIT(mData, aData);
     if (!aSign) {
       ATTEMPT_BUFFER_INIT(mSignature, aSignature);
     }
 
     // Look up the SECOidTag based on the KeyAlgorithm
     // static_cast is safe because we only get here if the algorithm name
     // is RSASSA-PKCS1-v1_5, and that only happens if we've constructed
@@ -589,28 +707,34 @@ public:
 
     nsString algName;
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
+    TelemetryAlgorithm telemetryAlg;
     if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1))   {
       mOidTag = SEC_OID_SHA1;
+      telemetryAlg = TA_SHA_1;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
       mOidTag = SEC_OID_SHA256;
+      telemetryAlg = TA_SHA_224;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
       mOidTag = SEC_OID_SHA384;
+      telemetryAlg = TA_SHA_256;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
       mOidTag = SEC_OID_SHA512;
+      telemetryAlg = TA_SHA_384;
     } else {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
   }
 
 private:
   SECOidTag mOidTag;
   CryptoBuffer mData;
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
@@ -1292,16 +1416,20 @@ private:
 
 WebCryptoTask*
 WebCryptoTask::EncryptDecryptTask(JSContext* aCx,
                                   const ObjectOrString& aAlgorithm,
                                   Key& aKey,
                                   const CryptoOperationData& aData,
                                   bool aEncrypt)
 {
+  TelemetryMethod method = (aEncrypt)? TM_ENCRYPT : TM_DECRYPT;
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_ENC, aKey.Extractable());
+
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   // Ensure key is usable for this operation
   if ((aEncrypt  && !aKey.HasUsage(Key::ENCRYPT)) ||
@@ -1323,16 +1451,20 @@ WebCryptoTask::EncryptDecryptTask(JSCont
 WebCryptoTask*
 WebCryptoTask::SignVerifyTask(JSContext* aCx,
                               const ObjectOrString& aAlgorithm,
                               Key& aKey,
                               const CryptoOperationData& aSignature,
                               const CryptoOperationData& aData,
                               bool aSign)
 {
+  TelemetryMethod method = (aSign)? TM_SIGN : TM_VERIFY;
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_SIG, aKey.Extractable());
+
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   // Ensure key is usable for this operation
   if ((aSign  && !aKey.HasUsage(Key::SIGN)) ||
@@ -1349,27 +1481,31 @@ WebCryptoTask::SignVerifyTask(JSContext*
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::DigestTask(JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           const CryptoOperationData& aData)
 {
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DIGEST);
   return new SimpleDigestTask(aCx, aAlgorithm, aData);
 }
 
 WebCryptoTask*
 WebCryptoTask::ImportKeyTask(JSContext* aCx,
                              const nsAString& aFormat,
                              const KeyData& aKeyData,
                              const ObjectOrString& aAlgorithm,
                              bool aExtractable,
                              const Sequence<nsString>& aKeyUsages)
 {
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_IMPORTKEY);
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_IMPORT, aExtractable);
+
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
@@ -1385,29 +1521,34 @@ WebCryptoTask::ImportKeyTask(JSContext* 
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
 WebCryptoTask::ExportKeyTask(const nsAString& aFormat,
                              Key& aKey)
 {
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_EXPORTKEY);
+
   if (aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   } else {
     return new UnifiedExportKeyTask(aFormat, aKey);
   }
 }
 
 WebCryptoTask*
 WebCryptoTask::GenerateKeyTask(JSContext* aCx,
                                const ObjectOrString& aAlgorithm,
                                bool aExtractable,
                                const Sequence<nsString>& aKeyUsages)
 {
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_GENERATEKEY);
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_GENERATE, aExtractable);
+
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
@@ -1425,23 +1566,25 @@ WebCryptoTask::GenerateKeyTask(JSContext
 WebCryptoTask*
 WebCryptoTask::DeriveKeyTask(JSContext* aCx,
                              const ObjectOrString& aAlgorithm,
                              Key& aBaseKey,
                              const ObjectOrString& aDerivedKeyType,
                              bool aExtractable,
                              const Sequence<nsString>& aKeyUsages)
 {
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEKEY);
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::DeriveBitsTask(JSContext* aCx,
                               const ObjectOrString& aAlgorithm,
                               Key& aKey,
                               uint32_t aLength)
 {
+  Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEBITS);
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/crypto/WebCryptoTask.h
+++ b/dom/crypto/WebCryptoTask.h
@@ -175,65 +175,23 @@ protected:
   // For things that need to happen on the main thread
   // either before or after CalculateResult
   virtual nsresult BeforeCrypto() { return NS_OK; }
   virtual nsresult DoCrypto() { return NS_OK; }
   virtual nsresult AfterCrypto() { return NS_OK; }
   virtual void Resolve() {}
   virtual void Cleanup() {}
 
-  void FailWithError(nsresult aRv)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    // Blindly convert nsresult to DOMException
-    // Individual tasks must ensure they pass the right values
-    mResultPromise->MaybeReject(aRv);
-    // Manually release mResultPromise while we're on the main thread
-    mResultPromise = nullptr;
-    Cleanup();
-  }
+  void FailWithError(nsresult aRv);
 
   // Subclasses should override this method if they keep references to
   // any NSS objects, e.g., SECKEYPrivateKey or PK11SymKey.
   virtual void ReleaseNSSResources() MOZ_OVERRIDE {}
 
-  virtual nsresult CalculateResult() MOZ_OVERRIDE MOZ_FINAL
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    if (NS_FAILED(mEarlyRv)) {
-      return mEarlyRv;
-    }
-
-    if (isAlreadyShutDown()) {
-      return NS_ERROR_DOM_UNKNOWN_ERR;
-    }
-
-    return DoCrypto();
-  }
+  virtual nsresult CalculateResult() MOZ_OVERRIDE MOZ_FINAL;
 
-  virtual void CallCallback(nsresult rv) MOZ_OVERRIDE MOZ_FINAL
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    if (NS_FAILED(rv)) {
-      FailWithError(rv);
-      return;
-    }
-
-    nsresult rv2 = AfterCrypto();
-    if (NS_FAILED(rv2)) {
-      FailWithError(rv2);
-      return;
-    }
-
-    Resolve();
-
-    // Manually release mResultPromise while we're on the main thread
-    mResultPromise = nullptr;
-    Cleanup();
-  }
+  virtual void CallCallback(nsresult rv) MOZ_OVERRIDE MOZ_FINAL;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_WebCryptoTask_h
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -314,17 +314,17 @@
     "high": "500",
     "n_buckets": 50,
     "description": "Time spent sweeping slowest compartment SCC (ms)"
   },
   "GEOLOCATION_ACCURACY": {
     "expires_in_version": "never",
     "kind": "linear",
     "high": "18000",
-    "n_buckets": 200,  
+    "n_buckets": 200,
     "description": "Location accuracy"
   },
   "GEOLOCATION_ERROR": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "Has seen location error"
   },
   "JS_MINOR_VERSION": {
@@ -6340,10 +6340,47 @@
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether FxA Sync is configured to use a custom authentication server"
   },
   "WEAVE_CUSTOM_TOKEN_SERVER_CONFIGURATION": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether FxA Sync is configured to use a custom token server"
+  },
+  "WEBCRYPTO_EXTRACTABLE_IMPORT": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Whether an imported key was marked as extractable"
+  },
+  "WEBCRYPTO_EXTRACTABLE_GENERATE": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Whether a generated key was marked as extractable"
+  },
+  "WEBCRYPTO_EXTRACTABLE_ENC": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Whether a key used in an encrypt/decrypt operation was marked as extractable"
+  },
+  "WEBCRYPTO_EXTRACTABLE_SIG": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Whether a key used in a sign/verify operation was marked as extractable"
+  },
+  "WEBCRYPTO_RESOLVED": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Whether a promise created by WebCrypto was resolved (vs rejected)"
+  },
+  "WEBCRYPTO_METHOD": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 20,
+    "description": "Methods invoked under window.crypto.subtle (0=encrypt, 1=decrypt, 2=sign, 3=verify, 4=digest, 5=generateKey, 6=deriveKey, 7=deriveBits, 8=importKey, 9=exportKey, 10=wrapKey, 11=unwrapKey)"
+  },
+  "WEBCRYPTO_ALG": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 30,
+    "description": "Algorithms used with WebCrypto (see table in WebCryptoTask.cpp)"
   }
 }