| /* 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; |
| } |