// Copyright (c) 2012 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/base/server_bound_cert_service.h"

#include <string>
#include <vector>

#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/threading/sequenced_worker_pool.h"
#include "crypto/ec_private_key.h"
#include "net/base/asn1_util.h"
#include "net/base/default_server_bound_cert_store.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/base/x509_certificate.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {

void FailTest(int /* result */) {
  FAIL();
}

class ServerBoundCertServiceTest : public testing::Test {
 protected:
  virtual void SetUp() OVERRIDE {
    sequenced_worker_pool_ = new base::SequencedWorkerPool(
        3, "ServerBoundCertServiceTest");
    service_.reset(new ServerBoundCertService(
        new DefaultServerBoundCertStore(NULL), sequenced_worker_pool_));
  }

  virtual void TearDown() OVERRIDE {
    sequenced_worker_pool_->Shutdown();
  }

  scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool_;
  scoped_ptr<ServerBoundCertService> service_;
};

TEST_F(ServerBoundCertServiceTest, GetDomainForHost) {
  EXPECT_EQ("google.com",
            ServerBoundCertService::GetDomainForHost("google.com"));
  EXPECT_EQ("google.com",
            ServerBoundCertService::GetDomainForHost("www.google.com"));
  // NOTE(rch): we would like to segregate cookies and certificates for
  // *.appspot.com, but currently we can not do that becaues we want to
  // allow direct navigation to appspot.com.
  EXPECT_EQ("appspot.com",
            ServerBoundCertService::GetDomainForHost("foo.appspot.com"));
  EXPECT_EQ("google.com",
            ServerBoundCertService::GetDomainForHost("www.mail.google.com"));
  EXPECT_EQ("goto",
            ServerBoundCertService::GetDomainForHost("goto"));
  EXPECT_EQ("127.0.0.1",
            ServerBoundCertService::GetDomainForHost("127.0.0.1"));
}

// See http://crbug.com/91512 - implement OpenSSL version of CreateSelfSigned.
#if !defined(USE_OPENSSL)

TEST_F(ServerBoundCertServiceTest, CacheHit) {
  std::string origin("https://encrypted.google.com:443");

  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  TestCompletionCallback callback;
  ServerBoundCertService::RequestHandle request_handle;

  // Asynchronous completion.
  SSLClientCertType type1;
  std::string private_key_info1, der_cert1;
  EXPECT_EQ(0, service_->cert_count());
  error = service_->GetDomainBoundCert(
      origin, types, &type1, &private_key_info1, &der_cert1,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  error = callback.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(1, service_->cert_count());
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1);
  EXPECT_FALSE(private_key_info1.empty());
  EXPECT_FALSE(der_cert1.empty());

  // Synchronous completion.
  SSLClientCertType type2;
  std::string private_key_info2, der_cert2;
  error = service_->GetDomainBoundCert(
      origin, types, &type2, &private_key_info2, &der_cert2,
      callback.callback(), &request_handle);
  EXPECT_TRUE(request_handle == NULL);
  EXPECT_EQ(OK, error);
  EXPECT_EQ(1, service_->cert_count());
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2);
  EXPECT_EQ(private_key_info1, private_key_info2);
  EXPECT_EQ(der_cert1, der_cert2);

  EXPECT_EQ(2u, service_->requests());
  EXPECT_EQ(1u, service_->cert_store_hits());
  EXPECT_EQ(0u, service_->inflight_joins());
}

TEST_F(ServerBoundCertServiceTest, UnsupportedTypes) {
  std::string origin("https://encrypted.google.com:443");

  int error;
  std::vector<uint8> types;
  TestCompletionCallback callback;
  ServerBoundCertService::RequestHandle request_handle;

  // Empty requested_types.
  SSLClientCertType type1;
  std::string private_key_info1, der_cert1;
  error = service_->GetDomainBoundCert(
      origin, types, &type1, &private_key_info1, &der_cert1,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_INVALID_ARGUMENT, error);
  EXPECT_TRUE(request_handle == NULL);

  // No supported types in requested_types.
  types.push_back(CLIENT_CERT_RSA_SIGN);
  types.push_back(2);
  types.push_back(3);
  error = service_->GetDomainBoundCert(
      origin, types, &type1, &private_key_info1, &der_cert1,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, error);
  EXPECT_TRUE(request_handle == NULL);

  // Supported types after unsupported ones in requested_types.
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  // Asynchronous completion.
  EXPECT_EQ(0, service_->cert_count());
  error = service_->GetDomainBoundCert(
      origin, types, &type1, &private_key_info1, &der_cert1,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  error = callback.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(1, service_->cert_count());
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1);
  EXPECT_FALSE(private_key_info1.empty());
  EXPECT_FALSE(der_cert1.empty());

  // Now that the cert is created, doing requests for unsupported types
  // shouldn't affect the created cert.
  // Empty requested_types.
  types.clear();
  SSLClientCertType type2;
  std::string private_key_info2, der_cert2;
  error = service_->GetDomainBoundCert(
      origin, types, &type2, &private_key_info2, &der_cert2,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_INVALID_ARGUMENT, error);
  EXPECT_TRUE(request_handle == NULL);

  // No supported types in requested_types.
  types.push_back(CLIENT_CERT_RSA_SIGN);
  types.push_back(2);
  types.push_back(3);
  error = service_->GetDomainBoundCert(
      origin, types, &type2, &private_key_info2, &der_cert2,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, error);
  EXPECT_TRUE(request_handle == NULL);

  // If we request EC, the cert we created before should still be there.
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  error = service_->GetDomainBoundCert(
      origin, types, &type2, &private_key_info2, &der_cert2,
      callback.callback(), &request_handle);
  EXPECT_TRUE(request_handle == NULL);
  EXPECT_EQ(OK, error);
  EXPECT_EQ(1, service_->cert_count());
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2);
  EXPECT_EQ(private_key_info1, private_key_info2);
  EXPECT_EQ(der_cert1, der_cert2);
}

TEST_F(ServerBoundCertServiceTest, StoreCerts) {
  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  TestCompletionCallback callback;
  ServerBoundCertService::RequestHandle request_handle;

  std::string origin1("https://encrypted.google.com:443");
  SSLClientCertType type1;
  std::string private_key_info1, der_cert1;
  EXPECT_EQ(0, service_->cert_count());
  error = service_->GetDomainBoundCert(
      origin1, types, &type1, &private_key_info1, &der_cert1,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  error = callback.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(1, service_->cert_count());

  std::string origin2("https://www.verisign.com:443");
  SSLClientCertType type2;
  std::string private_key_info2, der_cert2;
  error = service_->GetDomainBoundCert(
      origin2, types, &type2, &private_key_info2, &der_cert2,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  error = callback.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(2, service_->cert_count());

  std::string origin3("https://www.twitter.com:443");
  SSLClientCertType type3;
  std::string private_key_info3, der_cert3;
  error = service_->GetDomainBoundCert(
      origin3, types, &type3, &private_key_info3, &der_cert3,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  error = callback.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(3, service_->cert_count());

  EXPECT_NE(private_key_info1, private_key_info2);
  EXPECT_NE(der_cert1, der_cert2);
  EXPECT_NE(private_key_info1, private_key_info3);
  EXPECT_NE(der_cert1, der_cert3);
  EXPECT_NE(private_key_info2, private_key_info3);
  EXPECT_NE(der_cert2, der_cert3);
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1);
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2);
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type3);
}

// Tests an inflight join.
TEST_F(ServerBoundCertServiceTest, InflightJoin) {
  std::string origin("https://encrypted.google.com:443");
  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);

  SSLClientCertType type1;
  std::string private_key_info1, der_cert1;
  TestCompletionCallback callback1;
  ServerBoundCertService::RequestHandle request_handle1;

  SSLClientCertType type2;
  std::string private_key_info2, der_cert2;
  TestCompletionCallback callback2;
  ServerBoundCertService::RequestHandle request_handle2;

  error = service_->GetDomainBoundCert(
      origin, types, &type1, &private_key_info1, &der_cert1,
      callback1.callback(), &request_handle1);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle1 != NULL);
  // If we request RSA and EC in the 2nd request, should still join with the
  // original request.
  types.insert(types.begin(), CLIENT_CERT_RSA_SIGN);
  error = service_->GetDomainBoundCert(
      origin, types, &type2, &private_key_info2, &der_cert2,
      callback2.callback(), &request_handle2);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle2 != NULL);

  error = callback1.WaitForResult();
  EXPECT_EQ(OK, error);
  error = callback2.WaitForResult();
  EXPECT_EQ(OK, error);

  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1);
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2);
  EXPECT_EQ(2u, service_->requests());
  EXPECT_EQ(0u, service_->cert_store_hits());
  EXPECT_EQ(1u, service_->inflight_joins());
}

TEST_F(ServerBoundCertServiceTest, ExtractValuesFromBytesEC) {
  std::string origin("https://encrypted.google.com:443");
  SSLClientCertType type;
  std::string private_key_info, der_cert;
  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  TestCompletionCallback callback;
  ServerBoundCertService::RequestHandle request_handle;

  error = service_->GetDomainBoundCert(
      origin, types, &type, &private_key_info, &der_cert, callback.callback(),
      &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  error = callback.WaitForResult();
  EXPECT_EQ(OK, error);

  base::StringPiece spki_piece;
  ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(der_cert, &spki_piece));
  std::vector<uint8> spki(
      spki_piece.data(),
      spki_piece.data() + spki_piece.size());

  // Check that we can retrieve the key from the bytes.
  std::vector<uint8> key_vec(private_key_info.begin(), private_key_info.end());
  scoped_ptr<crypto::ECPrivateKey> private_key(
      crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
          ServerBoundCertService::kEPKIPassword, key_vec, spki));
  EXPECT_TRUE(private_key != NULL);

  // Check that we can retrieve the cert from the bytes.
  scoped_refptr<X509Certificate> x509cert(
      X509Certificate::CreateFromBytes(der_cert.data(), der_cert.size()));
  EXPECT_TRUE(x509cert != NULL);
}

// Tests that the callback of a canceled request is never made.
TEST_F(ServerBoundCertServiceTest, CancelRequest) {
  std::string origin("https://encrypted.google.com:443");
  SSLClientCertType type;
  std::string private_key_info, der_cert;
  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  ServerBoundCertService::RequestHandle request_handle;

  error = service_->GetDomainBoundCert(origin,
                                      types,
                                      &type,
                                      &private_key_info,
                                      &der_cert,
                                      base::Bind(&FailTest),
                                      &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  service_->CancelRequest(request_handle);

  // Wait for generation to finish.
  sequenced_worker_pool_->FlushForTesting();
  // Wait for reply from ServerBoundCertServiceWorker to be posted back to the
  // ServerBoundCertService.
  MessageLoop::current()->RunAllPending();

  // Even though the original request was cancelled, the service will still
  // store the result, it just doesn't call the callback.
  EXPECT_EQ(1, service_->cert_count());
}

TEST_F(ServerBoundCertServiceTest, DestructionWithPendingRequest) {
  std::string origin("https://encrypted.google.com:443");
  SSLClientCertType type;
  std::string private_key_info, der_cert;
  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  ServerBoundCertService::RequestHandle request_handle;

  error = service_->GetDomainBoundCert(origin,
                                      types,
                                      &type,
                                      &private_key_info,
                                      &der_cert,
                                      base::Bind(&FailTest),
                                      &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);

  // Cancel request and destroy the ServerBoundCertService.
  service_->CancelRequest(request_handle);
  service_.reset();

  // Wait for generation to finish.
  sequenced_worker_pool_->FlushForTesting();
  // ServerBoundCertServiceWorker should not post anything back to the
  // non-existant ServerBoundCertService, but run the loop just to be sure it
  // doesn't.
  MessageLoop::current()->RunAllPending();

  // If we got here without crashing or a valgrind error, it worked.
}

// Tests that simultaneous creation of different certs works.
TEST_F(ServerBoundCertServiceTest, SimultaneousCreation) {
  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  ServerBoundCertService::RequestHandle request_handle;

  std::string origin1("https://encrypted.google.com:443");
  SSLClientCertType type1;
  std::string private_key_info1, der_cert1;
  TestCompletionCallback callback1;

  std::string origin2("https://foo.com:443");
  SSLClientCertType type2;
  std::string private_key_info2, der_cert2;
  TestCompletionCallback callback2;

  std::string origin3("https://bar.com:443");
  SSLClientCertType type3;
  std::string private_key_info3, der_cert3;
  TestCompletionCallback callback3;

  error = service_->GetDomainBoundCert(origin1,
                                       types,
                                       &type1,
                                       &private_key_info1,
                                       &der_cert1,
                                       callback1.callback(),
                                       &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);

  error = service_->GetDomainBoundCert(origin2,
                                       types,
                                       &type2,
                                       &private_key_info2,
                                       &der_cert2,
                                       callback2.callback(),
                                       &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);

  error = service_->GetDomainBoundCert(origin3,
                                       types,
                                       &type3,
                                       &private_key_info3,
                                       &der_cert3,
                                       callback3.callback(),
                                       &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);

  error = callback1.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1);
  EXPECT_FALSE(private_key_info1.empty());
  EXPECT_FALSE(der_cert1.empty());

  error = callback2.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2);
  EXPECT_FALSE(private_key_info2.empty());
  EXPECT_FALSE(der_cert2.empty());

  error = callback3.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type3);
  EXPECT_FALSE(private_key_info3.empty());
  EXPECT_FALSE(der_cert3.empty());

  EXPECT_NE(private_key_info1, private_key_info2);
  EXPECT_NE(der_cert1, der_cert2);

  EXPECT_NE(private_key_info1, private_key_info3);
  EXPECT_NE(der_cert1, der_cert3);

  EXPECT_NE(private_key_info2, private_key_info3);
  EXPECT_NE(der_cert2, der_cert3);

  EXPECT_EQ(3, service_->cert_count());
}

TEST_F(ServerBoundCertServiceTest, Expiration) {
  ServerBoundCertStore* store = service_->GetCertStore();
  base::Time now = base::Time::Now();
  store->SetServerBoundCert("good",
                            CLIENT_CERT_ECDSA_SIGN,
                            now,
                            now + base::TimeDelta::FromDays(1),
                            "a",
                            "b");
  store->SetServerBoundCert("expired",
                            CLIENT_CERT_ECDSA_SIGN,
                            now - base::TimeDelta::FromDays(2),
                            now - base::TimeDelta::FromDays(1),
                            "c",
                            "d");
  EXPECT_EQ(2, service_->cert_count());

  int error;
  std::vector<uint8> types;
  types.push_back(CLIENT_CERT_ECDSA_SIGN);
  TestCompletionCallback callback;
  ServerBoundCertService::RequestHandle request_handle;

  // Cert still valid - synchronous completion.
  SSLClientCertType type1;
  std::string private_key_info1, der_cert1;
  error = service_->GetDomainBoundCert(
      "https://good", types, &type1, &private_key_info1, &der_cert1,
      callback.callback(), &request_handle);
  EXPECT_EQ(OK, error);
  EXPECT_TRUE(request_handle == NULL);
  EXPECT_EQ(2, service_->cert_count());
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1);
  EXPECT_STREQ("a", private_key_info1.c_str());
  EXPECT_STREQ("b", der_cert1.c_str());

  // Cert expired - New cert will be generated, asynchronous completion.
  SSLClientCertType type2;
  std::string private_key_info2, der_cert2;
  error = service_->GetDomainBoundCert(
      "https://expired", types, &type2, &private_key_info2, &der_cert2,
      callback.callback(), &request_handle);
  EXPECT_EQ(ERR_IO_PENDING, error);
  EXPECT_TRUE(request_handle != NULL);
  error = callback.WaitForResult();
  EXPECT_EQ(OK, error);
  EXPECT_EQ(2, service_->cert_count());
  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2);
  EXPECT_LT(1U, private_key_info2.size());
  EXPECT_LT(1U, der_cert2.size());
}

#endif  // !defined(USE_OPENSSL)

}  // namespace

}  // namespace net
