cmscipher.c   [plain text]


/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

/*
 * Encryption/decryption routines for CMS implementation, none of which are exported.
 *
 */
#include <limits.h>

#include "cmslocal.h"

#include "secoid.h"
#include <security_asn1/secerr.h>
#include <security_asn1/secasn1.h>
#include <security_asn1/secport.h>

#include <Security/SecAsn1Templates.h>
#include <Security/SecRandom.h>
#include <CommonCrypto/CommonCryptor.h>

/*
 * -------------------------------------------------------------------
 * Cipher stuff.
 */

#if 0
typedef OSStatus (*nss_cms_cipher_function) (void *, unsigned char *, unsigned int *,
					unsigned int, const unsigned char *, unsigned int);
typedef OSStatus (*nss_cms_cipher_destroy) (void *, Boolean);
#endif

#define BLOCK_SIZE 4096

struct SecCmsCipherContextStr {
#if 1
    void *              cc;			/* CSP CONTEXT */
    Boolean		encrypt;		/* encrypt / decrypt switch */
    int			block_size;		/* block & pad sizes for cipher */
#else
    void *		cx;			/* PK11 cipher context */
    nss_cms_cipher_function doit;
    nss_cms_cipher_destroy destroy;
    Boolean		encrypt;		/* encrypt / decrypt switch */
    int			pad_size;
    int			pending_count;		/* pending data (not yet en/decrypted */
    unsigned char	pending_buf[BLOCK_SIZE];/* because of blocking */
#endif
};

typedef struct sec_rc2cbcParameterStr {
    SecAsn1Item rc2ParameterVersion;
    SecAsn1Item iv;
} sec_rc2cbcParameter;

__unused static const SecAsn1Template sec_rc2cbc_parameter_template[] = {
    { SEC_ASN1_SEQUENCE,
          0, NULL, sizeof(sec_rc2cbcParameter) },
    { SEC_ASN1_INTEGER | SEC_ASN1_SIGNED_INT,
          offsetof(sec_rc2cbcParameter,rc2ParameterVersion) },
    { SEC_ASN1_OCTET_STRING,
          offsetof(sec_rc2cbcParameter,iv) },
    { 0 }
};

/* default IV size in bytes */
#define DEFAULT_IV_SIZE	    8
/* IV/block size for AES */
#define AES_BLOCK_SIZE	    16
/* max IV size in bytes */
#define MAX_IV_SIZE	    AES_BLOCK_SIZE

#ifndef kCCKeySizeMaxRC2
#define kCCKeySizeMaxRC2 16
#endif
#ifndef kCCBlockSizeRC2
#define kCCBlockSizeRC2 8
#endif

static SecCmsCipherContextRef
SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorithmID *algid, Boolean encrypt)
{
    SecCmsCipherContextRef cc;
    SECOidData *oidData;
    SECOidTag algtag;
    OSStatus rv;
    uint8_t ivbuf[MAX_IV_SIZE];
    SecAsn1Item initVector = { DEFAULT_IV_SIZE, ivbuf };
    CCCryptorRef ciphercc = NULL;
    CCOptions cipheroptions = kCCOptionPKCS7Padding;
    int cipher_blocksize = 0;
    // @@@ Add support for PBE based stuff

    oidData = SECOID_FindOID(&algid->algorithm);
    if (!oidData)
	goto loser;
    algtag = oidData->offset;

    CCAlgorithm alg = -1;
    switch (algtag) {
        case SEC_OID_DES_CBC:
            alg = kCCAlgorithmDES;
            cipher_blocksize = kCCBlockSizeDES;
            break;
        case SEC_OID_DES_EDE3_CBC:
            alg = kCCAlgorithm3DES;
            cipher_blocksize = kCCBlockSize3DES;
            break;
        case SEC_OID_RC2_CBC:
            alg = kCCAlgorithmRC2;
            cipher_blocksize = kCCBlockSizeRC2;
            break;
        case SEC_OID_AES_128_CBC: 
        case SEC_OID_AES_192_CBC:
        case SEC_OID_AES_256_CBC:
            alg = kCCAlgorithmAES128;
            cipher_blocksize = kCCBlockSizeAES128;
            initVector.Length = AES_BLOCK_SIZE;
             break;
        default: 
            goto loser;
    }

    if (encrypt)
    {
        if (SecRandomCopyBytes(kSecRandomDefault, 
            initVector.Length, initVector.Data))
                goto loser;

	// Put IV into algid.parameters
	switch (algtag)
	{
	case SEC_OID_RC4:
	case SEC_OID_DES_EDE3_CBC:
	case SEC_OID_DES_EDE:
	case SEC_OID_DES_CBC:
	case SEC_OID_AES_128_CBC:
	case SEC_OID_AES_192_CBC:
	case SEC_OID_AES_256_CBC:
	case SEC_OID_FORTEZZA_SKIPJACK:
	case SEC_OID_DES_ECB:
	case SEC_OID_AES_128_ECB:
	case SEC_OID_AES_192_ECB:
	case SEC_OID_AES_256_ECB:
	case SEC_OID_DES_OFB:
	case SEC_OID_DES_CFB:
	    /* Just encode the initVector as an octet string. */
	    if (!SEC_ASN1EncodeItem(poolp, &algid->parameters,
				    &initVector, kSecAsn1OctetStringTemplate))
		goto loser;
	    break;
	case SEC_OID_RC2_CBC:
	case SEC_OID_RC5_CBC_PAD:
	default:
	    // @@@ Implement rc5 params stuff.
	    goto loser;
	    break;
	}
    }
    else
    {
	// Extract IV from algid.parameters
	// Put IV into algid.parameters
	switch (algtag)
	{
	case SEC_OID_RC4:
	case SEC_OID_DES_EDE3_CBC:
	case SEC_OID_DES_EDE:
	case SEC_OID_DES_CBC:
	case SEC_OID_AES_128_CBC:
	case SEC_OID_AES_192_CBC:
	case SEC_OID_AES_256_CBC:
	case SEC_OID_FORTEZZA_SKIPJACK:
	case SEC_OID_DES_ECB:
	case SEC_OID_AES_128_ECB:
	case SEC_OID_AES_192_ECB:
	case SEC_OID_AES_256_ECB:
	case SEC_OID_DES_OFB:
	case SEC_OID_DES_CFB:
	{
	    SecAsn1Item iv = {};
	    /* Just decode the initVector from an octet string. */
	    rv = SEC_ASN1DecodeItem(NULL, &iv, kSecAsn1OctetStringTemplate, &(algid->parameters));
	    if (rv)
		goto loser;
	    if (initVector.Length != iv.Length) {
		PORT_Free(iv.Data);
		goto loser;
	    }
	    memcpy(initVector.Data, iv.Data, initVector.Length);
	    PORT_Free(iv.Data);
	    break;
	}
	case SEC_OID_RC2_CBC:
	case SEC_OID_RC5_CBC_PAD:
	default:
	    // @@@ Implement rc5 params stuff.
	    goto loser;
	    break;
	}
    }

        if (CCCryptorCreate(encrypt ? kCCEncrypt : kCCDecrypt, 
            alg, cipheroptions, CFDataGetBytePtr(key), CFDataGetLength(key), 
            initVector.Data, &ciphercc))
                goto loser;

    cc = (SecCmsCipherContextRef)PORT_ZAlloc(sizeof(SecCmsCipherContext));
    if (cc == NULL)
	goto loser;

    cc->cc = ciphercc;
    cc->encrypt = encrypt;
    cc->block_size =cipher_blocksize;
    return cc;
loser:
    if (ciphercc)
        CCCryptorRelease(ciphercc);

    return NULL;
}

/*
 * SecCmsCipherContextStartDecrypt - create a cipher context to do decryption
 * based on the given bulk * encryption key and algorithm identifier (which may include an iv).
 *
 * XXX Once both are working, it might be nice to combine this and the
 * function below (for starting up encryption) into one routine, and just
 * have two simple cover functions which call it. 
 */
SecCmsCipherContextRef
SecCmsCipherContextStartDecrypt(SecSymmetricKeyRef key, SECAlgorithmID *algid)
{
    return SecCmsCipherContextStart(NULL, key, algid, PR_FALSE);
}

/*
 * SecCmsCipherContextStartEncrypt - create a cipher object to do encryption,
 * based on the given bulk encryption key and algorithm tag.  Fill in the algorithm
 * identifier (which may include an iv) appropriately.
 *
 * XXX Once both are working, it might be nice to combine this and the
 * function above (for starting up decryption) into one routine, and just
 * have two simple cover functions which call it. 
 */
SecCmsCipherContextRef
SecCmsCipherContextStartEncrypt(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorithmID *algid)
{
    return SecCmsCipherContextStart(poolp, key, algid, PR_TRUE);
}

void
SecCmsCipherContextDestroy(SecCmsCipherContextRef cc)
{
    PORT_Assert(cc != NULL);
    if (cc == NULL)
	return;

    CCCryptorRelease(cc->cc);

    PORT_Free(cc);
}

static unsigned int
SecCmsCipherContextLength(SecCmsCipherContextRef cc, unsigned int input_len, Boolean final, Boolean encrypt)
{
    return ((input_len + cc->block_size - 1) / cc->block_size * cc->block_size) + (final ? cc->block_size : 0);
}

/*
 * SecCmsCipherContextDecryptLength - find the output length of the next call to decrypt.
 *
 * cc - the cipher context
 * input_len - number of bytes used as input
 * final - true if this is the final chunk of data
 *
 * Result can be used to perform memory allocations.  Note that the amount
 * is exactly accurate only when not doing a block cipher or when final
 * is false, otherwise it is an upper bound on the amount because until
 * we see the data we do not know how many padding bytes there are
 * (always between 1 and bsize).
 *
 * Note that this can return zero, which does not mean that the decrypt
 * operation can be skipped!  (It simply means that there are not enough
 * bytes to make up an entire block; the bytes will be reserved until
 * there are enough to encrypt/decrypt at least one block.)  However,
 * if zero is returned it *does* mean that no output buffer need be
 * passed in to the subsequent decrypt operation, as no output bytes
 * will be stored.
 */
unsigned int
SecCmsCipherContextDecryptLength(SecCmsCipherContextRef cc, unsigned int input_len, Boolean final)
{
    return SecCmsCipherContextLength(cc, input_len, final, PR_FALSE);
}

/*
 * SecCmsCipherContextEncryptLength - find the output length of the next call to encrypt.
 *
 * cc - the cipher context
 * input_len - number of bytes used as input
 * final - true if this is the final chunk of data
 *
 * Result can be used to perform memory allocations.
 *
 * Note that this can return zero, which does not mean that the encrypt
 * operation can be skipped!  (It simply means that there are not enough
 * bytes to make up an entire block; the bytes will be reserved until
 * there are enough to encrypt/decrypt at least one block.)  However,
 * if zero is returned it *does* mean that no output buffer need be
 * passed in to the subsequent encrypt operation, as no output bytes
 * will be stored.
 */
unsigned int
SecCmsCipherContextEncryptLength(SecCmsCipherContextRef cc, unsigned int input_len, Boolean final)
{
    return SecCmsCipherContextLength(cc, input_len, final, PR_TRUE);
}


static OSStatus
SecCmsCipherContextCrypt(SecCmsCipherContextRef cc, unsigned char *output,
		  unsigned int *output_len_p, unsigned int max_output_len,
		  const unsigned char *input, unsigned int input_len,
		  Boolean final, Boolean encrypt)
{
    size_t bytes_output = 0;
    OSStatus rv = 0;

    if (input_len)
    {
        rv = CCCryptorUpdate(cc->cc, input, input_len, output, max_output_len, &bytes_output);
    }

    if (!rv && final)
    {
        size_t bytes_output_final = 0;
        rv = CCCryptorFinal(cc->cc, output+bytes_output, max_output_len-bytes_output, &bytes_output_final);
        bytes_output += bytes_output_final;
    }

    if (rv)
	PORT_SetError(SEC_ERROR_BAD_DATA);
    else if (output_len_p)
	*output_len_p = (unsigned int)bytes_output; /* This cast is safe since bytes_output can't be bigger than max_output_len */

    return rv;
}

/*
 * SecCmsCipherContextDecrypt - do the decryption
 *
 * cc - the cipher context
 * output - buffer for decrypted result bytes
 * output_len_p - number of bytes in output
 * max_output_len - upper bound on bytes to put into output
 * input - pointer to input bytes
 * input_len - number of input bytes
 * final - true if this is the final chunk of data
 *
 * Decrypts a given length of input buffer (starting at "input" and
 * containing "input_len" bytes), placing the decrypted bytes in
 * "output" and storing the output length in "*output_len_p".
 * "cc" is the return value from SecCmsCipherStartDecrypt.
 * When "final" is true, this is the last of the data to be decrypted.
 *
 * This is much more complicated than it sounds when the cipher is
 * a block-type, meaning that the decryption function will only
 * operate on whole blocks.  But our caller is operating stream-wise,
 * and can pass in any number of bytes.  So we need to keep track
 * of block boundaries.  We save excess bytes between calls in "cc".
 * We also need to determine which bytes are padding, and remove
 * them from the output.  We can only do this step when we know we
 * have the final block of data.  PKCS #7 specifies that the padding
 * used for a block cipher is a string of bytes, each of whose value is
 * the same as the length of the padding, and that all data is padded.
 * (Even data that starts out with an exact multiple of blocks gets
 * added to it another block, all of which is padding.)
 */ 
OSStatus
SecCmsCipherContextDecrypt(SecCmsCipherContextRef cc, unsigned char *output,
		  unsigned int *output_len_p, unsigned int max_output_len,
		  const unsigned char *input, unsigned int input_len,
		  Boolean final)
{
    return SecCmsCipherContextCrypt(cc, output,
		  output_len_p,  max_output_len,
		  input, input_len,
		  final, PR_FALSE);
}

/*
 * SecCmsCipherContextEncrypt - do the encryption
 *
 * cc - the cipher context
 * output - buffer for decrypted result bytes
 * output_len_p - number of bytes in output
 * max_output_len - upper bound on bytes to put into output
 * input - pointer to input bytes
 * input_len - number of input bytes
 * final - true if this is the final chunk of data
 *
 * Encrypts a given length of input buffer (starting at "input" and
 * containing "input_len" bytes), placing the encrypted bytes in
 * "output" and storing the output length in "*output_len_p".
 * "cc" is the return value from SecCmsCipherStartEncrypt.
 * When "final" is true, this is the last of the data to be encrypted.
 *
 * This is much more complicated than it sounds when the cipher is
 * a block-type, meaning that the encryption function will only
 * operate on whole blocks.  But our caller is operating stream-wise,
 * and can pass in any number of bytes.  So we need to keep track
 * of block boundaries.  We save excess bytes between calls in "cc".
 * We also need to add padding bytes at the end.  PKCS #7 specifies
 * that the padding used for a block cipher is a string of bytes,
 * each of whose value is the same as the length of the padding,
 * and that all data is padded.  (Even data that starts out with
 * an exact multiple of blocks gets added to it another block,
 * all of which is padding.)
 *
 * XXX I would kind of like to combine this with the function above
 * which does decryption, since they have a lot in common.  But the
 * tricky parts about padding and filling blocks would be much
 * harder to read that way, so I left them separate.  At least for
 * now until it is clear that they are right.
 */ 
OSStatus
SecCmsCipherContextEncrypt(SecCmsCipherContextRef cc, unsigned char *output,
		  unsigned int *output_len_p, unsigned int max_output_len,
		  const unsigned char *input, unsigned int input_len,
		  Boolean final)
{
    return SecCmsCipherContextCrypt(cc, output,
		  output_len_p,  max_output_len,
		  input, input_len,
		  final, PR_TRUE);
}