michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CertVerifier.h" michael@0: #include "OCSPCache.h" michael@0: #include "nss.h" michael@0: #include "prerr.h" michael@0: #include "prprf.h" michael@0: #include "secerr.h" michael@0: michael@0: #include "gtest/gtest.h" michael@0: michael@0: const int MaxCacheEntries = 1024; michael@0: michael@0: class OCSPCacheTest : public ::testing::Test michael@0: { michael@0: protected: michael@0: static void SetUpTestCase() michael@0: { michael@0: NSS_NoDB_Init(nullptr); michael@0: mozilla::psm::InitCertVerifierLog(); michael@0: } michael@0: michael@0: mozilla::psm::OCSPCache cache; michael@0: }; michael@0: michael@0: // Makes a fake certificate with just the fields we need for testing here. michael@0: // (And those values are almost entirely bogus.) michael@0: // stackCert should be stack-allocated memory. michael@0: static void michael@0: MakeFakeCert(CERTCertificate* stackCert, const char* subjectValue, michael@0: const char* issuerValue, const char* serialNumberValue, michael@0: const char* publicKeyValue) michael@0: { michael@0: stackCert->derSubject.data = (unsigned char*)subjectValue; michael@0: stackCert->derSubject.len = strlen(subjectValue); michael@0: stackCert->derIssuer.data = (unsigned char*)issuerValue; michael@0: stackCert->derIssuer.len = strlen(issuerValue); michael@0: stackCert->serialNumber.data = (unsigned char*)serialNumberValue; michael@0: stackCert->serialNumber.len = strlen(serialNumberValue); michael@0: stackCert->derPublicKey.data = (unsigned char*)publicKeyValue; michael@0: stackCert->derPublicKey.len = strlen(publicKeyValue); michael@0: CERTName *subject = CERT_AsciiToName(subjectValue); // TODO: this will leak... michael@0: ASSERT_TRUE(subject); michael@0: stackCert->subject.arena = subject->arena; michael@0: stackCert->subject.rdns = subject->rdns; michael@0: } michael@0: michael@0: static void michael@0: PutAndGet(mozilla::psm::OCSPCache& cache, CERTCertificate* subject, michael@0: CERTCertificate* issuer, michael@0: PRErrorCode error, PRTime time) michael@0: { michael@0: // The first time is thisUpdate. The second is validUntil. michael@0: // The caller is expecting the validUntil returned with Get michael@0: // to be equal to the passed-in time. Since these values will michael@0: // be different in practice, make thisUpdate less than validUntil. michael@0: ASSERT_TRUE(time >= 10); michael@0: SECStatus rv = cache.Put(subject, issuer, error, time - 10, time); michael@0: ASSERT_TRUE(rv == SECSuccess); michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_TRUE(cache.Get(subject, issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(error == errorOut && time == timeOut); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, TestPutAndGet) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate subject; michael@0: MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001"); michael@0: CERTCertificate issuer; michael@0: MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: PutAndGet(cache, &subject, &issuer, 0, PR_Now()); michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_FALSE(cache.Get(&issuer, &issuer, errorOut, timeOut)); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, TestVariousGets) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate issuer; michael@0: MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: PRTime timeIn = PR_Now(); michael@0: for (int i = 0; i < MaxCacheEntries; i++) { michael@0: CERTCertificate subject; michael@0: char subjectBuf[64]; michael@0: PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); michael@0: char serialBuf[8]; michael@0: PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); michael@0: MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); michael@0: PutAndGet(cache, &subject, &issuer, 0, timeIn + i); michael@0: } michael@0: CERTCertificate subject; michael@0: // This will be at the end of the list in the cache michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: MakeFakeCert(&subject, "CN=subject0000", "CN=issuer1", "0000", "key000"); michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == 0 && timeOut == timeIn); michael@0: // Once we access it, it goes to the front michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == 0 && timeOut == timeIn); michael@0: MakeFakeCert(&subject, "CN=subject0512", "CN=issuer1", "0512", "key000"); michael@0: // This will be in the middle michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == 0 && timeOut == timeIn + 512); michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == 0 && timeOut == timeIn + 512); michael@0: // We've never seen this certificate michael@0: MakeFakeCert(&subject, "CN=subject1111", "CN=issuer1", "1111", "key000"); michael@0: ASSERT_FALSE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, TestEviction) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate issuer; michael@0: PRTime timeIn = PR_Now(); michael@0: MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: // By putting more distinct entries in the cache than it can hold, michael@0: // we cause the least recently used entry to be evicted. michael@0: for (int i = 0; i < MaxCacheEntries + 1; i++) { michael@0: CERTCertificate subject; michael@0: char subjectBuf[64]; michael@0: PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); michael@0: char serialBuf[8]; michael@0: PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); michael@0: MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); michael@0: PutAndGet(cache, &subject, &issuer, 0, timeIn + i); michael@0: } michael@0: CERTCertificate evictedSubject; michael@0: MakeFakeCert(&evictedSubject, "CN=subject0000", "CN=issuer1", "0000", "key000"); michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_FALSE(cache.Get(&evictedSubject, &issuer, errorOut, timeOut)); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, TestNoEvictionForRevokedResponses) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate issuer; michael@0: MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: CERTCertificate notEvictedSubject; michael@0: MakeFakeCert(¬EvictedSubject, "CN=subject0000", "CN=issuer1", "0000", "key000"); michael@0: PRTime timeIn = PR_Now(); michael@0: PutAndGet(cache, ¬EvictedSubject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, timeIn); michael@0: // By putting more distinct entries in the cache than it can hold, michael@0: // we cause the least recently used entry that isn't revoked to be evicted. michael@0: for (int i = 1; i < MaxCacheEntries + 1; i++) { michael@0: CERTCertificate subject; michael@0: char subjectBuf[64]; michael@0: PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); michael@0: char serialBuf[8]; michael@0: PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); michael@0: MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); michael@0: PutAndGet(cache, &subject, &issuer, 0, timeIn + i); michael@0: } michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_TRUE(cache.Get(¬EvictedSubject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == SEC_ERROR_REVOKED_CERTIFICATE && timeOut == timeIn); michael@0: CERTCertificate evictedSubject; michael@0: MakeFakeCert(&evictedSubject, "CN=subject0001", "CN=issuer1", "0001", "key000"); michael@0: ASSERT_FALSE(cache.Get(&evictedSubject, &issuer, errorOut, timeOut)); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, TestEverythingIsRevoked) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate issuer; michael@0: MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: PRTime timeIn = PR_Now(); michael@0: // Fill up the cache with revoked responses. michael@0: for (int i = 0; i < MaxCacheEntries; i++) { michael@0: CERTCertificate subject; michael@0: char subjectBuf[64]; michael@0: PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); michael@0: char serialBuf[8]; michael@0: PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); michael@0: MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); michael@0: PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, timeIn + i); michael@0: } michael@0: CERTCertificate goodSubject; michael@0: MakeFakeCert(&goodSubject, "CN=subject1025", "CN=issuer1", "1025", "key000"); michael@0: // This will "succeed", allowing verification to continue. However, michael@0: // nothing was actually put in the cache. michael@0: SECStatus result = cache.Put(&goodSubject, &issuer, 0, timeIn + 1025 - 50, michael@0: timeIn + 1025); michael@0: ASSERT_TRUE(result == SECSuccess); michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_FALSE(cache.Get(&goodSubject, &issuer, errorOut, timeOut)); michael@0: michael@0: CERTCertificate revokedSubject; michael@0: MakeFakeCert(&revokedSubject, "CN=subject1026", "CN=issuer1", "1026", "key000"); michael@0: // This will fail, causing verification to fail. michael@0: result = cache.Put(&revokedSubject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, michael@0: timeIn + 1026 - 50, timeIn + 1026); michael@0: PRErrorCode error = PR_GetError(); michael@0: ASSERT_TRUE(result == SECFailure); michael@0: ASSERT_TRUE(error == SEC_ERROR_REVOKED_CERTIFICATE); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, VariousIssuers) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate issuer1; michael@0: MakeFakeCert(&issuer1, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: CERTCertificate issuer2; michael@0: MakeFakeCert(&issuer2, "CN=issuer2", "CN=issuer2", "000", "key001"); michael@0: CERTCertificate issuer3; michael@0: // Note: same CN as issuer1 michael@0: MakeFakeCert(&issuer3, "CN=issuer1", "CN=issuer3", "000", "key003"); michael@0: CERTCertificate subject; michael@0: MakeFakeCert(&subject, "CN=subject", "CN=issuer1", "001", "key002"); michael@0: PRTime timeIn = PR_Now(); michael@0: PutAndGet(cache, &subject, &issuer1, 0, timeIn); michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer1, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == 0 && timeOut == timeIn); michael@0: ASSERT_FALSE(cache.Get(&subject, &issuer2, errorOut, timeOut)); michael@0: ASSERT_FALSE(cache.Get(&subject, &issuer3, errorOut, timeOut)); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, Times) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate subject; michael@0: MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001"); michael@0: CERTCertificate issuer; michael@0: MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: PutAndGet(cache, &subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 100); michael@0: PutAndGet(cache, &subject, &issuer, 0, 200); michael@0: // This should not override the more recent entry. michael@0: SECStatus rv = cache.Put(&subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 100, 100); michael@0: ASSERT_TRUE(rv == SECSuccess); michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: // Here we see the more recent time. michael@0: ASSERT_TRUE(errorOut == 0 && timeOut == 200); michael@0: michael@0: // SEC_ERROR_REVOKED_CERTIFICATE overrides everything michael@0: PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 50); michael@0: } michael@0: michael@0: TEST_F(OCSPCacheTest, NetworkFailure) michael@0: { michael@0: SCOPED_TRACE(""); michael@0: CERTCertificate subject; michael@0: MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001"); michael@0: CERTCertificate issuer; michael@0: MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); michael@0: PutAndGet(cache, &subject, &issuer, PR_CONNECT_REFUSED_ERROR, 100); michael@0: PutAndGet(cache, &subject, &issuer, 0, 200); michael@0: // This should not override the already present entry. michael@0: SECStatus rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 300, 350); michael@0: ASSERT_TRUE(rv == SECSuccess); michael@0: PRErrorCode errorOut; michael@0: PRTime timeOut; michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == 0 && timeOut == 200); michael@0: michael@0: PutAndGet(cache, &subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 400); michael@0: // This should not override the already present entry. michael@0: rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 500, 550); michael@0: ASSERT_TRUE(rv == SECSuccess); michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == SEC_ERROR_OCSP_UNKNOWN_CERT && timeOut == 400); michael@0: michael@0: PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 600); michael@0: // This should not override the already present entry. michael@0: rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 700, 750); michael@0: ASSERT_TRUE(rv == SECSuccess); michael@0: ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); michael@0: ASSERT_TRUE(errorOut == SEC_ERROR_REVOKED_CERTIFICATE && timeOut == 600); michael@0: }