RSA BSAFE Cert-C

Certificate Components for C

Crypto-C 6.2.1 Developer's Guide
Search

cms.c

Creates or reads a CMS message.

/* $Id: cms.c,v 1.5 2004/03/02 05:18:36 gsingh Exp $ */
/* cms.c
** Copyright (c) 2002-2003, RSA Security Inc.
**
** This file is for demonstration purposes only.
** You have a royalty-free right to use,
** modify, reproduce and distribute this demonstration file (including
** any modified version), provided that you agree that RSA Security has
** no warranty, implied or otherwise, or liability for this demonstration
** file or any modified version.
**
** Use Cert-C to make or read a CMS message.
**
** Note that the in-memory database used when reading and writing signed data
** and enveloped data messages lasts for the lifetime of the application.
** (i.e. one shared database is used and the contents are not destroyed)
** That way, the user doesn't have to keep adding the same certs over and over
** again until the application is restarted.  This program can be modified to
** either use a new database each time by registering and unregistering the
** database called RSA_DEMO_DATABASE_NAME in each procedure that uses the
** database SERVICE.  Or, for a more persistent data store, the RSA default
** database provider can be used.
**
** When compiling, define the macro RSA_REQUIRE_FILE_LOG (-D compile
** option, or equivelent) to force the program to return an error code
** if file logging cannot be initialized.  For example, if the file
** containing the log message format strings cannot be located (certc.msg
** or equivalent).
*/

#include "certc.h"
#include "filelog.h"
#include "demoutil.h"
#include "p7util.h"
#include "certutil.h"
#include "imdb.h"
#include "rsacsp.h"
#include "pkixpath.h"

#include "cmsobj.h"

#ifdef _MSC_VER
# pragma warning (disable: 279)   /* while (0) */
#endif

/*  For demonstration purposes, we use the in-memory database with the
 *  following name for easy access.
 */
#define RSA_DEMO_DATABASE_NAME "in-memory database"

/* For multiple updates */
#define DEMO_UPDATE_SIZE        64000


/* These functions are from the Crypto-C sample code.
   Could use RSA utils functions instead */
static int GetDataFromFile (FILE *, unsigned int, unsigned char *, unsigned int *, int *);

static int AppendDataToFile (FILE *, unsigned char *, unsigned int);

static int InitializeDatabase (CERTC_CTX ctx);
static int DecomposeCMSEnvelopedDataMsg (CERTC_CTX ctx);
static int WriteCMSEnvelopedDataMsgFromRawData (CERTC_CTX ctx);

/*  Used by WriteCMSEnvelopedDataMsgFromRawData, this procedure prompts the user for the
 *  necessary information to obtain the recipientInfos.  The certificates will
 *  be placed into the database.
 */
static int AddCMSRecipients (CERTC_CTX ctx, C_CMS_OBJ cmsObj, SERVICE database);

/*  Number of service providers registered in the context.  */
#define SP_COUNT 2


/*  This function initializes the database with the name 
 *  RSA_DEMO_DATABASE_NAME, which must be registered in the given CERTC_CTX.
 */
static int InitializeDatabase (CERTC_CTX ctx)
{
  SERVICE db = NULL;
  LIST_OBJ trustedRoots = NULL, certList = NULL;
  int status = 0;

  RSA_PrintMessage ("Initializing database...\n");

  /* Obtain trusted roots and other intermediate certs */
  status = C_CreateListObject (&trustedRoots);
  if (status != 0)
    goto CLEANUP;

  status = C_CreateListObject (&certList);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("\nSupply trusted root certificates.\n");
  status = RSA_AddCertsToListPrompt (ctx, trustedRoots);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("\nTrusted Roots\n");
  status = RSA_PrintCertList (trustedRoots);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("\nOptionally supply other relevant certificates, such ");
  RSA_PrintMessage ("as those needed to\nconstruct the certificate chain.\n");
  status = RSA_AddCertsToListPrompt (ctx, certList);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("\nOther certificates\n");
  status = RSA_PrintCertList (certList);
  if (status != 0)
    goto CLEANUP;

  /* Add roots and intermediate certs to database */
  status = C_BindService (ctx, SPT_DATABASE, RSA_DEMO_DATABASE_NAME, &db);
  if (status != 0)
    goto CLEANUP;

  status = C_InsertCertList (db, trustedRoots);
  if (status != 0)
    goto CLEANUP;

  status = C_InsertCertList (db, certList);
  if (status != 0)
    goto CLEANUP;


CLEANUP:
  if (status != 0)
    RSA_PrintError ("InitializeDatabase", status);

  C_DestroyListObject (&trustedRoots);
  C_DestroyListObject (&certList);

  C_UnbindService (&db);

  return (status);
}

int main (int argc, char *argv[])
{
  int status = 0;
  char command[RSA_DEMO_MAX_LINE_LEN];
  CERTC_CTX ctx = NULL;
  UINT4 flags;

  SERVICE_HANDLER spTable[SP_COUNT] = {
    {SPT_DATABASE, RSA_DEMO_DATABASE_NAME, S_InitializeMemoryDB},
    {SPT_CRYPTO, "BSAFE Crypto-C", S_InitializeDefaultCSP}
  };
  POINTER spParams[SP_COUNT] = {
    NULL, NULL
  };

  FILE_LOG_PARAMS logParams = {NULL, NULL};
  SERVICE_HANDLER logHandler = {
    SPT_LOG, "Default File Log", S_InitializeFileLog
  };

  status = RSA_SetOptions (&logParams, argc, argv);
  if (status != 0)
    goto CLEANUP;

  status = C_InitializeCertC (spTable, spParams, SP_COUNT, &ctx);
  if (status != 0)
    goto CLEANUP;

  /* Set Cert-C flags to indicate the CMS streaming API version 1 */
  flags = CERTC_CTX_FLAG_CMS_STREAM_1;
  status = C_SetCertCFlags (ctx, flags);
  if (status != 0)
    goto CLEANUP;

  /* Attempt to initialize file logging, but unless RSA_REQUIRE_FILE_LOG is
   * defined, treat it as a non-fatal condition.
   */
  status = C_RegisterService (ctx, &logHandler, (POINTER)&logParams,
                              SERVICE_ORDER_FIRST);
#ifdef RSA_REQUIRE_FILE_LOG
  if (status != 0)
    goto CLEANUP;
#endif

  RSA_PrintMessage ("CMS Message Objects\n");
  RSA_PrintMessage ("=======================\n");

  status = InitializeDatabase (ctx);
  if (status != 0)
    goto CLEANUP;

  for (;;) {
    RSA_PrintMessage ("\nCMS Object Operations\n");
    RSA_PrintMessage ("  A - Pull apart a CMS EnvelopedData message\n");
    RSA_PrintMessage ("  B - Create CMS EnvelopedData message\n");

    status = RSA_GetCommand (command, sizeof (command),
                             "Enter choice (blank to quit)");
    if (status != 0)
      goto CLEANUP;

    switch (command[0]) {
      case 'a':
      case 'A':
        status = DecomposeCMSEnvelopedDataMsg (ctx);
        break;
      case 'b':
      case 'B':
        status = WriteCMSEnvelopedDataMsgFromRawData (ctx);
        break;
      case '\0':
      case 'q':
      case 'Q':
        goto CLEANUP;
      default:
        RSA_PrintMessage ("Unrecognized Option: %c\n", command[0]);
        status = RSA_DEMO_E_INVALID_PARAMETER;
    }

    if (status != 0)
      RSA_PrintMessage ("Operation not completed.\n");
    else
      RSA_PrintMessage ("Operation successful!\n");
  }
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("cms", status);

  C_FinalizeCertC (&ctx);
  
  return status;
}  /* end main */


static int WriteCMSEnvelopedDataMsgFromRawData (CERTC_CTX ctx)
{
  C_CMS_OBJ envelopedObj = NULL;
  UINT4 cmsFlag = 0;

  char inputFilename[256], outputFilename[256];
  FILE *inputFile=NULL, *outputFile=NULL;
  ITEM dataToRead =  {NULL, 0};
  ITEM buffer = {NULL, 0};

  int status = 0;
  int endFlag = 0;
  int done = 0;

  SERVICE database = NULL;

  status = C_BindService (ctx, SPT_DATABASE, RSA_DEMO_DATABASE_NAME,
                          &database);
  if (status != 0)
    goto CLEANUP;

  /* To allow "(blank for empty message): "); as data, use RSA_GetCommand */
  status = RSA_GetRequiredCommand (inputFilename, sizeof (inputFilename), 
       "Enter name of file to encapsulate in an enveloped message (required)");
  if (status != 0)
    goto CLEANUP;
    
  inputFile = fopen (inputFilename, "rb");
  if (inputFile == (FILE *)NULL_PTR) {
    status = RSA_DEMO_E_FILE_IO;
    goto CLEANUP;
  };  

  status = RSA_GetRequiredCommand (outputFilename, sizeof (outputFilename),
                "Enter name of file to store CMS data message");
  if (status != 0)
    goto CLEANUP;

  outputFile = fopen (outputFilename, "wb");
  if (outputFile == (FILE *)NULL_PTR) {
    status = RSA_DEMO_E_FILE_IO;
    goto CLEANUP;
  }

  /* Step 1: Create a CMS object */
  status = C_CreateCMSObject (ctx, &envelopedObj);
  if (status != 0)
    goto CLEANUP;

  /* Step 2: Set CMS message type */
  status = C_SetCMSMessageType (envelopedObj, C_CMS_MSGTYPE_ENVELOPED_DATA);
  if (status != 0)
    goto CLEANUP;

  /* Step 3: Add recipient info */
  /* Get the certs of the recipients into the database  */
  status = AddCMSRecipients (ctx, envelopedObj, database);
  if (status != 0)
    goto CLEANUP;

  /* Symmetric encryption algorithm is 3DES for now, so don't need to 
     set it.
   */

  dataToRead.data = T_malloc(DEMO_UPDATE_SIZE);
  buffer.data = T_malloc(DEMO_UPDATE_SIZE);

  while (!done) {
    if (!endFlag) {
      status = GetDataFromFile (inputFile, DEMO_UPDATE_SIZE, dataToRead.data,
                                &dataToRead.len, &endFlag);
      if (status != 0)
        goto CLEANUP;

      /* If we are done reading from the file, then set C_CMS_IO_FLAGS_COMPLETE.
         Otherwise, set the flag to 0.
       */
      if (endFlag == 1)
        cmsFlag = C_CMS_IO_FLAGS_COMPLETE;
      else
        cmsFlag = 0;

      /* Step 4: Write data from buffer into CMS object */
      status = C_WriteCMSMessage (envelopedObj, dataToRead.data,
                                  dataToRead.len, cmsFlag);
      if (status != 0)
        goto CLEANUP;
    } /* end if (!endFlag) */

    /* Step 5: Read data from CMS object into buffer */
    status = C_ReadCMSMessage (envelopedObj, DEMO_UPDATE_SIZE, buffer.data,
                               &buffer.len, &cmsFlag);
    if (status != 0)
      goto CLEANUP;

    if (cmsFlag & C_CMS_IO_FLAGS_COMPLETE) {
      done = 1;
    }

    /* Write envelopedData from buffer into file */
    status = AppendDataToFile (outputFile, buffer.data, buffer.len);
    if (status != 0)
      goto CLEANUP;

  } /*  end while  */

  if (status != 0)
    goto CLEANUP;

CLEANUP:
  if (status != 0)
    RSA_PrintError ("WriteDataMsg", status);

  /* Step 6: Destroy */
  C_DestroyCMSObject (&envelopedObj);

  if (dataToRead.data)
    T_free (dataToRead.data);
  if (buffer.data)
    T_free (buffer.data);

  if (inputFile)
    fclose (inputFile);
  if (outputFile)
    fclose (outputFile);

  C_UnbindService (&database);
  
  return (status);
}  /* end WriteCMSEnvelopedDataMsgFromRawData */

static int DecomposeCMSEnvelopedDataMsg (CERTC_CTX ctx)
{
  C_CMS_OBJ cmsObj = NULL;
  UINT4 cmsFlag = 0;

  char inputFilename[256], outputFilename[256];
  FILE *inputFile=NULL, *outputFile=NULL;
  ITEM buffer = {NULL, 0};

  B_KEY_OBJ recipientPvtKey = NULL;
  CERT_OBJ recipientCert = NULL;

  int status = 0;
  int endFlag = 0;
  int done = 0;

  SERVICE database = NULL;

  status = C_BindService (ctx, SPT_DATABASE, RSA_DEMO_DATABASE_NAME,
                          &database);
  if (status != 0)
    goto CLEANUP;

  status = RSA_GetRequiredCommand (inputFilename, sizeof (inputFilename), 
             "Enter name of file containing CMS message (required)");
  if (status != 0)
    goto CLEANUP;
    
  inputFile = fopen (inputFilename, "rb");
  if (inputFile == (FILE *)NULL_PTR) {
    status = RSA_DEMO_E_FILE_IO;
    goto CLEANUP;
  };  

  status = RSA_GetRequiredCommand (outputFilename, sizeof (outputFilename),
                "Enter name of file to store output message (required)");
  if (status != 0)
    goto CLEANUP;

  outputFile = fopen (outputFilename, "wb");
  if (outputFile == (FILE *)NULL_PTR) {
    status = RSA_DEMO_E_FILE_IO;
    goto CLEANUP;
  }

  /* Obtain recipient's private key */
  RSA_PrintMessage ("Obtaining recipient certificate and private key...\n");
  status = C_CreateCertObject (&recipientCert, ctx);
  if (status != 0)
    goto CLEANUP;

  status = B_CreateKeyObject (&recipientPvtKey);
  if (status != 0)
    goto CLEANUP;
  
  status = RSA_GetCertAndPvtKey (ctx, recipientCert, recipientPvtKey);
  if (status != 0)
    goto CLEANUP;

  status = C_InsertCert (database, recipientCert);
  if (status != 0)
    goto CLEANUP;

  status = C_InsertPrivateKey (database, recipientCert, recipientPvtKey);
  if (status != 0)
    goto CLEANUP;


  /* Step 1: Create a CMS object */
  status = C_CreateCMSObject (ctx, &cmsObj);
  if (status != 0)
    goto CLEANUP;
  
  /* Do not set CMS message type, since we don't know what type yet */

  buffer.data = T_malloc(DEMO_UPDATE_SIZE);

  while (!done) {
    if (!endFlag) {
      status = GetDataFromFile (inputFile, DEMO_UPDATE_SIZE, buffer.data,
                                &buffer.len, &endFlag);
      if (status != 0)
        goto CLEANUP;

      /* If we are done reading from the file, then set C_CMS_IO_FLAGS_COMPLETE.
         Otherwise, set the flag to 0.
       */
      if (endFlag == 1)
        cmsFlag = C_CMS_IO_FLAGS_COMPLETE;
      else
        cmsFlag = 0;

      /* Step 2: Write data from buffer into CMS object */
      /* The object will configure itself on the first write(s),
      ** based on information in the message.  If this is
      ** an EnvelopedData object, then recipient certificates (necessary
      ** for decrypting the message) will be fetched from any of
      ** the databases found in the Cert-C context.
      */
      status = C_WriteCMSMessage (cmsObj, buffer.data, buffer.len, cmsFlag);
      if (status != 0)
        goto CLEANUP;
    } /* end if (!endFlag) */

    /* Step 3: Read data from CMS object into buffer */
    status = C_ReadCMSMessage (cmsObj, DEMO_UPDATE_SIZE, buffer.data,
                               &buffer.len, &cmsFlag);
    if (status != 0)
      goto CLEANUP;

    if (cmsFlag & C_CMS_IO_FLAGS_COMPLETE) {
      done = 1;
    }     
   
    status = AppendDataToFile (outputFile, buffer.data, buffer.len);
    if (status != 0)
      goto CLEANUP;

  } /* end while (!done) */

  if (status != 0)
    goto CLEANUP;

  /* Call C_GetCMSRecipients() to get recipient list */

CLEANUP:
  if (status != 0)
    RSA_PrintError ("DecomposeCMSEnvelopedDataMsg", status);

  /* Step 4: Destroy */
  C_DestroyCMSObject (&cmsObj);

  if (buffer.data)
    T_free (buffer.data);

  if (inputFile)
    fclose (inputFile);
  if (outputFile)
    fclose (outputFile);

  C_UnbindService (&database);
  C_DestroyCertObject (&recipientCert);
  B_DestroyKeyObject (&recipientPvtKey);

  return (status);
}

static int AddCMSRecipients (CERTC_CTX ctx, C_CMS_OBJ cmsObj, SERVICE database)
{
  int status = 0, recipientCount = 1;

  unsigned char *certBer = NULL;
  unsigned int certBerLen = 0;
  
  CERT_OBJ receiverCert = NULL;
  CERT_FIELDS certFields;
  RECIPIENT_INFO recipientInfo;

  RSA_PrintMessage ("\nSpecify at least one message recipient.\n");

  for (;;) {
    RSA_PrintMessage ("Supply certificate for recipient #%i,",
                      recipientCount++);
    RSA_PrintMessage ("blank if there are no more recipients.\n");
    status = RSA_GetFileToAllocBuffer
             (&certBer, &certBerLen,
              "Enter name of file containing cert binary");
    if (status == RSA_DEMO_E_CANCEL) {
      status = 0;
      break;
    }
    else if (status != 0)
      goto CLEANUP;

    /*  For now, the only encryption algorithm available with which to encrypt
     *  the data encryption key is RSA.  If there is another alternative, the
     *  user should be prompted to choose here for use below.
     */

    status = C_CreateCertObject (&receiverCert, ctx);
    if (status != 0)
      goto CLEANUP;

    status = C_SetCertBER (receiverCert, certBer, certBerLen);
    if (status != 0)
      goto CLEANUP;

    status = C_InsertCert (database, receiverCert);
    if (status != 0)
      goto CLEANUP;

    status = C_GetCertFields (receiverCert, &certFields);
    if (status != 0)
      goto CLEANUP;

    recipientInfo.type = KEY_TRANSPORT;  /* only one available for now */
    recipientInfo.info.keyTrans.recipCertId.type = ISSUER_SERIAL;
    recipientInfo.info.keyTrans.recipCertId.id.issuerSerialNumber.issuerName =
      certFields.issuerName;
    recipientInfo.info.keyTrans.recipCertId.id.issuerSerialNumber.serialNumber
        = certFields.serialNumber;
    recipientInfo.info.keyTrans.keyEncryptionAlgorithmId.algorithmId =
      KA_RSA_ENCRYPTION;
    recipientInfo.info.keyTrans.keyEncryptionAlgorithmId.algorithmParam = NULL;

    /* status = C_AddRecipientToList (recipientInfos, &recipientInfo, NULL); */
    status = C_AddCMSRecipient (cmsObj, &recipientInfo);
    if (status != 0)
      goto CLEANUP;
    
    T_free (certBer);
    certBer = NULL;
    C_DestroyCertObject (&receiverCert);
  }

  RSA_PrintMessage ("\nFinished collecting Recipient Information\n");
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("GetRecipientInfoList", status);

  T_free (certBer);
  C_DestroyCertObject (&receiverCert);
  
  return (status);
}  /* end AddCMSRecipients */

static int GetDataFromFile
(
 FILE *inputFile,
 unsigned int sizeToRead,
 unsigned char *dataToRead,
 unsigned int *dataToReadLen,
 int *endFlag)
{
  unsigned char dummy;

  do {    
    *dataToReadLen = fread (dataToRead, 1, sizeToRead, inputFile);
    if (*dataToReadLen == sizeToRead)
    /* Read exactly sizeToRead bytes, so reading one more will set 
       end of file if there were exactly sizeToRead bytes in the file.  */
      fread (&dummy, 1, 1, inputFile);
  
    if (feof (inputFile)) {
      *endFlag = 1;
      fclose (inputFile);
    }
    else {
      *endFlag = 0;
      fseek (inputFile, -1, SEEK_CUR);
    }
  } while (0);    
  return (0);
}  /*  end GetDataFromFile  */


int AppendDataToFile
(FILE *outputFile, unsigned char *blockOfData, unsigned int outputLenUpdate)
{
  do {  
    if (fwrite (blockOfData, 1, outputLenUpdate, outputFile)
          < outputLenUpdate) {
      fclose (outputFile);
    }
  } while (0);
  return (0);
}  /*  end AppendDataToFile  */

Copyright (c) 1999-2005 RSA Security Inc. All rights reserved. 067-001001-2720-001-000 - 2.7.2