RSA BSAFE Micro Edition Suite

Streamlined security for mobile and embedded devices

Search  Print

cm_sign_strm.c

/* $Id: cm_sign_strm.c,v 1.45 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_sign_strm.c
 * This sample demonstrates how to create a PKCS #7 signed data message using
 * the streaming interface.
 *
 * The data may be signed by a number of signers. The private key, certificate,
 * and digest must be specified for each signer. In the case of multiple
 * signers, the information is colon separated in a continuous string (see
 * the examples that follow). The order of the keys, certificates, and digests
 * specified must be consistent. That is, the 2nd key in the list must
 * correspond to the 2nd certificate and digest.
 *
 * For example, to:
 *
 * Sign data contained within the PKCS #7 signed data message:
 *   cm_sign_strm -out pkcs7.sd -data data.txt
 *           -keys a.key -sign_certs a.cert -digests MD5
 *
 * Sign data within the PKCS #7 message - 2 signers!
 *   cm_sign_strm -out pkcs7.sd -data data.txt
 *           -keys a.key:b.key -sign_certs a.cert:b.cert -digests MD5:SHA1
 *
 * where: pkcs7.sd    = The output file where PKCS #7 signed data is written.
 *        data.txt    = The text file that has the data to be signed.
 *        a.cert      = The signer's certificate.
 *        a.key       = The private key of the signer.
 *        b.certs     = One or more Certificates to be added to an unsigned
 *                      message.
*/

#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_sign_strm_usage[] =
{
    "usage: cm_sign_strm [options]\n",
    "where options are:\n",
    " -out file            - The file containing the cryptographic message\n",
    " -data file           - The file containing the data to sign\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               - The output message has no ContentInfo\n",
    " -keys list           - The list of private keys to sign with (colon\n",
    "                        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 */
    " -digests list        - Digests to use with each key (colon separated)\n",
    " -sign_certs list     - List of certificates matching private keys\n",
    " -certs list          - List of CA certificates to add (colon\n",
    "                        separated)\n",
    " -certtype encoding   - Encoding of the certificates - only X509\n",
    "                        (default) supported\n",
#ifdef NO_PEM
    " -certform format     - Format of the certs (BIN only)\n",
#else
    " -certform format     - Format of the certs one of BIN (default), PEM\n",
#endif /* NO_PEM */
    " -block_size number   - Size of the blocks to write out in one chunk\n",
    " -print_signer        - Print the certificate information for each of the\n",
    "                        signers in the cryptographic message\n",
    " -print_data          - Print the message data\n",
#ifdef NO_SOFTWARE_CRYPTO
    " -no_fips140          - Use non FIPS140 crypto implementations\n",
#endif /* NO_SOFTWARE_CRYPTO */
    NULL
};


/*
 * 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_out   = NULL;
    BIO              *bio_cm    = NULL;
    BIO              *bio_err   = NULL;
    BIO              *bio_in    = 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;
    R_PKEY_CTX       *key_ctx   = NULL;
    unsigned char    *data      = NULL;
    int               data_len;
    char             *cm_file;
    char             *datafile;
    char             *certfile;
    R_CERT_TYPE       certtype;
    R_FORMAT          certform;
    char             *signcertfile;
    char             *dgstname;
    char             *keyfile;
    R_PKEY_TYPE       keytype;
    R_FORMAT          keyform;
    char             *options;
    unsigned char    *cm_data   = NULL;
    char             *str;
    int               print_sig;
    int               print_data;
    int               len;
    int               no_ci;
    int               data_type;
    R_CM_TYPE         cm_type;
    int               block_size;

    /* Set the defaults */
    cm_file      = NULL;
    datafile     = NULL;
    certfile     = NULL;
    certtype     = R_CERT_TYPE_X509;
    certform     = R_FORMAT_BINARY;
    signcertfile = NULL;
    keyfile      = NULL;
    keytype      = R_PKEY_TYPE_RSA;
    keyform      = R_FORMAT_BINARY;
    dgstname     = NULL;
    options      = NULL;
    print_sig    = 0;
    print_data   = 0;
    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.
     */
    if ((bio_err = BIO_new_fp(stderr, BIO_NOCLOSE)) == 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, "-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_out, "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_out, "Unknown key format %s\n", str);
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-digests") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            dgstname = *(++argv);
        }
        else if (Strcmp(*argv, "-sign_certs") == 0)
        {
            if (--argc < 1)
            {
                goto bad;
            }
            signcertfile = *(++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_out, "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_out, "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_out, "Block size must be greater than 0\n");
                goto bad;
            }
        }
        else if (Strcmp(*argv, "-print_signer") == 0)
        {
            print_sig = 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, "Data required for signing.\n");
        goto bad;
    }

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

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

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

    /*
     * Create and configure the signed data object. The signed data message
     * will eventually hold all of the information for the cryptographic
     * message. Set up the information required to construct the cryptographic
     * message:
     *     - The new signed data message object.
     *     - The signer certificates.
     *     - The extra certificates to include in the cryptographic message
     *       (optional).
     *     - The data to sign.
     *     - The flags to indicate which certificates to include in the
     *       cryptographic message.
     */

    /* Create a new signed data message */
    if ((ret = R_CM_new(ctx, R_CM_TYPE_SIGNED_DATA, &obj)) != R_ERROR_NONE)
    {
        BIO_printf(bio_err, "R_CM_new failure (Signed Data)\n");
        goto end;
    }

    /* Load the certificates to be sent in the signed data message */
    if ((ret = add_certs(bio_err, cert_ctx, obj, certfile,
        certtype, certform)) != R_ERROR_NONE)
    {
        goto end;
    }

    /* Add all signers to the signed data message */
    if ((ret = add_signers(bio_err, cert_ctx, key_ctx, obj, signcertfile,
        certtype, certform, keyfile, keytype, keyform, dgstname)) !=
        R_ERROR_NONE)
    {
        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 signing.
     */
    ret = R_CM_to_BIO(obj, R_RES_FLAG_DEF, &bio_cm);
    if (ret != R_ERROR_NONE)
    {
        BIO_printf(bio_err, "Could not signed data message 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 content info 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
     * signed to the streaming interface. This is only required when nesting
     * cryptographic messages. The nested message must have the outer content
     * info sequence explicitly removed. If the ContentInfo is not present, the
     * streaming interface cannot determine the type of data being signed,
     * unless explicitly told.
     */
    if (data_type)
    {
        BIO_set_content_type(bio_cm, cm_type);
    }

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

    /*
     * Create the signing output stream. Create the output BIO to write to a
     * file and push the cryptographic message filter on top of the output
     * stream.
     */

    /* Open the output BIO */
    if (cm_file == NULL)
    {
        if ((bio_out = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL)
        {
            ret = R_ERROR_ALLOC_FAILURE;
            goto end;
        }
        BIO_set_flags(bio_out, BIO_FLAGS_FLUSH_ON_WRITE);
    }
    else if ((bio_out = 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;
    }

    BIO_push(bio_cm, bio_out);
    /*
     * Read in the data file and write it out through a signed data
     * BIO filter
     */

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

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

    /* Create a buffer to write data into */
    data = (unsigned char *)Malloc(BUF_LEN);
    if (data == NULL)
    {
        ret = R_ERROR_ALLOC_FAILURE;
        goto end;
    }

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

        if (print_data == 1)
        {
            BIO_dump(bio_err, data, data_len);
        }

        p = (char *)data;

        /*
         * Write out the data in the signed 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;
            }
        }
        while (data_len > 0);

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

    /*
     * Calculate the signature and finalize the cryptographic message. All data
     * has been written. Write out the signer information of the signed data
     * message.
     */
    do
    {
        len = BIO_end_of_msg(bio_cm);
    }
    while ((len == -1) && (BIO_should_retry(bio_cm)));

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

    /* Print out the signer details if required */
    if (print_sig == 1)
    {
        /*
         * Print the signed data message. If print_data != 0 then the
         * data will also be printed to bio_out.
         */
        if ((ret = R_CM_write(obj, bio_err, R_FORMAT_TEXT, &print_data,
            R_CM_ENCODING_FORMAT_WRAPPED)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Write signedData 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 (cm_data != NULL)
    {
        Free(cm_data);
    }
    if (data != NULL)
    {
        Free(data);
    }
    if (obj != NULL)
    {
        R_CM_free(obj);
    }
    if (data_obj != NULL)
    {
        R_CM_free(data_obj);
    }
    if (ctx != NULL)
    {
        R_CM_CTX_free(ctx);
    }
    if (cert_ctx != NULL)
    {
        R_CERT_CTX_free(cert_ctx);
    }
    if (key_ctx != NULL)
    {
        R_PKEY_CTX_free(key_ctx);
    }
    if (bio_cm != NULL)
    {
        BIO_free(bio_cm);
    }
    if (bio_out != NULL)
    {
        BIO_free(bio_out);
    }
    if (bio_in != NULL)
    {
        BIO_free(bio_in);
    }
    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