RSA BSAFE Micro Edition Suite

Streamlined security for mobile and embedded devices

Search  Print

cm_open.c

/* $Id: cm_open.c,v 1.48 2005/08/08 05:33:30 jlevander Exp $ */
/*
 * Copyright (C) 1998-2003 RSA Security Inc.
 *
 * This file shall only be used to demonstrate how to interface to an
 * RSA Security Inc. licensed development product.
 *
 * You have a royalty-free right to use, reproduce and distribute this
 * demonstration file, provided that you agree that RSA Security Inc.
 * has no warranty, implied or otherwise, or liability for this
 * demonstration file (including any modified version).  This software
 * is provided "as is" without warranties or representations of any
 * kind. RSA Security disclaims all conditions and warranties, statutory
 * and otherwise, both express and implied, with respect to the software,
 * its quality and performance, including but not limited to, all
 * implied warranties of merchantability, fitness for a particular
 * purpose, title and noninfringement of third party rights. Without
 * limiting the foregoing, RSA Security does not warrant that the
 * software is error-free or that errors in the product will be
 * corrected. You agree that RSA Security shall not be liable for any
 * direct, indirect, incidental, special, consequential, punitive or
 * other damages whatsoever resulting from your use of this software
 * or any modified version.
 *
 *
 */

/*
 * @file
 * This sample demonstrates how to decrypt a PKCS #7 enveloped data message.
 *
 * In its simplest form this sample will decrypt the enveloped data. Use the
 * optional switches to print the data and/or recipient information from the
 * enveloped data message.
 *
 * See cm_env for how to generate PKCS #7 enveloped data messages.
 *
 * For example, to:
 *
 * Open and decrypt a PKCS #7 enveloped data message (data within message):
 *   cm_open -cm_msg env.data -keys rec_priv.key -certs recipient.cert
 *
 * Decrypt a PKCS #7 enveloped data message (encrypted data in a separate
 * file):
 * The original data is printed along with the recipient information.
 *   cm_open -cm_msg env.data -enc_msg data.env -keys rec_priv.key
 *           -certs recipient.cert -print_data -print_recipients
 *
 * where:  env.data       = The output file where PKCS #7 data is written.
 *         rec_priv.key   = The private key of the recipient (matches the
 *                          certificate).
 *         recipient.cert = The recipient's certificate.
 *         data.env       = The detached file containing only the encrypted
 *                          data.
 *
 */

#include "r_prod.h"
#include "cm_com.h"

/* Usage help message */
static char *cm_open_usage[] =
{
    "usage: cm_open [options]\n",
    "where options are:\n",
    " -cm_msg file         - File containing the cryptographic message\n",
    " -enc_msg file        - File containing the encrypted data if it's not\n",
    "                        part of the cryptographic message\n",
    " -single              - The cryptographic message and detached data are\n",
    "                        in the same file, the cryptographic message\n",
    "                        first followed by the detached data\n",
    " -wrapped             - The detached message is wrapped and should be\n",
    "                        unwrapped before use\n",
    " -certs list          - List of certificates (colon separated)\n",
    " -certtype encoding   - Encoding of the certificates - only X509\n",
    "                        (default) supported\n",
#ifdef NO_PEM
    " -certform format     - Format of the certificates (BIN only)\n",
#else
    " -certform format     - Format of the certificates\n"
    "                        - one of BIN (default), PEM\n",
#endif /* NO_PEM */
    " -keys list           - List of private keys matching certificates\n",
    "                        (colon separated)\n",
    " -keytype encoding    - Encoding of the keys - one of RSA (default)\n",
#ifdef NO_PEM
    " -keyform format      - Format of the keys (BIN only)\n",
#else
    " -keyform format      - Format of the keys - one of BIN (default), PEM\n",
#endif /* NO_PEM */
    " -print_recipient     - Print the certificate information for each of\n",
    "                        the recipients in the cryptographic message\n",
    " -print_data          - Print the enveloped data of the cryptographic\n",
    "                        message\n",
#ifdef NO_SOFTWARE_CRYPTO
    " -no_fips140          - Use non FIPS140 crypto implementations\n",
#endif /* NO_SOFTWARE_CRYPTO */
    " -eg                  - Example usage\n",
    NULL
};

static char *cm_open_example_usage[] =
{
    "Open and decrypt a PKCS #7 enveloped data message (data within message)\n",
    "cm_open -cm_msg env.data -keys rec_priv.key -certs recipient.cert\n",
    "\n",
    "Decrypt a PKCS #7 enveloped data message (encrypted data in a separate"
    "file)\n",
    "The original data is printed along with the recipient information\n",
    "cm_open -cm_msg env.data -enc_msg data.env -keys rec_priv.key\n",
    "        -certs recipient.cert -print_data -print_recipients\n",
    "\n",
    "where:  env.data       = Output file where PKCS #7 data is written\n",
    "        rec_priv.key   = Private key of the recipient (matches the cert)"
    "\n",
    "        recipient.cert = Recipient's certificate\n",
    "        data.env       = Detached file containing only the encrypted data"
    "\n",
    "\n",
    NULL
};

int decrypt_recipients(BIO *bio_err, R_CERT_CTX *cert_ctx,
    R_PKEY_CTX *pkey_ctx, R_CM *obj, char *certs, R_CERT_TYPE certtype,
    R_FORMAT certform, char *keys, R_PKEY_TYPE keytype, R_FORMAT keyform);

/*
 * Main sample program entry point
 *
 * @param argc  [In]  The number of arguments typed on the command line.
 * @param argv  [In]  The array of individual arguments from the command line.
 *
 * @returns   R_ERROR_NONE indicates success.
 *            See @ref R_ERROR_IDS for valid values.
 */
int main(int argc, char **argv)
{
    int               ret       = R_ERROR_NONE;
    R_RES_LIST       *res_list  = NULL;
    R_LIB_CTX        *lib_ctx   = NULL;
    R_CM_CTX         *ctx       = NULL;
    R_CM             *obj       = NULL;
    R_CM             *data_obj  = NULL;
    R_CM             *tmp_obj   = NULL;
    R_CERT_CTX       *cert_ctx  = NULL;
    R_PKEY_CTX       *pkey_ctx  = NULL;
    R_TITEM           titem     = { R_FLAG_SHARE_DATA, 0, NULL };
    BIO              *bio_err   = NULL;
    BIO              *bio_out   = NULL;
    BIO              *bio_file  = NULL;
    char             *cm_file;
    char             *encfile;
    char             *out_file;
    char             *keyfile;
    char             *certfile;
    char             *str;
    unsigned char    *cm_data;
    unsigned char    *msg_data;
    unsigned int      cm_len;
    unsigned int      msg_len;
    unsigned int      num_used;
    R_CERT_TYPE       certtype;
    R_PKEY_TYPE       keytype;
    R_FORMAT          certform;
    R_FORMAT          keyform;
    R_CM_ENCODING     format;
    int               print_data;
    int               print_rec;
    int               single;
    int               wrapped;

    /* Set the defaults */
    cm_file    = NULL;
    out_file   = NULL;
    certfile   = NULL;
    encfile    = NULL;
    cm_data    = NULL;
    msg_data   = NULL;
    format     = R_CM_ENCODING_FORMAT_RAW;
    certtype   = R_CERT_TYPE_X509;
    certform   = R_FORMAT_BINARY;
    keyfile    = NULL;
    keytype    = R_PKEY_TYPE_RSA;
    keyform    = R_FORMAT_BINARY;
    print_data = 0;
    print_rec  = 0;
    single     = 0;
    wrapped    = 0;

    res_list = PRODUCT_DEFAULT_RESOURCE_LIST();

    /*
     * Create BIOs to stderr and stdout. BIOs are the Basic Input/Output
     * mechanism provided by RSA and are recommended for all input and output
     * from applications.
     */
    bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
    bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);
    if ((bio_err == NULL) || (bio_out == NULL))
    {
        ret = R_ERROR_ALLOC_FAILURE;
        goto end;
    }

    BIO_set_flags(bio_err, BIO_FLAGS_FLUSH_ON_WRITE);
    BIO_set_flags(bio_out, BIO_FLAGS_FLUSH_ON_WRITE);

    /* Parse the command line parameters */

    /* Skip the program name */
    argc--;
    argv++;

    /* Process all command line options */
    while (argc >= 1)
    {
        if (Strcmp(*argv, "-cm_msg") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            cm_file = *(++argv);
        }
        else if (Strcmp(*argv, "-enc_msg") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            encfile = *(++argv);
        }
        else if (Strcmp(*argv, "-single") == 0)
        {
            single = 1;
        }
        else if (Strcmp(*argv, "-wrapped") == 0)
        {
            wrapped = 1;
        }
        else if (Strcmp(*argv, "-keys") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            keyfile = *(++argv);
        }
        else if (Strcmp(*argv, "-keytype") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            str = *(++argv);

            if ((ret = R_PKEY_TYPE_from_string(&keytype, str)) !=
                R_ERROR_NONE)
            {
                BIO_printf(bio_err, "Unknown key type %s\n", str);
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-keyform") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            str = *(++argv);

            if ((ret = R_FORMAT_from_string(str, &keyform)) != R_ERROR_NONE)
            {
                BIO_printf(bio_err, "Unknown key format %s\n", str);
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-certs") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            certfile = *(++argv);
        }
        else if (Strcmp(*argv, "-certtype") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            str = *(++argv);

            if ((ret = R_CERT_TYPE_from_string(str, &certtype)) !=
                R_ERROR_NONE)
            {
                BIO_printf(bio_err, "Unknown certificate type %s\n", str);
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-certform") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            str = *(++argv);

            if ((ret = R_FORMAT_from_string(str, &certform)) != R_ERROR_NONE)
            {
                BIO_printf(bio_err, "Unknown certificate format %s\n", str);
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-print_data") == 0)
        {
            print_data = 1;
        }
        else if (Strcmp(*argv, "-print_recipient") == 0)
        {
            print_rec = 1;
        }
#ifdef NO_SOFTWARE_CRYPTO
        else if (Strcmp(*argv, "-no_fips140") == 0)
        {
            res_list = PRODUCT_NON_FIPS_140_MODE_RESOURCE_LIST();
        }
#endif /* NO_SOFTWARE_CRYPTO */
        else if (Strcmp(*argv,"-eg") == 0)
        {
            char **egp;
            for (egp = cm_open_example_usage; (*egp) != NULL; egp++)
            {
                BIO_printf(bio_out, *egp);
            }
            goto end;
        }
        else
        {
            BIO_printf(bio_err, "Unknown option %s\n", *argv);
            goto bad;
        }
        argc--;
        argv++;
    }

    /* Simple checks first */
    if (cm_file == NULL)
    {
        BIO_printf(bio_out, "Input file required\n");
        goto bad;
    }

    /* Display the help menu if an invalid command line option was entered */
    if (0)
    {
        char **pp;
bad:
        for (pp = cm_open_usage; (*pp != NULL); pp++)
        {
            BIO_printf(bio_err, *pp);
        }
        goto end;
    }

    /*
     * Create a library context to provide access to all configurable aspects
     * of the library
     */

    /* Create a new library context using the default resource list */
    if ((ret = PRODUCT_LIBRARY_NEW(res_list, R_RES_FLAG_DEF, &lib_ctx)) !=
        R_ERROR_NONE)
    {
        BIO_printf(bio_err, "Library new failure\n");
        goto end;
    }

    /*
     * Create public key, certificate and cryptographic message contexts. These
     * contexts are required if any R_CERT_*, R_PKEY_*, or R_CM_* routines are
     * used.
     */
    if ((ret = R_CERT_CTX_new(lib_ctx, R_RES_FLAG_DEF, certtype, &cert_ctx)) !=
        R_ERROR_NONE)
    {
        BIO_printf(bio_err, "Certificate context new failure\n");
        goto end;
    }

    /* Create a new key context */
    if ((ret = R_PKEY_CTX_new(lib_ctx, R_RES_FLAG_DEF, keytype, &pkey_ctx)) !=
        R_ERROR_NONE)
    {
        BIO_printf(bio_err, "Private key context new failure\n");
        goto end;
    }

    /* Create a new cryptographic message context */
    if ((ret = R_CM_CTX_new(lib_ctx, R_RES_FLAG_DEF, R_CM_TYPE_DEFAULT,
        &ctx)) != R_ERROR_NONE)
    {
        BIO_printf(bio_err, "R_CM_CTX_new failure\n");
        goto end;
    }

    /*
     * Load the cryptographic message file data into a cryptographic
     * message object.
     */

    /* Read the file into a buffer. The data will be in binary format. */
    if ((ret = data_from_file(bio_out, cm_file, &cm_data, &cm_len)) !=
        R_ERROR_NONE)
    {
        goto end;
    }

    /*
     * Do a from binary on the data to load into a cryptographic message
     * object
     */
    if ((ret = R_CM_from_binary(ctx, R_FLAG_SHARE_DATA, R_CM_TYPE_UNKNOWN,
        R_CM_ENCODING_FORMAT_WRAPPED, cm_len, cm_data, &num_used, &obj)) !=
        R_ERROR_NONE)
    {
        BIO_printf(bio_out, "R_CM_from_binary failure\n");
        goto end;
    }

    /*
     * Access the data to decrypt. Determine where the data resides so that, if
     * required, the decrypted data can be added to the cryptographic message
     * object before decryption.
     *
     * If the data is detached but in the same file then the data follows the
     * cryptographic message. Therefore, point to the data after the
     * cryptographic message that was read above.
     *
     * If the data is detached but in a seperate file then read the data from
     * the specified file.
     *
     * If the data is attached then it is inside the cryptographic message
     * and no further action needs to be taken.
     */

    /* The detached message is appended to the cryptographic message file */
    if (single == 1)
    {
        /* Set the data message with the remainder of the data */
        msg_data = cm_data + num_used;
        msg_len  = cm_len  - num_used;
    }
    /* The detached message is stored in a separate file */
    else if (encfile != NULL)
    {
        if ((ret = data_from_file(bio_out, encfile, &msg_data, &msg_len)) !=
            R_ERROR_NONE)
        {
            goto end;
        }
    }

    /*
     * The detached encrypted data is now available. Unwrap it, if necessary,
     * and add it to the cryptographic message object.
     */
    if (msg_data != NULL)
    {
        /*
         * The data must be detached. Therefore, if it is wrapped, unwrap
         * it now.
         */
        if (wrapped != 0)
        {
            /* Do a from binary on the data in order to unwrap the data */
            if ((ret = R_CM_from_binary(ctx, R_FLAG_SHARE_DATA,
                R_CM_TYPE_UNKNOWN, R_CM_ENCODING_FORMAT_WRAPPED, msg_len,
                msg_data, &num_used, &tmp_obj)) != R_ERROR_NONE)
            {
                BIO_printf(bio_out, "Failed to unwrap encrypted data\n");
                goto end;
            }

            if ((ret = R_CM_get_info(tmp_obj, R_CM_INFO_DATA, &titem)) !=
                R_ERROR_NONE)
            {
                BIO_printf(bio_out,
                      "Failed to retrieve unwrapped encrypted data\n");
                goto end;
            }
        }
        else
        {
            titem.data = msg_data;
            titem.len = msg_len;
        }

        titem.type = R_FLAG_SHARE_NONE;

        /* Add the detached encrypted data to the R_CM object */
        if ((ret = R_CM_set_info(obj, R_CM_INFO_ENC_DATA, &titem)) !=
            R_ERROR_NONE)
        {
             BIO_printf(bio_err, "Failed to set encrypted data into R_CM\n");
             goto end;
        }
    }

    /*
     * Decrypt the data. Each of the certificates provided on the command line
     * (certfile) is searched for within the cryptographic message object. The
     * index of the certificate is used to retrieve the corresponding encrypted
     * symmetric key used to encrypt the original data. This key is decrypted
     * and the key is used to decrypt the message in the cryptographic message
     * object to produce the original data.
     */
    if ((ret = decrypt_recipients(bio_err, cert_ctx, pkey_ctx, obj, certfile,
        certtype, certform, keyfile, keytype, keyform)) != R_ERROR_NONE)
    {
        goto end;
    }

    /* Display the recipient details if requested */
    if (print_rec == 1)
    {
        /*
         * Print the enveloped data message. If print_data != 0 then the
         * encrypted data will also be printed to bio_out.
         */
        if ((ret = R_CM_write(obj, bio_err, R_FORMAT_TEXT, &print_data, 0)) !=
            R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Write envelopedData data failure\n");
            goto end;
        }
    }

    /* Print the decrypted data if requested */
    if (print_data == 1)
    {
        /* Retrieve the content, returning the decrypted data */
        if ((ret = R_CM_content_to_R_CM(obj, R_FLAG_SHARE_DATA, &data_obj)) !=
            R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Retrieve content data failure\n");
            goto end;
        }

        if ((ret = R_CM_write(data_obj, bio_out, R_FORMAT_TEXT, NULL,
            R_CM_ENCODING_FORMAT_RAW)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Failed to write decrypted data\n");
            goto end;
        }
    }

end:

    /*
     * Clean up. Report errors if there is an output stream using both the
     * error and the string representation. Destroy the dynamically allocated
     * objects and return an exit code.
     */
    if ((ret != R_ERROR_NONE) && (bio_err != NULL))
    {
        BIO_printf(bio_err, "ERROR: (%d) %s\n", ret,
            R_LIB_CTX_get_error_string(lib_ctx, R_RES_MOD_ID_LIBRARY, ret));
    }

    if (obj != NULL)
    {
        R_CM_free(obj);
    }
    if (tmp_obj != NULL)
    {
        R_CM_free(tmp_obj);
    }
    if (data_obj != NULL)
    {
        R_CM_free(data_obj);
    }
    if (cm_data != NULL)
    {
        Free(cm_data);
    }
    if ((encfile != NULL) && (msg_data != NULL))
    {
        Free(msg_data);
    }
    if (ctx != NULL)
    {
        R_CM_CTX_free(ctx);
    }
    if (pkey_ctx != NULL)
    {
        R_PKEY_CTX_free(pkey_ctx);
    }
    /*
     * The certificate context cannot be freed until the store has been emptied
     * as the certificates in the store hold a reference back to the
     * certificate context
     */
    if (cert_ctx != NULL)
    {
        R_CERT_CTX_free(cert_ctx);
    }
    if (lib_ctx != NULL)
    {
        PRODUCT_LIBRARY_FREE(lib_ctx);
    }
    if (bio_file != NULL)
    {
        BIO_free(bio_file);
    }
    if (bio_out != NULL)
    {
        BIO_free(bio_out);
    }
    if (bio_err != NULL)
    {
        BIO_free(bio_err);
    }

    return(R_ERROR_EXIT_CODE(ret));
}

/*
 * Decrypts the enveloped data message and verifies the result for each
 * recipient.
 *
 * @param bio_err   [In]  The BIO for error messages.
 * @param cert_ctx  [In]  The certificate context.
 * @param key_ctx   [In]  The key context.
 * @param obj       [In]  The object into which to add the recipients.
 * @param certs     [In]  A colon separated list of certificate files.
 * @param certtype  [In]  The certificate type.
 * @param certform  [In]  The certificate format.
 * @param keys      [In]  A colon separated list of key files.
 * @param keytype   [In]  The key type.
 * @param keyform   [In]  The key format.
 *
 * @returns  R_ERROR_NONE indicates success.
 *           See @ref R_ERROR_IDS for valid values.
 */
int decrypt_recipients(BIO *bio_err, R_CERT_CTX *cert_ctx,
    R_PKEY_CTX *pkey_ctx, R_CM *obj, char *certs, R_CERT_TYPE certtype,
    R_FORMAT certform, char *keys, R_PKEY_TYPE keytype, R_FORMAT keyform)
{
    int ret = R_ERROR_NONE;
    R_CERT *cert = NULL;
    R_PKEY *pkey = NULL;
    char *cert_str = NULL;
    char *key_str = NULL;
    int cert_len = 0;
    int key_len = 0;
    int index;

    if ((certs == NULL) || (keys == NULL))
    {
        BIO_printf(bio_err, "Missing recipient information\n");
        ret = R_ERROR_NULL_ARG;
        goto err;
    }

    cert_len = R_STR_token(&certs, ':', NULL);
    key_len  = R_STR_token(&keys, ':', NULL);

    while ((cert_len > 0) && (key_len > 0))
    {
        if ((cert_str = (char *)Malloc(cert_len + 1)) == NULL)
        {
            ret = R_ERROR_ALLOC_FAILURE;
            goto err;
        }

        if ((key_str = (char *)Malloc(key_len + 1)) == NULL)
        {
            ret = R_ERROR_ALLOC_FAILURE;
            goto err;
        }

        R_STR_token(&certs, ':', cert_str);
        R_STR_token(&keys, ':', key_str);

        /* Read in the certificate from file */
        if ((ret = R_CERT_read_file(cert_ctx, cert_str, certtype, certform,
            &cert)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Certificate from file failure: %s\n",
                cert_str);
            goto err;
        }

        /* Read in the key from file */
        if ((ret = R_PKEY_from_file(pkey_ctx, &pkey, key_str, keytype,
            keyform)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Private key from file failure: %s\n", key_str);
            goto err;
        }

        /* Retrieve the index of the recipient in the cryptographic message */
        if ((ret = R_CM_get_index(obj, cert, &index)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Failed to lookup recipient %s\n", cert_str);
            goto err;
        }

        /* Decrypt for the recipient at a particular index */
        if ((ret = R_CM_decrypt(obj, pkey, index)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Failed to decrypt with key: %s\n", key_str);
            goto err;
        }

        if (cert != NULL)
        {
            R_CERT_free(cert);
            cert = NULL;
        }

        if (pkey != NULL)
        {
            R_PKEY_free(pkey);
            pkey = NULL;
        }

        /* Free allocated strings */
        Free(cert_str);
        cert_str = NULL;

        Free(key_str);
        key_str = NULL;

        cert_len = R_STR_token(&certs, ':', NULL);
        key_len  = R_STR_token(&keys, ':', NULL);
    }

err:

    /* There should be an equal number of certificates and keys */
    if ((cert_len > 0) || (key_len > 0))
    {
        BIO_printf(bio_err, "Mismatch in the number of certs and keys\n");
        ret = R_ERROR_FAILED;
    }

    if (cert != NULL)
    {
        R_CERT_free(cert);
        cert = NULL;
    }
    if (pkey != NULL)
    {
        R_PKEY_free(pkey);
        pkey = NULL;
    }
    if (cert_str != NULL)
    {
        Free(cert_str);
    }
    if (key_str != NULL)
    {
        Free(key_str);
    }

    return(ret);
}


Copyright (c) 1999-2005 RSA Security Inc. All rights reserved. 072-001001-2100-001-000 - 2.1