Blame nss/lib/pkcs7/secmime.c

Packit 40b132
/* This Source Code Form is subject to the terms of the Mozilla Public
Packit 40b132
 * License, v. 2.0. If a copy of the MPL was not distributed with this
Packit 40b132
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * Stuff specific to S/MIME policy and interoperability.
Packit 40b132
 * Depends on PKCS7, but there should be no dependency the other way around.
Packit 40b132
 */
Packit 40b132
Packit 40b132
#include "secmime.h"
Packit 40b132
#include "secoid.h"
Packit 40b132
#include "pk11func.h"
Packit 40b132
#include "ciferfam.h"	/* for CIPHER_FAMILY symbols */
Packit 40b132
#include "secasn1.h"
Packit 40b132
#include "secitem.h"
Packit 40b132
#include "cert.h"
Packit 40b132
#include "key.h"
Packit 40b132
#include "secerr.h"
Packit 40b132
Packit 40b132
typedef struct smime_cipher_map_struct {
Packit 40b132
    unsigned long cipher;
Packit 40b132
    SECOidTag algtag;
Packit 40b132
    SECItem *parms;
Packit 40b132
} smime_cipher_map;
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * These are macros because I think some subsequent parameters,
Packit 40b132
 * like those for RC5, will want to use them, too, separately.
Packit 40b132
 */
Packit 40b132
#define SMIME_DER_INTVAL_16	SEC_ASN1_INTEGER, 0x01, 0x10
Packit 40b132
#define SMIME_DER_INTVAL_40	SEC_ASN1_INTEGER, 0x01, 0x28
Packit 40b132
#define SMIME_DER_INTVAL_64	SEC_ASN1_INTEGER, 0x01, 0x40
Packit 40b132
#define SMIME_DER_INTVAL_128	SEC_ASN1_INTEGER, 0x02, 0x00, 0x80
Packit 40b132
Packit 40b132
#ifdef SMIME_DOES_RC5	/* will be needed; quiet unused warning for now */
Packit 40b132
static unsigned char smime_int16[] = { SMIME_DER_INTVAL_16 };
Packit 40b132
#endif
Packit 40b132
static unsigned char smime_int40[] = { SMIME_DER_INTVAL_40 };
Packit 40b132
static unsigned char smime_int64[] = { SMIME_DER_INTVAL_64 };
Packit 40b132
static unsigned char smime_int128[] = { SMIME_DER_INTVAL_128 };
Packit 40b132
Packit 40b132
static SECItem smime_rc2p40 = { siBuffer, smime_int40, sizeof(smime_int40) };
Packit 40b132
static SECItem smime_rc2p64 = { siBuffer, smime_int64, sizeof(smime_int64) };
Packit 40b132
static SECItem smime_rc2p128 = { siBuffer, smime_int128, sizeof(smime_int128) };
Packit 40b132
Packit 40b132
static smime_cipher_map smime_cipher_maps[] = {
Packit 40b132
    { SMIME_RC2_CBC_40,		SEC_OID_RC2_CBC,	&smime_rc2p40 },
Packit 40b132
    { SMIME_RC2_CBC_64,		SEC_OID_RC2_CBC,	&smime_rc2p64 },
Packit 40b132
    { SMIME_RC2_CBC_128,	SEC_OID_RC2_CBC,	&smime_rc2p128 },
Packit 40b132
#ifdef SMIME_DOES_RC5
Packit 40b132
    { SMIME_RC5PAD_64_16_40,	SEC_OID_RC5_CBC_PAD,	&smime_rc5p40 },
Packit 40b132
    { SMIME_RC5PAD_64_16_64,	SEC_OID_RC5_CBC_PAD,	&smime_rc5p64 },
Packit 40b132
    { SMIME_RC5PAD_64_16_128,	SEC_OID_RC5_CBC_PAD,	&smime_rc5p128 },
Packit 40b132
#endif
Packit 40b132
    { SMIME_DES_CBC_56,		SEC_OID_DES_CBC,	NULL },
Packit 40b132
    { SMIME_DES_EDE3_168,	SEC_OID_DES_EDE3_CBC,	NULL }
Packit 40b132
};
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * Note, the following value really just needs to be an upper bound
Packit 40b132
 * on the ciphers.
Packit 40b132
 */
Packit 40b132
static const int smime_symmetric_count = sizeof(smime_cipher_maps)
Packit 40b132
					 / sizeof(smime_cipher_map);
Packit 40b132
Packit 40b132
static unsigned long *smime_prefs, *smime_newprefs;
Packit 40b132
static int smime_current_pref_index = 0;
Packit 40b132
static PRBool smime_prefs_complete = PR_FALSE;
Packit 40b132
static PRBool smime_prefs_changed = PR_TRUE;
Packit 40b132
Packit 40b132
static unsigned long smime_policy_bits = 0;
Packit 40b132
Packit 40b132
Packit 40b132
static int
Packit 40b132
smime_mapi_by_cipher (unsigned long cipher)
Packit 40b132
{
Packit 40b132
    int i;
Packit 40b132
Packit 40b132
    for (i = 0; i < smime_symmetric_count; i++) {
Packit 40b132
	if (smime_cipher_maps[i].cipher == cipher)
Packit 40b132
	    break;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    if (i == smime_symmetric_count)
Packit 40b132
	return -1;
Packit 40b132
Packit 40b132
    return i;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * this function locally records the user's preference
Packit 40b132
 */
Packit 40b132
SECStatus 
Packit 40b132
SECMIME_EnableCipher(long which, int on)
Packit 40b132
{
Packit 40b132
    unsigned long mask;
Packit 40b132
Packit 40b132
    if (smime_newprefs == NULL || smime_prefs_complete) {
Packit 40b132
	/*
Packit 40b132
	 * This is either the very first time, or we are starting over.
Packit 40b132
	 */
Packit 40b132
	smime_newprefs = (unsigned long*)PORT_ZAlloc (smime_symmetric_count
Packit 40b132
				      * sizeof(*smime_newprefs));
Packit 40b132
	if (smime_newprefs == NULL)
Packit 40b132
	    return SECFailure;
Packit 40b132
	smime_current_pref_index = 0;
Packit 40b132
	smime_prefs_complete = PR_FALSE;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    mask = which & CIPHER_FAMILYID_MASK;
Packit 40b132
    if (mask == CIPHER_FAMILYID_MASK) {
Packit 40b132
    	/*
Packit 40b132
	 * This call signifies that all preferences have been set.
Packit 40b132
	 * Move "newprefs" over, after checking first whether or
Packit 40b132
	 * not the new ones are different from the old ones.
Packit 40b132
	 */
Packit 40b132
	if (smime_prefs != NULL) {
Packit 40b132
	    if (PORT_Memcmp (smime_prefs, smime_newprefs,
Packit 40b132
			     smime_symmetric_count * sizeof(*smime_prefs)) == 0)
Packit 40b132
		smime_prefs_changed = PR_FALSE;
Packit 40b132
	    else
Packit 40b132
		smime_prefs_changed = PR_TRUE;
Packit 40b132
	    PORT_Free (smime_prefs);
Packit 40b132
	}
Packit 40b132
Packit 40b132
	smime_prefs = smime_newprefs;
Packit 40b132
	smime_prefs_complete = PR_TRUE;
Packit 40b132
	return SECSuccess;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
Packit 40b132
    if (mask != CIPHER_FAMILYID_SMIME) {
Packit 40b132
	/* XXX set an error! */
Packit 40b132
    	return SECFailure;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    if (on) {
Packit 40b132
	PORT_Assert (smime_current_pref_index < smime_symmetric_count);
Packit 40b132
	if (smime_current_pref_index >= smime_symmetric_count) {
Packit 40b132
	    /* XXX set an error! */
Packit 40b132
	    return SECFailure;
Packit 40b132
	}
Packit 40b132
Packit 40b132
	smime_newprefs[smime_current_pref_index++] = which;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    return SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * this function locally records the export policy
Packit 40b132
 */
Packit 40b132
SECStatus 
Packit 40b132
SECMIME_SetPolicy(long which, int on)
Packit 40b132
{
Packit 40b132
    unsigned long mask;
Packit 40b132
Packit 40b132
    PORT_Assert ((which & CIPHER_FAMILYID_MASK) == CIPHER_FAMILYID_SMIME);
Packit 40b132
    if ((which & CIPHER_FAMILYID_MASK) != CIPHER_FAMILYID_SMIME) {
Packit 40b132
	/* XXX set an error! */
Packit 40b132
    	return SECFailure;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    which &= ~CIPHER_FAMILYID_MASK;
Packit 40b132
Packit 40b132
    PORT_Assert (which < 32);	/* bits in the long */
Packit 40b132
    if (which >= 32) {
Packit 40b132
	/* XXX set an error! */
Packit 40b132
    	return SECFailure;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    mask = 1UL << which;
Packit 40b132
Packit 40b132
    if (on) {
Packit 40b132
    	smime_policy_bits |= mask;
Packit 40b132
    } else {
Packit 40b132
    	smime_policy_bits &= ~mask;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    return SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * Based on the given algorithm (including its parameters, in some cases!)
Packit 40b132
 * and the given key (may or may not be inspected, depending on the
Packit 40b132
 * algorithm), find the appropriate policy algorithm specification
Packit 40b132
 * and return it.  If no match can be made, -1 is returned.
Packit 40b132
 */
Packit 40b132
static long
Packit 40b132
smime_policy_algorithm (SECAlgorithmID *algid, PK11SymKey *key)
Packit 40b132
{
Packit 40b132
    SECOidTag algtag;
Packit 40b132
Packit 40b132
    algtag = SECOID_GetAlgorithmTag (algid);
Packit 40b132
    switch (algtag) {
Packit 40b132
      case SEC_OID_RC2_CBC:
Packit 40b132
	{
Packit 40b132
	    unsigned int keylen_bits;
Packit 40b132
Packit 40b132
	    keylen_bits = PK11_GetKeyStrength (key, algid);
Packit 40b132
	    switch (keylen_bits) {
Packit 40b132
	      case 40:
Packit 40b132
		return SMIME_RC2_CBC_40;
Packit 40b132
	      case 64:
Packit 40b132
		return SMIME_RC2_CBC_64;
Packit 40b132
	      case 128:
Packit 40b132
		return SMIME_RC2_CBC_128;
Packit 40b132
	      default:
Packit 40b132
		break;
Packit 40b132
	    }
Packit 40b132
	}
Packit 40b132
	break;
Packit 40b132
      case SEC_OID_DES_CBC:
Packit 40b132
	return SMIME_DES_CBC_56;
Packit 40b132
      case SEC_OID_DES_EDE3_CBC:
Packit 40b132
	return SMIME_DES_EDE3_168;
Packit 40b132
#ifdef SMIME_DOES_RC5
Packit 40b132
      case SEC_OID_RC5_CBC_PAD:
Packit 40b132
	PORT_Assert (0);	/* XXX need to pull out parameters and match */
Packit 40b132
	break;
Packit 40b132
#endif
Packit 40b132
      default:
Packit 40b132
	break;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    return -1;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
static PRBool
Packit 40b132
smime_cipher_allowed (unsigned long which)
Packit 40b132
{
Packit 40b132
    unsigned long mask;
Packit 40b132
Packit 40b132
    which &= ~CIPHER_FAMILYID_MASK;
Packit 40b132
    PORT_Assert (which < 32);	/* bits per long (min) */
Packit 40b132
    if (which >= 32)
Packit 40b132
	return PR_FALSE;
Packit 40b132
Packit 40b132
    mask = 1UL << which;
Packit 40b132
    if ((mask & smime_policy_bits) == 0)
Packit 40b132
	return PR_FALSE;
Packit 40b132
Packit 40b132
    return PR_TRUE;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
PRBool
Packit 40b132
SECMIME_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
Packit 40b132
{
Packit 40b132
    long which;
Packit 40b132
Packit 40b132
    which = smime_policy_algorithm (algid, key);
Packit 40b132
    if (which < 0)
Packit 40b132
	return PR_FALSE;
Packit 40b132
Packit 40b132
    return smime_cipher_allowed ((unsigned long)which);
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * Does the current policy allow *any* S/MIME encryption (or decryption)?
Packit 40b132
 *
Packit 40b132
 * This tells whether or not *any* S/MIME encryption can be done,
Packit 40b132
 * according to policy.  Callers may use this to do nicer user interface
Packit 40b132
 * (say, greying out a checkbox so a user does not even try to encrypt
Packit 40b132
 * a message when they are not allowed to) or for any reason they want
Packit 40b132
 * to check whether S/MIME encryption (or decryption, for that matter)
Packit 40b132
 * may be done.
Packit 40b132
 *
Packit 40b132
 * It takes no arguments.  The return value is a simple boolean:
Packit 40b132
 *   PR_TRUE means encryption (or decryption) is *possible*
Packit 40b132
 *	(but may still fail due to other reasons, like because we cannot
Packit 40b132
 *	find all the necessary certs, etc.; PR_TRUE is *not* a guarantee)
Packit 40b132
 *   PR_FALSE means encryption (or decryption) is not permitted
Packit 40b132
 *
Packit 40b132
 * There are no errors from this routine.
Packit 40b132
 */
Packit 40b132
PRBool
Packit 40b132
SECMIME_EncryptionPossible (void)
Packit 40b132
{
Packit 40b132
    if (smime_policy_bits != 0)
Packit 40b132
	return PR_TRUE;
Packit 40b132
Packit 40b132
    return PR_FALSE;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * XXX Would like the "parameters" field to be a SECItem *, but the
Packit 40b132
 * encoder is having trouble with optional pointers to an ANY.  Maybe
Packit 40b132
 * once that is fixed, can change this back...
Packit 40b132
 */
Packit 40b132
typedef struct smime_capability_struct {
Packit 40b132
    unsigned long cipher;	/* local; not part of encoding */
Packit 40b132
    SECOidTag capIDTag;		/* local; not part of encoding */
Packit 40b132
    SECItem capabilityID;
Packit 40b132
    SECItem parameters;
Packit 40b132
} smime_capability;
Packit 40b132
Packit 40b132
static const SEC_ASN1Template smime_capability_template[] = {
Packit 40b132
    { SEC_ASN1_SEQUENCE,
Packit 40b132
	  0, NULL, sizeof(smime_capability) },
Packit 40b132
    { SEC_ASN1_OBJECT_ID,
Packit 40b132
	  offsetof(smime_capability,capabilityID), },
Packit 40b132
    { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
Packit 40b132
	  offsetof(smime_capability,parameters), },
Packit 40b132
    { 0, }
Packit 40b132
};
Packit 40b132
Packit 40b132
static const SEC_ASN1Template smime_capabilities_template[] = {
Packit 40b132
    { SEC_ASN1_SEQUENCE_OF, 0, smime_capability_template }
Packit 40b132
};
Packit 40b132
Packit 40b132
Packit 40b132
Packit 40b132
static void
Packit 40b132
smime_fill_capability (smime_capability *cap)
Packit 40b132
{
Packit 40b132
    unsigned long cipher;
Packit 40b132
    SECOidTag algtag;
Packit 40b132
    int i;
Packit 40b132
Packit 40b132
    algtag = SECOID_FindOIDTag (&(cap->capabilityID));
Packit 40b132
Packit 40b132
    for (i = 0; i < smime_symmetric_count; i++) {
Packit 40b132
	if (smime_cipher_maps[i].algtag != algtag)
Packit 40b132
	    continue;
Packit 40b132
	/*
Packit 40b132
	 * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing
Packit 40b132
	 * 2 NULLs as equal and NULL and non-NULL as not equal), we could
Packit 40b132
	 * use that here instead of all of the following comparison code.
Packit 40b132
	 */
Packit 40b132
	if (cap->parameters.data != NULL) {
Packit 40b132
	    if (smime_cipher_maps[i].parms == NULL)
Packit 40b132
		continue;
Packit 40b132
	    if (cap->parameters.len != smime_cipher_maps[i].parms->len)
Packit 40b132
		continue;
Packit 40b132
	    if (PORT_Memcmp (cap->parameters.data,
Packit 40b132
			     smime_cipher_maps[i].parms->data,
Packit 40b132
			     cap->parameters.len) == 0)
Packit 40b132
		break;
Packit 40b132
	} else if (smime_cipher_maps[i].parms == NULL) {
Packit 40b132
	    break;
Packit 40b132
	}
Packit 40b132
    }
Packit 40b132
Packit 40b132
    if (i == smime_symmetric_count)
Packit 40b132
	cipher = 0;
Packit 40b132
    else
Packit 40b132
	cipher = smime_cipher_maps[i].cipher;
Packit 40b132
Packit 40b132
    cap->cipher = cipher;
Packit 40b132
    cap->capIDTag = algtag;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
static long
Packit 40b132
smime_choose_cipher (CERTCertificate *scert, CERTCertificate **rcerts)
Packit 40b132
{
Packit 40b132
    PLArenaPool *poolp;
Packit 40b132
    long chosen_cipher;
Packit 40b132
    int *cipher_abilities;
Packit 40b132
    int *cipher_votes;
Packit 40b132
    int strong_mapi;
Packit 40b132
    int rcount, mapi, max;
Packit 40b132
Packit 40b132
    if (smime_policy_bits == 0) {
Packit 40b132
	PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM);
Packit 40b132
	return -1;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    chosen_cipher = SMIME_RC2_CBC_40;		/* the default, LCD */
Packit 40b132
Packit 40b132
    poolp = PORT_NewArena (1024);		/* XXX what is right value? */
Packit 40b132
    if (poolp == NULL)
Packit 40b132
	goto done;
Packit 40b132
Packit 40b132
    cipher_abilities = (int*)PORT_ArenaZAlloc (poolp,
Packit 40b132
					 smime_symmetric_count * sizeof(int));
Packit 40b132
    if (cipher_abilities == NULL)
Packit 40b132
	goto done;
Packit 40b132
Packit 40b132
    cipher_votes = (int*)PORT_ArenaZAlloc (poolp,
Packit 40b132
				     smime_symmetric_count * sizeof(int));
Packit 40b132
    if (cipher_votes == NULL)
Packit 40b132
	goto done;
Packit 40b132
Packit 40b132
    /*
Packit 40b132
     * XXX Should have a #define somewhere which specifies default
Packit 40b132
     * strong cipher.  (Or better, a way to configure.)
Packit 40b132
     */
Packit 40b132
Packit 40b132
    /* Make triple-DES the strong cipher. */
Packit 40b132
    strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168);
Packit 40b132
Packit 40b132
    PORT_Assert (strong_mapi >= 0);
Packit 40b132
Packit 40b132
    for (rcount = 0; rcerts[rcount] != NULL; rcount++) {
Packit 40b132
	SECItem *profile;
Packit 40b132
	smime_capability **caps;
Packit 40b132
	int capi, pref;
Packit 40b132
	SECStatus dstat;
Packit 40b132
Packit 40b132
	pref = smime_symmetric_count;
Packit 40b132
	profile = CERT_FindSMimeProfile (rcerts[rcount]);
Packit 40b132
	if (profile != NULL && profile->data != NULL && profile->len > 0) {
Packit 40b132
	    caps = NULL;
Packit 40b132
	    dstat = SEC_QuickDERDecodeItem (poolp, &caps,
Packit 40b132
					smime_capabilities_template,
Packit 40b132
					profile);
Packit 40b132
	    if (dstat == SECSuccess && caps != NULL) {
Packit 40b132
		for (capi = 0; caps[capi] != NULL; capi++) {
Packit 40b132
		    smime_fill_capability (caps[capi]);
Packit 40b132
		    mapi = smime_mapi_by_cipher (caps[capi]->cipher);
Packit 40b132
		    if (mapi >= 0) {
Packit 40b132
			cipher_abilities[mapi]++;
Packit 40b132
			cipher_votes[mapi] += pref;
Packit 40b132
			--pref;
Packit 40b132
		    }
Packit 40b132
		}
Packit 40b132
	    }
Packit 40b132
	} else {
Packit 40b132
	    SECKEYPublicKey *key;
Packit 40b132
	    unsigned int pklen_bits;
Packit 40b132
Packit 40b132
	    /*
Packit 40b132
	     * XXX This is probably only good for RSA keys.  What I would
Packit 40b132
	     * really like is a function to just say;  Is the public key in
Packit 40b132
	     * this cert an export-length key?  Then I would not have to
Packit 40b132
	     * know things like the value 512, or the kind of key, or what
Packit 40b132
	     * a subjectPublicKeyInfo is, etc.
Packit 40b132
	     */
Packit 40b132
	    key = CERT_ExtractPublicKey (rcerts[rcount]);
Packit 40b132
	    if (key != NULL) {
Packit 40b132
		pklen_bits = SECKEY_PublicKeyStrength (key) * 8;
Packit 40b132
		SECKEY_DestroyPublicKey (key);
Packit 40b132
Packit 40b132
		if (pklen_bits > 512) {
Packit 40b132
		    cipher_abilities[strong_mapi]++;
Packit 40b132
		    cipher_votes[strong_mapi] += pref;
Packit 40b132
		}
Packit 40b132
	    }
Packit 40b132
	}
Packit 40b132
	if (profile != NULL)
Packit 40b132
	    SECITEM_FreeItem (profile, PR_TRUE);
Packit 40b132
    }
Packit 40b132
Packit 40b132
    max = 0;
Packit 40b132
    for (mapi = 0; mapi < smime_symmetric_count; mapi++) {
Packit 40b132
	if (cipher_abilities[mapi] != rcount)
Packit 40b132
	    continue;
Packit 40b132
	if (! smime_cipher_allowed (smime_cipher_maps[mapi].cipher))
Packit 40b132
	    continue;
Packit 40b132
	if (cipher_votes[mapi] > max) {
Packit 40b132
	    chosen_cipher = smime_cipher_maps[mapi].cipher;
Packit 40b132
	    max = cipher_votes[mapi];
Packit 40b132
	} /* XXX else if a tie, let scert break it? */
Packit 40b132
    }
Packit 40b132
Packit 40b132
done:
Packit 40b132
    if (poolp != NULL)
Packit 40b132
	PORT_FreeArena (poolp, PR_FALSE);
Packit 40b132
Packit 40b132
    return chosen_cipher;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * XXX This is a hack for now to satisfy our current interface.
Packit 40b132
 * Eventually, with more parameters needing to be specified, just
Packit 40b132
 * looking up the keysize is not going to be sufficient.
Packit 40b132
 */
Packit 40b132
static int
Packit 40b132
smime_keysize_by_cipher (unsigned long which)
Packit 40b132
{
Packit 40b132
    int keysize;
Packit 40b132
Packit 40b132
    switch (which) {
Packit 40b132
      case SMIME_RC2_CBC_40:
Packit 40b132
	keysize = 40;
Packit 40b132
	break;
Packit 40b132
      case SMIME_RC2_CBC_64:
Packit 40b132
	keysize = 64;
Packit 40b132
	break;
Packit 40b132
      case SMIME_RC2_CBC_128:
Packit 40b132
	keysize = 128;
Packit 40b132
	break;
Packit 40b132
#ifdef SMIME_DOES_RC5
Packit 40b132
      case SMIME_RC5PAD_64_16_40:
Packit 40b132
      case SMIME_RC5PAD_64_16_64:
Packit 40b132
      case SMIME_RC5PAD_64_16_128:
Packit 40b132
	/* XXX See comment above; keysize is not enough... */
Packit 40b132
	PORT_Assert (0);
Packit 40b132
	PORT_SetError (SEC_ERROR_INVALID_ALGORITHM);
Packit 40b132
	keysize = -1;
Packit 40b132
	break;
Packit 40b132
#endif
Packit 40b132
      case SMIME_DES_CBC_56:
Packit 40b132
      case SMIME_DES_EDE3_168:
Packit 40b132
	/*
Packit 40b132
	 * These are special; since the key size is fixed, we actually
Packit 40b132
	 * want to *avoid* specifying a key size.
Packit 40b132
	 */
Packit 40b132
	keysize = 0;
Packit 40b132
	break;
Packit 40b132
      default:
Packit 40b132
	keysize = -1;
Packit 40b132
	break;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    return keysize;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * Start an S/MIME encrypting context.
Packit 40b132
 *
Packit 40b132
 * "scert" is the cert for the sender.  It will be checked for validity.
Packit 40b132
 * "rcerts" are the certs for the recipients.  They will also be checked.
Packit 40b132
 *
Packit 40b132
 * "certdb" is the cert database to use for verifying the certs.
Packit 40b132
 * It can be NULL if a default database is available (like in the client).
Packit 40b132
 *
Packit 40b132
 * This function already does all of the stuff specific to S/MIME protocol
Packit 40b132
 * and local policy; the return value just needs to be passed to
Packit 40b132
 * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data,
Packit 40b132
 * and finally to SEC_PKCS7DestroyContentInfo().
Packit 40b132
 *
Packit 40b132
 * An error results in a return value of NULL and an error set.
Packit 40b132
 * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
Packit 40b132
 */
Packit 40b132
SEC_PKCS7ContentInfo *
Packit 40b132
SECMIME_CreateEncrypted(CERTCertificate *scert,
Packit 40b132
			CERTCertificate **rcerts,
Packit 40b132
			CERTCertDBHandle *certdb,
Packit 40b132
			SECKEYGetPasswordKey pwfn,
Packit 40b132
			void *pwfn_arg)
Packit 40b132
{
Packit 40b132
    SEC_PKCS7ContentInfo *cinfo;
Packit 40b132
    long cipher;
Packit 40b132
    SECOidTag encalg;
Packit 40b132
    int keysize;
Packit 40b132
    int mapi, rci;
Packit 40b132
Packit 40b132
    cipher = smime_choose_cipher (scert, rcerts);
Packit 40b132
    if (cipher < 0)
Packit 40b132
	return NULL;
Packit 40b132
Packit 40b132
    mapi = smime_mapi_by_cipher (cipher);
Packit 40b132
    if (mapi < 0)
Packit 40b132
	return NULL;
Packit 40b132
Packit 40b132
    /*
Packit 40b132
     * XXX This is stretching it -- CreateEnvelopedData should probably
Packit 40b132
     * take a cipher itself of some sort, because we cannot know what the
Packit 40b132
     * future will bring in terms of parameters for each type of algorithm.
Packit 40b132
     * For example, just an algorithm and keysize is *not* sufficient to
Packit 40b132
     * fully specify the usage of RC5 (which also needs to know rounds and
Packit 40b132
     * block size).  Work this out into a better API!
Packit 40b132
     */
Packit 40b132
    encalg = smime_cipher_maps[mapi].algtag;
Packit 40b132
    keysize = smime_keysize_by_cipher (cipher);
Packit 40b132
    if (keysize < 0)
Packit 40b132
	return NULL;
Packit 40b132
Packit 40b132
    cinfo = SEC_PKCS7CreateEnvelopedData (scert, certUsageEmailRecipient,
Packit 40b132
					  certdb, encalg, keysize,
Packit 40b132
					  pwfn, pwfn_arg);
Packit 40b132
    if (cinfo == NULL)
Packit 40b132
	return NULL;
Packit 40b132
Packit 40b132
    for (rci = 0; rcerts[rci] != NULL; rci++) {
Packit 40b132
	if (rcerts[rci] == scert)
Packit 40b132
	    continue;
Packit 40b132
	if (SEC_PKCS7AddRecipient (cinfo, rcerts[rci], certUsageEmailRecipient,
Packit 40b132
				   NULL) != SECSuccess) {
Packit 40b132
	    SEC_PKCS7DestroyContentInfo (cinfo);
Packit 40b132
	    return NULL;
Packit 40b132
	}
Packit 40b132
    }
Packit 40b132
Packit 40b132
    return cinfo;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
static smime_capability **smime_capabilities;
Packit 40b132
static SECItem *smime_encoded_caps;
Packit 40b132
Packit 40b132
Packit 40b132
static SECStatus
Packit 40b132
smime_init_caps (void)
Packit 40b132
{
Packit 40b132
    smime_capability *cap;
Packit 40b132
    smime_cipher_map *map;
Packit 40b132
    SECOidData *oiddata;
Packit 40b132
    SECStatus rv;
Packit 40b132
    int i;
Packit 40b132
Packit 40b132
    if (smime_encoded_caps != NULL && (! smime_prefs_changed))
Packit 40b132
	return SECSuccess;
Packit 40b132
Packit 40b132
    if (smime_encoded_caps != NULL) {
Packit 40b132
	SECITEM_FreeItem (smime_encoded_caps, PR_TRUE);
Packit 40b132
	smime_encoded_caps = NULL;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    if (smime_capabilities == NULL) {
Packit 40b132
	smime_capabilities = (smime_capability**)PORT_ZAlloc (
Packit 40b132
					  (smime_symmetric_count + 1)
Packit 40b132
					  * sizeof(smime_capability *));
Packit 40b132
	if (smime_capabilities == NULL)
Packit 40b132
	    return SECFailure;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    rv = SECFailure;
Packit 40b132
Packit 40b132
    /* 
Packit 40b132
       The process of creating the encoded PKCS7 cipher capability list
Packit 40b132
       involves two basic steps: 
Packit 40b132
Packit 40b132
       (a) Convert our internal representation of cipher preferences 
Packit 40b132
           (smime_prefs) into an array containing cipher OIDs and 
Packit 40b132
	   parameter data (smime_capabilities). This step is
Packit 40b132
	   performed here.
Packit 40b132
Packit 40b132
       (b) Encode, using ASN.1, the cipher information in 
Packit 40b132
           smime_capabilities, leaving the encoded result in 
Packit 40b132
	   smime_encoded_caps.
Packit 40b132
Packit 40b132
       (In the process of performing (a), Lisa put in some optimizations
Packit 40b132
       which allow us to avoid needlessly re-populating elements in 
Packit 40b132
       smime_capabilities as we walk through smime_prefs.)
Packit 40b132
    */
Packit 40b132
    for (i = 0; i < smime_current_pref_index; i++) {
Packit 40b132
	int mapi;
Packit 40b132
Packit 40b132
	/* Get the next cipher preference in smime_prefs. */
Packit 40b132
	mapi = smime_mapi_by_cipher (smime_prefs[i]);
Packit 40b132
	if (mapi < 0)
Packit 40b132
	    break;
Packit 40b132
Packit 40b132
	/* Find the corresponding entry in the cipher map. */
Packit 40b132
	PORT_Assert (mapi < smime_symmetric_count);
Packit 40b132
	map = &(smime_cipher_maps[mapi]);
Packit 40b132
Packit 40b132
	/*
Packit 40b132
	 * Convert the next preference found in smime_prefs into an
Packit 40b132
	 * smime_capability.
Packit 40b132
	 */
Packit 40b132
Packit 40b132
	cap = smime_capabilities[i];
Packit 40b132
	if (cap == NULL) {
Packit 40b132
	    cap = (smime_capability*)PORT_ZAlloc (sizeof(smime_capability));
Packit 40b132
	    if (cap == NULL)
Packit 40b132
		break;
Packit 40b132
	    smime_capabilities[i] = cap;
Packit 40b132
	} else if (cap->cipher == smime_prefs[i]) {
Packit 40b132
	    continue;		/* no change to this one */
Packit 40b132
	}
Packit 40b132
Packit 40b132
	cap->capIDTag = map->algtag;
Packit 40b132
	oiddata = SECOID_FindOIDByTag (map->algtag);
Packit 40b132
	if (oiddata == NULL)
Packit 40b132
	    break;
Packit 40b132
Packit 40b132
	if (cap->capabilityID.data != NULL) {
Packit 40b132
	    SECITEM_FreeItem (&(cap->capabilityID), PR_FALSE);
Packit 40b132
	    cap->capabilityID.data = NULL;
Packit 40b132
	    cap->capabilityID.len = 0;
Packit 40b132
	}
Packit 40b132
Packit 40b132
	rv = SECITEM_CopyItem (NULL, &(cap->capabilityID), &(oiddata->oid));
Packit 40b132
	if (rv != SECSuccess)
Packit 40b132
	    break;
Packit 40b132
Packit 40b132
	if (map->parms == NULL) {
Packit 40b132
	    cap->parameters.data = NULL;
Packit 40b132
	    cap->parameters.len = 0;
Packit 40b132
	} else {
Packit 40b132
	    cap->parameters.data = map->parms->data;
Packit 40b132
	    cap->parameters.len = map->parms->len;
Packit 40b132
	}
Packit 40b132
Packit 40b132
	cap->cipher = smime_prefs[i];
Packit 40b132
    }
Packit 40b132
Packit 40b132
    if (i != smime_current_pref_index)
Packit 40b132
	return rv;
Packit 40b132
Packit 40b132
    while (i < smime_symmetric_count) {
Packit 40b132
	cap = smime_capabilities[i];
Packit 40b132
	if (cap != NULL) {
Packit 40b132
	    SECITEM_FreeItem (&(cap->capabilityID), PR_FALSE);
Packit 40b132
	    PORT_Free (cap);
Packit 40b132
	}
Packit 40b132
	smime_capabilities[i] = NULL;
Packit 40b132
	i++;
Packit 40b132
    }
Packit 40b132
    smime_capabilities[i] = NULL;
Packit 40b132
Packit 40b132
    smime_encoded_caps = SEC_ASN1EncodeItem (NULL, NULL, &smime_capabilities,
Packit 40b132
					     smime_capabilities_template);
Packit 40b132
    if (smime_encoded_caps == NULL)
Packit 40b132
	return SECFailure;
Packit 40b132
Packit 40b132
    return SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
static SECStatus
Packit 40b132
smime_add_profile (CERTCertificate *cert, SEC_PKCS7ContentInfo *cinfo)
Packit 40b132
{
Packit 40b132
    PORT_Assert (smime_prefs_complete);
Packit 40b132
    if (! smime_prefs_complete)
Packit 40b132
	return SECFailure;
Packit 40b132
Packit 40b132
    /* For that matter, if capabilities haven't been initialized yet,
Packit 40b132
       do so now. */
Packit 40b132
    if (smime_encoded_caps == NULL || smime_prefs_changed) {
Packit 40b132
	SECStatus rv;
Packit 40b132
Packit 40b132
	rv = smime_init_caps();
Packit 40b132
	if (rv != SECSuccess)
Packit 40b132
	    return rv;
Packit 40b132
Packit 40b132
	PORT_Assert (smime_encoded_caps != NULL);
Packit 40b132
    }
Packit 40b132
Packit 40b132
    return SEC_PKCS7AddSignedAttribute (cinfo, SEC_OID_PKCS9_SMIME_CAPABILITIES,
Packit 40b132
					smime_encoded_caps);
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * Start an S/MIME signing context.
Packit 40b132
 *
Packit 40b132
 * "scert" is the cert that will be used to sign the data.  It will be
Packit 40b132
 * checked for validity.
Packit 40b132
 *
Packit 40b132
 * "ecert" is the signer's encryption cert.  If it is different from
Packit 40b132
 * scert, then it will be included in the signed message so that the
Packit 40b132
 * recipient can save it for future encryptions.
Packit 40b132
 *
Packit 40b132
 * "certdb" is the cert database to use for verifying the cert.
Packit 40b132
 * It can be NULL if a default database is available (like in the client).
Packit 40b132
 * 
Packit 40b132
 * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1).
Packit 40b132
 * XXX There should be SECMIME functions for hashing, or the hashing should
Packit 40b132
 * be built into this interface, which we would like because we would
Packit 40b132
 * support more smartcards that way, and then this argument should go away.)
Packit 40b132
 *
Packit 40b132
 * "digest" is the actual digest of the data.  It must be provided in
Packit 40b132
 * the case of detached data or NULL if the content will be included.
Packit 40b132
 *
Packit 40b132
 * This function already does all of the stuff specific to S/MIME protocol
Packit 40b132
 * and local policy; the return value just needs to be passed to
Packit 40b132
 * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data,
Packit 40b132
 * and finally to SEC_PKCS7DestroyContentInfo().
Packit 40b132
 *
Packit 40b132
 * An error results in a return value of NULL and an error set.
Packit 40b132
 * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
Packit 40b132
 */
Packit 40b132
Packit 40b132
SEC_PKCS7ContentInfo *
Packit 40b132
SECMIME_CreateSigned (CERTCertificate *scert,
Packit 40b132
		      CERTCertificate *ecert,
Packit 40b132
		      CERTCertDBHandle *certdb,
Packit 40b132
		      SECOidTag digestalg,
Packit 40b132
		      SECItem *digest,
Packit 40b132
		      SECKEYGetPasswordKey pwfn,
Packit 40b132
		      void *pwfn_arg)
Packit 40b132
{
Packit 40b132
    SEC_PKCS7ContentInfo *cinfo;
Packit 40b132
    SECStatus rv;
Packit 40b132
Packit 40b132
    /* See note in header comment above about digestalg. */
Packit 40b132
    /* Doesn't explain this. PORT_Assert (digestalg == SEC_OID_SHA1); */
Packit 40b132
Packit 40b132
    cinfo = SEC_PKCS7CreateSignedData (scert, certUsageEmailSigner,
Packit 40b132
				       certdb, digestalg, digest,
Packit 40b132
				       pwfn, pwfn_arg);
Packit 40b132
    if (cinfo == NULL)
Packit 40b132
	return NULL;
Packit 40b132
Packit 40b132
    if (SEC_PKCS7IncludeCertChain (cinfo, NULL) != SECSuccess) {
Packit 40b132
	SEC_PKCS7DestroyContentInfo (cinfo);
Packit 40b132
	return NULL;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    /* if the encryption cert and the signing cert differ, then include
Packit 40b132
     * the encryption cert too.
Packit 40b132
     */
Packit 40b132
    /* it is ok to compare the pointers since we ref count, and the same
Packit 40b132
     * cert will always have the same pointer
Packit 40b132
     */
Packit 40b132
    if ( ( ecert != NULL ) && ( ecert != scert ) ) {
Packit 40b132
	rv = SEC_PKCS7AddCertificate(cinfo, ecert);
Packit 40b132
	if ( rv != SECSuccess ) {
Packit 40b132
	    SEC_PKCS7DestroyContentInfo (cinfo);
Packit 40b132
	    return NULL;
Packit 40b132
	}
Packit 40b132
    }
Packit 40b132
    /*
Packit 40b132
     * Add the signing time.  But if it fails for some reason,
Packit 40b132
     * may as well not give up altogether -- just assert.
Packit 40b132
     */
Packit 40b132
    rv = SEC_PKCS7AddSigningTime (cinfo);
Packit 40b132
    PORT_Assert (rv == SECSuccess);
Packit 40b132
Packit 40b132
    /*
Packit 40b132
     * Add the email profile.  Again, if it fails for some reason,
Packit 40b132
     * may as well not give up altogether -- just assert.
Packit 40b132
     */
Packit 40b132
    rv = smime_add_profile (ecert, cinfo);
Packit 40b132
    PORT_Assert (rv == SECSuccess);
Packit 40b132
Packit 40b132
    return cinfo;
Packit 40b132
}