diff options
Diffstat (limited to 'security/nss/lib/pkcs7/secmime.c')
-rw-r--r-- | security/nss/lib/pkcs7/secmime.c | 800 |
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 000000000..ca1046aa5 --- /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 "key.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; +} |