summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/pkcs7/secmime.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/nss/lib/pkcs7/secmime.c800
1 files changed, 800 insertions, 0 deletions
diff --git a/security/nss/lib/pkcs7/secmime.c b/security/nss/lib/pkcs7/secmime.c
new file mode 100644
index 0000000000..8a4afe45b7
--- /dev/null
+++ b/security/nss/lib/pkcs7/secmime.c
@@ -0,0 +1,800 @@
+/* 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.
+ * Depends on PKCS7, but there should be no dependency the other way around.
+ */
+
+#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"
+
+typedef struct smime_cipher_map_struct {
+ unsigned long cipher;
+ SECOidTag algtag;
+ SECItem *parms;
+} smime_cipher_map;
+
+/*
+ * These are macros because I think some subsequent parameters,
+ * like those for RC5, will want to use them, too, separately.
+ */
+#define SMIME_DER_INTVAL_16 SEC_ASN1_INTEGER, 0x01, 0x10
+#define SMIME_DER_INTVAL_40 SEC_ASN1_INTEGER, 0x01, 0x28
+#define SMIME_DER_INTVAL_64 SEC_ASN1_INTEGER, 0x01, 0x40
+#define SMIME_DER_INTVAL_128 SEC_ASN1_INTEGER, 0x02, 0x00, 0x80
+
+#ifdef SMIME_DOES_RC5 /* will be needed; quiet unused warning for now */
+static unsigned char smime_int16[] = { SMIME_DER_INTVAL_16 };
+#endif
+static unsigned char smime_int40[] = { SMIME_DER_INTVAL_40 };
+static unsigned char smime_int64[] = { SMIME_DER_INTVAL_64 };
+static unsigned char smime_int128[] = { SMIME_DER_INTVAL_128 };
+
+static SECItem smime_rc2p40 = { siBuffer, smime_int40, sizeof(smime_int40) };
+static SECItem smime_rc2p64 = { siBuffer, smime_int64, sizeof(smime_int64) };
+static SECItem smime_rc2p128 = { siBuffer, smime_int128, sizeof(smime_int128) };
+
+static smime_cipher_map smime_cipher_maps[] = {
+ { SMIME_RC2_CBC_40, SEC_OID_RC2_CBC, &smime_rc2p40 },
+ { SMIME_RC2_CBC_64, SEC_OID_RC2_CBC, &smime_rc2p64 },
+ { SMIME_RC2_CBC_128, SEC_OID_RC2_CBC, &smime_rc2p128 },
+#ifdef SMIME_DOES_RC5
+ { SMIME_RC5PAD_64_16_40, SEC_OID_RC5_CBC_PAD, &smime_rc5p40 },
+ { SMIME_RC5PAD_64_16_64, SEC_OID_RC5_CBC_PAD, &smime_rc5p64 },
+ { SMIME_RC5PAD_64_16_128, SEC_OID_RC5_CBC_PAD, &smime_rc5p128 },
+#endif
+ { SMIME_DES_CBC_56, SEC_OID_DES_CBC, NULL },
+ { SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC, NULL }
+};
+
+/*
+ * Note, the following value really just needs to be an upper bound
+ * on the ciphers.
+ */
+static const int smime_symmetric_count = sizeof(smime_cipher_maps) / sizeof(smime_cipher_map);
+
+static unsigned long *smime_prefs, *smime_newprefs;
+static int smime_current_pref_index = 0;
+static PRBool smime_prefs_complete = PR_FALSE;
+static PRBool smime_prefs_changed = PR_TRUE;
+
+static unsigned long smime_policy_bits = 0;
+
+static int
+smime_mapi_by_cipher(unsigned long cipher)
+{
+ int i;
+
+ for (i = 0; i < smime_symmetric_count; i++) {
+ if (smime_cipher_maps[i].cipher == cipher)
+ break;
+ }
+
+ if (i == smime_symmetric_count)
+ return -1;
+
+ return i;
+}
+
+/*
+ * this function locally records the user's preference
+ */
+SECStatus
+SECMIME_EnableCipher(long which, int on)
+{
+ unsigned long mask;
+
+ if (smime_newprefs == NULL || smime_prefs_complete) {
+ /*
+ * This is either the very first time, or we are starting over.
+ */
+ smime_newprefs = (unsigned long *)PORT_ZAlloc(smime_symmetric_count * sizeof(*smime_newprefs));
+ if (smime_newprefs == NULL)
+ return SECFailure;
+ smime_current_pref_index = 0;
+ smime_prefs_complete = PR_FALSE;
+ }
+
+ mask = which & CIPHER_FAMILYID_MASK;
+ if (mask == CIPHER_FAMILYID_MASK) {
+ /*
+ * This call signifies that all preferences have been set.
+ * Move "newprefs" over, after checking first whether or
+ * not the new ones are different from the old ones.
+ */
+ if (smime_prefs != NULL) {
+ if (PORT_Memcmp(smime_prefs, smime_newprefs,
+ smime_symmetric_count * sizeof(*smime_prefs)) == 0)
+ smime_prefs_changed = PR_FALSE;
+ else
+ smime_prefs_changed = PR_TRUE;
+ PORT_Free(smime_prefs);
+ }
+
+ smime_prefs = smime_newprefs;
+ smime_prefs_complete = PR_TRUE;
+ return SECSuccess;
+ }
+
+ PORT_Assert(mask == CIPHER_FAMILYID_SMIME);
+ if (mask != CIPHER_FAMILYID_SMIME) {
+ /* XXX set an error! */
+ return SECFailure;
+ }
+
+ if (on) {
+ PORT_Assert(smime_current_pref_index < smime_symmetric_count);
+ if (smime_current_pref_index >= smime_symmetric_count) {
+ /* XXX set an error! */
+ return SECFailure;
+ }
+
+ smime_newprefs[smime_current_pref_index++] = which;
+ }
+
+ return SECSuccess;
+}
+
+/*
+ * this function locally records the export policy
+ */
+SECStatus
+SECMIME_SetPolicy(long which, int on)
+{
+ unsigned long mask;
+
+ PORT_Assert((which & CIPHER_FAMILYID_MASK) == CIPHER_FAMILYID_SMIME);
+ if ((which & CIPHER_FAMILYID_MASK) != CIPHER_FAMILYID_SMIME) {
+ /* XXX set an error! */
+ return SECFailure;
+ }
+
+ which &= ~CIPHER_FAMILYID_MASK;
+
+ PORT_Assert(which < 32); /* bits in the long */
+ if (which >= 32) {
+ /* XXX set an error! */
+ return SECFailure;
+ }
+
+ mask = 1UL << which;
+
+ if (on) {
+ smime_policy_bits |= mask;
+ } else {
+ smime_policy_bits &= ~mask;
+ }
+
+ 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 long
+smime_policy_algorithm(SECAlgorithmID *algid, PK11SymKey *key)
+{
+ SECOidTag algtag;
+
+ algtag = SECOID_GetAlgorithmTag(algid);
+ switch (algtag) {
+ case SEC_OID_RC2_CBC: {
+ unsigned int keylen_bits;
+
+ keylen_bits = PK11_GetKeyStrength(key, algid);
+ switch (keylen_bits) {
+ case 40:
+ return SMIME_RC2_CBC_40;
+ case 64:
+ return SMIME_RC2_CBC_64;
+ case 128:
+ return SMIME_RC2_CBC_128;
+ default:
+ break;
+ }
+ } break;
+ case SEC_OID_DES_CBC:
+ return SMIME_DES_CBC_56;
+ case SEC_OID_DES_EDE3_CBC:
+ return SMIME_DES_EDE3_168;
+#ifdef SMIME_DOES_RC5
+ case SEC_OID_RC5_CBC_PAD:
+ PORT_Assert(0); /* XXX need to pull out parameters and match */
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+static PRBool
+smime_cipher_allowed(unsigned long which)
+{
+ unsigned long mask;
+
+ which &= ~CIPHER_FAMILYID_MASK;
+ PORT_Assert(which < 32); /* bits per long (min) */
+ if (which >= 32)
+ return PR_FALSE;
+
+ mask = 1UL << which;
+ if ((mask & smime_policy_bits) == 0)
+ return PR_FALSE;
+
+ return PR_TRUE;
+}
+
+PRBool
+SECMIME_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
+{
+ long which;
+
+ which = smime_policy_algorithm(algid, key);
+ if (which < 0)
+ return PR_FALSE;
+
+ return smime_cipher_allowed((unsigned long)which);
+}
+
+/*
+ * Does the current policy allow *any* S/MIME encryption (or decryption)?
+ *
+ * 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
+SECMIME_EncryptionPossible(void)
+{
+ if (smime_policy_bits != 0)
+ return PR_TRUE;
+
+ return PR_FALSE;
+}
+
+/*
+ * 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 smime_capability_struct {
+ unsigned long cipher; /* local; not part of encoding */
+ SECOidTag capIDTag; /* local; not part of encoding */
+ SECItem capabilityID;
+ SECItem parameters;
+} smime_capability;
+
+static const SEC_ASN1Template smime_capability_template[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(smime_capability) },
+ { SEC_ASN1_OBJECT_ID,
+ offsetof(smime_capability, capabilityID) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
+ offsetof(smime_capability, parameters) },
+ { 0 }
+};
+
+static const SEC_ASN1Template smime_capabilities_template[] = {
+ { SEC_ASN1_SEQUENCE_OF, 0, smime_capability_template }
+};
+
+static void
+smime_fill_capability(smime_capability *cap)
+{
+ unsigned long cipher;
+ SECOidTag algtag;
+ int i;
+
+ algtag = SECOID_FindOIDTag(&(cap->capabilityID));
+
+ for (i = 0; i < smime_symmetric_count; i++) {
+ if (smime_cipher_maps[i].algtag != algtag)
+ 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 (cap->parameters.data != NULL) {
+ if (smime_cipher_maps[i].parms == NULL)
+ continue;
+ if (cap->parameters.len != smime_cipher_maps[i].parms->len)
+ continue;
+ if (PORT_Memcmp(cap->parameters.data,
+ smime_cipher_maps[i].parms->data,
+ cap->parameters.len) == 0)
+ break;
+ } else if (smime_cipher_maps[i].parms == NULL) {
+ break;
+ }
+ }
+
+ if (i == smime_symmetric_count)
+ cipher = 0;
+ else
+ cipher = smime_cipher_maps[i].cipher;
+
+ cap->cipher = cipher;
+ cap->capIDTag = algtag;
+}
+
+static long
+smime_choose_cipher(CERTCertificate *scert, CERTCertificate **rcerts)
+{
+ PLArenaPool *poolp;
+ long chosen_cipher;
+ int *cipher_abilities;
+ int *cipher_votes;
+ int strong_mapi;
+ int rcount, mapi, max;
+
+ 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.)
+ */
+
+ /* Make triple-DES the strong cipher. */
+ 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 (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;
+}
+
+/*
+ * 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:
+ keysize = 128;
+ break;
+#ifdef SMIME_DOES_RC5
+ case SMIME_RC5PAD_64_16_40:
+ case SMIME_RC5PAD_64_16_64:
+ case SMIME_RC5PAD_64_16_128:
+ /* XXX See comment above; keysize is not enough... */
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ keysize = -1;
+ break;
+#endif
+ 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;
+}
+
+/*
+ * Start an S/MIME encrypting context.
+ *
+ * "scert" is the cert for the sender. It will be checked for validity.
+ * "rcerts" are the certs for the recipients. They will also be checked.
+ *
+ * "certdb" is the cert database to use for verifying the certs.
+ * It can be NULL if a default database is available (like in the client).
+ *
+ * This function already does all of the stuff specific to S/MIME protocol
+ * and local policy; the return value just needs to be passed to
+ * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data,
+ * and finally to SEC_PKCS7DestroyContentInfo().
+ *
+ * An error results in a return value of NULL and an error set.
+ * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
+ */
+SEC_PKCS7ContentInfo *
+SECMIME_CreateEncrypted(CERTCertificate *scert,
+ CERTCertificate **rcerts,
+ CERTCertDBHandle *certdb,
+ SECKEYGetPasswordKey pwfn,
+ void *pwfn_arg)
+{
+ SEC_PKCS7ContentInfo *cinfo;
+ long cipher;
+ SECOidTag encalg;
+ int keysize;
+ int mapi, rci;
+
+ cipher = smime_choose_cipher(scert, rcerts);
+ if (cipher < 0)
+ return NULL;
+
+ mapi = smime_mapi_by_cipher(cipher);
+ if (mapi < 0)
+ return NULL;
+
+ /*
+ * XXX This is stretching it -- CreateEnvelopedData should probably
+ * take a cipher itself of some sort, because we cannot know what the
+ * future will bring in terms of parameters for each type of algorithm.
+ * For example, just an algorithm and keysize is *not* sufficient to
+ * fully specify the usage of RC5 (which also needs to know rounds and
+ * block size). Work this out into a better API!
+ */
+ encalg = smime_cipher_maps[mapi].algtag;
+ keysize = smime_keysize_by_cipher(cipher);
+ if (keysize < 0)
+ return NULL;
+
+ cinfo = SEC_PKCS7CreateEnvelopedData(scert, certUsageEmailRecipient,
+ certdb, encalg, keysize,
+ pwfn, pwfn_arg);
+ if (cinfo == NULL)
+ return NULL;
+
+ for (rci = 0; rcerts[rci] != NULL; rci++) {
+ if (rcerts[rci] == scert)
+ continue;
+ if (SEC_PKCS7AddRecipient(cinfo, rcerts[rci], certUsageEmailRecipient,
+ NULL) != SECSuccess) {
+ SEC_PKCS7DestroyContentInfo(cinfo);
+ return NULL;
+ }
+ }
+
+ return cinfo;
+}
+
+static smime_capability **smime_capabilities;
+static SECItem *smime_encoded_caps;
+
+static SECStatus
+smime_init_caps(void)
+{
+ smime_capability *cap;
+ smime_cipher_map *map;
+ SECOidData *oiddata;
+ SECStatus rv;
+ int i;
+
+ if (smime_encoded_caps != NULL && (!smime_prefs_changed))
+ return SECSuccess;
+
+ if (smime_encoded_caps != NULL) {
+ SECITEM_FreeItem(smime_encoded_caps, PR_TRUE);
+ smime_encoded_caps = NULL;
+ }
+
+ if (smime_capabilities == NULL) {
+ smime_capabilities = (smime_capability **)PORT_ZAlloc(
+ (smime_symmetric_count + 1) * sizeof(smime_capability *));
+ if (smime_capabilities == NULL)
+ return SECFailure;
+ }
+
+ rv = SECFailure;
+
+ /*
+ The process of creating the encoded PKCS7 cipher capability list
+ involves two basic steps:
+
+ (a) Convert our internal representation of cipher preferences
+ (smime_prefs) into an array containing cipher OIDs and
+ parameter data (smime_capabilities). This step is
+ performed here.
+
+ (b) Encode, using ASN.1, the cipher information in
+ smime_capabilities, leaving the encoded result in
+ smime_encoded_caps.
+
+ (In the process of performing (a), Lisa put in some optimizations
+ which allow us to avoid needlessly re-populating elements in
+ smime_capabilities as we walk through smime_prefs.)
+ */
+ for (i = 0; i < smime_current_pref_index; i++) {
+ int mapi;
+
+ /* Get the next cipher preference in smime_prefs. */
+ mapi = smime_mapi_by_cipher(smime_prefs[i]);
+ if (mapi < 0)
+ break;
+
+ /* Find the corresponding entry in the cipher map. */
+ PORT_Assert(mapi < smime_symmetric_count);
+ map = &(smime_cipher_maps[mapi]);
+
+ /*
+ * Convert the next preference found in smime_prefs into an
+ * smime_capability.
+ */
+
+ cap = smime_capabilities[i];
+ if (cap == NULL) {
+ cap = (smime_capability *)PORT_ZAlloc(sizeof(smime_capability));
+ if (cap == NULL)
+ break;
+ smime_capabilities[i] = cap;
+ } else if (cap->cipher == smime_prefs[i]) {
+ continue; /* no change to this one */
+ }
+
+ cap->capIDTag = map->algtag;
+ oiddata = SECOID_FindOIDByTag(map->algtag);
+ if (oiddata == NULL)
+ break;
+
+ if (cap->capabilityID.data != NULL) {
+ SECITEM_FreeItem(&(cap->capabilityID), PR_FALSE);
+ cap->capabilityID.data = NULL;
+ cap->capabilityID.len = 0;
+ }
+
+ rv = SECITEM_CopyItem(NULL, &(cap->capabilityID), &(oiddata->oid));
+ if (rv != SECSuccess)
+ break;
+
+ if (map->parms == NULL) {
+ cap->parameters.data = NULL;
+ cap->parameters.len = 0;
+ } else {
+ cap->parameters.data = map->parms->data;
+ cap->parameters.len = map->parms->len;
+ }
+
+ cap->cipher = smime_prefs[i];
+ }
+
+ if (i != smime_current_pref_index)
+ return rv;
+
+ while (i < smime_symmetric_count) {
+ cap = smime_capabilities[i];
+ if (cap != NULL) {
+ SECITEM_FreeItem(&(cap->capabilityID), PR_FALSE);
+ PORT_Free(cap);
+ }
+ smime_capabilities[i] = NULL;
+ i++;
+ }
+ smime_capabilities[i] = NULL;
+
+ smime_encoded_caps = SEC_ASN1EncodeItem(NULL, NULL, &smime_capabilities,
+ smime_capabilities_template);
+ if (smime_encoded_caps == NULL)
+ return SECFailure;
+
+ return SECSuccess;
+}
+
+static SECStatus
+smime_add_profile(CERTCertificate *cert, SEC_PKCS7ContentInfo *cinfo)
+{
+ PORT_Assert(smime_prefs_complete);
+ if (!smime_prefs_complete)
+ return SECFailure;
+
+ /* For that matter, if capabilities haven't been initialized yet,
+ do so now. */
+ if (smime_encoded_caps == NULL || smime_prefs_changed) {
+ SECStatus rv;
+
+ rv = smime_init_caps();
+ if (rv != SECSuccess)
+ return rv;
+
+ PORT_Assert(smime_encoded_caps != NULL);
+ }
+
+ return SEC_PKCS7AddSignedAttribute(cinfo, SEC_OID_PKCS9_SMIME_CAPABILITIES,
+ smime_encoded_caps);
+}
+
+/*
+ * Start an S/MIME signing context.
+ *
+ * "scert" is the cert that will be used to sign the data. It will be
+ * checked for validity.
+ *
+ * "ecert" is the signer's encryption cert. If it is different from
+ * scert, then it will be included in the signed message so that the
+ * recipient can save it for future encryptions.
+ *
+ * "certdb" is the cert database to use for verifying the cert.
+ * It can be NULL if a default database is available (like in the client).
+ *
+ * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1).
+ * XXX There should be SECMIME functions for hashing, or the hashing should
+ * be built into this interface, which we would like because we would
+ * support more smartcards that way, and then this argument should go away.)
+ *
+ * "digest" is the actual digest of the data. It must be provided in
+ * the case of detached data or NULL if the content will be included.
+ *
+ * This function already does all of the stuff specific to S/MIME protocol
+ * and local policy; the return value just needs to be passed to
+ * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data,
+ * and finally to SEC_PKCS7DestroyContentInfo().
+ *
+ * An error results in a return value of NULL and an error set.
+ * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
+ */
+
+SEC_PKCS7ContentInfo *
+SECMIME_CreateSigned(CERTCertificate *scert,
+ CERTCertificate *ecert,
+ CERTCertDBHandle *certdb,
+ SECOidTag digestalg,
+ SECItem *digest,
+ SECKEYGetPasswordKey pwfn,
+ void *pwfn_arg)
+{
+ SEC_PKCS7ContentInfo *cinfo;
+ SECStatus rv;
+
+ /* See note in header comment above about digestalg. */
+ /* Doesn't explain this. PORT_Assert (digestalg == SEC_OID_SHA1); */
+
+ cinfo = SEC_PKCS7CreateSignedData(scert, certUsageEmailSigner,
+ certdb, digestalg, digest,
+ pwfn, pwfn_arg);
+ if (cinfo == NULL)
+ return NULL;
+
+ if (SEC_PKCS7IncludeCertChain(cinfo, NULL) != SECSuccess) {
+ SEC_PKCS7DestroyContentInfo(cinfo);
+ return NULL;
+ }
+
+ /* if the encryption cert and the signing cert differ, then include
+ * the encryption cert too.
+ */
+ /* it is ok to compare the pointers since we ref count, and the same
+ * cert will always have the same pointer
+ */
+ if ((ecert != NULL) && (ecert != scert)) {
+ rv = SEC_PKCS7AddCertificate(cinfo, ecert);
+ if (rv != SECSuccess) {
+ SEC_PKCS7DestroyContentInfo(cinfo);
+ return NULL;
+ }
+ }
+ /*
+ * Add the signing time. But if it fails for some reason,
+ * may as well not give up altogether -- just assert.
+ */
+ rv = SEC_PKCS7AddSigningTime(cinfo);
+ PORT_Assert(rv == SECSuccess);
+
+ /*
+ * Add the email profile. Again, if it fails for some reason,
+ * may as well not give up altogether -- just assert.
+ */
+ rv = smime_add_profile(ecert, cinfo);
+ PORT_Assert(rv == SECSuccess);
+
+ return cinfo;
+}