diff --git a/automation/abi-check/expected-report-libnss3.so.txt b/automation/abi-check/expected-report-libnss3.so.txt index 66d2902d14..7e308fc2e9 100644 --- a/automation/abi-check/expected-report-libnss3.so.txt +++ b/automation/abi-check/expected-report-libnss3.so.txt @@ -1,4 +1,9 @@ +1 Added function: + + 'function SECStatus PK11_FindRawCertsWithSubject(PK11SlotInfo*, SECItem*, CERTCertificateList**)' {PK11_FindRawCertsWithSubject@@NSS_3.45} + + 1 Added function: 'function SECStatus CERT_GetCertificateDer(const CERTCertificate*, SECItem*)' {CERT_GetCertificateDer@@NSS_3.44} diff --git a/cpputil/nss_scoped_ptrs.h b/cpputil/nss_scoped_ptrs.h index 450e787af5..9b1fbb47f0 100644 --- a/cpputil/nss_scoped_ptrs.h +++ b/cpputil/nss_scoped_ptrs.h @@ -14,6 +14,7 @@ #include "pk11pqg.h" #include "pk11pub.h" #include "pkcs11uri.h" +#include "secmod.h" struct ScopedDelete { void operator()(CERTCertificate* cert) { CERT_DestroyCertificate(cert); } @@ -47,6 +48,7 @@ struct ScopedDelete { SEC_PKCS12DecoderFinish(dcx); } void operator()(CERTDistNames* names) { CERT_FreeDistNames(names); } + void operator()(SECMODModule* module) { SECMOD_DestroyModule(module); } }; template @@ -82,6 +84,7 @@ SCOPED(PK11Context); SCOPED(PK11GenericObject); SCOPED(SEC_PKCS12DecoderContext); SCOPED(CERTDistNames); +SCOPED(SECMODModule); #undef SCOPED diff --git a/gtests/common/util.h b/gtests/common/util.h index 7ed1fd7991..9a4c8da106 100644 --- a/gtests/common/util.h +++ b/gtests/common/util.h @@ -8,7 +8,21 @@ #define util_h__ #include +#include +#include +#include +#include +#include #include +#if defined(_WIN32) +#include +#include +#include +#else +#include +#endif + +#include "nspr.h" static inline std::vector hex_string_to_bytes(std::string s) { std::vector bytes; @@ -18,4 +32,81 @@ static inline std::vector hex_string_to_bytes(std::string s) { return bytes; } +// Given a prefix, attempts to create a unique directory that the user can do +// work in without impacting other tests. For example, if given the prefix +// "scratch", a directory like "scratch05c17b25" will be created in the current +// working directory (or the location specified by NSS_GTEST_WORKDIR, if +// defined). +// Upon destruction, the implementation will attempt to delete the directory. +// However, no attempt is made to first remove files in the directory - the +// user is responsible for this. If the directory is not empty, deleting it will +// fail. +// Statistically, it is technically possible to fail to create a unique +// directory name, but this is extremely unlikely given the expected workload of +// this implementation. +class ScopedUniqueDirectory { + public: + explicit ScopedUniqueDirectory(const std::string &prefix) { + std::string path; + const char *workingDirectory = PR_GetEnvSecure("NSS_GTEST_WORKDIR"); + if (workingDirectory) { + path.assign(workingDirectory); + } + path.append(prefix); + for (int i = 0; i < RETRY_LIMIT; i++) { + std::string pathCopy(path); + // TryMakingDirectory will modify its input. If it fails, we want to throw + // away the modified result. + if (TryMakingDirectory(pathCopy)) { + mPath.assign(pathCopy); + break; + } + } + assert(mPath.length() > 0); +#if defined(_WIN32) + // sqldb always uses UTF-8 regardless of the current system locale. + DWORD len = + MultiByteToWideChar(CP_ACP, 0, mPath.data(), mPath.size(), nullptr, 0); + std::vector buf(len, L'\0'); + MultiByteToWideChar(CP_ACP, 0, mPath.data(), mPath.size(), buf.data(), + buf.size()); + std::wstring_convert> converter; + mUTF8Path = converter.to_bytes(std::wstring(buf.begin(), buf.end())); +#else + mUTF8Path = mPath; +#endif + } + + // NB: the directory must be empty upon destruction + ~ScopedUniqueDirectory() { assert(rmdir(mPath.c_str()) == 0); } + + const std::string &GetPath() { return mPath; } + const std::string &GetUTF8Path() { return mUTF8Path; } + + private: + static const int RETRY_LIMIT = 5; + + static void GenerateRandomName(/*in/out*/ std::string &prefix) { + std::stringstream ss; + ss << prefix; + // RAND_MAX is at least 32767. + ss << std::setfill('0') << std::setw(4) << std::hex << rand() << rand(); + // This will overwrite the value of prefix. This is a little inefficient, + // but at least it makes the code simple. + ss >> prefix; + } + + static bool TryMakingDirectory(/*in/out*/ std::string &prefix) { + GenerateRandomName(prefix); +#if defined(_WIN32) + return _mkdir(prefix.c_str()) == 0; +#else + return mkdir(prefix.c_str(), 0777) == 0; +#endif + } + + std::string mPath; + std::string mUTF8Path; +}; + #endif // util_h__ diff --git a/gtests/pk11_gtest/manifest.mn b/gtests/pk11_gtest/manifest.mn index 475a35b64b..b49985ab7c 100644 --- a/gtests/pk11_gtest/manifest.mn +++ b/gtests/pk11_gtest/manifest.mn @@ -14,6 +14,7 @@ CPPSRCS = \ pk11_ecdsa_unittest.cc \ pk11_encrypt_derive_unittest.cc \ pk11_export_unittest.cc \ + pk11_find_certs_unittest.cc \ pk11_import_unittest.cc \ pk11_pbkdf2_unittest.cc \ pk11_prf_unittest.cc \ diff --git a/gtests/pk11_gtest/pk11_find_certs_unittest.cc b/gtests/pk11_gtest/pk11_find_certs_unittest.cc new file mode 100644 index 0000000000..bf7000b016 --- /dev/null +++ b/gtests/pk11_gtest/pk11_find_certs_unittest.cc @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 et sw=4 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/. */ + +#include + +#include "nss.h" +#include "pk11pub.h" +#include "prenv.h" +#include "prerror.h" +#include "secmod.h" + +#include "gtest/gtest.h" +#include "nss_scoped_ptrs.h" +#include "util.h" + +namespace nss_test { + +// These test certificates were generated using pycert/pykey from +// mozilla-central (https://hg.mozilla.org/mozilla-central/file/ ... +// 9968319230a74eb8c1953444a0e6973c7500a9f8/security/manager/ssl/ ... +// tests/unit/pycert.py). + +// issuer:test cert +// subject:test cert +// issuerKey:secp256r1 +// subjectKey:secp256r1 +// serialNumber:1 +std::vector kTestCert1DER = { + 0x30, 0x82, 0x01, 0x1D, 0x30, 0x81, 0xC2, 0xA0, 0x03, 0x02, 0x01, 0x02, + 0x02, 0x01, 0x01, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x09, 0x74, 0x65, 0x73, 0x74, 0x20, + 0x63, 0x65, 0x72, 0x74, 0x30, 0x22, 0x18, 0x0F, 0x32, 0x30, 0x31, 0x37, + 0x31, 0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, + 0x0F, 0x32, 0x30, 0x32, 0x30, 0x30, 0x32, 0x30, 0x35, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x5A, 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0C, 0x09, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x65, + 0x72, 0x74, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, + 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, + 0x07, 0x03, 0x42, 0x00, 0x04, 0x4F, 0xBF, 0xBB, 0xBB, 0x61, 0xE0, 0xF8, + 0xF9, 0xB1, 0xA6, 0x0A, 0x59, 0xAC, 0x87, 0x04, 0xE2, 0xEC, 0x05, 0x0B, + 0x42, 0x3E, 0x3C, 0xF7, 0x2E, 0x92, 0x3F, 0x2C, 0x4F, 0x79, 0x4B, 0x45, + 0x5C, 0x2A, 0x69, 0xD2, 0x33, 0x45, 0x6C, 0x36, 0xC4, 0x11, 0x9D, 0x07, + 0x06, 0xE0, 0x0E, 0xED, 0xC8, 0xD1, 0x93, 0x90, 0xD7, 0x99, 0x1B, 0x7B, + 0x2D, 0x07, 0xA3, 0x04, 0xEA, 0xA0, 0x4A, 0xA6, 0xC0, 0x30, 0x0D, 0x06, + 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, + 0x03, 0x47, 0x00, 0x30, 0x44, 0x02, 0x20, 0x5C, 0x75, 0x51, 0x9F, 0x13, + 0x11, 0x50, 0xCD, 0x5D, 0x8A, 0xDE, 0x20, 0xA3, 0xBC, 0x06, 0x30, 0x91, + 0xFF, 0xB2, 0x73, 0x75, 0x5F, 0x31, 0x64, 0xEC, 0xFD, 0xCB, 0x42, 0x80, + 0x0A, 0x70, 0xE6, 0x02, 0x20, 0x11, 0xFA, 0xA2, 0xCA, 0x06, 0xF3, 0xBC, + 0x5F, 0x8A, 0xCA, 0x17, 0x63, 0x36, 0x87, 0xCF, 0x8D, 0x5C, 0xA0, 0x56, + 0x84, 0x44, 0x61, 0xB2, 0x33, 0x42, 0x07, 0x58, 0x9F, 0x0C, 0x9E, 0x49, + 0x83, +}; + +// issuer:test cert +// subject:test cert +// issuerKey:secp256r1 +// subjectKey:secp256r1 +// serialNumber:2 +std::vector kTestCert2DER = { + 0x30, 0x82, 0x01, 0x1E, 0x30, 0x81, 0xC2, 0xA0, 0x03, 0x02, 0x01, 0x02, + 0x02, 0x01, 0x02, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x09, 0x74, 0x65, 0x73, 0x74, 0x20, + 0x63, 0x65, 0x72, 0x74, 0x30, 0x22, 0x18, 0x0F, 0x32, 0x30, 0x31, 0x37, + 0x31, 0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, + 0x0F, 0x32, 0x30, 0x32, 0x30, 0x30, 0x32, 0x30, 0x35, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x5A, 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0C, 0x09, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x65, + 0x72, 0x74, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, + 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, + 0x07, 0x03, 0x42, 0x00, 0x04, 0x4F, 0xBF, 0xBB, 0xBB, 0x61, 0xE0, 0xF8, + 0xF9, 0xB1, 0xA6, 0x0A, 0x59, 0xAC, 0x87, 0x04, 0xE2, 0xEC, 0x05, 0x0B, + 0x42, 0x3E, 0x3C, 0xF7, 0x2E, 0x92, 0x3F, 0x2C, 0x4F, 0x79, 0x4B, 0x45, + 0x5C, 0x2A, 0x69, 0xD2, 0x33, 0x45, 0x6C, 0x36, 0xC4, 0x11, 0x9D, 0x07, + 0x06, 0xE0, 0x0E, 0xED, 0xC8, 0xD1, 0x93, 0x90, 0xD7, 0x99, 0x1B, 0x7B, + 0x2D, 0x07, 0xA3, 0x04, 0xEA, 0xA0, 0x4A, 0xA6, 0xC0, 0x30, 0x0D, 0x06, + 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, + 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x20, 0x5C, 0x75, 0x51, 0x9F, 0x13, + 0x11, 0x50, 0xCD, 0x5D, 0x8A, 0xDE, 0x20, 0xA3, 0xBC, 0x06, 0x30, 0x91, + 0xFF, 0xB2, 0x73, 0x75, 0x5F, 0x31, 0x64, 0xEC, 0xFD, 0xCB, 0x42, 0x80, + 0x0A, 0x70, 0xE6, 0x02, 0x21, 0x00, 0xF6, 0x5E, 0x42, 0xC7, 0x54, 0x40, + 0x81, 0xE9, 0x4C, 0x16, 0x48, 0xB1, 0x39, 0x0A, 0xA0, 0xE2, 0x8C, 0x23, + 0xAA, 0xC5, 0xBB, 0xAC, 0xEB, 0x9B, 0x15, 0x0B, 0x2F, 0xB7, 0xF5, 0x85, + 0xB2, 0x54, +}; + +std::vector kTestCertSubjectDER = { + 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x0C, 0x09, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, +}; + +// issuer:test cert +// subject:unrelated subject DN +// issuerKey:secp256r1 +// subjectKey:secp256r1 +// serialNumber:3 +std::vector kUnrelatedTestCertDER = { + 0x30, 0x82, 0x01, 0x28, 0x30, 0x81, 0xCD, 0xA0, 0x03, 0x02, 0x01, 0x02, + 0x02, 0x01, 0x03, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x09, 0x74, 0x65, 0x73, 0x74, 0x20, + 0x63, 0x65, 0x72, 0x74, 0x30, 0x22, 0x18, 0x0F, 0x32, 0x30, 0x31, 0x37, + 0x31, 0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, + 0x0F, 0x32, 0x30, 0x32, 0x30, 0x30, 0x32, 0x30, 0x35, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x5A, 0x30, 0x1F, 0x31, 0x1D, 0x30, 0x1B, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0C, 0x14, 0x75, 0x6E, 0x72, 0x65, 0x6C, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x73, 0x75, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x20, 0x44, + 0x4E, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, + 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, + 0x03, 0x42, 0x00, 0x04, 0x4F, 0xBF, 0xBB, 0xBB, 0x61, 0xE0, 0xF8, 0xF9, + 0xB1, 0xA6, 0x0A, 0x59, 0xAC, 0x87, 0x04, 0xE2, 0xEC, 0x05, 0x0B, 0x42, + 0x3E, 0x3C, 0xF7, 0x2E, 0x92, 0x3F, 0x2C, 0x4F, 0x79, 0x4B, 0x45, 0x5C, + 0x2A, 0x69, 0xD2, 0x33, 0x45, 0x6C, 0x36, 0xC4, 0x11, 0x9D, 0x07, 0x06, + 0xE0, 0x0E, 0xED, 0xC8, 0xD1, 0x93, 0x90, 0xD7, 0x99, 0x1B, 0x7B, 0x2D, + 0x07, 0xA3, 0x04, 0xEA, 0xA0, 0x4A, 0xA6, 0xC0, 0x30, 0x0D, 0x06, 0x09, + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x03, + 0x47, 0x00, 0x30, 0x44, 0x02, 0x20, 0x5C, 0x75, 0x51, 0x9F, 0x13, 0x11, + 0x50, 0xCD, 0x5D, 0x8A, 0xDE, 0x20, 0xA3, 0xBC, 0x06, 0x30, 0x91, 0xFF, + 0xB2, 0x73, 0x75, 0x5F, 0x31, 0x64, 0xEC, 0xFD, 0xCB, 0x42, 0x80, 0x0A, + 0x70, 0xE6, 0x02, 0x20, 0x0F, 0x1A, 0x04, 0xC2, 0xF8, 0xBA, 0xC2, 0x94, + 0x26, 0x6E, 0xBC, 0x91, 0x7D, 0xDB, 0x75, 0x7B, 0xE8, 0xA3, 0x4F, 0x69, + 0x1B, 0xF3, 0x1F, 0x2C, 0xCE, 0x82, 0x67, 0xC9, 0x5B, 0xBB, 0xBA, 0x0A, +}; + +class PK11FindRawCertsBySubjectTest : public ::testing::Test { + protected: + PK11FindRawCertsBySubjectTest() + : mSlot(nullptr), mTestCertDBDir("PK11FindRawCertsBySubjectTest-") {} + + virtual void SetUp() { + std::string testCertDBPath(mTestCertDBDir.GetPath()); + const char* testName = + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::string modspec = "configDir='sql:"; + modspec.append(testCertDBPath); + modspec.append("' tokenDescription='"); + modspec.append(testName); + modspec.append("'"); + mSlot = SECMOD_OpenUserDB(modspec.c_str()); + ASSERT_NE(mSlot, nullptr); + } + + virtual void TearDown() { + ASSERT_EQ(SECMOD_CloseUserDB(mSlot), SECSuccess); + PK11_FreeSlot(mSlot); + std::string testCertDBPath(mTestCertDBDir.GetPath()); + ASSERT_EQ(0, unlink((testCertDBPath + "/cert9.db").c_str())); + ASSERT_EQ(0, unlink((testCertDBPath + "/key4.db").c_str())); + } + + PK11SlotInfo* mSlot; + ScopedUniqueDirectory mTestCertDBDir; +}; + +// If we don't have any certificates, we shouldn't get any when we search for +// them. +TEST_F(PK11FindRawCertsBySubjectTest, TestNoCertsImportedNoCertsFound) { + SECItem subjectItem = {siBuffer, + const_cast(kTestCertSubjectDER.data()), + (unsigned int)kTestCertSubjectDER.size()}; + CERTCertificateList* certificates = nullptr; + SECStatus rv = + PK11_FindRawCertsWithSubject(mSlot, &subjectItem, &certificates); + EXPECT_EQ(rv, SECSuccess); + EXPECT_EQ(certificates, nullptr); +} + +// If we have one certificate but it has an unrelated subject DN, we shouldn't +// get it when we search. +TEST_F(PK11FindRawCertsBySubjectTest, TestOneCertImportedNoCertsFound) { + char certNickname[] = "Unrelated Cert"; + SECItem certItem = {siBuffer, + const_cast(kUnrelatedTestCertDER.data()), + (unsigned int)kUnrelatedTestCertDER.size()}; + ASSERT_EQ(PK11_ImportDERCert(mSlot, &certItem, CK_INVALID_HANDLE, + certNickname, false), + SECSuccess); + + SECItem subjectItem = {siBuffer, + const_cast(kTestCertSubjectDER.data()), + (unsigned int)kTestCertSubjectDER.size()}; + CERTCertificateList* certificates = nullptr; + SECStatus rv = + PK11_FindRawCertsWithSubject(mSlot, &subjectItem, &certificates); + EXPECT_EQ(rv, SECSuccess); + EXPECT_EQ(certificates, nullptr); +} + +TEST_F(PK11FindRawCertsBySubjectTest, TestMultipleMatchingCertsFound) { + char cert1Nickname[] = "Test Cert 1"; + SECItem cert1Item = {siBuffer, + const_cast(kTestCert1DER.data()), + (unsigned int)kTestCert1DER.size()}; + ASSERT_EQ(PK11_ImportDERCert(mSlot, &cert1Item, CK_INVALID_HANDLE, + cert1Nickname, false), + SECSuccess); + char cert2Nickname[] = "Test Cert 2"; + SECItem cert2Item = {siBuffer, + const_cast(kTestCert2DER.data()), + (unsigned int)kTestCert2DER.size()}; + ASSERT_EQ(PK11_ImportDERCert(mSlot, &cert2Item, CK_INVALID_HANDLE, + cert2Nickname, false), + SECSuccess); + char unrelatedCertNickname[] = "Unrelated Test Cert"; + SECItem unrelatedCertItem = { + siBuffer, const_cast(kUnrelatedTestCertDER.data()), + (unsigned int)kUnrelatedTestCertDER.size()}; + ASSERT_EQ(PK11_ImportDERCert(mSlot, &unrelatedCertItem, CK_INVALID_HANDLE, + unrelatedCertNickname, false), + SECSuccess); + + CERTCertificateList* certificates = nullptr; + SECItem subjectItem = {siBuffer, + const_cast(kTestCertSubjectDER.data()), + (unsigned int)kTestCertSubjectDER.size()}; + SECStatus rv = + PK11_FindRawCertsWithSubject(mSlot, &subjectItem, &certificates); + EXPECT_EQ(rv, SECSuccess); + ASSERT_NE(certificates, nullptr); + ScopedCERTCertificateList scopedCertificates(certificates); + ASSERT_EQ(scopedCertificates->len, 2); + + std::vector foundCert1( + scopedCertificates->certs[0].data, + scopedCertificates->certs[0].data + scopedCertificates->certs[0].len); + std::vector foundCert2( + scopedCertificates->certs[1].data, + scopedCertificates->certs[1].data + scopedCertificates->certs[1].len); + EXPECT_TRUE(foundCert1 == kTestCert1DER || foundCert1 == kTestCert2DER); + EXPECT_TRUE(foundCert2 == kTestCert1DER || foundCert2 == kTestCert2DER); + EXPECT_TRUE(foundCert1 != foundCert2); +} + +// If we try to search the internal slots, we won't find the certificate we just +// imported (because it's on a different slot). +TEST_F(PK11FindRawCertsBySubjectTest, TestNoCertsOnInternalSlots) { + char cert1Nickname[] = "Test Cert 1"; + SECItem cert1Item = {siBuffer, + const_cast(kTestCert1DER.data()), + (unsigned int)kTestCert1DER.size()}; + ASSERT_EQ(PK11_ImportDERCert(mSlot, &cert1Item, CK_INVALID_HANDLE, + cert1Nickname, false), + SECSuccess); + + SECItem subjectItem = {siBuffer, + const_cast(kTestCertSubjectDER.data()), + (unsigned int)kTestCertSubjectDER.size()}; + CERTCertificateList* internalKeySlotCertificates = nullptr; + ScopedPK11SlotInfo internalKeySlot(PK11_GetInternalKeySlot()); + SECStatus rv = PK11_FindRawCertsWithSubject( + internalKeySlot.get(), &subjectItem, &internalKeySlotCertificates); + EXPECT_EQ(rv, SECSuccess); + EXPECT_EQ(internalKeySlotCertificates, nullptr); + + CERTCertificateList* internalSlotCertificates = nullptr; + ScopedPK11SlotInfo internalSlot(PK11_GetInternalSlot()); + rv = PK11_FindRawCertsWithSubject(internalSlot.get(), &subjectItem, + &internalSlotCertificates); + EXPECT_EQ(rv, SECSuccess); + EXPECT_EQ(internalSlotCertificates, nullptr); +} + +// issuer:test cert +// subject:(empty - this had to be done by hand as pycert doesn't support this) +// issuerKey:secp256r1 +// subjectKey:secp256r1 +// serialNumber:4 +std::vector kEmptySubjectCertDER = { + 0x30, 0x82, 0x01, 0x09, 0x30, 0x81, 0xAE, 0xA0, 0x03, 0x02, 0x01, 0x02, + 0x02, 0x01, 0x04, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x09, 0x74, 0x65, 0x73, 0x74, 0x20, + 0x63, 0x65, 0x72, 0x74, 0x30, 0x22, 0x18, 0x0F, 0x32, 0x30, 0x31, 0x37, + 0x31, 0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, + 0x0F, 0x32, 0x30, 0x32, 0x30, 0x30, 0x32, 0x30, 0x35, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x5A, 0x30, 0x00, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, + 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, + 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x4F, 0xBF, 0xBB, + 0xBB, 0x61, 0xE0, 0xF8, 0xF9, 0xB1, 0xA6, 0x0A, 0x59, 0xAC, 0x87, 0x04, + 0xE2, 0xEC, 0x05, 0x0B, 0x42, 0x3E, 0x3C, 0xF7, 0x2E, 0x92, 0x3F, 0x2C, + 0x4F, 0x79, 0x4B, 0x45, 0x5C, 0x2A, 0x69, 0xD2, 0x33, 0x45, 0x6C, 0x36, + 0xC4, 0x11, 0x9D, 0x07, 0x06, 0xE0, 0x0E, 0xED, 0xC8, 0xD1, 0x93, 0x90, + 0xD7, 0x99, 0x1B, 0x7B, 0x2D, 0x07, 0xA3, 0x04, 0xEA, 0xA0, 0x4A, 0xA6, + 0xC0, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, + 0x01, 0x0B, 0x05, 0x00, 0x03, 0x47, 0x00, 0x30, 0x44, 0x02, 0x20, 0x5C, + 0x75, 0x51, 0x9F, 0x13, 0x11, 0x50, 0xCD, 0x5D, 0x8A, 0xDE, 0x20, 0xA3, + 0xBC, 0x06, 0x30, 0x91, 0xFF, 0xB2, 0x73, 0x75, 0x5F, 0x31, 0x64, 0xEC, + 0xFD, 0xCB, 0x42, 0x80, 0x0A, 0x70, 0xE6, 0x02, 0x20, 0x31, 0x1B, 0x92, + 0xAA, 0xA8, 0xB7, 0x51, 0x52, 0x7B, 0x64, 0xD6, 0xF7, 0x2F, 0x0C, 0xFB, + 0xBB, 0xD5, 0xDF, 0x86, 0xA3, 0x97, 0x96, 0x60, 0x42, 0xDA, 0xD4, 0xA8, + 0x5F, 0x2F, 0xA4, 0xDE, 0x7C}; + +std::vector kEmptySubjectDER = {0x30, 0x00}; + +// This certificate has the smallest possible subject. Finding it should work. +TEST_F(PK11FindRawCertsBySubjectTest, TestFindEmptySubject) { + char emptySubjectCertNickname[] = "Empty Subject Cert"; + SECItem emptySubjectCertItem = { + siBuffer, const_cast(kEmptySubjectCertDER.data()), + (unsigned int)kEmptySubjectCertDER.size()}; + ASSERT_EQ(PK11_ImportDERCert(mSlot, &emptySubjectCertItem, CK_INVALID_HANDLE, + emptySubjectCertNickname, false), + SECSuccess); + + SECItem subjectItem = {siBuffer, + const_cast(kEmptySubjectDER.data()), + (unsigned int)kEmptySubjectDER.size()}; + CERTCertificateList* certificates = nullptr; + SECStatus rv = + PK11_FindRawCertsWithSubject(mSlot, &subjectItem, &certificates); + EXPECT_EQ(rv, SECSuccess); + ASSERT_NE(certificates, nullptr); + ScopedCERTCertificateList scopedCertificates(certificates); + ASSERT_EQ(scopedCertificates->len, 1); + + std::vector foundCert( + scopedCertificates->certs[0].data, + scopedCertificates->certs[0].data + scopedCertificates->certs[0].len); + EXPECT_EQ(foundCert, kEmptySubjectCertDER); +} + +// Searching for a zero-length subject doesn't make sense (the minimum subject +// is the SEQUENCE tag followed by a length byte of 0), but it shouldn't cause +// problems. +TEST_F(PK11FindRawCertsBySubjectTest, TestSearchForNullSubject) { + char cert1Nickname[] = "Test Cert 1"; + SECItem cert1Item = {siBuffer, + const_cast(kTestCert1DER.data()), + (unsigned int)kTestCert1DER.size()}; + ASSERT_EQ(PK11_ImportDERCert(mSlot, &cert1Item, CK_INVALID_HANDLE, + cert1Nickname, false), + SECSuccess); + + SECItem subjectItem = {siBuffer, nullptr, 0}; + CERTCertificateList* certificates = nullptr; + SECStatus rv = + PK11_FindRawCertsWithSubject(mSlot, &subjectItem, &certificates); + EXPECT_EQ(rv, SECSuccess); + EXPECT_EQ(certificates, nullptr); +} + +} // namespace nss_test diff --git a/gtests/pk11_gtest/pk11_gtest.gyp b/gtests/pk11_gtest/pk11_gtest.gyp index f1f682d86f..cbb3614292 100644 --- a/gtests/pk11_gtest/pk11_gtest.gyp +++ b/gtests/pk11_gtest/pk11_gtest.gyp @@ -18,6 +18,7 @@ 'pk11_curve25519_unittest.cc', 'pk11_ecdsa_unittest.cc', 'pk11_encrypt_derive_unittest.cc', + 'pk11_find_certs_unittest.cc', 'pk11_import_unittest.cc', 'pk11_pbkdf2_unittest.cc', 'pk11_prf_unittest.cc', diff --git a/gtests/softoken_gtest/manifest.mn b/gtests/softoken_gtest/manifest.mn index 4b34c099f5..0e998adf4c 100644 --- a/gtests/softoken_gtest/manifest.mn +++ b/gtests/softoken_gtest/manifest.mn @@ -12,6 +12,7 @@ CPPSRCS = \ INCLUDES += \ -I$(CORE_DEPTH)/gtests/google_test/gtest/include \ + -I$(CORE_DEPTH)/gtests/common \ -I$(CORE_DEPTH)/cpputil \ $(NULL) diff --git a/gtests/softoken_gtest/softoken_gtest.cc b/gtests/softoken_gtest/softoken_gtest.cc index 5e2a497b8b..c6cd32afdb 100644 --- a/gtests/softoken_gtest/softoken_gtest.cc +++ b/gtests/softoken_gtest/softoken_gtest.cc @@ -1,9 +1,3 @@ -#include -#if defined(_WIN32) -#include -#include -#endif - #include "cert.h" #include "certdb.h" #include "nspr.h" @@ -12,93 +6,13 @@ #include "secerr.h" #include "nss_scoped_ptrs.h" +#include "util.h" #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" namespace nss_test { -// Given a prefix, attempts to create a unique directory that the user can do -// work in without impacting other tests. For example, if given the prefix -// "scratch", a directory like "scratch05c17b25" will be created in the current -// working directory (or the location specified by NSS_GTEST_WORKDIR, if -// defined). -// Upon destruction, the implementation will attempt to delete the directory. -// However, no attempt is made to first remove files in the directory - the -// user is responsible for this. If the directory is not empty, deleting it will -// fail. -// Statistically, it is technically possible to fail to create a unique -// directory name, but this is extremely unlikely given the expected workload of -// this implementation. -class ScopedUniqueDirectory { - public: - explicit ScopedUniqueDirectory(const std::string &prefix); - - // NB: the directory must be empty upon destruction - ~ScopedUniqueDirectory() { assert(rmdir(mPath.c_str()) == 0); } - - const std::string &GetPath() { return mPath; } - const std::string &GetUTF8Path() { return mUTF8Path; } - - private: - static const int RETRY_LIMIT = 5; - static void GenerateRandomName(/*in/out*/ std::string &prefix); - static bool TryMakingDirectory(/*in/out*/ std::string &prefix); - - std::string mPath; - std::string mUTF8Path; -}; - -ScopedUniqueDirectory::ScopedUniqueDirectory(const std::string &prefix) { - std::string path; - const char *workingDirectory = PR_GetEnvSecure("NSS_GTEST_WORKDIR"); - if (workingDirectory) { - path.assign(workingDirectory); - } - path.append(prefix); - for (int i = 0; i < RETRY_LIMIT; i++) { - std::string pathCopy(path); - // TryMakingDirectory will modify its input. If it fails, we want to throw - // away the modified result. - if (TryMakingDirectory(pathCopy)) { - mPath.assign(pathCopy); - break; - } - } - assert(mPath.length() > 0); -#if defined(_WIN32) - // sqldb always uses UTF-8 regardless of the current system locale. - DWORD len = - MultiByteToWideChar(CP_ACP, 0, mPath.data(), mPath.size(), nullptr, 0); - std::vector buf(len, L'\0'); - MultiByteToWideChar(CP_ACP, 0, mPath.data(), mPath.size(), buf.data(), - buf.size()); - std::wstring_convert> converter; - mUTF8Path = converter.to_bytes(std::wstring(buf.begin(), buf.end())); -#else - mUTF8Path = mPath; -#endif -} - -void ScopedUniqueDirectory::GenerateRandomName(std::string &prefix) { - std::stringstream ss; - ss << prefix; - // RAND_MAX is at least 32767. - ss << std::setfill('0') << std::setw(4) << std::hex << rand() << rand(); - // This will overwrite the value of prefix. This is a little inefficient, but - // at least it makes the code simple. - ss >> prefix; -} - -bool ScopedUniqueDirectory::TryMakingDirectory(std::string &prefix) { - GenerateRandomName(prefix); -#if defined(_WIN32) - return _mkdir(prefix.c_str()) == 0; -#else - return mkdir(prefix.c_str(), 0777) == 0; -#endif -} - class SoftokenTest : public ::testing::Test { protected: SoftokenTest() : mNSSDBDir("SoftokenTest.d-") {} diff --git a/lib/nss/nss.def b/lib/nss/nss.def index 53d463a66f..17c69a7797 100644 --- a/lib/nss/nss.def +++ b/lib/nss/nss.def @@ -1151,3 +1151,9 @@ CERT_GetCertificateDer; ;+ local: ;+ *; ;+}; +;+NSS_3.45 { # NSS 3.45 release +;+ global: +PK11_FindRawCertsWithSubject; +;+ local: +;+ *; +;+}; diff --git a/lib/pk11wrap/pk11obj.c b/lib/pk11wrap/pk11obj.c index 937ac654a5..b65cccbdc2 100644 --- a/lib/pk11wrap/pk11obj.c +++ b/lib/pk11wrap/pk11obj.c @@ -4,6 +4,8 @@ /* * This file manages object type indepentent functions. */ +#include + #include "seccomon.h" #include "secmod.h" #include "secmodi.h" @@ -1883,6 +1885,96 @@ pk11_FindObjectsByTemplate(PK11SlotInfo *slot, CK_ATTRIBUTE *findTemplate, *object_count = -1; return objID; } + +SECStatus +PK11_FindRawCertsWithSubject(PK11SlotInfo *slot, SECItem *derSubject, + CERTCertificateList **results) +{ + if (!slot || !derSubject || !results) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *results = NULL; + + // derSubject->data may be null. If so, derSubject->len must be 0. + if (!derSubject->data && derSubject->len != 0) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + CK_CERTIFICATE_TYPE ckc_x_509 = CKC_X_509; + CK_OBJECT_CLASS cko_certificate = CKO_CERTIFICATE; + CK_ATTRIBUTE subjectTemplate[] = { + { CKA_CERTIFICATE_TYPE, &ckc_x_509, sizeof(ckc_x_509) }, + { CKA_CLASS, &cko_certificate, sizeof(cko_certificate) }, + { CKA_SUBJECT, derSubject->data, derSubject->len }, + }; + int templateCount = sizeof(subjectTemplate) / sizeof(subjectTemplate[0]); + int handleCount = 0; + CK_OBJECT_HANDLE *handles = + pk11_FindObjectsByTemplate(slot, subjectTemplate, templateCount, + &handleCount); + if (!handles) { + // pk11_FindObjectsByTemplate indicates there was an error by setting + // handleCount to -1 (and it has set an error with PORT_SetError). + if (handleCount == -1) { + return SECFailure; + } + return SECSuccess; + } + PORT_Assert(handleCount > 0); + if (handleCount <= 0) { + PORT_Free(handles); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (handleCount > INT_MAX / sizeof(SECItem)) { + PORT_Free(handles); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + PORT_Free(handles); + return SECFailure; + } + CERTCertificateList *rawCertificates = + PORT_ArenaNew(arena, CERTCertificateList); + if (!rawCertificates) { + PORT_Free(handles); + PORT_FreeArena(arena, PR_FALSE); + return SECFailure; + } + rawCertificates->arena = arena; + rawCertificates->certs = PORT_ArenaNewArray(arena, SECItem, handleCount); + if (!rawCertificates->certs) { + PORT_Free(handles); + PORT_FreeArena(arena, PR_FALSE); + return SECFailure; + } + rawCertificates->len = handleCount; + int handleIndex; + for (handleIndex = 0; handleIndex < handleCount; handleIndex++) { + SECStatus rv = + PK11_ReadAttribute(slot, handles[handleIndex], CKA_VALUE, arena, + &rawCertificates->certs[handleIndex]); + if (rv != SECSuccess) { + PORT_Free(handles); + PORT_FreeArena(arena, PR_FALSE); + return SECFailure; + } + if (!rawCertificates->certs[handleIndex].data) { + PORT_Free(handles); + PORT_FreeArena(arena, PR_FALSE); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + } + PORT_Free(handles); + *results = rawCertificates; + return SECSuccess; +} + /* * given a PKCS #11 object, match it's peer based on the KeyID. searchID * is typically a privateKey or a certificate while the peer is the opposite diff --git a/lib/pk11wrap/pk11pub.h b/lib/pk11wrap/pk11pub.h index 8db969e4cf..9ca4018d94 100644 --- a/lib/pk11wrap/pk11pub.h +++ b/lib/pk11wrap/pk11pub.h @@ -875,6 +875,17 @@ SECStatus PK11_WriteRawAttribute(PK11ObjectType type, void *object, PK11SlotList * PK11_GetAllSlotsForCert(CERTCertificate *cert, void *arg); +/* + * Finds all certificates on the given slot with the given subject distinguished + * name and returns them as DER bytes. If no such certificates can be found, + * returns SECSuccess and sets *results to NULL. If a failure is encountered + * while fetching any of the matching certificates, SECFailure is returned and + * *results will be NULL. + */ +SECStatus +PK11_FindRawCertsWithSubject(PK11SlotInfo *slot, SECItem *derSubject, + CERTCertificateList **results); + /********************************************************************** * New functions which are already deprecated.... **********************************************************************/