RSA BSAFE Micro Edition Suite

Streamlined security for mobile and embedded devices

Search  Print

cm_env_strm_membio.c

/* $Id: cm_env_strm_membio.c,v 1.6 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 cm_env_strm_membio.c
 * This sample demonstrates how to create an enveloped PKCS #7 message using
 * the streaming interface. It is similar to cm_env_strm.c but uses memory
 * BIOs.
 *
 * Refer to cm_env_strm.c for detailed explanation.
 *
 */

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

/* The number of data Bytes to read */
#define BUF_LEN  1024

/* Usage help message */
static char *cm_env_strm_usage[] =
{
    "usage: cm_env_strm [options]\n",
    "where options are:\n",
    " -out file            - The file containing the cryptographic message\n",
    " -data file           - The file containing the data to envelope\n",
    " -data_type alg       - The data file contains content of a specified\n",
    "                        type e.g. SIGNED_DATA. Only required for\n",
    "                        nesting messages generated with -no_ci\n",
    " -no_ci               - Output message has no ContentInfo\n",
    " -enc_alg alg         - The encryption algorithm identifier to use\n",
    " -key_size            - The key size in bits for encryption algorithm\n",
    "                        (RC2_CBC, RC5_CBC only)\n",
    " -eff_bits            - The effective key bits (RC2_CBC only)\n",
    " -no_rounds           - No. of rounds during encryption (RC5_CBC only)\n",
    " -certs list          - List of certificates to use (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 */
    " -block_size number   - Size of the blocks to write out in one chunk\n",
    " -print_recipient     - Print the certificate information for each of the\n",
    "                        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 */
    NULL
};

BIO  *bio_err   = NULL;

/*
 * Routine to read the PKCS #7 data from the memory BIO. In this case
 * the routine writes the data to a file. However, this can be any output
 * stream. Typically, this routine would be replaced by an
 * application-specific routine such as a network driver that sends the
 * data over a link to a server.
 */
int data_stream_sink ( BIO *bio_out, BIO *bio_mem )
{
  int data_len;
  int len;
  static char data[BUF_LEN];
  static int outlen=0;
  int ret=R_ERROR_NONE;

    /*
     * Read in the data from the memory BIO (that is, the enveloped data) and
     * write it out to file
     */
    while ((data_len = BIO_read(bio_mem, (char *)(data), BUF_LEN)) != 0)
    {
        char *p;

        /* If the read is to be retried there will be nothing to write */
        if (data_len == -1)
        {
            if (BIO_should_retry(bio_mem))
            {
                /* Continue */
break;
            }
            else
            {
                BIO_printf(bio_err, "Fatal error while reading bio stack\n" );
                ret = R_ERROR_IO;
                goto error;
            }
        }

        p = (char *)data;

        /*
         * Write out the data in the enveloped data message.
         * Keep trying while the error is retried.
         */
        do
        {
            len = BIO_write(bio_out, p, data_len);
            if (len > 0)
            {
                data_len -= len;
                p += len;
                outlen+=len;
            }
            else if ((len < 0) && !BIO_should_retry(bio_out))
            {
                break;
            }
        }
        while (data_len > 0);

        if (data_len > 0)
        {
            BIO_printf(bio_err, "Failed to write out data\n");
            ret = R_ERROR_IO;
            goto error;
        }
    }

error:
  return ret;
}

/*
 * 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;
    BIO              *bio_mem   = NULL;
    BIO              *bio_outfile   = NULL;
    BIO              *bio_in    = NULL;
    BIO              *bio_cm    = NULL;
    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_CERT_CTX       *cert_ctx  = NULL;
    unsigned char    *data      = NULL;
    int               data_len;
    int               len;
    char             *certfile;
    char             *cm_file;
    char             *datafile;
    char             *options;
    char             *str;
    R_CERT_TYPE       certtype;
    R_FORMAT          certform;
    int               eff_bits;
    int               enc_alg;
    int               key_size;
    int               no_rounds;
    int               print_rec;
    int               print_data;
    R_CM_ENCODING     format;
    int               no_ci;
    int               data_type;
    R_CM_TYPE         cm_type;
    int               block_size;

    /* Set the defaults */
    certfile     = NULL;
    cm_file      = NULL;
    options      = NULL;
    datafile     = NULL;
    certtype     = R_CERT_TYPE_X509;
    certform     = R_FORMAT_BINARY;
    enc_alg      = R_CR_ID_DES_CBC;
    eff_bits     = 0;
    key_size     = 0;
    no_rounds    = 0;
    print_rec    = 0;
    print_data   = 0;
    format       = R_CM_ENCODING_FORMAT_RAW;
    no_ci        = 0;
    data_type    = 0;
    block_size   = 0;

    res_list = PRODUCT_DEFAULT_RESOURCE_LIST();

    /*
     * Create BIO to stderr. 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);
    if (bio_err == NULL)
    {
        ret = R_ERROR_ALLOC_FAILURE;
        goto end;
    }
    BIO_set_flags(bio_err, 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, "-out") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            cm_file = *(++argv);
        }
        else if (Strcmp(*argv, "-data") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            datafile = *(++argv);
        }
        else if (Strcmp(*argv, "-data_type") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            str = *(++argv);

            if ((ret = R_CM_TYPE_from_string(str, &cm_type)) != R_ERROR_NONE)
            {
                BIO_printf(bio_err, "Bad type: %s\n", str);
                goto bad;
            }
            data_type = 1;
        }
        else if (Strcmp(*argv, "-no_ci") == 0)
        {
            no_ci = 1;
        }
        else if (Strcmp(*argv, "-enc_alg") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            str = *(++argv);

            if ((ret = R_CR_ID_from_string(str, &enc_alg)) != R_ERROR_NONE)
            {
                BIO_printf(bio_err, "Bad algorithm identifier: %s\n", str);
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-key_size") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            key_size = atoi(*(++argv));
        }
        else if (Strcmp(*argv, "-eff_bits") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            eff_bits = atoi(*(++argv));
        }
        else if (Strcmp(*argv, "-no_rounds") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            no_rounds = atoi(*(++argv));
        }
        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, "-block_size") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }

            block_size = atoi(*(++argv));

            if (block_size <= 0)
            {
                BIO_printf(bio_err, "Block size must be greater than 0\n");
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-print_recipient") == 0)
        {
            print_rec = 1;
        }
        else if (Strcmp(*argv, "-print_data") == 0)
        {
            print_data = 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
        {
            BIO_printf(bio_err, "Unknown option %s\n", *argv);
            goto bad;
        }
        argc--;
        argv++;
    }

    /* Simple checks first */
    if (datafile == NULL)
    {
        BIO_printf(bio_err, "Must supply message data to envelope.\n");
        ret = R_ERROR_FAILED;
        goto bad;
    }

    /* Display the help menu if an invalid command line option was entered */
    if (0)
    {
        char **pp;
bad:
        for (pp = cm_env_strm_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 */
    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 the certificate and cryptographic message contexts. These
     * contexts are required if any R_CERT_* or R_CM_* routines are used.
     */

    /* Create a new certificate context */
    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 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;
    }

    /*
     * Configure the enveloped data object. The enveloped data message will
     * eventually hold all of the information for the cryptographic message.
     * Set up the information required to construct the cryptographic message:
     *     - The recipient certificates.
     *     - The encryption details, such as key size and Initialization
     *       Vector.
     *     - The data to envelope.
     */

    /* Create a new enveloped data object */
    if ((ret = R_CM_new(ctx, R_CM_TYPE_ENVELOPED_DATA, &obj)) != R_ERROR_NONE)
    {
        BIO_printf(bio_err, "R_CM_new failure (Enveloped Data)\n");
        goto end;
    }

    /* Add all recipients into the enveloped data message */
    if ((ret = add_recipients(bio_err, cert_ctx, obj, certfile, certtype,
        certform)) != R_ERROR_NONE)
    {
        goto end;
    }

    /* Set the key size against the object (optional) */
    if (key_size != 0)
    {
        if ((ret = R_CM_set_info(obj, R_CM_INFO_KEY_SIZE, &key_size)) !=
            R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Set symmetric key size failure\n");
            goto end;
        }
    }

    /* Set the effective bits of the key against the object (optional) */
    if (eff_bits != 0)
    {
        if ((ret = R_CM_set_info(obj, R_CM_INFO_NO_BITS, &eff_bits)) !=
            R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Set symmetric key size failure\n");
            goto end;
        }
    }

    /* Set the number of encryption rounds against the object (optional) */
    if (no_rounds != 0)
    {
        if ((ret = R_CM_set_info(obj, R_CM_INFO_NO_ROUNDS, &no_rounds)) !=
            R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Set symmetric key size failure\n");
            goto end;
        }
    }

    /*
     * Assign the cryptographic message object to the BIO. A stack of BIOs is
     * formed for streaming, and the data is streamed through each BIO to
     * perform a specific part of the enveloping.
     */
    ret = R_CM_to_BIO(obj, R_RES_FLAG_DEF, &bio_cm);
    if (ret != R_ERROR_NONE)
    {
        BIO_printf(bio_err, "Could not assign message object to BIO filter\n");
        goto end;
    }

    /*
     * The no_ci option creates a cryptographic message without the outer
     * ContentInfo. It is supplied so that cryptographic messages can
     * be nested, since the nested messages must have the ContentInfo removed.
     * The streaming implementation does not strip off the ContentInfo
     * automatically so this must be explicitly performed if nesting is
     * required.
     */
    if (no_ci)
    {
        BIO_set_unwrapped(bio_cm);
    }

    /*
     * The data_type option is used to communicate the type of data being
     * enveloped to the streaming interface. This is only required when nesting
     * cryptographic messages. The nested message must have the outer content
     * information sequence explicitly removed. If the ContentInfo is not
     * present, the streaming interface cannot determine the type of data being
     * enveloped, unless explicitly told.
     */
    if (data_type)
    {
        BIO_set_content_type(bio_cm, cm_type);
    }

    /*
     * The block size can be set so that the encrypted data is written out in
     * chunks of the specified size
     */
    if (block_size > 0)
    {
        BIO_set_buffer_size(bio_cm, block_size);
    }

    /*
     * Create the enveloping output stream and set the encryption options.
     * Create the output BIO to write to a file and push the cryptographic
     * message filter on top of the output stream. Set any options required by
     * the BIO filter.
     */

    /* Open the output BIO */
    if ((bio_mem = BIO_new_mem()) == NULL)
    {
        BIO_printf(bio_err, "Failed to create memory BIO\n");
        ret = R_ERROR_FAILED;
        goto end;
    }

    /* Push the cryptographic message filter on top of the output stream */
    BIO_push(bio_cm, bio_mem);

    if (BIO_set_alg(bio_cm, enc_alg) != 1)
    {
        BIO_printf(bio_err, "Set encryption algorithm failure\n");
        ret = R_ERROR_FAILED;
        goto end;
    }

    /*
     * Open the file BIO. This is used to store the output of the BIO stack
     * to a file so it can be referred to later and used by the server sample
     * (that is, cm_open_strm_membio.c).
     */

    /* Open the file BIO */
    if (cm_file == NULL)
    {
        if ((bio_outfile = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL)
        {
            ret = R_ERROR_ALLOC_FAILURE;
            goto end;
        }
        BIO_set_flags(bio_outfile, BIO_FLAGS_FLUSH_ON_WRITE);
    }
    else if ((bio_outfile = BIO_new_file(cm_file, "wb")) == NULL)
    {
        BIO_printf(bio_err, "Could not open file: %s\n", cm_file);
        ret = R_ERROR_FAILED;
        goto end;
    }

    /*
     * Read in the data file and write it out through a enveloped
     * data BIO filter
     */

    /* Open a BIO to read the data from */
    if ((bio_in = BIO_new_file(datafile, "rb")) == NULL)
    {
        BIO_printf(bio_err, "Could not open: %s\nNo data to envelop.\n",
            datafile);
        ret = R_ERROR_FAILED;
        goto end;
    }

    /*
     * Create a buffer to store the data from the input file before writing
     * it to the cryptographic BIO (that is, the top of the BIO stack)
     */
    data = (unsigned char *)Malloc(BUF_LEN);
    if (data == NULL)
    {
        ret = R_ERROR_ALLOC_FAILURE;
        goto end;
    }

    /* The message data is written out as it is read - diagnostic only! */
    if (print_data == 1)
    {
        BIO_printf(bio_err, "\nMESSAGE:\n");
    }

    /*
     * Read in the data file and write it out through the enveloped data BIO
     * filter
     */
    while ((data_len = BIO_read(bio_in, (char *)(data), BUF_LEN)) != 0)
    {
        char *p;

        /* If the read is to be retried there will be nothing to write */
        if (data_len == -1)
        {
            if (BIO_should_retry(bio_in))
            {
                continue;
            }
            else
            {
                BIO_printf(bio_err, "Fatal error while reading\n" );
                ret = R_ERROR_IO;
                goto end;
            }
        }

        /* Write the data that is read - diagnostic only! */
        if (print_data == 1)
        {
            BIO_dump(bio_err, data, data_len);
        }

        p = (char *)data;

        /*
         * Write out the data in the enveloped data message.
         * Keep trying while the error is retried.
         */
        do
        {
            len = BIO_write(bio_cm, p, data_len);
            if (len > 0)
            {
                data_len -= len;
                p += len;
            }
            else if ((len < 0) && !BIO_should_retry(bio_cm))
            {
                break;
            }

            /*
             * Read the output of the bio stack and write it to a file. This
             * extracts the enveloped data from the bottom of the BIO stack. The
             * routine will grab whatever data has been processed, if any. This
             * can be replaced by another routine for specific application needs.
             */
            if ((data_stream_sink(bio_outfile, bio_mem)) != 0)
            {
              BIO_printf(bio_err, "Failed to write data to output stream\n");
              ret = R_ERROR_IO;
              goto end;
            }
        }
        while (data_len > 0);

        if (data_len > 0)
        {
            BIO_printf(bio_err, "Failed to write out data\n");
            ret = R_ERROR_IO;
            goto end;
        }
    }

    /*
     * Signal the end of the msg to the cryptographic BIO. At this point all
     * the data to be enveloped has been written to the BIO stack. However,
     * the BIOs in the stack need to finish processing the data and then add
     * trailer information. This requires data to be passed down the stack of
     * BIOs and a number of iterations may occur before the BIOs signal to
     * the application that all the data has been written out.
     */
    do
    {
        len = BIO_end_of_msg(bio_cm);
    }
    while ((len == -1) && (BIO_should_retry(bio_cm)));

    /*
     * Extract the remainder of the enveloped data as everything has been
     * processed; that is, all data has been pushed through the BIO stack.
     */
    if ((data_stream_sink(bio_outfile, bio_mem)) != 0)
    {
      BIO_printf(bio_err, "Failed to write data to output stream\n");
      ret = R_ERROR_IO;
      goto end;
    }

    /* A length of -1 indicates there was an unrecoverable error */
    if (len == -1)
    {
        BIO_printf(bio_err,
            "Fatal error while encrypting or writing recipients\n" );
        ret = R_ERROR_IO;
    }

    print_data = 0;

    /* Print out the recipient details if requested */
    if (print_rec == 1)
    {
        /*
         * Print the enveloped data message. If print_data != 0 then the
         * data will also be printed to bio_err.
         */
        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;
        }
    }

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 (data != NULL)
    {
        Free(data);
    }
    if (obj != NULL)
    {
        R_CM_free(obj);
    }
    if (data_obj != NULL)
    {
        R_CM_free(data_obj);
    }
    if (bio_cm != NULL)
    {
        BIO_free(bio_cm);
    }
    if (ctx != NULL)
    {
        R_CM_CTX_free(ctx);
    }
    if (cert_ctx != NULL)
    {
        R_CERT_CTX_free(cert_ctx);
    }
    if (bio_outfile != NULL)
    {
        BIO_free(bio_outfile);
    }
    if (bio_in != NULL)
    {
        BIO_free(bio_in);
    }
    if (bio_mem != NULL)
    {
        BIO_free(bio_mem);
    }
    if (lib_ctx != NULL)
    {
        PRODUCT_LIBRARY_FREE(lib_ctx);
    }
    if (bio_err != NULL)
    {
        BIO_free(bio_err);
    }

    return(R_ERROR_EXIT_CODE(ret));
}


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