// This file was extracted from the TCG Published
// Trusted Platform Module Library
// Part 3: Commands
// Family "2.0"
// Level 00 Revision 01.16
// October 30, 2014

#include "InternalRoutines.h"
#include "Duplicate_fp.h"
#include "Object_spt_fp.h"
//
//
//     Error Returns                Meaning
//
//     TPM_RC_ATTRIBUTES            key to duplicate has fixedParent SET
//     TPM_RC_HIERARCHY             encryptedDuplication is SET and newParentHandle specifies Null
//                                  Hierarchy
//     TPM_RC_KEY                   newParentHandle references invalid ECC key (public point not on the
//                                  curve)
//     TPM_RC_SIZE                  input encryption key size does not match the size specified in
//                                  symmetric algorithm
//     TPM_RC_SYMMETRIC             encryptedDuplication is SET but no symmetric algorithm is provided
//     TPM_RC_TYPE                  newParentHandle is neither a storage key nor TPM_RH_NULL; or
//                                  the object has a NULL nameAlg
//
TPM_RC
TPM2_Duplicate(
   Duplicate_In      *in,            // IN: input parameter list
   Duplicate_Out     *out            // OUT: output parameter list
   )
{
   TPM_RC                   result = TPM_RC_SUCCESS;
   TPMT_SENSITIVE           sensitive;

   UINT16                   innerKeySize = 0; // encrypt key size for inner wrap

   OBJECT                   *object;
   TPM2B_DATA               data;

// Input Validation

   // Get duplicate object pointer
   object = ObjectGet(in->objectHandle);

   // duplicate key must have fixParent bit CLEAR.
   if(object->publicArea.objectAttributes.fixedParent == SET)
       return TPM_RC_ATTRIBUTES + RC_Duplicate_objectHandle;

   // Do not duplicate object with NULL nameAlg
   if(object->publicArea.nameAlg == TPM_ALG_NULL)
       return TPM_RC_TYPE + RC_Duplicate_objectHandle;

   // new parent key must be a storage object or TPM_RH_NULL
   if(in->newParentHandle != TPM_RH_NULL
           && !ObjectIsStorage(in->newParentHandle))
       return TPM_RC_TYPE + RC_Duplicate_newParentHandle;

   // If the duplicates object has encryptedDuplication SET, then there must be
   // an inner wrapper and the new parent may not be TPM_RH_NULL
   if(object->publicArea.objectAttributes.encryptedDuplication == SET)
   {
       if(in->symmetricAlg.algorithm == TPM_ALG_NULL)
           return TPM_RC_SYMMETRIC + RC_Duplicate_symmetricAlg;
       if(in->newParentHandle == TPM_RH_NULL)
            return TPM_RC_HIERARCHY + RC_Duplicate_newParentHandle;
   }

   if(in->symmetricAlg.algorithm == TPM_ALG_NULL)
   {
       // if algorithm is TPM_ALG_NULL, input key size must be 0
       if(in->encryptionKeyIn.t.size != 0)
           return TPM_RC_SIZE + RC_Duplicate_encryptionKeyIn;
   }
   else
   {
       // Get inner wrap key size
       innerKeySize = in->symmetricAlg.keyBits.sym;

       // If provided the input symmetric key must match the size of the algorithm
       if(in->encryptionKeyIn.t.size != 0
               && in->encryptionKeyIn.t.size != (innerKeySize + 7) / 8)
           return TPM_RC_SIZE + RC_Duplicate_encryptionKeyIn;
   }

// Command Output

   if(in->newParentHandle != TPM_RH_NULL)
   {

       // Make encrypt key and its associated secret structure. A TPM_RC_KEY
       // error may be returned at this point
       out->outSymSeed.t.size = sizeof(out->outSymSeed.t.secret);
       result = CryptSecretEncrypt(in->newParentHandle,
                                   "DUPLICATE", &data, &out->outSymSeed);
       pAssert(result != TPM_RC_VALUE);
       if(result != TPM_RC_SUCCESS)
           return result;
   }
   else
   {
       // Do not apply outer wrapper
       data.t.size = 0;
       out->outSymSeed.t.size = 0;
   }

   // Copy sensitive area
   sensitive = object->sensitive;

   // Prepare output private data from sensitive
   SensitiveToDuplicate(&sensitive, &object->name, in->newParentHandle,
                        object->publicArea.nameAlg, (TPM2B_SEED *) &data,
                        &in->symmetricAlg, &in->encryptionKeyIn,
                        &out->duplicate);

   out->encryptionKeyOut = in->encryptionKeyIn;

   return TPM_RC_SUCCESS;
}