/*
 * <insert one-line description of what the program does>
 * Copyright (c) <2008-2009>, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *

	File Name:    Provision.c       

	Description:	TPM Provision Operations
	This file implements class 
	This class has following functions.
		1. Init TPM NV Storage
		2. Set Owner Password
		3. Change Owner Password

    OS: Metasys
    SE: SUSE 10.2

	Notes (opt):
****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include "Provision/Provision.h"
#include "GUI/error.h"

int Provision_Construct(Provision *this)
{
	int		ret = SUCCESS;

	if(this == NULL)
	{
		return TPMCtrl_ProvisionStatus_ParamError;
	}

	this -> bTPMPresence = 1;
	ret = Provision_ConncetToTPM();
	if(ret != SUCCESS)
	{
		this -> bTPMPresence = 0;
	}

	return ret;
}

int Provision_Destruct(void)
{
	return SUCCESS;
}

TSS_RESULT createKnownPolicy(
    TSS_HCONTEXT	context,
    TSS_FLAG		policyType,
    TSS_HPOLICY		*policy,
    TSS_HOBJECT		target)
{
    TPM_SECRET		secret = {TSS_WELL_KNOWN_SECRET};
	TSS_RESULT	    tResult = TSS_SUCCESS;

    tResult = createAltPolicy(context, policyType, &secret, policy, target);

	return tResult;
}

TSS_RESULT createAltPolicy(
	TSS_HCONTEXT	context, 
	TSS_FLAG		policyType, 
	TPM_SECRET		*secret, 
	TSS_HPOLICY		*policy, 
	TSS_HOBJECT		target)
{
    TSS_HPOLICY		localPolicy;
    UINT32			secret_len = sizeof(*secret);
    UINT32			initFlags = policyType;
	TSS_RESULT	    tResult = TSS_SUCCESS;

    if (policy == NULL)
	{
		policy = &localPolicy;
	}

    tResult = Tspi_Context_CreateObject(
		context, 
		TSS_OBJECT_TYPE_POLICY,
        initFlags, 
		policy);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

    tResult = Tspi_Policy_SetSecret(
		*policy, 
		TSS_SECRET_MODE_SHA1,                                   
		secret_len, 
		secret -> authdata);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
    
    if(target != 0)
      tResult = Tspi_Policy_AssignToObject(
		*policy, 
		target);

    return tResult;
}


TSS_RESULT createKey(
    TSS_HCONTEXT	context,
    UINT32			initFlags,
    TSS_HKEY		*key,
    TSS_BOOL		createInTPM,
    TSS_BOOL		loadInTPM,
    TSS_HKEY		parent)
{
    TSS_HPOLICY		p;
	TSS_RESULT	    tResult = TSS_SUCCESS;

    tResult = Tspi_Context_CreateObject(
		context, 
		TSS_OBJECT_TYPE_RSAKEY,
        initFlags, 
		key);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
    
    tResult = createKnownPolicy(
		context, 
		TSS_POLICY_USAGE, 
		&p, 
		*key);
 	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

    tResult = createKnownPolicy(
		context, 
		TSS_POLICY_MIGRATION, 
		&p, 
		*key);
  	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

    if (createInTPM)
    {
        if((initFlags & 0x00F0) != TSS_KEY_TYPE_IDENTITY)
			tResult = Tspi_Key_CreateKey(*key, parent, 0);
        else
        {
            // Make an identity key
            TSS_HTPM   tpm;
            TSS_BOOL   createParent = (parent == 0);
            UINT32     idReqLen;
            BYTE	   *idReq;

            if(createParent)
              tResult = Tspi_Context_CreateObject(
					context, 
					TSS_OBJECT_TYPE_RSAKEY,
                    TSS_KEY_TSP_SRK, 
					&parent);
            if(tResult == TSS_SUCCESS)
            {
                // If we created the SRK in the line above, or if
                // the caller retrieved the SRK from the registry and
                // the registry entry did not have a blob or an RSA modulus
                // for the SRK, then 'parent' cannot be used as the CA
                // below.
                // To allow the SRK to be used as the CA in this case,
                // call GetPubKey here. The TSP will cache the
                // public info in the keyhandle so it is accessible
                // later.
                UINT32 pubLen;
                BYTE  *pubBytes;
                tResult = Tspi_Key_GetPubKey(
					parent, 
					&pubLen, 
					&pubBytes);
                if(tResult == TSS_SUCCESS)
                  Tspi_Context_FreeMemory(context, pubBytes);
            }

            if (tResult == TSS_SUCCESS)
              tResult = Tspi_Context_GetTpmObject(context, &tpm);
            
			if (tResult == TSS_SUCCESS)
              tResult = Tspi_TPM_CollateIdentityRequest(tpm, parent, parent,
                                                       1, (BYTE*)"a",
                                                       *key, TSS_ALG_AES,
                                                       &idReqLen, &idReq);
            if (createParent && parent)
              Tspi_Context_CloseObject(context, parent);
        }
		if(tResult != TSS_SUCCESS) 
		{
			return tResult;
		}

        if (loadInTPM)
        {
            tResult = Tspi_Key_LoadKey(*key, parent);
			if(tResult != TSS_SUCCESS) 
			{
				return tResult;
			}
        }
    }
    
    return TSS_SUCCESS;
}

void setup_tss_validation(
    TSS_VALIDATION *valid,
    TSS_NONCE      *nonce)
{
    memset(valid, 0, sizeof(TSS_VALIDATION));
    
	valid -> ulExternalDataLength = sizeof(TSS_NONCE);
    
	valid -> rgbExternalData = nonce -> nonce;
}

void cleanup_tss_validation(
    TSS_HCONTEXT    context,
    TSS_VALIDATION	*valid)
{
    if (valid -> rgbData)
      Tspi_Context_FreeMemory(context, valid -> rgbData);

    if (valid -> rgbValidationData)
      Tspi_Context_FreeMemory(context, valid -> rgbValidationData);
    
	memset(valid, 0, sizeof(TSS_VALIDATION));
}

int Provision_VerifyRight(void)
{
	TSS_HCONTEXT	context;
    TSS_HKEY		srkObj;
    TSS_HPOLICY		p0;
	TSS_RESULT	    tResult = TSS_SUCCESS;

    UINT32			pubLen;
    BYTE			*pubKey = NULL;

	// Create the context object, the TSP's root object
	Tspi_Context_Create(&context);

	// Connect to the TPM - here on the local system
	Tspi_Context_Connect(context, NULL);

    // Check whether the SRK exists by calling TPM_GetPubKey on it.
    // Based on the error that is returned we know whether or not
    // the SRK exists.
    tResult = Tspi_Context_CreateObject(
		context, 
		TSS_OBJECT_TYPE_RSAKEY,                                    
		TSS_KEY_TSP_SRK, 
		&srkObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
      
	tResult = createKnownPolicy(
		context, 
		TSS_POLICY_USAGE, 
		&p0, 
		srkObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
      
	tResult = Tspi_Key_GetPubKey(
		srkObj, 
		&pubLen, 
		&pubKey);
    if(pubKey != NULL)
	{
		Tspi_Context_FreeMemory(context, pubKey);
	}

	// Disconnect to the TPM: local system 
	Tspi_Context_Close(context);

	// If SRK exists then return TPM Owner exists
    if((tResult == TSS_SUCCESS) || (tResult == TPM_E_AUTHFAIL))
	{
		return TPM_OWNER_VALID;

	}
    else
	{
		return TPM_OWNER_INVALID;
	}
}

// This function will connect to local TPM
int Provision_ConncetToTPM(void)
{
	TSS_HCONTEXT	context;

	int				i = 0;
	TSS_RESULT	    tResult = TSS_SUCCESS;

	// Create the context object, the TSP's root object
	Tspi_Context_Create(&context);

	// Connect to the TPM - here on the local system
	Tspi_Context_Connect(context, NULL);
	
	// Disconnect to the TPM: local system 
	Tspi_Context_Close(context);

	if (tResult == TSS_SUCCESS)
	{
		return SUCCESS;
	}
	else
	{
		return TPMCtrl_ProvisionStatus_UnknownError;
	}
}

// Get the device MAC address used for Hardware ID  
int Provision_GetMacAddress(STRING strHWID)
{
	char device[32];
	int  flag = 0;
	int  n = 0;
	int  fd = 0;
	struct ifreq ifr;

	if(strHWID == NULL)
	{
		return TPMCtrl_ProvisionStatus_ParamError;
	}

	for(n = 0; n < 16; n ++)
	{
		strncpy(device, "eth", 3);
		device[3] = n + '0';
		device[4] = 0;
		if((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0) 
		{
			return TPMCtrl_ProvisionStatus_InitTPMFailed;
		}
		strcpy(ifr.ifr_name, device);
		// get mac address
		if(ioctl(fd, SIOCGIFHWADDR, &ifr) >= 0) 
		{
			flag = 1;
			break;
		}
	}
    if(flag) 
	{
		sprintf(strHWID, "%02X%02X%02X%02X%02X%02X", 
			(unsigned char)ifr.ifr_hwaddr.sa_data[0], 
			(unsigned char)ifr.ifr_hwaddr.sa_data[1], 
			(unsigned char)ifr.ifr_hwaddr.sa_data[2], 
			(unsigned char)ifr.ifr_hwaddr.sa_data[3], 
			(unsigned char)ifr.ifr_hwaddr.sa_data[4], 
			(unsigned char)ifr.ifr_hwaddr.sa_data[5]);
		close(fd);
		return SUCCESS;
	}
    else 
	{
		close(fd);
        return TPMCtrl_ProvisionStatus_InitTPMFailed;
    }
}

// This function will init TPM NV storage data
int Provision_InitTPMNV(BYTE *InitData, UINT32 DataLen, UINT32 nIndex)
{	
	TSS_HCONTEXT	context;
	TSS_HNVSTORE	nvStorageObj;
	TSS_RESULT	    tResult = TSS_SUCCESS;

	int				i = 0;
	UINT32			unDataLen = 0;

	// Check if input parameter is availabe
	if(InitData == NULL || DataLen == 0 || nIndex == 0)
	{
		return TPMCtrl_ProvisionStatus_ParamError;
	}

	// Create the context object, the TSP's root object
	Tspi_Context_Create(&context);

	// Connect to the TPM: local system
	Tspi_Context_Connect(context, NULL);

	//Create NV Storage object
	tResult = Tspi_Context_CreateObject(
		context,
		TSS_OBJECT_TYPE_NV, 
		0, 
		&nvStorageObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Select the corresponding index number for NVM storage
	tResult = Tspi_SetAttribUint32(
            nvStorageObj, 
			TSS_TSPATTRIB_NV_INDEX, 
			0, 
			nIndex);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Get the length of data available at this area.
	tResult = Tspi_GetAttribUint32(
		nvStorageObj,
		TSS_TSPATTRIB_NV_DATASIZE,
		0,
		&unDataLen);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
	if(unDataLen < DataLen)
	{
		return TPMCtrl_ProvisionStatus_GetAttribUnitFailed;
	}

	// Start writing User Data area to NV
	tResult = Tspi_NV_WriteValue(
		nvStorageObj,
		0,
		DataLen,
		InitData);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Disconnect to the TPM: local system 
	Tspi_Context_Close(context);

	return SUCCESS;
}

// This function initializes the TPM user data
int Provision_InitUserData(void)
{
	BYTE		btShareKey[TDAGENT_SHAREDSECRET_LENGTH];
	BYTE		btUserData[TDAGENT_USERDATA_LENGTH];
	BYTE		btPacket[TDAGENT_PACKET_LENGTH];
	char		expiredate[7];
	int			i = 0;
	char		strHWID[13];

	// init packet area to 0
	memset(btPacket, 0, TDAGENT_PACKET_LENGTH);
	if(SUCCESS != Provision_InitTPMNV(btPacket, TDAGENT_PACKET_LENGTH, TDAGENT_PACKET_INDEX))
	{
		return TPMCtrl_ProvisionStatus_InitTPMFailed;
	}

	// init share secret to ASCII "0"
	memset(btShareKey, BLANK_CHARACTER, TDAGENT_SHAREDSECRET_LENGTH);
	if(SUCCESS != Provision_InitTPMNV(btShareKey, TDAGENT_SHAREDSECRET_LENGTH, TDAGENT_SHAREDSECRET_INDEX))
	{
		return TPMCtrl_ProvisionStatus_InitTPMFailed;
	}

	// init user data
	memset(btUserData, 0, BOOTTICK_LENGTH + BOOTCOUNT_LENGTH);
	btUserData[5] = INITIAL_BOOT_COUNT;
	strcpy(expiredate, INITIAL_EXPIRY_DATE);
	for(i = 0; i < EXPIRYDATE_LENGTH; i ++)
	{
		btUserData[i + EXPIRYDATE_OFFSET] =  (BYTE)expiredate[i];
	}

	// Get MAC address
	Provision_GetMacAddress(strHWID);

	for(i = 0; i < HWID_LENGTH; i ++)
	{
		btUserData[i + HWID_OFFSET] = (BYTE)strHWID[i];
	}
	if(SUCCESS != Provision_InitTPMNV(btUserData, TDAGENT_USERDATA_LENGTH, TDAGENT_USERDATA_INDEX))
	{
		return TPMCtrl_ProvisionStatus_InitTPMFailed;
	}

	return SUCCESS;
}

// This function write down the Server Public Key & Intel Public Key
int	Provision_WritePublicKey(BYTE *IntelPK, BYTE *ServerPK)
{
	BYTE btPKData[TDAGENT_PUBLICKEY_LENGTH];

	// Check if input parameter is availabe
	if(IntelPK == NULL || ServerPK == 0)
	{
		return TPMCtrl_ProvisionStatus_ParamError;
	}

	memcpy(btPKData, ServerPK, TDAGENT_PUBLICKEY_LENGTH / 2);
	memcpy(btPKData + TDAGENT_PUBLICKEY_LENGTH / 2, IntelPK, TDAGENT_PUBLICKEY_LENGTH / 2);

	if(SUCCESS != Provision_InitTPMNV(btPKData, TDAGENT_PUBLICKEY_LENGTH, TDAGENT_PUBLICKEY_INDEX))
	{
		return TPMCtrl_ProvisionStatus_InitTPMFailed;
	}

	return SUCCESS;
}

// This function will set TPM owner with well know secret
int Provision_CreateOwnerShip(Provision *this)
{
	TSS_HCONTEXT	context;
	TSS_HKEY		ekObj, srkObj;
	TSS_HTPM		tpmObj;
	TSS_HPOLICY		tpmPolicyObj;
	TSS_VALIDATION	valid;
    TSS_NONCE       nonce;
	TSS_RESULT	    tResult = TSS_SUCCESS;

    UINT32			srkFlags;

	// Check if input parameter is availabe
	if(this == NULL)
	{
		return TPMCtrl_ProvisionStatus_ParamError;
	}

	// Create the context object, the TSP's root object
	Tspi_Context_Create(&context);

	// Connect to the TPM: local system
	Tspi_Context_Connect(context, NULL);

	tResult = Tspi_Context_GetTpmObject(
		context, 
		&tpmObj);
 	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	tResult = createKnownPolicy(
		context, 
		TSS_POLICY_USAGE, 
		NULL, 
		tpmObj);
 	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
      
	// Read the public endorsement key
	setup_tss_validation(&valid, &nonce);
	tResult = Tspi_TPM_GetPubEndorsementKey(
		tpmObj, 
		FALSE, 
		&valid, 
		&ekObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
    cleanup_tss_validation(context, &valid);

	// Create Storage Root Key template
    srkFlags = TSS_KEY_TYPE_STORAGE | TSS_KEY_SIZE_2048 | TSS_KEY_NO_AUTHORIZATION | TSS_KEY_NOT_MIGRATABLE;
    tResult = createKey(context, srkFlags, &srkObj, FALSE, FALSE, 0);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
	
	// Take the TPM ownership
	tResult = Tspi_TPM_TakeOwnership(
		tpmObj, 
		srkObj, 
		ekObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Close the context object
    Tspi_Context_Close(context);

	return  SUCCESS;
} 

// This function will clear disable TPM owner function
int Provision_DisableOwnerClear(Provision *this)
{
	TSS_HCONTEXT	context;
	TSS_HKEY		srkObj, ekObj;
	TSS_HTPM		tpmObj;
	TSS_RESULT	    tResult = TSS_SUCCESS;

    UINT32			srkFlags;
	TSS_BOOL 		bStatus = 0;

	// Check if input parameter is availabe
	if(this == NULL)
	{
		return TPMCtrl_ProvisionStatus_ParamError;
	}

	// Create the TSS 1.2 context object
	Tspi_Context_Create(&context);

	// Connect to the TPM - here on the local system
	Tspi_Context_Connect(context, NULL);
	
	// Get TPM object
    tResult = Tspi_Context_GetTpmObject(context, &tpmObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

    tResult = createKnownPolicy(context, TSS_POLICY_USAGE, NULL, tpmObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Set TPM status
	bStatus = TRUE;
	tResult = Tspi_TPM_SetStatus(
		tpmObj,         
		TSS_TPMSTATUS_DISABLEOWNERCLEAR,
		bStatus);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Close the context object
	Tspi_Context_Close(context);
	return  SUCCESS;
}

// This function will change the owner password
int Provision_ChangeTPMOwnerShip(void)
{
	TSS_HCONTEXT	context;
	TSS_HKEY		srkObj, ekObj, k1;
	TSS_HTPM		tpmObj;
	TSS_HPOLICY		altPolicy;
	TSS_RESULT	    tResult = TSS_SUCCESS;
    TSS_VALIDATION	valid;
    TSS_NONCE		nonce;
	TPM_AUTHDATA	altAuth;

    UINT32			initFlags;
	int				i, altAuthLen = TPM_SHA1_160_HASH_LEN;

	for(i = 0; i < TPM_SHA1_160_HASH_LEN; i ++)
	{
		altAuth.authdata[i] = rand(0, 0x80);
	}

	// Create the TSS 1.2 context object
	Tspi_Context_Create(&context);

	// Connect to the TPM - here on the local system
	Tspi_Context_Connect(context, NULL);

	// Initialize Storage Root Key
	tResult = createKey(context, TSS_KEY_TSP_SRK, &srkObj, FALSE, FALSE, 0);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
	
	// Get TPM object
    tResult = Tspi_Context_GetTpmObject(context, &tpmObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

    tResult = createKnownPolicy(context, TSS_POLICY_USAGE, NULL, tpmObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Create alternate policies
	tResult = createKnownPolicy(context, TSS_POLICY_USAGE, &altPolicy, 0);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	tResult = Tspi_Policy_SetSecret(
		altPolicy, 
		TSS_SECRET_MODE_SHA1,                                  
		altAuthLen, 
		altAuth.authdata);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Change Storage Root Key authentication
	tResult = Tspi_ChangeAuth(srkObj, tpmObj, altPolicy);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

	// Use modified Storage Root Key
	initFlags = TSS_KEY_TYPE_BIND | TSS_KEY_SIZE_2048 | TSS_KEY_NO_AUTHORIZATION | TSS_KEY_MIGRATABLE;
    tResult = Tspi_Policy_SetSecret(
		altPolicy, 
		TSS_SECRET_MODE_SHA1,
		altAuthLen, 
		altAuth.authdata);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}

    tResult = createKey(context, initFlags, &k1, TRUE, TRUE, srkObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
	
	// Change owner authentication
	tResult = Tspi_Policy_SetSecret(
		altPolicy, 
		TSS_SECRET_MODE_SHA1,
		altAuthLen, 
		altAuth.authdata);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
	
	tResult = Tspi_ChangeAuth(tpmObj, 0, altPolicy);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
    
	// Use modified owner authentication, get Endorsement Key
	setup_tss_validation(&valid, &nonce);    
	tResult = Tspi_TPM_GetPubEndorsementKey(tpmObj, TRUE, &valid, &ekObj);
	if(tResult != TSS_SUCCESS) 
	{
		return tResult;
	}
	cleanup_tss_validation(context, &valid);

	// Close the context object
	Tspi_Context_Close(context);

	return  SUCCESS;
}

// This function is for complete the whole TPM provision process
int	Provision_TPMAutoProvision(Provision *this, BYTE *IntelPK, BYTE *ServerPK)
{
	int	    iRet = SUCCESS;

	iRet = Provision_InitUserData();
	if(iRet != SUCCESS) 
	{
		return iRet;
	}

	iRet = Provision_WritePublicKey(IntelPK, ServerPK);
	if(iRet != SUCCESS) 
	{
		return iRet;
	}

	iRet = Provision_CreateOwnerShip(this);
	if(iRet != SUCCESS) 
	{
		return iRet;
	}

	iRet = Provision_DisableOwnerClear(this);
	if(iRet != SUCCESS) 
	{
		return iRet;
	}

	iRet = Provision_ChangeTPMOwnerShip();
	if(iRet != SUCCESS) 
	{
		return iRet;
	}

	return  SUCCESS;
}
