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 "secitem.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 CSSM_DATA param_int40 = { sizeof(asn1_int40), asn1_int40 };
61static CSSM_DATA param_int64 = { sizeof(asn1_int64), asn1_int64 };
62static CSSM_DATA param_int128 = { sizeof(asn1_int128), asn1_int128 };
63
64/*
65 * XXX Would like the "parameters" field to be a CSSM_DATA_PTR , 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    CSSM_DATA capabilityID;
71    CSSM_DATA 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	CSSM_DATA_PTR 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    CSSM_DATA_PTR 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_TRUE, PR_TRUE },
143    { SMIME_DES_CBC_56,		SEC_OID_DES_CBC,	NULL,		PR_TRUE, PR_TRUE },
144    { SMIME_RC2_CBC_64,		SEC_OID_RC2_CBC,	&param_int64,	PR_TRUE, PR_TRUE },
145    { SMIME_RC2_CBC_128,	SEC_OID_RC2_CBC,	&param_int128,	PR_TRUE, PR_TRUE },
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_FORTEZZA,		SEC_OID_FORTEZZA_SKIPJACK, NULL,	PR_TRUE, PR_TRUE }
149};
150static const int smime_cipher_map_count = sizeof(smime_cipher_map) / sizeof(smime_cipher_map_entry);
151
152/*
153 * smime_mapi_by_cipher - find index into smime_cipher_map by cipher
154 */
155static int
156smime_mapi_by_cipher(unsigned long cipher)
157{
158    int i;
159
160    for (i = 0; i < smime_cipher_map_count; i++) {
161	if (smime_cipher_map[i].cipher == cipher)
162	    return i;	/* bingo */
163    }
164    return -1;		/* should not happen if we're consistent, right? */
165}
166
167/*
168 * NSS_SMIME_EnableCipher - this function locally records the user's preference
169 */
170OSStatus
171SecSMIMEEnableCipher(uint32 which, Boolean on)
172{
173    unsigned long mask;
174    int mapi;
175
176    mask = which & CIPHER_FAMILYID_MASK;
177
178    PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
179    if (mask != CIPHER_FAMILYID_SMIME)
180	/* XXX set an error! */
181    	return SECFailure;
182
183    mapi = smime_mapi_by_cipher(which);
184    if (mapi < 0)
185	/* XXX set an error */
186	return SECFailure;
187
188    /* do we try to turn on a forbidden cipher? */
189    if (!smime_cipher_map[mapi].allowed && on) {
190	PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM);
191	return SECFailure;
192    }
193
194    if (smime_cipher_map[mapi].enabled != on)
195	smime_cipher_map[mapi].enabled = on;
196
197    return SECSuccess;
198}
199
200
201/*
202 * this function locally records the export policy
203 */
204OSStatus
205SecSMIMEAllowCipher(uint32 which, Boolean on)
206{
207    unsigned long mask;
208    int mapi;
209
210    mask = which & CIPHER_FAMILYID_MASK;
211
212    PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
213    if (mask != CIPHER_FAMILYID_SMIME)
214	/* XXX set an error! */
215    	return SECFailure;
216
217    mapi = smime_mapi_by_cipher(which);
218    if (mapi < 0)
219	/* XXX set an error */
220	return SECFailure;
221
222    if (smime_cipher_map[mapi].allowed != on)
223	smime_cipher_map[mapi].allowed = on;
224
225    return SECSuccess;
226}
227
228/*
229 * Based on the given algorithm (including its parameters, in some cases!)
230 * and the given key (may or may not be inspected, depending on the
231 * algorithm), find the appropriate policy algorithm specification
232 * and return it.  If no match can be made, -1 is returned.
233 */
234static OSStatus
235nss_smime_get_cipher_for_alg_and_key(SECAlgorithmID *algid, SecSymmetricKeyRef key, unsigned long *cipher)
236{
237    SECOidTag algtag;
238    unsigned int keylen_bits;
239    unsigned long c;
240
241    algtag = SECOID_GetAlgorithmTag(algid);
242    switch (algtag) {
243    case SEC_OID_RC2_CBC:
244	if (SecKeyGetStrengthInBits(key, algid, &keylen_bits))
245	    return SECFailure;
246	switch (keylen_bits) {
247	case 40:
248	    c = SMIME_RC2_CBC_40;
249	    break;
250	case 64:
251	    c = SMIME_RC2_CBC_64;
252	    break;
253	case 128:
254	    c = SMIME_RC2_CBC_128;
255	    break;
256	default:
257	    return SECFailure;
258	}
259	break;
260    case SEC_OID_DES_CBC:
261	c = SMIME_DES_CBC_56;
262	break;
263    case SEC_OID_DES_EDE3_CBC:
264	c = SMIME_DES_EDE3_168;
265	break;
266    case SEC_OID_AES_128_CBC:
267	c = SMIME_AES_CBC_128;
268	break;
269    case SEC_OID_FORTEZZA_SKIPJACK:
270	c = SMIME_FORTEZZA;
271	break;
272    default:
273	return SECFailure;
274    }
275    *cipher = c;
276    return SECSuccess;
277}
278
279static Boolean
280nss_smime_cipher_allowed(unsigned long which)
281{
282    int mapi;
283
284    mapi = smime_mapi_by_cipher(which);
285    if (mapi < 0)
286	return PR_FALSE;
287    return smime_cipher_map[mapi].allowed;
288}
289
290Boolean
291SecSMIMEDecryptionAllowed(SECAlgorithmID *algid, SecSymmetricKeyRef key)
292{
293    unsigned long which;
294
295    if (nss_smime_get_cipher_for_alg_and_key(algid, key, &which) != SECSuccess)
296	return PR_FALSE;
297
298    return nss_smime_cipher_allowed(which);
299}
300
301
302/*
303 * NSS_SMIME_EncryptionPossible - check if any encryption is allowed
304 *
305 * This tells whether or not *any* S/MIME encryption can be done,
306 * according to policy.  Callers may use this to do nicer user interface
307 * (say, greying out a checkbox so a user does not even try to encrypt
308 * a message when they are not allowed to) or for any reason they want
309 * to check whether S/MIME encryption (or decryption, for that matter)
310 * may be done.
311 *
312 * It takes no arguments.  The return value is a simple boolean:
313 *   PR_TRUE means encryption (or decryption) is *possible*
314 *	(but may still fail due to other reasons, like because we cannot
315 *	find all the necessary certs, etc.; PR_TRUE is *not* a guarantee)
316 *   PR_FALSE means encryption (or decryption) is not permitted
317 *
318 * There are no errors from this routine.
319 */
320Boolean
321SecSMIMEEncryptionPossible(void)
322{
323    int i;
324
325    for (i = 0; i < smime_cipher_map_count; i++) {
326	if (smime_cipher_map[i].allowed)
327	    return PR_TRUE;
328    }
329    return PR_FALSE;
330}
331
332
333static unsigned long
334nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap)
335{
336    int i;
337    SECOidTag capIDTag;
338
339    /* we need the OIDTag here */
340    capIDTag = SECOID_FindOIDTag(&(cap->capabilityID));
341
342    /* go over all the SMIME ciphers we know and see if we find a match */
343    for (i = 0; i < smime_cipher_map_count; i++) {
344	if (smime_cipher_map[i].algtag != capIDTag)
345	    continue;
346	/*
347	 * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing
348	 * 2 NULLs as equal and NULL and non-NULL as not equal), we could
349	 * use that here instead of all of the following comparison code.
350	 */
351	if (cap->parameters.Data == NULL && smime_cipher_map[i].parms == NULL)
352	    break;	/* both empty: bingo */
353
354	if (cap->parameters.Data != NULL && smime_cipher_map[i].parms != NULL &&
355	    cap->parameters.Length == smime_cipher_map[i].parms->Length &&
356	    PORT_Memcmp (cap->parameters.Data, smime_cipher_map[i].parms->Data,
357			     cap->parameters.Length) == 0)
358	{
359	    break;	/* both not empty, same length & equal content: bingo */
360	}
361    }
362
363    if (i == smime_cipher_map_count)
364	return 0;				/* no match found */
365    else
366	return smime_cipher_map[i].cipher;	/* match found, point to cipher */
367}
368
369/*
370 * smime_choose_cipher - choose a cipher that works for all the recipients
371 *
372 * "scert"  - sender's certificate
373 * "rcerts" - recipient's certificates
374 */
375static long
376smime_choose_cipher(SecCertificateRef scert, SecCertificateRef *rcerts)
377{
378    PRArenaPool *poolp;
379    long cipher;
380    long chosen_cipher;
381    int *cipher_abilities;
382    int *cipher_votes;
383    int weak_mapi;
384    int strong_mapi;
385    int rcount, mapi, max, i;
386#if 1
387    // @@@ We Don't support Fortezza yet.
388    Boolean scert_is_fortezza  = PR_FALSE;
389#else
390    Boolean scert_is_fortezza = (scert == NULL) ? PR_FALSE : PK11_FortezzaHasKEA(scert);
391#endif
392
393    chosen_cipher = SMIME_RC2_CBC_40;		/* the default, LCD */
394    weak_mapi = smime_mapi_by_cipher(chosen_cipher);
395
396    poolp = PORT_NewArena (1024);		/* XXX what is right value? */
397    if (poolp == NULL)
398	goto done;
399
400    cipher_abilities = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int));
401    cipher_votes     = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int));
402    if (cipher_votes == NULL || cipher_abilities == NULL)
403	goto done;
404
405    /* If the user has the Fortezza preference turned on, make
406     *  that the strong cipher. Otherwise, use triple-DES. */
407    strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168);
408    if (scert_is_fortezza) {
409	mapi = smime_mapi_by_cipher(SMIME_FORTEZZA);
410	if (mapi >= 0 && smime_cipher_map[mapi].enabled)
411	    strong_mapi = mapi;
412    }
413
414    /* walk all the recipient's certs */
415    for (rcount = 0; rcerts[rcount] != NULL; rcount++) {
416	CSSM_DATA_PTR profile;
417	NSSSMIMECapability **caps;
418	int pref;
419
420	/* the first cipher that matches in the user's SMIME profile gets
421	 * "smime_cipher_map_count" votes; the next one gets "smime_cipher_map_count" - 1
422	 * and so on. If every cipher matches, the last one gets 1 (one) vote */
423	pref = smime_cipher_map_count;
424
425	/* find recipient's SMIME profile */
426	profile = CERT_FindSMimeProfile(rcerts[rcount]);
427
428	if (profile != NULL && profile->Data != NULL && profile->Length > 0) {
429	    /* we have a profile (still DER-encoded) */
430	    caps = NULL;
431	    /* decode it */
432	    if (SEC_ASN1DecodeItem(poolp, &caps, NSSSMIMECapabilitiesTemplate, profile) == SECSuccess &&
433		    caps != NULL)
434	    {
435		/* walk the SMIME capabilities for this recipient */
436		for (i = 0; caps[i] != NULL; i++) {
437		    cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]);
438		    mapi = smime_mapi_by_cipher(cipher);
439		    if (mapi >= 0) {
440			/* found the cipher */
441			cipher_abilities[mapi]++;
442			cipher_votes[mapi] += pref;
443			--pref;
444		    }
445		}
446	    }
447	} else {
448	    /* no profile found - so we can only assume that the user can do
449	     * the mandatory algorithms which is RC2-40 (weak crypto) and 3DES (strong crypto) */
450	    SecPublicKeyRef key;
451	    unsigned int pklen_bits;
452
453	    /*
454	     * if recipient's public key length is > 512, vote for a strong cipher
455	     * please not that the side effect of this is that if only one recipient
456	     * has an export-level public key, the strong cipher is disabled.
457	     *
458	     * XXX This is probably only good for RSA keys.  What I would
459	     * really like is a function to just say;  Is the public key in
460	     * this cert an export-length key?  Then I would not have to
461	     * know things like the value 512, or the kind of key, or what
462	     * a subjectPublicKeyInfo is, etc.
463	     */
464	    key = CERT_ExtractPublicKey(rcerts[rcount]);
465	    pklen_bits = 0;
466	    if (key != NULL) {
467		SecKeyGetStrengthInBits(key, NULL, &pklen_bits);
468		SECKEY_DestroyPublicKey (key);
469	    }
470
471	    if (pklen_bits > 512) {
472		/* cast votes for the strong algorithm */
473		cipher_abilities[strong_mapi]++;
474		cipher_votes[strong_mapi] += pref;
475		pref--;
476	    }
477
478	    /* always cast (possibly less) votes for the weak algorithm */
479	    cipher_abilities[weak_mapi]++;
480	    cipher_votes[weak_mapi] += pref;
481	}
482	if (profile != NULL)
483	    SECITEM_FreeItem(profile, PR_TRUE);
484    }
485
486    /* find cipher that is agreeable by all recipients and that has the most votes */
487    max = 0;
488    for (mapi = 0; mapi < smime_cipher_map_count; mapi++) {
489	/* if not all of the recipients can do this, forget it */
490	if (cipher_abilities[mapi] != rcount)
491	    continue;
492	/* if cipher is not enabled or not allowed by policy, forget it */
493	if (!smime_cipher_map[mapi].enabled || !smime_cipher_map[mapi].allowed)
494	    continue;
495	/* if we're not doing fortezza, but the cipher is fortezza, forget it */
496	if (!scert_is_fortezza  && (smime_cipher_map[mapi].cipher == SMIME_FORTEZZA))
497	    continue;
498	/* now see if this one has more votes than the last best one */
499	if (cipher_votes[mapi] >= max) {
500	    /* if equal number of votes, prefer the ones further down in the list */
501	    /* with the expectation that these are higher rated ciphers */
502	    chosen_cipher = smime_cipher_map[mapi].cipher;
503	    max = cipher_votes[mapi];
504	}
505    }
506    /* if no common cipher was found, chosen_cipher stays at the default */
507
508done:
509    if (poolp != NULL)
510	PORT_FreeArena (poolp, PR_FALSE);
511
512    return chosen_cipher;
513}
514
515/*
516 * XXX This is a hack for now to satisfy our current interface.
517 * Eventually, with more parameters needing to be specified, just
518 * looking up the keysize is not going to be sufficient.
519 */
520static int
521smime_keysize_by_cipher (unsigned long which)
522{
523    int keysize;
524
525    switch (which) {
526      case SMIME_RC2_CBC_40:
527	keysize = 40;
528	break;
529      case SMIME_RC2_CBC_64:
530	keysize = 64;
531	break;
532      case SMIME_RC2_CBC_128:
533      case SMIME_AES_CBC_128:
534	keysize = 128;
535	break;
536      case SMIME_DES_CBC_56:
537	keysize = 64;
538	break;
539      case SMIME_DES_EDE3_168:
540	keysize = 192;
541	break;
542      case SMIME_FORTEZZA:
543	/*
544	 * This is special; since the key size is fixed, we actually
545	 * want to *avoid* specifying a key size.
546	 */
547	keysize = 0;
548	break;
549      default:
550	keysize = -1;
551	break;
552    }
553
554    return keysize;
555}
556
557/*
558 * SecSMIMEFindBulkAlgForRecipients - find bulk algorithm suitable for all recipients
559 *
560 * it would be great for UI purposes if there would be a way to find out which recipients
561 * prevented a strong cipher from being used...
562 */
563OSStatus
564SecSMIMEFindBulkAlgForRecipients(SecCertificateRef *rcerts, SECOidTag *bulkalgtag, int *keysize)
565{
566    unsigned long cipher;
567    int mapi;
568
569    cipher = smime_choose_cipher(NULL, rcerts);
570    mapi = smime_mapi_by_cipher(cipher);
571
572    *bulkalgtag = smime_cipher_map[mapi].algtag;
573    *keysize = smime_keysize_by_cipher(smime_cipher_map[mapi].cipher);
574
575    return SECSuccess;
576}
577
578/*
579 * SecSMIMECreateSMIMECapabilities - get S/MIME capabilities for this instance of NSS
580 *
581 * scans the list of allowed and enabled ciphers and construct a PKCS9-compliant
582 * S/MIME capabilities attribute value.
583 *
584 * XXX Please note that, in contradiction to RFC2633 2.5.2, the capabilities only include
585 * symmetric ciphers, NO signature algorithms or key encipherment algorithms.
586 *
587 * "poolp" - arena pool to create the S/MIME capabilities data on
588 * "dest" - CSSM_DATA to put the data in
589 * "includeFortezzaCiphers" - PR_TRUE if fortezza ciphers should be included
590 */
591OSStatus
592SecSMIMECreateSMIMECapabilities(SecArenaPoolRef pool, CSSM_DATA_PTR dest, Boolean includeFortezzaCiphers)
593{
594    PLArenaPool *poolp = (PLArenaPool *)pool;
595    NSSSMIMECapability *cap;
596    NSSSMIMECapability **smime_capabilities;
597    smime_cipher_map_entry *map;
598    SECOidData *oiddata;
599    CSSM_DATA_PTR dummy;
600    int i, capIndex;
601
602    /* if we have an old NSSSMIMECapability array, we'll reuse it (has the right size) */
603    /* smime_cipher_map_count + 1 is an upper bound - we might end up with less */
604    smime_capabilities = (NSSSMIMECapability **)PORT_ZAlloc((smime_cipher_map_count + 1)
605				      * sizeof(NSSSMIMECapability *));
606    if (smime_capabilities == NULL)
607	return SECFailure;
608
609    capIndex = 0;
610
611    /* Add all the symmetric ciphers
612     * We walk the cipher list backwards, as it is ordered by increasing strength,
613     * we prefer the stronger cipher over a weaker one, and we have to list the
614     * preferred algorithm first */
615    for (i = smime_cipher_map_count - 1; i >= 0; i--) {
616	/* Find the corresponding entry in the cipher map. */
617	map = &(smime_cipher_map[i]);
618	if (!map->enabled)
619	    continue;
620
621	/* If we're using a non-Fortezza cert, only advertise non-Fortezza
622	   capabilities. (We advertise all capabilities if we have a
623	   Fortezza cert.) */
624	if ((!includeFortezzaCiphers) && (map->cipher == SMIME_FORTEZZA))
625	    continue;
626
627	/* get next SMIME capability */
628	cap = (NSSSMIMECapability *)PORT_ZAlloc(sizeof(NSSSMIMECapability));
629	if (cap == NULL)
630	    break;
631	smime_capabilities[capIndex++] = cap;
632
633	oiddata = SECOID_FindOIDByTag(map->algtag);
634	if (oiddata == NULL)
635	    break;
636
637	cap->capabilityID.Data = oiddata->oid.Data;
638	cap->capabilityID.Length = oiddata->oid.Length;
639	cap->parameters.Data = map->parms ? map->parms->Data : NULL;
640	cap->parameters.Length = map->parms ? map->parms->Length : 0;
641	cap->cipher = smime_cipher_map[i].cipher;
642    }
643
644    /* XXX add signature algorithms */
645    /* XXX add key encipherment algorithms */
646
647    smime_capabilities[capIndex] = NULL;	/* last one - now encode */
648    dummy = SEC_ASN1EncodeItem(poolp, dest, &smime_capabilities, NSSSMIMECapabilitiesTemplate);
649
650    /* now that we have the proper encoded SMIMECapabilities (or not),
651     * free the work data */
652    for (i = 0; smime_capabilities[i] != NULL; i++)
653	PORT_Free(smime_capabilities[i]);
654    PORT_Free(smime_capabilities);
655
656    return (dummy == NULL) ? SECFailure : SECSuccess;
657}
658
659/*
660 * SecSMIMECreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value
661 *
662 * "poolp" - arena pool to create the attr value on
663 * "dest" - CSSM_DATA to put the data in
664 * "cert" - certificate that should be marked as preferred encryption key
665 *          cert is expected to have been verified for EmailRecipient usage.
666 */
667OSStatus
668SecSMIMECreateSMIMEEncKeyPrefs(SecArenaPoolRef pool, CSSM_DATA_PTR dest, SecCertificateRef cert)
669{
670    PLArenaPool *poolp = (PLArenaPool *)pool;
671    NSSSMIMEEncryptionKeyPreference ekp;
672    CSSM_DATA_PTR dummy = NULL;
673    PLArenaPool *tmppoolp = NULL;
674
675    if (cert == NULL)
676	goto loser;
677
678    tmppoolp = PORT_NewArena(1024);
679    if (tmppoolp == NULL)
680	goto loser;
681
682    /* XXX hardcoded IssuerSN choice for now */
683    ekp.selector = NSSSMIMEEncryptionKeyPref_IssuerSN;
684    ekp.id.issuerAndSN = CERT_GetCertIssuerAndSN(tmppoolp, cert);
685    if (ekp.id.issuerAndSN == NULL)
686	goto loser;
687
688    dummy = SEC_ASN1EncodeItem(poolp, dest, &ekp, smime_encryptionkeypref_template);
689
690loser:
691    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
692
693    return (dummy == NULL) ? SECFailure : SECSuccess;
694}
695
696/*
697 * SecSMIMECreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value using MS oid
698 *
699 * "poolp" - arena pool to create the attr value on
700 * "dest" - CSSM_DATA to put the data in
701 * "cert" - certificate that should be marked as preferred encryption key
702 *          cert is expected to have been verified for EmailRecipient usage.
703 */
704OSStatus
705SecSMIMECreateMSSMIMEEncKeyPrefs(SecArenaPoolRef pool, CSSM_DATA_PTR dest, SecCertificateRef cert)
706{
707    PLArenaPool *poolp = (PLArenaPool *)pool;
708    CSSM_DATA_PTR dummy = NULL;
709    PLArenaPool *tmppoolp = NULL;
710    SecCmsIssuerAndSN *isn;
711
712    if (cert == NULL)
713	goto loser;
714
715    tmppoolp = PORT_NewArena(1024);
716    if (tmppoolp == NULL)
717	goto loser;
718
719    isn = CERT_GetCertIssuerAndSN(tmppoolp, cert);
720    if (isn == NULL)
721	goto loser;
722
723    dummy = SEC_ASN1EncodeItem(poolp, dest, isn, SEC_ASN1_GET(SecCmsIssuerAndSNTemplate));
724
725loser:
726    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
727
728    return (dummy == NULL) ? SECFailure : SECSuccess;
729}
730
731/*
732 * SecSMIMEGetCertFromEncryptionKeyPreference -
733 *				find cert marked by EncryptionKeyPreference attribute
734 *
735 * "keychainOrArray" - handle for the cert database to look in
736 * "DERekp" - DER-encoded value of S/MIME Encryption Key Preference attribute
737 *
738 * if certificate is supposed to be found among the message's included certificates,
739 * they are assumed to have been imported already.
740 */
741SecCertificateRef
742SecSMIMEGetCertFromEncryptionKeyPreference(SecKeychainRef keychainOrArray, CSSM_DATA_PTR DERekp)
743{
744    PLArenaPool *tmppoolp = NULL;
745    SecCertificateRef cert = NULL;
746    NSSSMIMEEncryptionKeyPreference ekp;
747
748    tmppoolp = PORT_NewArena(1024);
749    if (tmppoolp == NULL)
750	return NULL;
751
752    /* decode DERekp */
753    if (SEC_ASN1DecodeItem(tmppoolp, &ekp, smime_encryptionkeypref_template, DERekp) != SECSuccess)
754	goto loser;
755
756    /* find cert */
757    switch (ekp.selector) {
758    case NSSSMIMEEncryptionKeyPref_IssuerSN:
759	cert = CERT_FindCertByIssuerAndSN(keychainOrArray, NULL, NULL, ekp.id.issuerAndSN);
760	break;
761    case NSSSMIMEEncryptionKeyPref_RKeyID:
762    case NSSSMIMEEncryptionKeyPref_SubjectKeyID:
763	/* XXX not supported yet - we need to be able to look up certs by SubjectKeyID */
764	break;
765    default:
766	PORT_Assert(0);
767    }
768loser:
769    if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
770
771    return cert;
772}
773
774#if 0
775extern const char __nss_smime_rcsid[];
776extern const char __nss_smime_sccsid[];
777#endif
778
779#if 0 /* -- unused */
780Boolean
781NSSSMIME_VersionCheck(const char *importedVersion)
782{
783#if 1
784    return PR_TRUE;
785#else
786    /*
787     * This is the secret handshake algorithm.
788     *
789     * This release has a simple version compatibility
790     * check algorithm.  This release is not backward
791     * compatible with previous major releases.  It is
792     * not compatible with future major, minor, or
793     * patch releases.
794     */
795    volatile char c; /* force a reference that won't get optimized away */
796
797    c = __nss_smime_rcsid[0] + __nss_smime_sccsid[0];
798
799    return NSS_VersionCheck(importedVersion);
800#endif
801}
802#endif
803