summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/smime/smimeutil.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/smime/smimeutil.c')
-rw-r--r--security/nss/lib/smime/smimeutil.c795
1 files changed, 795 insertions, 0 deletions
diff --git a/security/nss/lib/smime/smimeutil.c b/security/nss/lib/smime/smimeutil.c
new file mode 100644
index 0000000000..2056195dbd
--- /dev/null
+++ b/security/nss/lib/smime/smimeutil.c
@@ -0,0 +1,795 @@
+/* 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 "cert.h"
+#include "keyhi.h"
+#include "secerr.h"
+#include "cms.h"
+#include "nss.h"
+
+SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate)
+SEC_ASN1_MKSUB(SEC_OctetStringTemplate)
+SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate)
+
+/* various integer's ASN.1 encoding */
+static unsigned char asn1_int40[] = { SEC_ASN1_INTEGER, 0x01, 0x28 };
+static unsigned char asn1_int64[] = { SEC_ASN1_INTEGER, 0x01, 0x40 };
+static unsigned char asn1_int128[] = { SEC_ASN1_INTEGER, 0x02, 0x00, 0x80 };
+
+/* RC2 algorithm parameters (used in smime_cipher_map) */
+static SECItem param_int40 = { siBuffer, asn1_int40, sizeof(asn1_int40) };
+static SECItem param_int64 = { siBuffer, asn1_int64, sizeof(asn1_int64) };
+static SECItem param_int128 = { siBuffer, asn1_int128, sizeof(asn1_int128) };
+
+/*
+ * 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 }
+};
+
+/* smime_cipher_map - map of SMIME symmetric "ciphers" to algtag & parameters */
+typedef struct {
+ unsigned long cipher;
+ SECOidTag algtag;
+ SECItem *parms;
+ PRBool enabled; /* in the user's preferences */
+ PRBool allowed; /* per export policy */
+} smime_cipher_map_entry;
+
+/* global: list of supported SMIME symmetric ciphers, ordered roughly by increasing strength */
+static smime_cipher_map_entry smime_cipher_map[] = {
+ /* cipher, algtag, parms, enabled, allowed */
+ /* --------------------------------------- */
+ { SMIME_RC2_CBC_40, SEC_OID_RC2_CBC, &param_int40, PR_TRUE, PR_TRUE },
+ { SMIME_DES_CBC_56, SEC_OID_DES_CBC, NULL, PR_TRUE, PR_TRUE },
+ { SMIME_RC2_CBC_64, SEC_OID_RC2_CBC, &param_int64, PR_TRUE, PR_TRUE },
+ { SMIME_RC2_CBC_128, SEC_OID_RC2_CBC, &param_int128, PR_TRUE, PR_TRUE },
+ { SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC, NULL, PR_TRUE, PR_TRUE },
+ { SMIME_AES_CBC_128, SEC_OID_AES_128_CBC, NULL, PR_TRUE, PR_TRUE },
+ { SMIME_AES_CBC_256, SEC_OID_AES_256_CBC, NULL, PR_TRUE, PR_TRUE }
+};
+static const int smime_cipher_map_count = sizeof(smime_cipher_map) / sizeof(smime_cipher_map_entry);
+
+/*
+ * smime_mapi_by_cipher - find index into smime_cipher_map by cipher
+ */
+static int
+smime_mapi_by_cipher(unsigned long cipher)
+{
+ int i;
+
+ for (i = 0; i < smime_cipher_map_count; i++) {
+ if (smime_cipher_map[i].cipher == cipher)
+ return i; /* bingo */
+ }
+ return -1; /* should not happen if we're consistent, right? */
+}
+
+/*
+ * NSS_SMIME_EnableCipher - this function locally records the user's preference
+ */
+SECStatus
+NSS_SMIMEUtil_EnableCipher(unsigned long which, PRBool on)
+{
+ unsigned long mask;
+ int mapi;
+
+ mask = which & CIPHER_FAMILYID_MASK;
+
+ PORT_Assert(mask == CIPHER_FAMILYID_SMIME);
+ if (mask != CIPHER_FAMILYID_SMIME)
+ /* XXX set an error! */
+ return SECFailure;
+
+ mapi = smime_mapi_by_cipher(which);
+ if (mapi < 0)
+ /* XXX set an error */
+ return SECFailure;
+
+ /* do we try to turn on a forbidden cipher? */
+ if (!smime_cipher_map[mapi].allowed && on) {
+ PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM);
+ return SECFailure;
+ }
+
+ if (smime_cipher_map[mapi].enabled != on)
+ smime_cipher_map[mapi].enabled = on;
+
+ return SECSuccess;
+}
+
+/*
+ * this function locally records the export policy
+ */
+SECStatus
+NSS_SMIMEUtil_AllowCipher(unsigned long which, PRBool on)
+{
+ unsigned long mask;
+ int mapi;
+
+ mask = which & CIPHER_FAMILYID_MASK;
+
+ PORT_Assert(mask == CIPHER_FAMILYID_SMIME);
+ if (mask != CIPHER_FAMILYID_SMIME)
+ /* XXX set an error! */
+ return SECFailure;
+
+ mapi = smime_mapi_by_cipher(which);
+ if (mapi < 0)
+ /* XXX set an error */
+ return SECFailure;
+
+ if (smime_cipher_map[mapi].allowed != on)
+ smime_cipher_map[mapi].allowed = on;
+
+ return SECSuccess;
+}
+
+/*
+ * Based on the given algorithm (including its parameters, in some cases!)
+ * and the given key (may or may not be inspected, depending on the
+ * algorithm), find the appropriate policy algorithm specification
+ * and return it. If no match can be made, -1 is returned.
+ */
+static SECStatus
+nss_smime_get_cipher_for_alg_and_key(SECAlgorithmID *algid, PK11SymKey *key,
+ unsigned long *cipher)
+{
+ SECOidTag algtag;
+ unsigned int keylen_bits;
+ unsigned long c;
+
+ algtag = SECOID_GetAlgorithmTag(algid);
+ switch (algtag) {
+ case SEC_OID_RC2_CBC:
+ keylen_bits = PK11_GetKeyStrength(key, algid);
+ switch (keylen_bits) {
+ case 40:
+ c = SMIME_RC2_CBC_40;
+ break;
+ case 64:
+ c = SMIME_RC2_CBC_64;
+ break;
+ case 128:
+ c = SMIME_RC2_CBC_128;
+ break;
+ default:
+ return SECFailure;
+ }
+ break;
+ case SEC_OID_DES_CBC:
+ c = SMIME_DES_CBC_56;
+ break;
+ case SEC_OID_DES_EDE3_CBC:
+ c = SMIME_DES_EDE3_168;
+ break;
+ case SEC_OID_AES_128_CBC:
+ c = SMIME_AES_CBC_128;
+ break;
+ case SEC_OID_AES_256_CBC:
+ c = SMIME_AES_CBC_256;
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ return SECFailure;
+ }
+ *cipher = c;
+ return SECSuccess;
+}
+
+static PRBool
+nss_smime_cipher_allowed(unsigned long which)
+{
+ int mapi;
+
+ mapi = smime_mapi_by_cipher(which);
+ if (mapi < 0)
+ return PR_FALSE;
+ return smime_cipher_map[mapi].allowed;
+}
+
+PRBool
+NSS_SMIMEUtil_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
+{
+ unsigned long which;
+
+ if (nss_smime_get_cipher_for_alg_and_key(algid, key, &which) != SECSuccess)
+ return PR_FALSE;
+
+ return nss_smime_cipher_allowed(which);
+}
+
+/*
+ * 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)
+{
+ int i;
+
+ for (i = 0; i < smime_cipher_map_count; i++) {
+ if (smime_cipher_map[i].allowed)
+ return PR_TRUE;
+ }
+ return PR_FALSE;
+}
+
+static int
+nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap)
+{
+ int i;
+ SECOidTag capIDTag;
+
+ /* we need the OIDTag here */
+ capIDTag = SECOID_FindOIDTag(&(cap->capabilityID));
+
+ /* go over all the SMIME ciphers we know and see if we find a match */
+ for (i = 0; i < smime_cipher_map_count; i++) {
+ if (smime_cipher_map[i].algtag != capIDTag)
+ continue;
+ /*
+ * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing
+ * 2 NULLs as equal and NULL and non-NULL as not equal), we could
+ * use that here instead of all of the following comparison code.
+ */
+ if (!smime_cipher_map[i].parms) {
+ if (!cap->parameters.data || !cap->parameters.len)
+ break; /* both empty: bingo */
+ if (cap->parameters.len == 2 &&
+ cap->parameters.data[0] == SEC_ASN1_NULL &&
+ cap->parameters.data[1] == 0)
+ break; /* DER NULL == NULL, bingo */
+ } else if (cap->parameters.data != NULL &&
+ cap->parameters.len == smime_cipher_map[i].parms->len &&
+ PORT_Memcmp(cap->parameters.data, smime_cipher_map[i].parms->data,
+ cap->parameters.len) == 0) {
+ break; /* both not empty, same length & equal content: bingo */
+ }
+ }
+
+ if (i == smime_cipher_map_count)
+ return 0; /* no match found */
+ return smime_cipher_map[i].cipher; /* match found, point to 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(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 > 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_mapi]++;
+ cipher_votes[aes256_mapi] += 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_mapi]++;
+ cipher_votes[aes128_mapi] += pref;
+ pref--;
+ }
+ 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;
+}
+
+/*
+ * XXX This is a hack for now to satisfy our current interface.
+ * Eventually, with more parameters needing to be specified, just
+ * looking up the keysize is not going to be sufficient.
+ */
+static int
+smime_keysize_by_cipher(unsigned long which)
+{
+ int keysize;
+
+ switch (which) {
+ case SMIME_RC2_CBC_40:
+ keysize = 40;
+ break;
+ case SMIME_RC2_CBC_64:
+ keysize = 64;
+ break;
+ case SMIME_RC2_CBC_128:
+ case SMIME_AES_CBC_128:
+ keysize = 128;
+ break;
+ case SMIME_AES_CBC_256:
+ keysize = 256;
+ break;
+ case SMIME_DES_CBC_56:
+ case SMIME_DES_EDE3_168:
+ /*
+ * These are special; since the key size is fixed, we actually
+ * want to *avoid* specifying a key size.
+ */
+ keysize = 0;
+ break;
+ default:
+ keysize = -1;
+ break;
+ }
+
+ return keysize;
+}
+
+/*
+ * 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)
+{
+ unsigned long cipher;
+ int mapi;
+
+ cipher = smime_choose_cipher(NULL, rcerts);
+ mapi = smime_mapi_by_cipher(cipher);
+
+ *bulkalgtag = smime_cipher_map[mapi].algtag;
+ *keysize = smime_keysize_by_cipher(smime_cipher_map[mapi].cipher);
+
+ return SECSuccess;
+}
+
+/*
+ * 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;
+ NSSSMIMECapability **smime_capabilities;
+ smime_cipher_map_entry *map;
+ SECOidData *oiddata;
+ SECItem *dummy;
+ int i, capIndex;
+
+ /* if we have an old NSSSMIMECapability array, we'll reuse it (has the right size) */
+ /* smime_cipher_map_count + 1 is an upper bound - we might end up with less */
+ smime_capabilities = (NSSSMIMECapability **)PORT_ZAlloc((smime_cipher_map_count + 1) * sizeof(NSSSMIMECapability *));
+ if (smime_capabilities == NULL)
+ return SECFailure;
+
+ capIndex = 0;
+
+ /* Add all the symmetric ciphers
+ * We walk the cipher list backwards, as it is ordered by increasing strength,
+ * we prefer the stronger cipher over a weaker one, and we have to list the
+ * preferred algorithm first */
+ for (i = smime_cipher_map_count - 1; i >= 0; i--) {
+ /* Find the corresponding entry in the cipher map. */
+ map = &(smime_cipher_map[i]);
+ if (!map->enabled)
+ continue;
+
+ /* get next SMIME capability */
+ cap = (NSSSMIMECapability *)PORT_ZAlloc(sizeof(NSSSMIMECapability));
+ if (cap == NULL)
+ break;
+ smime_capabilities[capIndex++] = cap;
+
+ oiddata = SECOID_FindOIDByTag(map->algtag);
+ if (oiddata == NULL)
+ break;
+
+ cap->capabilityID.data = oiddata->oid.data;
+ cap->capabilityID.len = oiddata->oid.len;
+ cap->parameters.data = map->parms ? map->parms->data : NULL;
+ cap->parameters.len = map->parms ? map->parms->len : 0;
+ cap->cipher = smime_cipher_map[i].cipher;
+ }
+
+ /* XXX add signature algorithms */
+ /* XXX add key encipherment algorithms */
+
+ 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++)
+ 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;
+}