bug 1240173 - improve nsIX509Cert.dbKey r=Cykesiopka
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 15 Jan 2016 14:33:56 -0800
changeset 281089 b9aad3c0e80e50052d7bf214517b2dc536888cce
parent 281088 05d3068b8573f177cced316a0e4cf6fca03e0dd3
child 281090 9e34df8e0e84d59eea4950af27136dcf4cee51f9
push id29930
push usercbook@mozilla.com
push dateFri, 22 Jan 2016 11:05:50 +0000
treeherdermozilla-central@7104d650a97d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCykesiopka
bugs1240173
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1240173 - improve nsIX509Cert.dbKey r=Cykesiopka
security/manager/ssl/nsCertOverrideService.cpp
security/manager/ssl/nsClientAuthRemember.cpp
security/manager/ssl/nsIX509Cert.idl
security/manager/ssl/nsNSSCertificate.cpp
security/manager/ssl/nsNSSCertificate.h
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/nsNSSCertificateFakeTransport.cpp
security/manager/ssl/tests/unit/test_cert_dbKey.js
security/manager/ssl/tests/unit/xpcshell.ini
--- a/security/manager/ssl/nsCertOverrideService.cpp
+++ b/security/manager/ssl/nsCertOverrideService.cpp
@@ -393,44 +393,34 @@ nsCertOverrideService::RememberValidityO
   PR_FREEIF(nickname);
 
   nsAutoCString fpStr;
   nsresult rv = GetCertFingerprintByOidTag(nsscert.get(),
                   mOidTagForStoringNewHashes, fpStr);
   if (NS_FAILED(rv))
     return rv;
 
-  char *dbkey = nullptr;
-  rv = aCert->GetDbKey(&dbkey);
-  if (NS_FAILED(rv) || !dbkey)
+  nsAutoCString dbkey;
+  rv = aCert->GetDbKey(dbkey);
+  if (NS_FAILED(rv)) {
     return rv;
-
-  // change \n and \r to spaces in the possibly multi-line-base64-encoded key
-  for (char *dbkey_walk = dbkey;
-       *dbkey_walk;
-      ++dbkey_walk) {
-    char c = *dbkey_walk;
-    if (c == '\r' || c == '\n') {
-      *dbkey_walk = ' ';
-    }
   }
 
   {
     ReentrantMonitorAutoEnter lock(monitor);
     AddEntryToList(aHostName, aPort,
                    aTemporary ? aCert : nullptr,
                      // keep a reference to the cert for temporary overrides
                    aTemporary, 
                    mDottedOidForStoringNewHashes, fpStr, 
                    (nsCertOverride::OverrideBits)aOverrideBits, 
-                   nsDependentCString(dbkey));
+                   dbkey);
     Write();
   }
 
-  PR_Free(dbkey);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCertOverrideService::HasMatchingOverride(const nsACString & aHostName, int32_t aPort,
                                            nsIX509Cert *aCert, 
                                            uint32_t *aOverrideBits,
                                            bool *aIsTemporary,
@@ -548,16 +538,18 @@ nsCertOverrideService::AddEntryToList(co
     nsCertOverride &settings = entry->mSettings;
     settings.mAsciiHost = aHostName;
     settings.mPort = aPort;
     settings.mIsTemporary = aIsTemporary;
     settings.mFingerprintAlgOID = fingerprintAlgOID;
     settings.mFingerprint = fingerprint;
     settings.mOverrideBits = ob;
     settings.mDBKey = dbKey;
+    // remove whitespace from stored dbKey for backwards compatibility
+    settings.mDBKey.StripWhitespace();
     settings.mCert = aCert;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCertOverrideService::ClearValidityOverride(const nsACString & aHostName, int32_t aPort)
@@ -594,61 +586,24 @@ nsCertOverrideService::CountPermanentOve
       overrideCount++;
     }
   }
   Telemetry::Accumulate(Telemetry::SSL_PERMANENT_CERT_ERROR_OVERRIDES,
                         overrideCount);
 }
 
 static bool
-matchesDBKey(nsIX509Cert *cert, const char *match_dbkey)
+matchesDBKey(nsIX509Cert* cert, const nsCString& matchDbKey)
 {
-  char *dbkey = nullptr;
-  nsresult rv = cert->GetDbKey(&dbkey);
-  if (NS_FAILED(rv) || !dbkey)
+  nsAutoCString dbKey;
+  nsresult rv = cert->GetDbKey(dbKey);
+  if (NS_FAILED(rv)) {
     return false;
-
-  bool found_mismatch = false;
-  const char *key1 = dbkey;
-  const char *key2 = match_dbkey;
-
-  // skip over any whitespace when comparing
-  while (*key1 && *key2) {
-    char c1 = *key1;
-    char c2 = *key2;
-    
-    switch (c1) {
-      case ' ':
-      case '\t':
-      case '\n':
-      case '\r':
-        ++key1;
-        continue;
-    }
-
-    switch (c2) {
-      case ' ':
-      case '\t':
-      case '\n':
-      case '\r':
-        ++key2;
-        continue;
-    }
-
-    if (c1 != c2) {
-      found_mismatch = true;
-      break;
-    }
-
-    ++key1;
-    ++key2;
   }
-
-  PR_Free(dbkey);
-  return !found_mismatch;
+  return dbKey.Equals(matchDbKey);
 }
 
 NS_IMETHODIMP
 nsCertOverrideService::IsCertUsedForOverrides(nsIX509Cert *aCert, 
                                               bool aCheckTemporaries,
                                               bool aCheckPermanents,
                                               uint32_t *_retval)
 {
@@ -662,17 +617,17 @@ nsCertOverrideService::IsCertUsedForOver
       const nsCertOverride &settings = iter.Get()->mSettings;
       bool still_ok = true;
 
       if (( settings.mIsTemporary && !aCheckTemporaries) ||
           (!settings.mIsTemporary && !aCheckPermanents)) {
         still_ok = false;
       }
 
-      if (still_ok && matchesDBKey(aCert, settings.mDBKey.get())) {
+      if (still_ok && matchesDBKey(aCert, settings.mDBKey)) {
         nsAutoCString cert_fingerprint;
         nsresult rv = NS_ERROR_UNEXPECTED;
         if (settings.mFingerprintAlgOID.Equals(mDottedOidForStoringNewHashes)) {
           rv = GetCertFingerprintByOidTag(aCert,
                  mOidTagForStoringNewHashes, cert_fingerprint);
         }
         if (NS_SUCCEEDED(rv) &&
             settings.mFingerprint.Equals(cert_fingerprint)) {
@@ -692,17 +647,17 @@ nsCertOverrideService::EnumerateCertOver
 {
   ReentrantMonitorAutoEnter lock(monitor);
   for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
     const nsCertOverride &settings = iter.Get()->mSettings;
 
     if (!aCert) {
       aEnumerator(settings, aUserData);
     } else {
-      if (matchesDBKey(aCert, settings.mDBKey.get())) {
+      if (matchesDBKey(aCert, settings.mDBKey)) {
         nsAutoCString cert_fingerprint;
         nsresult rv = NS_ERROR_UNEXPECTED;
         if (settings.mFingerprintAlgOID.Equals(mDottedOidForStoringNewHashes)) {
           rv = GetCertFingerprintByOidTag(aCert,
                  mOidTagForStoringNewHashes, cert_fingerprint);
         }
         if (NS_SUCCEEDED(rv) &&
             settings.mFingerprint.Equals(cert_fingerprint)) {
--- a/security/manager/ssl/nsClientAuthRemember.cpp
+++ b/security/manager/ssl/nsClientAuthRemember.cpp
@@ -97,39 +97,36 @@ nsClientAuthRememberService::RemoveAllFr
 }
 
 nsresult
 nsClientAuthRememberService::RememberDecision(const nsACString & aHostName, 
                                               CERTCertificate *aServerCert, CERTCertificate *aClientCert)
 {
   // aClientCert == nullptr means: remember that user does not want to use a cert
   NS_ENSURE_ARG_POINTER(aServerCert);
-  if (aHostName.IsEmpty())
+  if (aHostName.IsEmpty()) {
     return NS_ERROR_INVALID_ARG;
+  }
 
   nsAutoCString fpStr;
   nsresult rv = GetCertFingerprintByOidTag(aServerCert, SEC_OID_SHA256, fpStr);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   {
     ReentrantMonitorAutoEnter lock(monitor);
     if (aClientCert) {
       RefPtr<nsNSSCertificate> pipCert(new nsNSSCertificate(aClientCert));
-      char *dbkey = nullptr;
-      rv = pipCert->GetDbKey(&dbkey);
-      if (NS_SUCCEEDED(rv) && dbkey) {
-        AddEntryToList(aHostName, fpStr, 
-                       nsDependentCString(dbkey));
+      nsAutoCString dbkey;
+      rv = pipCert->GetDbKey(dbkey);
+      if (NS_SUCCEEDED(rv)) {
+        AddEntryToList(aHostName, fpStr, dbkey);
       }
-      if (dbkey) {
-        PORT_Free(dbkey);
-      }
-    }
-    else {
+    } else {
       nsCString empty;
       AddEntryToList(aHostName, fpStr, empty);
     }
   }
 
   return NS_OK;
 }
 
--- a/security/manager/ssl/nsIX509Cert.idl
+++ b/security/manager/ssl/nsIX509Cert.idl
@@ -15,17 +15,17 @@ interface nsICertVerificationListener;
  /* forward declaration */
  typedef struct CERTCertificateStr CERTCertificate;
 %}
 [ptr] native CERTCertificatePtr(CERTCertificate);
 
 /**
  * This represents a X.509 certificate.
  */
-[scriptable, uuid(f8ed8364-ced9-4c6e-86ba-48af53c393e6)]
+[scriptable, uuid(bdc3979a-5422-4cd5-8589-696b6e96ea83)]
 interface nsIX509Cert : nsISupports {
 
   /**
    *  A nickname for the certificate.
    */
   readonly attribute AString nickname;
 
   /**
@@ -125,17 +125,17 @@ interface nsIX509Cert : nsISupports {
   /**
    *  This certificate's validity period.
    */
   readonly attribute nsIX509CertValidity validity;
 
   /**
    *  A unique identifier of this certificate within the local storage.
    */
-  readonly attribute string dbKey;
+  readonly attribute ACString dbKey;
 
   /**
    *  A human readable identifier to label this certificate.
    */
   readonly attribute AString windowTitle;
 
   /**
    *  Constants to classify the type of a certificate.
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -45,16 +45,20 @@
 #include "pkix/pkixtypes.h"
 #include "secerr.h"
 #include "nssb64.h"
 #include "secasn1.h"
 #include "secder.h"
 #include "ssl.h"
 #include "plbase64.h"
 
+#ifdef XP_WIN
+#include <winsock.h> // for htonl
+#endif
+
 using namespace mozilla;
 using namespace mozilla::psm;
 
 extern PRLogModuleInfo* gPIPNSSLog;
 
 // This is being stored in an uint32_t that can otherwise
 // only take values from nsIX509Cert's list of cert types.
 // As nsIX509Cert is frozen, we choose a value not contained
@@ -481,42 +485,47 @@ nsNSSCertificate::FormatUIStrings(const 
   //   Email: $address(es)
   //   Issued by: $issuerName
   //   Stored in: $token
 
   return rv;
 }
 
 NS_IMETHODIMP
-nsNSSCertificate::GetDbKey(char** aDbKey)
+nsNSSCertificate::GetDbKey(nsACString& aDbKey)
 {
   nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown())
+  if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
-
-  SECItem key;
+  }
 
-  NS_ENSURE_ARG(aDbKey);
-  *aDbKey = nullptr;
-  key.len = NS_NSS_LONG*4+mCert->serialNumber.len+mCert->derIssuer.len;
-  key.data = (unsigned char*) moz_xmalloc(key.len);
-  if (!key.data)
-    return NS_ERROR_OUT_OF_MEMORY;
-  NS_NSS_PUT_LONG(0,key.data); // later put moduleID
-  NS_NSS_PUT_LONG(0,&key.data[NS_NSS_LONG]); // later put slotID
-  NS_NSS_PUT_LONG(mCert->serialNumber.len,&key.data[NS_NSS_LONG*2]);
-  NS_NSS_PUT_LONG(mCert->derIssuer.len,&key.data[NS_NSS_LONG*3]);
-  memcpy(&key.data[NS_NSS_LONG*4], mCert->serialNumber.data,
-         mCert->serialNumber.len);
-  memcpy(&key.data[NS_NSS_LONG*4+mCert->serialNumber.len],
-         mCert->derIssuer.data, mCert->derIssuer.len);
+  static_assert(sizeof(uint64_t) == 8, "type size sanity check");
+  static_assert(sizeof(uint32_t) == 4, "type size sanity check");
+  // The format of the key is the base64 encoding of the following:
+  // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was
+  //                        never implemented)
+  // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was
+  //                        never implemented)
+  // 4 bytes: <serial number length in big-endian order>
+  // 4 bytes: <DER-encoded issuer distinguished name length in big-endian order>
+  // n bytes: <bytes of serial number>
+  // m bytes: <DER-encoded issuer distinguished name>
+  nsAutoCString buf;
+  const char leadingZeroes[] = {0, 0, 0, 0, 0, 0, 0, 0};
+  buf.Append(leadingZeroes, sizeof(leadingZeroes));
+  uint32_t serialNumberLen = htonl(mCert->serialNumber.len);
+  buf.Append(reinterpret_cast<const char*>(&serialNumberLen), sizeof(uint32_t));
+  uint32_t issuerLen = htonl(mCert->derIssuer.len);
+  buf.Append(reinterpret_cast<const char*>(&issuerLen), sizeof(uint32_t));
+  buf.Append(reinterpret_cast<const char*>(mCert->serialNumber.data),
+             mCert->serialNumber.len);
+  buf.Append(reinterpret_cast<const char*>(mCert->derIssuer.data),
+             mCert->derIssuer.len);
 
-  *aDbKey = NSSBase64_EncodeItem(nullptr, nullptr, 0, &key);
-  free(key.data); // SECItem is a 'c' type without a destructor
-  return (*aDbKey) ? NS_OK : NS_ERROR_FAILURE;
+  return Base64Encode(buf, aDbKey);
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetWindowTitle(nsAString& aWindowTitle)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
--- a/security/manager/ssl/nsNSSCertificate.h
+++ b/security/manager/ssl/nsNSSCertificate.h
@@ -127,27 +127,16 @@ private:
    void destructorSafeDestroyNSSReference();
 
    mozilla::ScopedCERTCertList mCertList;
 
    nsNSSCertListEnumerator(const nsNSSCertListEnumerator&) = delete;
    void operator=(const nsNSSCertListEnumerator&) = delete;
 };
 
-
-#define NS_NSS_LONG 4
-#define NS_NSS_GET_LONG(x) ((((unsigned long)((x)[0])) << 24) | \
-                            (((unsigned long)((x)[1])) << 16) | \
-                            (((unsigned long)((x)[2])) <<  8) | \
-                             ((unsigned long)((x)[3])) )
-#define NS_NSS_PUT_LONG(src,dest) (dest)[0] = (((src) >> 24) & 0xff); \
-                                  (dest)[1] = (((src) >> 16) & 0xff); \
-                                  (dest)[2] = (((src) >>  8) & 0xff); \
-                                  (dest)[3] = ((src) & 0xff);
-
 #define NS_X509CERT_CID { /* 660a3226-915c-4ffb-bb20-8985a632df05 */   \
     0x660a3226,                                                        \
     0x915c,                                                            \
     0x4ffb,                                                            \
     { 0xbb, 0x20, 0x89, 0x85, 0xa6, 0x32, 0xdf, 0x05 }                 \
   }
 
 #endif // _NS_NSSCERTIFICATE_H_
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -43,16 +43,20 @@
 #include "certdb.h"
 #include "secerr.h"
 #include "nssb64.h"
 #include "secasn1.h"
 #include "secder.h"
 #include "ssl.h"
 #include "plbase64.h"
 
+#ifdef XP_WIN
+#include <winsock.h> // for ntohl
+#endif
+
 using namespace mozilla;
 using namespace mozilla::psm;
 using mozilla::psm::SharedSSLState;
 
 extern PRLogModuleInfo* gPIPNSSLog;
 
 static nsresult
 attemptToLogInWithDefaultPassword()
@@ -132,52 +136,68 @@ nsNSSCertificateDB::FindCertByDBKey(cons
   NS_ENSURE_ARG_POINTER(_cert);
   *_cert = nullptr;
 
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  SECItem keyItem = {siBuffer, nullptr, 0};
-  SECItem *dummy;
+  static_assert(sizeof(uint64_t) == 8, "type size sanity check");
+  static_assert(sizeof(uint32_t) == 4, "type size sanity check");
+  // (From nsNSSCertificate::GetDbKey)
+  // The format of the key is the base64 encoding of the following:
+  // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was
+  //                        never implemented)
+  // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was
+  //                        never implemented)
+  // 4 bytes: <serial number length in big-endian order>
+  // 4 bytes: <DER-encoded issuer distinguished name length in big-endian order>
+  // n bytes: <bytes of serial number>
+  // m bytes: <DER-encoded issuer distinguished name>
+  nsAutoCString decoded;
+  nsAutoCString tmpDBKey(aDBkey);
+  // Filter out any whitespace for backwards compatibility.
+  tmpDBKey.StripWhitespace();
+  nsresult rv = Base64Decode(tmpDBKey, decoded);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (decoded.Length() < 16) {
+    return NS_ERROR_ILLEGAL_INPUT;
+  }
+  const char* reader = decoded.BeginReading();
+  uint64_t zeroes = *reinterpret_cast<const uint64_t*>(reader);
+  if (zeroes != 0) {
+    return NS_ERROR_ILLEGAL_INPUT;
+  }
+  reader += sizeof(uint64_t);
+  uint32_t serialNumberLen = ntohl(*reinterpret_cast<const uint32_t*>(reader));
+  reader += sizeof(uint32_t);
+  uint32_t issuerLen = ntohl(*reinterpret_cast<const uint32_t*>(reader));
+  reader += sizeof(uint32_t);
+  if (decoded.Length() != 16ULL + serialNumberLen + issuerLen) {
+    return NS_ERROR_ILLEGAL_INPUT;
+  }
   CERTIssuerAndSN issuerSN;
-  //unsigned long moduleID,slotID;
-
-  dummy = NSSBase64_DecodeBuffer(nullptr, &keyItem, aDBkey,
-                                 (uint32_t)strlen(aDBkey)); 
-  if (!dummy || keyItem.len < NS_NSS_LONG*4) {
-    PR_FREEIF(keyItem.data);
-    return NS_ERROR_INVALID_ARG;
-  }
+  issuerSN.serialNumber.len = serialNumberLen;
+  issuerSN.serialNumber.data = (unsigned char*)reader;
+  reader += serialNumberLen;
+  issuerSN.derIssuer.len = issuerLen;
+  issuerSN.derIssuer.data = (unsigned char*)reader;
+  reader += issuerLen;
+  MOZ_ASSERT(reader == decoded.EndReading());
 
-  ScopedCERTCertificate cert;
-  // someday maybe we can speed up the search using the moduleID and slotID
-  // moduleID = NS_NSS_GET_LONG(keyItem.data);
-  // slotID = NS_NSS_GET_LONG(&keyItem.data[NS_NSS_LONG]);
-
-  // build the issuer/SN structure
-  issuerSN.serialNumber.len = NS_NSS_GET_LONG(&keyItem.data[NS_NSS_LONG*2]);
-  issuerSN.derIssuer.len = NS_NSS_GET_LONG(&keyItem.data[NS_NSS_LONG*3]);
-  if (issuerSN.serialNumber.len == 0 || issuerSN.derIssuer.len == 0
-      || issuerSN.serialNumber.len + issuerSN.derIssuer.len
-         != keyItem.len - NS_NSS_LONG*4) {
-    PR_FREEIF(keyItem.data);
-    return NS_ERROR_INVALID_ARG;
-  }
-  issuerSN.serialNumber.data= &keyItem.data[NS_NSS_LONG*4];
-  issuerSN.derIssuer.data= &keyItem.data[NS_NSS_LONG*4+
-                                              issuerSN.serialNumber.len];
-
-  cert = CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN);
-  PR_FREEIF(keyItem.data);
+  ScopedCERTCertificate cert(
+    CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN));
   if (cert) {
     nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
-    if (!nssCert)
+    if (!nssCert) {
       return NS_ERROR_OUT_OF_MEMORY;
+    }
     nssCert.forget(_cert);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsNSSCertificateDB::FindCertNicknames(nsISupports *aToken, 
                                      uint32_t      aType,
@@ -1216,32 +1236,31 @@ nsNSSCertificateDB::getCertNames(CERTCer
   int nc = (numcerts == 0) ? 1 : numcerts;
   tmpArray = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nc);
   if (numcerts == 0) goto finish;
   for (node = CERT_LIST_HEAD(certList);
        !CERT_LIST_END(node, certList);
        node = CERT_LIST_NEXT(node)) {
     if (getCertType(node->cert) == type) {
       RefPtr<nsNSSCertificate> pipCert(new nsNSSCertificate(node->cert));
-      char *dbkey = nullptr;
+      nsAutoCString dbkey;
+      pipCert->GetDbKey(dbkey);
+      nsAutoString keystr = NS_ConvertASCIItoUTF16(dbkey);
       char *namestr = nullptr;
-      nsAutoString certstr;
-      pipCert->GetDbKey(&dbkey);
-      nsAutoString keystr = NS_ConvertASCIItoUTF16(dbkey);
-      PR_FREEIF(dbkey);
       if (type == nsIX509Cert::EMAIL_CERT) {
         namestr = node->cert->emailAddr;
       } else {
         namestr = node->cert->nickname;
         if (namestr) {
           char *sc = strchr(namestr, ':');
           if (sc) *sc = DELIM;
         }
       }
       nsAutoString certname = NS_ConvertASCIItoUTF16(namestr ? namestr : "");
+      nsAutoString certstr;
       certstr.Append(char16_t(DELIM));
       certstr += certname;
       certstr.Append(char16_t(DELIM));
       certstr += keystr;
       tmpArray[i++] = ToNewUnicode(certstr);
     }
   }
 finish:
--- a/security/manager/ssl/nsNSSCertificateFakeTransport.cpp
+++ b/security/manager/ssl/nsNSSCertificateFakeTransport.cpp
@@ -26,17 +26,17 @@ nsNSSCertificateFakeTransport::nsNSSCert
 nsNSSCertificateFakeTransport::~nsNSSCertificateFakeTransport()
 {
   if (mCertSerialization) {
     SECITEM_FreeItem(mCertSerialization, true);
   }
 }
 
 NS_IMETHODIMP
-nsNSSCertificateFakeTransport::GetDbKey(char**)
+nsNSSCertificateFakeTransport::GetDbKey(nsACString&)
 {
   NS_NOTREACHED("Unimplemented on content process");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsNSSCertificateFakeTransport::GetWindowTitle(nsAString&)
 {
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_dbKey.js
@@ -0,0 +1,152 @@
+// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+// 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/.
+
+"use strict";
+
+// This test tests that the nsIX509Cert.dbKey and nsIX509CertDB.findCertByDBKey
+// APIs work as expected. That is, getting a certificate's dbKey and using it
+// in findCertByDBKey should return the same certificate. Also, for backwards
+// compatibility, findCertByDBKey should ignore any whitespace in its input
+// (even though now nsIX509Cert.dbKey will never have whitespace in it).
+
+function hexStringToBytes(hex) {
+  let bytes = [];
+  for (let hexByteStr of hex.split(":")) {
+    bytes.push(parseInt(hexByteStr, 16));
+  }
+  return bytes;
+}
+
+function encodeCommonNameAsBytes(commonName) {
+  // The encoding will look something like this (in hex):
+  // 30 (SEQUENCE) <length of contents>
+  //    31 (SET) <length of contents>
+  //       30 (SEQUENCE) <length of contents>
+  //          06 (OID) 03 (length)
+  //             55 04 03 (id-at-commonName)
+  //          0C (UTF8String) <length of common name>
+  //             <common name bytes>
+  // To make things simple, it would be nice to have the length of each
+  // component be less than 128 bytes (so we can have single-byte lengths).
+  // For this to hold, the maximum length of the contents of the outermost
+  // SEQUENCE must be 127. Everything not in the contents of the common name
+  // will take up 11 bytes, so the value of the common name itself can be at
+  // most 116 bytes.
+  ok(commonName.length <= 116,
+     "test assumption: common name can't be longer than 116 bytes (makes " +
+     "DER encoding easier)");
+  let commonNameOIDBytes = [ 0x06, 0x03, 0x55, 0x04, 0x03 ];
+  let commonNameBytes = [ 0x0C, commonName.length ];
+  for (let i = 0; i < commonName.length; i++) {
+    commonNameBytes.push(commonName.charCodeAt(i));
+  }
+  let bytes = commonNameOIDBytes.concat(commonNameBytes);
+  bytes.unshift(bytes.length);
+  bytes.unshift(0x30); // SEQUENCE
+  bytes.unshift(bytes.length);
+  bytes.unshift(0x31); // SET
+  bytes.unshift(bytes.length);
+  bytes.unshift(0x30); // SEQUENCE
+  return bytes;
+}
+
+function testInvalidDBKey(certDB, dbKey) {
+  let exceptionCaught = false;
+  try {
+    let cert = certDB.findCertByDBKey(dbKey, null);
+  } catch(e) {
+    do_print(e);
+    exceptionCaught = true;
+  }
+  ok(exceptionCaught, "should have thrown and caught an exception");
+}
+
+function testDBKeyForNonexistentCert(certDB, dbKey) {
+  let cert = certDB.findCertByDBKey(dbKey, null);
+  ok(!cert, "shouldn't find cert for given dbKey");
+}
+
+function byteArrayToByteString(bytes) {
+  let byteString = "";
+  for (let b of bytes) {
+    byteString += String.fromCharCode(b);
+  }
+  return byteString;
+}
+
+function run_test() {
+  do_get_profile();
+  let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+  let cert = constructCertFromFile("bad_certs/test-ca.pem");
+  equal(cert.issuerName, "CN=" + cert.issuerCommonName,
+        "test assumption: this certificate's issuer distinguished name " +
+        "consists only of a common name");
+  let issuerBytes = encodeCommonNameAsBytes(cert.issuerCommonName);
+  ok(issuerBytes.length < 256,
+     "test assumption: length of encoded issuer is less than 256 bytes");
+  let serialNumberBytes = hexStringToBytes(cert.serialNumber);
+  ok(serialNumberBytes.length < 256,
+     "test assumption: length of encoded serial number is less than 256 bytes");
+  let dbKeyHeader = [ 0, 0, 0, 0, 0, 0, 0, 0,
+                      0, 0, 0, serialNumberBytes.length,
+                      0, 0, 0, issuerBytes.length ];
+  let expectedDbKeyBytes = dbKeyHeader.concat(serialNumberBytes, issuerBytes);
+  let expectedDbKey = btoa(byteArrayToByteString(expectedDbKeyBytes));
+  equal(cert.dbKey, expectedDbKey,
+        "actual and expected dbKey values should match");
+
+  let certFromDbKey = certDB.findCertByDBKey(expectedDbKey, null);
+  ok(certFromDbKey.equals(cert),
+     "nsIX509CertDB.findCertByDBKey should find the right certificate");
+
+  ok(expectedDbKey.length > 64,
+     "test assumption: dbKey should be longer than 64 characters");
+  let expectedDbKeyWithCRLF = expectedDbKey.replace(/(.{64})/, "$1\r\n");
+  ok(expectedDbKeyWithCRLF.indexOf("\r\n") == 64,
+     "test self-check: adding CRLF to dbKey should succeed");
+  certFromDbKey = certDB.findCertByDBKey(expectedDbKeyWithCRLF, null);
+  ok(certFromDbKey.equals(cert),
+     "nsIX509CertDB.findCertByDBKey should work with dbKey with CRLF");
+
+  let expectedDbKeyWithSpaces = expectedDbKey.replace(/(.{64})/, "$1  ");
+  ok(expectedDbKeyWithSpaces.indexOf("  ") == 64,
+     "test self-check: adding spaces to dbKey should succeed");
+  certFromDbKey = certDB.findCertByDBKey(expectedDbKeyWithSpaces, null);
+  ok(certFromDbKey.equals(cert),
+     "nsIX509CertDB.findCertByDBKey should work with dbKey with spaces");
+
+  // Test some invalid dbKey values.
+  testInvalidDBKey(certDB, "AAAA"); // Not long enough.
+  // No header.
+  testInvalidDBKey(certDB, btoa(byteArrayToByteString(
+    [ 0, 0, 0, serialNumberBytes.length,
+      0, 0, 0, issuerBytes.length ].concat(serialNumberBytes, issuerBytes))));
+  testInvalidDBKey(certDB, btoa(byteArrayToByteString(
+    [ 0, 0, 0, 0, 0, 0, 0, 0,
+      255, 255, 255, 255, // serial number length is way too long
+      255, 255, 255, 255, // issuer length is way too long
+      0, 0, 0, 0 ])));
+  // Truncated issuer.
+  testInvalidDBKey(certDB, btoa(byteArrayToByteString(
+    [ 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 1,
+      0, 0, 0, 10,
+      1,
+      1, 2, 3 ])));
+  // Issuer doesn't decode to valid common name.
+  testDBKeyForNonexistentCert(certDB, btoa(byteArrayToByteString(
+    [ 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 1,
+      0, 0, 0, 3,
+      1,
+      1, 2, 3 ])));
+
+  // zero-length serial number and issuer -> no such certificate
+  testDBKeyForNonexistentCert(certDB, btoa(byteArrayToByteString(
+    [ 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0,
+      0, 0, 0, 0 ])));
+}
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -48,16 +48,17 @@ skip-if = toolkit == 'android' || toolki
 [test_sss_readstate_empty.js]
 [test_sss_readstate_garbage.js]
 [test_sss_readstate_huge.js]
 [test_sss_savestate.js]
 
 [test_pinning_dynamic.js]
 [test_pinning_header_parsing.js]
 
+[test_cert_dbKey.js]
 [test_cert_keyUsage.js]
 [test_logoutAndTeardown.js]
 run-sequentially = hardcoded ports
 [test_ocsp_stapling.js]
 run-sequentially = hardcoded ports
 [test_cert_blocklist.js]
 skip-if = buildapp == "b2g"
 tags = addons