API tutorial: development of a sample application
This section offers a tutorial for fcrypt, a sample application that is provided with ProtectToolkit-C.
The fcrypt application enables files to be encrypted for a given recipient and then decrypted by that recipient. Since the encrypted file contains a message authentication code (MAC), the recipient of a document will also be able to verify that the encrypted file was not modified.
In order to follow this example effectively, the reader is strongly encouraged to open or print the source of the application as a reference. The source code for fcrypt can be found in the file fcrypt.c within your chosen install directory.
Note
To avoid running into issues, move samples out of the installation directory before modifying, compiling, or running them.
Required header files
You will note in the initial code segments that, apart from the standard header files, we include the ProtectToolkit-C set of required library files.
#include "cryptoki.h"
#include "ctextra.h"
#include "ctlutil.h"
#include "chkret.h"
Whereas cryptoki.h is the required PKCS#11 header, the remainder implement some of the advanced or extended features of the ProtectToolkit-C implementation, such as error feedback.
Runtime switches
We want to develop fcrypt to be able to take a series of command line inputs to allow us to decrypt a message, use password-based encryption (pbe) or to display time information for a cipher operation. With that in mind, the following flags are defined appropriately.
static int dflag = 0;
/* 1 - decrypt */static int tflag = 0;
/* 1 - time */static int pflag = 0;
/* 1 - use pbe */
Encrypt functions
-
For our file encryption and subsequent decryption, we define the following two functions:
int encryptFile( char * sender, char * recipient, char *ifile,char * ofile ); int decryptFile( char * sender, char * recipient, char *ifile,char * ofile );
We want the encrypt function to take the public key of the receiving party (recipient), encrypt the data (ifile) with the given key and sign the encrypted data with the sender's private key (sender), before outputting and encoding the file to the output file (ofile).
For error handling purposes, we define the function as follows:
#undef FN #define FN "encryptFile:" int encryptFile( char * sender, char * recipient,char * ifile, char * ofile )
-
We now need to define the required PKCS#11 data types pertaining to the session, slot identification, and object handles we will use for the sender and recipient keys.
/* sender slot key session handles */ CK_SLOT_ID hsSlot; CK_OBJECT_HANDLE hsKey = 0; CK_SESSION_HANDLE hsSession; /* recipient slot key session handles */ CK_SLOT_ID hrSlot; CK_OBJECT_HANDLE hrKey; CK_SESSION_HANDLE hrSession;
-
We must also allocate variables to define the type of mechanism, digest, and key information during encryption.
CK_RV rv; /* Return Value for PKCS#11 function */ CK_MECHANISM mech; /* Structure for cipher mechanism */ CK_BYTE iv[8]; /* Init. Vector used with CBC encryption */ CK_BYTE digest[80]; CK_SIZE len; CK_OBJECT_HANDLE hKey; /* random encrypting key */ CK_BYTE wrappedKey[2 * 1024]; CK_SIZE wrappedKeyLen; CK_BYTE signature[2 * 1024]; unsigned long fileSize; unsigned long encodedSize;
Earlier, we said that we wanted to be able to perform password-based encryption via a runtime switch, so accordingly this is the first instance that we check for with our pflag variable.
-
Our next step is to define our secret key that we will use to encrypt the data. The key type to be used is double-length DES. The CK_BBOOL refers to a byte-sized Boolean flag that we have defined as either
TRUE
orFALSE
for easier reference.CK_ATTRIBUTE is a structure that includes the type, value, and length of an attribute. Since every PKCS#11 key object is required to be assigned certain attributes, this structure is later used during our key derivation and generation to assign those attributes to the key.
if ( pflag ) { /* use PBE to do the encryption */ static CK_OBJECT_CLASS at_class = CKO_SECRET_KEY; static CK_KEY_TYPE kt = CKK_DES2; static const CK_BBOOL True = TRUE; static const CK_BBOOL False = FALSE; CK_ATTRIBUTE attr[] = { {CKA_CLASS, &at_class, sizeof(at_class)},{CKA_KEY_TYPE, &kt, sizeof(at_class)},{CKA_EXTRACTABLE, (void*)&True, sizeof(True)},{CKA_SENSITIVE, (void*)&False, sizeof(False)},{CKA_DERIVE, (void*)&True, sizeof(True)}};
-
The params variable is defined using the PKCS#11 definition CK_PBE_PARAMS, a structure that provides all of the necessary information required by the PKCS#11 password-based encryption mechanisms.
CK_BYTE iv[8]; CK_PBE_PARAMS params; memset(¶ms, 0x0, sizeof(CK_PBE_PARAMS)); params.pInitVector = iv; params.pPassword = sender; params.passwordLen = strlen(sender); params.pSalt = NULL; params.saltLen = 0; params.iteration = 1;
-
PKCS#11 also uses a structure for defining the mechanism. Within
CK_MECHANISM
we need to specify the mechanism type, a pointer to the parameters we defined earlier and the size of the parameters. The mechanism type we will use isCKM_PBE_SHA1_DES2_EDE_CBC
that is used for generating a 2-key triple-DES secret key and IV from a password and a salt value by using the SHA-1 digest algorithm and an iteration count.memset(&mech, 0x0, sizeof(CK_MECHANISM)); mech.mechanism = CKM_PBE_SHA1_DES2_EDE_CBC; mech.pParameter = ¶ms; mech.parameterLen = sizeof(CK_PBE_PARAMS);
-
We have now set up our required structures, and the next step is to open a session between the application and a token in a particular slot using the PKCS#11 call C_OpenSession. This call requires the slot ID flags which indicate the type of session, an application-defined pointer to be passed to the notification callback; an address of the notification callback function, and a pointer to the location that receives the handle for the new session.
rv = C_OpenSession(0, CKF_RW_SESSION|CKF_SERIAL_SESSION,NULL,NULL, &hsSession); if ( rv ) return 1; hrSession = hsSession;
-
Once we have successfully opened a session with the token, we now want to generate the key that we will use to encrypt our input file. The C_GenerateKey function will generate a secret key and thereby create a new key object. This function call requires the session’s handle, a pointer to the key generation mechanism, a pointer to the template for the new key, the number of attributes in the template and a pointer to the location that receives the handle of the new key.
The CHECK_RV() function call is part of the ProtectToolkit-C extended capability for better error feedback and handling.
rv = C_GenerateKey(hsSession, &mech, attr, NUMITEMS(attr),&hKey); CHECK_RV(FN "C_GenerateKey:CKM_PBE_SHA1_DES2_EDE_CBC", rv);if ( rv ) return 1;
-
If we are not using the password-based encryption switch at program execution, the desired reaction is to perform file encryption using RSA, and hence we will need to generate the secret key value for the operation.
The function FindKeyFromName is part of the ProtectToolkit-C CTUTIL library to provide extended functionality. It is used here to locate the keys which are passed into fcrypt at the command line and return the slot ID, session handle and object handle of those keys.
else { /* use RSA to encrypt the file */ /* locate encrypting key */ rv = FindKeyFromName(sender, CKO_PRIVATE_KEY, &hsSlot, &hsSession, &hsKey);if ( rv ) {fprintf( stderr, "Unable to access sender (%s)key\n", sender );CHECK_RV(FN "FindKeyFromName", rv);if ( rv ) return 1; } /* locate signing key */ rv = FindKeyFromName(recipient, CKO_CERTIFICATE, &hrSlot, &hrSession, &hrKey);if ( rv ) {rv = FindKeyFromName(recipient, CKO_PUBLIC_KEY, &hrSlot, &hrSession, &hrKey); } if ( rv ) { fprintf( stderr, "Unable to access recipient (%s) key\n", recipient );CHECK_RV(FN "FindKeyFromName", rv);if ( rv ) return 1;}
-
To achieve acceptable performance during file encryption and decryption we need to use a symmetric key cipher such as DES. The DES key we generate for this purpose is to be wrapped with the recipient’s RSA key so it can later be unwrapped and used for decryption without the value of the key ever being known.
-
Rather than simply using the same key for each file encryption, we will generate a random DES key for each encryption of the input file. The mechanism used here is
CKM_DES2_KEY_GEN
that is used for generating double-length DES keys. -
The key wrapping is performed with the C_WrapKey function that encrypts (wraps) a private or secret key. The function requires the session handle, the wrapping mechanism, the handle of the wrapping key, the handle of the key to be wrapped, a pointer to the location that receives the wrapped key and a pointer to the location that receives the length of the wrapped key.
-
For the wrapping mechanism we will choose
CKM_RSA_PKCS
that is a multi-purpose mechanism based on the RSA public-key cryptosystem and the block formats defined in PKCS #1. It supports single-part encryption and decryption, single-part signatures and verification with and without message recovery, key wrapping and key unwrapping./* create a random des key for the encryption */ memset(&mech,0,sizeof(mech)); mech.mechanism = CKM_DES2_KEY_GEN; /* generate the key */ rv = C_GenerateKey(hrSession, &mech, wrappedKeyTemp, NUMITEMS(wrappedKeyTemp), &hKey); CHECK_RV(FN "C_GenerateKey", rv); if ( rv ) return 1; /* wrap the encryption key with the recipients public key */ memset(&mech,0,sizeof(mech)); mech.mechanism = CKM_RSA_PKCS; memset(wrappedKey,0,sizeof(wrappedKey)); wrappedKeyLen = sizeof(wrappedKey); rv = C_WrapKey(hrSession, &mech, hrKey, hKey, wrappedKey, &wrappedKeyLen); CHECK_RV(FN "C_WrapKey", rv); if ( rv ) return 1;
-
-
Now that we have a random secret key to perform the encryption with, we will need to set the required mechanism and parameters prior to encrypting the input file. As a mechanism for the encryption we will choose CKM_DES3_CBC_PAD which is using triple-DES in Cipher Block Chaining mode and PKCS#1 padding.
An application cannot call C_Encrypt in a session without having called C_EncryptInit first to activate an encryption operation. C_EncryptInit requires the session’s handle, a pointer to the encryption mechanism and the handle of the encryption key.
In the same manner as we initialized and set up, our digest operation is to be the signature verification to send along to the recipient with the encrypted data. The mechanism used for our digest is SHA-1 that is defined in PKCS#11 terms as
CKM_SHA_1
./* set up the encryption operation using the random key */ memset(&mech, 0, sizeof(CK_MECHANISM)); mech.mechanism = CKM_DES3_CBC_PAD; memset(iv, 0, sizeof(iv)); mech.pParameter = iv; mech.parameterLen = sizeof(iv); rv = C_EncryptInit(hrSession, &mech, hKey); CHECK_RV(FN"C_EncryptInit", rv); if ( rv ) return 1; /* Set up the digest operation */ memset(&mech, 0, sizeof(CK_MECHANISM)); mech.mechanism = CKM_SHA_1; rv = C_DigestInit(hrSession, &mech); CHECK_RV(FN "C_DigestInit", rv); if ( rv ) return 1;
-
We are now ready to process our input file by encrypting the data, generating the message digest and writing the output to file.
/* ** Process the file. */ { FILE * ifp; /* input */ FILE * ofp; /* output */ CK_SIZE curLen; CK_SIZE slen; unsigned char buffer[10 * 1024]; unsigned char encbuffer[10 * 1024]; unsigned int br; /* bytes read */ unsigned int totbw; /* total bytes written */ /* open input and output file pointers */ ifp = fopen(ifile, "rb"); if ( ifp == NULL ) { fprintf( stderr, "Cannot open %s for input\n",ifile ); return -1; } ofp = fopen(ofile, "wb"); if ( ofp == NULL ) { fprintf( stderr, "Cannot open %s for input\n",ofile ); return -1; }
If the password based encryption switch wasn’t set, the first instance we write to file is the DES secret key wrapped by the recipient’s public key.
if ( ! pflag ) {/* write the encrypted key to the output file */encodedSize = htonl( (unsigned long) wrappedKeyLen);br = fwrite(&encodedSize, 1, sizeof(encodedSize), ofp);br = fwrite (wrappedKey, 1, (int)wrappedKeyLen, ofp); } /* get the file length */ { struct _stat buf; int result; result = _fstat( _fileno(ifp), &buf ); if( result != 0 ) { fprintf( stderr, "Cannot get file size for %s\n", ofile ); return -1; } fileSize = buf.st_size; /* fileSize = _filelength(_fileno(ifp)); */ } fileSize = (fileSize + 8) & ~7; /* round up for padding */ /* write file size to output file */ encodedSize = htonl(fileSize); /* big endian */ br = fwrite(&encodedSize, 1, sizeof(encodedSize), ofp);
-
Since our mode of encryption is cipher block chaining (CBC) we need to perform our output using four definitive looping steps until our data is processed.
-
For the digest we use the PKCS#11 function C_Digest_Update, which continues a multiple-part message-digesting operation, processing another data part. The function requires the session handle, a pointer to the data part and the length of the data part.
-
For the encryption, we use C_EncryptUpdate, which continues a multiple-part encryption operation, processing another data part. The function requires the session handle, a pointer to the data part; the length of the data part; a pointer to the location that receives the encrypted data part and a pointer to the location that holds the length in bytes of the encrypted data part.
/* read, encrypt, digest and write the cipher text in chunks */ totbw = 0; for ( ;; ) { br = fread(buffer, 1, sizeof(buffer), ifp); if ( br == 0 ) break; /* digest */ rv = C_DigestUpdate(hrSession, buffer, (CK_SIZE)br); CHECK_RV(FN "C_DigestUpdate", rv); if ( rv ) return 1; /* encrypt */ curLen = sizeof(encbuffer); rv = C_EncryptUpdate(hrSession, buffer, (CK_SIZE)br, encbuffer, &curLen); CHECK_RV(FN "C_EncryptUpdate", rv); if ( rv ) return 1; /* write cipher text */ br = fwrite(encbuffer, 1, (int)curLen, ofp); totbw += br;}
-
-
Once all the data has been processed, we need to finalize the encryption and digest operation.
-
To finish the encryption, we use the C_EncryptFinal call, which finishes a multiple-part encryption operation. The function requires the session handle, a pointer to the location that receives the last encrypted data part, if any, and a pointer to the location that holds the length of the last encrypted data part.
-
For finalizing the digest, we call C_DigestFinal, which finishes a multiple-part message-digesting operation, returning the message digest. The function requires the session’s handle, a pointer to the location that receives the message digest and a pointer to the location that holds the length of the message digest.
/* finish off the encryption */ curLen = sizeof(encbuffer); rv = C_EncryptFinal(hrSession, encbuffer, &curLen); CHECK_RV(FN "C_EncryptFinal", rv); if ( rv ) return 1; if ( curLen ) { br = fwrite(encbuffer, 1, (int)curLen, ofp); totbw += br;} if ( totbw != fileSize ) { fprintf( stderr, "size prediction incorrect %ld, %ld\n", totbw, fileSize );} /* finish off the digest */ len = sizeof(digest); rv = C_DigestFinal(hrSession, digest, &len); CHECK_RV(FN "C_DigestFinal", rv); if ( rv ) return 1;
-
-
If the password-based encryption flag was set, we use the digest created in the above process as our signature, since there is no recipient key to sign the data with. For our DES encryption we will sign the digest with our recipient’s public key.
-
The function C_SignInit is our first call and initializes a signature operation, where the signature is an appendix to the data. The function requires the session’s handle, a pointer to the signature mechanism and the handle of the signature key.
-
We also need to specify a mechanism to use for our signature operation, in this case
CKM_RSA_PKCS
, which is an RSA PKCS #1 mechanism. -
The signature generation is performed with the call to C_Sign that signs data in a single part, where the signature is an appendix to the data. The function requires the session’s handle, a pointer to the data, the length of the data, a pointer to the location that receives the signature, and a pointer to the location that holds the length of the signature.
if ( pflag ) { slen = len; memcpy(signature, digest, slen); } else {/* Set up the signature operation */memset(&mech, 0, sizeof(CK_MECHANISM));mech.mechanism = CKM_RSA_PKCS;rv = C_SignInit(hsSession, &mech, hsKey);CHECK_RV(FN "C_SignInit", rv);if ( rv ) return 1;slen = sizeof(signature);rv = C_Sign(hsSession, digest, len, signature, &slen);CHECK_RV (FN "C_SignInit", rv);if ( rv ) return 1; } /* write the signature to the file */ encodedSize = htonl((unsigned long) slen); br = fwrite(&encodedSize, 1, sizeof(encodedSize), ofp); br = fwrite(signature, 1, (int)slen, ofp); /* clean up */ fclose(ifp); fclose(ofp); } C_CloseSession(hrSession); C_CloseSession(hsSession); return 0; }
-
Decrypt function
For our decryption, we want to basically reverse the processes that were covered previously in the encryption section.
-
Following the initial function setup, we firstly check for our input and output files.
-
Once file existence is established, we test for our password-based encryption runtime switch. It can be seen that once again we generate the same secret key from the input password that we will need for the decryption. Since this was a secret key cipher, we use the same key for encryption as well as decryption.
#undef FN #define FN "decryptFile:" int decryptFile( char * sender, char * recipient,char * ifile, char * ofile ) { CK_SLOT_ID hsSlot; CK_OBJECT_HANDLE hsKey; CK_SESSION_HANDLE hsSession; CK_SLOT_ID hrSlot; CK_OBJECT_HANDLE hrKey; CK_SESSION_HANDLE hrSession; CK_RV rv; CK_MECHANISM mech; CK_BYTE digest[80]; CK_SIZE len; CK_OBJECT_HANDLE hKey; CK_BYTE wrappedKey[2 * 1024]; CK_SIZE wrappedKeyLen; CK_BYTE signature[2 * 1024]; CK_BYTE iv[8]; unsigned long encodedSize; FILE * ifp; FILE * ofp; int br; ifp = fopen(ifile, "rb");if ( ifp == NULL ) {fprintf( stderr, "Cannot open %s for input\n",ifile ); return -1; } ofp = fopen(ofile, "wb"); if ( ofp == NULL ) { fprintf( stderr, "Cannot open %s for input\n",ofile ); return -1; } if ( pflag ) {/* use PBE to do the encryption */static CK_OBJECT_CLASS at_class = CKO_SECRET_KEY;static CK_KEY_TYPE kt = CKK_DES2; static const CK_BBOOL True = TRUE;static const CK_BBOOL False = FALSE;CK_ATTRIBUTE attr[] = { {CKA_CLASS, &at_class, sizeof(at_class)}, {CKA_KEY_TYPE, &kt, sizeof(at_class)}, {CKA_EXTRACTABLE, (void*)&True, sizeof(True)}, {CKA_SENSITIVE, (void*)&False, sizeof(False)}, {CKA_DERIVE, (void*)&True, sizeof(True)} };CK_BYTE iv[8]; CK_PBE_PARAMS params; memset(¶ms, 0x0, sizeof(CK_PBE_PARAMS));params.pInitVector = iv;params.pPassword = sender;params.passwordLen = strlen(sender);params.pSalt = NULL;params.saltLen = 0;params.iteration = 1; memset(&mech, 0x0, sizeof(CK_MECHANISM));mech.mechanism = CKM_PBE_SHA1_DES2_EDE_CBC;mech.pParameter = ¶ms;mech.parameterLen = sizeof(CK_PBE_PARAMS); rv = C_OpenSession(0, CKF_RW_SESSION|CKF_SERIAL_SESSION, NULL, NULL, &hsSession); if ( rv ) return 1; hrSession = hsSession; rv = C_GenerateKey(hsSession, &mech, attr, NUMITEMS(attr), &hKey);CHECK_RV(FN "C_GenerateKey:CKM_PBE_SHA1_DES2_EDE_CBC", rv);if ( rv ) return 1; memset(&mech, 0x0, sizeof(CK_MECHANISM));mech.mechanism = CKM_SHA1_KEY_DERIVATION; rv = C_DeriveKey(hsSession, &mech, hKey, attr,NUMITEMS(attr),&hrKey);CHECK_RV(FN "C_DeriveKey:CKM_SHA1_KEY_DERIVATION", rv); if ( rv ) return 1;}
-
For our public key cipher, we will use the recipient’s private RSA key to unwrap the secret DES key contained in the input file. The DES key will then be used to decrypt the file.
The PKCS#11 function C_UnwrapKey is used to decrypt (unwrap) a wrapped key, creating a new private key or secret key object. This function requires the session handle, a pointer to the unwrapping mechanism, the handle of the unwrapping key, a pointer to the wrapped key, the length of the wrapped key, a pointer to the template for the new key, the number of attributes in the template, and a pointer to the location that receives the handle of the recovered key.
else { /* decrypting */ rv = FindKeyFromName(sender, CKO_CERTIFICATE, &hsSlot, &hsSession, &hsKey);if ( rv ) {rv = FindKeyFromName(sender, CKO_PUBLIC_KEY, &hsSlot, &hsSession, &hsKey); } if ( rv ) { fprintf( stderr, "Unable to access sender (%s)key\n", sender );CHECK_RV(FN "FindKeyFromName", rv);if ( rv ) return 1; }rv = FindKeyFromName(recipient, CKO_PRIVATE_KEY,&hrSlot, &hrSession, &hrKey);if ( rv ) {fprintf( stderr, "Unable to access recipient (%s) key\n", recipient );CHECK_RV(FN "FindKeyFromName", rv);if ( rv ) return 1;} /* read the encrypted key to the file */br = fread(&encodedSize, 1, sizeof (encodedSize), ifp);wrappedKeyLen = (CK_SIZE) ntohl((unsigned long) encodedSize);br = fread(wrappedKey, 1, (int)wrappedKeyLen, ifp); /* unwrap decryption key with the recipients private key */ memset(&mech,0,sizeof(mech)); mech.mechanism = CKM_RSA_PKCS; rv = C_UnwrapKey(hrSession, &mech, hrKey, wrappedKey, wrappedKeyLen, wrappedKeyTemp, NUMITEMS(wrappedKeyTemp), &hKey ); CHECK_RV(FN "C_UnwrapKey", rv); if ( rv ) return 1; }
-
Now that we have recovered the decryption key, we perform our initialization in exactly the same manner as for our encryption, but using the function C_DecryptInit. The digest is calculated in the same manner used for the encryption.
-
For the file decryption we are using the functions C_DecryptUpdate and C_DecryptFinal which take the same parameters as their encrypt counterparts.
/* set up the decryption operation using the random key */ memset(&mech, 0, sizeof(CK_MECHANISM)); mech.mechanism = CKM_DES3_CBC_PAD; memset(iv, 0, sizeof(iv)); mech.pParameter = iv; mech.parameterLen = sizeof(iv); rv = C_DecryptInit(hrSession, &mech, hKey); CHECK_RV(FN"C_EncryptInit", rv); if ( rv ) return 1; /* Set up the digest operation */ memset(&mech, 0, sizeof(CK_MECHANISM)); mech.mechanism = CKM_SHA_1; rv = C_DigestInit(hrSession, &mech); CHECK_RV(FN "C_DigestInit", rv); if ( rv ) return 1; { CK_SIZE curLen; CK_SIZE slen; unsigned char buffer[10 * 1024]; unsigned char decbuffer[10 * 1024]; unsigned int br; br = fread(&encodedSize, 1, sizeof(encodedSize), ifp); encodedSize = htonl(encodedSize); for ( ;encodedSize > 0; ) { br = sizeof(buffer); if ( encodedSize < br ) br = (unsigned int)encodedSize;br = fread(buffer, 1, br, ifp);encodedSize -= br;if ( br ) { curLen = sizeof(decbuffer); rv = C_DecryptUpdate(hrSession, buffer,(CK_SIZE) br, decbuffer, &curLen); CHECK_RV(FN "C_DecryptUpdate", rv); if ( rv ) return 1;rv = C_DigestUpdate(hrSession, decbuffer, curLen); CHECK_RV(FN "C_DigestUpdate", rv); if ( rv ) return 1;br = fwrite(decbuffer, 1, (unsigned int)curLen, ofp); }}curLen = sizeof(decbuffer);rv = C_DecryptFinal(hrSession, decbuffer, &curLen);CHECK_RV(FN "C_DecryptFinal", rv); if ( rv ) return 1;if ( curLen ) {br = fwrite(decbuffer, 1, (unsigned int)curLen, ofp); rv = C_DigestUpdate(hrSession, decbuffer, curLen);CHECK_RV(FN "C_DigestUpdate", rv); } len = sizeof(digest); rv = C_DigestFinal(hrSession, digest, &len); CHECK_RV(FN "C_DigestFinal", rv); if ( rv ) return 1;
-
Finally, we verify the signature contained in the data file. Since the signature is identical to the digest when using the password-based encryption option, it is a simple matter of comparing the two. For our DES encryption on the other hand, we need to verify the signature against the sender’s public key.
To perform this we start by calling C_VerifyInit that initializes a verification operation, where the signature is an appendix to the data. This function requires the session’s handle, a pointer to the structure that specifies the verification mechanism and the handle of the verification key.
/* read the signature from the file */br = fread(&encodedSize, 1, sizeof(encodedSize) , ifp);slen = (CK_SIZE) ntohl((unsigned long) encodedSize);br = fread(signature, 1, (unsigned int)slen, ifp); if ( pflag ) { if ( memcmp(digest, signature, len) ) {fprintf( stderr, "Verify failed\n" );return 1; } } else { /* Set up the signature verify operation */ memset(&mech, 0, sizeof(CK_MECHANISM)); mech.mechanism = CKM_RSA_PKCS; rv = C_VerifyInit(hsSession, &mech, hsKey); CHECK_RV(FN "C_VerifyInit", rv); if ( rv ) return 1; rv = C_Verify(hsSession, digest, len, signature, slen); if ( rv ) { C_ErrorString(rv,ErrorString,sizeof(ErrorString));fprintf( stderr, "Verify failed 0x%x, %s\n", rv, ErrorString ); }} /* clean up */ fclose(ifp); fclose(ofp); } C_CloseSession(hrSession); C_CloseSession(hsSession); return (int)rv; }
fcrypt usage
When no command line inputs are received by the application, it can be useful to show the required inputs on screen in a help context.
void usage(void){ printf( "usage fcrypt -d [-s<sender>] [-r<recipient>]
[-o<output file>] <input file>\n" );printf( " or\n" );printf( "usage fcrypt -d [-p<password>] [-o<outputfile>]
<input file>\n" );printf( " -d decrypt\n" );printf( " -p PBE password\n" );printf( " -s Sender name\n" );printf( " -r Recipient name\n" );printf( " -o output file name\n" );printf( " -t Report timing info\n" );printf( "\nKey naming syntax :\n");printf( " <token name>(<user pin>)/<key name>\n" );printf( " for example, -sAlice(0000)/Sign\n"
);}
Wrapped encryption key template
The DES encryption key that we wrap with the user RSA key will need to have its attributes specified within a template as follows:
/* Wrapped encryption key template */static char True = TRUE;static CK_OBJECT_CLASS Class = CKO_SECRET_KEY;static CK_KEY_TYPE Kt = CKK_DES2;static CK_ATTRIBUTE wrappedKeyTemp[] = {
{CKA_CLASS, &Class, sizeof(Class)},{CKA_KEY_TYPE, &Kt, sizeof(Kt)},{CKA_EXTRACTABLE, &True, 1},{CKA_ENCRYPT, &True, 1},};
Assembling the application
-
Now bring all the required components for the fcrypt application together in the main application body.
#undef FN #define FN "main:" int main(int argc, char ** argv) { CK_RV rv; int err = 0;char * arg;char * sender = NULL; /* provides signing key */char * recipient = NULL; /* provides encryption key */char * ofile = "file.enc"; /* default output file name */ printf( "Cryptoki File Encryption $Revision: 1.1 $\n" );printf( "Copyright (c) SafeNet, Inc 1999-2006\n" );
-
The first call within a PKCS#11 application must be C_Initialize, which initializes the PKCS#11 library. The function takes as an argument either value NULL_PTR or points to a CK_C_INITIALIZE_ARGS structure containing information on how the library should deal with multi-threaded access - no threading information is required for ProtectToolkit-C, so a NULL_PTR is used as the argument.
-
The function call to CT_ErrorString is part of the ProtectToolkit-C extended capability within ctutil.h and converts a PKCS#11 error code into a printable string.
/* This must be the first PKCS#11 call made */ rv = C_Initialize(NULL_PTR); if ( rv ) { C_ErrorString(rv,ErrorString,sizeof(ErrorString));fprintf(stderr, "C_Initialize error %x, %s\n", rv,ErrorString);}
-
Since Thales supports versions of PKCS#11 that are incompatible with one another, the CheckCryptokiVersion function is called to ensure that an application compiled for V1.X compliance is not going to fail if it links against a V 2.X-compliant DLL and vice versa. This function is part of the extended ProtectToolkit-C functionality within ctutil.h and ensures that the version of PKCS#11 is correct.
/* Check PKCS#11 version */ rv = CheckCryptokiVersion(); if ( rv ) {printf( "Incompatible PKCS#11 version (0x%x)\n", rv ); return -1; } /* process command line arguments */ for ( argv++; (arg = *argv) != NULL; argv++ ) { if ( arg[0] == '-' || arg[0] == '/' ) { switch( arg[1] ) { case 'd': dflag = 1;break; case 't': tflag = 1; break; case 'o': ofile = arg+2; break; case 's': sender = arg+2; break; case 'r': recipient = arg+2; break; case 'p': recipient = sender = arg+2; pflag = 1; break; default: usage(); return 1; } } else { time_t now, t1, t2; /* we will time the operation */ if ( sender == NULL || recipient == NULL ) {usage(); return 2; } if ( tflag ) {/* Mark the time now */for ( t1 = now = time(NULL); now == t1; ) t1 = time(NULL); } /* process the file */if ( dflag )err = decryptFile( sender, recipient, arg,ofile );else err = encryptFile( sender, recipient, arg,ofile ); /* report error or timing */if ( err ) {fprintf(stderr, "Error %scrypting file %s\n", dflag?"de":"en", arg ); }else if ( tflag ) { t2 = time(NULL); printf("%d seconds\n", t2-t1); } } } /* shut down PKCS#11 operations */
-
When the application is done using PKCS#11, it calls the PKCS#11 function C_Finalize and ceases to be a PKCS#11 application. It should be the last PKCS#11 call made by an application. The parameter is reserved for future versions and should be set to NULL_PTR.
rv = C_Finalize(NULL_PTR); if ( rv ) {C_ErrorString(rv,ErrorString,sizeof(ErrorString));fprintf(stderr, "C_Finalize error %x, %s\n", rv, ErrorString); } return err;