/* * 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(CERTCertificate *scert, CERTCertificate **rcerts) { PLArenaPool *poolp; long cipher; long chosen_cipher; int *cipher_abilities; int *cipher_votes; int weak_mapi; int strong_mapi; int aes128_mapi; int aes256_mapi; int rcount, mapi, max, i; chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */ weak_mapi = smime_mapi_by_cipher(chosen_cipher); aes128_mapi = smime_mapi_by_cipher(SMIME_AES_CBC_128); aes256_mapi = smime_mapi_by_cipher(SMIME_AES_CBC_256); 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; /* Make triple-DES the strong cipher. */ strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168); /* walk all the recipient's certs */ for (rcount = 0; rcerts[rcount] != NULL; rcount++) { SECItem *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->len > 0) { /* we have a profile (still DER-encoded) */ caps = NULL; /* decode it */ if (SEC_QuickDERDecodeItem(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 are RC2-40 (weak crypto) and * 3DES (strong crypto), unless the user has an elliptic curve * key. For elliptic curve keys, RFC 5753 mandates support * for AES 128 CBC. */ SECKEYPublicKey *key; unsigned int pklen_bits; KeyType key_type; /* * 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; key_type = nullKey; if (key != NULL) { pklen_bits = SECKEY_PublicKeyStrengthInBits (key); key_type = SECKEY_GetPublicKeyType(key); SECKEY_DestroyPublicKey (key); key = NULL; } if (key_type == ecKey) { /* While RFC 5753 mandates support for AES-128 CBC, should use * AES 256 if user's key provides more than 128 bits of * security strength so that symmetric key is not weak link. */ /* RC2-40 is not compatible with elliptic curve keys. */ chosen_cipher = SMIME_DES_EDE3_168; if (pklen_bits > 256) { cipher_abilities[aes256_mapi]++; cipher_votes[aes256_mapi] += pref; pref--; } cipher_abilities[aes128_mapi]++; cipher_votes[aes128_mapi] += pref; pref--; cipher_abilities[strong_mapi]++; cipher_votes[strong_mapi] += pref; pref--; } else { 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; /* 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); return chosen_cipher; }
/* * 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; }