/* 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.
 */
//** Includes and Defines
#include "Tpm.h"

#if CC_ECC_Encrypt || CC_ECC_Encrypt

//** Functions

//*** CryptEccSelectScheme()
// This function is used by TPM2_ECC_Decrypt and TPM2_ECC_Encrypt.  It sets scheme
// either the input scheme or the key scheme. If they key scheme is not TPM_ALG_NULL
// then the input scheme must be TPM_ALG_NULL or the same as the key scheme. If
// not, then the function returns FALSE.
//  Return Type: BOOL
//      TRUE        'scheme' is set
//      FALSE       'scheme' is not valid (it may have been changed).
BOOL CryptEccSelectScheme(OBJECT*          key,    //IN: key containing default scheme
                          TPMT_KDF_SCHEME* scheme  // IN: a decrypt scheme
)
{
    TPMT_KDF_SCHEME* keyScheme = &key->publicArea.parameters.eccDetail.kdf;

    // Get sign object pointer
    if(scheme->scheme == TPM_ALG_NULL)
        *scheme = *keyScheme;
    if(keyScheme->scheme == TPM_ALG_NULL)
        keyScheme = scheme;
    return (
        scheme->scheme != TPM_ALG_NULL
        && (keyScheme->scheme == scheme->scheme
            && keyScheme->details.anyKdf.hashAlg == scheme->details.anyKdf.hashAlg));
}

//*** CryptEccEncrypt()
//This function performs ECC-based data obfuscation. The only scheme that is currently
// supported is MGF1 based. See Part 1, Annex D for details.
//  Return Type: TPM_RC
//      TPM_RC_CURVE            unsupported curve
//      TPM_RC_HASH             hash not allowed
//      TPM_RC_SCHEME           'scheme' is not supported
//      TPM_RC_NO_RESULT        internal error in big number processing
LIB_EXPORT TPM_RC CryptEccEncrypt(
    OBJECT*           key,        // IN: public key of recipient
    TPMT_KDF_SCHEME*  scheme,     // IN: scheme to use.
    TPM2B_MAX_BUFFER* plainText,  // IN: the text to obfuscate
    TPMS_ECC_POINT*   c1,         // OUT: public ephemeral key
    TPM2B_MAX_BUFFER* c2,         // OUT: obfuscated text
    TPM2B_DIGEST*     c3          // OUT: digest of ephemeral key
                                  //      and plainText
)
{
    CURVE_INITIALIZED(E, key->publicArea.parameters.eccDetail.curveID);
    POINT_INITIALIZED(PB, &key->publicArea.unique.ecc);
    POINT_VAR(Px, MAX_ECC_KEY_BITS);
    TPMS_ECC_POINT p2;
    ECC_NUM(D);
    TPM2B_TYPE(2ECC, MAX_ECC_KEY_BYTES * 2);
    TPM2B_2ECC z;
    int        i;
    HASH_STATE hashState;
    TPM_RC     retVal = TPM_RC_SUCCESS;
    //
#  if defined DEBUG_ECC_ENCRYPT && DEBUG_ECC_ENCRYPT == YES
    RND_DEBUG dbg;
    // This value is one less than the value from the reference so that it
    // will become the correct value after having one added
    TPM2B_ECC_PARAMETER k = {24, {0x38, 0x4F, 0x30, 0x35, 0x30, 0x73, 0xAE, 0xEC,
                                  0xE7, 0xA1, 0x65, 0x43, 0x30, 0xA9, 0x62, 0x04,
                                  0xD3, 0x79, 0x82, 0xA3, 0xE1, 0x5B, 0x2C, 0xB4}};
    RND_DEBUG_Instantiate(&dbg, &k.b);
#    define RANDOM (RAND_STATE*)&dbg

#  else
#    define RANDOM NULL
#  endif
    if(E == NULL)
        ERROR_RETURN(TPM_RC_CURVE);
    if(TPM_ALG_KDF2 != scheme->scheme)
        ERROR_RETURN(TPM_RC_SCHEME);
    // generate an ephemeral key from a random k
    if(!BnEccGenerateKeyPair(D, Px, E, RANDOM)
       // C1 is the public part of the ephemeral key
       || !BnPointTo2B(c1, Px, E)
       // Compute P2
       || (BnPointMult(Px, PB, D, NULL, NULL, E) != TPM_RC_SUCCESS)
       || !BnPointTo2B(&p2, Px, E))
        ERROR_RETURN(TPM_RC_NO_RESULT);

    //Compute the C3 value hash(x2 || M || y2)
    if(0 == CryptHashStart(&hashState, scheme->details.mgf1.hashAlg))
        ERROR_RETURN(TPM_RC_HASH);
    CryptDigestUpdate2B(&hashState, &p2.x.b);
    CryptDigestUpdate2B(&hashState, &plainText->b);
    CryptDigestUpdate2B(&hashState, &p2.y.b);
    c3->t.size = CryptHashEnd(&hashState, sizeof(c3->t.buffer), c3->t.buffer);

    MemoryCopy2B(&z.b, &p2.x.b, sizeof(z.t.buffer));
    MemoryConcat2B(&z.b, &p2.y.b, sizeof(z.t.buffer));
    // Generate the mask value from MGF1 and put it in the return buffer
    c2->t.size = CryptMGF_KDF(plainText->t.size,
                              c2->t.buffer,
                              scheme->details.mgf1.hashAlg,
                              z.t.size,
                              z.t.buffer,
                              1);
    // XOR the plainText into the generated mask to create the obfuscated data
    for(i = 0; i < plainText->t.size; i++)
        c2->t.buffer[i] ^= plainText->t.buffer[i];
Exit:
    CURVE_FREE(E);
    return retVal;
}

//*** CryptEccDecrypt()
// This function performs ECC decryption and integrity check of the input data.
//  Return Type: TPM_RC
//      TPM_RC_CURVE            unsupported curve
//      TPM_RC_HASH             hash not allowed
//      TPM_RC_SCHEME           'scheme' is not supported
//      TPM_RC_NO_RESULT        internal error in big number processing
//      TPM_RC_VALUE            C3 did not match hash of recovered data
LIB_EXPORT TPM_RC CryptEccDecrypt(
    OBJECT*           key,        // IN: key used for data recovery
    TPMT_KDF_SCHEME*  scheme,     // IN: scheme to use.
    TPM2B_MAX_BUFFER* plainText,  // OUT: the recovered text
    TPMS_ECC_POINT*   c1,         // IN: public ephemeral key
    TPM2B_MAX_BUFFER* c2,         // IN: obfuscated text
    TPM2B_DIGEST*     c3          // IN: digest of ephemeral key
                                  //      and plainText
)
{
    CURVE_INITIALIZED(E, key->publicArea.parameters.eccDetail.curveID);
    ECC_INITIALIZED(D, &key->sensitive.sensitive.ecc.b);
    POINT_INITIALIZED(C1, c1);
    TPMS_ECC_POINT p2;
    TPM2B_TYPE(2ECC, MAX_ECC_KEY_BYTES * 2);
    TPM2B_DIGEST check;
    TPM2B_2ECC   z;
    int          i;
    HASH_STATE   hashState;
    TPM_RC       retVal = TPM_RC_SUCCESS;
    //
    if(E == NULL)
        ERROR_RETURN(TPM_RC_CURVE);
    if(TPM_ALG_KDF2 != scheme->scheme)
        ERROR_RETURN(TPM_RC_SCHEME);
    // Generate the Z value
    BnPointMult(C1, C1, D, NULL, NULL, E);
    BnPointTo2B(&p2, C1, E);

    // Start the hash to check the algorithm
    if(0 == CryptHashStart(&hashState, scheme->details.mgf1.hashAlg))
        ERROR_RETURN(TPM_RC_HASH);
    CryptDigestUpdate2B(&hashState, &p2.x.b);

    MemoryCopy2B(&z.b, &p2.x.b, sizeof(z.t.buffer));
    MemoryConcat2B(&z.b, &p2.y.b, sizeof(z.t.buffer));

    // Generate the mask
    plainText->t.size = CryptMGF_KDF(c2->t.size,
                                     plainText->t.buffer,
                                     scheme->details.mgf1.hashAlg,
                                     z.t.size,
                                     z.t.buffer,
                                     1);
    // XOR the obfuscated data into the generated mask to create the plainText data
    for(i = 0; i < plainText->t.size; i++)
        plainText->t.buffer[i] ^= c2->t.buffer[i];

    // Complete the hash and verify the data
    CryptDigestUpdate2B(&hashState, &plainText->b);
    CryptDigestUpdate2B(&hashState, &p2.y.b);
    check.t.size = CryptHashEnd(&hashState, sizeof(check.t.buffer), check.t.buffer);
    if(!MemoryEqual2B(&check.b, &c3->b))
        ERROR_RETURN(TPM_RC_VALUE);
Exit:
    CURVE_FREE(E);
    return retVal;
}

#endif  // CC_ECC_Encrypt || CC_ECC_Encrypt
