static SECStatus smime_add_profile (CERTCertificate *cert, SEC_PKCS7ContentInfo *cinfo) { PRBool isFortezza = PR_FALSE; PORT_Assert (smime_prefs_complete); if (! smime_prefs_complete) return SECFailure; /* See if the sender's cert specifies Fortezza key exchange. */ if (cert != NULL) isFortezza = PK11_FortezzaHasKEA(cert); /* For that matter, if capabilities haven't been initialized yet, do so now. */ if (isFortezza != lastUsedFortezza || smime_encoded_caps == NULL || smime_prefs_changed) { SECStatus rv; rv = smime_init_caps(isFortezza); if (rv != SECSuccess) return rv; PORT_Assert (smime_encoded_caps != NULL); } return SEC_PKCS7AddSignedAttribute (cinfo, SEC_OID_PKCS9_SMIME_CAPABILITIES, smime_encoded_caps); }
/* * SecCmsSignerInfoAddSMIMECaps - add a SMIMECapabilities attribute to the * authenticated (i.e. signed) attributes of "signerinfo". * * This is expected to be included in outgoing signed * messages for email (S/MIME). */ OSStatus SecCmsSignerInfoAddSMIMECaps(SecCmsSignerInfoRef signerinfo) { SecCmsAttribute *attr; CSSM_DATA_PTR smimecaps = NULL; void *mark; PLArenaPool *poolp; poolp = signerinfo->cmsg->poolp; mark = PORT_ArenaMark(poolp); smimecaps = SECITEM_AllocItem(poolp, NULL, 0); if (smimecaps == NULL) goto loser; /* create new signing time attribute */ #if 1 // @@@ We don't do Fortezza yet. if (SecSMIMECreateSMIMECapabilities((SecArenaPoolRef)poolp, smimecaps, PR_FALSE) != SECSuccess) #else if (SecSMIMECreateSMIMECapabilities(poolp, smimecaps, PK11_FortezzaHasKEA(signerinfo->cert)) != SECSuccess) #endif goto loser; if ((attr = SecCmsAttributeCreate(poolp, SEC_OID_PKCS9_SMIME_CAPABILITIES, smimecaps, PR_TRUE)) == NULL) goto loser; if (SecCmsSignerInfoAddAuthAttr(signerinfo, attr) != SECSuccess) goto loser; PORT_ArenaUnmark (poolp, mark); return SECSuccess; loser: PORT_ArenaRelease (poolp, mark); return SECFailure; }
/* * smime_choose_cipher - choose a cipher that works for all the recipients * * "scert" - sender's certificate * "rcerts" - recipient's certificates */ static long smime_choose_cipher(SecCertificateRef scert, SecCertificateRef *rcerts) { PRArenaPool *poolp; long cipher; long chosen_cipher; int *cipher_abilities; int *cipher_votes; int weak_mapi; int strong_mapi; int rcount, mapi, max, i; #if 1 // @@@ We Don't support Fortezza yet. Boolean scert_is_fortezza = PR_FALSE; #else Boolean scert_is_fortezza = (scert == NULL) ? PR_FALSE : PK11_FortezzaHasKEA(scert); #endif chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */ weak_mapi = smime_mapi_by_cipher(chosen_cipher); poolp = PORT_NewArena (1024); /* XXX what is right value? */ if (poolp == NULL) goto done; cipher_abilities = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int)); cipher_votes = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int)); if (cipher_votes == NULL || cipher_abilities == NULL) goto done; /* If the user has the Fortezza preference turned on, make * that the strong cipher. Otherwise, use triple-DES. */ strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168); if (scert_is_fortezza) { mapi = smime_mapi_by_cipher(SMIME_FORTEZZA); if (mapi >= 0 && smime_cipher_map[mapi].enabled) strong_mapi = mapi; } /* walk all the recipient's certs */ for (rcount = 0; rcerts[rcount] != NULL; rcount++) { CSSM_DATA_PTR profile; NSSSMIMECapability **caps; int pref; /* the first cipher that matches in the user's SMIME profile gets * "smime_cipher_map_count" votes; the next one gets "smime_cipher_map_count" - 1 * and so on. If every cipher matches, the last one gets 1 (one) vote */ pref = smime_cipher_map_count; /* find recipient's SMIME profile */ profile = CERT_FindSMimeProfile(rcerts[rcount]); if (profile != NULL && profile->Data != NULL && profile->Length > 0) { /* we have a profile (still DER-encoded) */ caps = NULL; /* decode it */ if (SEC_ASN1DecodeItem(poolp, &caps, NSSSMIMECapabilitiesTemplate, profile) == SECSuccess && caps != NULL) { /* walk the SMIME capabilities for this recipient */ for (i = 0; caps[i] != NULL; i++) { cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]); mapi = smime_mapi_by_cipher(cipher); if (mapi >= 0) { /* found the cipher */ cipher_abilities[mapi]++; cipher_votes[mapi] += pref; --pref; } } } } else { /* no profile found - so we can only assume that the user can do * the mandatory algorithms which is RC2-40 (weak crypto) and 3DES (strong crypto) */ SecPublicKeyRef key; unsigned int pklen_bits; /* * if recipient's public key length is > 512, vote for a strong cipher * please not that the side effect of this is that if only one recipient * has an export-level public key, the strong cipher is disabled. * * XXX This is probably only good for RSA keys. What I would * really like is a function to just say; Is the public key in * this cert an export-length key? Then I would not have to * know things like the value 512, or the kind of key, or what * a subjectPublicKeyInfo is, etc. */ key = CERT_ExtractPublicKey(rcerts[rcount]); pklen_bits = 0; if (key != NULL) { SecKeyGetStrengthInBits(key, NULL, &pklen_bits); SECKEY_DestroyPublicKey (key); } if (pklen_bits > 512) { /* cast votes for the strong algorithm */ cipher_abilities[strong_mapi]++; cipher_votes[strong_mapi] += pref; pref--; } /* always cast (possibly less) votes for the weak algorithm */ cipher_abilities[weak_mapi]++; cipher_votes[weak_mapi] += pref; } if (profile != NULL) SECITEM_FreeItem(profile, PR_TRUE); } /* find cipher that is agreeable by all recipients and that has the most votes */ max = 0; for (mapi = 0; mapi < smime_cipher_map_count; mapi++) { /* if not all of the recipients can do this, forget it */ if (cipher_abilities[mapi] != rcount) continue; /* if cipher is not enabled or not allowed by policy, forget it */ if (!smime_cipher_map[mapi].enabled || !smime_cipher_map[mapi].allowed) continue; /* if we're not doing fortezza, but the cipher is fortezza, forget it */ if (!scert_is_fortezza && (smime_cipher_map[mapi].cipher == SMIME_FORTEZZA)) continue; /* now see if this one has more votes than the last best one */ if (cipher_votes[mapi] >= max) { /* if equal number of votes, prefer the ones further down in the list */ /* with the expectation that these are higher rated ciphers */ chosen_cipher = smime_cipher_map[mapi].cipher; max = cipher_votes[mapi]; } } /* if no common cipher was found, chosen_cipher stays at the default */ done: if (poolp != NULL) PORT_FreeArena (poolp, PR_FALSE); if (smime_keysize_by_cipher(chosen_cipher) < 128) { /* you're going to use strong(er) crypto whether you like it or not */ chosen_cipher = SMIME_DES_EDE3_168; } return chosen_cipher; }
static long smime_choose_cipher (CERTCertificate *scert, CERTCertificate **rcerts) { PRArenaPool *poolp; long chosen_cipher; int *cipher_abilities; int *cipher_votes; int strong_mapi; int rcount, mapi, max, i; PRBool isFortezza = PK11_FortezzaHasKEA(scert); if (smime_policy_bits == 0) { PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM); return -1; } chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */ poolp = PORT_NewArena (1024); /* XXX what is right value? */ if (poolp == NULL) goto done; cipher_abilities = (int*)PORT_ArenaZAlloc (poolp, smime_symmetric_count * sizeof(int)); if (cipher_abilities == NULL) goto done; cipher_votes = (int*)PORT_ArenaZAlloc (poolp, smime_symmetric_count * sizeof(int)); if (cipher_votes == NULL) goto done; /* * XXX Should have a #define somewhere which specifies default * strong cipher. (Or better, a way to configure, which would * take Fortezza into account as well.) */ /* If the user has the Fortezza preference turned on, make * that the strong cipher. Otherwise, use triple-DES. */ strong_mapi = -1; if (isFortezza) { for(i=0;i < smime_current_pref_index && strong_mapi < 0;i++) { if (smime_prefs[i] == SMIME_FORTEZZA) strong_mapi = smime_mapi_by_cipher(SMIME_FORTEZZA); } } if (strong_mapi == -1) strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168); PORT_Assert (strong_mapi >= 0); for (rcount = 0; rcerts[rcount] != NULL; rcount++) { SECItem *profile; smime_capability **caps; int capi, pref; SECStatus dstat; pref = smime_symmetric_count; profile = CERT_FindSMimeProfile (rcerts[rcount]); if (profile != NULL && profile->data != NULL && profile->len > 0) { caps = NULL; dstat = SEC_QuickDERDecodeItem (poolp, &caps, smime_capabilities_template, profile); if (dstat == SECSuccess && caps != NULL) { for (capi = 0; caps[capi] != NULL; capi++) { smime_fill_capability (caps[capi]); mapi = smime_mapi_by_cipher (caps[capi]->cipher); if (mapi >= 0) { cipher_abilities[mapi]++; cipher_votes[mapi] += pref; --pref; } } } } else { SECKEYPublicKey *key; unsigned int pklen_bits; /* * XXX This is probably only good for RSA keys. What I would * really like is a function to just say; Is the public key in * this cert an export-length key? Then I would not have to * know things like the value 512, or the kind of key, or what * a subjectPublicKeyInfo is, etc. */ key = CERT_ExtractPublicKey (rcerts[rcount]); if (key != NULL) { pklen_bits = SECKEY_PublicKeyStrength (key) * 8; SECKEY_DestroyPublicKey (key); if (pklen_bits > 512) { cipher_abilities[strong_mapi]++; cipher_votes[strong_mapi] += pref; } } } if (profile != NULL) SECITEM_FreeItem (profile, PR_TRUE); } max = 0; for (mapi = 0; mapi < smime_symmetric_count; mapi++) { if (cipher_abilities[mapi] != rcount) continue; if (! smime_cipher_allowed (smime_cipher_maps[mapi].cipher)) continue; if (!isFortezza && (smime_cipher_maps[mapi].cipher == SMIME_FORTEZZA)) continue; if (cipher_votes[mapi] > max) { chosen_cipher = smime_cipher_maps[mapi].cipher; max = cipher_votes[mapi]; } /* XXX else if a tie, let scert break it? */ } done: if (poolp != NULL) PORT_FreeArena (poolp, PR_FALSE); return chosen_cipher; }