text: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  /* vim: set ts=8 sts=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/. */
  
  #include "CTPolicyEnforcer.h"
  
  #include <algorithm>
  #include <stdint.h>
  #include <stdio.h>
  
  #include "CTLogVerifier.h"
  #include "CTVerifyResult.h"
  #include "SignedCertificateTimestamp.h"
  #include "gtest/gtest.h"
  #include "hasht.h"
  #include "prtime.h"
  
  // Implemented in CertVerifier.cpp.
  extern mozilla::pkix::Result GetCertLifetimeInFullMonths(PRTime certNotBefore,
                                                           PRTime certNotAfter,
                                                           size_t& months);
  
  namespace mozilla {
  namespace ct {
  
  using namespace mozilla::pkix;
  
  class CTPolicyEnforcerTest : public ::testing::Test {
   public:
    void SetUp() override {
      OPERATORS_1_AND_2.push_back(OPERATOR_1);
      OPERATORS_1_AND_2.push_back(OPERATOR_2);
    }
  
    void GetLogId(Buffer& logId, size_t logNo) {
      logId.resize(SHA256_LENGTH);
      std::fill(logId.begin(), logId.end(), 0);
      // Just raw-copy |logId| into the output buffer.
      assert(sizeof(logNo) <= logId.size());
      memcpy(logId.data(), &logNo, sizeof(logNo));
    }
  
    void AddSct(VerifiedSCTList& verifiedScts, size_t logNo,
                CTLogOperatorId operatorId, VerifiedSCT::Origin origin,
                uint64_t timestamp,
                VerifiedSCT::Status status = VerifiedSCT::Status::Valid) {
      VerifiedSCT verifiedSct;
      verifiedSct.status = status;
      verifiedSct.origin = origin;
      verifiedSct.logOperatorId = operatorId;
      verifiedSct.logDisqualificationTime =
          status == VerifiedSCT::Status::ValidFromDisqualifiedLog
              ? DISQUALIFIED_AT
              : UINT64_MAX;
      verifiedSct.sct.version = SignedCertificateTimestamp::Version::V1;
      verifiedSct.sct.timestamp = timestamp;
      Buffer logId;
      GetLogId(logId, logNo);
      verifiedSct.sct.logId = std::move(logId);
      verifiedScts.push_back(std::move(verifiedSct));
    }
  
    void AddMultipleScts(
        VerifiedSCTList& verifiedScts, size_t logsCount, uint8_t operatorsCount,
        VerifiedSCT::Origin origin, uint64_t timestamp,
        VerifiedSCT::Status status = VerifiedSCT::Status::Valid) {
      for (size_t logNo = 0; logNo < logsCount; logNo++) {
        CTLogOperatorId operatorId = logNo % operatorsCount;
        AddSct(verifiedScts, logNo, operatorId, origin, timestamp, status);
      }
    }
  
    void CheckCompliance(const VerifiedSCTList& verifiedSct,
                         size_t certLifetimeInCalendarMonths,
                         const CTLogOperatorList& dependentLogOperators,
                         CTPolicyCompliance expectedCompliance) {
      CTPolicyCompliance compliance;
      mPolicyEnforcer.CheckCompliance(verifiedSct, certLifetimeInCalendarMonths,
                                      dependentLogOperators, compliance);
      EXPECT_EQ(expectedCompliance, compliance);
    }
  
   protected:
    CTPolicyEnforcer mPolicyEnforcer;
  
    const size_t LOG_1 = 1;
    const size_t LOG_2 = 2;
    const size_t LOG_3 = 3;
    const size_t LOG_4 = 4;
    const size_t LOG_5 = 5;
  
    const CTLogOperatorId OPERATOR_1 = 1;
    const CTLogOperatorId OPERATOR_2 = 2;
    const CTLogOperatorId OPERATOR_3 = 3;
  
    CTLogOperatorList NO_OPERATORS;
    CTLogOperatorList OPERATORS_1_AND_2;
  
    const VerifiedSCT::Origin ORIGIN_EMBEDDED = VerifiedSCT::Origin::Embedded;
    const VerifiedSCT::Origin ORIGIN_TLS = VerifiedSCT::Origin::TLSExtension;
    const VerifiedSCT::Origin ORIGIN_OCSP = VerifiedSCT::Origin::OCSPResponse;
  
    // 4 years of cert lifetime requires 5 SCTs for the embedded case.
    const size_t DEFAULT_MONTHS = 4 * 12L;
  
    // Date.parse("2015-08-15T00:00:00Z")
    const uint64_t TIMESTAMP_1 = 1439596800000L;
  
    // Date.parse("2016-04-15T00:00:00Z")
    const uint64_t DISQUALIFIED_AT = 1460678400000L;
  
    // Date.parse("2016-04-01T00:00:00Z")
    const uint64_t BEFORE_DISQUALIFIED = 1459468800000L;
  
    // Date.parse("2016-04-16T00:00:00Z")
    const uint64_t AFTER_DISQUALIFIED = 1460764800000L;
  };
  
  TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithNonEmbeddedSCTs) {
    VerifiedSCTList scts;
  
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::Compliant);
  }
  
  TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseNonEmbeddedSCTs) {
    VerifiedSCTList scts;
  
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
  
    CheckCompliance(scts, DEFAULT_MONTHS, OPERATORS_1_AND_2,
                    CTPolicyCompliance::NotDiverseScts);
  }
  
  TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithEmbeddedSCTs) {
    VerifiedSCTList scts;
  
    // 5 embedded SCTs required for DEFAULT_MONTHS.
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::Compliant);
  }
  
  TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseEmbeddedSCTs) {
    VerifiedSCTList scts;
  
    // 5 embedded SCTs required for DEFAULT_MONTHS.
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
  
    CheckCompliance(scts, DEFAULT_MONTHS, OPERATORS_1_AND_2,
                    CTPolicyCompliance::NotDiverseScts);
  }
  
  TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledNonEmbeddedSCTs) {
    VerifiedSCTList scts;
  
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_OCSP, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::Compliant);
  }
  
  TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledEmbeddedSCTs) {
    VerifiedSCTList scts;
  
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_OCSP, TIMESTAMP_1);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::Compliant);
  }
  
  TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughSCTs) {
    VerifiedSCTList scts;
  
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
  }
  
  TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughFreshSCTs) {
    VerifiedSCTList scts;
  
    // The results should be the same before and after disqualification,
    // regardless of the delivery method.
  
    // SCT from before disqualification.
    scts.clear();
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, BEFORE_DISQUALIFIED,
           VerifiedSCT::Status::ValidFromDisqualifiedLog);
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
    // SCT from after disqualification.
    scts.clear();
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, AFTER_DISQUALIFIED,
           VerifiedSCT::Status::ValidFromDisqualifiedLog);
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
  
    // Embedded SCT from before disqualification.
    scts.clear();
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_DISQUALIFIED,
           VerifiedSCT::Status::ValidFromDisqualifiedLog);
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
  
    // Embedded SCT from after disqualification.
    scts.clear();
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
           VerifiedSCT::Status::ValidFromDisqualifiedLog);
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
  }
  
  TEST_F(CTPolicyEnforcerTest,
         ConformsWithDisqualifiedLogBeforeDisqualificationDate) {
    VerifiedSCTList scts;
  
    // 5 embedded SCTs required for DEFAULT_MONTHS.
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_DISQUALIFIED,
           VerifiedSCT::Status::ValidFromDisqualifiedLog);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::Compliant);
  }
  
  TEST_F(CTPolicyEnforcerTest,
         DoesNotConformWithDisqualifiedLogAfterDisqualificationDate) {
    VerifiedSCTList scts;
  
    // 5 embedded SCTs required for DEFAULT_MONTHS.
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
           VerifiedSCT::Status::ValidFromDisqualifiedLog);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
  }
  
  TEST_F(CTPolicyEnforcerTest,
         DoesNotConformWithIssuanceDateAfterDisqualificationDate) {
    VerifiedSCTList scts;
  
    // 5 embedded SCTs required for DEFAULT_MONTHS.
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
           VerifiedSCT::Status::ValidFromDisqualifiedLog);
    AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
    AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
    AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
    AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
  
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
  }
  
  TEST_F(CTPolicyEnforcerTest,
         DoesNotConformToCTPolicyNotEnoughUniqueEmbeddedLogs) {
    VerifiedSCTList scts;
  
    // Operator #1
    AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
    // Operator #2, different logs
    AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
    // Operator #3, same log
    AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
    AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
  
    // 5 embedded SCTs required. However, only 4 are from distinct logs.
    CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
                    CTPolicyCompliance::NotEnoughScts);
  }
  
  TEST_F(CTPolicyEnforcerTest,
         ConformsToPolicyExactNumberOfSCTsForValidityPeriod) {
    // Test multiple validity periods.
    const struct TestData {
      size_t certLifetimeInCalendarMonths;
      size_t sctsRequired;
    } kTestData[] = {{3, 2},          {12 + 2, 2},     {12 + 3, 3},
                     {2 * 12 + 2, 3}, {2 * 12 + 3, 4}, {3 * 12 + 2, 4},
                     {3 * 12 + 4, 5}};
  
    for (size_t i = 0; i < MOZILLA_CT_ARRAY_LENGTH(kTestData); ++i) {
      SCOPED_TRACE(i);
  
      size_t months = kTestData[i].certLifetimeInCalendarMonths;
      size_t sctsRequired = kTestData[i].sctsRequired;
  
      // Less SCTs than required is not enough.
      for (size_t sctsAvailable = 0; sctsAvailable < sctsRequired;
           ++sctsAvailable) {
        VerifiedSCTList scts;
        AddMultipleScts(scts, sctsAvailable, 1, ORIGIN_EMBEDDED, TIMESTAMP_1);
  
        CTPolicyCompliance compliance;
        mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS, compliance);
        EXPECT_EQ(CTPolicyCompliance::NotEnoughScts, compliance)
            << "i=" << i << " sctsRequired=" << sctsRequired
            << " sctsAvailable=" << sctsAvailable;
      }
  
      // Add exactly the required number of SCTs (from 2 operators).
      VerifiedSCTList scts;
      AddMultipleScts(scts, sctsRequired, 2, ORIGIN_EMBEDDED, TIMESTAMP_1);
  
      CTPolicyCompliance compliance;
      mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS, compliance);
      EXPECT_EQ(CTPolicyCompliance::Compliant, compliance) << "i=" << i;
    }
  }
  
  TEST_F(CTPolicyEnforcerTest, TestEdgeCasesOfGetCertLifetimeInFullMonths) {
    const struct TestData {
      PRTime notBefore;
      PRTime notAfter;
      size_t expectedMonths;
    } kTestData[] = {
        {                   // 1 second less than 1 month
         1424863500000000,  // Date.parse("2015-02-25T11:25:00Z") * 1000
         1427196299000000,  // Date.parse("2015-03-24T11:24:59Z") * 1000
         0},
        {                   // exactly 1 month
         1424863500000000,  // Date.parse("2015-02-25T11:25:00Z") * 1000
         1427282700000000,  // Date.parse("2015-03-25T11:25:00Z") * 1000
         1},
        {                   // 1 year, 1 month
         1427282700000000,  // Date.parse("2015-03-25T11:25:00Z") * 1000
         1461583500000000,  // Date.parse("2016-04-25T11:25:00Z") * 1000
         13},
        {// 1 year, 1 month, first day of notBefore month, last of notAfter
         1425209100000000,  // Date.parse("2015-03-01T11:25:00Z") * 1000
         1462015500000000,  // Date.parse("2016-04-30T11:25:00Z") * 1000
         13},
        {// 1 year, adjacent months, last day of notBefore month, first of
         // notAfter
         1427801100000000,  // Date.parse("2015-03-31T11:25:00Z") * 1000
         1459509900000000,  // Date.parse("2016-04-01T11:25:00Z") * 1000
         12}};
  
    for (size_t i = 0; i < MOZILLA_CT_ARRAY_LENGTH(kTestData); ++i) {
      SCOPED_TRACE(i);
  
      size_t months;
      ASSERT_EQ(Success,
                GetCertLifetimeInFullMonths(kTestData[i].notBefore,
                                            kTestData[i].notAfter, months))
          << "i=" << i;
      EXPECT_EQ(kTestData[i].expectedMonths, months) << "i=" << i;
    }
  }
  
  }  // namespace ct
  }  // namespace mozilla
  