/* Microsoft Reference Implementation for TPM 2.0
 *
 *  The copyright in this software is being made available under the BSD License,
 *  included below. This software may be subject to other third party and
 *  contributor rights, including patent rights, and no such rights are granted
 *  under this license.
 *
 *  Copyright (c) Microsoft Corporation
 *
 *  All rights reserved.
 *
 *  BSD License
 *
 *  Redistribution and use in source and binary forms, with or without modification,
 *  are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this list
 *  of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice, this
 *  list of conditions and the following disclaimer in the documentation and/or
 *  other materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS""
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 *  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "Tpm.h"
#include "CertifyX509_fp.h"
#include "X509.h"
#include "TpmASN1_fp.h"
#include "X509_spt_fp.h"
#include "Attest_spt_fp.h"
#if CERTIFYX509_DEBUG
#  include "Platform_fp.h"
#endif

#if CC_CertifyX509  // Conditional expansion of this file

/*(See part 3 specification)
// Certify using an X509-formatted certificate
*/
// return type: TPM_RC
//      TPM_RC_ATTRIBUTES       the attributes of 'objectHandle' are not compatible
//                              with the KeyUsage or TPMA_OBJECT values in the
//                              extensions fields
//      TPM_RC_BINDING          the public and private portions of the key are not
//                              properly bound.
//      TPM_RC_HASH             the hash algorithm in the scheme is not supported
//      TPM_RC_KEY              'signHandle' does not reference a signing key;
//      TPM_RC_SCHEME           the scheme is not compatible with sign key type,
//                              or input scheme is not compatible with default
//                              scheme, or the chosen scheme is not a valid
//                              sign scheme
//      TPM_RC_VALUE            most likely a problem with the format of
//                              'partialCertificate'
TPM_RC
TPM2_CertifyX509(CertifyX509_In*  in,  // IN: input parameter list
                 CertifyX509_Out* out  // OUT: output parameter list
)
{
    TPM_RC               result;
    OBJECT*              signKey = HandleToObject(in->signHandle);
    OBJECT*              object  = HandleToObject(in->objectHandle);
    HASH_STATE           hash;
    INT16                length;  // length for a tagged element
    ASN1UnmarshalContext ctx;
    ASN1MarshalContext   ctxOut;
    // certTBS holds an array of pointers and lengths. Each entry references the
    // corresponding value in a TBSCertificate structure. For example, the 1th
    // element references the version number
    stringRef certTBS[REF_COUNT] = {{0}};
#  define ALLOWED_SEQUENCES (SUBJECT_PUBLIC_KEY_REF - SIGNATURE_REF)
    stringRef partial[ALLOWED_SEQUENCES] = {{0}};
    INT16     countOfSequences           = 0;
    INT16     i;
    //
#  if CERTIFYX509_DEBUG
    DebugFileInit();
    DebugDumpBuffer(in->partialCertificate.t.size,
                    in->partialCertificate.t.buffer,
                    "partialCertificate");
#  endif

    // Input Validation
    if(in->reserved.b.size != 0)
        return TPM_RC_SIZE + RC_CertifyX509_reserved;
    // signing key must be able to sign
    if(!IsSigningObject(signKey))
        return TPM_RCS_KEY + RC_CertifyX509_signHandle;
    // Pick a scheme for sign.  If the input sign scheme is not compatible with
    // the default scheme, return an error.
    if(!CryptSelectSignScheme(signKey, &in->inScheme))
        return TPM_RCS_SCHEME + RC_CertifyX509_inScheme;
    // Make sure that the public Key encoding is known
    if(X509AddPublicKey(NULL, object) == 0)
        return TPM_RCS_ASYMMETRIC + RC_CertifyX509_objectHandle;
    // Unbundle 'partialCertificate'.
    // Initialize the unmarshaling context
    if(!ASN1UnmarshalContextInitialize(
           &ctx, in->partialCertificate.t.size, in->partialCertificate.t.buffer))
        return TPM_RCS_VALUE + RC_CertifyX509_partialCertificate;
    // Make sure that this is a constructed SEQUENCE
    length = ASN1NextTag(&ctx);
    // Must be a constructed SEQUENCE that uses all of the input parameter
    if((ctx.tag != (ASN1_CONSTRUCTED_SEQUENCE))
       || ((ctx.offset + length) != in->partialCertificate.t.size))
        return TPM_RCS_SIZE + RC_CertifyX509_partialCertificate;

    // This scans through the contents of the outermost SEQUENCE. This would be the
    // 'issuer', 'validity', 'subject', 'issuerUniqueID' (optional),
    // 'subjectUniqueID' (optional), and 'extensions.'
    while(ctx.offset < ctx.size)
    {
        INT16 startOfElement = ctx.offset;
        //
        // Read the next tag and length field.
        length = ASN1NextTag(&ctx);
        if(length < 0)
            break;
        if(ctx.tag == ASN1_CONSTRUCTED_SEQUENCE)
        {
            partial[countOfSequences].buf = &ctx.buffer[startOfElement];
            ctx.offset += length;
            partial[countOfSequences].len = (INT16)ctx.offset - startOfElement;
            if(++countOfSequences > ALLOWED_SEQUENCES)
                break;
        }
        else if(ctx.tag == X509_EXTENSIONS)
        {
            if(certTBS[EXTENSIONS_REF].len != 0)
                return TPM_RCS_VALUE + RC_CertifyX509_partialCertificate;
            certTBS[EXTENSIONS_REF].buf = &ctx.buffer[startOfElement];
            ctx.offset += length;
            certTBS[EXTENSIONS_REF].len = (INT16)ctx.offset - startOfElement;
        }
        else
            return TPM_RCS_VALUE + RC_CertifyX509_partialCertificate;
    }
    // Make sure that we used all of the data and found at least the required
    // number of elements.
    if((ctx.offset != ctx.size) || (countOfSequences < 3) || (countOfSequences > 4)
       || (certTBS[EXTENSIONS_REF].buf == NULL))
        return TPM_RCS_VALUE + RC_CertifyX509_partialCertificate;
    // Now that we know how many sequences there were, we can put them where they
    // belong
    for(i = 0; i < countOfSequences; i++)
        certTBS[SUBJECT_KEY_REF - i] = partial[countOfSequences - 1 - i];

    // If only three SEQUENCES, then the TPM needs to produce the signature algorithm.
    // See if it can
    if((countOfSequences == 3)
       && (X509AddSigningAlgorithm(NULL, signKey, &in->inScheme) == 0))
        return TPM_RCS_SCHEME + RC_CertifyX509_signHandle;

    // Process the extensions
    result = X509ProcessExtensions(object, &certTBS[EXTENSIONS_REF]);
    if(result != TPM_RC_SUCCESS)
        // If the extension has the TPMA_OBJECT extension and the attributes don't
        // match, then the error code will be TPM_RCS_ATTRIBUTES. Otherwise, the error
        // indicates a malformed partialCertificate.
        return result
               + ((result == TPM_RCS_ATTRIBUTES) ? RC_CertifyX509_objectHandle
                                                 : RC_CertifyX509_partialCertificate);
    // Command Output
    // Create the addedToCertificate values

    // Build the addedToCertificate from the bottom up.
    // Initialize the context structure
    ASN1InitialializeMarshalContext(&ctxOut,
                                    sizeof(out->addedToCertificate.t.buffer),
                                    out->addedToCertificate.t.buffer);
    // Place a marker for the overall context
    ASN1StartMarshalContext(&ctxOut);  // SEQUENCE for addedToCertificate

    // Add the subject public key descriptor
    certTBS[SUBJECT_PUBLIC_KEY_REF].len = X509AddPublicKey(&ctxOut, object);
    certTBS[SUBJECT_PUBLIC_KEY_REF].buf = ctxOut.buffer + ctxOut.offset;
    // If the caller didn't provide the algorithm identifier, create it
    if(certTBS[SIGNATURE_REF].len == 0)
    {
        certTBS[SIGNATURE_REF].len =
            X509AddSigningAlgorithm(&ctxOut, signKey, &in->inScheme);
        certTBS[SIGNATURE_REF].buf = ctxOut.buffer + ctxOut.offset;
    }
    // Create the serial number value. Use the out->tbsDigest as scratch.
    {
        TPM2B* digest = &out->tbsDigest.b;
        //
        digest->size = (INT16)CryptHashStart(&hash, signKey->publicArea.nameAlg);
        pAssert(digest->size != 0);

        // The serial number size is the smaller of the digest and the vendor-defined
        // value
        digest->size = MIN(digest->size, SIZE_OF_X509_SERIAL_NUMBER);
        // Add all the parts of the certificate other than the serial number
        // and version number
        for(i = SIGNATURE_REF; i < REF_COUNT; i++)
            CryptDigestUpdate(&hash, certTBS[i].len, certTBS[i].buf);
        // throw in the Name of the signing key...
        CryptDigestUpdate2B(&hash, &signKey->name.b);
        // ...and the Name of the signed key.
        CryptDigestUpdate2B(&hash, &object->name.b);
        // Done
        CryptHashEnd2B(&hash, digest);
    }

    // Add the serial number
    certTBS[SERIAL_NUMBER_REF].len =
        ASN1PushInteger(&ctxOut, out->tbsDigest.t.size, out->tbsDigest.t.buffer);
    certTBS[SERIAL_NUMBER_REF].buf = ctxOut.buffer + ctxOut.offset;

    // Add the static version number
    ASN1StartMarshalContext(&ctxOut);
    ASN1PushUINT(&ctxOut, 2);
    certTBS[VERSION_REF].len =
        ASN1EndEncapsulation(&ctxOut, ASN1_APPLICAIION_SPECIFIC);
    certTBS[VERSION_REF].buf = ctxOut.buffer + ctxOut.offset;

    // Create a fake tag and length for the TBS in the space used for
    // 'addedToCertificate'
    {
        for(length = 0, i = 0; i < REF_COUNT; i++)
            length += certTBS[i].len;
        // Put a fake tag and length into the buffer for use in the tbsDigest
        certTBS[ENCODED_SIZE_REF].len =
            ASN1PushTagAndLength(&ctxOut, ASN1_CONSTRUCTED_SEQUENCE, length);
        certTBS[ENCODED_SIZE_REF].buf = ctxOut.buffer + ctxOut.offset;
        // Restore the buffer pointer to add back the number of octets used for the
        // tag and length
        ctxOut.offset += certTBS[ENCODED_SIZE_REF].len;
    }
    // sanity check
    if(ctxOut.offset < 0)
        return TPM_RC_FAILURE;
    // Create the tbsDigest to sign
    out->tbsDigest.t.size = CryptHashStart(&hash, in->inScheme.details.any.hashAlg);
    for(i = 0; i < REF_COUNT; i++)
        CryptDigestUpdate(&hash, certTBS[i].len, certTBS[i].buf);
    CryptHashEnd2B(&hash, &out->tbsDigest.b);

#  if CERTIFYX509_DEBUG
    {
        BYTE  fullTBS[4096];
        BYTE* fill = fullTBS;
        int   j;
        for(j = 0; j < REF_COUNT; j++)
        {
            MemoryCopy(fill, certTBS[j].buf, certTBS[j].len);
            fill += certTBS[j].len;
        }
        DebugDumpBuffer((int)(fill - &fullTBS[0]), fullTBS, "\nfull TBS");
    }
#  endif

    // Finish up the processing of addedToCertificate
    // Create the actual tag and length for the addedToCertificate structure
    out->addedToCertificate.t.size =
        ASN1EndEncapsulation(&ctxOut, ASN1_CONSTRUCTED_SEQUENCE);
    // Now move all the addedToContext to the start of the buffer
    MemoryCopy(out->addedToCertificate.t.buffer,
               ctxOut.buffer + ctxOut.offset,
               out->addedToCertificate.t.size);
#  if CERTIFYX509_DEBUG
    DebugDumpBuffer(out->addedToCertificate.t.size,
                    out->addedToCertificate.t.buffer,
                    "\naddedToCertificate");
#  endif
    // only thing missing is the signature
    result = CryptSign(signKey, &in->inScheme, &out->tbsDigest, &out->signature);

    return result;
}

#endif  // CC_CertifyX509
