/* -*- 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/. */

#ifndef CertVerifier_h
#define CertVerifier_h

#include "BRNameMatchingPolicy.h"
#include "CTVerifyResult.h"
#include "OCSPCache.h"
#include "ScopedNSSTypes.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "pkix/pkixtypes.h"

#if defined(_MSC_VER)
#pragma warning(push)
// Silence "RootingAPI.h(718): warning C4324: 'js::DispatchWrapper<T>':
// structure was padded due to alignment specifier with [ T=void * ]"
#pragma warning(disable:4324)
// Silence "Value.h(448): warning C4365: 'return': conversion from 'const
// int32_t' to 'JS::Value::PayloadType', signed/unsigned mismatch"
#pragma warning(disable:4365)
// Silence "warning C5031: #pragma warning(pop): likely mismatch, popping
// warning state pushed in different file 
#pragma warning(disable:5031)
#endif /* defined(_MSC_VER) */
#include "mozilla/BasePrincipal.h"
#if defined(_MSC_VER)
#pragma warning(pop) /* popping the pragma in Vector.h */
#pragma warning(pop) /* popping the pragma in this file */
#endif /* defined(_MSC_VER) */

namespace mozilla { namespace ct {

// Including MultiLogCTVerifier.h would bring along all of its dependent
// headers and force us to export them in moz.build. Just forward-declare
// the class here instead.
class MultiLogCTVerifier;

} } // namespace mozilla::ct

namespace mozilla { namespace psm {

typedef mozilla::pkix::Result Result;

// These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
enum class KeySizeStatus {
  NeverChecked = 0,
  LargeMinimumSucceeded = 1,
  CompatibilityRisk = 2,
  AlreadyBad = 3,
};

// These values correspond to the CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
enum class SHA1ModeResult {
  NeverChecked = 0,
  SucceededWithoutSHA1 = 1,
  SucceededWithImportedRoot = 2,
  SucceededWithImportedRootOrSHA1Before2016 = 3,
  SucceededWithSHA1 = 4,
  Failed = 5,
};

enum class NetscapeStepUpPolicy : uint32_t;

class PinningTelemetryInfo
{
public:
  PinningTelemetryInfo() { Reset(); }

  // Should we accumulate pinning telemetry for the result?
  bool accumulateResult;
  Telemetry::ID certPinningResultHistogram;
  int32_t certPinningResultBucket;
  // Should we accumulate telemetry for the root?
  bool accumulateForRoot;
  int32_t rootBucket;

  void Reset() { accumulateForRoot = false; accumulateResult = false; }
};

class CertificateTransparencyInfo
{
public:
  CertificateTransparencyInfo() { Reset(); }

  // Was CT enabled?
  bool enabled;
  // Did we receive and process any binary SCT data from the supported sources?
  bool processedSCTs;
  // Verification result of the processed SCTs.
  mozilla::ct::CTVerifyResult verifyResult;

  void Reset() { enabled = false; processedSCTs = false; verifyResult.Reset(); }
};

class NSSCertDBTrustDomain;

class CertVerifier
{
public:
  typedef unsigned int Flags;
  // XXX: FLAG_LOCAL_ONLY is ignored in the classic verification case
  static const Flags FLAG_LOCAL_ONLY;
  // Don't perform fallback DV validation on EV validation failure.
  static const Flags FLAG_MUST_BE_EV;
  // TLS feature request_status should be ignored
  static const Flags FLAG_TLS_IGNORE_STATUS_REQUEST;

  // These values correspond to the SSL_OCSP_STAPLING telemetry.
  enum OCSPStaplingStatus {
    OCSP_STAPLING_NEVER_CHECKED = 0,
    OCSP_STAPLING_GOOD = 1,
    OCSP_STAPLING_NONE = 2,
    OCSP_STAPLING_EXPIRED = 3,
    OCSP_STAPLING_INVALID = 4,
  };

  // *evOidPolicy == SEC_OID_UNKNOWN means the cert is NOT EV
  // Only one usage per verification is supported.
  mozilla::pkix::Result VerifyCert(
                    CERTCertificate* cert,
                    SECCertificateUsage usage,
                    mozilla::pkix::Time time,
                    void* pinArg,
                    const char* hostname,
            /*out*/ UniqueCERTCertList& builtChain,
                    Flags flags = 0,
    /*optional in*/ const SECItem* stapledOCSPResponse = nullptr,
    /*optional in*/ const SECItem* sctsFromTLS = nullptr,
    /*optional in*/ const NeckoOriginAttributes& originAttributes =
                      NeckoOriginAttributes(),
   /*optional out*/ SECOidTag* evOidPolicy = nullptr,
   /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
   /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
   /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
   /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
   /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);

  mozilla::pkix::Result VerifySSLServerCert(
                    const UniqueCERTCertificate& peerCert,
       /*optional*/ const SECItem* stapledOCSPResponse,
       /*optional*/ const SECItem* sctsFromTLS,
                    mozilla::pkix::Time time,
       /*optional*/ void* pinarg,
                    const char* hostname,
            /*out*/ UniqueCERTCertList& builtChain,
       /*optional*/ bool saveIntermediatesInPermanentDatabase = false,
       /*optional*/ Flags flags = 0,
       /*optional*/ const NeckoOriginAttributes& originAttributes =
                      NeckoOriginAttributes(),
   /*optional out*/ SECOidTag* evOidPolicy = nullptr,
   /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
   /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
   /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
   /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
   /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);

  enum PinningMode {
    pinningDisabled = 0,
    pinningAllowUserCAMITM = 1,
    pinningStrict = 2,
    pinningEnforceTestMode = 3
  };

  enum class SHA1Mode {
    Allowed = 0,
    Forbidden = 1,
    // There used to be a policy that only allowed SHA1 for certificates issued
    // before 2016. This is no longer available. If a user has selected this
    // policy in about:config, it now maps to Forbidden.
    UsedToBeBefore2016ButNowIsForbidden = 2,
    ImportedRoot = 3,
    ImportedRootOrBefore2016 = 4,
  };

  enum OcspDownloadConfig {
    ocspOff = 0,
    ocspOn = 1,
    ocspEVOnly = 2
  };
  enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
  enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };

  enum class CertificateTransparencyMode {
    Disabled = 0,
    TelemetryOnly = 1,
  };

  CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
               OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
               PinningMode pinningMode, SHA1Mode sha1Mode,
               BRNameMatchingPolicy::Mode nameMatchingMode,
               NetscapeStepUpPolicy netscapeStepUpPolicy,
               CertificateTransparencyMode ctMode);
  ~CertVerifier();

  void ClearOCSPCache() { mOCSPCache.Clear(); }

  const OcspDownloadConfig mOCSPDownloadConfig;
  const bool mOCSPStrict;
  const bool mOCSPGETEnabled;
  const uint32_t mCertShortLifetimeInDays;
  const PinningMode mPinningMode;
  const SHA1Mode mSHA1Mode;
  const BRNameMatchingPolicy::Mode mNameMatchingMode;
  const NetscapeStepUpPolicy mNetscapeStepUpPolicy;
  const CertificateTransparencyMode mCTMode;

private:
  OCSPCache mOCSPCache;

  // We only have a forward declaration of MultiLogCTVerifier (see above),
  // so we keep a pointer to it and allocate dynamically.
  UniquePtr<mozilla::ct::MultiLogCTVerifier> mCTVerifier;

  void LoadKnownCTLogs();
  mozilla::pkix::Result VerifySignedCertificateTimestamps(
                     NSSCertDBTrustDomain& trustDomain,
                     const UniqueCERTCertList& builtChain,
                     mozilla::pkix::Input sctsFromTLS,
                     mozilla::pkix::Time time,
    /*optional out*/ CertificateTransparencyInfo* ctInfo);

  // Returns true if the configured SHA1 mode is more restrictive than the given
  // mode. SHA1Mode::Forbidden is more restrictive than any other mode except
  // Forbidden. Next is ImportedRoot, then ImportedRootOrBefore2016, then
  // Allowed. (A mode is never more restrictive than itself.)
  bool SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode);
};

mozilla::pkix::Result IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
mozilla::pkix::Result CertListContainsExpectedKeys(
  const CERTCertList* certList, const char* hostname, mozilla::pkix::Time time,
  CertVerifier::PinningMode pinningMode);

} } // namespace mozilla::psm

#endif // CertVerifier_h