/*
 * The Initial Developer of the Original Code is International
 * Business Machines Corporation. Portions created by IBM
 * Corporation are Copyright (C) 2005, 2006 International Business
 * Machines Corporation. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the Common Public License as published by
 * IBM Corporation; either version 1 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Common Public License for more details.
 *
 * You should have received a copy of the Common Public License
 * along with this program; if not, a copy can be viewed at
 * http://www.opensource.org/licenses/cpl1.0.php.
 */

#include "data_protect.h"
#include "data_common.h"

#include <tpm_pkcs11.h>
#include <tpm_utils.h>

#include <stdlib.h>
#include <unistd.h>
#define _GNU_SOURCE
#include <getopt.h>
#include <errno.h>


/*
 * Global variables
 */
int       g_bEncrypt     = TRUE;	// Encrypt/Decrypt operation specifier

char     *g_pszInFile    = NULL;	// Input file name
FILE     *g_pInFile      = NULL;	// Input file stream
CK_BYTE  *g_pbInData     = NULL;	// Input file buffer

char     *g_pszOutFile   = NULL;	// Output file name
FILE     *g_pOutFile     = NULL;	// Output file stream
CK_ULONG  g_ulOutBuffLen = 0;		// Output file buffer length
CK_BYTE  *g_pbOutData    = NULL;	// Output file buffer
CK_ULONG  g_ulOutDataLen = 0;		// Length of data contained in output buffer

char     *g_pszToken     = NULL;	// Token label to be used

/*
 * parseCallback
 *   Process the command specific options.
 */
int
parseCallback( int         a_iOpt,
               const char *a_pszOptArg ) {

	switch ( a_iOpt ) {
		case 'd':
			g_bEncrypt = FALSE;
			break;

		case 'e':
			g_bEncrypt = TRUE;
			break;

		case 'i':
			if ( !a_pszOptArg )
				return -1;

			g_pszInFile = strdup( a_pszOptArg );
			break;

		// Use the specified token label when finding the token
		case 'k':
			if ( !a_pszOptArg )
				return -1;

			g_pszToken = strdup( a_pszOptArg );
			break;

		case 'o':
			if ( !a_pszOptArg )
				return -1;

			g_pszOutFile = strdup( a_pszOptArg );
			break;
	}

	return 0;
}

/*
 * usageCallback
 *   Display command usage information.
 */
void
usageCallback( const char *a_szCmd ) {

	logCmdHelp( a_szCmd );
	logCmdOption( "-d, --decrypt",
			_("Decrypt the input data") );
	logCmdOption( "-e, --encrypt",
			_("Encrypt the input data (default)") );
	logCmdOption( "-i, --infile FILE",
			_("Use FILE as the input to the specified operation") );
	logCmdOption( "-k, --token STRING",
			_("Use STRING to identify the label of the PKCS#11 token to be used") );
	logCmdOption( "-o, --outfile FILE",
			_("Use FILE as the output of the specified operation") );
}

/*
 * parseCmd
 *   Parse the command line options.
 */
int
parseCmd( int    a_iArgc,
          char **a_szArgv ) {

	int  rc;

	char          *szShortOpts = "dei:k:o:";
	struct option  stLongOpts[]  = {
			{ "decrypt", no_argument, NULL, 'd' },
			{ "encrypt", no_argument, NULL, 'e' },
			{ "infile", required_argument, NULL, 'i' },
			{ "token", required_argument, NULL, 'k' },
			{ "outfile", required_argument, NULL, 'o' },
		};
	int  iNumLongOpts = sizeof( stLongOpts ) / sizeof( struct option );

	rc = genericOptHandler( a_iArgc, a_szArgv,
		szShortOpts, stLongOpts, iNumLongOpts,
		parseCallback, usageCallback );
	if ( rc == -1 )
		return -1;

	// Make sure "-i" is specified until stdin support is added
	if ( !g_pszInFile ) {
		logMsg( TOKEN_INPUT_FILE_ERROR );
		rc = -1;
	}

	// Make sure "-o" is specified until stdout support is added
	if ( !g_pszOutFile ) {
		logMsg( TOKEN_OUTPUT_FILE_ERROR );
		rc = -1;
	}

	if ( rc == -1 ) {
		usageCallback( a_szArgv[ 0 ] );
		return -1;
	}

	return 0;
}

/*
 * makeKey
 *   Make the 256-bit AES symmetric key used to encrypt
 *   or decrypt the input data.
 */
int
makeKey( CK_SESSION_HANDLE  a_hSession ) {

	int  rc = -1;

	// Generate a 256-bit AES key
	CK_RV             rv;
	CK_BBOOL          bTrue       = TRUE;
	CK_BBOOL          bFalse      = FALSE;
	CK_OBJECT_CLASS   tKeyClass   = CKO_SECRET_KEY;
	CK_KEY_TYPE       tKeyType    = CKK_AES;
	CK_ULONG          ulKeyLen    = 32;
	CK_MECHANISM      tMechanism  = { CKM_AES_KEY_GEN, NULL, 0 };
	CK_ATTRIBUTE      tAttr[]     = {
			{ CKA_CLASS, &tKeyClass, sizeof( tKeyClass ) },
			{ CKA_TOKEN, &bTrue, sizeof( bTrue ) },
			{ CKA_PRIVATE, &bTrue, sizeof( bTrue ) },
			{ CKA_MODIFIABLE, &bFalse, sizeof( bFalse ) },
			{ CKA_LABEL, TOKEN_PROTECT_KEY_LABEL, strlen( TOKEN_PROTECT_KEY_LABEL ) },
			{ CKA_KEY_TYPE, &tKeyType, sizeof( tKeyType ) },
			{ CKA_SENSITIVE, &bTrue, sizeof( bTrue ) },
			{ CKA_ENCRYPT, &bTrue, sizeof( bTrue ) },
			{ CKA_DECRYPT, &bTrue, sizeof( bTrue ) },
			{ CKA_SIGN, &bFalse, sizeof( bFalse ) },
			{ CKA_VERIFY, &bFalse, sizeof( bFalse ) },
			{ CKA_WRAP, &bTrue, sizeof( bTrue ) },
			{ CKA_UNWRAP, &bTrue, sizeof( bTrue ) },
			{ CKA_EXTRACTABLE, &bFalse, sizeof( bFalse ) },
			{ CKA_VALUE_LEN, &ulKeyLen, sizeof( ulKeyLen ) },
		};
	CK_ULONG          ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );
	CK_OBJECT_HANDLE  hObject;

	// Generate the key on the token
	rv = generateKey( a_hSession, &tMechanism, tAttr, ulAttrCount, &hObject );
	if ( rv != CKR_OK )
		goto out;

	rc = 0;
out:
	return rc;
}

/*
 * getKey
 *   Get the symmetric key used for encryption or decryption.
 */
int
getKey( CK_SESSION_HANDLE  a_hSession,
        CK_OBJECT_HANDLE  *a_phObject ) {

	int  rc = -1;

	CK_RV             rv;
	CK_BBOOL          bTrue       = TRUE;
	CK_OBJECT_CLASS   tKeyClass   = CKO_SECRET_KEY;
	CK_ATTRIBUTE      tAttr[]     = {
			{ CKA_CLASS, &tKeyClass, sizeof( tKeyClass ) },
			{ CKA_TOKEN, &bTrue, sizeof( bTrue ) },
			{ CKA_LABEL, TOKEN_PROTECT_KEY_LABEL, strlen( TOKEN_PROTECT_KEY_LABEL ) },
		};
	CK_ULONG          ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );
	CK_OBJECT_HANDLE *phObjList   = NULL;
	CK_ULONG          ulObjCount  = 0;

	*a_phObject = 0;

	// Search for the protection key
	rv = findObjects( a_hSession, tAttr, ulAttrCount, &phObjList, &ulObjCount );
	if ( rv != CKR_OK )
		goto out;

	if ( ulObjCount == 0 ) {
		// Key doesn't exist, create it
		if ( makeKey( a_hSession ) == -1 )
			goto out;

		// Search for the protection key again
		rv = findObjects( a_hSession, tAttr, ulAttrCount, &phObjList, &ulObjCount );
		if ( rv != CKR_OK )
			goto out;

	}

	// Make sure we found it
	if ( ulObjCount == 0 ) {
		logError( TOKEN_NO_KEY_ERROR );
		goto out;
	}

	// Return the handle to the key
	*a_phObject = phObjList[ 0 ];

	rc = 0;

out:
	return rc;
}

/*
 * readData
 *   Callback routine that reads the input data for the encryption
 *   or decryption operation.  The operation (encryption or decryption)
 *   determines some of the logic in this routine.
 */
int
readData( CK_BYTE  **a_pbData,
          CK_ULONG  *a_pulDataLen,
          CK_BBOOL  *a_pbMoreData,
          CK_BBOOL   a_bEncrypt ) {

	CK_ULONG  iBytes;
	CK_BBOOL  bMoreData = TRUE;

	if ( !g_pInFile ) {
		// Open the input file
		errno = 0;
		g_pInFile = fopen( g_pszInFile, "r" );
		if ( !g_pInFile ) {
			logError( TOKEN_FILE_OPEN_ERROR, g_pszInFile, strerror( errno ) );
			return -1;
		}

		// Allocate an input buffer
		g_pbInData = malloc( TOKEN_BUFFER_SIZE );
		if ( !g_pbInData ) {
			logError( TOKEN_MEMORY_ERROR );
			return -1;
		}
	}

	// Read the data
	iBytes = fread( g_pbInData, 1, TOKEN_BUFFER_SIZE, g_pInFile );
	if ( feof( g_pInFile ) ) {
		fclose( g_pInFile );

		// End of file encountered, indicate that there is
		// no more data
		bMoreData = FALSE;
	}
	else {
		// Not end of file so make sure the buffer was filled
		if ( iBytes != TOKEN_BUFFER_SIZE ) {
			// Error encountered, terminate
			fclose( g_pInFile );
			return -1;
		}
	}

	if ( !bMoreData && a_bEncrypt ) {
		// No more data, so if we are encrypting then we MUST add padding
		int  iCount   = iBytes - 1;
		int  iPadding = TOKEN_AES_BLOCKSIZE - ( iBytes % TOKEN_AES_BLOCKSIZE );

		iBytes += iPadding;

		g_pbInData[iCount + iPadding] = iPadding;
		iPadding--;
		while ( iPadding > 0 ) {
			g_pbInData[iCount + iPadding] = 0;
			iPadding--;
		}
	}

	*a_pbData = g_pbInData;
	*a_pulDataLen = iBytes;
	*a_pbMoreData = bMoreData;

	return 0;
}

/*
 * writeData
 *   Callback routine that writes the output data for the encryption
 *   or decryption operation.  The operation (encryption or decryption)
 *   determines some of the logic in this routine.
 */
int
writeData( CK_BYTE  *a_pbData,
           CK_ULONG  a_ulDataLen,
           CK_BBOOL  a_bMoreData,
           CK_BBOOL  a_bEncrypt ) {

	size_t tWriteCount;

	if ( !g_pOutFile ) {
		// Open the output file
		errno = 0;
		g_pOutFile = fopen( g_pszOutFile, "w" );
		if ( !g_pOutFile ) {
			logError( TOKEN_FILE_OPEN_ERROR, g_pszOutFile, strerror( errno ) );
			return -1;
		}
	}

	if ( !a_bMoreData ) {
		// No more data so remove padding if we are decrypting
		if ( !a_bEncrypt ) {
			int      iPadding;

			if ( a_ulDataLen == 0 ) {
				// Remove padding from previous block is current
				// block is zero length
				if ( g_pbOutData ) {
					iPadding = g_pbOutData[g_ulOutDataLen - 1];
					g_ulOutDataLen -= iPadding;
				}
			}
			else {
				// Remove padding from current block
				iPadding = a_pbData[a_ulDataLen - 1];
				a_ulDataLen -= iPadding;
			}
		}
	}

	// Write the previous buffer if there is one
	if ( g_pbOutData && ( g_ulOutDataLen > 0 ) ) {
		tWriteCount = fwrite( g_pbOutData, 1, g_ulOutDataLen, g_pOutFile );

		if ( tWriteCount != g_ulOutDataLen ) {
			logError(TOKEN_FILE_WRITE_ERROR, g_pOutFile, "short write");
			return -1;
		}
	}

	if ( a_bMoreData ) {
		// Allocate a (new) buffer if necessary
		if ( a_ulDataLen > g_ulOutBuffLen ) {
			free( g_pbOutData );

			g_ulOutBuffLen = a_ulDataLen;
			g_pbOutData = malloc( g_ulOutBuffLen );
			if ( !g_pbOutData ) {
				logError( TOKEN_MEMORY_ERROR );
				return -1;
			}
		}

		// Copy the current data to the holding buffer
		if ( a_ulDataLen > 0 )
			memcpy( g_pbOutData, a_pbData, a_ulDataLen );
		g_ulOutDataLen = a_ulDataLen;
	}
	else {
		// No more data so write the last piece of data
		if ( a_ulDataLen > 0 ) {
			tWriteCount = fwrite( a_pbData, 1, a_ulDataLen, g_pOutFile );

			if ( tWriteCount != a_ulDataLen ) {
				logError(TOKEN_FILE_WRITE_ERROR, g_pOutFile, "short write");
				return -1;
			}
		}

		fclose( g_pOutFile );
	}

	return 0;
}

int
main( int    a_iArgc,
      char **a_szArgv ) {

	int  rc = 1;

	char *pszPin = NULL;

	CK_RV              rv;
	CK_SESSION_HANDLE  hSession;
	CK_OBJECT_HANDLE   hObject;
	CK_MECHANISM       tMechanism = { CKM_AES_ECB, NULL, 0 };

	// Set up i18n
	initIntlSys( );

	// Parse the command
	if ( parseCmd( a_iArgc, a_szArgv ) == -1 )
		goto out;

	// Open the PKCS#11 TPM Token
	rv = openToken( g_pszToken );
	if ( rv != CKR_OK )
		goto out;

	// Make sure the token is initialized
	if ( !isTokenInitialized( ) ) {
		logMsg( TOKEN_NOT_INIT_ERROR );
		goto out;
	}

	// Open a session
	rv = openTokenSession( CKF_RW_SESSION, &hSession );
	if ( rv != CKR_OK )
		goto out;

	pszPin = getPlainPasswd( TOKEN_USER_PIN_PROMPT, FALSE );
	if ( !pszPin )
		goto out;

	// Login to the token
	rv = loginToken( hSession, CKU_USER, pszPin );
	if ( rv != CKR_OK )
		goto out;

	// Obtain the key
	if ( getKey( hSession, &hObject ) == -1 )
		goto out;

	// Perform the operation
	if ( g_bEncrypt )
		rv = encryptData( hSession, hObject, &tMechanism,
			readData, writeData );
	else
		rv = decryptData( hSession, hObject, &tMechanism,
			readData, writeData );

	if ( rv != CKR_OK )
		goto out;

	rc = 0;

out:
	shredPasswd( pszPin );

	if ( hSession )
		closeTokenSession( hSession );

	closeToken( );

	free( g_pszInFile );
	free( g_pszOutFile );
	free( g_pbInData );
	free( g_pbOutData );

	if ( rc == 0 )
		logInfo( TOKEN_CMD_SUCCESS, a_szArgv[ 0 ] );
	else
		logInfo( TOKEN_CMD_FAILED, a_szArgv[ 0 ] );

	return rc;
}
