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
--- 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(¤t->name.OthName.oid, key, ' ');
if (NS_FAILED(rv)) {
return rv;
}
ProcessRawBytes(¤t->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(¤t->name.other, value);
break;
case certDirectoryName:
GetPIPNSSBundleString("CertDumpDirectoryName", key);
rv = ProcessName(
¤t->name.directoryName, getter_Copies(value));
if (NS_FAILED(rv)) {
return rv;
}
break;
case certEDIPartyName:
GetPIPNSSBundleString("CertDumpEDIPartyName", key);
ProcessRawBytes(¤t->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]