// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/cert/cert_verify_proc_mac.h"

#include <memory>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/macros.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_verifier.h"
#include "net/cert/cert_verify_result.h"
#include "net/cert/crl_set.h"
#include "net/cert/test_keychain_search_list_mac.h"
#include "net/cert/test_root_certs.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using net::test::IsError;
using net::test::IsOk;

namespace net {

namespace {

// Test that a CRLSet blocking one of the intermediates supplied by the server
// can be worked around by the chopping workaround for path building. (Once the
// supplied chain is chopped back to just the target, a better path can be
// found out-of-band. Normally that would be by AIA fetching, for the purposes
// of this test the better path is supplied by a test keychain.)
//
// In this test, there are two possible paths to validate a leaf (A):
// 1. A(B) -> B(C) -> C(E) -> E(E)
// 2. A(B) -> B(F) -> F(E) -> E(E)
//
// A(B) -> B(C) -> C(E) is supplied to the verifier.
// B(F) and F(E) are supplied in a test keychain.
// C is blocked by a CRLset.
//
// The verifier should rollback until it just tries A(B) alone, at which point
// it will pull B(F) & F(E) from the keychain and succeed.
TEST(CertVerifyProcMacTest, MacCRLIntermediate) {
  if (base::mac::IsAtLeastOS10_12()) {
    // TODO(crbug.com/671889): Investigate SecTrustSetKeychains issue on Sierra.
    LOG(INFO) << "Skipping test, SecTrustSetKeychains does not work on 10.12";
    return;
  }
  CertificateList path_2_certs;
  ASSERT_TRUE(
      LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-C.pem",
                            "multi-root-C-by-E.pem", "multi-root-E-by-E.pem"},
                           &path_2_certs));

  CertificateList path_3_certs;
  ASSERT_TRUE(
      LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-F.pem",
                            "multi-root-F-by-E.pem", "multi-root-E-by-E.pem"},
                           &path_3_certs));

  // Add E as trust anchor.
  ScopedTestRoot test_root_E(path_3_certs[3].get());  // E-by-E

  std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
  intermediates.push_back(
      bssl::UpRef(path_2_certs[1]->cert_buffer()));  // B-by-C
  intermediates.push_back(
      bssl::UpRef(path_2_certs[2]->cert_buffer()));  // C-by-E
  scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromBuffer(
      bssl::UpRef(path_3_certs[0]->cert_buffer()), std::move(intermediates));
  ASSERT_TRUE(cert);

  std::unique_ptr<TestKeychainSearchList> test_keychain_search_list(
      TestKeychainSearchList::Create());
  ASSERT_TRUE(test_keychain_search_list);

  base::FilePath keychain_path(
      GetTestCertsDirectory().AppendASCII("multi-root-BFE.keychain"));
  // SecKeychainOpen does not fail if the file doesn't exist, so assert it here
  // for easier debugging.
  ASSERT_TRUE(base::PathExists(keychain_path));
  SecKeychainRef keychain;
  OSStatus status =
      SecKeychainOpen(keychain_path.MaybeAsASCII().c_str(), &keychain);
  ASSERT_EQ(errSecSuccess, status);
  ASSERT_TRUE(keychain);
  base::ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain);
  test_keychain_search_list->AddKeychain(keychain);

  scoped_refptr<CRLSet> crl_set;
  std::string crl_set_bytes;
  // CRL which blocks C by SPKI.
  EXPECT_TRUE(base::ReadFileToString(
      GetTestCertsDirectory().AppendASCII("multi-root-crlset-C.raw"),
      &crl_set_bytes));
  ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));

  int flags = 0;
  CertVerifyResult verify_result;

  scoped_refptr<CertVerifyProc> verify_proc = new CertVerifyProcMac;
  int error =
      verify_proc->Verify(cert.get(), "127.0.0.1", std::string(), flags,
                          crl_set.get(), CertificateList(), &verify_result);

  ASSERT_EQ(OK, error);
  ASSERT_EQ(0U, verify_result.cert_status);
  ASSERT_TRUE(verify_result.verified_cert.get());

  const auto& verified_intermediates =
      verify_result.verified_cert->intermediate_buffers();
  ASSERT_EQ(3U, verified_intermediates.size());

  scoped_refptr<X509Certificate> intermediate =
      X509Certificate::CreateFromBuffer(
          bssl::UpRef(verified_intermediates[1].get()), {});
  ASSERT_TRUE(intermediate);

  scoped_refptr<X509Certificate> expected_intermediate = path_3_certs[2];
  EXPECT_TRUE(expected_intermediate->EqualsExcludingChain(intermediate.get()))
      << "Expected: " << expected_intermediate->subject().common_name
      << " issued by " << expected_intermediate->issuer().common_name
      << "; Got: " << intermediate->subject().common_name << " issued by "
      << intermediate->issuer().common_name;
}

// Test that if a keychain is present which trusts a less-desirable root (ex,
// one using SHA1), that the keychain reordering hack will cause the better
// root in the System Roots to be used instead.
// TODO(crbug.com/867174): Re-enable this test.
TEST(CertVerifyProcMacTest, DISABLED_MacKeychainReordering) {
  // Note: target cert expires Dec 30 23:59:59 2019 GMT
  scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
      GetTestCertsDirectory(), "gms.hongleong.com.my-verisign-chain.pem",
      X509Certificate::FORMAT_AUTO);
  ASSERT_TRUE(cert);

  // Create a test keychain search list that will Always Trust the SHA1
  // cross-signed VeriSign Class 3 Public Primary Certification Authority - G5
  std::unique_ptr<TestKeychainSearchList> test_keychain_search_list(
      TestKeychainSearchList::Create());
  ASSERT_TRUE(test_keychain_search_list);

  base::FilePath keychain_path(GetTestCertsDirectory().AppendASCII(
      "verisign_class3_g5_crosssigned-trusted.keychain"));
  // SecKeychainOpen does not fail if the file doesn't exist, so assert it here
  // for easier debugging.
  ASSERT_TRUE(base::PathExists(keychain_path));
  SecKeychainRef keychain;
  OSStatus status =
      SecKeychainOpen(keychain_path.MaybeAsASCII().c_str(), &keychain);
  ASSERT_EQ(errSecSuccess, status);
  ASSERT_TRUE(keychain);
  base::ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain);
  test_keychain_search_list->AddKeychain(keychain);

  int flags = 0;
  CertVerifyResult verify_result;
  scoped_refptr<CertVerifyProc> verify_proc = new CertVerifyProcMac;
  int error = verify_proc->Verify(cert.get(), "gms.hongleong.com.my",
                                  std::string(), flags, nullptr /* crl_set */,
                                  CertificateList(), &verify_result);

  ASSERT_EQ(OK, error);
  EXPECT_FALSE(verify_result.has_sha1);
  ASSERT_TRUE(verify_result.verified_cert.get());

  const auto& verified_intermediates =
      verify_result.verified_cert->intermediate_buffers();
  ASSERT_EQ(2U, verified_intermediates.size());
}

// Test that the system root certificate keychain is in the expected location
// and can be opened. Other tests would fail if this was not true, but this
// test makes the reason for the failure obvious.
TEST(CertVerifyProcMacTest, MacSystemRootCertificateKeychainLocation) {
  const char* root_keychain_path =
      "/System/Library/Keychains/SystemRootCertificates.keychain";
  ASSERT_TRUE(base::PathExists(base::FilePath(root_keychain_path)));

  SecKeychainRef keychain;
  OSStatus status = SecKeychainOpen(root_keychain_path, &keychain);
  ASSERT_EQ(errSecSuccess, status);
  CFRelease(keychain);
}

// Test that CertVerifyProcMac reacts appropriately when Apple's certificate
// verifier rejects a certificate with a fatal error. This is a regression
// test for https://crbug.com/472291.
// (Since 10.12, this causes a recoverable error instead of a fatal one.)
// TODO(mattm): Try to find a different way to cause a fatal error that works
// on 10.12.
TEST(CertVerifyProcMacTest, LargeKey) {
  // Load root_ca_cert.pem into the test root store.
  ScopedTestRoot test_root(
      ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem").get());

  scoped_refptr<X509Certificate> cert(
      ImportCertFromFile(GetTestCertsDirectory(), "large_key.pem"));

  // Apple's verifier rejects this certificate as invalid because the
  // RSA key is too large. If a future version of OS X changes this,
  // large_key.pem may need to be regenerated with a larger key.
  int flags = 0;
  CertVerifyResult verify_result;
  scoped_refptr<CertVerifyProc> verify_proc = new CertVerifyProcMac;
  int error = verify_proc->Verify(cert.get(), "127.0.0.1", std::string(), flags,
                                  NULL, CertificateList(), &verify_result);
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
}

}  // namespace

}  // namespace net
