/* ***************************************************************** *
 * Copyright 1998 International Business Machines Corporation. All   *
 * Rights Reserved.                                                  *
 *                                                                   *
 * Please read this carefully.  Your use of this reference           *
 * implementation of certain of the IETF public-key infrastructure   *
 * specifications ("Software") indicates your acceptance of the      *
 * following.  If you do not agree to the following, do not install  *
 * or use any of the Software.                                       *
 *                                                                   *
 * Permission to use, reproduce, distribute and create derivative    *
 * works from the Software ("Software Derivative Works"), and to     *
 * distribute such Software Derivative Works is hereby granted to    *
 * you by International Business Machines Corporation ("IBM").  This *
 * permission includes a license under the patents of IBM that are   *
 * necessarily infringed by your use of the Software as provided by  *
 * IBM.                                                              *
 *                                                                   *
 * IBM licenses the Software to you on an "AS IS" basis, without     *
 * warranty of any kind.  IBM HEREBY EXPRESSLY DISCLAIMS ALL         *
 * WARRANTIES OR CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,   *
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF       *
 * MERCHANTABILITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR    *
 * PURPOSE.  You are solely responsible for determining the          *
 * appropriateness of using this Software and assume all risks       *
 * associated with the use of this Software, including but not       *
 * limited to the risks of program errors, damage to or loss of      *
 * data, programs or equipment, and unavailability or interruption   *
 * of operations.                                                    *
 *                                                                   *
 * IBM WILL NOT BE LIABLE FOR ANY DIRECT DAMAGES OR FOR ANY SPECIAL, *
 * INCIDENTAL, OR  INDIRECT DAMAGES OR FOR ANY ECONOMIC              *
 * CONSEQUENTIAL DAMAGES (INCLUDING LOST PROFITS OR SAVINGS), EVEN   *
 * IF IBM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IBM  *
 * will not be liable for the loss of, or damage to, your records or *
 * data, or any damages claimed by you based on a third party claim. *
 *                                                                   *
 * IBM wishes to obtain your feedback to assist in improving the     *
 * Software.  You grant IBM a world-wide, royalty-free right to use, *
 * copy, distribute, sublicense and prepare derivative works based   *
 * upon any feedback, including materials, error corrections,        *
 * Software Derivatives, enhancements, suggestions and the like that *
 * you provide to IBM relating to the Software (this does not        *
 * include products for which you charge a royalty and distribute to *
 * IBM under other terms and conditions).                            *
 *                                                                   *
 * You agree to distribute the Software and any Software Derivatives *
 * under a license agreement that: 1) is sufficient to notify all    *
 * licensees of the Software and Software Derivatives that IBM       *
 * assumes no liability for any claim that may arise regarding the   *
 * Software or Software Derivatives, and 2) that disclaims all       *
 * warranties, both express and implied, from IBM regarding the      *
 * Software and Software Derivatives.  (If you include this          *
 * Agreement with any distribution of the Software or Software       *
 * Derivatives you will have met this requirement.)  You agree that  *
 * you will not delete any copyright notices in the Software.        *
 *                                                                   *
 * This Agreement is the exclusive statement of your rights in the   *
 * Software as provided by IBM.   Except for the rights granted to   *
 * you in the second paragraph above, You are not granted any other  *
 * patent rights, including but not limited to the right to make     *
 * combinations of the Software with products that infringe IBM      *
 * patents. You agree to comply with all applicable laws and         *
 * regulations, including all export and import laws and regulation. *
 * This Agreement is governed by the laws of the State of New York.  *
 * This Agreement supersedes all other communications,               *
 * understandings or agreements we may have had prior to this        *
 * Agreement.                                                        *
 * ***************************************************************** */

//------------------------------------------------------------
// includes
//------------------------------------------------------------

#include "x509.h"
#include "functiontrace.h"
#include "oidchecker.h"
#include "pkixvalidator.h"


//------------------------------------------------------------
// X.509/PKIX certificate extensions
//------------------------------------------------------------

unsigned long AuthorityKeyIdentifier_val[4]     = {2,5,29,35};
unsigned long SubjectKeyIdentifier_val[4]       = {2,5,29,14};
unsigned long KeyUsage_val[4]                   = {2,5,29,15};
unsigned long PrivateKeyUsagePeriod_val[4]      = {2,5,29,16};
unsigned long CertificatePolicies_val[4]        = {2,5,29,32};
unsigned long PolicyMappings_val[4]             = {2,5,29,33};
unsigned long SubjectAlternativeName_val[4]     = {2,5,29,17};
unsigned long IssuerAlternativeName_val[4]      = {2,5,29,18};
unsigned long BasicConstraints_val[4]           = {2,5,29,19};
unsigned long NameConstraints_val[4]            = {2,5,29,30};
unsigned long PolicyConstraints_val[4]          = {2,5,29,36};
unsigned long CRLDistributionPoints_val[4]      = {2,5,29,31};
unsigned long ExtendedKeyUsage_val[4]           = {2,5,29,37};
unsigned long SubjectDirectoryAttributes_val[4] = {2,5,29, 9};
unsigned long AuthorityInfoAccess_val[9]        = {1,3,6,1,5,5,7,1,1};


//------------------------------------------------------------
// INI file parameters that control TP behavior
//------------------------------------------------------------

extern bool _iniUseCRLs;


// constructor(s)

//------------------------------------------------------------
// method: constructor
//------------------------------------------------------------

pkix_validator::pkix_validator(time_t               currentTime,
                               time_t               validationTime,
                               pkix_certificate*    certificationPath,
                               int                  certificationPathLength,
                               const PolicyState&   initialPolicySet,
                               CSSM_CSP_HANDLE      cspHandle,
                               CSSM_DL_DB_LIST_PTR  DBList)
:  _currentTime(currentTime),
   _validationTime(validationTime),
   _path(certificationPath),
   _pathLength(certificationPathLength),
   _evidence(),
   _currentChainPosition(-1),
   // --- CSSM variables ---
   _cspHandle(cspHandle),
   _DBList(DBList),
   // --- state variables ---
   _permittedNameSet(),
   _excludedNameSet(),
   _userPolicySet(initialPolicySet), 
   _authPolicySet(),
   _explicitPolicy(certificationPathLength+1),
   _policyMappingInhibit(certificationPathLength+1)
{
   TPTRACE(jonahtp_trace_info, "pkix_validator::pkix_validator(time_t,time_t,pkix_certificate*,int,PolicyState&,CSSM_CSP_HANDLE,CSSM_DL_DB_LIST_PTR");
};


// destructor

//------------------------------------------------------------
// method: ~pkix_validator
//------------------------------------------------------------

pkix_validator::~pkix_validator() 
{
   TPTRACE(jonahtp_trace_info, "pkix_validator::~pkix_validator()");
}


//------------------------------------------------------------
// method: validateCertificationPath
//------------------------------------------------------------

void
pkix_validator::validateCertificationPath()
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateCertificationPath()");

   //------------------------------------------------------------
   // walk the path, start at top (from self-signed down to end-entity)
   // Note:
   //    index is an array index in certification path; 
   //    "i" is to sync with PKIX which refers to certs 1..n
   //------------------------------------------------------------

   for (int index = 0, i = 1; index < pathLength(); index++, i++)
   {
      // remember where we are in chain processing

      chainPosition( i );

      // initialize references to subject and issuer certificates

      pkix_certificate& subject = _path[index];
      pkix_certificate& issuer  = i == 1 ? _path[index] : _path[index-1];

      // validate subject/issuer pair

      validateLinkInChain(subject, issuer);
   }
}


//------------------------------------------------------------
// method: validateLinkInChain
//------------------------------------------------------------

void
pkix_validator::validateLinkInChain(pkix_certificate& subject, 
                                    pkix_certificate& issuer)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateLinkInChain(pkix_certificate&,pkix_certificate&)");

   //------------------------------------------------------------
   // (a) Verify the basic certificate information
   //------------------------------------------------------------

   // (1) check signature on ith chain certificate
   //     sanity check: inner and outer signature algorithm 
   //     identifiers must match

   if (chainPosition() != 1)
   {
      validateInnerOuterAlgorithmsAgree(subject.tbsCertificate.signature,
                                        subject.signatureAlgorithm);

      validateCertificateSignature(subject, issuer);
   }

   // (2) check certificate validity period includes time T

   validateValidity(subject);

   // (3) check certificate had not been revoked at time T

   if (_iniUseCRLs)
      validateCertificateNotOnCRL(subject, issuer);

   // (4) check subject and issuer names chain correctly

   validateNameChaining(subject, issuer);

   // (5) check proper use of version/uniqueId/extensions

   validateVersionUsage(subject);

   //------------------------------------------------------------
   // (b)-(c) Verify constrained and excluded subtrees 
   //         state variables.
   //------------------------------------------------------------

   validateNameConstraints(subject);

   enforceNameConstraintsState(subject);

   //------------------------------------------------------------
   // (d)-(g) Verify policy information
   //------------------------------------------------------------

   validateCertificatePolicies(subject);

   enforcePolicyConstraintsState(subject);

   //------------------------------------------------------------
   // following checks not applicable to end-entity
   //------------------------------------------------------------

   if ( chainPosition() != pathLength() )
   {
      //------------------------------------------------------------
      // (h) Recognize and process any other critical extensions
      //     present in certificate
      //------------------------------------------------------------

      validateCertificateExtensions(subject, issuer);

      //------------------------------------------------------------
      // (i) Verify that certificate is a CA (since all certificates
      //     i != n are CAs).
      //------------------------------------------------------------

      validateBasicConstraints(subject);

      //------------------------------------------------------------
      // (j)-(k) Update name constraint state information
      //------------------------------------------------------------

      updateNameConstraintsState(subject);

      //------------------------------------------------------------
      // (l) Update policy constraints state information
      //------------------------------------------------------------

      updatePolicyConstraintsState(subject);

      //------------------------------------------------------------
      // (m) if key usage marked critical, ensure keyCertSign bit set
      //     (this check done in validateCertificateExtensions)
      //------------------------------------------------------------
   
   }   // check for end-of-chain

}


//------------------------------------------------------------
// method: validateInnerOuterAlgorithmsAgree 
//------------------------------------------------------------

void
pkix_validator::validateInnerOuterAlgorithmsAgree(AlgorithmIdentifier& inner, AlgorithmIdentifier& outer)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateInnerOuterAlgorithmsAgree(pkix_certificate&)");

   if ( !(inner == outer) )
      throw TPException(TP_INNER_OUTER_SIGNATURE_ALGORITHM_MISMATCH);
}


//------------------------------------------------------------
// method: validateVersionUsage 
//------------------------------------------------------------

void
pkix_validator::validateVersionUsage(pkix_certificate& subject)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateVersionUsage(pkix_certificate&)");

   int status = 0;

   x509_version_t ver;
   if ((status = subject.tbsCertificate.version.get_value(ver)) != 0) 
      throw TPASNException(status);

   if (X509_V1 == ver)
   {
      if ( subject.isIssuerUniqueIDPresent() || subject.isSubjectUniqueIDPresent() )
         throw TPException(TP_USAGE_UIDS_NOT_ALLOWED_FOR_THIS_VERSION);
      if ( subject.isExtensionsPresent() )
         throw TPException(TP_USAGE_EXTS_NOT_ALLOWED_FOR_THIS_VERSION);
   }
   else if (X509_V2 == ver)
   {
      if ( subject.isExtensionsPresent() )
         throw TPException(TP_USAGE_EXTS_NOT_ALLOWED_FOR_THIS_VERSION);
   }

   // no requirements for V3 certificates to have extensions
}
  

//------------------------------------------------------------
// method: validateCertificateExtensions 
//------------------------------------------------------------

void
pkix_validator::validateCertificateExtensions(pkix_certificate& subject, 
                                              pkix_certificate& issuer)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateCertificateExtensions(pkix_certificate&,pkix_certificate&)");

   int status = 0;

   // no validation to perform if subject has no extensions

   if ( !subject.isExtensionsPresent() )
   {
      return;
   }

   //------------------------------------------------------------
   // > Check for unknown critical extensions
   // > Ignore unknown, non-critical extensions
   // > Process remaining PKIX extensions not validated elsewhere
   // > Watch out for V1, V2 certificates that don't have extensions
   //------------------------------------------------------------
   
   // iterate over extensions

   OIDChecker duplicate_oids;

   int children = subject.tbsCertificate.extensions.value.get_child_count();
   for (int i = 0; i < children ; i++)
   {
      bool isCritical;

      x509_Extension* child = subject.tbsCertificate.extensions.value[i];
      if ((status = child->critical.get_value(isCritical)) != 0)
         throw TPASNException(status);

      //------------------------------------------------------------
      // check only one instance of any extension apppears
      //------------------------------------------------------------

      if ( !duplicate_oids.insert(child->extnID) )
         throw TPException(TP_EXTS_MULTIPLE_EXTS);

      if (child->extnID.is_equal(AuthorityKeyIdentifier_val,4))
      {
         XAuthorityKeyIdentifier& authorityKeyIdentifier = subject.authorityKeyIdentifier();
         validateAuthorityKeyIdentifier(authorityKeyIdentifier, isCritical, issuer);
      }
      else if (child->extnID.is_equal(SubjectKeyIdentifier_val, 4))
      {
         validateSubjectKeyIdentifier(subject);
      }
      else if (child->extnID.is_equal(KeyUsage_val, 4))
      {
         validateKeyUsage(subject);
      }
      else if (child->extnID.is_equal(IssuerAlternativeName_val, 4))
      {
         XIssuerAltName& issuerAltName = subject.issuerAltName();
         validateIssuerAltName(issuerAltName, isCritical, issuer);
      }
      else if 
         ( 
            child->extnID.is_equal(CertificatePolicies_val,4)    ||
            child->extnID.is_equal(SubjectAlternativeName_val,4) ||
            child->extnID.is_equal(BasicConstraints_val,4)       ||
            child->extnID.is_equal(NameConstraints_val,4)        ||
            child->extnID.is_equal(PolicyConstraints_val,4)
         )
      {
         ;  // These extensions are validated elsewhere
      }
      else if
         (
           !isCritical && 
           (
              child->extnID.is_equal(PolicyMappings_val,4)             ||
              child->extnID.is_equal(CRLDistributionPoints_val,4)      ||
              child->extnID.is_equal(PrivateKeyUsagePeriod_val,4)      ||
              child->extnID.is_equal(ExtendedKeyUsage_val,4)           ||
              child->extnID.is_equal(SubjectDirectoryAttributes_val,4) ||
              child->extnID.is_equal(AuthorityInfoAccess_val,9)        
           )
         )
      {
         ;   // unsupported PKIX extensions; we can safely ignore if
             // if not critical; may be supported after August
      }  
      else if (isCritical)
      {
         throw TPException(TP_EXTS_UNKNOWN_CRITICAL_EXT);
      }
   }
}


//------------------------------------------------------------
// method: validateIssuerAltName
//------------------------------------------------------------

void
pkix_validator::validateIssuerAltName(XIssuerAltName& issuerAltName,
                                      bool isCritical,
                                      pkix_certificate& issuer)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateIssuerAltName(XIssuerAltName&,bool,pkix_certificate&)");
   // write me
}


//------------------------------------------------------------
// method: validateAuthorityKeyIdentifier
//------------------------------------------------------------

void
pkix_validator::validateAuthorityKeyIdentifier(XAuthorityKeyIdentifier& authorityKeyIdentifier,
                                               bool isCritical,
                                               pkix_certificate& issuer)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateAuthorityKeyIdentifier(pkix_certificate&,bool,pkix_certificate&)");

   //------------------------------------------------------------
   // ASSUMPTIONS:
   // (1) PKIX allows V1 and V2 certificates, so we have no 
   //     requirement for any extension to be present.
   // (2) We will validate only the keyIdentifier field against
   //    the issuer's SubjectKeyIdentifier, which is what PKIX 
   //    recommends you use anyway.  This extension should be 
   //    non-critical; if critical, use of alternative fields 
   //    (authorityIdentifier, authorityCertSerialNumber) is an error.
   // TO DO:
   //    Add support for alternative fields.
   //------------------------------------------------------------

   if ( authorityKeyIdentifier.keyIdentifier.is_present() )
   {
      // check issuer's SubjectKeyIdentifier
      if ( issuer.isSubjectKeyIdentifierPresent() )
      {
         XSubjectKeyIdentifier& subjectKeyIdentifier = issuer.subjectKeyIdentifier();
         if (authorityKeyIdentifier.keyIdentifier == subjectKeyIdentifier) 
            throw TPException(TP_AKID_KEYIDENTIFIER_VALIDATION_FAILED);
      }
      else
      {
         // we need issuer to have corresponding SubjectKeyIdentifier
         throw TPException(TP_AKID_ISSUER_MISSING_SKID);
      }
   }
   else if ( isCritical )
   {
      // critical extension with fields we do not validate
      throw TPException(TP_AKID_MISSING_KEYIDENTIFIER_FIELD);
   }

}


//------------------------------------------------------------
// method: validateSubjectKeyIdentifier
//------------------------------------------------------------

void
pkix_validator::validateSubjectKeyIdentifier(pkix_certificate& subject)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateSubjectKeyIdentifier(pkix_certificate&)");

   //------------------------------------------------------------
   // ASSUMPTIONS:
   // (1) PKIX allows V1 and V2 certificates, so we have no 
   //     requirement for any extension to be present.
   // (2) PKIX says shall not be critical if present
   //------------------------------------------------------------

   if ( !subject.isSubjectKeyIdentifierPresent() )
      return;

   if ( subject.isSubjectKeyIdentifierCritical() )
      throw TPException(TP_SKID_SHALL_NOT_BE_CRITICAL);

}


//------------------------------------------------------------
// method: validateBasicConstraints 
//------------------------------------------------------------

void
pkix_validator::validateBasicConstraints(pkix_certificate& subject)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateBasicConstraints(pkix_certificate&)");

   int status;

   //------------------------------------------------------------
   // ASSUMPTIONS:
   // (1) PKIX allows V1 and V2 certificates, so we have no 
   //     requirement for any extension to be present.
   // (2) Subject is a CA.
   // (3) PKIX requires extension to be critical if present.
   //------------------------------------------------------------

   if ( !subject.isBasicConstraintsPresent() )
      return;

   if ( subject.isBasicConstraintsPresent() && !subject.isBasicConstraintsCritical() )
      throw TPException(TP_BASIC_CONSTRAINTS_MUST_BE_CRITICAL);

   XBasicConstraints& basicConstraints = subject.basicConstraints();

   // how many certificates are under us for current certification path?
   // (-1 removes the end-entity at end of chain from count)

   int numOfCAsUnderSubject = pathLength() - chainPosition() - 1;

   bool isCA;
   if ((status = basicConstraints.cA.get_value(isCA)) != 0)
      throw TPASNException(status);
   if ( !isCA )
      throw TPException(TP_BASIC_CONSTRAINTS_NOT_CA);

   if ( basicConstraints.pathLenConstraints.is_present() )
   {
      long pathLen;
      if ((status = basicConstraints.pathLenConstraints.get_value(pathLen)) != 0)
         throw TPASNException(status);
      if ( pathLen < 0 || numOfCAsUnderSubject > pathLen )
         throw TPException(TP_BASIC_CONSTRAINTS_PATHLENGTH);
   }
}


//------------------------------------------------------------
// method: validateKeyUsage 
//------------------------------------------------------------

void
pkix_validator::validateKeyUsage(pkix_certificate& subject)
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::validateKeyUsage(pkix_certificate&)");

   //------------------------------------------------------------
   // ASSUMPTIONS:
   // (1) PKIX allows V1 and V2 certificates, so we have no 
   //     requirement for any extension to be present.
   // (2) Subject is a CA.
   // (3) PKIX requires extension to be critical if present.
   //------------------------------------------------------------

   int status;

   if ( !subject.isKeyUsagePresent() )
      return;

   if ( !subject.isKeyUsageCritical() )
      throw TPException(TP_KEY_USAGE_MUST_BE_CRITICAL);

   XKeyUsage& keyUsage = subject.keyUsage();

   long usage;
   if ((status = keyUsage.get_value(usage)) != 0)
      throw TPASNException(status);
   if ( ! (usage & USAGE_keyCertSign) )
      throw TPException(TP_KEY_USAGE_CERTSIGN_NOT_SET);

}


// helpers

//------------------------------------------------------------
// method: pathLength 
//------------------------------------------------------------

int
pkix_validator::pathLength() const
{ 
   TPTRACE(jonahtp_trace_info, "int pkix_validator::pathLength()");
   return _pathLength; 
} 


//------------------------------------------------------------
// method: chainPosition 
//------------------------------------------------------------

int  
pkix_validator::chainPosition() const
{ 
   TPTRACE(jonahtp_trace_info, "int pkix_validator::chainPosition()");
   return _currentChainPosition; 
} 


//------------------------------------------------------------
// method: chainPosition 
//------------------------------------------------------------

void 
pkix_validator::chainPosition(int i)  
{
   TPTRACE(jonahtp_trace_info, "void pkix_validator::chainPosition(int)");
   _currentChainPosition = i; 
}


//------------------------------------------------------------
// method: getEvidence
//------------------------------------------------------------

PKIXEvidence 
pkix_validator::getEvidence()
{
   // retrieve evidenced gathered during validation
   return _evidence; 
}
