/* -*- 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 "CTVerifyResult.h"
#include "gtest/gtest.h"
#include "hasht.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "prtime.h"
#include "SignedCertificateTimestamp.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
  {
    MOZ_ALWAYS_TRUE(OPERATORS_1_AND_2.append(OPERATOR_1));
    MOZ_ALWAYS_TRUE(OPERATORS_1_AND_2.append(OPERATOR_2));
  }

  void GetLogId(Buffer& logId, size_t logNo)
  {
    ASSERT_TRUE(logId.resize(SHA256_LENGTH));
    std::fill(logId.begin(), logId.end(), 0);
    // Just raw-copy |logId| into the output buffer.
    MOZ_ASSERT(sizeof(logNo) <= logId.length());
    memcpy(logId.begin(), &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 = Move(logId);
    ASSERT_TRUE(verifiedScts.append(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;
    ASSERT_EQ(Success,
              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 < ArrayLength(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;
      ASSERT_EQ(Success,
                mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS,
                                                compliance))
        << "i=" << i
        << " sctsRequired=" << sctsRequired
        << " sctsAvailable=" << sctsAvailable;
      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;
    ASSERT_EQ(Success,
              mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS,
                                              compliance))
      << "i=" << i;
    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 < ArrayLength(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 mozilla::ct
