1/*
2 * The contents of this file are subject to the Mozilla Public
3 * License Version 1.1 (the "License"); you may not use this file
4 * except in compliance with the License. You may obtain a copy of
5 * the License at http://www.mozilla.org/MPL/
6 *
7 * Software distributed under the License is distributed on an "AS
8 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9 * implied. See the License for the specific language governing
10 * rights and limitations under the License.
11 *
12 * The Original Code is the Netscape security libraries.
13 *
14 * The Initial Developer of the Original Code is Netscape
15 * Communications Corporation.  Portions created by Netscape are
16 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
17 * Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * Alternatively, the contents of this file may be used under the
22 * terms of the GNU General Public License Version 2 or later (the
23 * "GPL"), in which case the provisions of the GPL are applicable
24 * instead of those above.  If you wish to allow use of your
25 * version of this file only under the terms of the GPL and not to
26 * allow others to use your version of this file under the MPL,
27 * indicate your decision by deleting the provisions above and
28 * replace them with the notice and other provisions required by
29 * the GPL.  If you do not delete the provisions above, a recipient
30 * may use your version of this file under either the MPL or the
31 * GPL.
32 */
33
34/*
35 * Stuff specific to S/MIME policy and interoperability.
36 */
37
38#include "cmslocal.h"
39
40#include "secoid.h"
41#include "SecAsn1Item.h"
42#include "cert.h"
43#include "SecSMIMEPriv.h"
44
45#include <security_asn1/secasn1.h>
46#include <security_asn1/secerr.h>
47#include <Security/SecSMIME.h>
48#include <Security/SecKeyPriv.h>
49
50SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate)
51SEC_ASN1_MKSUB(SEC_OctetStringTemplate)
52SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate)
53
54/* various integer's ASN.1 encoding */
55static unsigned char asn1_int40[] = { SEC_ASN1_INTEGER, 0x01, 0x28 };
56static unsigned char asn1_int64[] = { SEC_ASN1_INTEGER, 0x01, 0x40 };
57static unsigned char asn1_int128[] = { SEC_ASN1_INTEGER, 0x02, 0x00, 0x80 };
58
59/* RC2 algorithm parameters (used in smime_cipher_map) */
60static SecAsn1Item param_int40 = { sizeof(asn1_int40), asn1_int40 };
61static SecAsn1Item param_int64 = { sizeof(asn1_int64), asn1_int64 };
62static SecAsn1Item param_int128 = { sizeof(asn1_int128), asn1_int128 };
63
64/*
65 * XXX Would like the "parameters" field to be a SecAsn1Item * , but the
66 * encoder is having trouble with optional pointers to an ANY.  Maybe
67 * once that is fixed, can change this back...
68 */
69typedef struct {
70    SecAsn1Item capabilityID;
71    SecAsn1Item parameters;
72    long cipher;		/* optimization */
73} NSSSMIMECapability;
74
75static const SecAsn1Template NSSSMIMECapabilityTemplate[] = {
76    { SEC_ASN1_SEQUENCE,
77	  0, NULL, sizeof(NSSSMIMECapability) },
78    { SEC_ASN1_OBJECT_ID,
79	  offsetof(NSSSMIMECapability,capabilityID), },
80    { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
81	  offsetof(NSSSMIMECapability,parameters), },
82    { 0, }
83};
84
85static const SecAsn1Template NSSSMIMECapabilitiesTemplate[] = {
86    { SEC_ASN1_SEQUENCE_OF, 0, NSSSMIMECapabilityTemplate }
87};
88
89/*
90 * NSSSMIMEEncryptionKeyPreference - if we find one of these, it needs to prompt us
91 *  to store this and only this certificate permanently for the sender email address.
92 */
93typedef enum {
94    NSSSMIMEEncryptionKeyPref_IssuerSN,
95    NSSSMIMEEncryptionKeyPref_RKeyID,
96    NSSSMIMEEncryptionKeyPref_SubjectKeyID
97} NSSSMIMEEncryptionKeyPrefSelector;
98
99typedef struct {
100    NSSSMIMEEncryptionKeyPrefSelector selector;
101    union {
102	SecCmsIssuerAndSN		*issuerAndSN;
103	SecCmsRecipientKeyIdentifier	*recipientKeyID;
104	SecAsn1Item *subjectKeyID;
105    } id;
106} NSSSMIMEEncryptionKeyPreference;
107
108extern const SecAsn1Template SecCmsRecipientKeyIdentifierTemplate[];
109
110static const SecAsn1Template smime_encryptionkeypref_template[] = {
111    { SEC_ASN1_CHOICE,
112	  offsetof(NSSSMIMEEncryptionKeyPreference,selector), NULL,
113	  sizeof(NSSSMIMEEncryptionKeyPreference) },
114    { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
115	  offsetof(NSSSMIMEEncryptionKeyPreference,id.issuerAndSN),
116	  SEC_ASN1_SUB(SecCmsIssuerAndSNTemplate),
117	  NSSSMIMEEncryptionKeyPref_IssuerSN },
118    { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | 1,
119	  offsetof(NSSSMIMEEncryptionKeyPreference,id.recipientKeyID),
120	  SecCmsRecipientKeyIdentifierTemplate,
121	  NSSSMIMEEncryptionKeyPref_IssuerSN },
122    { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2,
123	  offsetof(NSSSMIMEEncryptionKeyPreference,id.subjectKeyID),
124	  SEC_ASN1_SUB(kSecAsn1OctetStringTemplate),
125	  NSSSMIMEEncryptionKeyPref_SubjectKeyID },
126    { 0, }
127};
128
129/* smime_cipher_map - map of SMIME symmetric "ciphers" to algtag & parameters */
130typedef struct {
131    unsigned long cipher;
132    SECOidTag algtag;
133    SecAsn1Item *parms;
134    Boolean enabled;	/* in the user's preferences */
135    Boolean allowed;	/* per export policy */
136} smime_cipher_map_entry;
137
138/* global: list of supported SMIME symmetric ciphers, ordered roughly by increasing strength */
139static smime_cipher_map_entry smime_cipher_map[] = {
140/*    cipher			algtag			parms		enabled  allowed */
141/*    ---------------------------------------------------------------------------------- */
142    { SMIME_RC2_CBC_40,		SEC_OID_RC2_CBC,	&param_int40,	PR_FALSE, PR_FALSE },
143    { SMIME_DES_CBC_56,		SEC_OID_DES_CBC,	NULL,			PR_TRUE, PR_FALSE },
144    { SMIME_RC2_CBC_64,		SEC_OID_RC2_CBC,	&param_int64,	PR_FALSE, PR_FALSE },
145    { SMIME_RC2_CBC_128,	SEC_OID_RC2_CBC,	&param_int128,	PR_FALSE, PR_FALSE },
146    { SMIME_DES_EDE3_168,	SEC_OID_DES_EDE3_CBC,	NULL,		PR_TRUE, PR_TRUE },
147    { SMIME_AES_CBC_128,	SEC_OID_AES_128_CBC,	NULL,		PR_TRUE, PR_TRUE },
148    { SMIME_AES_CBC_192,	SEC_OID_AES_192_CBC,	NULL,		PR_TRUE, PR_TRUE },
149    { SMIME_AES_CBC_256,	SEC_OID_AES_256_CBC,	NULL,		PR_TRUE, PR_TRUE },
150    { SMIME_FORTEZZA,		SEC_OID_FORTEZZA_SKIPJACK, NULL,	PR_TRUE, PR_TRUE }
151};
152static const int smime_cipher_map_count = sizeof(smime_cipher_map) / sizeof(smime_cipher_map_entry);
153
154/*
155 * smime_mapi_by_cipher - find index into smime_cipher_map by cipher
156 */
157static int
158smime_mapi_by_cipher(unsigned long cipher)
159{
160    int i;
161
162    for (i = 0; i < smime_cipher_map_count; i++) {
163	if (smime_cipher_map[i].cipher == cipher)
164	    return i;	/* bingo */
165    }
166    return -1;		/* should not happen if we're consistent, right? */
167}
168
169/*
170 * NSS_SMIME_EnableCipher - this function locally records the user's preference
171 */
172OSStatus
173SecSMIMEEnableCipher(unsigned long which, Boolean on)
174{
175    unsigned long mask;
176    int mapi;
177
178    mask = which & CIPHER_FAMILYID_MASK;
179
180    PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
181    if (mask != CIPHER_FAMILYID_SMIME)
182	/* XXX set an error! */
183    	return SECFailure;
184
185    mapi = smime_mapi_by_cipher(which);
186    if (mapi < 0)
187	/* XXX set an error */
188	return SECFailure;
189
190    /* do we try to turn on a forbidden cipher? */
191    if (!smime_cipher_map[mapi].allowed && on) {
192	PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM);
193	return SECFailure;
194    }
195
196    if (smime_cipher_map[mapi].enabled != on)
197	smime_cipher_map[mapi].enabled = on;
198
199    return SECSuccess;
200}
201
202
203/*
204 * this function locally records the export policy
205 */
206OSStatus
207SecSMIMEAllowCipher(unsigned long which, Boolean on)
208{
209    unsigned long mask;
210    int mapi;
211
212    mask = which & CIPHER_FAMILYID_MASK;
213
214    PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
215    if (mask != CIPHER_FAMILYID_SMIME)
216	/* XXX set an error! */
217    	return SECFailure;
218
219    mapi = smime_mapi_by_cipher(which);
220    if (mapi < 0)
221	/* XXX set an error */
222	return SECFailure;
223
224    if (smime_cipher_map[mapi].allowed != on)
225	smime_cipher_map[mapi].allowed = on;
226
227    return SECSuccess;
228}
229
230/*
231 * Based on the given algorithm (including its parameters, in some cases!)
232 * and the given key (may or may not be inspected, depending on the
233 * algorithm), find the appropriate policy algorithm specification
234 * and return it.  If no match can be made, -1 is returned.
235 */
236static OSStatus
237nss_smime_get_cipher_for_alg_and_key(SECAlgorithmID *algid, SecSymmetricKeyRef key, unsigned long *cipher)
238{
239    SECOidTag algtag;
240    CFIndex keylen_bits;
241    unsigned long c;
242
243    algtag = SECOID_GetAlgorithmTag(algid);
244    switch (algtag) {
245    case SEC_OID_RC2_CBC:
246#if USE_CDSA_CRYPTO
247	if (SecKeyGetStrengthInBits(key, algid, &keylen_bits))
248	    return SECFailure;
249#else
250	keylen_bits = CFDataGetLength((CFDataRef)key) * 8;
251#endif
252	switch (keylen_bits) {
253	case 40:
254	    c = SMIME_RC2_CBC_40;
255	    break;
256	case 64:
257	    c = SMIME_RC2_CBC_64;
258	    break;
259	case 128:
260	    c = SMIME_RC2_CBC_128;
261	    break;
262	default:
263	    return SECFailure;
264	}
265	break;
266    case SEC_OID_DES_CBC:
267	c = SMIME_DES_CBC_56;
268	break;
269    case SEC_OID_FORTEZZA_SKIPJACK:
270	c = SMIME_FORTEZZA;
271	break;
272    case SEC_OID_DES_EDE3_CBC:
273	c = SMIME_DES_EDE3_168;
274	break;
275    case SEC_OID_AES_128_CBC:
276	c = SMIME_AES_CBC_128;
277	break;
278    case SEC_OID_AES_192_CBC:
279	c = SMIME_AES_CBC_192;
280	break;
281    case SEC_OID_AES_256_CBC:
282	c = SMIME_AES_CBC_256;
283	break;
284    default:
285	return SECFailure;
286    }
287    *cipher = c;
288    return SECSuccess;
289}
290
291static Boolean
292nss_smime_cipher_allowed(unsigned long which)
293{
294    int mapi;
295
296    mapi = smime_mapi_by_cipher(which);
297    if (mapi < 0)
298	return PR_FALSE;
299    return smime_cipher_map[mapi].allowed;
300}
301
302Boolean
303SecSMIMEDecryptionAllowed(SECAlgorithmID *algid, SecSymmetricKeyRef key)
304{
305    unsigned long which;
306
307    if (nss_smime_get_cipher_for_alg_and_key(algid, key, &which) != SECSuccess)
308	return PR_FALSE;
309
310    return nss_smime_cipher_allowed(which);
311}
312
313
314/*
315 * NSS_SMIME_EncryptionPossible - check if any encryption is allowed
316 *
317 * This tells whether or not *any* S/MIME encryption can be done,
318 * according to policy.  Callers may use this to do nicer user interface
319 * (say, greying out a checkbox so a user does not even try to encrypt
320 * a message when they are not allowed to) or for any reason they want
321 * to check whether S/MIME encryption (or decryption, for that matter)
322 * may be done.
323 *
324 * It takes no arguments.  The return value is a simple boolean:
325 *   PR_TRUE means encryption (or decryption) is *possible*
326 *	(but may still fail due to other reasons, like because we cannot
327 *	find all the necessary certs, etc.; PR_TRUE is *not* a guarantee)
328 *   PR_FALSE means encryption (or decryption) is not permitted
329 *
330 * There are no errors from this routine.
331 */
332Boolean
333SecSMIMEEncryptionPossible(void)
334{
335    int i;
336
337    for (i = 0; i < smime_cipher_map_count; i++) {
338	if (smime_cipher_map[i].allowed)
339	    return PR_TRUE;
340    }
341    return PR_FALSE;
342}
343
344
345static unsigned long
346nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap)
347{
348    int i;
349    SECOidTag capIDTag;
350
351    /* we need the OIDTag here */
352    capIDTag = SECOID_FindOIDTag(&(cap->capabilityID));
353
354    /* go over all the SMIME ciphers we know and see if we find a match */
355    for (i = 0; i < smime_cipher_map_count; i++) {
356	if (smime_cipher_map[i].algtag != capIDTag)
357	    continue;
358	/*
359	 * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing
360	 * 2 NULLs as equal and NULL and non-NULL as not equal), we could
361	 * use that here instead of all of the following comparison code.
362	 */
363	if (cap->parameters.Data == NULL && smime_cipher_map[i].parms == NULL)
364	    break;	/* both empty: bingo */
365
366	if (cap->parameters.Data != NULL && smime_cipher_map[i].parms != NULL &&
367	    cap->parameters.Length == smime_cipher_map[i].parms->Length &&
368	    PORT_Memcmp (cap->parameters.Data, smime_cipher_map[i].parms->Data,
369			     cap->parameters.Length) == 0)
370	{
371	    break;	/* both not empty, same length & equal content: bingo */
372	}
373    }
374
375    if (i == smime_cipher_map_count)
376	return 0;				/* no match found */
377    else
378	return smime_cipher_map[i].cipher;	/* match found, point to cipher */
379}
380
381/*
382 * smime_choose_cipher - choose a cipher that works for all the recipients
383 *
384 * "scert"  - sender's certificate
385 * "rcerts" - recipient's certificates
386 */
387static long
388smime_choose_cipher(SecCertificateRef scert, SecCertificateRef *rcerts)
389{
390    PRArenaPool *poolp;
391    long cipher;
392    long chosen_cipher;
393    int *cipher_abilities;
394    int *cipher_votes;
395    int weak_mapi;
396    int strong_mapi;
397    int rcount, mapi, max, i;
398#if 1
399    // @@@ We Don't support Fortezza yet.
400    Boolean scert_is_fortezza  = PR_FALSE;
401#else
402    Boolean scert_is_fortezza = (scert == NULL) ? PR_FALSE : PK11_FortezzaHasKEA(scert);
403#endif
404
405    chosen_cipher = SMIME_DES_CBC_56;		/* the default, LCD */
406    weak_mapi = smime_mapi_by_cipher(chosen_cipher);
407
408    poolp = PORT_NewArena (1024);		/* XXX what is right value? */
409    if (poolp == NULL)
410	goto done;
411
412    cipher_abilities = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int));
413    cipher_votes     = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int));
414    if (cipher_votes == NULL || cipher_abilities == NULL)
415	goto done;
416
417    /* If the user has the Fortezza preference turned on, make
418     *  that the strong cipher. Otherwise, use triple-DES. */
419    strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168);
420    if (scert_is_fortezza) {
421	mapi = smime_mapi_by_cipher(SMIME_FORTEZZA);
422	if (mapi >= 0 && smime_cipher_map[mapi].enabled)
423	    strong_mapi = mapi;
424    }
425
426    /* walk all the recipient's certs */
427    for (rcount = 0; rcerts[rcount] != NULL; rcount++) {
428	SecAsn1Item *profile;
429	NSSSMIMECapability **caps;
430	int pref;
431
432	/* the first cipher that matches in the user's SMIME profile gets
433	 * "smime_cipher_map_count" votes; the next one gets "smime_cipher_map_count" - 1
434	 * and so on. If every cipher matches, the last one gets 1 (one) vote */
435	pref = smime_cipher_map_count;
436
437	/* find recipient's SMIME profile */
438	profile = CERT_FindSMimeProfile(rcerts[rcount]);
439
440	if (profile != NULL && profile->Data != NULL && profile->Length > 0) {
441	    /* we have a profile (still DER-encoded) */
442	    caps = NULL;
443	    /* decode it */
444	    if (SEC_ASN1DecodeItem(poolp, &caps, NSSSMIMECapabilitiesTemplate, profile) == SECSuccess &&
445		    caps != NULL)
446	    {
447		/* walk the SMIME capabilities for this recipient */
448		for (i = 0; caps[i] != NULL; i++) {
449		    cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]);
450		    mapi = smime_mapi_by_cipher(cipher);
451		    if (mapi >= 0) {
452			/* found the cipher */
453			cipher_abilities[mapi]++;
454			cipher_votes[mapi] += pref;
455			--pref;
456		    }
457		}
458	    }
459	} else {
460	    /* no profile found - so we can only assume that the user can do
461	     * the mandatory algorithms which is RC2-40 (weak crypto) and 3DES (strong crypto) */
462	    SecPublicKeyRef key;
463	    size_t pklen_bits;
464
465	    /*
466	     * if recipient's public key length is > 512, vote for a strong cipher
467	     * please not that the side effect of this is that if only one recipient
468	     * has an export-level public key, the strong cipher is disabled.
469	     *
470	     * XXX This is probably only good for RSA keys.  What I would
471	     * really like is a function to just say;  Is the public key in
472	     * this cert an export-length key?  Then I would not have to
473	     * know things like the value 512, or the kind of key, or what
474	     * a subjectPublicKeyInfo is, etc.
475	     */
476	    key = CERT_ExtractPublicKey(rcerts[rcount]);
477	    pklen_bits = 0;
478	    if (key != NULL) {
479#if USE_CDSA_CRYPTO
480		SecKeyGetStrengthInBits(key, NULL, &pklen_bits);
481#else
482                pklen_bits = SecKeyGetSize(key, kSecKeyKeySizeInBits);
483#endif
484		SECKEY_DestroyPublicKey (key);
485	    }
486
487	    if (pklen_bits > 512) {
488		/* cast votes for the strong algorithm */
489		cipher_abilities[strong_mapi]++;
490		cipher_votes[strong_mapi] += pref;
491		pref--;
492	    }
493
494	    /* always cast (possibly less) votes for the weak algorithm */
495	    cipher_abilities[weak_mapi]++;
496	    cipher_votes[weak_mapi] += pref;
497	}
498	if (profile != NULL)
499	    SECITEM_FreeItem(profile, PR_TRUE);
500    }
501
502    /* find cipher that is agreeable by all recipients and that has the most votes */
503    max = 0;
504    for (mapi = 0; mapi < smime_cipher_map_count; mapi++) {
505	/* if not all of the recipients can do this, forget it */
506	if (cipher_abilities[mapi] != rcount)
507	    continue;
508	/* if cipher is not enabled or not allowed by policy, forget it */
509	if (!smime_cipher_map[mapi].enabled || !smime_cipher_map[mapi].allowed)
510	    continue;
511	/* if we're not doing fortezza, but the cipher is fortezza, forget it */
512	if (!scert_is_fortezza  && (smime_cipher_map[mapi].cipher == SMIME_FORTEZZA))
513	    continue;
514	/* now see if this one has more votes than the last best one */
515	if (cipher_votes[mapi] >= max) {
516	    /* if equal number of votes, prefer the ones further down in the list */
517	    /* with the expectation that these are higher rated ciphers */
518	    chosen_cipher = smime_cipher_map[mapi].cipher;
519	    max = cipher_votes[mapi];
520	}
521    }
522    /* if no common cipher was found, chosen_cipher stays at the default */
523
524done:
525    if (poolp != NULL)
526	PORT_FreeArena (poolp, PR_FALSE);
527
528    return chosen_cipher;
529}
530
531/*
532 * XXX This is a hack for now to satisfy our current interface.
533 * Eventually, with more parameters needing to be specified, just
534 * looking up the keysize is not going to be sufficient.
535 */
536static int
537smime_keysize_by_cipher (unsigned long which)
538{
539    int keysize;
540
541    switch (which) {
542      case SMIME_RC2_CBC_40:
543	keysize = 40;
544	break;
545      case SMIME_RC2_CBC_64:
546	keysize = 64;
547	break;
548      case SMIME_RC2_CBC_128:
549	keysize = 128;
550	break;
551      case SMIME_DES_CBC_56:
552	keysize = 64;
553	break;
554      case SMIME_DES_EDE3_168:
555	keysize = 192;
556	break;
557      case SMIME_FORTEZZA:
558	/*
559	 * This is special; since the key size is fixed, we actually
560	 * want to *avoid* specifying a key size.
561	 */
562	keysize = 0;
563	break;
564      default:
565	keysize = -1;
566	break;
567    }
568
569    return keysize;
570}
571
572/*
573 * SecSMIMEFindBulkAlgForRecipients - find bulk algorithm suitable for all recipients
574 *
575 * it would be great for UI purposes if there would be a way to find out which recipients
576 * prevented a strong cipher from being used...
577 */
578OSStatus
579SecSMIMEFindBulkAlgForRecipients(SecCertificateRef *rcerts, SECOidTag *bulkalgtag, int *keysize)
580{
581    unsigned long cipher;
582    int mapi;
583
584    cipher = smime_choose_cipher(NULL, rcerts);
585    mapi = smime_mapi_by_cipher(cipher);
586
587    *bulkalgtag = smime_cipher_map[mapi].algtag;
588    *keysize = smime_keysize_by_cipher(smime_cipher_map[mapi].cipher);
589
590    return SECSuccess;
591}
592
593/*
594 * SecSMIMECreateSMIMECapabilities - get S/MIME capabilities for this instance of NSS
595 *
596 * scans the list of allowed and enabled ciphers and construct a PKCS9-compliant
597 * S/MIME capabilities attribute value.
598 *
599 * XXX Please note that, in contradiction to RFC2633 2.5.2, the capabilities only include
600 * symmetric ciphers, NO signature algorithms or key encipherment algorithms.
601 *
602 * "poolp" - arena pool to create the S/MIME capabilities data on
603 * "dest" - SecAsn1Item to put the data in
604 * "includeFortezzaCiphers" - PR_TRUE if fortezza ciphers should be included
605 */
606OSStatus
607SecSMIMECreateSMIMECapabilities(PLArenaPool *poolp, SecAsn1Item *dest, Boolean includeFortezzaCiphers)
608{
609    NSSSMIMECapability *cap;
610    NSSSMIMECapability **smime_capabilities;
611    smime_cipher_map_entry *map;
612    SECOidData *oiddata;
613    SecAsn1Item *dummy;
614    int i, capIndex;
615
616    /* if we have an old NSSSMIMECapability array, we'll reuse it (has the right size) */
617    /* smime_cipher_map_count + 1 is an upper bound - we might end up with less */
618    smime_capabilities = (NSSSMIMECapability **)PORT_ZAlloc((smime_cipher_map_count + 1)
619				      * sizeof(NSSSMIMECapability *));
620    if (smime_capabilities == NULL)
621	return SECFailure;
622
623    capIndex = 0;
624
625    /* Add all the symmetric ciphers
626     * We walk the cipher list backwards, as it is ordered by increasing strength,
627     * we prefer the stronger cipher over a weaker one, and we have to list the
628     * preferred algorithm first */
629    for (i = smime_cipher_map_count - 1; i >= 0; i--) {
630	/* Find the corresponding entry in the cipher map. */
631	map = &(smime_cipher_map[i]);
632	if (!map->enabled)
633	    continue;
634
635	/* If we're using a non-Fortezza cert, only advertise non-Fortezza
636	   capabilities. (We advertise all capabilities if we have a
637	   Fortezza cert.) */
638	if ((!includeFortezzaCiphers) && (map->cipher == SMIME_FORTEZZA))
639	    continue;
640
641	/* get next SMIME capability */
642	cap = (NSSSMIMECapability *)PORT_ZAlloc(sizeof(NSSSMIMECapability));
643	if (cap == NULL)
644	    break;
645	smime_capabilities[capIndex++] = cap;
646
647	oiddata = SECOID_FindOIDByTag(map->algtag);
648	if (oiddata == NULL)
649	    break;
650
651	cap->capabilityID.Data = oiddata->oid.Data;
652	cap->capabilityID.Length = oiddata->oid.Length;
653	cap->parameters.Data = map->parms ? map->parms->Data : NULL;
654	cap->parameters.Length = map->parms ? map->parms->Length : 0;
655	cap->cipher = smime_cipher_map[i].cipher;
656    }
657
658    /* XXX add signature algorithms */
659    /* XXX add key encipherment algorithms */
660
661    smime_capabilities[capIndex] = NULL;	/* last one - now encode */
662    dummy = SEC_ASN1EncodeItem(poolp, dest, &smime_capabilities, NSSSMIMECapabilitiesTemplate);
663
664    /* now that we have the proper encoded SMIMECapabilities (or not),
665     * free the work data */
666    for (i = 0; smime_capabilities[i] != NULL; i++)
667	PORT_Free(smime_capabilities[i]);
668    PORT_Free(smime_capabilities);
669
670    return (dummy == NULL) ? SECFailure : SECSuccess;
671}
672
673/*
674 * SecSMIMECreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value
675 *
676 * "poolp" - arena pool to create the attr value on
677 * "dest" - SecAsn1Item to put the data in
678 * "cert" - certificate that should be marked as preferred encryption key
679 *          cert is expected to have been verified for EmailRecipient usage.
680 */
681OSStatus
682SecSMIMECreateSMIMEEncKeyPrefs(PLArenaPool *poolp, SecAsn1Item *dest, SecCertificateRef cert)
683{
684    NSSSMIMEEncryptionKeyPreference ekp;
685    SecAsn1Item *dummy = NULL;
686    PLArenaPool *tmppoolp = NULL;
687
688    if (cert == NULL)
689	goto loser;
690
691    tmppoolp = PORT_NewArena(1024);
692    if (tmppoolp == NULL)
693	goto loser;
694
695    /* XXX hardcoded IssuerSN choice for now */
696    ekp.selector = NSSSMIMEEncryptionKeyPref_IssuerSN;
697    ekp.id.issuerAndSN = CERT_GetCertIssuerAndSN(tmppoolp, cert);
698    if (ekp.id.issuerAndSN == NULL)
699	goto loser;
700
701    dummy = SEC_ASN1EncodeItem(poolp, dest, &ekp, smime_encryptionkeypref_template);
702
703loser:
704    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
705
706    return (dummy == NULL) ? SECFailure : SECSuccess;
707}
708
709/*
710 * SecSMIMECreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value using MS oid
711 *
712 * "poolp" - arena pool to create the attr value on
713 * "dest" - SecAsn1Item to put the data in
714 * "cert" - certificate that should be marked as preferred encryption key
715 *          cert is expected to have been verified for EmailRecipient usage.
716 */
717OSStatus
718SecSMIMECreateMSSMIMEEncKeyPrefs(PLArenaPool *poolp, SecAsn1Item *dest, SecCertificateRef cert)
719{
720    SecAsn1Item *dummy = NULL;
721    PLArenaPool *tmppoolp = NULL;
722    SecCmsIssuerAndSN *isn;
723
724    if (cert == NULL)
725	goto loser;
726
727    tmppoolp = PORT_NewArena(1024);
728    if (tmppoolp == NULL)
729	goto loser;
730
731    isn = CERT_GetCertIssuerAndSN(tmppoolp, cert);
732    if (isn == NULL)
733	goto loser;
734
735    dummy = SEC_ASN1EncodeItem(poolp, dest, isn, SEC_ASN1_GET(SecCmsIssuerAndSNTemplate));
736
737loser:
738    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
739
740    return (dummy == NULL) ? SECFailure : SECSuccess;
741}
742
743#if 0
744/*
745 * SecSMIMEGetCertFromEncryptionKeyPreference -
746 *				find cert marked by EncryptionKeyPreference attribute
747 *
748 * "keychainOrArray" - handle for the cert database to look in
749 * "DERekp" - DER-encoded value of S/MIME Encryption Key Preference attribute
750 *
751 * if certificate is supposed to be found among the message's included certificates,
752 * they are assumed to have been imported already.
753 */
754SecCertificateRef
755SecSMIMEGetCertFromEncryptionKeyPreference(SecKeychainRef keychainOrArray, SecAsn1Item *DERekp)
756{
757    PLArenaPool *tmppoolp = NULL;
758    SecCertificateRef cert = NULL;
759    NSSSMIMEEncryptionKeyPreference ekp;
760
761    tmppoolp = PORT_NewArena(1024);
762    if (tmppoolp == NULL)
763	return NULL;
764
765    /* decode DERekp */
766    if (SEC_ASN1DecodeItem(tmppoolp, &ekp, smime_encryptionkeypref_template, DERekp) != SECSuccess)
767	goto loser;
768
769    /* find cert */
770    switch (ekp.selector) {
771    case NSSSMIMEEncryptionKeyPref_IssuerSN:
772	cert = CERT_FindCertByIssuerAndSN(keychainOrArray, ekp.id.issuerAndSN);
773	break;
774    case NSSSMIMEEncryptionKeyPref_RKeyID:
775    case NSSSMIMEEncryptionKeyPref_SubjectKeyID:
776	/* XXX not supported yet - we need to be able to look up certs by SubjectKeyID */
777	break;
778    default:
779	PORT_Assert(0);
780    }
781loser:
782    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
783
784    return cert;
785}
786#endif
787
788#if 0
789extern const char __nss_smime_rcsid[];
790extern const char __nss_smime_sccsid[];
791
792Boolean
793NSSSMIME_VersionCheck(const char *importedVersion)
794{
795#if 1
796    return PR_TRUE;
797#else
798    /*
799     * This is the secret handshake algorithm.
800     *
801     * This release has a simple version compatibility
802     * check algorithm.  This release is not backward
803     * compatible with previous major releases.  It is
804     * not compatible with future major, minor, or
805     * patch releases.
806     */
807    volatile char c; /* force a reference that won't get optimized away */
808
809    c = __nss_smime_rcsid[0] + __nss_smime_sccsid[0];
810
811    return NSS_VersionCheck(importedVersion);
812#endif
813}
814#endif /* 0 */
815
816