// 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/x509_util.h"
#include "net/base/x509_util_nss.h"

#include <cert.h>
#include <secoid.h>

#include "base/memory/scoped_ptr.h"
#include "base/memory/ref_counted.h"
#include "crypto/ec_private_key.h"
#include "crypto/scoped_nss_types.h"
#include "crypto/signature_verifier.h"
#include "net/base/x509_certificate.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {

CERTCertificate* CreateNSSCertHandleFromBytes(const char* data, size_t length) {
  SECItem der_cert;
  der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data));
  der_cert.len  = length;
  der_cert.type = siDERCertBuffer;

  // Parse into a certificate structure.
  return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL,
                                 PR_FALSE, PR_TRUE);
}

#if !defined(OS_WIN) && !defined(OS_MACOSX)
void VerifyCertificateSignature(const std::string& der_cert,
                                const std::vector<uint8>& der_spki) {
  crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));

  CERTSignedData sd;
  memset(&sd, 0, sizeof(sd));

  SECItem der_cert_item = {
    siDERCertBuffer,
    reinterpret_cast<unsigned char*>(const_cast<char*>(der_cert.data())),
    der_cert.size()
  };
  SECStatus rv = SEC_ASN1DecodeItem(arena.get(), &sd,
                                    SEC_ASN1_GET(CERT_SignedDataTemplate),
                                    &der_cert_item);
  ASSERT_EQ(SECSuccess, rv);

  // The CERTSignedData.signatureAlgorithm is decoded, but SignatureVerifier
  // wants the DER encoded form, so re-encode it again.
  SECItem* signature_algorithm = SEC_ASN1EncodeItem(
      arena.get(),
      NULL,
      &sd.signatureAlgorithm,
      SEC_ASN1_GET(SECOID_AlgorithmIDTemplate));
  ASSERT_TRUE(signature_algorithm);

  crypto::SignatureVerifier verifier;
  bool ok = verifier.VerifyInit(
      signature_algorithm->data,
      signature_algorithm->len,
      sd.signature.data,
      sd.signature.len / 8,  // Signature is a BIT STRING, convert to bytes.
      &der_spki[0],
      der_spki.size());

  ASSERT_TRUE(ok);
  verifier.VerifyUpdate(sd.data.data,
                        sd.data.len);

  ok = verifier.VerifyFinal();
  EXPECT_TRUE(ok);
}
#endif  // !defined(OS_WIN) && !defined(OS_MACOSX)

void VerifyDomainBoundCert(const std::string& domain,
                           const std::string& der_cert) {
  // Origin Bound Cert OID.
  static const char oid_string[] = "1.3.6.1.4.1.11129.2.1.6";

  // Create object neccessary for extension lookup call.
  SECItem extension_object = {
    siAsciiString,
    (unsigned char*)domain.data(),
    domain.size()
  };

  // IA5Encode and arena allocate SECItem.
  PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
  SECItem* expected = SEC_ASN1EncodeItem(arena,
                                         NULL,
                                         &extension_object,
                                         SEC_ASN1_GET(SEC_IA5StringTemplate));

  ASSERT_NE(static_cast<SECItem*>(NULL), expected);

  // Create OID SECItem.
  SECItem ob_cert_oid = { siDEROID, NULL, 0 };
  SECStatus ok = SEC_StringToOID(arena, &ob_cert_oid,
                                 oid_string, 0);

  ASSERT_EQ(SECSuccess, ok);

  SECOidTag ob_cert_oid_tag = SECOID_FindOIDTag(&ob_cert_oid);

  ASSERT_NE(SEC_OID_UNKNOWN, ob_cert_oid_tag);

  // This test is run on Mac and Win where X509Certificate::os_cert_handle isn't
  // an NSS type, so we have to manually create a NSS certificate object so we
  // can use CERT_FindCertExtension.  We also check the subject and validity
  // times using NSS since X509Certificate will fail with EC certs on OSX 10.5
  // (http://crbug.com/101231).
  CERTCertificate* nss_cert = CreateNSSCertHandleFromBytes(
      der_cert.data(), der_cert.size());

  char* common_name = CERT_GetCommonName(&nss_cert->subject);
  ASSERT_TRUE(common_name);
  EXPECT_STREQ("anonymous.invalid", common_name);
  PORT_Free(common_name);
  EXPECT_EQ(SECSuccess, CERT_CertTimesValid(nss_cert));

  // Lookup Origin Bound Cert extension in generated cert.
  SECItem actual = { siBuffer, NULL, 0 };
  ok = CERT_FindCertExtension(nss_cert,
                              ob_cert_oid_tag,
                              &actual);
  CERT_DestroyCertificate(nss_cert);
  ASSERT_EQ(SECSuccess, ok);

  // Compare expected and actual extension values.
  PRBool result = SECITEM_ItemsAreEqual(expected, &actual);
  ASSERT_TRUE(result);

  // Do Cleanup.
  SECITEM_FreeItem(&actual, PR_FALSE);
  PORT_FreeArena(arena, PR_FALSE);
}

}  // namespace

// This test creates a domain-bound cert from an EC private key and
// then verifies the content of the certificate.
TEST(X509UtilNSSTest, CreateDomainBoundCertEC) {
  // Create a sample ASCII weborigin.
  std::string domain = "weborigin.com";
  base::Time now = base::Time::Now();

  scoped_ptr<crypto::ECPrivateKey> private_key(
      crypto::ECPrivateKey::Create());
  std::string der_cert;
  ASSERT_TRUE(x509_util::CreateDomainBoundCertEC(
      private_key.get(),
      domain, 1,
      now,
      now + base::TimeDelta::FromDays(1),
      &der_cert));

  VerifyDomainBoundCert(domain, der_cert);

#if !defined(OS_WIN) && !defined(OS_MACOSX)
  // signature_verifier_win and signature_verifier_mac can't handle EC certs.
  std::vector<uint8> spki;
  ASSERT_TRUE(private_key->ExportPublicKey(&spki));
  VerifyCertificateSignature(der_cert, spki);
#endif
}

}  // namespace net
