/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* 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/. */ // Comprehensive tests for PK11 URI resolution via PK11_FindCertsFromURI and // PK11_FindCertFromURI (lib/pk11wrap/pk11cert.c: find_certs_from_uri / // transfer_uri_certs_to_collection). // // The pkcs11testmodule exposes a "public certs" token (slot 4) with: // cert1: CKA_ID = {0x00..0x0f}, CKA_LABEL = "cert1" // cert2: CKA_ID = {0x10..0x1f}, CKA_LABEL = "cert2" // Token label: "Test PKCS11 Public Certs Token" // Manufacturer ID: "Test PKCS11 Manufacturer ID" // Model: "Test Model" // Serial: (16 bytes, all spaces/zeros) // // cert1 and cert2 share identical DER (certValue/serial/issuer), so NSS // deduplicates them to one NSSCertificate under cert1's label when no id= // filter is applied. // // Two fixtures are used: // // PK11URIResolutionTest — cold cache; cert lookup goes entirely through the // token-search path (nssToken_FindObjectsByTemplate). // // PK11URIResolutionCachedTest — warm cache; SetUp primes the NSS // trust-domain cert cache with a bare pkcs11: lookup so that subsequent // calls exercise transfer_uri_certs_to_collection past its count==0 early // return. #include #include "nss.h" #include "pk11pub.h" #include "prerror.h" #include "secmod.h" #include "nss_scoped_ptrs.h" #include "gtest/gtest.h" namespace nss_test { // --------------------------------------------------------------------------- // Shared helpers // --------------------------------------------------------------------------- class PK11URITestBase : public ::testing::Test { protected: size_t CountCerts(CERTCertList* list) { if (!list) return 0; size_t n = 0; for (CERTCertListNode* node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); node = CERT_LIST_NEXT(node)) { n++; } return n; } bool HasCertWithNickname(CERTCertList* list, const char* nickname) { for (CERTCertListNode* node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); node = CERT_LIST_NEXT(node)) { if (node->cert->nickname && strcmp(node->cert->nickname, nickname) == 0) return true; } return false; } }; // --------------------------------------------------------------------------- // Cold-cache fixture — token-search path // --------------------------------------------------------------------------- class PK11URIResolutionTest : public PK11URITestBase { public: void SetUp() override { ASSERT_EQ(SECSuccess, SECMOD_AddNewModule( "PK11URIResolutionTest", DLL_PREFIX "pkcs11testmodule." DLL_SUFFIX, 0, 0)) << PORT_ErrorToName(PORT_GetError()); } void TearDown() override { int type; ASSERT_EQ(SECSuccess, SECMOD_DeleteModule("PK11URIResolutionTest", &type)); ASSERT_EQ(SECMOD_EXTERNAL, type); } }; // --------------------------------------------------------------------------- // Warm-cache fixture — exercises transfer_uri_certs_to_collection body // --------------------------------------------------------------------------- class PK11URIResolutionCachedTest : public PK11URITestBase { public: void SetUp() override { ASSERT_EQ(SECSuccess, SECMOD_AddNewModule( "PK11URIResolutionCachedTest", DLL_PREFIX "pkcs11testmodule." DLL_SUFFIX, 0, 0)) << PORT_ErrorToName(PORT_GetError()); // A bare pkcs11: lookup goes through the token-search path and registers // the discovered NSSCertificate objects in the trust-domain cache. We // keep the returned CERTCertList alive in prime_ so that each // NSSCertificate's refcount stays above zero throughout the test body. // nssTrustDomain_GetCertsFromCache uses the issuerAndSN hash, which only // contains certs with refcount > 0; dropping the last reference (by // letting a local ScopedCERTCertList go out of scope here) would evict // the cert and leave the cache empty again for the actual test call. prime_.reset(PK11_FindCertsFromURI("pkcs11:", nullptr)); ASSERT_NE(nullptr, prime_.get()); } void TearDown() override { // Release prime_ first so that its CERT_DestroyCertificate calls happen // before RemoveTokenCertsFromCache runs inside SECMOD_DeleteModule. prime_.reset(); int type; ASSERT_EQ(SECSuccess, SECMOD_DeleteModule("PK11URIResolutionCachedTest", &type)); ASSERT_EQ(SECMOD_EXTERNAL, type); } private: ScopedCERTCertList prime_; }; // --------------------------------------------------------------------------- // Shared nickname constants // --------------------------------------------------------------------------- static const char kNickname1[] = "Test PKCS11 Public Certs Token:cert1"; static const char kNickname2[] = "Test PKCS11 Public Certs Token:cert2"; // =========================================================================== // PK11URIResolutionTest — cold-cache tests (token-search path) // =========================================================================== // pkcs11: with no attributes must return certs from all tokens. cert1 and // cert2 in the test module share identical DER (same certValue/serial/issuer), // so NSS deduplicates them into a single NSSCertificate under cert1's label. // The result set therefore contains cert1 (from slot 4) plus whatever certs // live in the internal softoken db — we check the token cert is present // without asserting an exact count. TEST_F(PK11URIResolutionTest, BareURIReturnsAllCerts) { ScopedCERTCertList certs(PK11_FindCertsFromURI("pkcs11:", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // id= matching cert1 (0x00..0x0f) returns only cert1. TEST_F(PK11URIResolutionTest, FilterById_MatchesCert1) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // id= matching cert2 (0x10..0x1f) returns only cert2. TEST_F(PK11URIResolutionTest, FilterById_MatchesCert2) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname2)); } // id= with uppercase percent-encoding (%0A–%0F) must be accepted; the // pk11uri_Unescape 'A'–'F' branch is exercised. TEST_F(PK11URIResolutionTest, FilterById_UppercaseHex) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // id= with correct length but bytes that match neither cert returns null. TEST_F(PK11URIResolutionTest, FilterById_WrongBytes) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%aa%bb%cc%dd%ee%ff%aa%bb%cc%dd%ee%ff%aa%bb%cc%dd", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // id= whose length differs from any cert ID returns null. TEST_F(PK11URIResolutionTest, FilterById_WrongLength) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:id=%00%01%02", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // type=cert is the only permitted value; all certs are returned. TEST_F(PK11URIResolutionTest, FilterByType_CertAllowed) { ScopedCERTCertList certs(PK11_FindCertsFromURI("pkcs11:type=cert", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(2UL, CountCerts(certs.get())); } // type=private must be rejected immediately (not a certificate type). TEST_F(PK11URIResolutionTest, FilterByType_PrivateRejected) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:type=private", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // type=secret-key must also be rejected. TEST_F(PK11URIResolutionTest, FilterByType_SecretKeyRejected) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:type=secret-key", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // token= matching the public-certs token label returns that token's cert. // cert1 and cert2 share the same DER and deduplicate to one NSSCertificate. TEST_F(PK11URIResolutionTest, FilterByToken_Match) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:token=Test%20PKCS11%20Public%20Certs%20Token", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // token= that matches no loaded token returns null. TEST_F(PK11URIResolutionTest, FilterByToken_NoMatch) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:token=Nonexistent%20Token", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // manufacturer= matching the test module manufacturer returns cert1 (cert1 and // cert2 deduplicate; the internal NSS slots have a different manufacturer so // they are excluded). TEST_F(PK11URIResolutionTest, FilterByManufacturer_Match) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:manufacturer=Test%20PKCS11%20Manufacturer%20ID", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // manufacturer= with an unknown value returns null. TEST_F(PK11URIResolutionTest, FilterByManufacturer_NoMatch) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:manufacturer=No%20Such%20Manufacturer", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // model= matching the test token model returns cert1 (same deduplication // argument as manufacturer; internal NSS slots use a different model string). TEST_F(PK11URIResolutionTest, FilterByModel_Match) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:model=Test%20Model", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // model= with an unknown value returns null. TEST_F(PK11URIResolutionTest, FilterByModel_NoMatch) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:model=No%20Such%20Model", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Combining id= and token= must narrow results to one cert on the right token. TEST_F(PK11URIResolutionTest, FilterByIdAndToken) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f;" "token=Test%20PKCS11%20Public%20Certs%20Token", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // Combining id= with a wrong token= must return null. TEST_F(PK11URIResolutionTest, FilterByIdAndToken_WrongToken) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f;" "token=Wrong%20Token", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // object= (CKA_LABEL) matching cert1 returns cert1 via the token-search path // (nssTrustDomain_GetCertsForNicknameFromCache misses on short label; the // token search uses a CKA_LABEL attribute template that matches). TEST_F(PK11URIResolutionTest, FilterByObject_Label) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:object=cert1", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // A URI with a query component (pin-value) must still resolve certs normally; // the query string exercises pk11uri_CompareQueryAttributeName during parsing. TEST_F(PK11URIResolutionTest, URIWithQueryAttribute) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:token=Test%20PKCS11%20Public%20Certs%20Token?pin-value=", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // A string lacking the pkcs11: scheme must be rejected. TEST_F(PK11URIResolutionTest, InvalidURI_NoScheme) { ScopedCERTCertList certs(PK11_FindCertsFromURI("not-a-pkcs11-uri", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Malformed percent-encoding in the URI must be rejected. TEST_F(PK11URIResolutionTest, InvalidURI_BadPercentEncoding) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:id=%2;manufacturer=test", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Bug 2023478: PK11URI_ParseURI must reject a bare '%' at the end of the // string. strchr(PK11URI_HEXDIG, '\0') returns non-NULL (C standard: NUL is // part of the string for strchr), so an unguarded check treats the NUL byte as // a valid hex digit and reads 1-2 bytes past the heap allocation. // These three inputs cover the three trigger cases from the bug report. TEST_F(PK11URIResolutionTest, InvalidURI_TruncatedPercent_BareAtEnd) { ScopedPK11URI uri(PK11URI_ParseURI("pkcs11:id=%")); EXPECT_EQ(nullptr, uri.get()); } TEST_F(PK11URIResolutionTest, InvalidURI_TruncatedPercent_OneDigitAtEnd) { ScopedPK11URI uri(PK11URI_ParseURI("pkcs11:id=%A")); EXPECT_EQ(nullptr, uri.get()); } TEST_F(PK11URIResolutionTest, InvalidURI_TruncatedPercent_TokenAttr) { ScopedPK11URI uri(PK11URI_ParseURI("pkcs11:token=%2")); EXPECT_EQ(nullptr, uri.get()); } // Duplicate path attribute must be rejected. TEST_F(PK11URIResolutionTest, InvalidURI_DuplicateAttribute) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:token=aaa;token=bbb", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // PK11_FindCertFromURI with a matching id= returns the expected cert. TEST_F(PK11URIResolutionTest, FindCertFromURI_IdMatch) { ScopedCERTCertificate cert(PK11_FindCertFromURI( "pkcs11:id=%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f", nullptr)); ASSERT_NE(nullptr, cert.get()); EXPECT_EQ(0, strcmp(cert->nickname, kNickname1)); } // PK11_FindCertFromURI with a non-matching id= returns null. TEST_F(PK11URIResolutionTest, FindCertFromURI_NoMatch) { ScopedCERTCertificate cert( PK11_FindCertFromURI("pkcs11:id=%00%01%02", nullptr)); EXPECT_EQ(nullptr, cert.get()); } // PK11_FindCertFromURI with type=private must return null. TEST_F(PK11URIResolutionTest, FindCertFromURI_WrongType) { ScopedCERTCertificate cert( PK11_FindCertFromURI("pkcs11:type=private", nullptr)); EXPECT_EQ(nullptr, cert.get()); } // PK11_GetTokenURI builds a pkcs11: URI from the slot's token info fields, // exercising PK11URI_CreateURI, PK11URI_FormatURI, and pk11uri_Escape (which // percent-encodes spaces and other chars not in PK11URI_PCHAR). TEST_F(PK11URIResolutionTest, GetTokenURI) { ScopedPK11SlotInfo slot( PK11_FindSlotByName("Test PKCS11 Public Certs Token")); ASSERT_NE(nullptr, slot.get()); char* raw = PK11_GetTokenURI(slot.get()); ASSERT_NE(nullptr, raw); EXPECT_EQ(0, strncmp(raw, "pkcs11:", 7)); EXPECT_NE(nullptr, strstr(raw, "token=Test%20PKCS11%20Public%20Certs%20Token")); EXPECT_NE(nullptr, strstr(raw, "model=Test%20Model")); PORT_Free(raw); } // PK11_GetModuleURI builds a pkcs11: URI from the module's library info, // exercising the library-manufacturer / library-description / library-version // path attributes. TEST_F(PK11URIResolutionTest, GetModuleURI) { ScopedSECMODModule mod(SECMOD_FindModule("PK11URIResolutionTest")); ASSERT_NE(nullptr, mod.get()); char* raw = PK11_GetModuleURI(mod.get()); ASSERT_NE(nullptr, raw); EXPECT_EQ(0, strncmp(raw, "pkcs11:", 7)); EXPECT_NE(nullptr, strstr(raw, "library-description=Test%20PKCS11%20Library")); PORT_Free(raw); } // PK11_FindSlotByName dispatches to pk11_FindSlotByTokenURI when the name // starts with "pkcs11:", exercising pk11_MatchSlotByTokenURI. TEST_F(PK11URIResolutionTest, FindSlotByTokenURI) { ScopedPK11SlotInfo slot(PK11_FindSlotByName( "pkcs11:token=Test%20PKCS11%20Public%20Certs%20Token")); ASSERT_NE(nullptr, slot.get()); EXPECT_STREQ("Test PKCS11 Public Certs Token", PK11_GetTokenName(slot.get())); } // PK11URI_GetQueryAttribute and PK11URI_GetQueryAttributeItem retrieve named // query attributes from a parsed URI. Inserting two query attributes also // exercises pk11uri_CompareQueryAttributeName. TEST_F(PK11URIResolutionTest, QueryAttributeAccessors) { ScopedPK11URI uri(PK11URI_ParseURI( "pkcs11:token=test?pin-value=1234&pin-source=test-source")); ASSERT_NE(nullptr, uri.get()); const char* pin_value = PK11URI_GetQueryAttribute(uri.get(), "pin-value"); ASSERT_NE(nullptr, pin_value); EXPECT_STREQ("1234", pin_value); const char* pin_source = PK11URI_GetQueryAttribute(uri.get(), "pin-source"); ASSERT_NE(nullptr, pin_source); EXPECT_STREQ("test-source", pin_source); const SECItem* item = PK11URI_GetQueryAttributeItem(uri.get(), "pin-value"); ASSERT_NE(nullptr, item); EXPECT_EQ(4U, item->len); EXPECT_EQ(nullptr, PK11URI_GetQueryAttribute(uri.get(), "module-name")); } // Unrecognized path attribute names become vendor path attributes (stored in // vpattrs). PK11URI_GetPathAttribute finds them by searching vpattrs after // the named pattrs list — exercising the vpattrs loop in pk11uri_GetAttribute. TEST_F(PK11URIResolutionTest, VendorPathAttr_ParseAndLookup) { ScopedPK11URI uri(PK11URI_ParseURI("pkcs11:token=test;x-vendor=hello")); ASSERT_NE(nullptr, uri.get()); EXPECT_STREQ("test", PK11URI_GetPathAttribute(uri.get(), PK11URI_PATTR_TOKEN)); const char* vendor_val = PK11URI_GetPathAttribute(uri.get(), "x-vendor"); ASSERT_NE(nullptr, vendor_val); EXPECT_STREQ("hello", vendor_val); } // PK11URI_CreateURI with vendor path/query attrs and PK11URI_FormatURI cover: // - pk11uri_InsertAttributes vendor branch (attrs not in the named list) // - pk11uri_GetPathAttribute / GetQueryAttribute vpattrs/vqattrs lookup // - PK11URI_FormatURI: named+vendor path separator (";"), query "?" separator, // and named+vendor query separator ("&"). TEST_F(PK11URIResolutionTest, CreateAndFormatURI_VendorAndQueryAttrs) { const PK11URIAttribute pattrs[] = { {PK11URI_PATTR_TOKEN, "mytoken"}, {"x-pvendor", "pval"}, }; const PK11URIAttribute qattrs[] = { {PK11URI_QATTR_PIN_VALUE, "1234"}, {"x-qvendor", "qval"}, }; ScopedPK11URI uri(PK11URI_CreateURI(pattrs, 2, qattrs, 2)); ASSERT_NE(nullptr, uri.get()); EXPECT_STREQ("mytoken", PK11URI_GetPathAttribute(uri.get(), PK11URI_PATTR_TOKEN)); EXPECT_STREQ("pval", PK11URI_GetPathAttribute(uri.get(), "x-pvendor")); EXPECT_STREQ("1234", PK11URI_GetQueryAttribute(uri.get(), PK11URI_QATTR_PIN_VALUE)); EXPECT_STREQ("qval", PK11URI_GetQueryAttribute(uri.get(), "x-qvendor")); char* raw = PK11URI_FormatURI(nullptr, uri.get()); ASSERT_NE(nullptr, raw); EXPECT_EQ(0, strncmp(raw, "pkcs11:", 7)); EXPECT_NE(nullptr, strstr(raw, "token=mytoken")); EXPECT_NE(nullptr, strstr(raw, "x-pvendor=pval")); EXPECT_NE(nullptr, strstr(raw, "pin-value=1234")); EXPECT_NE(nullptr, strstr(raw, "x-qvendor=qval")); PORT_Free(raw); } // =========================================================================== // pk11cert.c — additional cert-lookup functions exercised with the test module // =========================================================================== // PK11_FindCertFromNickname with "TokenName:CertLabel" format finds the cert // via the token-search path (find_certs_from_nickname + // transfer_token_certs_to_collection). TEST_F(PK11URIResolutionTest, FindCertFromNickname_TokenColonName) { ScopedCERTCertificate cert(PK11_FindCertFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, cert.get()); EXPECT_EQ(0, strcmp(cert->nickname, kNickname1)); } // PK11_FindCertFromNickname with a pkcs11: URI dispatches to // find_certs_from_uri internally. TEST_F(PK11URIResolutionTest, FindCertFromNickname_URIDispatch) { ScopedCERTCertificate cert(PK11_FindCertFromNickname( "pkcs11:id=%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f", nullptr)); ASSERT_NE(nullptr, cert.get()); EXPECT_EQ(0, strcmp(cert->nickname, kNickname1)); } // PK11_FindCertsFromNickname with "TokenName:CertLabel" format returns a list. TEST_F(PK11URIResolutionTest, FindCertsFromNickname_TokenColonName) { ScopedCERTCertList certs(PK11_FindCertsFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_GE(CountCerts(certs.get()), 1UL); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // PK11_ListCertsInSlot enumerates all certs on a slot via // PK11_TraverseCertsInSlot / listCertsCallback. TEST_F(PK11URIResolutionTest, ListCertsInSlot_PublicCertsToken) { ScopedPK11SlotInfo slot( PK11_FindSlotByName("Test PKCS11 Public Certs Token")); ASSERT_NE(nullptr, slot.get()); ScopedCERTCertList certs(PK11_ListCertsInSlot(slot.get())); ASSERT_NE(nullptr, certs.get()); EXPECT_GE(CountCerts(certs.get()), 1UL); } // PK11_GetAllSlotsForCert returns a slot list containing the slot on which // the cert lives. TEST_F(PK11URIResolutionTest, GetAllSlotsForCert) { ScopedCERTCertificate cert(PK11_FindCertFromURI( "pkcs11:token=Test%20PKCS11%20Public%20Certs%20Token", nullptr)); ASSERT_NE(nullptr, cert.get()); ScopedPK11SlotList slots(PK11_GetAllSlotsForCert(cert.get(), nullptr)); ASSERT_NE(nullptr, slots.get()); EXPECT_NE(nullptr, slots->head); } // =========================================================================== // PK11URIResolutionCachedTest — warm-cache tests // // These tests exercise the body of transfer_uri_certs_to_collection, which // is only reached when nssTrustDomain_GetCertsFromCache returns a non-empty // list. The SetUp primes the cache; every test below then drives a specific // branch inside the function, including the memcmp(id->data, ...) that was // the site of the bug2030570 heap OOB read. // =========================================================================== // Cache path: id= matches cert1's CKA_ID — exercises memcmp(id->data, ...) // returning 0 and adding the cert to the collection. TEST_F(PK11URIResolutionCachedTest, IdMatch_Cert1) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1)); } // Cache path: id= has correct length but wrong bytes — memcmp returns non-zero, // the cert is skipped via continue, and the token search also misses. TEST_F(PK11URIResolutionCachedTest, IdMismatch_WrongBytes) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:id=%aa%bb%cc%dd%ee%ff%aa%bb%cc%dd%ee%ff%aa%bb%cc%dd", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Cache path: id= length differs from cert's CKA_ID size — the length guard // (id->len != certs[i]->id.size) fires and skips the cert. TEST_F(PK11URIResolutionCachedTest, IdMismatch_WrongLength) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:id=%00%01%02", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Cache path: no id filter but wrong token= — cert passes id check (no id // in URI) but fails the token label check inside the token loop → continue. TEST_F(PK11URIResolutionCachedTest, TokenMismatch) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:token=Wrong%20Token", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Cache path: wrong manufacturer= — cert passes id check but fails the // manufacturerID comparison → continue. TEST_F(PK11URIResolutionCachedTest, ManufacturerMismatch) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:manufacturer=No%20Such%20Manufacturer", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Cache path: wrong model= — cert passes id and manufacturer checks but fails // the model comparison → continue. TEST_F(PK11URIResolutionCachedTest, ModelMismatch) { ScopedCERTCertList certs( PK11_FindCertsFromURI("pkcs11:model=No%20Such%20Model", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Cache path: wrong serial= — cert passes id/manufacturer/model checks but // fails the serialNumber comparison → continue. TEST_F(PK11URIResolutionCachedTest, SerialMismatch) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:serial=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // Cache path: correct token= — the cached cert passes all filters and is // added to the collection (exercises the nssPKIObjectCollection_AddObject / // break path). TEST_F(PK11URIResolutionCachedTest, TokenMatch) { ScopedCERTCertList certs(PK11_FindCertsFromURI( "pkcs11:token=Test%20PKCS11%20Public%20Certs%20Token", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_EQ(1UL, CountCerts(certs.get())); EXPECT_TRUE(HasCertWithNickname(certs.get(), kNickname1) || HasCertWithNickname(certs.get(), kNickname2)); } // PK11_TraverseSlotCerts traverses certs registered in the trust-domain cache, // calling convert_cert → fake_der_cb → user callback for each entry. // prime_ ensures cert1 is in the cache when this runs. TEST_F(PK11URIResolutionCachedTest, TraverseSlotCerts_Count) { int count = 0; SECStatus rv = PK11_TraverseSlotCerts( [](CERTCertificate* cert, SECItem* der, void* arg) -> SECStatus { (*static_cast(arg))++; return SECSuccess; }, &count, nullptr); EXPECT_EQ(SECSuccess, rv); EXPECT_GE(count, 1); } // PK11_FindCertsFromEmailAddress exercises PK11_TraverseSlotCerts + // FindCertsEmailCallback. The test-module certs carry no email address so // FindCertsEmailCallback takes the early-return (cert_email == NULL) branch // and the result is an empty / NULL list. TEST_F(PK11URIResolutionCachedTest, FindCertsFromEmailAddress_NoEmail) { ScopedCERTCertList certs( PK11_FindCertsFromEmailAddress("nobody@example.com", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // PK11_ListCerts enumerates certs registered in the trust-domain issuerAndSN // hash via NSSTrustDomain_TraverseCertificates + pk11ListCertCallback. // prime_ in SetUp ensures cert1 is present in the hash. TEST_F(PK11URIResolutionCachedTest, ListCerts_AllCerts) { ScopedCERTCertList certs(PK11_ListCerts(PK11CertListAll, nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_GE(CountCerts(certs.get()), 1UL); } // PK11_ListCerts with PK11CertListUnique exercises the isUnique=PR_TRUE branch // inside pk11ListCertCallback (CERT_DupCertificate + // STAN_GetCERTCertificateName // + slot-based tail/head insertion). TEST_F(PK11URIResolutionCachedTest, ListCerts_Unique) { ScopedCERTCertList certs(PK11_ListCerts(PK11CertListUnique, nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_GE(CountCerts(certs.get()), 1UL); } // With a warm cache, PK11_FindCertsFromNickname("Token:label") hits the body of // transfer_token_certs_to_collection (count > 0 path) because cert1 is already // in the nickname cache under "cert1" from the prime_ call in SetUp. TEST_F(PK11URIResolutionCachedTest, FindCertsFromNickname_CachedByLabel) { ScopedCERTCertList certs(PK11_FindCertsFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, certs.get()); EXPECT_GE(CountCerts(certs.get()), 1UL); } // =========================================================================== // PK11URIResolutionTest — additional pk11cert.c function coverage // =========================================================================== // PK11_FindCertByIssuerAndSN locates a cert by its DER issuer and serial // number. issuerSN is populated from a cert we already hold. TEST_F(PK11URIResolutionTest, FindCertByIssuerAndSN) { ScopedCERTCertificate found(PK11_FindCertFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, found.get()); CERTIssuerAndSN issuerAndSN = {}; issuerAndSN.derIssuer = found->derIssuer; issuerAndSN.serialNumber = found->serialNumber; PK11SlotInfo* slot = nullptr; ScopedCERTCertificate cert( PK11_FindCertByIssuerAndSN(&slot, &issuerAndSN, nullptr)); if (slot) PK11_FreeSlot(slot); ASSERT_NE(nullptr, cert.get()); } // PK11_FindObjectForCert returns the CK_OBJECT_HANDLE for a cert and fills in // *pSlot with the owning token's slot. TEST_F(PK11URIResolutionTest, FindObjectForCert) { ScopedCERTCertificate cert(PK11_FindCertFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, cert.get()); PK11SlotInfo* pSlot = nullptr; CK_OBJECT_HANDLE h = PK11_FindObjectForCert(cert.get(), nullptr, &pSlot); if (pSlot) PK11_FreeSlot(pSlot); EXPECT_NE(static_cast(CK_INVALID_HANDLE), h); } // PK11_FindEncodedCertInSlot searches a slot for a cert matching the given DER. TEST_F(PK11URIResolutionTest, FindEncodedCertInSlot) { ScopedPK11SlotInfo slot( PK11_FindSlotByName("Test PKCS11 Public Certs Token")); ASSERT_NE(nullptr, slot.get()); ScopedCERTCertificate cert(PK11_FindCertFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, cert.get()); CK_OBJECT_HANDLE h = PK11_FindEncodedCertInSlot(slot.get(), &cert->derCert, nullptr); EXPECT_NE(static_cast(CK_INVALID_HANDLE), h); } // PK11_FindCertInSlot finds the PKCS#11 object handle for a cert in a given // slot, going through the series-check path when cert->slot matches. TEST_F(PK11URIResolutionTest, FindCertInSlot) { ScopedPK11SlotInfo slot( PK11_FindSlotByName("Test PKCS11 Public Certs Token")); ASSERT_NE(nullptr, slot.get()); ScopedCERTCertificate cert(PK11_FindCertFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, cert.get()); CK_OBJECT_HANDLE h = PK11_FindCertInSlot(slot.get(), cert.get(), nullptr); EXPECT_NE(static_cast(CK_INVALID_HANDLE), h); } // PK11_GetLowLevelKeyIDForCert with slot=NULL resolves the slot internally // via PK11_FindObjectForCert. TEST_F(PK11URIResolutionTest, GetLowLevelKeyIDForCert) { ScopedCERTCertificate cert(PK11_FindCertFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, cert.get()); ScopedSECItem item( PK11_GetLowLevelKeyIDForCert(nullptr, cert.get(), nullptr)); ASSERT_NE(nullptr, item.get()); EXPECT_GT(item->len, 0U); } // PK11_TraverseCertsForSubjectInSlot narrows the traversal to one slot. TEST_F(PK11URIResolutionTest, TraverseCertsForSubjectInSlot) { ScopedPK11SlotInfo slot( PK11_FindSlotByName("Test PKCS11 Public Certs Token")); ASSERT_NE(nullptr, slot.get()); ScopedCERTCertificate cert(PK11_FindCertFromNickname( "Test PKCS11 Public Certs Token:cert1", nullptr)); ASSERT_NE(nullptr, cert.get()); int count = 0; SECStatus rv = PK11_TraverseCertsForSubjectInSlot( cert.get(), slot.get(), [](CERTCertificate*, void* arg) -> SECStatus { (*static_cast(arg))++; return SECSuccess; }, &count); EXPECT_EQ(SECSuccess, rv); EXPECT_GT(count, 0); } // PK11_FindCertsFromNickname with no ':' exercises the else path inside // find_certs_from_nickname: slot = PK11_GetInternalKeySlot(). The cert is not // in the internal token, so the result is null — but the path is covered. TEST_F(PK11URIResolutionTest, FindCertsFromNickname_SimpleName) { ScopedCERTCertList certs(PK11_FindCertsFromNickname("cert1", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // PK11_FindCertsFromNickname with a "pkcs11:..." nickname that matches no // cert exercises the URI-then-colon-split fallthrough: find_certs_from_uri // returns NULL, the code falls through, splits on ':', looks for a token // named "pkcs11" (none exists), and returns null. TEST_F(PK11URIResolutionTest, FindCertsFromNickname_URIFallthrough) { ScopedCERTCertList certs( PK11_FindCertsFromNickname("pkcs11:id=%ff%ff%ff", nullptr)); EXPECT_EQ(nullptr, certs.get()); } // PK11_TraverseCertsForNicknameInSlot traverses by CKA_LABEL within a slot. // Passing the label without a null terminator exercises the nssUTF8_Create // copy path (nickname->data[len-1] != '\0'). TEST_F(PK11URIResolutionTest, TraverseCertsForNicknameInSlot) { ScopedPK11SlotInfo slot( PK11_FindSlotByName("Test PKCS11 Public Certs Token")); ASSERT_NE(nullptr, slot.get()); unsigned char label[] = {'c', 'e', 'r', 't', '1'}; SECItem nickname = {siBuffer, label, sizeof(label)}; int count = 0; SECStatus rv = PK11_TraverseCertsForNicknameInSlot( &nickname, slot.get(), [](CERTCertificate*, void* arg) -> SECStatus { (*static_cast(arg))++; return SECSuccess; }, &count); EXPECT_EQ(SECSuccess, rv); EXPECT_GT(count, 0); } } // namespace nss_test