RSA BSAFE Cert-C

Certificate Components for C

Crypto-C 6.2.1 Developer's Guide
Search

p7stream.c

Create or read a PKCS #7 message. As Cert-C cannot stream PKCS #7 messages (instead requiring that the entire message be present in memory to pass to one function), this sample provides a workaround for this problem. Shows creation of PKCS #7 Signed Data and EnvelopedData, as well as the verification and decryption of those messages.

/* $Id: p7stream.c,v 1.6 2004/03/02 05:18:41 gsingh Exp $ */
/* p7stream.c
** Copyright (c) 2001-2003, RSA Security Inc.
**
** This file is used to demonstrate how to interface to an RSA Security
** licensed development product.  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 PKCS #7 message.  Since Cert-C does not have
** the capability to stream PKCS #7 messages (instead requiring that the
** entire message be present in memory to pass to one function), this sample
** attempts a workaround for this problem.  We show the creation of PKCS #7
** SignedData and EnvelopedData, as well as the verification and decryption
** of those messages.
**
** Possible scenarios we'd have to add to this testcase: ability to handle
** signed-then-enveloped or enveloped-then-signed messages, encapsulated
** signed data messages, etc.  Keep in mind that this is a temporary
** workaround until PKCS #7 streaming is incorporated into Cert-C.
**
** 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).
**
** Also when compiling, define the symbol RSA_MEMUSE_WATERMARKS to enable
** memory-use sizing of the runtime environment, and logging at completion.
*/

#include "certc.h"
#include "filelog.h"
#include "demoutil.h"
#include "p7util.h"
#include "certutil.h"
#include "imdb.h"
#include "rsacsp.h"
#include "pkixpath.h"
#ifdef RSA_MEMUSE_WATERMARKS
# include "rsamuse.h"
#endif

#ifdef _MSC_VER
# pragma warning (disable: 171) /* invalid type conversion (often of very similar ptrs) */
#endif

/*  Number of bytes to read from a file at a time.
 */
#define RSA_FILE_BLOCK_SIZE 100

#define RSA_MAX_SIG_LEN 256 /* for a 2048 bit key */

/*  Upper bound for symmetric cipher block size.
 */
#define RSA_MAX_BLOCK_SIZE 16

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

static int WriteSignedDataMsg (CERTC_CTX ctx);

static int WriteEnvelopedDataMsg (CERTC_CTX ctx);

/*  This procedure prompts the user for the signer's cert and private key.
 *  It assembles the BER-encoded SignerInfo, which contains the signature.
 *  For simplicity, we use SHA1 with RSA to create the signature.  We also
 *  do not use any authenticated or unauthenticated attributes (using
 *  authenticated attributes affects the computation of the signature).
 *  This testcase can be modified to incorporate those features.
 *
 *  Note that the calling function must call T_free on signerInfoBer.data!
 */
static int GetSignerInfoBer (CERTC_CTX ctx, CERT_OBJ signer, ITEM *signerInfoBer);

/*  This procedure takes a CERT_OBJ and produces the BER-encoded
 *  IssuerAndSerialNumber sequence.
 *
 *  Note that the calling application must call T_free on issuerSerial.data.
 */
static int EncodeIssuerSerial (CERTC_CTX ctx, CERT_OBJ cert, ITEM *issuerSerial);

/*  This procedure calculates the signature of the inputFile contents and
 *  writes out the PKCS #7 data message to the outputFile.  The BER-encoded
 *  encryptedDigest is returned in encryptedDigest.  For simplicity, this
 *  function uses SHA1 with RSA to create the signature.
 *
 *  Note that the calling application must call T_free on encryptedDigest.data.
 */
static int GetEncryptedDigest (CERTC_CTX ctx, B_KEY_OBJ privateKey,
                        ITEM *encryptedDigest);

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

/*  Used by WriteEnvelopedDataMsg, this procedure uses the given secretKey and
 *  contentEncryptionAlgId containing the symmetric algorithm and paramters
 *  to encrypt the data in inputFile, writing the output as the
 *  encryptedContent (OCTET STRING using implicit tag [0]).
 */
static int OutputEncryptedContent
      (CERTC_CTX ctx, FILE *inputFile, FILE *outputFile, B_KEY_OBJ secretKey,
       ALGORITHM_IDENTIFIER *contentEncryptionAlgId);

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

/*  This global cert path context is used for path validation  */
static CERT_PATH_CTX P7_PATH_CTX;

/*  This function initializes the P7_PATH_CTX and requires a database with the
 *  name RSA_DEMO_DATABASE_NAME to be registered in the given CERTC_CTX.
 */
static int InitializeGlobalPathCtx (CERTC_CTX ctx)
{
  int status = 0;

  P7_PATH_CTX.pathAlgorithm = PA_X509_V1;
  P7_PATH_CTX.pathOptions = PF_IGNORE_REVOCATION;

  RSA_PrintMessage ("Initializing path context...\n");
  
  status = C_CreateListObject (&P7_PATH_CTX.trustedCerts);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("Supply trusted certificates.\n");
  status = RSA_AddCertsToListPrompt (ctx, P7_PATH_CTX.trustedCerts);
  if (status != 0)
    goto CLEANUP;

  P7_PATH_CTX.policies = ANY_POLICY;
  P7_PATH_CTX.validationTime = PF_VALIDATION_TIME_NOW;

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

  /* Add trusted certs to database */
  status = C_InsertCertList (P7_PATH_CTX.database, P7_PATH_CTX.trustedCerts);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("\n");

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

  return status;
}  /* end InitializeGlobalPathCtx */

static void DestroyGlobalPathCtx ()
{
  C_DestroyListObject (&P7_PATH_CTX.trustedCerts);
  C_DestroyListObject (&P7_PATH_CTX.policies);
  C_UnbindService (&P7_PATH_CTX.database);
}  /* end DestroyGlobalPathCtx */

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

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

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

# ifdef RSA_MEMUSE_WATERMARKS
    T_setStackBase((POINTER)&status);
# endif

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

  status = C_InitializeCertC (spTable, spParams, SP_COUNT, &ctx);
  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

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

  RSA_PrintMessage ("PKCS #7 Message Objects\n");
  RSA_PrintMessage ("=======================\n");

  for (;;) {
    RSA_PrintMessage ("\nPKCS #7 Object Operations\n");
    /* RSA_PrintMessage ("  A - Pull apart a PKCS #7 message\n"); */
    RSA_PrintMessage ("  B - Create PKCS #7 signed data message\n");
    RSA_PrintMessage ("  C - Create PKCS #7 enveloped data 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 = DecomposePkcs7 (ctx); */
        break;
      case 'b':
      case 'B':
        status = WriteSignedDataMsg (ctx);
        break;
      case 'c':
      case 'C':
        status = WriteEnvelopedDataMsg (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 ("p7stream", status);

# ifdef RSA_MEMUSE_WATERMARKS
  {
     char msg[128];
     (void)sprintf(msg, "Max toolkit stack/heap usage = %d/%d\n",
                   T_getMaxStackUse(), T_getMaxHeapUse());
     RSA_PrintMessage(msg);
  }
# endif
  DestroyGlobalPathCtx ();
  C_FinalizeCertC (&ctx);
  
  return status;
}  /* end main */

static int WriteSignedDataMsg (CERTC_CTX ctx)
{
  int status = 0;
  CERT_OBJ signer = NULL;
  LIST_OBJ signedData = NULL, certs = NULL, signerInfoList = NULL;
  ITEM signerInfoBer = {NULL, 0}, signerInfos = {NULL, 0};
  ITEM certBer = {NULL, 0}, certificates = {NULL, 0};

  unsigned char version[] = {
    0x02, 0x01, 0x01
  };

  /* just SHA1 for now */
  unsigned char digestAlgs[] = {
    0x31, 0x0B, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E,
    0x03, 0x02, 0x1A, 0x05, 0x00
  };

  /* says we're signing a PKCS #7 data */
  unsigned char dataContent[] = {
    0x30, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
    0xF7, 0x0D, 0x01, 0x07, 0x01
  };

  ITEM versionItem, digestAlgsItem, dataContentItem;
  ITEM signedDataBer = {NULL, 0}, content = {NULL, 0};

  unsigned char signedDataOid[] = {
    0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
    0x01, 0x07, 0x02
  };

  ITEM contentType = {NULL, 0}, signedDataMsgBer = {NULL, 0};
  LIST_OBJ contentInfoList = NULL;

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

  /* Let's collect the signer's cert and private key and calculate the
     signature.  Here, we just have one signer. */
  status = GetSignerInfoBer (ctx, signer, &signerInfoBer);
  if (status != 0)
    goto CLEANUP;

  /* Get our certificates list for the SignedData.  For now, we will
     include the signer's cert only. */
  status = C_CreateListObject (&certs);
  if (status != 0)
    goto CLEANUP;

  status = C_GetCertDER (signer, &certBer.data, &certBer.len);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (certs, &certBer, NULL);
  if (status != 0)
    goto CLEANUP;

  /* This is our certificates [0] */
  status = C_DEREncodeList (ctx, 0, VTC_CONTEXT | VTC_CONSTRUCTED, certs,
                            &certificates.data, &certificates.len);
  if (status != 0)
    goto CLEANUP;

  /* No CRLs (for now, anyway) */

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

  status = C_AddItemToList (signerInfoList, &signerInfoBer, NULL);
  if (status != 0)
    goto CLEANUP;

  /* Put together our SignerInfos SET */
  status = C_DEREncodeList (ctx, VT_SET, VTC_UNIVERSAL, signerInfoList,
                            &signerInfos.data, &signerInfos.len);
  if (status != 0)
    goto CLEANUP;

  /* Now that we have all the pieces, let's assemble our SignedData */
  status = C_CreateListObject (&signedData);
  if (status != 0)
    goto CLEANUP;

  /* Since we've stated up front that this testcase uses SHA1 with RSA only,
     it's simple enough to hard-code this information.  A future modification
     could involve using C_WriteSignedDataMsg to create a "shell" using some
     temporary data to sign and we'd just swap out the signerInfos in the
     "dummy" message with the real SignerInfos that we calculate.  Using a 
     "shell" would also be good to handle the case where the info we sign
     isn't a PKCS #7 */
  versionItem.data = version;
  versionItem.len = sizeof (version);
  digestAlgsItem.data = digestAlgs;
  digestAlgsItem.len = sizeof (digestAlgs);
  dataContentItem.data = dataContent;
  dataContentItem.len = sizeof (dataContent);

  status = C_AddItemToList (signedData, &versionItem, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (signedData, &digestAlgsItem, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (signedData, &dataContentItem, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (signedData, &certificates, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (signedData, &signerInfos, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_DEREncodeList (ctx, VT_SEQUENCE, VTC_UNIVERSAL, signedData,
                            &signedDataBer.data, &signedDataBer.len);
  if (status != 0)
    goto CLEANUP;

  /* Now assemble the ContentInfo for our Signed Data */
  status = C_CreateListObject (&contentInfoList);
  if (status != 0)
    goto CLEANUP;

  contentType.data = signedDataOid;
  contentType.len = sizeof (signedDataOid);

  status = C_AddItemToList (contentInfoList, &contentType, NULL);
  if (status != 0)
    goto CLEANUP;

  /* We need the content [0] containing the signedDataBer */
  status = C_DEREncodeTagAndValue (ctx, 0, VTC_CONTEXT | VTC_CONSTRUCTED,
                                   signedDataBer.data, signedDataBer.len,
                                   0, NULL, &content.len);
  if (status != 0)
    goto CLEANUP;

  content.data = T_malloc (content.len);
  if (content.data == NULL) {
    status = RSA_DEMO_E_ALLOC;
    goto CLEANUP;
  }

  status = C_DEREncodeTagAndValue (ctx, 0, VTC_CONTEXT | VTC_CONSTRUCTED,
                                   signedDataBer.data, signedDataBer.len,
                                   content.len, content.data, &content.len);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (contentInfoList, &content, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_DEREncodeList (ctx, VT_SEQUENCE, VTC_UNIVERSAL, contentInfoList,
                            &signedDataMsgBer.data, &signedDataMsgBer.len);
  if (status != 0)
    goto CLEANUP;

  /* This signed data message doesn't need to be streamed to output, since
     it is a detached signature */
  status = RSA_WriteDataToFile
             (signedDataMsgBer.data, signedDataMsgBer.len,
              "Enter name of file to store detached signature");

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

  T_free (certificates.data);
  T_free (content.data);
  T_free (signerInfoBer.data);
  T_free (signerInfos.data);
  T_free (signedDataBer.data);
  T_free (signedDataMsgBer.data);
  
  C_DestroyCertObject (&signer);
  C_DestroyListObject (&certs);
  C_DestroyListObject (&signedData);
  C_DestroyListObject (&signerInfoList);
  C_DestroyListObject (&contentInfoList);
  
  return status;
}  /* end WriteSignedDataMsg */

static int WriteEnvelopedDataMsg (CERTC_CTX ctx)
{
  int status = 0;
  FILE *inputFile = NULL, *outputFile = NULL;

  SERVICE database = NULL;
  LIST_OBJ recipientInfos = NULL;
  ALGORITHM_IDENTIFIER contentEncryptionAlgId = {0, NULL};
  B_KEY_OBJ secretKey = NULL;

  char *temp = "temporary arbitrary data";
  ITEM sampleData = {NULL, 0}, sampleDataMsg = {NULL, 0};
  ITEM sampleEnvelopedData = {NULL, 0};
  
  int tag = 0;
  unsigned int tagClass = 0;

  ITEM *berItem = NULL, envelopedDataBer = {NULL, 0};
  LIST_OBJ contentInfo = NULL, envelopedData = NULL;
  LIST_OBJ encryptedContentInfo = NULL;

  /* This header information includes the ContentInfo SEQUENCE, the enveloped
     data content type, the content [0] tag, the EnvelopedData SEQUENCE, the
     EnvelopedData version. */
  unsigned char p7Header[] = {
    0x30, 0x80, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
    0xF7, 0x0D, 0x01, 0x07, 0x03, 0xA0, 0x80, 0x30,
    0x80, 0x02, 0x01, 0x00
  };

  /* EncryptedContentInfo header */
  unsigned char eciHeader[] = {
    0x30, 0x80
  };

  /* Resolve the indefinite-length encoding used by the ContentInfo SEQUENCE
     tag, the content [0] tag, the EnvelopedData SEQUENCE, and the
     EncryptedContentInfo SEQUENCE. */
  unsigned char p7Footer[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  };

  int (*DestroyAlgParams) (POINTER *bufPtr) = NULL;

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

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

  /* Get the certs of the recipients into the database */
  status = GetRecipientInfoList (ctx, database, recipientInfos);
  if (status != 0)
    goto CLEANUP;

  status = RSA_ChooseSymEncAlgPrompt (ctx, &contentEncryptionAlgId,
                                      &DestroyAlgParams);
  if (status != 0)
    goto CLEANUP;

  /* Create a "dummy" data message, representing the type of message that
     we are going to envelope.  This affects the value that appears in the
     EncryptedContentInfo's ContentType field. */
  sampleData.data = (POINTER)temp;
  sampleData.len = T_strlen (temp);
  status = C_WriteDataMsg (ctx, &sampleData, &sampleDataMsg);
  if (status != 0)
    goto CLEANUP;

  status = B_CreateKeyObject (&secretKey);
  if (status != 0)
    goto CLEANUP;

  status = C_WriteEnvelopedDataMsg (ctx, database, &sampleDataMsg,
                                    &contentEncryptionAlgId, recipientInfos,
                                    secretKey, NULL, NULL,
                                    &sampleEnvelopedData);
  if (status != 0)
    goto CLEANUP;

  /* Obtain handles for input and output */
  status = RSA_OpenDataFilePrompt
             (&inputFile, RSA_DEMO_READ_BINARY,
              "Enter name of file containing content to encrypt");
  if (status != 0)
    goto CLEANUP;

  status = RSA_OpenDataFilePrompt
             (&outputFile, RSA_DEMO_WRITE_BINARY,
              "Enter name of file to store PKCS #7 enveloped data message");
  if (status != 0)
    goto CLEANUP;

  /* Output everything up to the recipientInfos */
  status = RSA_AppendDataToFile (outputFile, p7Header, sizeof (p7Header));
  if (status != 0)
    goto CLEANUP;

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

  status = C_BERDecodeList (ctx, sampleEnvelopedData.data,
                            sampleEnvelopedData.len, &tag, &tagClass,
                            contentInfo);
  if (status != 0)
    goto CLEANUP;

  /* get pointer to content */
  status = C_GetListObjectEntry (contentInfo, 1, (POINTER *)&berItem);
  if (status != 0)
    goto CLEANUP;

  status = C_BERDecodeTagAndValue (ctx, berItem->data, berItem->len, &tag,
                                   &tagClass, &envelopedDataBer.data,
                                   &envelopedDataBer.len);
  if (status != 0)
    goto CLEANUP;

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

  /* get pointer to recipientInfos and output it */
  status = C_BERDecodeList (ctx, envelopedDataBer.data, envelopedDataBer.len,
                            &tag, &tagClass, envelopedData);
  if (status != 0)
    goto CLEANUP;

  status = C_GetListObjectEntry (envelopedData, 1, (POINTER *)&berItem);
  if (status != 0)
    goto CLEANUP;

  status = RSA_AppendDataToFile (outputFile, berItem->data, berItem->len);
  if (status != 0)
    goto CLEANUP;

  status = RSA_AppendDataToFile (outputFile, eciHeader, sizeof (eciHeader));
  if (status != 0)
    goto CLEANUP;

  /* get pointer to EncryptedContentInfo */
  status = C_GetListObjectEntry (envelopedData, 2, (POINTER *)&berItem);
  if (status != 0)
    goto CLEANUP;

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

  status = C_BERDecodeList (ctx, berItem->data, berItem->len, &tag, &tagClass,
                            encryptedContentInfo);
  if (status != 0)
    goto CLEANUP;

  /* Get EncryptedContentInfo.contentType */
  status = C_GetListObjectEntry (encryptedContentInfo, 0, (POINTER *)&berItem);
  if (status != 0)
    goto CLEANUP;

  status = RSA_AppendDataToFile (outputFile, berItem->data, berItem->len);
  if (status != 0)
    goto CLEANUP;

  status = C_GetListObjectEntry (encryptedContentInfo, 1, (POINTER *)&berItem);
  if (status != 0)
    goto CLEANUP;

  status = RSA_AppendDataToFile (outputFile, berItem->data, berItem->len);
  if (status != 0)
    goto CLEANUP;

  /* Read in the data from inputFile, writing out blocks of encrypted data
     to outputFile */
  status = OutputEncryptedContent (ctx, inputFile, outputFile, secretKey,
                                   &contentEncryptionAlgId);
  if (status != 0)
    goto CLEANUP;

  /* Output the final portion to resolve the indefinite-length encodings */
  status = RSA_AppendDataToFile (outputFile, p7Footer, sizeof (p7Footer));
  if (status != 0)
    goto CLEANUP;

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

  RSA_CloseDataFile (&inputFile);
  RSA_CloseDataFile (&outputFile);

  if (DestroyAlgParams)
    (*DestroyAlgParams) (&contentEncryptionAlgId.algorithmParam);

  T_free (sampleDataMsg.data);
  T_free (sampleEnvelopedData.data);
  B_DestroyKeyObject (&secretKey);
  C_DestroyListObject (&recipientInfos);
  C_DestroyListObject (&contentInfo);
  C_DestroyListObject (&envelopedData);
  C_DestroyListObject (&encryptedContentInfo);
  C_UnbindService (&database);
  
  return status;
}  /* end WriteEnvelopedDataMsg */

static int GetSignerInfoBer (CERTC_CTX ctx, CERT_OBJ signer, ITEM *signerInfoBer)
{
  int status = 0;

  B_KEY_OBJ privateKey = NULL;
  LIST_OBJ signerInfoList = NULL;
  
  unsigned char version1[] = {0x02, 0x01, 0x01};

  unsigned char sha1AlgId[] = {
    0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02,
    0x1A, 0x05, 0x00
  };

  unsigned char rsaAlgId[] = {
    0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
    0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00
  };
  
  ITEM versionItem, digestAlgId, encAlgId;
  ITEM issuerSerial = {NULL, 0}, encryptedDigest = {NULL, 0};

  signerInfoBer->data = NULL;
  signerInfoBer->len = 0;
  
  status = B_CreateKeyObject (&privateKey);
  if (status != 0)
    goto CLEANUP;
  
  RSA_PrintMessage ("Supply the signer's cert and private key.\n");
  status = RSA_GetCertAndPvtKey (ctx, signer, privateKey);
  if (status != 0)
    goto CLEANUP;

  /* Use this list object to hold the contents of the SignerInfo */
  status = C_CreateListObject (&signerInfoList);
  if (status != 0)
    goto CLEANUP;

  /* version 1 */
  versionItem.data = version1;
  versionItem.len = sizeof (version1);

  status = C_AddItemToList (signerInfoList, &versionItem, NULL);
  if (status != 0)
    goto CLEANUP;

  /* Add issuer name and serial number to identify signer's cert */
  status = EncodeIssuerSerial (ctx, signer, &issuerSerial);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (signerInfoList, &issuerSerial, NULL);
  if (status != 0)
    goto CLEANUP;

  /* We could also query Crypto-C using AI_SHA1/AI_SHA1_BER to get the
     SHA1 algorithm ID, but for the purposes of this example, we just
     hard code it. */
  digestAlgId.data = sha1AlgId;
  digestAlgId.len = sizeof (sha1AlgId);

  status = C_AddItemToList (signerInfoList, &digestAlgId, NULL);
  if (status != 0)
    goto CLEANUP;

  /* No authenticated attributes */
  
  /* Same story for the RSA Encryption algorithm identifier */
  encAlgId.data = rsaAlgId;
  encAlgId.len = sizeof (rsaAlgId);

  status = C_AddItemToList (signerInfoList, &encAlgId, NULL);
  if (status != 0)
    goto CLEANUP;

  /* Calculate the signature */
  status = GetEncryptedDigest (ctx, privateKey, &encryptedDigest);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (signerInfoList, &encryptedDigest, NULL);
  if (status != 0)
    goto CLEANUP;

  /* No unauthenticated attributes */

  status = C_DEREncodeList (ctx, VT_SEQUENCE, VTC_UNIVERSAL, signerInfoList,
                            &signerInfoBer->data, &signerInfoBer->len);
  
CLEANUP:
  if (status != 0) {
    T_free (signerInfoBer->data);
    signerInfoBer->data = NULL;
    signerInfoBer->len = 0;
    RSA_PrintError ("GetSignerInfoBer", status);
  }

  T_free (issuerSerial.data);
  T_free (encryptedDigest.data);
  
  C_DestroyListObject (&signerInfoList);
  B_DestroyKeyObject (&privateKey);

  return status;
}  /* end GetSignerInfoBer */

static int EncodeIssuerSerial (CERTC_CTX ctx, CERT_OBJ cert, ITEM *issuerSerial)
{
  int status = 0;
  int tag = 0;
  unsigned int tagClass = 0;
  unsigned int issuerNameIndex = 0, serialNumberIndex = 0;
  
  ITEM tbsCertBer = {NULL, 0};
  LIST_OBJ tbsCert = NULL, issuerSerialList = NULL;

  ITEM *firstEntry = NULL, *serial = NULL, *issuerBer = NULL;

  issuerSerial->data = NULL;
  issuerSerial->len = 0;

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

  status = C_CreateListObject (&issuerSerialList);
  if (status != 0)
    goto CLEANUP;
  
  status = C_GetCertInnerDER (cert, &tbsCertBer.data, &tbsCertBer.len);
  if (status != 0)
    goto CLEANUP;

  status = C_BERDecodeList (ctx, tbsCertBer.data, tbsCertBer.len, &tag,
                            &tagClass, tbsCert);
  if (status != 0)
    goto CLEANUP;

  /* Detect whether or not the version number is present.  A version number
     is present if the first entry in the tbsCertBer has a [0] tag. */
  status = C_GetListObjectEntry (tbsCert, 0, (POINTER *)&firstEntry);
  if (status != 0)
    goto CLEANUP;

  if (firstEntry->data[0] == 0xa0) {
    /* version number is present */
    issuerNameIndex = 3;
    serialNumberIndex = 1;
  } else {
    /* version number is not present */
    issuerNameIndex = 2;
    serialNumberIndex = 0;
  }

  status = C_GetListObjectEntry (tbsCert, issuerNameIndex,
                                 (POINTER *)&issuerBer);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (issuerSerialList, issuerBer, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_GetListObjectEntry (tbsCert, serialNumberIndex,
                                 (POINTER *)&serial);
  if (status != 0)
    goto CLEANUP;

  status = C_AddItemToList (issuerSerialList, serial, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_DEREncodeList (ctx, VT_SEQUENCE, VTC_UNIVERSAL, issuerSerialList,
                            &issuerSerial->data, &issuerSerial->len);
  
CLEANUP:
  if (status != 0) {
    T_free (issuerSerial->data);
    issuerSerial->data = NULL;
    issuerSerial->len = 0;
    RSA_PrintError ("EncodeIssuerSerial", status);
  }

  C_DestroyListObject (&tbsCert);
  C_DestroyListObject (&issuerSerialList);

  return status;
}  /* end EncodeIssuerSerial */

static int GetEncryptedDigest (CERTC_CTX ctx, B_KEY_OBJ privateKey,
                               ITEM *encryptedDigest)
{
  int status = 0, finished = 0;
  FILE *inputFile = NULL, *outputFile = NULL;
  B_ALGORITHM_OBJ signObj = NULL;
  B_ALGORITHM_CHOOSER chooser;

  unsigned char dataToSign[RSA_FILE_BLOCK_SIZE], signature[RSA_MAX_SIG_LEN];
  unsigned int dataToSignLen = 0, signatureLen = 0;

  /* The p7DataHeader consists of the SEQUENCE tag, PKCS #7 Data OID,
     content [0] tag, and the OCTET STRING tag.  Note that we have a
     constructed OCTET STRING tag because we can't use indefinite-length
     encoding with primitives. */
  unsigned char p7DataHeader[] = {
    0x30, 0x80, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
    0xF7, 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x80, 0x24,
    0x80 
  };

  /* Resolve the indefinite-length encoding used by the SEQUENCE tag,
     the content [0] tag, and the OCTET STRING tag. */
  unsigned char p7DataFooter[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  };

  /* for graceful cleanup in case of error */
  encryptedDigest->data = NULL;
  encryptedDigest->len = 0;

  status = RSA_OpenDataFilePrompt (&inputFile, RSA_DEMO_READ_BINARY,
                                   "Enter name of file to sign");
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("In this example, we will create a detached signature.");
  RSA_PrintMessage ("  We need to\nstore the PKCS #7 Data message ");
  RSA_PrintMessage ("containing the data to sign.\n");
  status = RSA_OpenDataFilePrompt
             (&outputFile, RSA_DEMO_WRITE_BINARY,
              "Enter name of file to store PKCS #7 Data message");
  if (status != 0)
    goto CLEANUP;  
  
  /* output the PKCS #7 data header to begin the outputFile */
  status = RSA_AppendDataToFile (outputFile, p7DataHeader,
                                 sizeof (p7DataHeader));
  if (status != 0)
    goto CLEANUP;

  /* set up our algorithm object for signing */
  status = B_CreateAlgorithmObject (&signObj);
  if (status != 0)
    goto CLEANUP;

  status = B_SetAlgorithmInfo (signObj, AI_SHA1WithRSAEncryption, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_GetChooser (ctx, &chooser);
  if (status != 0)
    goto CLEANUP;

  status = B_SignInit (signObj, privateKey, chooser, NULL);
  if (status != 0)
    goto CLEANUP;

  /* digest a block of data and output the contents for the PKCS #7 data
     until we finish going through the data to sign */
  while (finished == 0) {
    /* something's wrong if we need more than 10 bytes to encode the octet
       string tag and length. */
    unsigned char berTag[10];
    unsigned int berTagLen;
    
    status = RSA_GetDataFromFile (inputFile, sizeof (dataToSign), dataToSign,
                                  &dataToSignLen);
    if (status == 0)
      finished = 1; /* last block */
    else if (status != RSA_DEMO_E_MORE_DATA)
      goto CLEANUP;

    status = B_SignUpdate (signObj, dataToSign, dataToSignLen, NULL);
    if (status != 0)
      goto CLEANUP;

    /* Use C_DEREncodeTagAndValue here to get the tag and length
       bytes only. */

    /* Remember that our PKCS #7 data contained a constructed OCTET STRING.
       Here, we finish what we started... */
    status = C_DEREncodeTagAndValue (ctx, VT_OCTET_STRING, VTC_UNIVERSAL,
                                     NULL, dataToSignLen, sizeof (berTag),
                                     berTag, &berTagLen);
    if (status != 0)
      goto CLEANUP;

    status = RSA_AppendDataToFile (outputFile, berTag, berTagLen);
    if (status != 0)
      goto CLEANUP;
    
    status = RSA_AppendDataToFile (outputFile, dataToSign, dataToSignLen);
    if (status != 0)
      goto CLEANUP;    
  }

  /* output the PKCS #7 data header to finish the outputFile */
  status = RSA_AppendDataToFile (outputFile, p7DataFooter,
                                 sizeof (p7DataFooter));
  if (status != 0)
    goto CLEANUP;

  status = B_SignFinal (signObj, signature, &signatureLen, sizeof (signature),
                        NULL, NULL);
  if (status != 0)
    goto CLEANUP;
  
  /* the encryptedDigest is an OCTET STRING containing the signature */
  status = C_DEREncodeString (ctx, VT_OCTET_STRING, VTC_UNIVERSAL, signature,
                              signatureLen, &encryptedDigest->data,
                              &encryptedDigest->len);

CLEANUP:
  if (status != 0) {
    T_free (encryptedDigest->data);
    encryptedDigest->data = NULL;
    encryptedDigest->len = 0;
    RSA_PrintError ("GetEncryptedDigest", status);
  }

  B_DestroyAlgorithmObject (&signObj);

  RSA_CloseDataFile (&inputFile);
  RSA_CloseDataFile (&outputFile);  
  
  return status;
}  /* end GetEncryptedDigest */

static int GetRecipientInfoList (CERTC_CTX ctx, SERVICE database,
                                 LIST_OBJ recipientInfos)
{
  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);
    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 GetRecipientInfoList */

static int OutputEncryptedContent
      (CERTC_CTX ctx, FILE *inputFile, FILE *outputFile, B_KEY_OBJ secretKey,
       ALGORITHM_IDENTIFIER *contentEncryptionAlgId)
{
  int status = 0, finished = 0;

  B_ALGORITHM_OBJ encryptor = NULL;
  B_ALGORITHM_CHOOSER chooser;

  unsigned char dataToEncrypt[RSA_FILE_BLOCK_SIZE];
  unsigned char cipherBlock[RSA_FILE_BLOCK_SIZE + RSA_MAX_BLOCK_SIZE];
  unsigned int dataToEncryptLen = 0, cipherBlockLen = 0;

  /* EncryptedContentInfo.encryptedContent [0] */
  unsigned char header[] = {
    0xA0, 0x80
  };

  /* resolve the indefinite-length encoding used in the header */
  unsigned char footer [] = {
    0x00, 0x00
  };

  status = RSA_AppendDataToFile (outputFile, header, sizeof (header));
  if (status != 0)
    goto CLEANUP;

  status = B_CreateAlgorithmObject (&encryptor);
  if (status != 0)
    goto CLEANUP;

  switch (contentEncryptionAlgId->algorithmId) {
    case EAI_DES3:
      status = B_SetAlgorithmInfo (encryptor, AI_DES_EDE3_CBCPadIV8,
                                   contentEncryptionAlgId->algorithmParam);
      break;
    case EAI_RC2:
      status = B_SetAlgorithmInfo (encryptor, AI_RC2_CBCPad,
                                   contentEncryptionAlgId->algorithmParam);
      break;
    case EAI_RC4:
      status = B_SetAlgorithmInfo (encryptor, AI_RC4,
                                   contentEncryptionAlgId->algorithmParam);
      break;
    case EAI_RC5:
      status = B_SetAlgorithmInfo (encryptor, AI_RC5_CBCPad,
                                   contentEncryptionAlgId->algorithmParam);
      break;
    case EAI_DES:
      status = B_SetAlgorithmInfo (encryptor, AI_DES_CBCPadIV8,
                                   contentEncryptionAlgId->algorithmParam);
      break;
    default:
      /* explicitly add support for other EAI_* identifiers as they become
         available */
      status = RSA_DEMO_E_NOT_IMPLEMENTED;
  }
  if (status != 0)
    goto CLEANUP;

  status = C_GetChooser (ctx, &chooser);
  if (status != 0)
    goto CLEANUP;

  status = B_EncryptInit (encryptor, secretKey, chooser, NULL);
  if (status != 0)
    goto CLEANUP;

  /* encrypt a block of data and output the contents in blocks */
  while (finished == 0) {
    /* something's wrong if we need more than 10 bytes to encode the octet
       string tag and length */
    unsigned char berTag[10];
    unsigned int berTagLen;

    status = RSA_GetDataFromFile (inputFile, sizeof (dataToEncrypt),
                                  dataToEncrypt, &dataToEncryptLen);
    if (status == 0)
      finished = 1; /* last block */
    else if (status != RSA_DEMO_E_MORE_DATA)
      goto CLEANUP;

    status = B_EncryptUpdate (encryptor, cipherBlock, &cipherBlockLen,
                              sizeof (cipherBlock), dataToEncrypt,
                              dataToEncryptLen, NULL, NULL);
    if (status != 0)
      goto CLEANUP;

    /* Use C_DEREncodeTagAndValue to get the tag and lenght bytes only.
       Remember that these primitive OCTET STRINGS are contained in the
       encryptedContent constructed OCTET STRINGS... */
    status = C_DEREncodeTagAndValue (ctx, VT_OCTET_STRING, VTC_UNIVERSAL,
                                     NULL, cipherBlockLen, sizeof (berTag),
                                     berTag, &berTagLen);
    if (status != 0)
      goto CLEANUP;

    status = RSA_AppendDataToFile (outputFile, berTag, berTagLen);
    if (status != 0)
      goto CLEANUP;

    status = RSA_AppendDataToFile (outputFile, cipherBlock, cipherBlockLen);
    if (status != 0)
      goto CLEANUP;

    if (finished == 1) { /* take care of the last block */
      status = B_EncryptFinal (encryptor, cipherBlock, &cipherBlockLen,
                               sizeof (cipherBlock), NULL, NULL);
      if (status != 0)
        goto CLEANUP;

      status = C_DEREncodeTagAndValue (ctx, VT_OCTET_STRING, VTC_UNIVERSAL,
                                       NULL, cipherBlockLen, sizeof (berTag),
                                       berTag, &berTagLen);
      if (status != 0)
        goto CLEANUP;

      status = RSA_AppendDataToFile (outputFile, berTag, berTagLen);
      if (status != 0)
        goto CLEANUP;

      status = RSA_AppendDataToFile (outputFile, cipherBlock, cipherBlockLen);
      if (status != 0)
        goto CLEANUP;
    }
  }

  status = RSA_AppendDataToFile (outputFile, footer, sizeof (footer));
  if (status != 0)
    goto CLEANUP;

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

  B_DestroyAlgorithmObject (&encryptor);

  return status;
}  /* end OutputEncryptedContent */

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