/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsCertTree.h" #include "ScopedNSSTypes.h" #include "mozilla/Logging.h" #include "mozilla/Maybe.h" #include "mozilla/intl/AppDateTimeFormat.h" #include "nsArray.h" #include "nsArrayUtils.h" #include "nsHashKeys.h" #include "nsISupportsPrimitives.h" #include "nsIX509CertDB.h" #include "nsIX509Cert.h" #include "nsIX509CertValidity.h" #include "nsNSSCertHelper.h" #include "nsNSSCertificate.h" #include "nsNSSCertificateDB.h" #include "nsNSSHelper.h" #include "nsReadableUtils.h" #include "nsTHashtable.h" #include "nsUnicharUtils.h" #include "nsXPCOMCID.h" #include "nsString.h" #include "nsTreeColumns.h" #include "mozpkix/pkixtypes.h" using namespace mozilla; extern LazyLogModule gPIPNSSLog; // treeArrayElStr // // structure used to hold map of tree. Each thread (an organization // field from a cert) has an element in the array. The numChildren field // stores the number of certs corresponding to that thread. struct treeArrayElStr { nsString orgName; /* heading for thread */ bool open; /* toggle open state for thread */ int32_t certIndex; /* index into cert array for 1st cert */ int32_t numChildren; /* number of chidren (certs) for thread */ }; CompareCacheHashEntryPtr::CompareCacheHashEntryPtr() { entry = new CompareCacheHashEntry; } CompareCacheHashEntryPtr::~CompareCacheHashEntryPtr() { delete entry; } CompareCacheHashEntry::CompareCacheHashEntry() : key(nullptr), mCritInit() { for (int i = 0; i < max_criterions; ++i) { mCritInit[i] = false; mCrit[i].SetIsVoid(true); } } static bool CompareCacheMatchEntry(const PLDHashEntryHdr* hdr, const void* key) { const CompareCacheHashEntryPtr* entryPtr = static_cast(hdr); return entryPtr->entry->key == key; } static void CompareCacheInitEntry(PLDHashEntryHdr* hdr, const void* key) { new (hdr) CompareCacheHashEntryPtr(); CompareCacheHashEntryPtr* entryPtr = static_cast(hdr); entryPtr->entry->key = (void*)key; } static void CompareCacheClearEntry(PLDHashTable* table, PLDHashEntryHdr* hdr) { CompareCacheHashEntryPtr* entryPtr = static_cast(hdr); entryPtr->~CompareCacheHashEntryPtr(); } static const PLDHashTableOps gMapOps = { PLDHashTable::HashVoidPtrKeyStub, CompareCacheMatchEntry, PLDHashTable::MoveEntryStub, CompareCacheClearEntry, CompareCacheInitEntry}; NS_IMPL_ISUPPORTS(nsCertTreeDispInfo, nsICertTreeItem) nsCertTreeDispInfo::~nsCertTreeDispInfo() = default; NS_IMETHODIMP nsCertTreeDispInfo::GetCert(nsIX509Cert** aCert) { NS_ENSURE_ARG(aCert); nsCOMPtr cert = mCert; cert.forget(aCert); return NS_OK; } NS_IMPL_ISUPPORTS(nsCertTree, nsICertTree, nsITreeView) nsCertTree::nsCertTree() : mTreeArray(nullptr), mNumOrgs(0), mNumRows(0), mCompareCache(&gMapOps, sizeof(CompareCacheHashEntryPtr), kInitialCacheLength) { mCellText = nullptr; } void nsCertTree::ClearCompareHash() { mCompareCache.ClearAndPrepareForLength(kInitialCacheLength); } nsCertTree::~nsCertTree() { delete[] mTreeArray; } void nsCertTree::FreeCertArray() { mDispInfo.Clear(); } CompareCacheHashEntry* nsCertTree::getCacheEntry(void* cache, void* aCert) { PLDHashTable& aCompareCache = *static_cast(cache); auto entryPtr = static_cast( aCompareCache.Add(aCert, fallible)); return entryPtr ? entryPtr->entry : nullptr; } void nsCertTree::RemoveCacheEntry(void* key) { mCompareCache.Remove(key); } // CountOrganizations // // Count the number of different organizations encountered in the cert // list. int32_t nsCertTree::CountOrganizations() { uint32_t i, certCount; certCount = mDispInfo.Length(); if (certCount == 0) return 0; nsCOMPtr orgCert = mDispInfo.ElementAt(0)->mCert; nsCOMPtr nextCert = nullptr; int32_t orgCount = 1; for (i = 1; i < certCount; i++) { nextCert = mDispInfo.SafeElementAt(i, nullptr)->mCert; // XXX we assume issuer org is always criterion 1 if (CmpBy(&mCompareCache, orgCert, nextCert, sort_IssuerOrg, sort_None, sort_None) != 0) { orgCert = nextCert; orgCount++; } } return orgCount; } // GetThreadDescAtIndex // // If the row at index is an organization thread, return the collection // associated with that thread. Otherwise, return null. treeArrayEl* nsCertTree::GetThreadDescAtIndex(int32_t index) { int i, idx = 0; if (index < 0) return nullptr; for (i = 0; i < mNumOrgs; i++) { if (index == idx) { return &mTreeArray[i]; } if (mTreeArray[i].open) { idx += mTreeArray[i].numChildren; } idx++; if (idx > index) break; } return nullptr; } // GetCertAtIndex // // If the row at index is a cert, return that cert. Otherwise, return null. already_AddRefed nsCertTree::GetCertAtIndex( int32_t index, int32_t* outAbsoluteCertOffset) { RefPtr certdi( GetDispInfoAtIndex(index, outAbsoluteCertOffset)); if (!certdi) return nullptr; nsCOMPtr ret = certdi->mCert; return ret.forget(); } // If the row at index is a cert, return that cert. Otherwise, return null. already_AddRefed nsCertTree::GetDispInfoAtIndex( int32_t index, int32_t* outAbsoluteCertOffset) { int i, idx = 0, cIndex = 0, nc; if (index < 0) return nullptr; // Loop over the threads for (i = 0; i < mNumOrgs; i++) { if (index == idx) return nullptr; // index is for thread idx++; // get past the thread nc = (mTreeArray[i].open) ? mTreeArray[i].numChildren : 0; if (index < idx + nc) { // cert is within range of this thread int32_t certIndex = cIndex + index - idx; if (outAbsoluteCertOffset) *outAbsoluteCertOffset = certIndex; RefPtr certdi( mDispInfo.SafeElementAt(certIndex, nullptr)); if (certdi) { return certdi.forget(); } break; } if (mTreeArray[i].open) idx += mTreeArray[i].numChildren; cIndex += mTreeArray[i].numChildren; if (idx > index) break; } return nullptr; } nsCertTree::nsCertCompareFunc nsCertTree::GetCompareFuncFromCertType( uint32_t aType) { switch (aType) { case nsIX509Cert::ANY_CERT: case nsIX509Cert::USER_CERT: return CmpUserCert; case nsIX509Cert::EMAIL_CERT: return CmpEmailCert; case nsIX509Cert::CA_CERT: default: return CmpCACert; } } nsresult nsCertTree::GetCertsByTypeFromCertList( const nsTArray>& aCertList, uint32_t aWantedType, nsCertCompareFunc aCertCmpFn, void* aCertCmpFnArg) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetCertsByTypeFromCertList")); nsTHashtable allHostPortOverrideKeys; if (aWantedType == nsIX509Cert::SERVER_CERT) { return NS_ERROR_INVALID_ARG; } int count = 0; for (const auto& cert : aCertList) { bool wantThisCert = (aWantedType == nsIX509Cert::ANY_CERT); if (!wantThisCert) { uint32_t thisCertType; nsresult rv = cert->GetCertType(&thisCertType); if (NS_FAILED(rv)) { return rv; } if (thisCertType == aWantedType) { wantThisCert = true; } } if (wantThisCert) { int InsertPosition = 0; for (; InsertPosition < count; ++InsertPosition) { nsCOMPtr otherCert = nullptr; RefPtr elem( mDispInfo.SafeElementAt(InsertPosition, nullptr)); if (elem) { otherCert = elem->mCert; } if ((*aCertCmpFn)(aCertCmpFnArg, cert, otherCert) < 0) { break; } } nsCertTreeDispInfo* certdi = new nsCertTreeDispInfo(cert); mDispInfo.InsertElementAt(InsertPosition, certdi); ++count; ++InsertPosition; } } return NS_OK; } // LoadCerts // // Load all of the certificates in the DB for this type. Sort them // by token, organization, then common name. NS_IMETHODIMP nsCertTree::LoadCertsFromCache(const nsTArray>& aCache, uint32_t aType) { if (mTreeArray) { FreeCertArray(); delete[] mTreeArray; mTreeArray = nullptr; mNumRows = 0; } ClearCompareHash(); nsresult rv = GetCertsByTypeFromCertList( aCache, aType, GetCompareFuncFromCertType(aType), &mCompareCache); if (NS_FAILED(rv)) { return rv; } return UpdateUIContents(); } nsresult nsCertTree::UpdateUIContents() { uint32_t count = mDispInfo.Length(); mNumOrgs = CountOrganizations(); mTreeArray = new treeArrayEl[mNumOrgs]; mCellText = nsArrayBase::Create(); if (count) { uint32_t j = 0; nsCOMPtr orgCert = mDispInfo.ElementAt(j)->mCert; for (int32_t i = 0; i < mNumOrgs; i++) { nsString& orgNameRef = mTreeArray[i].orgName; if (!orgCert) { GetPIPNSSBundleString("CertOrgUnknown", orgNameRef); } else { orgCert->GetIssuerOrganization(orgNameRef); if (orgNameRef.IsEmpty()) orgCert->GetCommonName(orgNameRef); } mTreeArray[i].open = true; mTreeArray[i].certIndex = j; mTreeArray[i].numChildren = 1; if (++j >= count) break; nsCOMPtr nextCert = mDispInfo.SafeElementAt(j, nullptr)->mCert; while (0 == CmpBy(&mCompareCache, orgCert, nextCert, sort_IssuerOrg, sort_None, sort_None)) { mTreeArray[i].numChildren++; if (++j >= count) break; nextCert = mDispInfo.SafeElementAt(j, nullptr)->mCert; } orgCert = nextCert; } } if (mTree) { mTree->BeginUpdateBatch(); mTree->RowCountChanged(0, -mNumRows); } mNumRows = count + mNumOrgs; if (mTree) mTree->EndUpdateBatch(); return NS_OK; } NS_IMETHODIMP nsCertTree::DeleteEntryObject(uint32_t index) { if (!mTreeArray) { return NS_ERROR_FAILURE; } nsCOMPtr certdb = do_GetService("@mozilla.org/security/x509certdb;1"); if (!certdb) { return NS_ERROR_FAILURE; } int i; uint32_t idx = 0, cIndex = 0, nc; // Loop over the threads for (i = 0; i < mNumOrgs; i++) { if (index == idx) return NS_OK; // index is for thread idx++; // get past the thread nc = (mTreeArray[i].open) ? mTreeArray[i].numChildren : 0; if (index < idx + nc) { // cert is within range of this thread int32_t certIndex = cIndex + index - idx; RefPtr certdi( mDispInfo.SafeElementAt(certIndex, nullptr)); if (certdi) { nsCOMPtr cert = certdi->mCert; RemoveCacheEntry(cert); certdb->DeleteCertificate(cert); } mDispInfo.RemoveElementAt(certIndex); delete[] mTreeArray; mTreeArray = nullptr; return UpdateUIContents(); } if (mTreeArray[i].open) idx += mTreeArray[i].numChildren; cIndex += mTreeArray[i].numChildren; if (idx > index) break; } return NS_ERROR_FAILURE; } ////////////////////////////////////////////////////////////////////////////// // // Begin nsITreeView methods // ///////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsCertTree::GetCert(uint32_t aIndex, nsIX509Cert** _cert) { NS_ENSURE_ARG(_cert); *_cert = GetCertAtIndex(aIndex).take(); return NS_OK; } NS_IMETHODIMP nsCertTree::GetTreeItem(uint32_t aIndex, nsICertTreeItem** _treeitem) { NS_ENSURE_ARG(_treeitem); RefPtr certdi(GetDispInfoAtIndex(aIndex)); if (!certdi) return NS_ERROR_FAILURE; *_treeitem = certdi; NS_IF_ADDREF(*_treeitem); return NS_OK; } NS_IMETHODIMP nsCertTree::GetRowCount(int32_t* aRowCount) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; uint32_t count = 0; for (int32_t i = 0; i < mNumOrgs; i++) { if (mTreeArray[i].open) { count += mTreeArray[i].numChildren; } count++; } *aRowCount = count; return NS_OK; } NS_IMETHODIMP nsCertTree::GetSelection(nsITreeSelection** aSelection) { *aSelection = mSelection; NS_IF_ADDREF(*aSelection); return NS_OK; } NS_IMETHODIMP nsCertTree::SetSelection(nsITreeSelection* aSelection) { mSelection = aSelection; return NS_OK; } NS_IMETHODIMP nsCertTree::GetRowProperties(int32_t index, nsAString& aProps) { return NS_OK; } NS_IMETHODIMP nsCertTree::GetCellProperties(int32_t row, nsTreeColumn* col, nsAString& aProps) { return NS_OK; } NS_IMETHODIMP nsCertTree::GetColumnProperties(nsTreeColumn* col, nsAString& aProps) { return NS_OK; } NS_IMETHODIMP nsCertTree::IsContainer(int32_t index, bool* _retval) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; treeArrayEl* el = GetThreadDescAtIndex(index); if (el) { *_retval = true; } else { *_retval = false; } return NS_OK; } NS_IMETHODIMP nsCertTree::IsContainerOpen(int32_t index, bool* _retval) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; treeArrayEl* el = GetThreadDescAtIndex(index); if (el && el->open) { *_retval = true; } else { *_retval = false; } return NS_OK; } NS_IMETHODIMP nsCertTree::IsContainerEmpty(int32_t index, bool* _retval) { *_retval = !mTreeArray; return NS_OK; } NS_IMETHODIMP nsCertTree::IsSeparator(int32_t index, bool* _retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsCertTree::GetParentIndex(int32_t rowIndex, int32_t* _retval) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; int i, idx = 0; for (i = 0; i < mNumOrgs && idx < rowIndex; i++, idx++) { if (mTreeArray[i].open) { if (rowIndex <= idx + mTreeArray[i].numChildren) { *_retval = idx; return NS_OK; } idx += mTreeArray[i].numChildren; } } *_retval = -1; return NS_OK; } NS_IMETHODIMP nsCertTree::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool* _retval) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; int i, idx = 0; for (i = 0; i < mNumOrgs && idx <= rowIndex; i++, idx++) { if (mTreeArray[i].open) { idx += mTreeArray[i].numChildren; if (afterIndex <= idx) { *_retval = afterIndex < idx; return NS_OK; } } } *_retval = false; return NS_OK; } NS_IMETHODIMP nsCertTree::GetLevel(int32_t index, int32_t* _retval) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; treeArrayEl* el = GetThreadDescAtIndex(index); if (el) { *_retval = 0; } else { *_retval = 1; } return NS_OK; } NS_IMETHODIMP nsCertTree::GetImageSrc(int32_t row, nsTreeColumn* col, nsAString& _retval) { _retval.Truncate(); return NS_OK; } NS_IMETHODIMP nsCertTree::GetCellValue(int32_t row, nsTreeColumn* col, nsAString& _retval) { _retval.Truncate(); return NS_OK; } static void PRTimeToLocalDateString(PRTime time, nsAString& result) { PRExplodedTime explodedTime; PR_ExplodeTime(time, PR_LocalTimeParameters, &explodedTime); intl::DateTimeFormat::StyleBag style; style.date = Some(intl::DateTimeFormat::Style::Long); style.time = Nothing(); Unused << intl::AppDateTimeFormat::Format(style, &explodedTime, result); } NS_IMETHODIMP nsCertTree::GetCellText(int32_t row, nsTreeColumn* col, nsAString& _retval) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; nsresult rv = NS_OK; _retval.Truncate(); const nsAString& colID = col->GetId(); treeArrayEl* el = GetThreadDescAtIndex(row); if (el) { if (u"certcol"_ns.Equals(colID)) _retval.Assign(el->orgName); else _retval.Truncate(); return NS_OK; } int32_t absoluteCertOffset; RefPtr certdi( GetDispInfoAtIndex(row, &absoluteCertOffset)); if (!certdi) return NS_ERROR_FAILURE; nsCOMPtr cert = certdi->mCert; int32_t colIndex = col->Index(); uint32_t arrayIndex = absoluteCertOffset + colIndex * (mNumRows - mNumOrgs); uint32_t arrayLength = 0; if (mCellText) { mCellText->GetLength(&arrayLength); } if (arrayIndex < arrayLength) { nsCOMPtr myString( do_QueryElementAt(mCellText, arrayIndex)); if (myString) { myString->GetData(_retval); return NS_OK; } } if (u"certcol"_ns.Equals(colID)) { if (!cert) { rv = GetPIPNSSBundleString("CertNotStored", _retval); } else { rv = cert->GetDisplayName(_retval); } } else if (u"tokencol"_ns.Equals(colID) && cert) { rv = cert->GetTokenName(_retval); } else if (u"emailcol"_ns.Equals(colID) && cert) { rv = cert->GetEmailAddress(_retval); } else if (u"issuedcol"_ns.Equals(colID) && cert) { nsCOMPtr validity; rv = cert->GetValidity(getter_AddRefs(validity)); if (NS_SUCCEEDED(rv)) { PRTime notBefore; rv = validity->GetNotBefore(¬Before); if (NS_SUCCEEDED(rv)) { PRTimeToLocalDateString(notBefore, _retval); } } } else if (u"expiredcol"_ns.Equals(colID) && cert) { nsCOMPtr validity; rv = cert->GetValidity(getter_AddRefs(validity)); if (NS_SUCCEEDED(rv)) { PRTime notAfter; rv = validity->GetNotAfter(¬After); if (NS_SUCCEEDED(rv)) { PRTimeToLocalDateString(notAfter, _retval); } } } else if (u"serialnumcol"_ns.Equals(colID) && cert) { rv = cert->GetSerialNumber(_retval); } else { return NS_ERROR_FAILURE; } if (mCellText) { nsCOMPtr text( do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); text->SetData(_retval); mCellText->ReplaceElementAt(text, arrayIndex); } return rv; } NS_IMETHODIMP nsCertTree::SetTree(mozilla::dom::XULTreeElement* tree) { mTree = tree; return NS_OK; } NS_IMETHODIMP nsCertTree::ToggleOpenState(int32_t index) { if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED; treeArrayEl* el = GetThreadDescAtIndex(index); if (el) { el->open = !el->open; int32_t newChildren = (el->open) ? el->numChildren : -el->numChildren; if (mTree) { mTree->RowCountChanged(index + 1, newChildren); mTree->InvalidateRow(index); } } return NS_OK; } NS_IMETHODIMP nsCertTree::CycleHeader(nsTreeColumn* col) { return NS_OK; } NS_IMETHODIMP nsCertTree::SelectionChangedXPCOM() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsCertTree::CycleCell(int32_t row, nsTreeColumn* col) { return NS_OK; } NS_IMETHODIMP nsCertTree::IsEditable(int32_t row, nsTreeColumn* col, bool* _retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsCertTree::SetCellValue(int32_t row, nsTreeColumn* col, const nsAString& value) { return NS_OK; } NS_IMETHODIMP nsCertTree::SetCellText(int32_t row, nsTreeColumn* col, const nsAString& value) { return NS_OK; } // // CanDrop // NS_IMETHODIMP nsCertTree::CanDrop(int32_t index, int32_t orientation, mozilla::dom::DataTransfer* aDataTransfer, bool* _retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = false; return NS_OK; } // // Drop // NS_IMETHODIMP nsCertTree::Drop(int32_t row, int32_t orient, mozilla::dom::DataTransfer* aDataTransfer) { return NS_OK; } // // IsSorted // // ... // NS_IMETHODIMP nsCertTree::IsSorted(bool* _retval) { *_retval = false; return NS_OK; } #define RETURN_NOTHING void nsCertTree::CmpInitCriterion(nsIX509Cert* cert, CompareCacheHashEntry* entry, sortCriterion crit, int32_t level) { NS_ENSURE_TRUE(cert && entry, RETURN_NOTHING); entry->mCritInit[level] = true; nsString& str = entry->mCrit[level]; switch (crit) { case sort_IssuerOrg: cert->GetIssuerOrganization(str); if (str.IsEmpty()) cert->GetCommonName(str); break; case sort_Org: cert->GetOrganization(str); break; case sort_Token: cert->GetTokenName(str); break; case sort_CommonName: cert->GetCommonName(str); break; case sort_IssuedDateDescending: { nsresult rv; nsCOMPtr validity; PRTime notBefore; rv = cert->GetValidity(getter_AddRefs(validity)); if (NS_SUCCEEDED(rv)) { rv = validity->GetNotBefore(¬Before); } if (NS_SUCCEEDED(rv)) { PRExplodedTime explodedTime; PR_ExplodeTime(notBefore, PR_GMTParameters, &explodedTime); char datebuf[20]; // 4 + 2 + 2 + 2 + 2 + 2 + 1 = 15 if (0 != PR_FormatTime(datebuf, sizeof(datebuf), "%Y%m%d%H%M%S", &explodedTime)) { str = NS_ConvertASCIItoUTF16(nsDependentCString(datebuf)); } } } break; case sort_Email: cert->GetEmailAddress(str); break; case sort_None: default: break; } } int32_t nsCertTree::CmpByCrit(nsIX509Cert* a, CompareCacheHashEntry* ace, nsIX509Cert* b, CompareCacheHashEntry* bce, sortCriterion crit, int32_t level) { NS_ENSURE_TRUE(a && ace && b && bce, 0); if (!ace->mCritInit[level]) { CmpInitCriterion(a, ace, crit, level); } if (!bce->mCritInit[level]) { CmpInitCriterion(b, bce, crit, level); } nsString& str_a = ace->mCrit[level]; nsString& str_b = bce->mCrit[level]; int32_t result; if (!str_a.IsVoid() && !str_b.IsVoid()) result = Compare(str_a, str_b, nsCaseInsensitiveStringComparator); else result = str_a.IsVoid() ? (str_b.IsVoid() ? 0 : -1) : 1; if (sort_IssuedDateDescending == crit) result *= -1; // reverse compare order return result; } int32_t nsCertTree::CmpBy(void* cache, nsIX509Cert* a, nsIX509Cert* b, sortCriterion c0, sortCriterion c1, sortCriterion c2) { // This will be called when comparing items for display sorting. // Some items might have no cert associated, so either a or b is null. // We want all those orphans show at the top of the list, // so we treat a null cert as "smaller" by returning -1. // We don't try to sort within the group of no-cert entries, // so we treat them as equal wrt sort order. if (!a && !b) return 0; if (!a) return -1; if (!b) return 1; NS_ENSURE_TRUE(cache && a && b, 0); CompareCacheHashEntry* ace = getCacheEntry(cache, a); CompareCacheHashEntry* bce = getCacheEntry(cache, b); int32_t cmp; cmp = CmpByCrit(a, ace, b, bce, c0, 0); if (cmp != 0) return cmp; if (c1 != sort_None) { cmp = CmpByCrit(a, ace, b, bce, c1, 1); if (cmp != 0) return cmp; if (c2 != sort_None) { return CmpByCrit(a, ace, b, bce, c2, 2); } } return cmp; } int32_t nsCertTree::CmpCACert(void* cache, nsIX509Cert* a, nsIX509Cert* b) { // XXX we assume issuer org is always criterion 1 return CmpBy(cache, a, b, sort_IssuerOrg, sort_Org, sort_Token); } int32_t nsCertTree::CmpUserCert(void* cache, nsIX509Cert* a, nsIX509Cert* b) { // XXX we assume issuer org is always criterion 1 return CmpBy(cache, a, b, sort_IssuerOrg, sort_Token, sort_IssuedDateDescending); } int32_t nsCertTree::CmpEmailCert(void* cache, nsIX509Cert* a, nsIX509Cert* b) { // XXX we assume issuer org is always criterion 1 return CmpBy(cache, a, b, sort_IssuerOrg, sort_Email, sort_CommonName); }