bug 1461037 - lossily convert invalid UTF8 in certificates for display purposes r?jcj draft
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 15 May 2018 16:41:46 -0700
changeset 798882 6be79f22efde0a0d9be1f58c6a87891d379bf5d6
parent 798691 d36cd8bdbc5c0df1d1d7a167f5fedb95c3a3648e
push id110870
push userbmo:dkeeler@mozilla.com
push dateWed, 23 May 2018 17:18:33 +0000
reviewersjcj
bugs1461037
milestone62.0a1
bug 1461037 - lossily convert invalid UTF8 in certificates for display purposes r?jcj In debug builds, we assert if any UTF8-to-UTF16 conversion fails. If we have invalid UTF8 in a certificate, we don't want to assert. So, we now lossily convert invalid UTF8 in certificates for any display purposes. This also handles fields that are supposed to be ASCII in a similar way. MozReview-Commit-ID: 6TdVPDTmNlh
security/manager/ssl/nsNSSCertHelper.cpp
security/manager/ssl/nsNSSCertHelper.h
security/manager/ssl/nsNSSCertificate.cpp
security/manager/ssl/tests/unit/moz.build
security/manager/ssl/tests/unit/pycert.py
security/manager/ssl/tests/unit/test_cert_utf8.js
security/manager/ssl/tests/unit/test_cert_utf8/certificateToAlter.pem
security/manager/ssl/tests/unit/test_cert_utf8/certificateToAlter.pem.certspec
security/manager/ssl/tests/unit/test_cert_utf8/moz.build
security/manager/ssl/tests/unit/xpcshell.ini
--- a/security/manager/ssl/nsNSSCertHelper.cpp
+++ b/security/manager/ssl/nsNSSCertHelper.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/Sprintf.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
 #include "nsIStringBundle.h"
 #include "nsNSSASN1Object.h"
 #include "nsNSSCertTrust.h"
 #include "nsNSSCertValidity.h"
 #include "nsNSSCertificate.h"
+#include "nsReadableUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "prerror.h"
 #include "secder.h"
 
 using namespace mozilla;
 
 static nsresult
 GetPIPNSSBundle(nsIStringBundle** pipnssBundle)
@@ -749,35 +750,40 @@ ProcessExtKeyUsage(SECItem* extData, nsA
 
     text.AppendLiteral(SEPARATOR);
     oids++;
   }
 
   return NS_OK;
 }
 
+void
+LossyUTF8ToUTF16(const char* str, uint32_t len, /*out*/ nsAString& result)
+{
+  nsDependentCSubstring substring(str, len);
+  if (IsUTF8(substring)) {
+    result.Assign(NS_ConvertUTF8toUTF16(substring));
+  } else {
+    char16_t* newUTF16(ToNewUnicode(substring));
+    result.Adopt(newUTF16);
+  }
+}
+
 static nsresult
 ProcessRDN(CERTRDN* rdn, nsAString& finalString)
 {
-  nsresult rv;
-  CERTAVA** avas;
-  CERTAVA* ava;
-  nsString avavalue;
-  nsString type;
-  nsAutoString temp;
-  const char16_t* params[2];
-
-  avas = rdn->avas;
-  while ((ava = *avas++) != 0) {
-    rv = GetOIDText(&ava->type, type);
+  CERTAVA** avas = rdn->avas;
+  for (auto i = 0; avas[i]; i++) {
+    CERTAVA* ava = avas[i];
+    nsAutoString type;
+    nsresult rv = GetOIDText(&ava->type, type);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    // This function returns a string in UTF8 format.
     UniqueSECItem decodeItem(CERT_DecodeAVAValue(&ava->value));
     if (!decodeItem) {
       return NS_ERROR_FAILURE;
     }
 
     // We know we can fit buffer of this length. CERT_RFC1485_EscapeAndQuote
     // will fail if we provide smaller buffer then the result can fit to.
     int escapedValueCapacity = decodeItem->len * 3 + 3;
@@ -786,20 +792,23 @@ ProcessRDN(CERTRDN* rdn, nsAString& fina
     SECStatus status = CERT_RFC1485_EscapeAndQuote(escapedValue.get(),
                                                    escapedValueCapacity,
                                                    (char*)decodeItem->data,
                                                    decodeItem->len);
     if (SECSuccess != status) {
       return NS_ERROR_FAILURE;
     }
 
-    avavalue = NS_ConvertUTF8toUTF16(escapedValue.get());
+    nsAutoString avaValue;
+    LossyUTF8ToUTF16(escapedValue.get(), strlen(escapedValue.get()), avaValue);
 
+    const char16_t* params[2];
     params[0] = type.get();
-    params[1] = avavalue.get();
+    params[1] = avaValue.get();
+    nsAutoString temp;
     PIPBundleFormatStringFromName("AVATemplate", params, 2, temp);
     finalString += temp + NS_LITERAL_STRING("\n");
   }
   return NS_OK;
 }
 
 static nsresult
 ProcessName(CERTName* name, char16_t** value)
@@ -847,18 +856,23 @@ ProcessIA5String(const SECItem& extData,
 {
   ScopedAutoSECItem item;
   if (SEC_ASN1DecodeItem(
         nullptr, &item, SEC_ASN1_GET(SEC_IA5StringTemplate), &extData) !=
       SECSuccess) {
     return NS_ERROR_FAILURE;
   }
 
-  text.AppendASCII(BitwiseCast<char*, unsigned char*>(item.data),
-                   AssertedCast<uint32_t>(item.len));
+  // Yes this is supposed to be ASCII and not UTF8, but this is just for display
+  // purposes.
+  nsAutoString utf16;
+  const char* str = BitwiseCast<char*, unsigned char*>(item.data);
+  uint32_t len = AssertedCast<uint32_t>(item.len);
+  LossyUTF8ToUTF16(str, len, utf16);
+  text.Append(utf16);
   return NS_OK;
 }
 
 static nsresult
 AppendBMPtoUTF16(const UniquePLArenaPool& arena,
                  unsigned char* data,
                  unsigned int len,
                  nsAString& text)
@@ -893,47 +907,53 @@ ProcessGeneralName(const UniquePLArenaPo
   switch (current->type) {
     case certOtherName:
       rv = GetDefaultOIDFormat(&current->name.OthName.oid, key, ' ');
       if (NS_FAILED(rv)) {
         return rv;
       }
       ProcessRawBytes(&current->name.OthName.name, value);
       break;
-    case certRFC822Name:
+    case certRFC822Name: {
       GetPIPNSSBundleString("CertDumpRFC822Name", key);
-      value.AssignASCII((char*)current->name.other.data,
-                        current->name.other.len);
+      const char* str = reinterpret_cast<const char*>(current->name.other.data);
+      uint32_t len = current->name.other.len;
+      LossyUTF8ToUTF16(str, len, value);
       break;
-    case certDNSName:
+    }
+    case certDNSName: {
       GetPIPNSSBundleString("CertDumpDNSName", key);
-      value.AssignASCII((char*)current->name.other.data,
-                        current->name.other.len);
+      const char* str = reinterpret_cast<const char*>(current->name.other.data);
+      uint32_t len = current->name.other.len;
+      LossyUTF8ToUTF16(str, len, value);
       break;
+    }
     case certX400Address:
       GetPIPNSSBundleString("CertDumpX400Address", key);
       ProcessRawBytes(&current->name.other, value);
       break;
     case certDirectoryName:
       GetPIPNSSBundleString("CertDumpDirectoryName", key);
       rv = ProcessName(
         &current->name.directoryName, getter_Copies(value));
       if (NS_FAILED(rv)) {
         return rv;
       }
       break;
     case certEDIPartyName:
       GetPIPNSSBundleString("CertDumpEDIPartyName", key);
       ProcessRawBytes(&current->name.other, value);
       break;
-    case certURI:
+    case certURI: {
       GetPIPNSSBundleString("CertDumpURI", key);
-      value.AssignASCII((char*)current->name.other.data,
-                        current->name.other.len);
+      const char* str = reinterpret_cast<const char*>(current->name.other.data);
+      uint32_t len = current->name.other.len;
+      LossyUTF8ToUTF16(str, len, value);
       break;
+    }
     case certIPAddress: {
       char buf[INET6_ADDRSTRLEN];
       PRStatus status = PR_FAILURE;
       PRNetAddr addr;
       memset(&addr, 0, sizeof(addr));
       GetPIPNSSBundleString("CertDumpIPAddress", key);
       if (current->name.other.len == 4) {
         addr.inet.family = PR_AF_INET;
@@ -1086,21 +1106,25 @@ ProcessUserNotice(SECItem* derNotice, ns
     ProcessRawBytes(derNotice, text);
     return NS_OK;
   }
 
   if (notice->noticeReference.organization.len != 0) {
     switch (notice->noticeReference.organization.type) {
       case siAsciiString:
       case siVisibleString:
-      case siUTF8String:
-        text.Append(NS_ConvertUTF8toUTF16(
-          (const char*)notice->noticeReference.organization.data,
-          notice->noticeReference.organization.len));
+      case siUTF8String: {
+        const char* str = reinterpret_cast<const char*>(
+          notice->noticeReference.organization.data);
+        uint32_t len = notice->noticeReference.organization.len;
+        nsAutoString utf16;
+        LossyUTF8ToUTF16(str, len, utf16);
+        text.Append(utf16);
         break;
+      }
       case siBMPString:
         AppendBMPtoUTF16(arena,
                          notice->noticeReference.organization.data,
                          notice->noticeReference.organization.len,
                          text);
         break;
       default:
         break;
@@ -1120,20 +1144,25 @@ ProcessUserNotice(SECItem* derNotice, ns
     }
   }
   if (notice->displayText.len != 0) {
     text.AppendLiteral(SEPARATOR);
     text.AppendLiteral("    ");
     switch (notice->displayText.type) {
       case siAsciiString:
       case siVisibleString:
-      case siUTF8String:
-        text.Append(NS_ConvertUTF8toUTF16((const char*)notice->displayText.data,
-                                          notice->displayText.len));
+      case siUTF8String: {
+        const char* str =
+          reinterpret_cast<const char*>(notice->displayText.data);
+        uint32_t len = notice->displayText.len;
+        nsAutoString utf16;
+        LossyUTF8ToUTF16(str, len, utf16);
+        text.Append(utf16);
         break;
+      }
       case siBMPString:
         AppendBMPtoUTF16(
           arena, notice->displayText.data, notice->displayText.len, text);
         break;
       default:
         break;
     }
   }
--- a/security/manager/ssl/nsNSSCertHelper.h
+++ b/security/manager/ssl/nsNSSCertHelper.h
@@ -14,16 +14,19 @@
 
 uint32_t
 getCertType(CERTCertificate* cert);
 
 nsresult
 GetCertFingerprintByOidTag(CERTCertificate* nsscert, SECOidTag aOidTag,
                            nsCString& fp);
 
+void
+LossyUTF8ToUTF16(const char* str, uint32_t len, /*out*/ nsAString& result);
+
 // Must be used on the main thread only.
 nsresult
 GetPIPNSSBundleString(const char* stringName, nsAString& result);
 nsresult
 PIPBundleFormatStringFromName(const char* stringName, const char16_t** params,
                               uint32_t numParams, nsAString& result);
 
 #endif // nsNSSCertHelper_h
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -357,33 +357,34 @@ nsNSSCertificate::GetDisplayName(nsAStri
     builtInRootNickname.get(),
     commonName.get(),
     organizationalUnitName.get(),
     organizationName.get(),
     mCert->subjectName,
     mCert->emailAddr
   };
 
-  nsAutoCString nameOption;
-  for (auto nameOptionPtr : nameOptions) {
-    nameOption.Assign(nameOptionPtr);
-    if (nameOption.Length() > 0 && IsUTF8(nameOption)) {
-      CopyUTF8toUTF16(nameOption, aDisplayName);
-      return NS_OK;
+  for (auto nameOption : nameOptions) {
+    if (nameOption) {
+      size_t len = strlen(nameOption);
+      if (len > 0) {
+        LossyUTF8ToUTF16(nameOption, len, aDisplayName);
+        return NS_OK;
+      }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetEmailAddress(nsAString& aEmailAddress)
 {
   if (mCert->emailAddr) {
-    CopyUTF8toUTF16(mCert->emailAddr, aEmailAddress);
+    LossyUTF8ToUTF16(mCert->emailAddr, strlen(mCert->emailAddr), aEmailAddress);
   } else {
     nsresult rv;
     nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
     if (NS_FAILED(rv) || !nssComponent) {
       return NS_ERROR_FAILURE;
     }
     nssComponent->GetPIPNSSBundleString("CertNoEmailAddress", aEmailAddress);
   }
@@ -393,158 +394,153 @@ nsNSSCertificate::GetEmailAddress(nsAStr
 NS_IMETHODIMP
 nsNSSCertificate::GetEmailAddresses(uint32_t* aLength, char16_t*** aAddresses)
 {
   NS_ENSURE_ARG(aLength);
   NS_ENSURE_ARG(aAddresses);
 
   *aLength = 0;
 
-  const char* aAddr;
-  for (aAddr = CERT_GetFirstEmailAddress(mCert.get())
-       ;
-       aAddr
-       ;
-       aAddr = CERT_GetNextEmailAddress(mCert.get(), aAddr))
-  {
+  for (const char* aAddr = CERT_GetFirstEmailAddress(mCert.get());
+       aAddr;
+       aAddr = CERT_GetNextEmailAddress(mCert.get(), aAddr)) {
     ++(*aLength);
   }
 
   *aAddresses = (char16_t**) moz_xmalloc(sizeof(char16_t*) * (*aLength));
-  if (!*aAddresses)
+  if (!*aAddresses) {
     return NS_ERROR_OUT_OF_MEMORY;
+  }
 
-  uint32_t iAddr;
-  for (aAddr = CERT_GetFirstEmailAddress(mCert.get()), iAddr = 0
-       ;
-       aAddr
-       ;
-       aAddr = CERT_GetNextEmailAddress(mCert.get(), aAddr), ++iAddr)
-  {
-    (*aAddresses)[iAddr] = ToNewUnicode(NS_ConvertUTF8toUTF16(aAddr));
+  uint32_t iAddr = 0;
+  for (const char* aAddr = CERT_GetFirstEmailAddress(mCert.get());
+       aAddr;
+       aAddr = CERT_GetNextEmailAddress(mCert.get(), aAddr)) {
+    (*aAddresses)[iAddr] = ToNewUnicode(nsDependentCString(aAddr));
+    iAddr++;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::ContainsEmailAddress(const nsAString& aEmailAddress,
                                        bool* result)
 {
   NS_ENSURE_ARG(result);
   *result = false;
 
-  const char* aAddr = nullptr;
-  for (aAddr = CERT_GetFirstEmailAddress(mCert.get())
-       ;
-       aAddr
-       ;
-       aAddr = CERT_GetNextEmailAddress(mCert.get(), aAddr))
-  {
-    NS_ConvertUTF8toUTF16 certAddr(aAddr);
+  for (const char* aAddr = CERT_GetFirstEmailAddress(mCert.get());
+       aAddr;
+       aAddr = CERT_GetNextEmailAddress(mCert.get(), aAddr)) {
+    nsAutoString certAddr;
+    LossyUTF8ToUTF16(aAddr, strlen(aAddr), certAddr);
     ToLowerCase(certAddr);
 
     nsAutoString testAddr(aEmailAddress);
     ToLowerCase(testAddr);
 
-    if (certAddr == testAddr)
-    {
+    if (certAddr == testAddr) {
       *result = true;
       break;
     }
-
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetCommonName(nsAString& aCommonName)
 {
   aCommonName.Truncate();
   if (mCert) {
     UniquePORTString commonName(CERT_GetCommonName(&mCert->subject));
     if (commonName) {
-      aCommonName = NS_ConvertUTF8toUTF16(commonName.get());
+      LossyUTF8ToUTF16(commonName.get(), strlen(commonName.get()), aCommonName);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetOrganization(nsAString& aOrganization)
 {
   aOrganization.Truncate();
   if (mCert) {
     UniquePORTString organization(CERT_GetOrgName(&mCert->subject));
     if (organization) {
-      aOrganization = NS_ConvertUTF8toUTF16(organization.get());
+      LossyUTF8ToUTF16(organization.get(), strlen(organization.get()),
+                       aOrganization);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetIssuerCommonName(nsAString& aCommonName)
 {
   aCommonName.Truncate();
   if (mCert) {
     UniquePORTString commonName(CERT_GetCommonName(&mCert->issuer));
     if (commonName) {
-      aCommonName = NS_ConvertUTF8toUTF16(commonName.get());
+      LossyUTF8ToUTF16(commonName.get(), strlen(commonName.get()), aCommonName);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetIssuerOrganization(nsAString& aOrganization)
 {
   aOrganization.Truncate();
   if (mCert) {
     UniquePORTString organization(CERT_GetOrgName(&mCert->issuer));
     if (organization) {
-      aOrganization = NS_ConvertUTF8toUTF16(organization.get());
+      LossyUTF8ToUTF16(organization.get(), strlen(organization.get()),
+                       aOrganization);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetIssuerOrganizationUnit(nsAString& aOrganizationUnit)
 {
   aOrganizationUnit.Truncate();
   if (mCert) {
     UniquePORTString organizationUnit(CERT_GetOrgUnitName(&mCert->issuer));
     if (organizationUnit) {
-      aOrganizationUnit = NS_ConvertUTF8toUTF16(organizationUnit.get());
+      LossyUTF8ToUTF16(organizationUnit.get(), strlen(organizationUnit.get()),
+                       aOrganizationUnit);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetOrganizationalUnit(nsAString& aOrganizationalUnit)
 {
   aOrganizationalUnit.Truncate();
   if (mCert) {
     UniquePORTString orgunit(CERT_GetOrgUnitName(&mCert->subject));
     if (orgunit) {
-      aOrganizationalUnit = NS_ConvertUTF8toUTF16(orgunit.get());
+      LossyUTF8ToUTF16(orgunit.get(), strlen(orgunit.get()),
+                       aOrganizationalUnit);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetSubjectName(nsAString& _subjectName)
 {
   _subjectName.Truncate();
   if (mCert->subjectName) {
-    _subjectName = NS_ConvertUTF8toUTF16(mCert->subjectName);
+    LossyUTF8ToUTF16(mCert->subjectName, strlen(mCert->subjectName),
+                     _subjectName);
   }
   return NS_OK;
 }
 
 // Reads dNSName and iPAddress entries encountered in the subject alternative
 // name extension of the certificate and stores them in mSubjectAltNames.
 void
 nsNSSCertificate::GetSubjectAltNames()
@@ -634,17 +630,17 @@ nsNSSCertificate::GetSubjectAltNames(nsA
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetIssuerName(nsAString& _issuerName)
 {
   _issuerName.Truncate();
   if (mCert->issuerName) {
-    _issuerName = NS_ConvertUTF8toUTF16(mCert->issuerName);
+    LossyUTF8ToUTF16(mCert->issuerName, strlen(mCert->issuerName), _issuerName);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetSerialNumber(nsAString& _serialNumber)
 {
   _serialNumber.Truncate();
--- a/security/manager/ssl/tests/unit/moz.build
+++ b/security/manager/ssl/tests/unit/moz.build
@@ -15,16 +15,17 @@ TEST_DIRS += [
     'test_baseline_requirements',
     'test_cert_eku',
     'test_cert_embedded_null',
     'test_cert_keyUsage',
     'test_cert_sha1',
     'test_cert_signatures',
     'test_cert_trust',
     'test_cert_version',
+    'test_cert_utf8',
     'test_certDB_import',
     'test_content_signing',
     'test_ct',
     'test_ev_certs',
     'test_intermediate_basic_usage_constraints',
     'test_keysize',
     'test_keysize_ev',
     'test_missing_intermediate',
--- a/security/manager/ssl/tests/unit/pycert.py
+++ b/security/manager/ssl/tests/unit/pycert.py
@@ -565,16 +565,18 @@ class Certificate(object):
     def addSubjectAlternativeName(self, names, critical):
         subjectAlternativeName = rfc2459.SubjectAltName()
         for count, name in enumerate(names.split(',')):
             generalName = rfc2459.GeneralName()
             if '/' in name:
                 directoryName = stringToDN(name,
                                            tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))
                 generalName['directoryName'] = directoryName
+            elif '@' in name:
+                generalName['rfc822Name'] = name
             else:
                 # The string may have things like '\0' (i.e. a slash
                 # followed by the number zero) that have to be decoded into
                 # the resulting '\x00' (i.e. a byte with value zero).
                 generalName['dNSName'] = name.decode(encoding='string_escape')
             subjectAlternativeName.setComponentByPosition(count, generalName)
         self.addExtension(rfc2459.id_ce_subjectAltName, subjectAlternativeName, critical)
 
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_utf8.js
@@ -0,0 +1,108 @@
+// -*- 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";
+
+do_get_profile();
+
+const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
+                  .getService(Ci.nsIX509CertDB);
+
+function run_test() {
+  const pem =
+    "MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgN" +
+    "VBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIE" +
+    "RpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw" +
+    "6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQG" +
+    "EwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWd" +
+    "pdGFsIC0gQ2VydGljw6FtYXJhIFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbW" +
+    "FyYSBTLkEuMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPU" +
+    "ZYILrgIem08kBeGqentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9" +
+    "JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpO" +
+    "QY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4Ny+LvB" +
+    "2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ54v5aHxwD6Mq0" +
+    "Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm8Ibbq0nXl21Ii/kD" +
+    "wFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhfHjlvgWJsxS3" +
+    "EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkX" +
+    "kwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5" +
+    "lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxE" +
+    "Cp1bczwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1U" +
+    "dDwEB/wQEAwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmD" +
+    "CBlTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb" +
+    "20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVz" +
+    "dGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb" +
+    "3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4" +
+    "902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBCl" +
+    "ETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb" +
+    "0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/Ey" +
+    "XyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNE" +
+    "CW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpO" +
+    "e9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJDW2Za" +
+    "iogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5hRqGEPQg" +
+    "nTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecUIw" +
+    "4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ==";
+  let cert = gCertDB.constructX509FromBase64(pem);
+  // This certificate contains a string that claims to be compatible with UTF-8
+  // but isn't. Getting the asn1 structure of it exercises the code path that
+  // decodes this value. If we don't assert in debug builds, presumably we
+  // handled this value safely.
+  notEqual(cert.ASN1Structure, null,
+           "accessing nsIX509Cert.ASN1Structure shouldn't assert");
+
+  // This certificate has a number of placeholder byte sequences that we can
+  // replace with invalid UTF-8 to ensure that we handle these cases safely.
+  let certificateToAlterFile =
+    do_get_file("test_cert_utf8/certificateToAlter.pem", false);
+  let certificateBytesToAlter =
+    atob(pemToBase64(readFile(certificateToAlterFile)));
+  testUTF8InField("issuerName", "ISSUER CN", certificateBytesToAlter);
+  testUTF8InField("issuerOrganization", "ISSUER O", certificateBytesToAlter);
+  testUTF8InField("issuerOrganizationUnit", "ISSUER OU",
+                  certificateBytesToAlter);
+  testUTF8InField("issuerCommonName", "ISSUER CN", certificateBytesToAlter);
+  testUTF8InField("organization", "SUBJECT O", certificateBytesToAlter);
+  testUTF8InField("organizationalUnit", "SUBJECT OU", certificateBytesToAlter);
+  testUTF8InField("subjectName", "SUBJECT CN", certificateBytesToAlter);
+  testUTF8InField("displayName", "SUBJECT CN", certificateBytesToAlter);
+  testUTF8InField("commonName", "SUBJECT CN", certificateBytesToAlter);
+  testUTF8InField("emailAddress", "SUBJECT EMAILADDRESS",
+                  certificateBytesToAlter);
+  testUTF8InField("subjectAltNames", "SUBJECT ALT DNSNAME",
+                  certificateBytesToAlter);
+  testUTF8InField("subjectAltNames", "SUBJECT ALT RFC822@NAME",
+                  certificateBytesToAlter);
+}
+
+// Every (issuer, serial number) pair must be unique. If NSS ever encounters two
+// different (in terms of encoding) certificates with the same values for this
+// pair, it will refuse to import it (even as a temporary certificate). Since
+// we're creating a number of different certificates, we need to ensure this
+// pair is always unique. The easiest way to do this is to change the issuer
+// distinguished name each time. To make sure this doesn't introduce additional
+// UTF8 issues, always use a printable ASCII value.
+var gUniqueIssuerCounter = 32;
+
+function testUTF8InField(field, replacementPrefix, certificateBytesToAlter) {
+  let toReplace = `${replacementPrefix} REPLACE ME`;
+  let replacement = "";
+  for (let i = 0; i < toReplace.length; i++) {
+    replacement += "\xEB";
+  }
+  let bytes = certificateBytesToAlter.replace(toReplace, replacement);
+  let uniqueIssuerReplacement = "ALWAYS MAKE ME UNIQU" +
+                                String.fromCharCode(gUniqueIssuerCounter);
+  bytes = bytes.replace("ALWAYS MAKE ME UNIQUE", uniqueIssuerReplacement);
+  ok(gUniqueIssuerCounter < 127,
+     "should have enough ASCII replacements to make a unique issuer DN");
+  gUniqueIssuerCounter++;
+  let cert = gCertDB.constructX509(bytes);
+  notEqual(cert[field], null, `accessing nsIX509Cert.${field} shouldn't fail`);
+  notEqual(cert.ASN1Structure, null,
+           "accessing nsIX509Cert.ASN1Structure shouldn't assert");
+  notEqual(cert.getEmailAddresses({}), null,
+           "calling nsIX509Cert.getEmailAddresses() shouldn't assert");
+  ok(!cert.containsEmailAddress("test@test.test"),
+     "calling nsIX509Cert.containsEmailAddress() shouldn't assert");
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_utf8/certificateToAlter.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIUI3hIT6C3aep3bBYeGkq34MtwHEswDQYJKoZIhvcNAQEL
+BQAwfDEcMBoGA1UECgwTSVNTVUVSIE8gUkVQTEFDRSBNRTEdMBsGA1UECwwUSVNT
+VUVSIE9VIFJFUExBQ0UgTUUxHTAbBgNVBAMMFElTU1VFUiBDTiBSRVBMQUNFIE1F
+MR4wHAYDVQQHDBVBTFdBWVMgTUFLRSBNRSBVTklRVUUwIhgPMjAxNjExMjcwMDAw
+MDBaGA8yMDE5MDIwNTAwMDAwMFowgY8xHTAbBgNVBAoMFFNVQkpFQ1QgTyBSRVBM
+QUNFIE1FMR4wHAYDVQQLDBVTVUJKRUNUIE9VIFJFUExBQ0UgTUUxHjAcBgNVBAMM
+FVNVQkpFQ1QgQ04gUkVQTEFDRSBNRTEuMCwGCSqGSIb3DQEJARYfU1VCSkVDVCBF
+TUFJTEFERFJFU1MgUkVQTEFDRSBNRTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72x
+nAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lM
+wmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF
+4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20
+yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xx
+j5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaNRME8wTQYDVR0RBEYwRIIeU1VC
+SkVDVCBBTFQgRE5TTkFNRSBSRVBMQUNFIE1FgSJTVUJKRUNUIEFMVCBSRkM4MjJA
+TkFNRSBSRVBMQUNFIE1FMA0GCSqGSIb3DQEBCwUAA4IBAQBdamlx/6oYDTbf06KN
+6tiBVCtf5GloczF8q+nfLMx90KICCeOLwNLD+Czm8vpImcMMDX2xEGM6aVZ+AGr+
+4Ez004u/WxfIWTIxF5hrlvH7UC3TukQetZzw6xjypbRzTlN74U0cqKmcBQAC7+6P
+sx+mWWajv0zSvMiz8XWf5Da7qFNYNqP6msdiaPRiS4ng6OADCeA3ntKTjpMH6cdB
+nDuCs9FsLwi5DR5/eYcpe8b/tZPHtkNZqZM7etb7tc8klYRcRAxdCKAR3d3W3Ay9
+OlDGqd6uv6oQLsm0eeZM5Udm9X+RjK2b51Fz00WED3RZ10IA6Hy41ucTfoMJLjEw
+dBD8
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_utf8/certificateToAlter.pem.certspec
@@ -0,0 +1,3 @@
+issuer:/O=ISSUER O REPLACE ME/OU=ISSUER OU REPLACE ME/CN=ISSUER CN REPLACE ME/L=ALWAYS MAKE ME UNIQUE
+subject:/O=SUBJECT O REPLACE ME/OU=SUBJECT OU REPLACE ME/CN=SUBJECT CN REPLACE ME/emailAddress=SUBJECT EMAILADDRESS REPLACE ME
+extension:subjectAlternativeName:SUBJECT ALT DNSNAME REPLACE ME,SUBJECT ALT RFC822@NAME REPLACE ME
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_utf8/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# Temporarily disabled. See bug 1256495.
+#test_certificates = (
+#    'certificateToAlter.pem',
+#)
+#
+#for test_certificate in test_certificates:
+#    GeneratedTestCertificate(test_certificate)
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -10,16 +10,17 @@ support-files =
   test_cert_embedded_null/**
   test_cert_isBuiltInRoot_reload/**
   test_cert_keyUsage/**
   test_cert_overrides_read_only/**
   test_cert_sha1/**
   test_cert_signatures/**
   test_cert_trust/**
   test_cert_version/**
+  test_cert_utf8/**
   test_certDB_import/**
   test_certviewer_invalid_oids/**
   test_content_signing/**
   test_ct/**
   test_ev_certs/**
   test_intermediate_basic_usage_constraints/**
   test_keysize/**
   test_keysize_ev/**
@@ -58,16 +59,17 @@ run-sequentially = hardcoded ports
 [test_cert_overrides_read_only.js]
 run-sequentially = hardcoded ports
 [test_cert_override_bits_mismatches.js]
 run-sequentially = hardcoded ports
 [test_cert_sha1.js]
 [test_cert_signatures.js]
 [test_cert_trust.js]
 [test_cert_version.js]
+[test_cert_utf8.js]
 [test_certDB_export_pkcs12.js]
 [test_certDB_export_pkcs12_with_master_password.js]
 [test_certDB_import.js]
 [test_certDB_import_pkcs12.js]
 [test_certDB_import_with_master_password.js]
 [test_certviewer_invalid_oids.js]
 skip-if = toolkit == 'android'
 [test_constructX509FromBase64.js]