/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "pkix/pkixutil.h"

namespace mozilla { namespace pkix {

Result
BackCert::Init()
{
  Result rv;

  // Certificate  ::=  SEQUENCE  {
  //         tbsCertificate       TBSCertificate,
  //         signatureAlgorithm   AlgorithmIdentifier,
  //         signatureValue       BIT STRING  }

  Reader tbsCertificate;

  // The scope of |input| and |certificate| are limited to this block so we
  // don't accidentally confuse them for tbsCertificate later.
  {
    Reader certificate;
    rv = der::ExpectTagAndGetValueAtEnd(der, der::SEQUENCE, certificate);
    if (rv != Success) {
      return rv;
    }
    rv = der::SignedData(certificate, tbsCertificate, signedData);
    if (rv != Success) {
      return rv;
    }
    rv = der::End(certificate);
    if (rv != Success) {
      return rv;
    }
  }

  // TBSCertificate  ::=  SEQUENCE  {
  //      version         [0]  EXPLICIT Version DEFAULT v1,
  //      serialNumber         CertificateSerialNumber,
  //      signature            AlgorithmIdentifier,
  //      issuer               Name,
  //      validity             Validity,
  //      subject              Name,
  //      subjectPublicKeyInfo SubjectPublicKeyInfo,
  //      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
  //                           -- If present, version MUST be v2 or v3
  //      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
  //                           -- If present, version MUST be v2 or v3
  //      extensions      [3]  EXPLICIT Extensions OPTIONAL
  //                           -- If present, version MUST be v3
  //      }
  rv = der::OptionalVersion(tbsCertificate, version);
  if (rv != Success) {
    return rv;
  }
  rv = der::CertificateSerialNumber(tbsCertificate, serialNumber);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, signature);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, issuer);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, validity);
  if (rv != Success) {
    return rv;
  }
  // TODO(bug XXXXXXX): We rely on the the caller of mozilla::pkix to validate
  // that the name is syntactically valid, if they care. In Gecko we do this
  // implicitly by parsing the certificate into a CERTCertificate object.
  // Instead of relying on the caller to do this, we should do it ourselves.
  rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, subject);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE,
                               subjectPublicKeyInfo);
  if (rv != Success) {
    return rv;
  }

  // According to RFC 5280, all fields below this line are forbidden for
  // certificate versions less than v3.  However, for compatibility reasons,
  // we parse v1/v2 certificates in the same way as v3 certificates.  So if
  // these fields appear in a v1 certificate, they will be used.

  // Ignore issuerUniqueID if present.
  rv = der::SkipOptionalImplicitPrimitiveTag(tbsCertificate, 1);
  if (rv != Success) {
    return rv;
  }

  // Ignore subjectUniqueID if present.
  rv = der::SkipOptionalImplicitPrimitiveTag(tbsCertificate, 2);
  if (rv != Success) {
    return rv;
  }

  static const uint8_t CSC = der::CONTEXT_SPECIFIC | der::CONSTRUCTED;
  rv = der::OptionalExtensions(
         tbsCertificate, CSC | 3,
         [this](Reader& extnID, const Input& extnValue, bool critical,
                /*out*/ bool& understood) {
           return RememberExtension(extnID, extnValue, critical, understood);
         });
  if (rv != Success) {
    return rv;
  }

  // The Netscape Certificate Type extension is an obsolete
  // Netscape-proprietary mechanism that we ignore in favor of the standard
  // extensions. However, some CAs have issued certificates with the Netscape
  // Cert Type extension marked critical. Thus, for compatibility reasons, we
  // "understand" this extension by ignoring it when it is not critical, and
  // by ensuring that the equivalent standardized extensions are present when
  // it is marked critical, based on the assumption that the information in
  // the Netscape Cert Type extension is consistent with the information in
  // the standard extensions.
  //
  // Here is a mapping between the Netscape Cert Type extension and the
  // standard extensions:
  //
  // Netscape Cert Type  |  BasicConstraints.cA  |  Extended Key Usage
  // --------------------+-----------------------+----------------------
  // SSL Server          |  false                |  id_kp_serverAuth
  // SSL Client          |  false                |  id_kp_clientAuth
  // S/MIME Client       |  false                |  id_kp_emailProtection
  // Object Signing      |  false                |  id_kp_codeSigning
  // SSL Server CA       |  true                 |  id_kp_serverAuth
  // SSL Client CA       |  true                 |  id_kp_clientAuth
  // S/MIME CA           |  true                 |  id_kp_emailProtection
  // Object Signing CA   |  true                 |  id_kp_codeSigning
  if (criticalNetscapeCertificateType.GetLength() > 0 &&
      (basicConstraints.GetLength() == 0 || extKeyUsage.GetLength() == 0)) {
    return Result::ERROR_UNKNOWN_CRITICAL_EXTENSION;
  }

  return der::End(tbsCertificate);
}

Result
BackCert::RememberExtension(Reader& extnID, Input extnValue,
                            bool critical, /*out*/ bool& understood)
{
  understood = false;

  // python DottedOIDToCode.py id-ce-keyUsage 2.5.29.15
  static const uint8_t id_ce_keyUsage[] = {
    0x55, 0x1d, 0x0f
  };
  // python DottedOIDToCode.py id-ce-subjectAltName 2.5.29.17
  static const uint8_t id_ce_subjectAltName[] = {
    0x55, 0x1d, 0x11
  };
  // python DottedOIDToCode.py id-ce-basicConstraints 2.5.29.19
  static const uint8_t id_ce_basicConstraints[] = {
    0x55, 0x1d, 0x13
  };
  // python DottedOIDToCode.py id-ce-nameConstraints 2.5.29.30
  static const uint8_t id_ce_nameConstraints[] = {
    0x55, 0x1d, 0x1e
  };
  // python DottedOIDToCode.py id-ce-certificatePolicies 2.5.29.32
  static const uint8_t id_ce_certificatePolicies[] = {
    0x55, 0x1d, 0x20
  };
  // python DottedOIDToCode.py id-ce-policyConstraints 2.5.29.36
  static const uint8_t id_ce_policyConstraints[] = {
    0x55, 0x1d, 0x24
  };
  // python DottedOIDToCode.py id-ce-extKeyUsage 2.5.29.37
  static const uint8_t id_ce_extKeyUsage[] = {
    0x55, 0x1d, 0x25
  };
  // python DottedOIDToCode.py id-ce-inhibitAnyPolicy 2.5.29.54
  static const uint8_t id_ce_inhibitAnyPolicy[] = {
    0x55, 0x1d, 0x36
  };
  // python DottedOIDToCode.py id-pe-authorityInfoAccess 1.3.6.1.5.5.7.1.1
  static const uint8_t id_pe_authorityInfoAccess[] = {
    0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
  };
  // python DottedOIDToCode.py id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5
  static const uint8_t id_pkix_ocsp_nocheck[] = {
    0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05
  };
  // python DottedOIDToCode.py Netscape-certificate-type 2.16.840.1.113730.1.1
  static const uint8_t Netscape_certificate_type[] = {
    0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01
  };
  // python DottedOIDToCode.py id-pe-tlsfeature 1.3.6.1.5.5.7.1.24
  static const uint8_t id_pe_tlsfeature[] = {
    0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18
  };
  // python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
  // See Section 3.3 of RFC 6962.
  static const uint8_t id_embeddedSctList[] = {
    0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
  };

  Input* out = nullptr;

  // We already enforce the maximum possible constraints for policies so we
  // can safely ignore even critical policy constraint extensions.
  //
  // XXX: Doing it this way won't allow us to detect duplicate
  // policyConstraints extensions, but that's OK because (and only because) we
  // ignore the extension.
  Input dummyPolicyConstraints;

  // We don't need to save the contents of this extension if it is present. We
  // just need to handle its presence (it is essentially ignored right now).
  Input dummyOCSPNocheck;

  // For compatibility reasons, for some extensions we have to allow empty
  // extension values. This would normally interfere with our duplicate
  // extension checking code. However, as long as the extensions we allow to
  // have empty values are also the ones we implicitly allow duplicates of,
  // this will work fine.
  bool emptyValueAllowed = false;

  // RFC says "Conforming CAs MUST mark this extension as non-critical" for
  // both authorityKeyIdentifier and subjectKeyIdentifier, and we do not use
  // them for anything, so we totally ignore them here.

  if (extnID.MatchRest(id_ce_keyUsage)) {
    out = &keyUsage;
  } else if (extnID.MatchRest(id_ce_subjectAltName)) {
    out = &subjectAltName;
  } else if (extnID.MatchRest(id_ce_basicConstraints)) {
    out = &basicConstraints;
  } else if (extnID.MatchRest(id_ce_nameConstraints)) {
    out = &nameConstraints;
  } else if (extnID.MatchRest(id_ce_certificatePolicies)) {
    out = &certificatePolicies;
  } else if (extnID.MatchRest(id_ce_policyConstraints)) {
    out = &dummyPolicyConstraints;
  } else if (extnID.MatchRest(id_ce_extKeyUsage)) {
    out = &extKeyUsage;
  } else if (extnID.MatchRest(id_ce_inhibitAnyPolicy)) {
    out = &inhibitAnyPolicy;
  } else if (extnID.MatchRest(id_pe_authorityInfoAccess)) {
    out = &authorityInfoAccess;
  } else if (extnID.MatchRest(id_pe_tlsfeature)) {
    out = &requiredTLSFeatures;
  } else if (extnID.MatchRest(id_embeddedSctList)) {
    out = &signedCertificateTimestamps;
  } else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) {
    // We need to make sure we don't reject delegated OCSP response signing
    // certificates that contain the id-pkix-ocsp-nocheck extension marked as
    // critical when validating OCSP responses. Without this, an application
    // that implements soft-fail OCSP might ignore a valid Revoked or Unknown
    // response, and an application that implements hard-fail OCSP might fail
    // to connect to a server given a valid Good response.
    out = &dummyOCSPNocheck;
    // We allow this extension to have an empty value.
    // See http://comments.gmane.org/gmane.ietf.x509/30947
    emptyValueAllowed = true;
  } else if (extnID.MatchRest(Netscape_certificate_type) && critical) {
    out = &criticalNetscapeCertificateType;
  }

  if (out) {
    // Don't allow an empty value for any extension we understand. This way, we
    // can test out->GetLength() != 0 or out->Init() to check for duplicates.
    if (extnValue.GetLength() == 0 && !emptyValueAllowed) {
      return Result::ERROR_EXTENSION_VALUE_INVALID;
    }
    if (out->Init(extnValue) != Success) {
      // Duplicate extension
      return Result::ERROR_EXTENSION_VALUE_INVALID;
    }
    understood = true;
  }

  return Success;
}

Result
ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
                                                   Input& sctList)
{
  Reader decodedValue;
  Result rv = der::ExpectTagAndGetValueAtEnd(extnValue, der::OCTET_STRING,
                                             decodedValue);
  if (rv != Success) {
    return rv;
  }
  return decodedValue.SkipToEnd(sctList);
}

} } // namespace mozilla::pkix
