RSA BSAFE Cert-C

Certificate Components for C

Crypto-C 6.2.1 Developer's Guide
Search

chain.c

Builds and validates certificates chains. Demonstrates certification path processing API. First, a list of the trusted root certificates is created. Next, remaining certificates and CRLs are added to an instance of the in-memory database for use in the CERT_PATH_CTX.

/* $Id: chain.c,v 1.5 2004/03/02 05:18:42 gsingh Exp $ */
/* chain.c
** Copyright (c) 1999-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.
**
** A simple example of the certification path processing API.  We have certs
** and CRLs hard-coded in this example.  First, we create a list of the trusted
** root certificates.  Then add the remaining certs and CRLs to an instance of
** the in-memory database for use in the CERT_PATH_CTX.
**
** 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 "imdb.h"
#include "filelog.h"
#include "pkixpath.h"
#include "crlstat.h"
#include "demoutil.h"
#include "certutil.h"
#include "crlutil.h"
#include "keyutil.h"
#include "timeutil.h"
#include "chaindata.h"

#define SP_COUNT 3

/*  Create a list object and fill it with trusted root certs.
 *
 *  Note that the calling procedure must call C_DestroyListObject on rootCerts
 *  when it is no longer needed.
 */
int CreateRootList (CERTC_CTX ctx, LIST_OBJ *rootCerts)
{
  int status = 0, i = 0, numRoots = 0;

  CERT_PATH_CTX pathCtx;
  CERT_OBJ certObj = (CERT_OBJ)0;
  B_KEY_OBJ publicKey = (B_KEY_OBJ)0;

  ITEM roots[2] = {
    {expiredRoot, sizeof (expiredRoot)}, {rootCert, sizeof (rootCert)}
  };
  
  status = C_CreateListObject (rootCerts);
  if (status != 0)
    goto CLEANUP;
  
  /* In this procedure, we have self-signed root certificates so we can verify
   * the certificate signature with the public key in the cert.  However, in
   * practice, we can have certs which are not self-signed as part of the
   * trusted collection of certs.  For example, for a cert whose issuer is
   * unrecognized, a client program may prompt the user with the certificate
   * information and ask whether or not the user wants to explicitly trust the
   * certificate.
   */

  numRoots = sizeof (roots) / sizeof (roots[0]);
  for (i = 0; i < numRoots; i++) {
    status = C_CreateCertObject (&certObj, ctx);
    if (status != 0)
      goto CLEANUP;

    status = C_SetCertBER (certObj, roots[i].data, roots[i].len);
    if (status != 0)
      goto CLEANUP;

    status = RSA_GetPubKeyFromCert (certObj, &publicKey);
    if (status != 0)
      goto CLEANUP;

    /* C_VerifyCertSignature only verifies the certificate signature and does
     * not check for example whether or not the cert is expired.  For more
     * strict checking, use C_ValidateCert.
     *
     * For this example, we disable the validity period check to simulate a
     * situation where we import a trusted root at a time when it is valid,
     * but expires while it is in the trusted store.  If we change pathOptions
     * to 0, we will see that Cert-C complains about the expiredRoot (examine
     * the chainlog.txt file).
     */
    pathCtx.pathAlgorithm = PA_PKIX;
    pathCtx.pathOptions = PF_IGNORE_VALIDATION_TIME;
    pathCtx.trustedCerts = (LIST_OBJ)0;
    pathCtx.policies = ANY_POLICY;
    pathCtx.validationTime = 0;
    pathCtx.database = (SERVICE)0;

    status = C_ValidateCert (ctx, &pathCtx, certObj, publicKey);
    if (status == E_NOT_VALIDATED) {
      RSA_PrintMessage ("The following certificate was not added to the list");
      RSA_PrintMessage (" of trusted roots:\n");
      status = RSA_PrintCertInfo (certObj);
      if (status != 0)
        goto CLEANUP;
    }
    else if (status != 0)
      goto CLEANUP;
    else {
      RSA_PrintMessage ("\nAdding Trusted Certificate...\n");
      status = RSA_PrintCertInfo (certObj);
      if (status != 0)
        goto CLEANUP;
      
      status = C_AddCertToList (*rootCerts, certObj, (unsigned int *)0);
      if (status != 0)
        goto CLEANUP;
    }
    
    C_DestroyCertObject (&certObj);
    B_DestroyKeyObject (&publicKey);
  }
  
CLEANUP:
  if (status != 0) {
    RSA_PrintError ("CreateRootList", status);
    C_DestroyListObject (rootCerts);
  }

  C_DestroyCertObject (&certObj);
  B_DestroyKeyObject (&publicKey);

  return status;
}  /* end CreateRootList */

/*  Takes a Cert-C Context and the name of the database in which to insert
 *  the sample intermediate certs and CRLs from chaindata.h.
 */
int InsertSampleData (CERTC_CTX ctx, char *dbName)
{
  int status = 0, i = 0, numCerts = 0, numCrls = 0;

  /* use this setup to allow the use of the for loop below */
  ITEM certs[5] = {
    {expiredRoot, sizeof (expiredRoot)}, {rootCert, sizeof (rootCert)},
    {issuer1, sizeof (issuer1)}, {issuer2, sizeof (issuer2)},
    {issuer3, sizeof (issuer3)}
  };

  ITEM crls[2] = {
    {rootCrl, sizeof (rootCrl)}, {issuer1Crl, sizeof (issuer1Crl)}
  };
    
  SERVICE db = (SERVICE)0;
  CERT_OBJ certObj = (CERT_OBJ)0;
  CRL_OBJ crlObj = (CRL_OBJ)0;

  status = C_BindService (ctx, SPT_DATABASE, dbName, &db);
  if (status != 0)
    goto CLEANUP;

  numCerts = sizeof (certs) / sizeof (certs[0]);
  for (i = 0; i < numCerts; i++) {
    status = C_CreateCertObject (&certObj, ctx);
    if (status != 0)
      goto CLEANUP;

    status = C_SetCertBER (certObj, certs[i].data, certs[i].len);
    if (status != 0)
      goto CLEANUP;

    RSA_PrintMessage ("\nInserting cert into database...\n");
    status = RSA_PrintCertInfo (certObj);
    if (status != 0)
      goto CLEANUP;
    
    status = C_InsertCert (db, certObj);
    if (status != 0)
      goto CLEANUP;

    C_DestroyCertObject (&certObj);
  }

  numCrls = sizeof (crls) / sizeof (crls[0]);
  for (i = 0; i < numCrls; i++) {
    status = C_CreateCRLObject (&crlObj, ctx);
    if (status != 0)
      goto CLEANUP;

    status = C_SetCRLBER (crlObj, crls[i].data, crls[i].len);
    if (status != 0)
      goto CLEANUP;

    RSA_PrintMessage ("\nInserting CRL into database...\n");
    status = RSA_PrintCrlInfo (crlObj);
    if (status != 0)
      goto CLEANUP;
    
    status = C_InsertCRL (db, crlObj);
    if (status != 0)
      goto CLEANUP;

    C_DestroyCRLObject (&crlObj);
  }
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("InsertSampleData", status);

  C_DestroyCertObject (&certObj);
  C_DestroyCRLObject (&crlObj);
  C_UnbindService (&db);

  return status;
}  /* end InsertSampleData */

/*  Examine the end entity certificates and if valid, construct the path to
 *  trusted root.  This function requires a list containing the trusted roots
 *  and the name of the registered database provider where other certs and CRLs
 *  were stored.
 */
int VerifyEndEntities (CERTC_CTX ctx, LIST_OBJ rootCerts, char *dbName)
{
  int status, i;
  SERVICE db = (SERVICE)0;
  CERT_PATH_CTX pathCtx;
  CERT_OBJ endEntity = (CERT_OBJ)0;
  LIST_OBJ certPath = (LIST_OBJ)0, crls = (LIST_OBJ)0, crlCerts = (LIST_OBJ)0;
  
  ITEM certs[6] = {
    {badCert,   sizeof (badCert)},               /* no, since root is expired */
    {subject1,  sizeof (subject1)},                     /* no, it was revoked */
    {subject2,  sizeof (subject2)},             /* no, its issuer was revoked */
    {subject3,  sizeof (subject3)},                 /* no, its issuer expired */
    {subject0,  sizeof (subject0)},                                  /* valid */
    {subject11, sizeof (subject11)}                                  /* valid */
  };  

  RSA_PrintMessage ("\n***Expected Results***\n");
  RSA_PrintMessage ("badCert - not valid, its root is expired\n");
  RSA_PrintMessage ("Subject 1 - not valid, it was revoked by Issuer 1\n");
  RSA_PrintMessage ("Subject 2 - not valid, its issuer was revoked by Root\n");
  RSA_PrintMessage ("Subject 3 - not valid, its issuer is expired\n");
  RSA_PrintMessage ("Subject 0 - valid\n");
  RSA_PrintMessage ("Subject 11 - valid\n");
  
  /* obtain the handle to the database where InsertSampleData put all the info
   */
  status = C_BindService (ctx, SPT_DATABASE, dbName, &db);
  if (status != 0)
    goto CLEANUP;

  /* use PA_X509_V1 instead of PA_PKIX to omit examination of extensions */
  pathCtx.pathAlgorithm = PA_X509_V1;
  /* The PF_* flags may be used to customize path processing behavior.
     We use the normal behavior here. */
  pathCtx.pathOptions = 0;
  pathCtx.trustedCerts = rootCerts;
  pathCtx.policies = ANY_POLICY;
  pathCtx.validationTime = 0;  /* current time */
  pathCtx.database = db;

  for (i = 0; i < sizeof (certs) / sizeof (certs[0]); i++) {
    status = C_CreateListObject (&certPath);
    if (status != 0)
      goto CLEANUP;

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

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

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

    status = C_SetCertBER (endEntity, certs[i].data, certs[i].len);
    if (status != 0)
      goto CLEANUP;

    RSA_PrintMessage ("\n***Attempting to construct certificate chain for\n");
    status = RSA_PrintCertInfo (endEntity);
    if (status != 0)
      goto CLEANUP;
    
    status = C_BuildCertPath (ctx, &pathCtx, (POINTER)endEntity, certPath,
                              crls, crlCerts, (LIST_OBJ)0);
    if (status == E_PATH_NOT_FOUND)
      RSA_PrintMessage ("\nNo path can be constructed.\n");
    else if (status != 0)
      goto CLEANUP;

    /* if a PKCS #7 certs-only message (certsonly.c) were to be constructed
       for this end entity, the certs in this list are normally included */
    RSA_PrintMessage ("\nChain of certificates to trusted root:\n");
    status = RSA_PrintCertList (certPath);
    if (status != 0)
      goto CLEANUP;

    RSA_PrintMessage ("\nCRLs needed to verify chain:\n");
    status = RSA_PrintCrlList (crls);
    if (status != 0)
      goto CLEANUP;

    RSA_PrintMessage ("\nAdditional certs needed to validate CRLs:\n");
    status = RSA_PrintCertList (crlCerts);
    if (status != 0)
      goto CLEANUP;
    
    C_DestroyListObject (&certPath);
    C_DestroyListObject (&crls);
    C_DestroyListObject (&crlCerts);
    C_DestroyCertObject (&endEntity);    
  }
  
CLEANUP:
  if (status != 0)
    RSA_PrintError ("VerifyEndEntities", status);

  C_DestroyListObject (&certPath);
  C_DestroyListObject (&crls);
  C_DestroyListObject (&crlCerts);
  C_DestroyCertObject (&endEntity);
  C_UnbindService (&db);
  
  return status;
}  /* end VerifyEndEntities */

int main (int argc, char *argv[])
{
  int status;

  CERTC_CTX ctx = (CERTC_CTX)0;
  SERVICE_HANDLER spTable[SP_COUNT];
  POINTER spParams[SP_COUNT];

  LIST_OBJ rootCerts = (LIST_OBJ)0;

  FILE_LOG_PARAMS logParams = {(char *)0, (char *)0};
  SERVICE_HANDLER logHandler = {
    SPT_LOG, "Default File Log", S_InitializeFileLog
  };

  status = RSA_SetOptions (&logParams, argc, argv);
  if (status != 0)
    goto CLEANUP;
  
  spTable[0].type = SPT_DATABASE;
  spTable[0].name = "Sample IM Database";
  spTable[0].Initialize = S_InitializeMemoryDB;
  spTable[1].type = SPT_CERT_PATH;
  spTable[1].name = "Cert Path Processing Provider";
  spTable[1].Initialize = S_InitializePKIXPath;
  /* unless the CERT_PATH_CTX for C_BuildCertPath uses PF_IGNORE_REVOCATION
   * as one of the pathOptions, we need a cert status provider to check CRLs
   */
  spTable[2].type = SPT_CERT_STATUS;
  spTable[2].name = "Cert Revocation Status Provider";
  spTable[2].Initialize = S_InitializeCRLStatus;

  spParams[0] = NULL_PTR;
  spParams[1] = NULL_PTR;
  spParams[2] = NULL_PTR;

  /* Note that the procedures which require access to the database where the
   * CRLs and intermediate certs are stored are given the name of the
   * registered database provider.  This is to illustrate that each function
   * is then able to access the database by binding to a SERVICE given the
   * name.  An alternate approach would be to bind a SERVICE here in main
   * and pass that instead of the string name of the database instance.
   */
  
  RSA_PrintMessage ("Cert Path Validation Sample\n");
  RSA_PrintMessage ("===========================\n");
  
  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

  /* put the root certificates into a list after validating them */
  status = CreateRootList (ctx, &rootCerts);
  if (status != 0)
    goto CLEANUP;  

  /* add a collection of intermediate certs into the database */
  status = InsertSampleData (ctx, spTable[0].name);
  if (status != 0)
    goto CLEANUP;  

  /* attempt to chain end entity certs to trusted roots */
  status = VerifyEndEntities (ctx, rootCerts, spTable[0].name);
  if (status != 0)
    goto CLEANUP;
    
CLEANUP:
  if (status != 0)
    RSA_PrintError ("chain.c", status);
  
  C_DestroyListObject (&rootCerts);
  C_FinalizeCertC (&ctx);

  return status;
}  /* end main */


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