/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Stuff specific to S/MIME policy and interoperability. */ #include "secmime.h" #include "secoid.h" #include "pk11func.h" #include "ciferfam.h" /* for CIPHER_FAMILY symbols */ #include "secasn1.h" #include "secitem.h" #include "sechash.h" #include "cert.h" #include "keyhi.h" #include "secerr.h" #include "cms.h" #include "nss.h" #include "prerror.h" #include "prinit.h" SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate) SEC_ASN1_MKSUB(SEC_OctetStringTemplate) SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate) /* * XXX Would like the "parameters" field to be a SECItem *, but the * encoder is having trouble with optional pointers to an ANY. Maybe * once that is fixed, can change this back... */ typedef struct { SECItem capabilityID; SECItem parameters; long cipher; /* optimization */ } NSSSMIMECapability; static const SEC_ASN1Template NSSSMIMECapabilityTemplate[] = { { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(NSSSMIMECapability) }, { SEC_ASN1_OBJECT_ID, offsetof(NSSSMIMECapability, capabilityID) }, { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, offsetof(NSSSMIMECapability, parameters) }, { 0 } }; static const SEC_ASN1Template NSSSMIMECapabilitiesTemplate[] = { { SEC_ASN1_SEQUENCE_OF, 0, NSSSMIMECapabilityTemplate } }; /* * NSSSMIMEEncryptionKeyPreference - if we find one of these, it needs to prompt us * to store this and only this certificate permanently for the sender email address. */ typedef enum { NSSSMIMEEncryptionKeyPref_IssuerSN, NSSSMIMEEncryptionKeyPref_RKeyID, NSSSMIMEEncryptionKeyPref_SubjectKeyID } NSSSMIMEEncryptionKeyPrefSelector; typedef struct { NSSSMIMEEncryptionKeyPrefSelector selector; union { CERTIssuerAndSN *issuerAndSN; NSSCMSRecipientKeyIdentifier *recipientKeyID; SECItem *subjectKeyID; } id; } NSSSMIMEEncryptionKeyPreference; extern const SEC_ASN1Template NSSCMSRecipientKeyIdentifierTemplate[]; static const SEC_ASN1Template smime_encryptionkeypref_template[] = { { SEC_ASN1_CHOICE, offsetof(NSSSMIMEEncryptionKeyPreference, selector), NULL, sizeof(NSSSMIMEEncryptionKeyPreference) }, { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0 | SEC_ASN1_CONSTRUCTED, offsetof(NSSSMIMEEncryptionKeyPreference, id.issuerAndSN), SEC_ASN1_SUB(CERT_IssuerAndSNTemplate), NSSSMIMEEncryptionKeyPref_IssuerSN }, { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | 1 | SEC_ASN1_CONSTRUCTED, offsetof(NSSSMIMEEncryptionKeyPreference, id.recipientKeyID), NSSCMSRecipientKeyIdentifierTemplate, NSSSMIMEEncryptionKeyPref_RKeyID }, { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2 | SEC_ASN1_CONSTRUCTED, offsetof(NSSSMIMEEncryptionKeyPreference, id.subjectKeyID), SEC_ASN1_SUB(SEC_OctetStringTemplate), NSSSMIMEEncryptionKeyPref_SubjectKeyID }, { 0 } }; /* table of implemented key exchange algorithms. As we add algorithms, * update this table */ static const SECOidTag implemented_key_encipherment[] = { SEC_OID_PKCS1_RSA_ENCRYPTION, SEC_OID_DHSINGLEPASS_STDDH_SHA1KDF_SCHEME, SEC_OID_DHSINGLEPASS_STDDH_SHA224KDF_SCHEME, SEC_OID_DHSINGLEPASS_STDDH_SHA256KDF_SCHEME, SEC_OID_DHSINGLEPASS_STDDH_SHA384KDF_SCHEME, SEC_OID_DHSINGLEPASS_STDDH_SHA512KDF_SCHEME, SEC_OID_DHSINGLEPASS_COFACTORDH_SHA1KDF_SCHEME, SEC_OID_DHSINGLEPASS_COFACTORDH_SHA224KDF_SCHEME, SEC_OID_DHSINGLEPASS_COFACTORDH_SHA256KDF_SCHEME, SEC_OID_DHSINGLEPASS_COFACTORDH_SHA384KDF_SCHEME, SEC_OID_DHSINGLEPASS_COFACTORDH_SHA512KDF_SCHEME, }; static const int implemented_key_encipherment_len = PR_ARRAY_SIZE(implemented_key_encipherment); /* smime_cipher_map - map of SMIME symmetric "ciphers" to algtag & parameters */ typedef struct { unsigned long cipher; SECOidTag policytag; } smime_legacy_map_entry; /* legacy array of S/MIME values to map old SMIME entries to modern * algtags. */ static const smime_legacy_map_entry smime_legacy_map[] = { /* cipher, algtag, policy */ /* --------------------------------------- */ { SMIME_RC2_CBC_40, SEC_OID_RC2_40_CBC }, { SMIME_DES_CBC_56, SEC_OID_DES_CBC }, { SMIME_RC2_CBC_64, SEC_OID_RC2_64_CBC }, { SMIME_RC2_CBC_128, SEC_OID_RC2_128_CBC }, { SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC }, { SMIME_AES_CBC_128, SEC_OID_AES_128_CBC }, { SMIME_AES_CBC_256, SEC_OID_AES_256_CBC }, }; static const int smime_legacy_map_count = PR_ARRAY_SIZE(smime_legacy_map); static int smime_legacy_pref(SECOidTag algtag) { int i; for (i = 0; i < smime_legacy_map_count; i++) { if (smime_legacy_map[i].policytag == algtag) return i; } return -1; } /* * smime_legacy_to policy - find policy algtag from a legacy input */ static SECOidTag smime_legacy_to_policy(unsigned long which) { int i; for (i = 0; i < smime_legacy_map_count; i++) { if (smime_legacy_map[i].cipher == which) return smime_legacy_map[i].policytag; } return SEC_OID_UNKNOWN; } /* map the old legacy values to modern oids. If the value isn't a recognized * legacy value, assume it's a SECOidTag and continue. This allows us to use * the old query and set interfaces with modern oids. */ SECOidTag smime_legacy_to_oid(unsigned long which) { unsigned long mask; /* NOTE: all the legacy values and a CIPHER_FAMILYID of 0x00010000, * (CIPHER_FAMILYID_MASK is 0xffff0000). SECOidTags start at 0 and * increase monotonically, so as long as there is less than 16K of * tags, we can distinguish between values intended to be SMIME ciphers * and values intended to be SECOidTags */ mask = which & CIPHER_FAMILYID_MASK; if (mask == CIPHER_FAMILYID_SMIME) { return smime_legacy_to_policy(which); } return (SECOidTag)which; } /* SEC_OID_RC2_CBC is actually 3 ciphers with different key lengths. All modern * symmetric ciphers include the key length with the oid. To handle policy for * the different keylengths, we include fake oids that let us map the policy based * on key length */ static SECOidTag smime_get_policy_tag_from_key_length(SECOidTag algtag, unsigned long keybits) { if (algtag == SEC_OID_RC2_CBC) { switch (keybits) { case 40: return SEC_OID_RC2_40_CBC; case 64: return SEC_OID_RC2_64_CBC; case 128: return SEC_OID_RC2_128_CBC; default: break; } return SEC_OID_UNKNOWN; } return algtag; } PRBool smime_allowed_by_policy(SECOidTag algtag, PRUint32 neededPolicy) { PRUint32 policyFlags; /* some S/MIME algs map to the same underlying KEA mechanism, * collaps them here */ if ((neededPolicy & (NSS_USE_ALG_IN_SMIME_KX | NSS_USE_ALG_IN_SMIME_KX_LEGACY)) != 0) { CK_MECHANISM_TYPE mechType = PK11_AlgtagToMechanism(algtag); switch (mechType) { case CKM_ECDH1_DERIVE: case CKM_ECDH1_COFACTOR_DERIVE: algtag = SEC_OID_ECDH_KEA; break; } } if ((NSS_GetAlgorithmPolicy(algtag, &policyFlags) == SECFailure) || ((policyFlags & neededPolicy) != neededPolicy)) { PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM); return PR_FALSE; } return PR_TRUE; } /* * We'll need this for the fake policy oids for RC2, but the * rest of these should be moved to pk11wrap for generic * algtag to key size values. We already need this for * sec_pkcs5v2_key_length_by oid. */ static int smime_keysize_by_cipher(SECOidTag algtag) { int keysize; switch (algtag) { case SEC_OID_RC2_40_CBC: keysize = 40; break; case SEC_OID_RC2_64_CBC: keysize = 64; break; case SEC_OID_RC2_128_CBC: case SEC_OID_AES_128_CBC: case SEC_OID_CAMELLIA_128_CBC: keysize = 128; break; case SEC_OID_AES_192_CBC: case SEC_OID_CAMELLIA_192_CBC: keysize = 192; break; case SEC_OID_AES_256_CBC: case SEC_OID_CAMELLIA_256_CBC: keysize = 256; break; default: keysize = 0; break; } return keysize; } static int smime_max_keysize_by_cipher(SECOidTag algtag) { int keysize = smime_keysize_by_cipher(algtag); if (keysize == 0) { CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(algtag); return PK11_GetMaxKeyLength(mech) * PR_BITS_PER_BYTE; } return keysize; } SECOidTag smime_get_alg_from_policy(SECOidTag policy) { switch (policy) { case SEC_OID_RC2_40_CBC: case SEC_OID_RC2_64_CBC: case SEC_OID_RC2_128_CBC: return SEC_OID_RC2_CBC; default: break; } return policy; } typedef struct SMIMEListStr { SECOidTag *tags; size_t space_len; size_t array_len; } SMIMEList; static SMIMEList *smime_algorithm_list = NULL; static PZLock *algorithm_list_lock = NULL; static PRCallOnceType smime_init_arg = { 0 }; /* return the number of algorithms in the list */ size_t smime_list_length(const SMIMEList *list) { if ((list == NULL) || (list->tags == NULL)) { return 0; } return list->array_len; } /* find the index of the algtag in the list. If the algtag isn't on the list, * return the size of the list */ size_t smime_list_index_find(const SMIMEList *list, SECOidTag algtag) { int i; if ((list == NULL) || (list->tags == NULL)) { return 0; } for (i = 0; i < list->array_len; i++) { if (algtag == list->tags[i]) { return i; } } return list->array_len; } #define SMIME_CHUNK_COUNT 10 /* initialize and grow the list if necessary */ static SECStatus smime_list_grow(SMIMEList **list) { /* first make sure the inital list is created */ if (*list == NULL) { *list = PORT_ZNew(SMIMEList); if (*list == NULL) { return SECFailure; } } /* make sure the tag array is intialized */ if ((*list)->tags == NULL) { (*list)->tags = PORT_ZNewArray(SECOidTag, SMIME_CHUNK_COUNT); if ((*list)->tags == NULL) { return SECFailure; } (*list)->space_len = SMIME_CHUNK_COUNT; } /* grow the tag array if necessary */ if ((*list)->array_len == (*list)->space_len) { SECOidTag *new_space; size_t new_len = (*list)->space_len + SMIME_CHUNK_COUNT; new_space = (SECOidTag *)PORT_Realloc((*list)->tags, new_len * sizeof(SECOidTag)); if (new_space) { return SECFailure; } (*list)->tags = new_space; (*list)->space_len = new_len; } return SECSuccess; } /* add a new algtag to the list. if the algtag is already on the list, * do nothing */ static SECStatus smime_list_add(SMIMEList **list, SECOidTag algtag) { SECStatus rv; size_t array_len = smime_list_length(*list); size_t c_index = smime_list_index_find(*list, algtag); if (array_len != c_index) { /* already on the list */ return SECSuccess; } /* go the list if necessary */ rv = smime_list_grow(list); if (rv != SECSuccess) { return rv; } (*list)->tags[(*list)->array_len++] = algtag; return SECSuccess; } static SECStatus smime_list_remove(SMIMEList *list, SECOidTag algtag) { size_t c_index, i; size_t cipher_count = smime_list_length(list); if (cipher_count == 0) { return SECSuccess; } c_index = smime_list_index_find(list, algtag); if (c_index == cipher_count) { /* already removed from the list */ return SECSuccess; } for (i = c_index; i < cipher_count - 1; i++) { list->tags[i] = list->tags[i + 1]; } list->array_len--; list->tags[i] = 0; return SECSuccess; } static SECOidTag smime_list_fetch_by_index(const SMIMEList *list, size_t c_index) { size_t cipher_count = smime_list_length(list); if (c_index >= cipher_count) { return SEC_OID_UNKNOWN; } /* we know this is safe because list cipher_count is non-zero (if it were * any value of c_index will cause the above if to trigger */ return list->tags[c_index]; } static void smime_free_list(SMIMEList **list) { if (*list) { if ((*list)->tags) { PORT_Free((*list)->tags); } PORT_Free(*list); } *list = NULL; } static void smime_lock_algorithm_list(void) { PORT_Assert(algorithm_list_lock); if (algorithm_list_lock) { PZ_Lock(algorithm_list_lock); } return; } static void smime_unlock_algorithm_list(void) { PORT_Assert(algorithm_list_lock); if (algorithm_list_lock) { PZ_Unlock(algorithm_list_lock); } return; } static SECStatus smime_shutdown(void *appData, void *nssData) { if (algorithm_list_lock) { PZ_DestroyLock(algorithm_list_lock); algorithm_list_lock = NULL; } smime_free_list(&smime_algorithm_list); memset(&smime_init_arg, 0, sizeof(smime_init_arg)); return SECSuccess; } static PRStatus smime_init_once(void *arg) { SECOidTag *tags = NULL; SECStatus rv; int tagCount; int i; int *error = (int *)arg; int *lengths = NULL; int *legacy_prefs = NULL; rv = NSS_RegisterShutdown(smime_shutdown, NULL); if (rv != SECSuccess) { *error = PORT_GetError(); return PR_FAILURE; } algorithm_list_lock = PZ_NewLock(nssILockCache); if (algorithm_list_lock == NULL) { *error = PORT_GetError(); return PR_FAILURE; } /* At initialization time, we need to set up the defaults. We first * look to see if the system or application has set up certain algorithms * by policy. If they have set up values by policy we'll only allow those * algorithms. We'll then look to see if any algorithms are enabled by * the application. */ rv = NSS_GetAlgorithmPolicyAll(NSS_USE_ALG_IN_SMIME_LEGACY, NSS_USE_ALG_IN_SMIME_LEGACY, &tags, &tagCount); if (tags) { PORT_Free(tags); tags = NULL; } if ((rv != SECSuccess) || (tagCount == 0)) { /* No algorithms have been enabled by policy (either by the system * or by the application, we then will use the traditional default * algorithms from the policy map */ for (i = smime_legacy_map_count - 1; i >= 0; i--) { SECOidTag policytag = smime_legacy_map[i].policytag; /* this enables the algorithm by policy. We need this or * the policy code will reject attempts to use it */ NSS_SetAlgorithmPolicy(policytag, NSS_USE_ALG_IN_SMIME, 0); /* We also need to enable the algorithm. This is usually unde * application control once the defaults are set up, so the * application can turn off a policy that is already on, but * not turn on a policy that is already off */ smime_list_add(&smime_algorithm_list, policytag); } return PR_SUCCESS; } /* We have a system supplied policy, do we also have * system supplied defaults? If we do we will only actually * turn on the algorithms that have been specified. */ rv = NSS_GetAlgorithmPolicyAll(NSS_USE_DEFAULT_NOT_VALID | NSS_USE_DEFAULT_SMIME_ENABLE, NSS_USE_DEFAULT_SMIME_ENABLE, &tags, &tagCount); /* if none found, enable the default algorithms */ if ((rv != SECSuccess) || (tagCount == 0)) { if (tags) { PORT_Free(tags); tags = NULL; } for (i = smime_legacy_map_count - 1; i >= 0; i--) { SECOidTag policytag = smime_legacy_map[i].policytag; /* we only enable the default algorithm, we don't change * it's policy, which the system has already set. NOTE: * what 'enable' means in the S/MIME sense is we advertise * that we can do the given algorithm in our smime capabilities. */ smime_list_add(&smime_algorithm_list, policytag); } return PR_SUCCESS; } /* Sort tags by key strength here */ lengths = PORT_ZNewArray(int, tagCount); if (lengths == NULL) { *error = PORT_GetError(); goto loser; } legacy_prefs = PORT_ZNewArray(int, tagCount); if (lengths == NULL) { *error = PORT_GetError(); goto loser; } /* Sort the tags array, highest preference at index 0 */ for (i = 0; i < tagCount; i++) { int len = smime_max_keysize_by_cipher(tags[i]); int lpref = smime_legacy_pref(tags[i]); SECOidTag current = tags[i]; PRBool shift = PR_FALSE; int j; /* Determine best position for tags[i]. * For each position j, check if tags [i] has a higher preference. * If yes, store tags[i] at position j, and move all following * entries one position to the back of the array. */ for (j = 0; j < i; j++) { int tlen = lengths[j]; int tpref = legacy_prefs[j]; SECOidTag ttag = tags[j]; /* we prefer ciphers with bigger keysizes, then * we prefer ciphers in our historical list, * then we prefer ciphers that show up first * from the oid table */ if (shift || (len > tlen) || ((len == tlen) && (lpref > tpref))) { tags[j] = current; lengths[j] = len; legacy_prefs[j] = lpref; current = ttag; len = tlen; lpref = tpref; shift = PR_TRUE; } } tags[i] = current; lengths[i] = len; legacy_prefs[i] = lpref; } /* put them in the enable list */ for (i = 0; i < tagCount; i++) { smime_list_add(&smime_algorithm_list, tags[i]); } PORT_Free(lengths); PORT_Free(legacy_prefs); PORT_Free(tags); return PR_SUCCESS; loser: if (lengths) PORT_Free(lengths); if (legacy_prefs) PORT_Free(legacy_prefs); if (tags) PORT_Free(tags); return PR_FAILURE; } static SECStatus smime_init(void) { static PRBool smime_policy_initted = PR_FALSE; static int error = 0; PRStatus nrv; /* has NSS been initialized? */ if (!NSS_IsInitialized()) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return SECFailure; } if (smime_policy_initted) { return SECSuccess; } nrv = PR_CallOnceWithArg(&smime_init_arg, smime_init_once, &error); if (nrv == PR_SUCCESS) { smime_policy_initted = PR_TRUE; return SECSuccess; } PORT_SetError(error); return SECFailure; } /* * NSS_SMIME_EnableCipher - this function locally records the user's preference */ SECStatus NSS_SMIMEUtil_EnableCipher(unsigned long which, PRBool on) { SECOidTag algtag; SECStatus rv = smime_init(); if (rv != SECSuccess) { return SECFailure; } algtag = smime_legacy_to_oid(which); if (!smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME)) { PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM); return SECFailure; } smime_lock_algorithm_list(); if (on) { rv = smime_list_add(&smime_algorithm_list, algtag); } else { rv = smime_list_remove(smime_algorithm_list, algtag); } smime_unlock_algorithm_list(); return rv; } /* * this function locally records the export policy */ SECStatus NSS_SMIMEUtil_AllowCipher(unsigned long which, PRBool on) { SECOidTag algtag = smime_legacy_to_oid(which); PRUint32 set = on ? NSS_USE_ALG_IN_SMIME : 0; PRUint32 clear = on ? 0 : NSS_USE_ALG_IN_SMIME; /* make sure we are inited before setting, so * the defaults are correct */ SECStatus rv = smime_init(); if (rv != SECSuccess) { return SECFailure; } return NSS_SetAlgorithmPolicy(algtag, set, clear); } PRBool NSS_SMIMEUtil_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key) { SECOidTag algtag; /* make sure we are inited before checking policy, so * the defaults are correct */ SECStatus rv = smime_init(); if (rv != SECSuccess) { return SECFailure; } algtag = smime_get_policy_tag_from_key_length(SECOID_GetAlgorithmTag(algid), PK11_GetKeyStrength(key, algid)); return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_LEGACY); } PRBool NSS_SMIMEUtil_EncryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key) { SECOidTag algtag; /* make sure we are inited before checking policy, so * the defaults are correct */ SECStatus rv = smime_init(); if (rv != SECSuccess) { return SECFailure; } algtag = smime_get_policy_tag_from_key_length(SECOID_GetAlgorithmTag(algid), PK11_GetKeyStrength(key, algid)); return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME); } PRBool NSS_SMIMEUtil_SigningAllowed(SECAlgorithmID *algid) { SECOidTag algtag; /* we don't adjust SIGNATURE policy based on defaults, so no need * to call smime_init() */ algtag = SECOID_GetAlgorithmTag(algid); return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_SIGNATURE); } static PRBool nss_smime_enforce_key_size(void) { PRInt32 optFlags; if (NSS_OptionGet(NSS_KEY_SIZE_POLICY_FLAGS, &optFlags) != SECFailure) { if (optFlags & NSS_KEY_SIZE_POLICY_SMIME_FLAG) { return PR_TRUE; } } return PR_FALSE; } PRBool NSS_SMIMEUtil_KeyEncodingAllowed(SECAlgorithmID *algid, CERTCertificate *cert, SECKEYPublicKey *key) { SECOidTag algtag; /* we don't adjust KEA policy based on defaults, so no need * to call smime_init() */ /* if required, make sure the key lengths are enforced */ if (nss_smime_enforce_key_size()) { SECStatus rv; PRBool freeKey = PR_FALSE; if (!key) { /* either the public key or the cert must be supplied. If the * key wasn't supplied, get it from the certificate */ if (!cert) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return PR_FALSE; } key = CERT_ExtractPublicKey(cert); freeKey = PR_TRUE; } rv = SECKEY_EnforceKeySize(key->keyType, SECKEY_PublicKeyStrengthInBits(key), SEC_ERROR_BAD_EXPORT_ALGORITHM); if (freeKey) { SECKEY_DestroyPublicKey(key); } if (rv != SECSuccess) { return PR_FALSE; } } algtag = SECOID_GetAlgorithmTag(algid); return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_KX); } PRBool NSS_SMIMEUtil_KeyDecodingAllowed(SECAlgorithmID *algid, SECKEYPrivateKey *key) { SECOidTag algtag; /* we don't adjust KEA policy based on defaults, so no need * to call smime_init() */ /* if required, make sure the key lengths are enforced */ if (nss_smime_enforce_key_size()) { SECStatus rv; rv = SECKEY_EnforceKeySize(key->keyType, SECKEY_PrivateKeyStrengthInBits(key), SEC_ERROR_BAD_EXPORT_ALGORITHM); if (rv != SECSuccess) { return PR_FALSE; } } algtag = SECOID_GetAlgorithmTag(algid); return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_KX_LEGACY); } /* * NSS_SMIME_EncryptionPossible - check if any encryption is allowed * * This tells whether or not *any* S/MIME encryption can be done, * according to policy. Callers may use this to do nicer user interface * (say, greying out a checkbox so a user does not even try to encrypt * a message when they are not allowed to) or for any reason they want * to check whether S/MIME encryption (or decryption, for that matter) * may be done. * * It takes no arguments. The return value is a simple boolean: * PR_TRUE means encryption (or decryption) is *possible* * (but may still fail due to other reasons, like because we cannot * find all the necessary certs, etc.; PR_TRUE is *not* a guarantee) * PR_FALSE means encryption (or decryption) is not permitted * * There are no errors from this routine. */ PRBool NSS_SMIMEUtil_EncryptionPossible(void) { SECStatus rv = smime_init(); size_t len; if (rv != SECSuccess) { return SECFailure; } smime_lock_algorithm_list(); len = smime_list_length(smime_algorithm_list); smime_unlock_algorithm_list(); return len != 0 ? PR_TRUE : PR_FALSE; } PRBool NSS_SMIMEUtil_EncryptionEnabled(int which) { SECOidTag algtag; size_t c_index, len; SECStatus rv = smime_init(); if (rv != SECSuccess) { return SECFailure; } algtag = smime_legacy_to_oid(which); smime_lock_algorithm_list(); len = smime_list_length(smime_algorithm_list); c_index = smime_list_index_find(smime_algorithm_list, algtag); smime_unlock_algorithm_list(); if (len >= c_index) { return PR_FALSE; } return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME); } static SECOidTag nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap) { SECOidTag capIDTag; /* we need the OIDTag here */ capIDTag = SECOID_FindOIDTag(&(cap->capabilityID)); /* RC2 used a generic oid and encoded the key length in the * parameters */ if (capIDTag == SEC_OID_RC2_CBC) { SECStatus rv; unsigned long key_bits; SECItem keyItem = { siBuffer, NULL, 0 }; rv = SEC_ASN1DecodeItem(NULL, &keyItem, SEC_ASN1_GET(SEC_IntegerTemplate), &cap->parameters); if (rv != SECSuccess) { return SEC_OID_UNKNOWN; } rv = SEC_ASN1DecodeInteger(&keyItem, &key_bits); SECITEM_FreeItem(&keyItem, PR_FALSE); if (rv != SECSuccess) { return SEC_OID_UNKNOWN; } return smime_get_policy_tag_from_key_length(capIDTag, key_bits); } /* everything else uses a null parameter */ if (!cap->parameters.data || !cap->parameters.len) { return capIDTag; } if (cap->parameters.len == 2 && cap->parameters.data[0] == SEC_ASN1_NULL && cap->parameters.data[1] == 0) { return capIDTag; } return SEC_OID_UNKNOWN; } /* * smime_choose_cipher - choose a cipher that works for all the recipients * * "rcerts" - recipient's certificates */ static SECOidTag smime_choose_cipher(CERTCertificate **rcerts) { PLArenaPool *poolp = NULL; SECOidTag chosen_cipher = SEC_OID_UNKNOWN; size_t cipher_count; SECOidTag cipher; int *cipher_abilities; int *cipher_votes; size_t weak_index; size_t strong_index; size_t aes128_index; size_t aes256_index; size_t c_index; int rcount, max; smime_lock_algorithm_list(); cipher_count = smime_list_length(smime_algorithm_list); if (cipher_count == 0) { goto done; } chosen_cipher = SEC_OID_RC2_40_CBC; /* the default, LCD */ weak_index = smime_list_index_find(smime_algorithm_list, chosen_cipher); strong_index = smime_list_index_find(smime_algorithm_list, SEC_OID_DES_EDE3_CBC); aes128_index = smime_list_index_find(smime_algorithm_list, SEC_OID_AES_128_CBC); aes256_index = smime_list_index_find(smime_algorithm_list, SEC_OID_AES_256_CBC); /* make sure the default selected cipher is enabled */ if (weak_index == cipher_count) { chosen_cipher = SEC_OID_DES_EDE3_CBC; if (strong_index == cipher_count) { chosen_cipher = SEC_OID_AES_128_CBC; if (aes128_index == cipher_count) { chosen_cipher = SEC_OID_AES_256_CBC; if (aes256_index == cipher_count) { /* none of the standard algorithms are enabled, If the * recipients don't explicitly include a better cipher * then fail */ chosen_cipher = SEC_OID_UNKNOWN; } } } } poolp = PORT_NewArena(1024); /* XXX what is right value? */ if (poolp == NULL) goto done; cipher_abilities = PORT_ArenaZNewArray(poolp, int, cipher_count + 1); cipher_votes = PORT_ArenaZNewArray(poolp, int, cipher_count + 1); if (cipher_votes == NULL || cipher_abilities == NULL) { goto done; } /* Make triple-DES the strong cipher. */ /* 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 * "cipher_count" votes; the next one gets "cipher_count" - 1 * and so on. If every cipher matches, the last one gets 1 (one) vote */ pref = cipher_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) { int i; /* walk the SMIME capabilities for this recipient */ for (i = 0; caps[i] != NULL; i++) { cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]); c_index = smime_list_index_find(smime_algorithm_list, cipher); if (c_index < cipher_count) { /* found the cipher */ cipher_abilities[c_index]++; cipher_votes[c_index] += 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. */ if (chosen_cipher == SEC_OID_RC2_40_CBC) { chosen_cipher = SEC_OID_AES_128_CBC; } if (pklen_bits > 256) { cipher_abilities[aes256_index]++; cipher_votes[aes256_index] += pref; pref--; } cipher_abilities[aes128_index]++; cipher_votes[aes128_index] += pref; pref--; cipher_abilities[strong_index]++; cipher_votes[strong_index] += pref; pref--; } else { if (pklen_bits > 3072) { /* While support for AES 256 is a SHOULD+ in RFC 5751 * rather than a MUST, RSA and DSA keys longer than 3072 * bits provide more than 128 bits of security strength. * So, AES 256 should be used to provide comparable * security. */ cipher_abilities[aes256_index]++; cipher_votes[aes256_index] += pref; pref--; } if (pklen_bits > 1023) { /* RFC 5751 mandates support for AES 128, but also says * that RSA and DSA signature keys SHOULD NOT be less than * 1024 bits. So, cast vote for AES 128 if key length * is at least 1024 bits. */ cipher_abilities[aes128_index]++; cipher_votes[aes128_index] += pref; pref--; } if (pklen_bits > 512) { /* cast votes for the strong algorithm */ cipher_abilities[strong_index]++; cipher_votes[strong_index] += pref; pref--; } /* always cast (possibly less) votes for the weak algorithm */ cipher_abilities[weak_index]++; cipher_votes[weak_index] += 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 (c_index = 0; c_index < cipher_count; c_index++) { /* if not all of the recipients can do this, forget it */ if (cipher_abilities[c_index] != rcount) continue; cipher = smime_list_fetch_by_index(smime_algorithm_list, c_index); /* if cipher is allowed by policy, forget it */ if (!smime_allowed_by_policy(cipher, NSS_USE_ALG_IN_SMIME)) { continue; } /* now see if this one has more votes than the last best one */ if (cipher_votes[c_index] >= 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 = cipher; max = cipher_votes[c_index]; } } /* if no common cipher was found, chosen_cipher stays at the default */ done: smime_unlock_algorithm_list(); if (poolp != NULL) PORT_FreeArena(poolp, PR_FALSE); return chosen_cipher; } /* * NSS_SMIMEUtil_FindBulkAlgForRecipients - find bulk algorithm suitable for all recipients * * it would be great for UI purposes if there would be a way to find out which recipients * prevented a strong cipher from being used... */ SECStatus NSS_SMIMEUtil_FindBulkAlgForRecipients(CERTCertificate **rcerts, SECOidTag *bulkalgtag, int *keysize) { SECOidTag cipher; SECStatus rv = smime_init(); if (rv != SECSuccess) { return SECFailure; } cipher = smime_choose_cipher(rcerts); if (cipher == SEC_OID_UNKNOWN) { PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM); return SECFailure; } *bulkalgtag = smime_get_alg_from_policy(cipher); *keysize = smime_keysize_by_cipher(cipher); return SECSuccess; } /* * Create a new Capability from an oid tag */ static NSSSMIMECapability * smime_create_capability(SECOidTag cipher) { NSSSMIMECapability *cap = NULL; SECOidData *oiddata = NULL; SECItem *dummy = NULL; oiddata = SECOID_FindOIDByTag(smime_get_alg_from_policy(cipher)); if (oiddata == NULL) { return NULL; } cap = PORT_ZNew(NSSSMIMECapability); if (cap == NULL) { return NULL; } cap->capabilityID.data = oiddata->oid.data; cap->capabilityID.len = oiddata->oid.len; if (cipher == SEC_OID_RC2_CBC) { SECItem keyItem = { siBuffer, NULL, 0 }; unsigned long keybits = smime_get_alg_from_policy(cipher); dummy = SEC_ASN1EncodeInteger(NULL, &keyItem, keybits); if (dummy == NULL) { PORT_Free(cap); return NULL; } dummy = SEC_ASN1EncodeItem(NULL, &cap->parameters, &keyItem, SEC_ASN1_GET(SEC_IntegerTemplate)); SECITEM_FreeItem(&keyItem, PR_FALSE); if (dummy == NULL) { PORT_Free(cap); return NULL; } } else { cap->parameters.data = NULL; cap->parameters.len = 0; } return cap; } /* * NSS_SMIMEUtil_CreateSMIMECapabilities - get S/MIME capabilities for this instance of NSS * * scans the list of allowed and enabled ciphers and construct a PKCS9-compliant * S/MIME capabilities attribute value. * * XXX Please note that, in contradiction to RFC2633 2.5.2, the capabilities only include * symmetric ciphers, NO signature algorithms or key encipherment algorithms. * * "poolp" - arena pool to create the S/MIME capabilities data on * "dest" - SECItem to put the data in */ SECStatus NSS_SMIMEUtil_CreateSMIMECapabilities(PLArenaPool *poolp, SECItem *dest) { NSSSMIMECapability *cap = NULL; NSSSMIMECapability **smime_capabilities = NULL; SECItem *dummy = NULL; int i, capIndex; int cap_count; int cipher_count; int hash_count; SECStatus rv = smime_init(); if (rv != SECSuccess) { return SECFailure; } /* First get the hash count */ for (i = HASH_AlgNULL + 1;; i++) { if (HASH_GetHashOidTagByHashType(i) == SEC_OID_UNKNOWN) { break; } } hash_count = i - 1; smime_lock_algorithm_list(); /* now get the cipher count */ cipher_count = smime_list_length(smime_algorithm_list); if (cipher_count == 0) { smime_unlock_algorithm_list(); PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); return SECFailure; } cap_count = cipher_count + hash_count + implemented_key_encipherment_len; /* cipher_count + 1 is an upper bound - we might end up with less */ smime_capabilities = PORT_ZNewArray(NSSSMIMECapability *, cap_count + 1); if (smime_capabilities == NULL) { smime_unlock_algorithm_list(); return SECFailure; } capIndex = 0; /* Add all the symmetric ciphers * We walk the cipher list, as it is ordered by decreasing strength, * we prefer the stronger cipher over a weaker one, and we have to list the * preferred algorithm first */ for (i = 0; i < cipher_count; i++) { SECOidTag cipher = smime_list_fetch_by_index(smime_algorithm_list, i); /* is it allowed by policy? */ if (!smime_allowed_by_policy(cipher, NSS_USE_ALG_IN_SMIME)) { continue; } cipher = smime_get_alg_from_policy(cipher); cap = smime_create_capability(cipher); if (cap == NULL) break; smime_capabilities[capIndex++] = cap; } /* add signature algorithms = hash algs. * probably also need to figure how what * actual signatures we support in secvfy * as well. We currently don't look a these * when choosing hash and signature (hash is * chosen by the application and signature * type is chosen by the signing cert/key) */ smime_unlock_algorithm_list(); for (i = HASH_AlgNULL + 1; i < hash_count + 1; i++) { SECOidTag hash_alg = HASH_GetHashOidTagByHashType(i); if (!smime_allowed_by_policy(hash_alg, NSS_USE_ALG_IN_SMIME_SIGNATURE | NSS_USE_ALG_IN_SIGNATURE)) { continue; } cap = smime_create_capability(hash_alg); /* get next SMIME capability */ if (cap == NULL) break; smime_capabilities[capIndex++] = cap; } /* add key encipherment algorithms . These are static * to the s/mime library, so we can just use the table. * new kea algs should be implemented. We don't use these * because the senders key pretty much selects what time * of kea we are going to implement */ for (i = 0; i < implemented_key_encipherment_len; i++) { SECOidTag kea_alg = implemented_key_encipherment[i]; if (!smime_allowed_by_policy(kea_alg, NSS_USE_ALG_IN_SMIME_KX)) { continue; } cap = smime_create_capability(kea_alg); /* get next SMIME capability */ if (cap == NULL) break; smime_capabilities[capIndex++] = cap; } smime_capabilities[capIndex] = NULL; /* last one - now encode */ dummy = SEC_ASN1EncodeItem(poolp, dest, &smime_capabilities, NSSSMIMECapabilitiesTemplate); /* now that we have the proper encoded SMIMECapabilities (or not), * free the work data */ for (i = 0; smime_capabilities[i] != NULL; i++) { if (smime_capabilities[i]->parameters.data) { PORT_Free(smime_capabilities[i]->parameters.data); } PORT_Free(smime_capabilities[i]); } PORT_Free(smime_capabilities); return (dummy == NULL) ? SECFailure : SECSuccess; } /* * NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value * * "poolp" - arena pool to create the attr value on * "dest" - SECItem to put the data in * "cert" - certificate that should be marked as preferred encryption key * cert is expected to have been verified for EmailRecipient usage. */ SECStatus NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs(PLArenaPool *poolp, SECItem *dest, CERTCertificate *cert) { NSSSMIMEEncryptionKeyPreference ekp; SECItem *dummy = NULL; PLArenaPool *tmppoolp = NULL; if (cert == NULL) goto loser; tmppoolp = PORT_NewArena(1024); if (tmppoolp == NULL) goto loser; /* XXX hardcoded IssuerSN choice for now */ ekp.selector = NSSSMIMEEncryptionKeyPref_IssuerSN; ekp.id.issuerAndSN = CERT_GetCertIssuerAndSN(tmppoolp, cert); if (ekp.id.issuerAndSN == NULL) goto loser; dummy = SEC_ASN1EncodeItem(poolp, dest, &ekp, smime_encryptionkeypref_template); loser: if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); return (dummy == NULL) ? SECFailure : SECSuccess; } /* * NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value using MS oid * * "poolp" - arena pool to create the attr value on * "dest" - SECItem to put the data in * "cert" - certificate that should be marked as preferred encryption key * cert is expected to have been verified for EmailRecipient usage. */ SECStatus NSS_SMIMEUtil_CreateMSSMIMEEncKeyPrefs(PLArenaPool *poolp, SECItem *dest, CERTCertificate *cert) { SECItem *dummy = NULL; PLArenaPool *tmppoolp = NULL; CERTIssuerAndSN *isn; if (cert == NULL) goto loser; tmppoolp = PORT_NewArena(1024); if (tmppoolp == NULL) goto loser; isn = CERT_GetCertIssuerAndSN(tmppoolp, cert); if (isn == NULL) goto loser; dummy = SEC_ASN1EncodeItem(poolp, dest, isn, SEC_ASN1_GET(CERT_IssuerAndSNTemplate)); loser: if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); return (dummy == NULL) ? SECFailure : SECSuccess; } /* * NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference - * find cert marked by EncryptionKeyPreference attribute * * "certdb" - handle for the cert database to look in * "DERekp" - DER-encoded value of S/MIME Encryption Key Preference attribute * * if certificate is supposed to be found among the message's included certificates, * they are assumed to have been imported already. */ CERTCertificate * NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference(CERTCertDBHandle *certdb, SECItem *DERekp) { PLArenaPool *tmppoolp = NULL; CERTCertificate *cert = NULL; NSSSMIMEEncryptionKeyPreference ekp; tmppoolp = PORT_NewArena(1024); if (tmppoolp == NULL) return NULL; /* decode DERekp */ if (SEC_QuickDERDecodeItem(tmppoolp, &ekp, smime_encryptionkeypref_template, DERekp) != SECSuccess) goto loser; /* find cert */ switch (ekp.selector) { case NSSSMIMEEncryptionKeyPref_IssuerSN: cert = CERT_FindCertByIssuerAndSN(certdb, ekp.id.issuerAndSN); break; case NSSSMIMEEncryptionKeyPref_RKeyID: case NSSSMIMEEncryptionKeyPref_SubjectKeyID: /* XXX not supported yet - we need to be able to look up certs by SubjectKeyID */ break; default: PORT_Assert(0); } loser: if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); return cert; } extern const char __nss_smime_version[]; PRBool NSSSMIME_VersionCheck(const char *importedVersion) { #define NSS_VERSION_VARIABLE __nss_smime_version #include "verref.h" /* * This is the secret handshake algorithm. * * This release has a simple version compatibility * check algorithm. This release is not backward * compatible with previous major releases. It is * not compatible with future major, minor, or * patch releases. */ return NSS_VersionCheck(importedVersion); } const char * NSSSMIME_GetVersion(void) { return NSS_VERSION; }