RSA BSAFE Micro Edition Suite

Streamlined security for mobile and embedded devices

Search  Print

cm_sign.c

/* $Id: cm_sign.c,v 1.41 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.c
 * This sample demonstrates how to create a PKCS #7 signed data message.
 *
 * The data to sign is provided in a file and specified by the -data option.
 * The file may contain plain data or the data may be wrapped. Where the data
 * is wrapped the -wrapped switch must be used. The data may be included in
 * the PKCS #7 signed data message or detached from the message. This is
 * controlled by the -detached switch. Where the data is detached it may be in
 * a separate file or appended to the PKCS #7 signed data message within the
 * same file. If the -single switch is specified the detached data is appended
 * in the same file.
 *
 * The data may be signed by a number of signers. For each signer the private
 * key, certificate and digest must be specified. In the case of multiple
 * signers, the information is colon separated in a continuous string (see
 * 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 the data contained within the PKCS #7 signed data message:
 *   cm_sign -out pkcs7.sd -data data.txt
 *           -keys a.key -sign_certs a.cert -digests MD5
 *
 * Sign the data within the PKCS #7 message - input data is wrapped:
 *   cm_sign -out pkcs7.sd -data data.txt -wrapped
 *           -keys a.key -sign_certs a.cert -digests MD5
 *
 * Sign data within PKCS #7 message - 2 signers!
 *   cm_sign -out pkcs7.sd -data data.txt
 *           -keys a.key:b.key -sign_certs a.cert:b.cert -digests MD5:SHA1
 *
 * Sign data is detached from the PKCS #7 message. The data is appended
 * to the PKCS #7 message in the same file:
 *   cm_sign -out pkcs7.sd -data data.txt
 *           -keys a.key -sign_certs a.cert -digests MD5 -detached -single
 *
 * The degenerate case where there are no signers on the content:
 *   cm_sign -out pkcs7.sd -no_sign no_sign_certs b.certs
 *
 * where: pkcs7.sd    = Output file where the PKCS #7 signed data is written.
 *        data.txt    = The text file that has the data to sign.
 *        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"

/* Usage help message */
static char *cm_sign_usage[] =
{
    "usage: cm_sign [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",
    " -wrapped             - The data in the input data file is wrapped\n",
    " -single              - The cryptographic message and detached data are\n",
    "                        put into the same file, the cryptographic\n",
    "                        message first followed by the detached data\n",
    " -wrap                - The data when written out is to be wrapped\n",
    " -detached            - The data is detached from the message\n",
    " -keys list           - A list of private keys to sign with (colon\n",
    "                        separated)\n",
    " -keytype encoding    - Encode 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 for each key (colon separated)\n",
    " -sign_certs list     - A list of certificates matching private keys\n",
    " -certs list          - A list of Certification Authority (CA)\n",
    "                        certificates to add (colon separated)\n",
    " -certtype encoding   - Encoding of the certificates - only X509\n",
    "                        (default) supported\n",
#ifdef NO_PEM
    " -certform format     - Format the certificates (BIN only)\n",
#else
    " -certform format     - Format the certificates\n",
    "                        - one of BIN (default), PEM\n",
#endif /* NO_PEM */
    " -no_auth             - Do not add digest to the authenticated\n",
    "                        attributes\n",
    " -no_cert             - Do not add the signer's certificate to the\n",
    "                        message\n",
    " -print_signer        - Print the certificate information for each of\n",
    "                        the signers in the cryptographic message\n",
    " -print_data          - Print the signed data of the cryptographic\n",
    "                        message\n",
    " -no_sign             - Do not sign the content (degenerate case)\n",
    " -no_sign_certs       - A list of Certificates to add to a messaage\n",
    "                        that is unsigned (only valid in conjunction\n",
    "                        with the -no_sign option)\n",
#ifdef NO_SOFTWARE_CRYPTO
    " -no_fips140          - Use non FIPS140 crypto implementations\n",
#endif /* NO_SOFTWARE_CRYPTO */
    " -eg                  - Print example usages for this utility\n",
    NULL
};

static char *cm_sign_example_usage[] =
{
    "Signed data contained within the PKCS7 signed data message:\n",
    "cm_sign -out pkcs7.sd -data data.txt \n",
    "        -keys a.key -sign_certs a.cert -digests MD5\n",
    "\n",
    "Signed data within the PKCS7 message - input data is wrapped.\n",
    "cm_sign -out pkcs7.sd -data data.txt -wrapped\n",
    "        -keys a.key -sign_certs a.cert -digests MD5\n",
    "\n",
    "Signed data within the PKCS7 message - 2 signers!\n",
    "  cm_sign -out pkcs7.sd -data data.txt\n",
    "         -keys a.key:b.key -sign_certs a.cert:b.cert -digests MD5:SHA1\n",
    "\n",
    "Signed data is detached from the PKCS7 message. The data is appended\n",
    "to the PKCS7 message in the same file.\n",
    "  cm_sign -out pkcs7.sd -data data.txt \n",
    "         -keys a.key -sign_certs a.cert -digests MD5 -detached -single\n",
    "\n",
    "The degenerate case where there are no signers on the content:\n",
    "cm_sign -out pkcs7.sd -no_sign no_sign_certs b.certs\n\n",
    "where: pkcs7.sd = Output file where the PKCS #7 signed data is written\n",
    "       data.txt = The text file that has the data to sign\n",
    "       a.cert   = The signer's certificate\n",
    "       a.key    = The private key of the signer\n",
    "       b.certs  = One or more Certificates to be added to an unsigned\n",
    "                  message.\n",
    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_err   = NULL;
    BIO              *bio_file  = 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;
    char             *cm_file;
    char             *datafile;
    char             *certfile;
    char             *signcertfile;
    char             *dgstname;
    char             *keyfile;
    char             *str;
    R_CERT_TYPE       certtype;
    R_FORMAT          certform;
    R_PKEY_TYPE       keytype;
    R_FORMAT          keyform;
    int               detached;
    int               wrap;
    int               wrapped;
    int               single;
    int               print_sig;
    int               print_data;
    int               add_cert;
    int               add_auth;
    int               no_sign;
    R_CM_ENCODING     format;

    /* Set the defaults */
    cm_file      = NULL;
    datafile     = NULL;
    certfile     = NULL;
    signcertfile = NULL;
    keyfile      = NULL;
    dgstname     = NULL;
    certtype     = R_CERT_TYPE_X509;
    certform     = R_FORMAT_BINARY;
    keytype      = R_PKEY_TYPE_RSA;
    keyform      = R_FORMAT_BINARY;
    print_sig    = 0;
    print_data   = 0;
    detached     = 0;
    wrap         = 0;
    wrapped      = 0;
    single       = 0;
    add_cert     = 1;
    add_auth     = 1;
    no_sign      = 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 err;
    }

    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, "-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, "-single") == 0)
        {
            single = 1;
            detached = 1;
        }
        else if (Strcmp(*argv, "-wrap") == 0)
        {
            wrap = 1;
        }
        else if (Strcmp(*argv, "-detached") == 0)
        {
            detached = 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_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 ||
                    Strcmp(*argv, "-no_sign_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, "-no_auth") == 0)
        {
            add_auth = 0;
        }
        else if (Strcmp(*argv, "-no_cert") == 0)
        {
            add_cert = 0;
        }
        else if (Strcmp(*argv, "-print_signer") == 0)
        {
            print_sig = 1;
        }
        else if (Strcmp(*argv, "-print_data") == 0)
        {
            print_data = 1;
        }
        else if(Strcmp(*argv, "-no_sign") == 0)
        {
            no_sign = 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_sign_example_usage; (*egp) != NULL; egp++)
            {
                BIO_printf(bio_out, *egp);
            }
            goto err;
        }
        else
        {
            BIO_printf(bio_err, "Unknown option %s\n", *argv);
            goto bad;
        }
        argc--;
        argv++;
    }

    /* Simple checks first */
    if (datafile == NULL && no_sign == 0)
    {
        BIO_printf(bio_err, "Message data required\n");
        goto bad;
    }
    /* Check for conflicting options */
    if(no_sign != 0 && (signcertfile != NULL || keyfile != NULL || dgstname != NULL) )
    {
        BIO_printf(bio_err, "-no_sign conflicts with other options");
        goto bad;
    }
    /* Display the help menu if an invalid command line option was entered */
    if (0)
    {
        char **pp;
bad:
        for (pp = cm_sign_usage; (*pp != NULL); pp++)
        {
            BIO_printf(bio_err, *pp);
        }
        goto err;
    }

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

    /*
     * Create the 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_out, "Key context new failure\n");
        goto err;
    }

    /* 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_out, "Certificate context new failure\n");
        goto err;
    }
    /* 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 err;
    }

    if(no_sign == 0) 
    {
        /* Load the data to sign into a cryptographic message object */
        format = R_CM_ENCODING_FORMAT_RAW;
        if (wrapped == 1)
        {
            format = R_CM_ENCODING_FORMAT_WRAPPED;
        }

        if ((ret = R_CM_read_file(ctx, datafile, R_FORMAT_BINARY, format,
            &data_obj)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Failed to read data from file %s\n", datafile);
            goto err;
        }
    }
    /*
     * 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:
     *     - Signer certificates.
     *     - Extra certificates to include in the cryptographic message
     *       (optional).
     *     - Data to sign.
     *     - Flags to indicate what 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 err;
    }

    /* Add extra certificates to the signed data message */
    if ((ret = add_certs(bio_err, cert_ctx, obj, certfile, certtype,
        certform)) != R_ERROR_NONE)
    {
        goto err;
    }

    if( no_sign == 0)
    {
        /* 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 err;
        }
        /* Set the data against the object */
        if ((ret = R_CM_content_from_R_CM(obj, R_FLAG_SHARE_DATA, data_obj)) !=
            R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Setting the content failed\n");
            goto err;
        }
        /* Configure what needs to be added to the signed data message */
        R_CM_set_info(obj, R_CM_INFO_ADD_SIGNER_CERT, &add_cert);
        R_CM_set_info(obj, R_CM_INFO_ADD_AUTH_DIGEST, &add_auth);

        /* Sign the data */
        if ((ret = R_CM_sign(obj, R_CM_INDEX_ALL)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Failed to sign\n");
            goto err;
        }
    }
    /*
     * Print the data. Display the signer details if requested.
     */
    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_out, R_FORMAT_TEXT, &print_data,
            R_CM_ENCODING_FORMAT_WRAPPED)) != R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Write signedData data failure\n");
            goto err;
        }
    }

    /* Output the cryptographic message */
    if (cm_file != NULL)
    {
        /* Open the output BIO */
        if ((bio_file = BIO_new_file(cm_file, "wb")) == NULL)
        {
            BIO_printf(bio_err, "Failed to open file: %s\n", cm_file);
            ret = R_ERROR_ALLOC_FAILURE;
            goto err;
        }

        /* Select the right formatting for the signed data message */
        format = R_CM_ENCODING_FORMAT_WRAPPED;

        if (detached != 0)
        {
            format |= R_CM_ENCODING_FORMAT_DETACHED_DATA;
        }

        /* Write out the signed data message */
        if ((ret = R_CM_write(obj, bio_file, R_FORMAT_BINARY, NULL, format)) !=
            R_ERROR_NONE)
        {
            BIO_printf(bio_err, "Failed to write signedData to file\n");
            goto err;
        }

        /*
         * Append the data message if the data is detached and it is to be
         * appended at the end of the signed data
         */
        if ((detached != 0) && (single != 0))
        {
            /* Write out data message in the format required */
            if (wrap != 0)
            {
                format = R_CM_ENCODING_FORMAT_WRAPPED;
            }
            else
            {
                format = R_CM_ENCODING_FORMAT_RAW;
            }

            if ((ret = R_CM_write(data_obj, bio_file, R_FORMAT_BINARY, NULL,
                format)) != R_ERROR_NONE)
            {
                BIO_printf(bio_err, "Failed to write the appended data\n");
            }
        }
    }

err:

    /*
     * 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 (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_file != NULL)
    {
        BIO_free(bio_file);
    }
    if (lib_ctx != NULL)
    {
        PRODUCT_LIBRARY_FREE(lib_ctx);
    }
    if (bio_out != NULL)
    {
        BIO_free(bio_out);
    }
    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