RSA BSAFE Cert-C

Certificate Components for C

Crypto-C 6.2.1 Developer's Guide
Search

datamsg.c

Decomposes a PKCS #7 message. Creates PKCS #7 data message. Creates a a PKCS #7 signed data message. Creates a PKCS #7 enveloped data message. Creates a PKCS #7 digested data message. Creates a PKCS #7 encrypted data message.

/* $Id: datamsg.c,v 1.4 2004/03/02 05:18:41 gsingh Exp $ */
/* datamsg.c
** Copyright (c) 1999-2002, 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.
**
** 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"

/*  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 DecomposePkcs7 (CERTC_CTX ctx);

/*  Note that for the following functions, the caller must free the data field
 *  of the contents output.
 */
static int DecomposeDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                             ITEM *contents);
static int DecomposeSignedDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                                   ITEM *contents);
static int DecomposeEnvelopedDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                                      ITEM *contents);
static int DecomposeDigestedDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                                     ITEM *contents);
static int DecomposeEncryptedDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                                      ITEM *contents);

static int WriteDataMsg (CERTC_CTX ctx);
static int WriteSignedDataMsg (CERTC_CTX ctx);
static int WriteEnvelopedDataMsg (CERTC_CTX ctx);
static int WriteDigestedDataMsg (CERTC_CTX ctx);
static int WriteEncryptedDataMsg (CERTC_CTX ctx);

/*  Used by WriteSignedDataMsg, this procedure prompts the user for the
 *  necessary information to obtain the signerInfos.  The private keys and
 *  certificates of the signers will be placed in the database.
 */
static int GetSignerInfoList (CERTC_CTX ctx, SERVICE database,
                              LIST_OBJ signerInfos);

/*  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);

/*  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
  };

  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 data message\n");
    RSA_PrintMessage ("  C - Create PKCS #7 signed data message\n");
    RSA_PrintMessage ("  D - Create PKCS #7 enveloped data message\n");
    RSA_PrintMessage ("  E - Create PKCS #7 digested data message\n");
    RSA_PrintMessage ("  F - Create PKCS #7 encrypted 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 = WriteDataMsg (ctx);
        break;
      case 'c':
      case 'C':
        status = WriteSignedDataMsg (ctx);
        break;
      case 'd':
      case 'D':
        status = WriteEnvelopedDataMsg (ctx);
        break;
      case 'e':
      case 'E':
        status = WriteDigestedDataMsg (ctx);
        break;
      case 'f':
      case 'F':
        status = WriteEncryptedDataMsg (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 ("datamsg", status);

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

static int DecomposePkcs7 (CERTC_CTX ctx)
{
  int status = 0;

  ITEM fileData = {NULL, 0}, pkcs7Oid = {NULL, 0}, outputData = {NULL, 0};

  status = RSA_GetFileToAllocBuffer (&fileData.data, &fileData.len,
    "Enter name of file containing PKCS #7 message\n(blank to cancel)");
  if (status != 0)
    goto CLEANUP;

  status = C_ReadMessageType (ctx, &fileData, &pkcs7Oid);  
  if (status != 0)
    goto CLEANUP;
  
  for (;;) {
    if (fileData.len == 0)
      break;  /* no more data... */

    if ((pkcs7Oid.len == CT_ID_DATA_LEN) &&
        (T_memcmp (pkcs7Oid.data, CT_ID_DATA, CT_ID_DATA_LEN) == 0))
      status = DecomposeDataMsg (ctx, &fileData, &pkcs7Oid, &outputData);
    else if ((pkcs7Oid.len == CT_ID_SIGNED_DATA_LEN) &&
               (T_memcmp (pkcs7Oid.data, CT_ID_SIGNED_DATA,
                          CT_ID_SIGNED_DATA_LEN) == 0))
      status = DecomposeSignedDataMsg (ctx, &fileData, &pkcs7Oid, &outputData);
    else if ((pkcs7Oid.len == CT_ID_ENVELOPED_DATA_LEN) &&
             (T_memcmp (pkcs7Oid.data, CT_ID_ENVELOPED_DATA,
                        CT_ID_ENVELOPED_DATA_LEN) == 0))
      status = DecomposeEnvelopedDataMsg (ctx, &fileData, &pkcs7Oid,
                                          &outputData);
    else if ((pkcs7Oid.len == CT_ID_DIGESTED_DATA_LEN) &&
             (T_memcmp (pkcs7Oid.data, CT_ID_DIGESTED_DATA,
                        CT_ID_DIGESTED_DATA_LEN) == 0))
      status = DecomposeDigestedDataMsg (ctx, &fileData, &pkcs7Oid,
                                         &outputData);
    else if ((pkcs7Oid.len == CT_ID_ENCRYPTED_DATA_LEN) &&
             (T_memcmp (pkcs7Oid.data, CT_ID_ENCRYPTED_DATA,
                        CT_ID_ENCRYPTED_DATA_LEN) == 0))
      status = DecomposeEncryptedDataMsg (ctx, &fileData, &pkcs7Oid,
                                          &outputData);
    else {
      status = 0;
      break;
    }

    if (status != 0)
      break;

    /* loop around to see if there is another contentInfo contained in the
     * data that was just extracted
     */
    T_free (fileData.data);
    fileData.data = outputData.data;
    fileData.len = outputData.len;
    outputData.data = NULL;
    outputData.len = 0;
  }

  if (status != 0)
    goto CLEANUP;
  
  RSA_PrintMessage ("No more PKCS #7 ContentInfos to decode.\nExiting.\n");

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

  T_free (outputData.data);
  T_free (fileData.data);

  return status;
}  /* end DecomposePkcs7 */

static int DecomposeDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                             ITEM *contents)
{
  int status = 0;

  oid->data = NULL;
  oid->len = 0;
  contents->data = NULL;
  contents->len = 0;
  RSA_PrintMessage ("Reading PKCS #7 Data message...\n");

  status = C_ReadDataMsg (ctx, dataMsg, contents);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile (contents->data, contents->len,
                                "Enter name of file to store extracted data");
  if (status != 0 && status != RSA_DEMO_E_CANCEL)
    goto CLEANUP;

  /* check if any other contentInfos are inside */
  status = C_ReadMessageType (ctx, contents, oid);
  if (status != 0) {
    oid->data = NULL;
    oid->len = 0;
    status = 0;  /* it's okay to have no more content */
    goto CLEANUP;
  }

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

  return status;
}  /* end DecomposeDataMsg */

static int DecomposeSignedDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                                   ITEM *contents)
{
  int status = 0;
  unsigned int i = 0;
  UINT4 cmsOptions = CMSF_NONE;
  SERVICE database = NULL;
  LIST_OBJ certs = NULL, crls = NULL;
  LIST_OBJ verifiedSigners = NULL, untrustedSigners = NULL;

  CERT_OBJ certObj = NULL;
  unsigned char *certBer = NULL;
  unsigned int certBerLen = 0, certCount = 0;

  CRL_OBJ crlObj = NULL;
  unsigned char *crlBer = NULL;
  unsigned int crlBerLen = 0, crlCount = 0;

  SIGNER_INFO *signerInfo = NULL;
  unsigned int signerInfoCount = 0;

  ITEM externMsg = {NULL, 0};
  
  oid->data = NULL;
  oid->len = 0;
  contents->data = NULL;
  contents->len = 0;
  RSA_PrintMessage ("Reading PKCS #7 Signed Data message...\n");

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

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

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

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

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

  RSA_PrintMessage ("Optionally supply any additional certs needed to verify");
  RSA_PrintMessage (" the message.\n");
  status = RSA_AddCertsToDbPrompt (ctx, database);
  if (status != 0)
    goto CLEANUP;
  
  /*  If we are dealing with a detached signature, setting the cmsOptions
   *  argument to C_ReadSignedDataMsg with CMSF_EXTERNAL_SIGNATURE will cause
   *  the data argument to be treated as an input parameter.
   */
  RSA_PrintMessage ("Enter name of PKCS #7 binary if this is an external ");
  status = RSA_GetFileToAllocBuffer
           (&externMsg.data, &externMsg.len, "signature\n(blank otherwise)");
  if (status != 0 && status != RSA_DEMO_E_CANCEL)
    goto CLEANUP;

  if (externMsg.len != 0) {  /* we have an external signature */
    cmsOptions |= CMSF_EXTERNAL_SIGNATURE;
    contents->data = externMsg.data;
    contents->len = externMsg.len;
  }

  RSA_PrintMessage ("Setting CMSF_VERIFY_SIGNER_CERTS...\n");
  status = C_ReadSignedDataMsg (ctx, &P7_PATH_CTX, database, dataMsg,
                                cmsOptions | CMSF_VERIFY_SIGNER_CERTS,
                                contents, oid, certs, crls,
                                verifiedSigners, untrustedSigners);
  if (status != 0) {
    RSA_PrintMessage ("Could not verify signer certs. (status = 0x%x)\n",
                      status);
    RSA_PrintMessage ("Try again without CMSF_VERIFY_SIGNER_CERTS...\n");

    C_ResetListObject (certs);
    C_ResetListObject (crls);
    C_ResetListObject (verifiedSigners);
    C_ResetListObject (untrustedSigners);

    status = C_ReadSignedDataMsg (ctx, &P7_PATH_CTX, database, dataMsg,
                                  cmsOptions, contents, oid, certs, crls,
                                  verifiedSigners, untrustedSigners);
    if (status == E_NOT_FOUND)
      RSA_PrintMessage ("Could not find signer's cert to verify signature.\n");
    else if (status == E_VERIFY_ASN_SIGNATURE)
      RSA_PrintMessage ("Could not verify signature.\n");

    if (status != 0)
      goto CLEANUP;
  }

  if (externMsg.len != 0) {
    /* if we had an external signature, reset contents for caller */
    contents->data = NULL;
    contents->len = 0;
  }

  status = C_GetListObjectCount (certs, &certCount);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("The PKCS #7 message contains %u certificates.\n",
                    certCount);

  for (i = 0; i < certCount; i++) {
    status = C_GetListObjectEntry (certs, i, (POINTER *)&certObj);
    if (status != 0)
      goto CLEANUP;

    status = C_GetCertDER (certObj, &certBer, &certBerLen);
    if (status != 0)
      goto CLEANUP;

    status = RSA_WriteDataToFile
             (certBer, certBerLen, "Enter name of file to store cert binary");
    if (status != 0 && status != RSA_DEMO_E_CANCEL)
      goto CLEANUP;
  }

  status = C_GetListObjectCount (crls, &crlCount);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("The PKCS #7 message contains %u CRLs.\n", crlCount);

  for (i = 0; i < crlCount; i++) {
    status = C_GetListObjectEntry (crls, i, (POINTER *)&crlObj);
    if (status != 0)
      goto CLEANUP;

    status = C_GetCRLDER (crlObj, &crlBer, &crlBerLen);
    if (status != 0)
      goto CLEANUP;

    status = RSA_WriteDataToFile
             (crlBer, crlBerLen, "Enter name of file to store CRL binary");
    if (status != 0 && status != RSA_DEMO_E_CANCEL)
      goto CLEANUP;
  }
  
  status = C_GetListObjectCount (verifiedSigners, &signerInfoCount);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("There are %u trusted signers.\n", signerInfoCount);  
  
  for (i = 0; i < signerInfoCount; i++) {
    status = C_GetListObjectEntry (verifiedSigners, i, (POINTER *)&signerInfo);
    if (status != 0)
      goto CLEANUP;

    RSA_PrintMessage ("\n--Trusted Signer #%u\n", i+1);
    status = RSA_PrintCertId (signerInfo->signerCertId);
    if (status != 0)
      goto CLEANUP;
  }
  
  status = C_GetListObjectCount (untrustedSigners, &signerInfoCount);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("\nThere are %u untrusted signers.\n", signerInfoCount);  
  
  for (i = 0; i < signerInfoCount; i++) {
    status = C_GetListObjectEntry (untrustedSigners, i,
                                   (POINTER *)&signerInfo);
    if (status != 0)
      goto CLEANUP;

    RSA_PrintMessage ("\n--Untrusted Signer #%u\n", i+1);
    status = RSA_PrintCertId (signerInfo->signerCertId);
    if (status != 0)
      goto CLEANUP;
  }
  
  status = RSA_WriteDataToFile
           (contents->data, contents->len,
            "\nEnter name of file to store extracted data");
  if (status == RSA_DEMO_E_CANCEL)
    status = 0;  /* it isn't required */
  
CLEANUP:
  if (status != 0) {
    /* contents->data is written if we don't have an external signature */
    if (externMsg.len == 0)
      T_free (contents->data);

    contents->data = NULL;
    contents->len = 0;
    oid->data = NULL;
    oid->len = 0;    
    RSA_PrintError ("DecomposeSignedDataMsg", status);
  }

  T_free (externMsg.data);
  C_UnbindService (&database);
  C_DestroyListObject (&certs);
  C_DestroyListObject (&crls);
  C_DestroyListObject (&verifiedSigners);
  C_DestroyListObject (&untrustedSigners);  
  
  return status;
}  /* end DecomposeSignedDataMsg */

static int DecomposeEnvelopedDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                                      ITEM *contents)
{
  int status = 0;
  char *nameString = NULL;
  SERVICE database = NULL;

  B_KEY_OBJ recipientPvtKey = NULL;
  CERT_OBJ recipientCert = NULL;
  
  RECIPIENT_INFO recipientInfo;

  oid->data = NULL;
  oid->len = 0;
  contents->data = NULL;
  contents->len = 0;
  RSA_PrintMessage ("Reading PKCS #7 Enveloped Data message...\n\n");
  RSA_PrintMessage ("Supply the recipient's certificate and private key to ");
  RSA_PrintMessage ("open the enveloped\nmessage.  ");

  status = C_BindService (ctx, SPT_DATABASE, RSA_DEMO_DATABASE_NAME,
                          &database);
  if (status != 0)
    goto CLEANUP;
  
  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;
  
  status = C_ReadEnvelopedDataMsg (ctx, database, dataMsg, contents, oid,
                                   &recipientInfo, NULL, NULL, NULL, NULL);
  if (status != 0)
    goto CLEANUP;

  status = C_GetNameString
           (recipientInfo.info.keyTrans.recipCertId.id.issuerSerialNumber.
            issuerName, &nameString);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("Recipient Info:\nIssuer = %s\n", nameString);
  RSA_PrintBuf ("Serial Number", recipientInfo.info.keyTrans.recipCertId.
                id.issuerSerialNumber.serialNumber.data, recipientInfo.
                info.keyTrans.recipCertId.id.issuerSerialNumber.
                serialNumber.len);
  
  status = RSA_WriteDataToFile
           (contents->data, contents->len,
            "Enter name of file to store decrypted message");
  if (status == RSA_DEMO_E_CANCEL)
    status = 0;  /* it isn't required */
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("DecomposeEnvelopedDataMsg", status);

  C_UnbindService (&database);
  C_FreeRecipientInfo (&recipientInfo);
  C_DestroyCertObject (&recipientCert);
  B_DestroyKeyObject (&recipientPvtKey);
  
  return status;
}  /* end DecomposeEnvelopedDataMsg */

static int DecomposeDigestedDataMsg (CERTC_CTX ctx, ITEM *dataMsg,
                                     ITEM *oid, ITEM *contents)
{
  int status = 0;

  oid->data = NULL;
  oid->len = 0;
  contents->data = NULL;
  contents->len = 0;  
  RSA_PrintMessage ("Reading PKCS #7 Digested Data message...\n");
  
  status = C_ReadDigestedDataMsg (ctx, dataMsg, contents, oid);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile (contents->data, contents->len,
                                "Enter name of file to store extracted data");
  if (status == RSA_DEMO_E_CANCEL)
    status = 0;  /* it isn't required */

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

  return status;
}  /* end DecomposeDigestedDataMsg */

static int DecomposeEncryptedDataMsg (CERTC_CTX ctx, ITEM *dataMsg, ITEM *oid,
                                      ITEM *contents)
{
  int status = 0;
  ITEM keyData = {NULL, 0};
  B_KEY_OBJ symmetricKey = NULL;

  oid->data = NULL;
  oid->len = 0;
  contents->data = NULL;
  contents->len = 0;
  RSA_PrintMessage ("Reading PKCS #7 Encrypted Data message...\n\n");
  RSA_PrintMessage ("For this type of message, it is assumed that the ");
  RSA_PrintMessage ("recipient has access to the\nsymmetric decryption key.");
  RSA_PrintMessage ("  Otherwise, a PKCS #7 enveloped message should be\n");
  RSA_PrintMessage ("used.\n\n");

  status = RSA_GetFileToAllocBuffer
           (&keyData.data, &keyData.len,
            "Enter name of file containing symmetric key bytes");
  if (status != 0)
    goto CLEANUP;

  RSA_PrintBuf ("Key Bytes", keyData.data, keyData.len);
  
  status = B_CreateKeyObject (&symmetricKey);
  if (status != 0)
    goto CLEANUP;

  status = B_SetKeyInfo (symmetricKey, KI_Item, (POINTER)&keyData);
  if (status != 0)
    goto CLEANUP;

  status = C_ReadEncryptedDataMsg (ctx, dataMsg, symmetricKey, contents, oid,
                                   NULL);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile (contents->data, contents->len,
                                "Enter name of file to store decrypted data");
  if (status == RSA_DEMO_E_CANCEL)
    status = 0;  /* it isn't required */
  
CLEANUP:
  if (status != 0) {
    T_free (contents->data);
    contents->data = NULL;
    contents->len = 0;
    oid->data = NULL;
    oid->len = 0;    
    RSA_PrintError ("DecomposeEncryptedDataMsg", status);
  }

  T_memset (keyData.data, 0, keyData.len);
  T_free (keyData.data);
  B_DestroyKeyObject (&symmetricKey);
  
  return status;
}  /* end DecomposeEncryptedDataMsg */

static int WriteDataMsg (CERTC_CTX ctx)
{
  int status = 0;
  ITEM fileData = {NULL, 0}, dataMsg = {NULL, 0};

  RSA_PrintMessage ("Enter name of file to encapsulate in data message\n");
  RSA_PrintMessage ("(blank for empty message): ");
  status = RSA_GetFileToAllocBuffer (&fileData.data, &fileData.len, NULL);
  if (status == RSA_DEMO_E_CANCEL)
    status = 0;  /* blank message, leave fileData empty */
  else if (status != 0)
    goto CLEANUP;

  status = C_WriteDataMsg (ctx, &fileData, &dataMsg);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile
           (dataMsg.data, dataMsg.len,
            "Enter name of file to store PKCS #7 data message");
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("WriteDataMsg", status);

  T_free (fileData.data);
  T_free (dataMsg.data);
  
  return status;
}  /* end WriteDataMsg */

static int WriteSignedDataMsg (CERTC_CTX ctx)
{
  int status = 0;
  char command[RSA_DEMO_MAX_LINE_LEN];
  ITEM fileData = {NULL, 0}, signedDataMsg = {NULL, 0};
  SERVICE database = NULL;
  LIST_OBJ signerInfos = NULL, certs = NULL, crls = NULL;
  UINT4 cmsOptions = CMSF_NONE;

  unsigned char *certBer = NULL, *crlBer = NULL;
  unsigned int certBerLen = 0, crlBerLen = 0;
  CERT_OBJ certObj = NULL;
  CRL_OBJ crlObj = NULL;
  
  RSA_PrintMessage ("\nFor a certs-only message, or CRL-only message, it is ");
  RSA_PrintMessage ("not necessary to\nspecify signers.  The file supplied ");
  RSA_PrintMessage ("here must be a BER-encoded PKCS #7\nContentInfo.  In ");
  RSA_PrintMessage ("the case of a certs-only or CRLs-only message, a blank");
  RSA_PrintMessage ("\nPKCS #7 Data message should be given.  The most ");
  RSA_PrintMessage ("common scenario is for a\ntext message to be ");
  RSA_PrintMessage ("encapsulated in a PKCS #7 Data message first (option B");
  RSA_PrintMessage ("\nin previous menu), then passed here to be signed.\n\n");
  status = RSA_GetFileToAllocBuffer
           (&fileData.data, &fileData.len,
            "Enter name of PKCS #7 binary to sign (blank to cancel)");
  if (status != 0)
    goto CLEANUP;
  
  status = RSA_GetRequiredCommand (command, sizeof (command),
                                   "Create a detached signature? (y/n)");
  if (status != 0)
    goto CLEANUP;

  if (command[0] == 'y')
    cmsOptions |= CMSF_EXTERNAL_SIGNATURE;

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

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

  status = GetSignerInfoList (ctx, database, signerInfos);
  if (status != 0)
    goto CLEANUP;

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

  RSA_PrintMessage ("\nOptionally supply certs for signed data message.\n");
  for (;;) {
    status = RSA_GetFileToAllocBuffer
             (&certBer, &certBerLen,
              "Enter name of certificate binary (blank when finished)");
    if (status == RSA_DEMO_E_CANCEL)
      break;
    else if (status != 0)
      goto CLEANUP;

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

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

    status = C_AddCertToList (certs, certObj, NULL);
    if (status != 0)
      goto CLEANUP;

    T_free (certBer);
    certBer = NULL;
    C_DestroyCertObject (&certObj);
  }

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

  RSA_PrintMessage ("\nOptionally supply CRLs for signed data message.\n");
  for (;;) {
    status = RSA_GetFileToAllocBuffer
             (&crlBer, &crlBerLen,
              "Enter name of CRL binary (blank when finished)");
    if (status == RSA_DEMO_E_CANCEL)
      break;
    else if (status != 0)
      goto CLEANUP;

    status = C_CreateCRLObject (&crlObj, ctx);
    if (status != 0)
      goto CLEANUP;

    status = C_SetCRLBER (crlObj, crlBer, crlBerLen);
    if (status != 0)
      goto CLEANUP;

    status = C_AddCRLToList (crls, crlObj, NULL);
    if (status != 0)
      goto CLEANUP;

    T_free (crlBer);
    crlBer = NULL;
    C_DestroyCRLObject (&crlObj);
  }

  status = C_WriteSignedDataMsg (ctx, NULL, database, &fileData, cmsOptions,
                                 certs, crls, signerInfos, &signedDataMsg);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile
           (signedDataMsg.data, signedDataMsg.len,
            "Enter name of file to store PKCS #7 signed data message");
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("WriteSignedDataMsg", status);

  T_free (fileData.data);
  T_free (certBer);
  T_free (signedDataMsg.data);
  C_DestroyCertObject (&certObj);
  C_DestroyCRLObject (&crlObj);
  C_DestroyListObject (&signerInfos);
  C_DestroyListObject (&certs);
  C_DestroyListObject (&crls);
  C_UnbindService (&database);

  /* note that nothing has been deleted from the database... */
  
  return status;
}  /* end WriteSignedDataMsg */

static int WriteEnvelopedDataMsg (CERTC_CTX ctx)
{
  int status = 0;

  ITEM fileData = {NULL, 0}, envelopedData = {NULL, 0};
  SERVICE database = NULL;
  LIST_OBJ recipientInfos = NULL;
  ALGORITHM_IDENTIFIER contentEncryptionAlgId = {0, NULL};

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

  RSA_PrintMessage ("\nThe file optionally supplied here must be a BER-");
  RSA_PrintMessage ("encoded PKCS #7 ContentInfo.\nThe most common scenario ");
  RSA_PrintMessage ("is for a text message to be encapsulated in a PKCS #7\n");
  RSA_PrintMessage ("Data message first (option B in previous menu), then ");
  RSA_PrintMessage ("passed here to be\nenveloped.\n\n");
  status = RSA_GetFileToAllocBuffer
           (&fileData.data, &fileData.len,
            "Enter name of PKCS #7 binary to envelope (blank to cancel)");
  if (status != 0)
    goto CLEANUP;

  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;

  /*  If you want to use a particular symmetric key, a B_KEY_OBJ which has
   *  been created and set with the appropriate key information can be passed
   *  in as the sixth argument.  On the other hand, if you want to have Cert-C
   *  create a key for you, you can use the sixth argument to retrieve the key
   *  that was used to encrypt the data message.  Declare a B_KEY_OBJ (don't
   *  call B_CreateKeyObject), be sure that it is set to NULL and pass it in
   *  as the sixth argument.  Following this call, that B_KEY_OBJ should be
   *  treated as a read-only argument which can be used with B_GetKeyInfo and
   *  KI_Item.  Here, we let Cert-C generate the key and don't want the key
   *  returned in the clear, so we just pass in NULL for the sixth argument.
   */
  status = C_WriteEnvelopedDataMsg (ctx, database, &fileData,
                                    &contentEncryptionAlgId, recipientInfos,
                                    NULL, NULL, NULL, &envelopedData);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile (envelopedData.data, envelopedData.len,
                                "Enter name of file to store enveloped data");

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

  if (DestroyAlgParams)
    (*DestroyAlgParams) (&contentEncryptionAlgId.algorithmParam);
  T_free (fileData.data);
  T_free (envelopedData.data);
  C_DestroyListObject (&recipientInfos);
  C_UnbindService (&database);
  
  return status;
}  /* end WriteEnvelopedDataMsg */

static int WriteDigestedDataMsg (CERTC_CTX ctx)
{
  int status = 0;
  ITEM fileData = {NULL, 0}, digestedData = {NULL, 0};
  ALGORITHM_IDENTIFIER digestAlgId = {0, NULL};

  RSA_PrintMessage ("\nThe file optionally supplied here must be a BER-");
  RSA_PrintMessage ("encoded PKCS #7 ContentInfo.\nThe most common scenario ");
  RSA_PrintMessage ("is for a text message to be encapsulated in a PKCS #7\n");
  RSA_PrintMessage ("Data message first (option B in previous menu), then ");
  RSA_PrintMessage ("passed here to be\ndigested.\n\n");  
  status = RSA_GetFileToAllocBuffer
           (&fileData.data, &fileData.len,
            "Enter name of PKCS #7 binary to digest (blank to cancel)");
  if (status != 0)
    goto CLEANUP;

  status = RSA_ChooseDigestAlgorithmPrompt (&digestAlgId);
  if (status != 0)
    goto CLEANUP;

  status = C_WriteDigestedDataMsg (ctx, &fileData, digestAlgId.algorithmId,
                                   &digestedData);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile (digestedData.data, digestedData.len,
                                "Enter name of file to store digested data");
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("WriteDigestedDataMsg", status);

  T_free (fileData.data);
  T_free (digestedData.data);
  
  return status;
}  /* end WriteDigestedDataMsg */

static int WriteEncryptedDataMsg (CERTC_CTX ctx)
{
  int status = 0;
  ITEM fileData = {NULL, 0}, encryptedData = {NULL, 0};
  ALGORITHM_IDENTIFIER encryptionAlgId = {0, NULL};
  B_KEY_OBJ encryptionKey = NULL;

  ITEM *keyInfo = NULL;
  
  int (*destroyAlgParams) (POINTER *algIdParams) = NULL;
  
  RSA_PrintMessage ("\nThe file optionally supplied here must be a BER-");
  RSA_PrintMessage ("encoded PKCS #7 ContentInfo.\nThe most common scenario ");
  RSA_PrintMessage ("is for a text message to be encapsulated in a PKCS #7\n");
  RSA_PrintMessage ("Data message first (option B in previous menu), then ");
  RSA_PrintMessage ("passed here to be\ndigested.\n\n");  
  status = RSA_GetFileToAllocBuffer
           (&fileData.data, &fileData.len,
            "Enter name of PKCS #7 binary to encrypt (blank to cancel)");
  if (status != 0)
    goto CLEANUP;

  status = RSA_ChooseSymEncAlgPrompt (ctx, &encryptionAlgId, &destroyAlgParams);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintMessage ("\nFor this type of PKCS #7 message, it is assumed that ");
  RSA_PrintMessage ("the symmetric key will\nbe accessible during ");
  RSA_PrintMessage ("decryption.  Whether it is encrypted (digital envelope");
  RSA_PrintMessage ("\nidea) and sent to another party, or stored securely ");
  RSA_PrintMessage ("somewhere is up to the\napplication.  Here, for sample ");
  RSA_PrintMessage ("purposes, we generate a random symmetric key\nand save ");
  RSA_PrintMessage ("it to a file in the clear, making it available to ");
  RSA_PrintMessage ("decrypt this\nPKCS #7 EncryptedData message.\n\n");

  status = RSA_GenerateSymmetricKey (ctx, encryptionAlgId, &encryptionKey);
  if (status != 0)
    goto CLEANUP;
  
  status = B_GetKeyInfo ((POINTER *)&keyInfo, encryptionKey, KI_Item);
  if (status != 0)
    goto CLEANUP;

  RSA_PrintBuf ("Symmetric key", keyInfo->data, keyInfo->len);

  status = RSA_WriteDataToFile
           (keyInfo->data, keyInfo->len,
            "Enter name of file to store symmetric key bytes");
  if (status != 0)
    goto CLEANUP;
  
  status = C_WriteEncryptedDataMsg (ctx, &fileData, &encryptionAlgId,
                                    encryptionKey, NULL, &encryptedData);
  if (status != 0)
    goto CLEANUP;

  status = RSA_WriteDataToFile
           (encryptedData.data, encryptedData.len,
            "Enter name of file to store PKCS #7 encrypted data message");

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

  if (destroyAlgParams)
    (*destroyAlgParams) (&encryptionAlgId.algorithmParam);
  T_free (fileData.data);
  T_free (encryptedData.data);
  B_DestroyKeyObject (&encryptionKey);
  
  return status;
}  /* end WriteEncryptedDataMsg */

static int GetSignerInfoList (CERTC_CTX ctx, SERVICE database,
                              LIST_OBJ signerInfos)
{
  int status = 0, i = 0, signerInfoCount = 0;

  CERT_OBJ signerCert = NULL;
  B_KEY_OBJ signerPvtKey = NULL;

  CERT_FIELDS certFields;
  SIGNER_INFO signerInfo;

  unsigned char *signedAttribBer = NULL, *unsignedAttribBer = NULL;
  unsigned int signedAttribBerLen = 0, unsignedAttribBerLen = 0;

  signerInfo.signedAttributes = NULL;
  signerInfo.unsignedAttributes = NULL;

  status = RSA_GetInteger (&signerInfoCount,
                           "Enter number of signers (blank to omit)");
  if (status == RSA_DEMO_E_CANCEL) {
    status = 0;
    signerInfoCount = 0;
  }
  else if (status != 0)
    goto CLEANUP;

  for (i = 0; i < signerInfoCount; i++) {
    RSA_PrintMessage ("--Info for Signer #%i\n", i+1);
    status = C_CreateCertObject (&signerCert, ctx);
    if (status != 0)
      goto CLEANUP;

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

    /* prompt for the cert and private key */
    status = RSA_GetCertAndPvtKey (ctx, signerCert, signerPvtKey);
    if (status != 0)
      goto CLEANUP;

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

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

    signerInfo.signerCertId.type = ISSUER_SERIAL;
    signerInfo.signerCertId.id.issuerSerialNumber.issuerName =
      certFields.issuerName;
    signerInfo.signerCertId.id.issuerSerialNumber.serialNumber =
      certFields.serialNumber;

    status = C_InsertPrivateKey (database, signerCert, signerPvtKey);
    if (status != 0)
      goto CLEANUP;

    /* Fill in the signer's digest algorithm and "signature" algorithm.  Note
     * that the digest algorithm can be either MD5 or SHA1 and the "signature"
     * algorithm, which specifies the operation performed on the digest, can
     * either be RSA Encryption or DSA with SHA1.
     */
    status = RSA_SignerInfoSignatureAlgPrompt
             (&signerInfo.digestAlgorithmId, &signerInfo.signatureAlgorithmId);
    if (status != 0)
      goto CLEANUP;

    /* prompt for attributes to be signed - this is optional
     *  If RSA_GetFileToAllocBuffer returns RSA_DEMO_E_CANCEL,
     *  signerInfo.signedAttributes will remain NULL.
     */
    RSA_PrintMessage ("\nOptionally supply an attributes object to be ");
    RSA_PrintMessage ("authenticated attributes.\nEnter name of file ");
    RSA_PrintMessage ("containing attributes object, or blank to omit:\n");
    status = RSA_GetFileToAllocBuffer (&signedAttribBer, &signedAttribBerLen,
                                       NULL);
    if (status != 0 && status != RSA_DEMO_E_CANCEL)
      goto CLEANUP;
    else if (status == 0) {
      status = C_CreateAttributesObject (&signerInfo.signedAttributes);
      if (status != 0)
        goto CLEANUP;

      status = C_SetAttributesBER (signerInfo.signedAttributes,
                                   signedAttribBer, signedAttribBerLen);
      if (status != 0)
        goto CLEANUP;
    }
    
    /* prompt for attributes which will not be signed - this is optional
     *  If RSA_GetFileToAllocBuffer returns RSA_DEMO_E_CANCEL,
     *  signerInfo.unsignedAttributes will remain NULL.
     */
    RSA_PrintMessage ("\nOptionally supply unauthenticated attributes.\n");
    RSA_PrintMessage ("Enter name of file containing attributes object, or ");
    RSA_PrintMessage ("blank to omit:\n");
    status = RSA_GetFileToAllocBuffer (&unsignedAttribBer,
                                       &unsignedAttribBerLen, NULL);
    if (status != 0 && status != RSA_DEMO_E_CANCEL)
      goto CLEANUP;
    else if (status == 0) {
      status = C_CreateAttributesObject (&signerInfo.unsignedAttributes);
      if (status != 0)
        goto CLEANUP;

      status = C_SetAttributesBER (signerInfo.unsignedAttributes,
                                   unsignedAttribBer, unsignedAttribBerLen);
      if (status != 0)
        goto CLEANUP;
    }

    /*  Other possibilities are C_AddUniqueSignerToList and
     *  C_InsertSignerInList.
     */
    status = C_AddSignerToList (signerInfos, &signerInfo, NULL);
    if (status != 0)
      goto CLEANUP;

    T_free (signedAttribBer);
    signedAttribBer = NULL;  /* to avoid multiple frees in case of an error */
    T_free (unsignedAttribBer);
    unsignedAttribBer = NULL;
    C_DestroyCertObject (&signerCert);
    B_DestroyKeyObject (&signerPvtKey);
    C_DestroyAttributesObject (&signerInfo.signedAttributes);
    C_DestroyAttributesObject (&signerInfo.unsignedAttributes);
  }

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

  T_free (signedAttribBer);
  T_free (unsignedAttribBer);
  C_DestroyCertObject (&signerCert);
  B_DestroyKeyObject (&signerPvtKey);  
  C_DestroyAttributesObject (&signerInfo.signedAttributes);
  C_DestroyAttributesObject (&signerInfo.unsignedAttributes);
  
  return status;
}  /* end GetSignerInfoList */

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 */

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