Blame nss/lib/smime/smimeutil.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
 */
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
#include "cms.h"
Packit 40b132
#include "nss.h"
Packit 40b132
Packit 40b132
SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate)
Packit 40b132
SEC_ASN1_MKSUB(SEC_OctetStringTemplate)
Packit 40b132
SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate)
Packit 40b132
Packit 40b132
/* various integer's ASN.1 encoding */
Packit 40b132
static unsigned char asn1_int40[] = { SEC_ASN1_INTEGER, 0x01, 0x28 };
Packit 40b132
static unsigned char asn1_int64[] = { SEC_ASN1_INTEGER, 0x01, 0x40 };
Packit 40b132
static unsigned char asn1_int128[] = { SEC_ASN1_INTEGER, 0x02, 0x00, 0x80 };
Packit 40b132
Packit 40b132
/* RC2 algorithm parameters (used in smime_cipher_map) */
Packit 40b132
static SECItem param_int40 = { siBuffer, asn1_int40, sizeof(asn1_int40) };
Packit 40b132
static SECItem param_int64 = { siBuffer, asn1_int64, sizeof(asn1_int64) };
Packit 40b132
static SECItem param_int128 = { siBuffer, asn1_int128, sizeof(asn1_int128) };
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 {
Packit 40b132
    SECItem capabilityID;
Packit 40b132
    SECItem parameters;
Packit 40b132
    long cipher;		/* optimization */
Packit 40b132
} NSSSMIMECapability;
Packit 40b132
Packit 40b132
static const SEC_ASN1Template NSSSMIMECapabilityTemplate[] = {
Packit 40b132
    { SEC_ASN1_SEQUENCE,
Packit 40b132
	  0, NULL, sizeof(NSSSMIMECapability) },
Packit 40b132
    { SEC_ASN1_OBJECT_ID,
Packit 40b132
	  offsetof(NSSSMIMECapability,capabilityID), },
Packit 40b132
    { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
Packit 40b132
	  offsetof(NSSSMIMECapability,parameters), },
Packit 40b132
    { 0, }
Packit 40b132
};
Packit 40b132
Packit 40b132
static const SEC_ASN1Template NSSSMIMECapabilitiesTemplate[] = {
Packit 40b132
    { SEC_ASN1_SEQUENCE_OF, 0, NSSSMIMECapabilityTemplate }
Packit 40b132
};
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * NSSSMIMEEncryptionKeyPreference - if we find one of these, it needs to prompt us
Packit 40b132
 *  to store this and only this certificate permanently for the sender email address.
Packit 40b132
 */
Packit 40b132
typedef enum {
Packit 40b132
    NSSSMIMEEncryptionKeyPref_IssuerSN,
Packit 40b132
    NSSSMIMEEncryptionKeyPref_RKeyID,
Packit 40b132
    NSSSMIMEEncryptionKeyPref_SubjectKeyID
Packit 40b132
} NSSSMIMEEncryptionKeyPrefSelector;
Packit 40b132
Packit 40b132
typedef struct {
Packit 40b132
    NSSSMIMEEncryptionKeyPrefSelector selector;
Packit 40b132
    union {
Packit 40b132
	CERTIssuerAndSN			*issuerAndSN;
Packit 40b132
	NSSCMSRecipientKeyIdentifier	*recipientKeyID;
Packit 40b132
	SECItem				*subjectKeyID;
Packit 40b132
    } id;
Packit 40b132
} NSSSMIMEEncryptionKeyPreference;
Packit 40b132
Packit 40b132
extern const SEC_ASN1Template NSSCMSRecipientKeyIdentifierTemplate[];
Packit 40b132
Packit 40b132
static const SEC_ASN1Template smime_encryptionkeypref_template[] = {
Packit 40b132
    { SEC_ASN1_CHOICE,
Packit 40b132
	  offsetof(NSSSMIMEEncryptionKeyPreference,selector), NULL,
Packit 40b132
	  sizeof(NSSSMIMEEncryptionKeyPreference) },
Packit 40b132
    { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0
Packit 40b132
          | SEC_ASN1_CONSTRUCTED,
Packit 40b132
	  offsetof(NSSSMIMEEncryptionKeyPreference,id.issuerAndSN),
Packit 40b132
	  SEC_ASN1_SUB(CERT_IssuerAndSNTemplate),
Packit 40b132
	  NSSSMIMEEncryptionKeyPref_IssuerSN },
Packit 40b132
    { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | 1
Packit 40b132
          | SEC_ASN1_CONSTRUCTED,
Packit 40b132
	  offsetof(NSSSMIMEEncryptionKeyPreference,id.recipientKeyID),
Packit 40b132
	  NSSCMSRecipientKeyIdentifierTemplate,
Packit 40b132
	  NSSSMIMEEncryptionKeyPref_RKeyID },
Packit 40b132
    { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2
Packit 40b132
          | SEC_ASN1_CONSTRUCTED,
Packit 40b132
	  offsetof(NSSSMIMEEncryptionKeyPreference,id.subjectKeyID),
Packit 40b132
	  SEC_ASN1_SUB(SEC_OctetStringTemplate),
Packit 40b132
	  NSSSMIMEEncryptionKeyPref_SubjectKeyID },
Packit 40b132
    { 0, }
Packit 40b132
};
Packit 40b132
Packit 40b132
/* smime_cipher_map - map of SMIME symmetric "ciphers" to algtag & parameters */
Packit 40b132
typedef struct {
Packit 40b132
    unsigned long cipher;
Packit 40b132
    SECOidTag algtag;
Packit 40b132
    SECItem *parms;
Packit 40b132
    PRBool enabled;	/* in the user's preferences */
Packit 40b132
    PRBool allowed;	/* per export policy */
Packit 40b132
} smime_cipher_map_entry;
Packit 40b132
Packit 40b132
/* global: list of supported SMIME symmetric ciphers, ordered roughly by increasing strength */
Packit 40b132
static smime_cipher_map_entry smime_cipher_map[] = {
Packit 40b132
/*    cipher			algtag			parms		enabled  allowed */
Packit 40b132
/*    ---------------------------------------------------------------------------------- */
Packit 40b132
    { SMIME_RC2_CBC_40,		SEC_OID_RC2_CBC,	&param_int40,	PR_TRUE, PR_TRUE },
Packit 40b132
    { SMIME_DES_CBC_56,		SEC_OID_DES_CBC,	NULL,		PR_TRUE, PR_TRUE },
Packit 40b132
    { SMIME_RC2_CBC_64,		SEC_OID_RC2_CBC,	&param_int64,	PR_TRUE, PR_TRUE },
Packit 40b132
    { SMIME_RC2_CBC_128,	SEC_OID_RC2_CBC,	&param_int128,	PR_TRUE, PR_TRUE },
Packit 40b132
    { SMIME_DES_EDE3_168,	SEC_OID_DES_EDE3_CBC,	NULL,		PR_TRUE, PR_TRUE },
Packit 40b132
    { SMIME_AES_CBC_128,	SEC_OID_AES_128_CBC,	NULL,		PR_TRUE, PR_TRUE },
Packit 40b132
    { SMIME_AES_CBC_256,	SEC_OID_AES_256_CBC,	NULL,		PR_TRUE, PR_TRUE }
Packit 40b132
};
Packit 40b132
static const int smime_cipher_map_count = sizeof(smime_cipher_map) / sizeof(smime_cipher_map_entry);
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * smime_mapi_by_cipher - find index into smime_cipher_map by cipher
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_cipher_map_count; i++) {
Packit 40b132
	if (smime_cipher_map[i].cipher == cipher)
Packit 40b132
	    return i;	/* bingo */
Packit 40b132
    }
Packit 40b132
    return -1;		/* should not happen if we're consistent, right? */
Packit 40b132
}
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * NSS_SMIME_EnableCipher - this function locally records the user's preference
Packit 40b132
 */
Packit 40b132
SECStatus 
Packit 40b132
NSS_SMIMEUtil_EnableCipher(unsigned long which, PRBool on)
Packit 40b132
{
Packit 40b132
    unsigned long mask;
Packit 40b132
    int mapi;
Packit 40b132
Packit 40b132
    mask = which & CIPHER_FAMILYID_MASK;
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
    mapi = smime_mapi_by_cipher(which);
Packit 40b132
    if (mapi < 0)
Packit 40b132
	/* XXX set an error */
Packit 40b132
	return SECFailure;
Packit 40b132
Packit 40b132
    /* do we try to turn on a forbidden cipher? */
Packit 40b132
    if (!smime_cipher_map[mapi].allowed && on) {
Packit 40b132
	PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM);
Packit 40b132
	return SECFailure;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    if (smime_cipher_map[mapi].enabled != on)
Packit 40b132
	smime_cipher_map[mapi].enabled = on;
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
NSS_SMIMEUtil_AllowCipher(unsigned long which, PRBool on)
Packit 40b132
{
Packit 40b132
    unsigned long mask;
Packit 40b132
    int mapi;
Packit 40b132
Packit 40b132
    mask = which & CIPHER_FAMILYID_MASK;
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
    mapi = smime_mapi_by_cipher(which);
Packit 40b132
    if (mapi < 0)
Packit 40b132
	/* XXX set an error */
Packit 40b132
	return SECFailure;
Packit 40b132
Packit 40b132
    if (smime_cipher_map[mapi].allowed != on)
Packit 40b132
	smime_cipher_map[mapi].allowed = on;
Packit 40b132
Packit 40b132
    return SECSuccess;
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 SECStatus
Packit 40b132
nss_smime_get_cipher_for_alg_and_key(SECAlgorithmID *algid, PK11SymKey *key, unsigned long *cipher)
Packit 40b132
{
Packit 40b132
    SECOidTag algtag;
Packit 40b132
    unsigned int keylen_bits;
Packit 40b132
    unsigned long c;
Packit 40b132
Packit 40b132
    algtag = SECOID_GetAlgorithmTag(algid);
Packit 40b132
    switch (algtag) {
Packit 40b132
    case SEC_OID_RC2_CBC:
Packit 40b132
	keylen_bits = PK11_GetKeyStrength(key, algid);
Packit 40b132
	switch (keylen_bits) {
Packit 40b132
	case 40:
Packit 40b132
	    c = SMIME_RC2_CBC_40;
Packit 40b132
	    break;
Packit 40b132
	case 64:
Packit 40b132
	    c = SMIME_RC2_CBC_64;
Packit 40b132
	    break;
Packit 40b132
	case 128:
Packit 40b132
	    c = SMIME_RC2_CBC_128;
Packit 40b132
	    break;
Packit 40b132
	default:
Packit 40b132
	    return SECFailure;
Packit 40b132
	}
Packit 40b132
	break;
Packit 40b132
    case SEC_OID_DES_CBC:
Packit 40b132
	c = SMIME_DES_CBC_56;
Packit 40b132
	break;
Packit 40b132
    case SEC_OID_DES_EDE3_CBC:
Packit 40b132
	c = SMIME_DES_EDE3_168;
Packit 40b132
	break;
Packit 40b132
    case SEC_OID_AES_128_CBC:
Packit 40b132
	c = SMIME_AES_CBC_128;
Packit 40b132
	break;
Packit 40b132
    case SEC_OID_AES_256_CBC:
Packit 40b132
	c = SMIME_AES_CBC_256;
Packit 40b132
	break;
Packit 40b132
    default:
Packit 40b132
	PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
Packit 40b132
	return SECFailure;
Packit 40b132
    }
Packit 40b132
    *cipher = c;
Packit 40b132
    return SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
static PRBool
Packit 40b132
nss_smime_cipher_allowed(unsigned long which)
Packit 40b132
{
Packit 40b132
    int mapi;
Packit 40b132
Packit 40b132
    mapi = smime_mapi_by_cipher(which);
Packit 40b132
    if (mapi < 0)
Packit 40b132
	return PR_FALSE;
Packit 40b132
    return smime_cipher_map[mapi].allowed;
Packit 40b132
}
Packit 40b132
Packit 40b132
PRBool
Packit 40b132
NSS_SMIMEUtil_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
Packit 40b132
{
Packit 40b132
    unsigned long which;
Packit 40b132
Packit 40b132
    if (nss_smime_get_cipher_for_alg_and_key(algid, key, &which) != SECSuccess)
Packit 40b132
	return PR_FALSE;
Packit 40b132
Packit 40b132
    return nss_smime_cipher_allowed(which);
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * NSS_SMIME_EncryptionPossible - check if any encryption is allowed
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
NSS_SMIMEUtil_EncryptionPossible(void)
Packit 40b132
{
Packit 40b132
    int i;
Packit 40b132
Packit 40b132
    for (i = 0; i < smime_cipher_map_count; i++) {
Packit 40b132
	if (smime_cipher_map[i].allowed)
Packit 40b132
	    return PR_TRUE;
Packit 40b132
    }
Packit 40b132
    return PR_FALSE;
Packit 40b132
}
Packit 40b132
Packit 40b132
Packit 40b132
static int
Packit 40b132
nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap)
Packit 40b132
{
Packit 40b132
    int i;
Packit 40b132
    SECOidTag capIDTag;
Packit 40b132
Packit 40b132
    /* we need the OIDTag here */
Packit 40b132
    capIDTag = SECOID_FindOIDTag(&(cap->capabilityID));
Packit 40b132
Packit 40b132
    /* go over all the SMIME ciphers we know and see if we find a match */
Packit 40b132
    for (i = 0; i < smime_cipher_map_count; i++) {
Packit 40b132
	if (smime_cipher_map[i].algtag != capIDTag)
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 (!smime_cipher_map[i].parms) { 
Packit 40b132
	    if (!cap->parameters.data || !cap->parameters.len)
Packit 40b132
		break;	/* both empty: bingo */
Packit 40b132
	    if (cap->parameters.len     == 2  &&
Packit 40b132
	        cap->parameters.data[0] == SEC_ASN1_NULL &&
Packit 40b132
		cap->parameters.data[1] == 0) 
Packit 40b132
		break;  /* DER NULL == NULL, bingo */
Packit 40b132
	} else if (cap->parameters.data != NULL && 
Packit 40b132
	    cap->parameters.len == smime_cipher_map[i].parms->len &&
Packit 40b132
	    PORT_Memcmp (cap->parameters.data, smime_cipher_map[i].parms->data,
Packit 40b132
			     cap->parameters.len) == 0)
Packit 40b132
	{
Packit 40b132
	    break;	/* both not empty, same length & equal content: bingo */
Packit 40b132
	}
Packit 40b132
    }
Packit 40b132
Packit 40b132
    if (i == smime_cipher_map_count)
Packit 40b132
	return 0;				/* no match found */
Packit 40b132
    return smime_cipher_map[i].cipher;	/* match found, point to cipher */
Packit 40b132
}
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * smime_choose_cipher - choose a cipher that works for all the recipients
Packit 40b132
 *
Packit 40b132
 * "scert"  - sender's certificate
Packit 40b132
 * "rcerts" - recipient's certificates
Packit 40b132
 */
Packit 40b132
static long
Packit 40b132
smime_choose_cipher(CERTCertificate *scert, CERTCertificate **rcerts)
Packit 40b132
{
Packit 40b132
    PLArenaPool *poolp;
Packit 40b132
    long cipher;
Packit 40b132
    long chosen_cipher;
Packit 40b132
    int *cipher_abilities;
Packit 40b132
    int *cipher_votes;
Packit 40b132
    int weak_mapi;
Packit 40b132
    int strong_mapi;
Packit 40b132
    int aes128_mapi;
Packit 40b132
    int aes256_mapi;
Packit 40b132
    int rcount, mapi, max, i;
Packit 40b132
Packit 40b132
    chosen_cipher = SMIME_RC2_CBC_40;		/* the default, LCD */
Packit 40b132
    weak_mapi = smime_mapi_by_cipher(chosen_cipher);
Packit 40b132
    aes128_mapi = smime_mapi_by_cipher(SMIME_AES_CBC_128);
Packit 40b132
    aes256_mapi = smime_mapi_by_cipher(SMIME_AES_CBC_256);
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, smime_cipher_map_count * sizeof(int));
Packit 40b132
    cipher_votes     = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int));
Packit 40b132
    if (cipher_votes == NULL || cipher_abilities == NULL)
Packit 40b132
	goto done;
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
    /* walk all the recipient's certs */
Packit 40b132
    for (rcount = 0; rcerts[rcount] != NULL; rcount++) {
Packit 40b132
	SECItem *profile;
Packit 40b132
	NSSSMIMECapability **caps;
Packit 40b132
	int pref;
Packit 40b132
Packit 40b132
	/* the first cipher that matches in the user's SMIME profile gets
Packit 40b132
	 * "smime_cipher_map_count" votes; the next one gets "smime_cipher_map_count" - 1
Packit 40b132
	 * and so on. If every cipher matches, the last one gets 1 (one) vote */
Packit 40b132
	pref = smime_cipher_map_count;
Packit 40b132
Packit 40b132
	/* find recipient's SMIME profile */
Packit 40b132
	profile = CERT_FindSMimeProfile(rcerts[rcount]);
Packit 40b132
Packit 40b132
	if (profile != NULL && profile->data != NULL && profile->len > 0) {
Packit 40b132
	    /* we have a profile (still DER-encoded) */
Packit 40b132
	    caps = NULL;
Packit 40b132
	    /* decode it */
Packit 40b132
	    if (SEC_QuickDERDecodeItem(poolp, &caps,
Packit 40b132
                    NSSSMIMECapabilitiesTemplate, profile) == SECSuccess &&
Packit 40b132
		    caps != NULL)
Packit 40b132
	    {
Packit 40b132
		/* walk the SMIME capabilities for this recipient */
Packit 40b132
		for (i = 0; caps[i] != NULL; i++) {
Packit 40b132
		    cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]);
Packit 40b132
		    mapi = smime_mapi_by_cipher(cipher);
Packit 40b132
		    if (mapi >= 0) {
Packit 40b132
			/* found the cipher */
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
	    /* no profile found - so we can only assume that the user can do
Packit 40b132
	     * the mandatory algorithms which are RC2-40 (weak crypto) and
Packit 40b132
	     * 3DES (strong crypto), unless the user has an elliptic curve
Packit 40b132
	     * key.  For elliptic curve keys, RFC 5753 mandates support
Packit 40b132
	     * for AES 128 CBC. */
Packit 40b132
	    SECKEYPublicKey *key;
Packit 40b132
	    unsigned int pklen_bits;
Packit 40b132
	    KeyType key_type;
Packit 40b132
Packit 40b132
	    /*
Packit 40b132
	     * if recipient's public key length is > 512, vote for a strong cipher
Packit 40b132
	     * please not that the side effect of this is that if only one recipient
Packit 40b132
	     * has an export-level public key, the strong cipher is disabled.
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
	    pklen_bits = 0;
Packit 40b132
	    key_type = nullKey;
Packit 40b132
	    if (key != NULL) {
Packit 40b132
		pklen_bits = SECKEY_PublicKeyStrengthInBits (key);
Packit 40b132
		key_type = SECKEY_GetPublicKeyType(key);
Packit 40b132
		SECKEY_DestroyPublicKey (key);
Packit 40b132
		key = NULL;
Packit 40b132
	    }
Packit 40b132
Packit 40b132
	    if (key_type == ecKey) {
Packit 40b132
		/* While RFC 5753 mandates support for AES-128 CBC, should use
Packit 40b132
		 * AES 256 if user's key provides more than 128 bits of
Packit 40b132
		 * security strength so that symmetric key is not weak link. */
Packit 40b132
Packit 40b132
		/* RC2-40 is not compatible with elliptic curve keys. */
Packit 40b132
		chosen_cipher = SMIME_DES_EDE3_168;
Packit 40b132
		if (pklen_bits > 256) {
Packit 40b132
		    cipher_abilities[aes256_mapi]++;
Packit 40b132
		    cipher_votes[aes256_mapi] += pref;
Packit 40b132
		    pref--;
Packit 40b132
		}
Packit 40b132
		cipher_abilities[aes128_mapi]++;
Packit 40b132
		cipher_votes[aes128_mapi] += pref;
Packit 40b132
		pref--;
Packit 40b132
		cipher_abilities[strong_mapi]++;
Packit 40b132
		cipher_votes[strong_mapi] += pref;
Packit 40b132
		pref--;
Packit 40b132
	    } else {
Packit 40b132
		if (pklen_bits > 512) {
Packit 40b132
		    /* cast votes for the strong algorithm */
Packit 40b132
		    cipher_abilities[strong_mapi]++;
Packit 40b132
		    cipher_votes[strong_mapi] += pref;
Packit 40b132
		    pref--;
Packit 40b132
		}
Packit 40b132
Packit 40b132
		/* always cast (possibly less) votes for the weak algorithm */
Packit 40b132
		cipher_abilities[weak_mapi]++;
Packit 40b132
		cipher_votes[weak_mapi] += pref;
Packit 40b132
	    } 
Packit 40b132
	}
Packit 40b132
	if (profile != NULL)
Packit 40b132
	    SECITEM_FreeItem(profile, PR_TRUE);
Packit 40b132
    }
Packit 40b132
Packit 40b132
    /* find cipher that is agreeable by all recipients and that has the most votes */
Packit 40b132
    max = 0;
Packit 40b132
    for (mapi = 0; mapi < smime_cipher_map_count; mapi++) {
Packit 40b132
	/* if not all of the recipients can do this, forget it */
Packit 40b132
	if (cipher_abilities[mapi] != rcount)
Packit 40b132
	    continue;
Packit 40b132
	/* if cipher is not enabled or not allowed by policy, forget it */
Packit 40b132
	if (!smime_cipher_map[mapi].enabled || !smime_cipher_map[mapi].allowed)
Packit 40b132
	    continue;
Packit 40b132
	/* now see if this one has more votes than the last best one */
Packit 40b132
	if (cipher_votes[mapi] >= max) {
Packit 40b132
	    /* if equal number of votes, prefer the ones further down in the list */
Packit 40b132
	    /* with the expectation that these are higher rated ciphers */
Packit 40b132
	    chosen_cipher = smime_cipher_map[mapi].cipher;
Packit 40b132
	    max = cipher_votes[mapi];
Packit 40b132
	}
Packit 40b132
    }
Packit 40b132
    /* if no common cipher was found, chosen_cipher stays at the default */
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
 * 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
      case SMIME_AES_CBC_128:
Packit 40b132
	keysize = 128;
Packit 40b132
	break;
Packit 40b132
      case SMIME_AES_CBC_256:
Packit 40b132
	keysize = 256;
Packit 40b132
	break;
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
 * NSS_SMIMEUtil_FindBulkAlgForRecipients - find bulk algorithm suitable for all recipients
Packit 40b132
 *
Packit 40b132
 * it would be great for UI purposes if there would be a way to find out which recipients
Packit 40b132
 * prevented a strong cipher from being used...
Packit 40b132
 */
Packit 40b132
SECStatus
Packit 40b132
NSS_SMIMEUtil_FindBulkAlgForRecipients(CERTCertificate **rcerts, SECOidTag *bulkalgtag, int *keysize)
Packit 40b132
{
Packit 40b132
    unsigned long cipher;
Packit 40b132
    int mapi;
Packit 40b132
Packit 40b132
    cipher = smime_choose_cipher(NULL, rcerts);
Packit 40b132
    mapi = smime_mapi_by_cipher(cipher);
Packit 40b132
Packit 40b132
    *bulkalgtag = smime_cipher_map[mapi].algtag;
Packit 40b132
    *keysize = smime_keysize_by_cipher(smime_cipher_map[mapi].cipher);
Packit 40b132
Packit 40b132
    return SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * NSS_SMIMEUtil_CreateSMIMECapabilities - get S/MIME capabilities for this instance of NSS
Packit 40b132
 *
Packit 40b132
 * scans the list of allowed and enabled ciphers and construct a PKCS9-compliant
Packit 40b132
 * S/MIME capabilities attribute value.
Packit 40b132
 *
Packit 40b132
 * XXX Please note that, in contradiction to RFC2633 2.5.2, the capabilities only include
Packit 40b132
 * symmetric ciphers, NO signature algorithms or key encipherment algorithms.
Packit 40b132
 *
Packit 40b132
 * "poolp" - arena pool to create the S/MIME capabilities data on
Packit 40b132
 * "dest" - SECItem to put the data in
Packit 40b132
 */
Packit 40b132
SECStatus
Packit 40b132
NSS_SMIMEUtil_CreateSMIMECapabilities(PLArenaPool *poolp, SECItem *dest)
Packit 40b132
{
Packit 40b132
    NSSSMIMECapability *cap;
Packit 40b132
    NSSSMIMECapability **smime_capabilities;
Packit 40b132
    smime_cipher_map_entry *map;
Packit 40b132
    SECOidData *oiddata;
Packit 40b132
    SECItem *dummy;
Packit 40b132
    int i, capIndex;
Packit 40b132
Packit 40b132
    /* if we have an old NSSSMIMECapability array, we'll reuse it (has the right size) */
Packit 40b132
    /* smime_cipher_map_count + 1 is an upper bound - we might end up with less */
Packit 40b132
    smime_capabilities = (NSSSMIMECapability **)PORT_ZAlloc((smime_cipher_map_count + 1)
Packit 40b132
				      * sizeof(NSSSMIMECapability *));
Packit 40b132
    if (smime_capabilities == NULL)
Packit 40b132
	return SECFailure;
Packit 40b132
Packit 40b132
    capIndex = 0;
Packit 40b132
Packit 40b132
    /* Add all the symmetric ciphers
Packit 40b132
     * We walk the cipher list backwards, as it is ordered by increasing strength,
Packit 40b132
     * we prefer the stronger cipher over a weaker one, and we have to list the
Packit 40b132
     * preferred algorithm first */
Packit 40b132
    for (i = smime_cipher_map_count - 1; i >= 0; i--) {
Packit 40b132
	/* Find the corresponding entry in the cipher map. */
Packit 40b132
	map = &(smime_cipher_map[i]);
Packit 40b132
	if (!map->enabled)
Packit 40b132
	    continue;
Packit 40b132
Packit 40b132
	/* get next SMIME capability */
Packit 40b132
	cap = (NSSSMIMECapability *)PORT_ZAlloc(sizeof(NSSSMIMECapability));
Packit 40b132
	if (cap == NULL)
Packit 40b132
	    break;
Packit 40b132
	smime_capabilities[capIndex++] = cap;
Packit 40b132
Packit 40b132
	oiddata = SECOID_FindOIDByTag(map->algtag);
Packit 40b132
	if (oiddata == NULL)
Packit 40b132
	    break;
Packit 40b132
Packit 40b132
	cap->capabilityID.data = oiddata->oid.data;
Packit 40b132
	cap->capabilityID.len = oiddata->oid.len;
Packit 40b132
	cap->parameters.data = map->parms ? map->parms->data : NULL;
Packit 40b132
	cap->parameters.len = map->parms ? map->parms->len : 0;
Packit 40b132
	cap->cipher = smime_cipher_map[i].cipher;
Packit 40b132
    }
Packit 40b132
Packit 40b132
    /* XXX add signature algorithms */
Packit 40b132
    /* XXX add key encipherment algorithms */
Packit 40b132
Packit 40b132
    smime_capabilities[capIndex] = NULL;	/* last one - now encode */
Packit 40b132
    dummy = SEC_ASN1EncodeItem(poolp, dest, &smime_capabilities, NSSSMIMECapabilitiesTemplate);
Packit 40b132
Packit 40b132
    /* now that we have the proper encoded SMIMECapabilities (or not),
Packit 40b132
     * free the work data */
Packit 40b132
    for (i = 0; smime_capabilities[i] != NULL; i++)
Packit 40b132
	PORT_Free(smime_capabilities[i]);
Packit 40b132
    PORT_Free(smime_capabilities);
Packit 40b132
Packit 40b132
    return (dummy == NULL) ? SECFailure : SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value
Packit 40b132
 *
Packit 40b132
 * "poolp" - arena pool to create the attr value on
Packit 40b132
 * "dest" - SECItem to put the data in
Packit 40b132
 * "cert" - certificate that should be marked as preferred encryption key
Packit 40b132
 *          cert is expected to have been verified for EmailRecipient usage.
Packit 40b132
 */
Packit 40b132
SECStatus
Packit 40b132
NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs(PLArenaPool *poolp, SECItem *dest, CERTCertificate *cert)
Packit 40b132
{
Packit 40b132
    NSSSMIMEEncryptionKeyPreference ekp;
Packit 40b132
    SECItem *dummy = NULL;
Packit 40b132
    PLArenaPool *tmppoolp = NULL;
Packit 40b132
Packit 40b132
    if (cert == NULL)
Packit 40b132
	goto loser;
Packit 40b132
Packit 40b132
    tmppoolp = PORT_NewArena(1024);
Packit 40b132
    if (tmppoolp == NULL)
Packit 40b132
	goto loser;
Packit 40b132
Packit 40b132
    /* XXX hardcoded IssuerSN choice for now */
Packit 40b132
    ekp.selector = NSSSMIMEEncryptionKeyPref_IssuerSN;
Packit 40b132
    ekp.id.issuerAndSN = CERT_GetCertIssuerAndSN(tmppoolp, cert);
Packit 40b132
    if (ekp.id.issuerAndSN == NULL)
Packit 40b132
	goto loser;
Packit 40b132
Packit 40b132
    dummy = SEC_ASN1EncodeItem(poolp, dest, &ekp, smime_encryptionkeypref_template);
Packit 40b132
Packit 40b132
loser:
Packit 40b132
    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
Packit 40b132
Packit 40b132
    return (dummy == NULL) ? SECFailure : SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value using MS oid
Packit 40b132
 *
Packit 40b132
 * "poolp" - arena pool to create the attr value on
Packit 40b132
 * "dest" - SECItem to put the data in
Packit 40b132
 * "cert" - certificate that should be marked as preferred encryption key
Packit 40b132
 *          cert is expected to have been verified for EmailRecipient usage.
Packit 40b132
 */
Packit 40b132
SECStatus
Packit 40b132
NSS_SMIMEUtil_CreateMSSMIMEEncKeyPrefs(PLArenaPool *poolp, SECItem *dest, CERTCertificate *cert)
Packit 40b132
{
Packit 40b132
    SECItem *dummy = NULL;
Packit 40b132
    PLArenaPool *tmppoolp = NULL;
Packit 40b132
    CERTIssuerAndSN *isn;
Packit 40b132
Packit 40b132
    if (cert == NULL)
Packit 40b132
	goto loser;
Packit 40b132
Packit 40b132
    tmppoolp = PORT_NewArena(1024);
Packit 40b132
    if (tmppoolp == NULL)
Packit 40b132
	goto loser;
Packit 40b132
Packit 40b132
    isn = CERT_GetCertIssuerAndSN(tmppoolp, cert);
Packit 40b132
    if (isn == NULL)
Packit 40b132
	goto loser;
Packit 40b132
Packit 40b132
    dummy = SEC_ASN1EncodeItem(poolp, dest, isn, SEC_ASN1_GET(CERT_IssuerAndSNTemplate));
Packit 40b132
Packit 40b132
loser:
Packit 40b132
    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
Packit 40b132
Packit 40b132
    return (dummy == NULL) ? SECFailure : SECSuccess;
Packit 40b132
}
Packit 40b132
Packit 40b132
/*
Packit 40b132
 * NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference -
Packit 40b132
 *				find cert marked by EncryptionKeyPreference attribute
Packit 40b132
 *
Packit 40b132
 * "certdb" - handle for the cert database to look in
Packit 40b132
 * "DERekp" - DER-encoded value of S/MIME Encryption Key Preference attribute
Packit 40b132
 *
Packit 40b132
 * if certificate is supposed to be found among the message's included certificates,
Packit 40b132
 * they are assumed to have been imported already.
Packit 40b132
 */
Packit 40b132
CERTCertificate *
Packit 40b132
NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference(CERTCertDBHandle *certdb, SECItem *DERekp)
Packit 40b132
{
Packit 40b132
    PLArenaPool *tmppoolp = NULL;
Packit 40b132
    CERTCertificate *cert = NULL;
Packit 40b132
    NSSSMIMEEncryptionKeyPreference ekp;
Packit 40b132
Packit 40b132
    tmppoolp = PORT_NewArena(1024);
Packit 40b132
    if (tmppoolp == NULL)
Packit 40b132
	return NULL;
Packit 40b132
Packit 40b132
    /* decode DERekp */
Packit 40b132
    if (SEC_QuickDERDecodeItem(tmppoolp, &ekp, smime_encryptionkeypref_template,
Packit 40b132
                               DERekp) != SECSuccess)
Packit 40b132
	goto loser;
Packit 40b132
Packit 40b132
    /* find cert */
Packit 40b132
    switch (ekp.selector) {
Packit 40b132
    case NSSSMIMEEncryptionKeyPref_IssuerSN:
Packit 40b132
	cert = CERT_FindCertByIssuerAndSN(certdb, ekp.id.issuerAndSN);
Packit 40b132
	break;
Packit 40b132
    case NSSSMIMEEncryptionKeyPref_RKeyID:
Packit 40b132
    case NSSSMIMEEncryptionKeyPref_SubjectKeyID:
Packit 40b132
	/* XXX not supported yet - we need to be able to look up certs by SubjectKeyID */
Packit 40b132
	break;
Packit 40b132
    default:
Packit 40b132
	PORT_Assert(0);
Packit 40b132
    }
Packit 40b132
loser:
Packit 40b132
    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
Packit 40b132
Packit 40b132
    return cert;
Packit 40b132
}
Packit 40b132
Packit 40b132
extern const char __nss_smime_version[];
Packit 40b132
Packit 40b132
PRBool
Packit 40b132
NSSSMIME_VersionCheck(const char *importedVersion)
Packit 40b132
{
Packit 40b132
    /*
Packit 40b132
     * This is the secret handshake algorithm.
Packit 40b132
     *
Packit 40b132
     * This release has a simple version compatibility
Packit 40b132
     * check algorithm.  This release is not backward
Packit 40b132
     * compatible with previous major releases.  It is
Packit 40b132
     * not compatible with future major, minor, or
Packit 40b132
     * patch releases.
Packit 40b132
     */
Packit 40b132
    volatile char c; /* force a reference that won't get optimized away */
Packit 40b132
Packit 40b132
    c = __nss_smime_version[0];
Packit 40b132
Packit 40b132
    return NSS_VersionCheck(importedVersion);
Packit 40b132
}
Packit 40b132
Packit 40b132
const char *
Packit 40b132
NSSSMIME_GetVersion(void)
Packit 40b132
{
Packit 40b132
    return NSS_VERSION;
Packit 40b132
}