security/nss/lib/pkcs7/secmime.c

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/security/nss/lib/pkcs7/secmime.c	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,822 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +/*
     1.9 + * Stuff specific to S/MIME policy and interoperability.
    1.10 + * Depends on PKCS7, but there should be no dependency the other way around.
    1.11 + */
    1.12 +
    1.13 +#include "secmime.h"
    1.14 +#include "secoid.h"
    1.15 +#include "pk11func.h"
    1.16 +#include "ciferfam.h"	/* for CIPHER_FAMILY symbols */
    1.17 +#include "secasn1.h"
    1.18 +#include "secitem.h"
    1.19 +#include "cert.h"
    1.20 +#include "key.h"
    1.21 +#include "secerr.h"
    1.22 +
    1.23 +typedef struct smime_cipher_map_struct {
    1.24 +    unsigned long cipher;
    1.25 +    SECOidTag algtag;
    1.26 +    SECItem *parms;
    1.27 +} smime_cipher_map;
    1.28 +
    1.29 +/*
    1.30 + * These are macros because I think some subsequent parameters,
    1.31 + * like those for RC5, will want to use them, too, separately.
    1.32 + */
    1.33 +#define SMIME_DER_INTVAL_16	SEC_ASN1_INTEGER, 0x01, 0x10
    1.34 +#define SMIME_DER_INTVAL_40	SEC_ASN1_INTEGER, 0x01, 0x28
    1.35 +#define SMIME_DER_INTVAL_64	SEC_ASN1_INTEGER, 0x01, 0x40
    1.36 +#define SMIME_DER_INTVAL_128	SEC_ASN1_INTEGER, 0x02, 0x00, 0x80
    1.37 +
    1.38 +#ifdef SMIME_DOES_RC5	/* will be needed; quiet unused warning for now */
    1.39 +static unsigned char smime_int16[] = { SMIME_DER_INTVAL_16 };
    1.40 +#endif
    1.41 +static unsigned char smime_int40[] = { SMIME_DER_INTVAL_40 };
    1.42 +static unsigned char smime_int64[] = { SMIME_DER_INTVAL_64 };
    1.43 +static unsigned char smime_int128[] = { SMIME_DER_INTVAL_128 };
    1.44 +
    1.45 +static SECItem smime_rc2p40 = { siBuffer, smime_int40, sizeof(smime_int40) };
    1.46 +static SECItem smime_rc2p64 = { siBuffer, smime_int64, sizeof(smime_int64) };
    1.47 +static SECItem smime_rc2p128 = { siBuffer, smime_int128, sizeof(smime_int128) };
    1.48 +
    1.49 +static smime_cipher_map smime_cipher_maps[] = {
    1.50 +    { SMIME_RC2_CBC_40,		SEC_OID_RC2_CBC,	&smime_rc2p40 },
    1.51 +    { SMIME_RC2_CBC_64,		SEC_OID_RC2_CBC,	&smime_rc2p64 },
    1.52 +    { SMIME_RC2_CBC_128,	SEC_OID_RC2_CBC,	&smime_rc2p128 },
    1.53 +#ifdef SMIME_DOES_RC5
    1.54 +    { SMIME_RC5PAD_64_16_40,	SEC_OID_RC5_CBC_PAD,	&smime_rc5p40 },
    1.55 +    { SMIME_RC5PAD_64_16_64,	SEC_OID_RC5_CBC_PAD,	&smime_rc5p64 },
    1.56 +    { SMIME_RC5PAD_64_16_128,	SEC_OID_RC5_CBC_PAD,	&smime_rc5p128 },
    1.57 +#endif
    1.58 +    { SMIME_DES_CBC_56,		SEC_OID_DES_CBC,	NULL },
    1.59 +    { SMIME_DES_EDE3_168,	SEC_OID_DES_EDE3_CBC,	NULL }
    1.60 +};
    1.61 +
    1.62 +/*
    1.63 + * Note, the following value really just needs to be an upper bound
    1.64 + * on the ciphers.
    1.65 + */
    1.66 +static const int smime_symmetric_count = sizeof(smime_cipher_maps)
    1.67 +					 / sizeof(smime_cipher_map);
    1.68 +
    1.69 +static unsigned long *smime_prefs, *smime_newprefs;
    1.70 +static int smime_current_pref_index = 0;
    1.71 +static PRBool smime_prefs_complete = PR_FALSE;
    1.72 +static PRBool smime_prefs_changed = PR_TRUE;
    1.73 +
    1.74 +static unsigned long smime_policy_bits = 0;
    1.75 +
    1.76 +
    1.77 +static int
    1.78 +smime_mapi_by_cipher (unsigned long cipher)
    1.79 +{
    1.80 +    int i;
    1.81 +
    1.82 +    for (i = 0; i < smime_symmetric_count; i++) {
    1.83 +	if (smime_cipher_maps[i].cipher == cipher)
    1.84 +	    break;
    1.85 +    }
    1.86 +
    1.87 +    if (i == smime_symmetric_count)
    1.88 +	return -1;
    1.89 +
    1.90 +    return i;
    1.91 +}
    1.92 +
    1.93 +
    1.94 +/*
    1.95 + * this function locally records the user's preference
    1.96 + */
    1.97 +SECStatus 
    1.98 +SECMIME_EnableCipher(long which, int on)
    1.99 +{
   1.100 +    unsigned long mask;
   1.101 +
   1.102 +    if (smime_newprefs == NULL || smime_prefs_complete) {
   1.103 +	/*
   1.104 +	 * This is either the very first time, or we are starting over.
   1.105 +	 */
   1.106 +	smime_newprefs = (unsigned long*)PORT_ZAlloc (smime_symmetric_count
   1.107 +				      * sizeof(*smime_newprefs));
   1.108 +	if (smime_newprefs == NULL)
   1.109 +	    return SECFailure;
   1.110 +	smime_current_pref_index = 0;
   1.111 +	smime_prefs_complete = PR_FALSE;
   1.112 +    }
   1.113 +
   1.114 +    mask = which & CIPHER_FAMILYID_MASK;
   1.115 +    if (mask == CIPHER_FAMILYID_MASK) {
   1.116 +    	/*
   1.117 +	 * This call signifies that all preferences have been set.
   1.118 +	 * Move "newprefs" over, after checking first whether or
   1.119 +	 * not the new ones are different from the old ones.
   1.120 +	 */
   1.121 +	if (smime_prefs != NULL) {
   1.122 +	    if (PORT_Memcmp (smime_prefs, smime_newprefs,
   1.123 +			     smime_symmetric_count * sizeof(*smime_prefs)) == 0)
   1.124 +		smime_prefs_changed = PR_FALSE;
   1.125 +	    else
   1.126 +		smime_prefs_changed = PR_TRUE;
   1.127 +	    PORT_Free (smime_prefs);
   1.128 +	}
   1.129 +
   1.130 +	smime_prefs = smime_newprefs;
   1.131 +	smime_prefs_complete = PR_TRUE;
   1.132 +	return SECSuccess;
   1.133 +    }
   1.134 +
   1.135 +    PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
   1.136 +    if (mask != CIPHER_FAMILYID_SMIME) {
   1.137 +	/* XXX set an error! */
   1.138 +    	return SECFailure;
   1.139 +    }
   1.140 +
   1.141 +    if (on) {
   1.142 +	PORT_Assert (smime_current_pref_index < smime_symmetric_count);
   1.143 +	if (smime_current_pref_index >= smime_symmetric_count) {
   1.144 +	    /* XXX set an error! */
   1.145 +	    return SECFailure;
   1.146 +	}
   1.147 +
   1.148 +	smime_newprefs[smime_current_pref_index++] = which;
   1.149 +    }
   1.150 +
   1.151 +    return SECSuccess;
   1.152 +}
   1.153 +
   1.154 +
   1.155 +/*
   1.156 + * this function locally records the export policy
   1.157 + */
   1.158 +SECStatus 
   1.159 +SECMIME_SetPolicy(long which, int on)
   1.160 +{
   1.161 +    unsigned long mask;
   1.162 +
   1.163 +    PORT_Assert ((which & CIPHER_FAMILYID_MASK) == CIPHER_FAMILYID_SMIME);
   1.164 +    if ((which & CIPHER_FAMILYID_MASK) != CIPHER_FAMILYID_SMIME) {
   1.165 +	/* XXX set an error! */
   1.166 +    	return SECFailure;
   1.167 +    }
   1.168 +
   1.169 +    which &= ~CIPHER_FAMILYID_MASK;
   1.170 +
   1.171 +    PORT_Assert (which < 32);	/* bits in the long */
   1.172 +    if (which >= 32) {
   1.173 +	/* XXX set an error! */
   1.174 +    	return SECFailure;
   1.175 +    }
   1.176 +
   1.177 +    mask = 1UL << which;
   1.178 +
   1.179 +    if (on) {
   1.180 +    	smime_policy_bits |= mask;
   1.181 +    } else {
   1.182 +    	smime_policy_bits &= ~mask;
   1.183 +    }
   1.184 +
   1.185 +    return SECSuccess;
   1.186 +}
   1.187 +
   1.188 +
   1.189 +/*
   1.190 + * Based on the given algorithm (including its parameters, in some cases!)
   1.191 + * and the given key (may or may not be inspected, depending on the
   1.192 + * algorithm), find the appropriate policy algorithm specification
   1.193 + * and return it.  If no match can be made, -1 is returned.
   1.194 + */
   1.195 +static long
   1.196 +smime_policy_algorithm (SECAlgorithmID *algid, PK11SymKey *key)
   1.197 +{
   1.198 +    SECOidTag algtag;
   1.199 +
   1.200 +    algtag = SECOID_GetAlgorithmTag (algid);
   1.201 +    switch (algtag) {
   1.202 +      case SEC_OID_RC2_CBC:
   1.203 +	{
   1.204 +	    unsigned int keylen_bits;
   1.205 +
   1.206 +	    keylen_bits = PK11_GetKeyStrength (key, algid);
   1.207 +	    switch (keylen_bits) {
   1.208 +	      case 40:
   1.209 +		return SMIME_RC2_CBC_40;
   1.210 +	      case 64:
   1.211 +		return SMIME_RC2_CBC_64;
   1.212 +	      case 128:
   1.213 +		return SMIME_RC2_CBC_128;
   1.214 +	      default:
   1.215 +		break;
   1.216 +	    }
   1.217 +	}
   1.218 +	break;
   1.219 +      case SEC_OID_DES_CBC:
   1.220 +	return SMIME_DES_CBC_56;
   1.221 +      case SEC_OID_DES_EDE3_CBC:
   1.222 +	return SMIME_DES_EDE3_168;
   1.223 +#ifdef SMIME_DOES_RC5
   1.224 +      case SEC_OID_RC5_CBC_PAD:
   1.225 +	PORT_Assert (0);	/* XXX need to pull out parameters and match */
   1.226 +	break;
   1.227 +#endif
   1.228 +      default:
   1.229 +	break;
   1.230 +    }
   1.231 +
   1.232 +    return -1;
   1.233 +}
   1.234 +
   1.235 +
   1.236 +static PRBool
   1.237 +smime_cipher_allowed (unsigned long which)
   1.238 +{
   1.239 +    unsigned long mask;
   1.240 +
   1.241 +    which &= ~CIPHER_FAMILYID_MASK;
   1.242 +    PORT_Assert (which < 32);	/* bits per long (min) */
   1.243 +    if (which >= 32)
   1.244 +	return PR_FALSE;
   1.245 +
   1.246 +    mask = 1UL << which;
   1.247 +    if ((mask & smime_policy_bits) == 0)
   1.248 +	return PR_FALSE;
   1.249 +
   1.250 +    return PR_TRUE;
   1.251 +}
   1.252 +
   1.253 +
   1.254 +PRBool
   1.255 +SECMIME_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
   1.256 +{
   1.257 +    long which;
   1.258 +
   1.259 +    which = smime_policy_algorithm (algid, key);
   1.260 +    if (which < 0)
   1.261 +	return PR_FALSE;
   1.262 +
   1.263 +    return smime_cipher_allowed ((unsigned long)which);
   1.264 +}
   1.265 +
   1.266 +
   1.267 +/*
   1.268 + * Does the current policy allow *any* S/MIME encryption (or decryption)?
   1.269 + *
   1.270 + * This tells whether or not *any* S/MIME encryption can be done,
   1.271 + * according to policy.  Callers may use this to do nicer user interface
   1.272 + * (say, greying out a checkbox so a user does not even try to encrypt
   1.273 + * a message when they are not allowed to) or for any reason they want
   1.274 + * to check whether S/MIME encryption (or decryption, for that matter)
   1.275 + * may be done.
   1.276 + *
   1.277 + * It takes no arguments.  The return value is a simple boolean:
   1.278 + *   PR_TRUE means encryption (or decryption) is *possible*
   1.279 + *	(but may still fail due to other reasons, like because we cannot
   1.280 + *	find all the necessary certs, etc.; PR_TRUE is *not* a guarantee)
   1.281 + *   PR_FALSE means encryption (or decryption) is not permitted
   1.282 + *
   1.283 + * There are no errors from this routine.
   1.284 + */
   1.285 +PRBool
   1.286 +SECMIME_EncryptionPossible (void)
   1.287 +{
   1.288 +    if (smime_policy_bits != 0)
   1.289 +	return PR_TRUE;
   1.290 +
   1.291 +    return PR_FALSE;
   1.292 +}
   1.293 +
   1.294 +
   1.295 +/*
   1.296 + * XXX Would like the "parameters" field to be a SECItem *, but the
   1.297 + * encoder is having trouble with optional pointers to an ANY.  Maybe
   1.298 + * once that is fixed, can change this back...
   1.299 + */
   1.300 +typedef struct smime_capability_struct {
   1.301 +    unsigned long cipher;	/* local; not part of encoding */
   1.302 +    SECOidTag capIDTag;		/* local; not part of encoding */
   1.303 +    SECItem capabilityID;
   1.304 +    SECItem parameters;
   1.305 +} smime_capability;
   1.306 +
   1.307 +static const SEC_ASN1Template smime_capability_template[] = {
   1.308 +    { SEC_ASN1_SEQUENCE,
   1.309 +	  0, NULL, sizeof(smime_capability) },
   1.310 +    { SEC_ASN1_OBJECT_ID,
   1.311 +	  offsetof(smime_capability,capabilityID), },
   1.312 +    { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
   1.313 +	  offsetof(smime_capability,parameters), },
   1.314 +    { 0, }
   1.315 +};
   1.316 +
   1.317 +static const SEC_ASN1Template smime_capabilities_template[] = {
   1.318 +    { SEC_ASN1_SEQUENCE_OF, 0, smime_capability_template }
   1.319 +};
   1.320 +
   1.321 +
   1.322 +
   1.323 +static void
   1.324 +smime_fill_capability (smime_capability *cap)
   1.325 +{
   1.326 +    unsigned long cipher;
   1.327 +    SECOidTag algtag;
   1.328 +    int i;
   1.329 +
   1.330 +    algtag = SECOID_FindOIDTag (&(cap->capabilityID));
   1.331 +
   1.332 +    for (i = 0; i < smime_symmetric_count; i++) {
   1.333 +	if (smime_cipher_maps[i].algtag != algtag)
   1.334 +	    continue;
   1.335 +	/*
   1.336 +	 * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing
   1.337 +	 * 2 NULLs as equal and NULL and non-NULL as not equal), we could
   1.338 +	 * use that here instead of all of the following comparison code.
   1.339 +	 */
   1.340 +	if (cap->parameters.data != NULL) {
   1.341 +	    if (smime_cipher_maps[i].parms == NULL)
   1.342 +		continue;
   1.343 +	    if (cap->parameters.len != smime_cipher_maps[i].parms->len)
   1.344 +		continue;
   1.345 +	    if (PORT_Memcmp (cap->parameters.data,
   1.346 +			     smime_cipher_maps[i].parms->data,
   1.347 +			     cap->parameters.len) == 0)
   1.348 +		break;
   1.349 +	} else if (smime_cipher_maps[i].parms == NULL) {
   1.350 +	    break;
   1.351 +	}
   1.352 +    }
   1.353 +
   1.354 +    if (i == smime_symmetric_count)
   1.355 +	cipher = 0;
   1.356 +    else
   1.357 +	cipher = smime_cipher_maps[i].cipher;
   1.358 +
   1.359 +    cap->cipher = cipher;
   1.360 +    cap->capIDTag = algtag;
   1.361 +}
   1.362 +
   1.363 +
   1.364 +static long
   1.365 +smime_choose_cipher (CERTCertificate *scert, CERTCertificate **rcerts)
   1.366 +{
   1.367 +    PLArenaPool *poolp;
   1.368 +    long chosen_cipher;
   1.369 +    int *cipher_abilities;
   1.370 +    int *cipher_votes;
   1.371 +    int strong_mapi;
   1.372 +    int rcount, mapi, max;
   1.373 +
   1.374 +    if (smime_policy_bits == 0) {
   1.375 +	PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM);
   1.376 +	return -1;
   1.377 +    }
   1.378 +
   1.379 +    chosen_cipher = SMIME_RC2_CBC_40;		/* the default, LCD */
   1.380 +
   1.381 +    poolp = PORT_NewArena (1024);		/* XXX what is right value? */
   1.382 +    if (poolp == NULL)
   1.383 +	goto done;
   1.384 +
   1.385 +    cipher_abilities = (int*)PORT_ArenaZAlloc (poolp,
   1.386 +					 smime_symmetric_count * sizeof(int));
   1.387 +    if (cipher_abilities == NULL)
   1.388 +	goto done;
   1.389 +
   1.390 +    cipher_votes = (int*)PORT_ArenaZAlloc (poolp,
   1.391 +				     smime_symmetric_count * sizeof(int));
   1.392 +    if (cipher_votes == NULL)
   1.393 +	goto done;
   1.394 +
   1.395 +    /*
   1.396 +     * XXX Should have a #define somewhere which specifies default
   1.397 +     * strong cipher.  (Or better, a way to configure.)
   1.398 +     */
   1.399 +
   1.400 +    /* Make triple-DES the strong cipher. */
   1.401 +    strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168);
   1.402 +
   1.403 +    PORT_Assert (strong_mapi >= 0);
   1.404 +
   1.405 +    for (rcount = 0; rcerts[rcount] != NULL; rcount++) {
   1.406 +	SECItem *profile;
   1.407 +	smime_capability **caps;
   1.408 +	int capi, pref;
   1.409 +	SECStatus dstat;
   1.410 +
   1.411 +	pref = smime_symmetric_count;
   1.412 +	profile = CERT_FindSMimeProfile (rcerts[rcount]);
   1.413 +	if (profile != NULL && profile->data != NULL && profile->len > 0) {
   1.414 +	    caps = NULL;
   1.415 +	    dstat = SEC_QuickDERDecodeItem (poolp, &caps,
   1.416 +					smime_capabilities_template,
   1.417 +					profile);
   1.418 +	    if (dstat == SECSuccess && caps != NULL) {
   1.419 +		for (capi = 0; caps[capi] != NULL; capi++) {
   1.420 +		    smime_fill_capability (caps[capi]);
   1.421 +		    mapi = smime_mapi_by_cipher (caps[capi]->cipher);
   1.422 +		    if (mapi >= 0) {
   1.423 +			cipher_abilities[mapi]++;
   1.424 +			cipher_votes[mapi] += pref;
   1.425 +			--pref;
   1.426 +		    }
   1.427 +		}
   1.428 +	    }
   1.429 +	} else {
   1.430 +	    SECKEYPublicKey *key;
   1.431 +	    unsigned int pklen_bits;
   1.432 +
   1.433 +	    /*
   1.434 +	     * XXX This is probably only good for RSA keys.  What I would
   1.435 +	     * really like is a function to just say;  Is the public key in
   1.436 +	     * this cert an export-length key?  Then I would not have to
   1.437 +	     * know things like the value 512, or the kind of key, or what
   1.438 +	     * a subjectPublicKeyInfo is, etc.
   1.439 +	     */
   1.440 +	    key = CERT_ExtractPublicKey (rcerts[rcount]);
   1.441 +	    if (key != NULL) {
   1.442 +		pklen_bits = SECKEY_PublicKeyStrength (key) * 8;
   1.443 +		SECKEY_DestroyPublicKey (key);
   1.444 +
   1.445 +		if (pklen_bits > 512) {
   1.446 +		    cipher_abilities[strong_mapi]++;
   1.447 +		    cipher_votes[strong_mapi] += pref;
   1.448 +		}
   1.449 +	    }
   1.450 +	}
   1.451 +	if (profile != NULL)
   1.452 +	    SECITEM_FreeItem (profile, PR_TRUE);
   1.453 +    }
   1.454 +
   1.455 +    max = 0;
   1.456 +    for (mapi = 0; mapi < smime_symmetric_count; mapi++) {
   1.457 +	if (cipher_abilities[mapi] != rcount)
   1.458 +	    continue;
   1.459 +	if (! smime_cipher_allowed (smime_cipher_maps[mapi].cipher))
   1.460 +	    continue;
   1.461 +	if (cipher_votes[mapi] > max) {
   1.462 +	    chosen_cipher = smime_cipher_maps[mapi].cipher;
   1.463 +	    max = cipher_votes[mapi];
   1.464 +	} /* XXX else if a tie, let scert break it? */
   1.465 +    }
   1.466 +
   1.467 +done:
   1.468 +    if (poolp != NULL)
   1.469 +	PORT_FreeArena (poolp, PR_FALSE);
   1.470 +
   1.471 +    return chosen_cipher;
   1.472 +}
   1.473 +
   1.474 +
   1.475 +/*
   1.476 + * XXX This is a hack for now to satisfy our current interface.
   1.477 + * Eventually, with more parameters needing to be specified, just
   1.478 + * looking up the keysize is not going to be sufficient.
   1.479 + */
   1.480 +static int
   1.481 +smime_keysize_by_cipher (unsigned long which)
   1.482 +{
   1.483 +    int keysize;
   1.484 +
   1.485 +    switch (which) {
   1.486 +      case SMIME_RC2_CBC_40:
   1.487 +	keysize = 40;
   1.488 +	break;
   1.489 +      case SMIME_RC2_CBC_64:
   1.490 +	keysize = 64;
   1.491 +	break;
   1.492 +      case SMIME_RC2_CBC_128:
   1.493 +	keysize = 128;
   1.494 +	break;
   1.495 +#ifdef SMIME_DOES_RC5
   1.496 +      case SMIME_RC5PAD_64_16_40:
   1.497 +      case SMIME_RC5PAD_64_16_64:
   1.498 +      case SMIME_RC5PAD_64_16_128:
   1.499 +	/* XXX See comment above; keysize is not enough... */
   1.500 +	PORT_Assert (0);
   1.501 +	PORT_SetError (SEC_ERROR_INVALID_ALGORITHM);
   1.502 +	keysize = -1;
   1.503 +	break;
   1.504 +#endif
   1.505 +      case SMIME_DES_CBC_56:
   1.506 +      case SMIME_DES_EDE3_168:
   1.507 +	/*
   1.508 +	 * These are special; since the key size is fixed, we actually
   1.509 +	 * want to *avoid* specifying a key size.
   1.510 +	 */
   1.511 +	keysize = 0;
   1.512 +	break;
   1.513 +      default:
   1.514 +	keysize = -1;
   1.515 +	break;
   1.516 +    }
   1.517 +
   1.518 +    return keysize;
   1.519 +}
   1.520 +
   1.521 +
   1.522 +/*
   1.523 + * Start an S/MIME encrypting context.
   1.524 + *
   1.525 + * "scert" is the cert for the sender.  It will be checked for validity.
   1.526 + * "rcerts" are the certs for the recipients.  They will also be checked.
   1.527 + *
   1.528 + * "certdb" is the cert database to use for verifying the certs.
   1.529 + * It can be NULL if a default database is available (like in the client).
   1.530 + *
   1.531 + * This function already does all of the stuff specific to S/MIME protocol
   1.532 + * and local policy; the return value just needs to be passed to
   1.533 + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data,
   1.534 + * and finally to SEC_PKCS7DestroyContentInfo().
   1.535 + *
   1.536 + * An error results in a return value of NULL and an error set.
   1.537 + * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
   1.538 + */
   1.539 +SEC_PKCS7ContentInfo *
   1.540 +SECMIME_CreateEncrypted(CERTCertificate *scert,
   1.541 +			CERTCertificate **rcerts,
   1.542 +			CERTCertDBHandle *certdb,
   1.543 +			SECKEYGetPasswordKey pwfn,
   1.544 +			void *pwfn_arg)
   1.545 +{
   1.546 +    SEC_PKCS7ContentInfo *cinfo;
   1.547 +    long cipher;
   1.548 +    SECOidTag encalg;
   1.549 +    int keysize;
   1.550 +    int mapi, rci;
   1.551 +
   1.552 +    cipher = smime_choose_cipher (scert, rcerts);
   1.553 +    if (cipher < 0)
   1.554 +	return NULL;
   1.555 +
   1.556 +    mapi = smime_mapi_by_cipher (cipher);
   1.557 +    if (mapi < 0)
   1.558 +	return NULL;
   1.559 +
   1.560 +    /*
   1.561 +     * XXX This is stretching it -- CreateEnvelopedData should probably
   1.562 +     * take a cipher itself of some sort, because we cannot know what the
   1.563 +     * future will bring in terms of parameters for each type of algorithm.
   1.564 +     * For example, just an algorithm and keysize is *not* sufficient to
   1.565 +     * fully specify the usage of RC5 (which also needs to know rounds and
   1.566 +     * block size).  Work this out into a better API!
   1.567 +     */
   1.568 +    encalg = smime_cipher_maps[mapi].algtag;
   1.569 +    keysize = smime_keysize_by_cipher (cipher);
   1.570 +    if (keysize < 0)
   1.571 +	return NULL;
   1.572 +
   1.573 +    cinfo = SEC_PKCS7CreateEnvelopedData (scert, certUsageEmailRecipient,
   1.574 +					  certdb, encalg, keysize,
   1.575 +					  pwfn, pwfn_arg);
   1.576 +    if (cinfo == NULL)
   1.577 +	return NULL;
   1.578 +
   1.579 +    for (rci = 0; rcerts[rci] != NULL; rci++) {
   1.580 +	if (rcerts[rci] == scert)
   1.581 +	    continue;
   1.582 +	if (SEC_PKCS7AddRecipient (cinfo, rcerts[rci], certUsageEmailRecipient,
   1.583 +				   NULL) != SECSuccess) {
   1.584 +	    SEC_PKCS7DestroyContentInfo (cinfo);
   1.585 +	    return NULL;
   1.586 +	}
   1.587 +    }
   1.588 +
   1.589 +    return cinfo;
   1.590 +}
   1.591 +
   1.592 +
   1.593 +static smime_capability **smime_capabilities;
   1.594 +static SECItem *smime_encoded_caps;
   1.595 +
   1.596 +
   1.597 +static SECStatus
   1.598 +smime_init_caps (void)
   1.599 +{
   1.600 +    smime_capability *cap;
   1.601 +    smime_cipher_map *map;
   1.602 +    SECOidData *oiddata;
   1.603 +    SECStatus rv;
   1.604 +    int i;
   1.605 +
   1.606 +    if (smime_encoded_caps != NULL && (! smime_prefs_changed))
   1.607 +	return SECSuccess;
   1.608 +
   1.609 +    if (smime_encoded_caps != NULL) {
   1.610 +	SECITEM_FreeItem (smime_encoded_caps, PR_TRUE);
   1.611 +	smime_encoded_caps = NULL;
   1.612 +    }
   1.613 +
   1.614 +    if (smime_capabilities == NULL) {
   1.615 +	smime_capabilities = (smime_capability**)PORT_ZAlloc (
   1.616 +					  (smime_symmetric_count + 1)
   1.617 +					  * sizeof(smime_capability *));
   1.618 +	if (smime_capabilities == NULL)
   1.619 +	    return SECFailure;
   1.620 +    }
   1.621 +
   1.622 +    rv = SECFailure;
   1.623 +
   1.624 +    /* 
   1.625 +       The process of creating the encoded PKCS7 cipher capability list
   1.626 +       involves two basic steps: 
   1.627 +
   1.628 +       (a) Convert our internal representation of cipher preferences 
   1.629 +           (smime_prefs) into an array containing cipher OIDs and 
   1.630 +	   parameter data (smime_capabilities). This step is
   1.631 +	   performed here.
   1.632 +
   1.633 +       (b) Encode, using ASN.1, the cipher information in 
   1.634 +           smime_capabilities, leaving the encoded result in 
   1.635 +	   smime_encoded_caps.
   1.636 +
   1.637 +       (In the process of performing (a), Lisa put in some optimizations
   1.638 +       which allow us to avoid needlessly re-populating elements in 
   1.639 +       smime_capabilities as we walk through smime_prefs.)
   1.640 +    */
   1.641 +    for (i = 0; i < smime_current_pref_index; i++) {
   1.642 +	int mapi;
   1.643 +
   1.644 +	/* Get the next cipher preference in smime_prefs. */
   1.645 +	mapi = smime_mapi_by_cipher (smime_prefs[i]);
   1.646 +	if (mapi < 0)
   1.647 +	    break;
   1.648 +
   1.649 +	/* Find the corresponding entry in the cipher map. */
   1.650 +	PORT_Assert (mapi < smime_symmetric_count);
   1.651 +	map = &(smime_cipher_maps[mapi]);
   1.652 +
   1.653 +	/*
   1.654 +	 * Convert the next preference found in smime_prefs into an
   1.655 +	 * smime_capability.
   1.656 +	 */
   1.657 +
   1.658 +	cap = smime_capabilities[i];
   1.659 +	if (cap == NULL) {
   1.660 +	    cap = (smime_capability*)PORT_ZAlloc (sizeof(smime_capability));
   1.661 +	    if (cap == NULL)
   1.662 +		break;
   1.663 +	    smime_capabilities[i] = cap;
   1.664 +	} else if (cap->cipher == smime_prefs[i]) {
   1.665 +	    continue;		/* no change to this one */
   1.666 +	}
   1.667 +
   1.668 +	cap->capIDTag = map->algtag;
   1.669 +	oiddata = SECOID_FindOIDByTag (map->algtag);
   1.670 +	if (oiddata == NULL)
   1.671 +	    break;
   1.672 +
   1.673 +	if (cap->capabilityID.data != NULL) {
   1.674 +	    SECITEM_FreeItem (&(cap->capabilityID), PR_FALSE);
   1.675 +	    cap->capabilityID.data = NULL;
   1.676 +	    cap->capabilityID.len = 0;
   1.677 +	}
   1.678 +
   1.679 +	rv = SECITEM_CopyItem (NULL, &(cap->capabilityID), &(oiddata->oid));
   1.680 +	if (rv != SECSuccess)
   1.681 +	    break;
   1.682 +
   1.683 +	if (map->parms == NULL) {
   1.684 +	    cap->parameters.data = NULL;
   1.685 +	    cap->parameters.len = 0;
   1.686 +	} else {
   1.687 +	    cap->parameters.data = map->parms->data;
   1.688 +	    cap->parameters.len = map->parms->len;
   1.689 +	}
   1.690 +
   1.691 +	cap->cipher = smime_prefs[i];
   1.692 +    }
   1.693 +
   1.694 +    if (i != smime_current_pref_index)
   1.695 +	return rv;
   1.696 +
   1.697 +    while (i < smime_symmetric_count) {
   1.698 +	cap = smime_capabilities[i];
   1.699 +	if (cap != NULL) {
   1.700 +	    SECITEM_FreeItem (&(cap->capabilityID), PR_FALSE);
   1.701 +	    PORT_Free (cap);
   1.702 +	}
   1.703 +	smime_capabilities[i] = NULL;
   1.704 +	i++;
   1.705 +    }
   1.706 +    smime_capabilities[i] = NULL;
   1.707 +
   1.708 +    smime_encoded_caps = SEC_ASN1EncodeItem (NULL, NULL, &smime_capabilities,
   1.709 +					     smime_capabilities_template);
   1.710 +    if (smime_encoded_caps == NULL)
   1.711 +	return SECFailure;
   1.712 +
   1.713 +    return SECSuccess;
   1.714 +}
   1.715 +
   1.716 +
   1.717 +static SECStatus
   1.718 +smime_add_profile (CERTCertificate *cert, SEC_PKCS7ContentInfo *cinfo)
   1.719 +{
   1.720 +    PORT_Assert (smime_prefs_complete);
   1.721 +    if (! smime_prefs_complete)
   1.722 +	return SECFailure;
   1.723 +
   1.724 +    /* For that matter, if capabilities haven't been initialized yet,
   1.725 +       do so now. */
   1.726 +    if (smime_encoded_caps == NULL || smime_prefs_changed) {
   1.727 +	SECStatus rv;
   1.728 +
   1.729 +	rv = smime_init_caps();
   1.730 +	if (rv != SECSuccess)
   1.731 +	    return rv;
   1.732 +
   1.733 +	PORT_Assert (smime_encoded_caps != NULL);
   1.734 +    }
   1.735 +
   1.736 +    return SEC_PKCS7AddSignedAttribute (cinfo, SEC_OID_PKCS9_SMIME_CAPABILITIES,
   1.737 +					smime_encoded_caps);
   1.738 +}
   1.739 +
   1.740 +
   1.741 +/*
   1.742 + * Start an S/MIME signing context.
   1.743 + *
   1.744 + * "scert" is the cert that will be used to sign the data.  It will be
   1.745 + * checked for validity.
   1.746 + *
   1.747 + * "ecert" is the signer's encryption cert.  If it is different from
   1.748 + * scert, then it will be included in the signed message so that the
   1.749 + * recipient can save it for future encryptions.
   1.750 + *
   1.751 + * "certdb" is the cert database to use for verifying the cert.
   1.752 + * It can be NULL if a default database is available (like in the client).
   1.753 + * 
   1.754 + * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1).
   1.755 + * XXX There should be SECMIME functions for hashing, or the hashing should
   1.756 + * be built into this interface, which we would like because we would
   1.757 + * support more smartcards that way, and then this argument should go away.)
   1.758 + *
   1.759 + * "digest" is the actual digest of the data.  It must be provided in
   1.760 + * the case of detached data or NULL if the content will be included.
   1.761 + *
   1.762 + * This function already does all of the stuff specific to S/MIME protocol
   1.763 + * and local policy; the return value just needs to be passed to
   1.764 + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data,
   1.765 + * and finally to SEC_PKCS7DestroyContentInfo().
   1.766 + *
   1.767 + * An error results in a return value of NULL and an error set.
   1.768 + * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
   1.769 + */
   1.770 +
   1.771 +SEC_PKCS7ContentInfo *
   1.772 +SECMIME_CreateSigned (CERTCertificate *scert,
   1.773 +		      CERTCertificate *ecert,
   1.774 +		      CERTCertDBHandle *certdb,
   1.775 +		      SECOidTag digestalg,
   1.776 +		      SECItem *digest,
   1.777 +		      SECKEYGetPasswordKey pwfn,
   1.778 +		      void *pwfn_arg)
   1.779 +{
   1.780 +    SEC_PKCS7ContentInfo *cinfo;
   1.781 +    SECStatus rv;
   1.782 +
   1.783 +    /* See note in header comment above about digestalg. */
   1.784 +    /* Doesn't explain this. PORT_Assert (digestalg == SEC_OID_SHA1); */
   1.785 +
   1.786 +    cinfo = SEC_PKCS7CreateSignedData (scert, certUsageEmailSigner,
   1.787 +				       certdb, digestalg, digest,
   1.788 +				       pwfn, pwfn_arg);
   1.789 +    if (cinfo == NULL)
   1.790 +	return NULL;
   1.791 +
   1.792 +    if (SEC_PKCS7IncludeCertChain (cinfo, NULL) != SECSuccess) {
   1.793 +	SEC_PKCS7DestroyContentInfo (cinfo);
   1.794 +	return NULL;
   1.795 +    }
   1.796 +
   1.797 +    /* if the encryption cert and the signing cert differ, then include
   1.798 +     * the encryption cert too.
   1.799 +     */
   1.800 +    /* it is ok to compare the pointers since we ref count, and the same
   1.801 +     * cert will always have the same pointer
   1.802 +     */
   1.803 +    if ( ( ecert != NULL ) && ( ecert != scert ) ) {
   1.804 +	rv = SEC_PKCS7AddCertificate(cinfo, ecert);
   1.805 +	if ( rv != SECSuccess ) {
   1.806 +	    SEC_PKCS7DestroyContentInfo (cinfo);
   1.807 +	    return NULL;
   1.808 +	}
   1.809 +    }
   1.810 +    /*
   1.811 +     * Add the signing time.  But if it fails for some reason,
   1.812 +     * may as well not give up altogether -- just assert.
   1.813 +     */
   1.814 +    rv = SEC_PKCS7AddSigningTime (cinfo);
   1.815 +    PORT_Assert (rv == SECSuccess);
   1.816 +
   1.817 +    /*
   1.818 +     * Add the email profile.  Again, if it fails for some reason,
   1.819 +     * may as well not give up altogether -- just assert.
   1.820 +     */
   1.821 +    rv = smime_add_profile (ecert, cinfo);
   1.822 +    PORT_Assert (rv == SECSuccess);
   1.823 +
   1.824 +    return cinfo;
   1.825 +}

mercurial