michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Stuff specific to S/MIME policy and interoperability. michael@0: * Depends on PKCS7, but there should be no dependency the other way around. michael@0: */ michael@0: michael@0: #include "secmime.h" michael@0: #include "secoid.h" michael@0: #include "pk11func.h" michael@0: #include "ciferfam.h" /* for CIPHER_FAMILY symbols */ michael@0: #include "secasn1.h" michael@0: #include "secitem.h" michael@0: #include "cert.h" michael@0: #include "key.h" michael@0: #include "secerr.h" michael@0: michael@0: typedef struct smime_cipher_map_struct { michael@0: unsigned long cipher; michael@0: SECOidTag algtag; michael@0: SECItem *parms; michael@0: } smime_cipher_map; michael@0: michael@0: /* michael@0: * These are macros because I think some subsequent parameters, michael@0: * like those for RC5, will want to use them, too, separately. michael@0: */ michael@0: #define SMIME_DER_INTVAL_16 SEC_ASN1_INTEGER, 0x01, 0x10 michael@0: #define SMIME_DER_INTVAL_40 SEC_ASN1_INTEGER, 0x01, 0x28 michael@0: #define SMIME_DER_INTVAL_64 SEC_ASN1_INTEGER, 0x01, 0x40 michael@0: #define SMIME_DER_INTVAL_128 SEC_ASN1_INTEGER, 0x02, 0x00, 0x80 michael@0: michael@0: #ifdef SMIME_DOES_RC5 /* will be needed; quiet unused warning for now */ michael@0: static unsigned char smime_int16[] = { SMIME_DER_INTVAL_16 }; michael@0: #endif michael@0: static unsigned char smime_int40[] = { SMIME_DER_INTVAL_40 }; michael@0: static unsigned char smime_int64[] = { SMIME_DER_INTVAL_64 }; michael@0: static unsigned char smime_int128[] = { SMIME_DER_INTVAL_128 }; michael@0: michael@0: static SECItem smime_rc2p40 = { siBuffer, smime_int40, sizeof(smime_int40) }; michael@0: static SECItem smime_rc2p64 = { siBuffer, smime_int64, sizeof(smime_int64) }; michael@0: static SECItem smime_rc2p128 = { siBuffer, smime_int128, sizeof(smime_int128) }; michael@0: michael@0: static smime_cipher_map smime_cipher_maps[] = { michael@0: { SMIME_RC2_CBC_40, SEC_OID_RC2_CBC, &smime_rc2p40 }, michael@0: { SMIME_RC2_CBC_64, SEC_OID_RC2_CBC, &smime_rc2p64 }, michael@0: { SMIME_RC2_CBC_128, SEC_OID_RC2_CBC, &smime_rc2p128 }, michael@0: #ifdef SMIME_DOES_RC5 michael@0: { SMIME_RC5PAD_64_16_40, SEC_OID_RC5_CBC_PAD, &smime_rc5p40 }, michael@0: { SMIME_RC5PAD_64_16_64, SEC_OID_RC5_CBC_PAD, &smime_rc5p64 }, michael@0: { SMIME_RC5PAD_64_16_128, SEC_OID_RC5_CBC_PAD, &smime_rc5p128 }, michael@0: #endif michael@0: { SMIME_DES_CBC_56, SEC_OID_DES_CBC, NULL }, michael@0: { SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC, NULL } michael@0: }; michael@0: michael@0: /* michael@0: * Note, the following value really just needs to be an upper bound michael@0: * on the ciphers. michael@0: */ michael@0: static const int smime_symmetric_count = sizeof(smime_cipher_maps) michael@0: / sizeof(smime_cipher_map); michael@0: michael@0: static unsigned long *smime_prefs, *smime_newprefs; michael@0: static int smime_current_pref_index = 0; michael@0: static PRBool smime_prefs_complete = PR_FALSE; michael@0: static PRBool smime_prefs_changed = PR_TRUE; michael@0: michael@0: static unsigned long smime_policy_bits = 0; michael@0: michael@0: michael@0: static int michael@0: smime_mapi_by_cipher (unsigned long cipher) michael@0: { michael@0: int i; michael@0: michael@0: for (i = 0; i < smime_symmetric_count; i++) { michael@0: if (smime_cipher_maps[i].cipher == cipher) michael@0: break; michael@0: } michael@0: michael@0: if (i == smime_symmetric_count) michael@0: return -1; michael@0: michael@0: return i; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * this function locally records the user's preference michael@0: */ michael@0: SECStatus michael@0: SECMIME_EnableCipher(long which, int on) michael@0: { michael@0: unsigned long mask; michael@0: michael@0: if (smime_newprefs == NULL || smime_prefs_complete) { michael@0: /* michael@0: * This is either the very first time, or we are starting over. michael@0: */ michael@0: smime_newprefs = (unsigned long*)PORT_ZAlloc (smime_symmetric_count michael@0: * sizeof(*smime_newprefs)); michael@0: if (smime_newprefs == NULL) michael@0: return SECFailure; michael@0: smime_current_pref_index = 0; michael@0: smime_prefs_complete = PR_FALSE; michael@0: } michael@0: michael@0: mask = which & CIPHER_FAMILYID_MASK; michael@0: if (mask == CIPHER_FAMILYID_MASK) { michael@0: /* michael@0: * This call signifies that all preferences have been set. michael@0: * Move "newprefs" over, after checking first whether or michael@0: * not the new ones are different from the old ones. michael@0: */ michael@0: if (smime_prefs != NULL) { michael@0: if (PORT_Memcmp (smime_prefs, smime_newprefs, michael@0: smime_symmetric_count * sizeof(*smime_prefs)) == 0) michael@0: smime_prefs_changed = PR_FALSE; michael@0: else michael@0: smime_prefs_changed = PR_TRUE; michael@0: PORT_Free (smime_prefs); michael@0: } michael@0: michael@0: smime_prefs = smime_newprefs; michael@0: smime_prefs_complete = PR_TRUE; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: PORT_Assert (mask == CIPHER_FAMILYID_SMIME); michael@0: if (mask != CIPHER_FAMILYID_SMIME) { michael@0: /* XXX set an error! */ michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (on) { michael@0: PORT_Assert (smime_current_pref_index < smime_symmetric_count); michael@0: if (smime_current_pref_index >= smime_symmetric_count) { michael@0: /* XXX set an error! */ michael@0: return SECFailure; michael@0: } michael@0: michael@0: smime_newprefs[smime_current_pref_index++] = which; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * this function locally records the export policy michael@0: */ michael@0: SECStatus michael@0: SECMIME_SetPolicy(long which, int on) michael@0: { michael@0: unsigned long mask; michael@0: michael@0: PORT_Assert ((which & CIPHER_FAMILYID_MASK) == CIPHER_FAMILYID_SMIME); michael@0: if ((which & CIPHER_FAMILYID_MASK) != CIPHER_FAMILYID_SMIME) { michael@0: /* XXX set an error! */ michael@0: return SECFailure; michael@0: } michael@0: michael@0: which &= ~CIPHER_FAMILYID_MASK; michael@0: michael@0: PORT_Assert (which < 32); /* bits in the long */ michael@0: if (which >= 32) { michael@0: /* XXX set an error! */ michael@0: return SECFailure; michael@0: } michael@0: michael@0: mask = 1UL << which; michael@0: michael@0: if (on) { michael@0: smime_policy_bits |= mask; michael@0: } else { michael@0: smime_policy_bits &= ~mask; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Based on the given algorithm (including its parameters, in some cases!) michael@0: * and the given key (may or may not be inspected, depending on the michael@0: * algorithm), find the appropriate policy algorithm specification michael@0: * and return it. If no match can be made, -1 is returned. michael@0: */ michael@0: static long michael@0: smime_policy_algorithm (SECAlgorithmID *algid, PK11SymKey *key) michael@0: { michael@0: SECOidTag algtag; michael@0: michael@0: algtag = SECOID_GetAlgorithmTag (algid); michael@0: switch (algtag) { michael@0: case SEC_OID_RC2_CBC: michael@0: { michael@0: unsigned int keylen_bits; michael@0: michael@0: keylen_bits = PK11_GetKeyStrength (key, algid); michael@0: switch (keylen_bits) { michael@0: case 40: michael@0: return SMIME_RC2_CBC_40; michael@0: case 64: michael@0: return SMIME_RC2_CBC_64; michael@0: case 128: michael@0: return SMIME_RC2_CBC_128; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: break; michael@0: case SEC_OID_DES_CBC: michael@0: return SMIME_DES_CBC_56; michael@0: case SEC_OID_DES_EDE3_CBC: michael@0: return SMIME_DES_EDE3_168; michael@0: #ifdef SMIME_DOES_RC5 michael@0: case SEC_OID_RC5_CBC_PAD: michael@0: PORT_Assert (0); /* XXX need to pull out parameters and match */ michael@0: break; michael@0: #endif michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: michael@0: static PRBool michael@0: smime_cipher_allowed (unsigned long which) michael@0: { michael@0: unsigned long mask; michael@0: michael@0: which &= ~CIPHER_FAMILYID_MASK; michael@0: PORT_Assert (which < 32); /* bits per long (min) */ michael@0: if (which >= 32) michael@0: return PR_FALSE; michael@0: michael@0: mask = 1UL << which; michael@0: if ((mask & smime_policy_bits) == 0) michael@0: return PR_FALSE; michael@0: michael@0: return PR_TRUE; michael@0: } michael@0: michael@0: michael@0: PRBool michael@0: SECMIME_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key) michael@0: { michael@0: long which; michael@0: michael@0: which = smime_policy_algorithm (algid, key); michael@0: if (which < 0) michael@0: return PR_FALSE; michael@0: michael@0: return smime_cipher_allowed ((unsigned long)which); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Does the current policy allow *any* S/MIME encryption (or decryption)? michael@0: * michael@0: * This tells whether or not *any* S/MIME encryption can be done, michael@0: * according to policy. Callers may use this to do nicer user interface michael@0: * (say, greying out a checkbox so a user does not even try to encrypt michael@0: * a message when they are not allowed to) or for any reason they want michael@0: * to check whether S/MIME encryption (or decryption, for that matter) michael@0: * may be done. michael@0: * michael@0: * It takes no arguments. The return value is a simple boolean: michael@0: * PR_TRUE means encryption (or decryption) is *possible* michael@0: * (but may still fail due to other reasons, like because we cannot michael@0: * find all the necessary certs, etc.; PR_TRUE is *not* a guarantee) michael@0: * PR_FALSE means encryption (or decryption) is not permitted michael@0: * michael@0: * There are no errors from this routine. michael@0: */ michael@0: PRBool michael@0: SECMIME_EncryptionPossible (void) michael@0: { michael@0: if (smime_policy_bits != 0) michael@0: return PR_TRUE; michael@0: michael@0: return PR_FALSE; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * XXX Would like the "parameters" field to be a SECItem *, but the michael@0: * encoder is having trouble with optional pointers to an ANY. Maybe michael@0: * once that is fixed, can change this back... michael@0: */ michael@0: typedef struct smime_capability_struct { michael@0: unsigned long cipher; /* local; not part of encoding */ michael@0: SECOidTag capIDTag; /* local; not part of encoding */ michael@0: SECItem capabilityID; michael@0: SECItem parameters; michael@0: } smime_capability; michael@0: michael@0: static const SEC_ASN1Template smime_capability_template[] = { michael@0: { SEC_ASN1_SEQUENCE, michael@0: 0, NULL, sizeof(smime_capability) }, michael@0: { SEC_ASN1_OBJECT_ID, michael@0: offsetof(smime_capability,capabilityID), }, michael@0: { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, michael@0: offsetof(smime_capability,parameters), }, michael@0: { 0, } michael@0: }; michael@0: michael@0: static const SEC_ASN1Template smime_capabilities_template[] = { michael@0: { SEC_ASN1_SEQUENCE_OF, 0, smime_capability_template } michael@0: }; michael@0: michael@0: michael@0: michael@0: static void michael@0: smime_fill_capability (smime_capability *cap) michael@0: { michael@0: unsigned long cipher; michael@0: SECOidTag algtag; michael@0: int i; michael@0: michael@0: algtag = SECOID_FindOIDTag (&(cap->capabilityID)); michael@0: michael@0: for (i = 0; i < smime_symmetric_count; i++) { michael@0: if (smime_cipher_maps[i].algtag != algtag) michael@0: continue; michael@0: /* michael@0: * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing michael@0: * 2 NULLs as equal and NULL and non-NULL as not equal), we could michael@0: * use that here instead of all of the following comparison code. michael@0: */ michael@0: if (cap->parameters.data != NULL) { michael@0: if (smime_cipher_maps[i].parms == NULL) michael@0: continue; michael@0: if (cap->parameters.len != smime_cipher_maps[i].parms->len) michael@0: continue; michael@0: if (PORT_Memcmp (cap->parameters.data, michael@0: smime_cipher_maps[i].parms->data, michael@0: cap->parameters.len) == 0) michael@0: break; michael@0: } else if (smime_cipher_maps[i].parms == NULL) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (i == smime_symmetric_count) michael@0: cipher = 0; michael@0: else michael@0: cipher = smime_cipher_maps[i].cipher; michael@0: michael@0: cap->cipher = cipher; michael@0: cap->capIDTag = algtag; michael@0: } michael@0: michael@0: michael@0: static long michael@0: smime_choose_cipher (CERTCertificate *scert, CERTCertificate **rcerts) michael@0: { michael@0: PLArenaPool *poolp; michael@0: long chosen_cipher; michael@0: int *cipher_abilities; michael@0: int *cipher_votes; michael@0: int strong_mapi; michael@0: int rcount, mapi, max; michael@0: michael@0: if (smime_policy_bits == 0) { michael@0: PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM); michael@0: return -1; michael@0: } michael@0: michael@0: chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */ michael@0: michael@0: poolp = PORT_NewArena (1024); /* XXX what is right value? */ michael@0: if (poolp == NULL) michael@0: goto done; michael@0: michael@0: cipher_abilities = (int*)PORT_ArenaZAlloc (poolp, michael@0: smime_symmetric_count * sizeof(int)); michael@0: if (cipher_abilities == NULL) michael@0: goto done; michael@0: michael@0: cipher_votes = (int*)PORT_ArenaZAlloc (poolp, michael@0: smime_symmetric_count * sizeof(int)); michael@0: if (cipher_votes == NULL) michael@0: goto done; michael@0: michael@0: /* michael@0: * XXX Should have a #define somewhere which specifies default michael@0: * strong cipher. (Or better, a way to configure.) michael@0: */ michael@0: michael@0: /* Make triple-DES the strong cipher. */ michael@0: strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168); michael@0: michael@0: PORT_Assert (strong_mapi >= 0); michael@0: michael@0: for (rcount = 0; rcerts[rcount] != NULL; rcount++) { michael@0: SECItem *profile; michael@0: smime_capability **caps; michael@0: int capi, pref; michael@0: SECStatus dstat; michael@0: michael@0: pref = smime_symmetric_count; michael@0: profile = CERT_FindSMimeProfile (rcerts[rcount]); michael@0: if (profile != NULL && profile->data != NULL && profile->len > 0) { michael@0: caps = NULL; michael@0: dstat = SEC_QuickDERDecodeItem (poolp, &caps, michael@0: smime_capabilities_template, michael@0: profile); michael@0: if (dstat == SECSuccess && caps != NULL) { michael@0: for (capi = 0; caps[capi] != NULL; capi++) { michael@0: smime_fill_capability (caps[capi]); michael@0: mapi = smime_mapi_by_cipher (caps[capi]->cipher); michael@0: if (mapi >= 0) { michael@0: cipher_abilities[mapi]++; michael@0: cipher_votes[mapi] += pref; michael@0: --pref; michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: SECKEYPublicKey *key; michael@0: unsigned int pklen_bits; michael@0: michael@0: /* michael@0: * XXX This is probably only good for RSA keys. What I would michael@0: * really like is a function to just say; Is the public key in michael@0: * this cert an export-length key? Then I would not have to michael@0: * know things like the value 512, or the kind of key, or what michael@0: * a subjectPublicKeyInfo is, etc. michael@0: */ michael@0: key = CERT_ExtractPublicKey (rcerts[rcount]); michael@0: if (key != NULL) { michael@0: pklen_bits = SECKEY_PublicKeyStrength (key) * 8; michael@0: SECKEY_DestroyPublicKey (key); michael@0: michael@0: if (pklen_bits > 512) { michael@0: cipher_abilities[strong_mapi]++; michael@0: cipher_votes[strong_mapi] += pref; michael@0: } michael@0: } michael@0: } michael@0: if (profile != NULL) michael@0: SECITEM_FreeItem (profile, PR_TRUE); michael@0: } michael@0: michael@0: max = 0; michael@0: for (mapi = 0; mapi < smime_symmetric_count; mapi++) { michael@0: if (cipher_abilities[mapi] != rcount) michael@0: continue; michael@0: if (! smime_cipher_allowed (smime_cipher_maps[mapi].cipher)) michael@0: continue; michael@0: if (cipher_votes[mapi] > max) { michael@0: chosen_cipher = smime_cipher_maps[mapi].cipher; michael@0: max = cipher_votes[mapi]; michael@0: } /* XXX else if a tie, let scert break it? */ michael@0: } michael@0: michael@0: done: michael@0: if (poolp != NULL) michael@0: PORT_FreeArena (poolp, PR_FALSE); michael@0: michael@0: return chosen_cipher; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * XXX This is a hack for now to satisfy our current interface. michael@0: * Eventually, with more parameters needing to be specified, just michael@0: * looking up the keysize is not going to be sufficient. michael@0: */ michael@0: static int michael@0: smime_keysize_by_cipher (unsigned long which) michael@0: { michael@0: int keysize; michael@0: michael@0: switch (which) { michael@0: case SMIME_RC2_CBC_40: michael@0: keysize = 40; michael@0: break; michael@0: case SMIME_RC2_CBC_64: michael@0: keysize = 64; michael@0: break; michael@0: case SMIME_RC2_CBC_128: michael@0: keysize = 128; michael@0: break; michael@0: #ifdef SMIME_DOES_RC5 michael@0: case SMIME_RC5PAD_64_16_40: michael@0: case SMIME_RC5PAD_64_16_64: michael@0: case SMIME_RC5PAD_64_16_128: michael@0: /* XXX See comment above; keysize is not enough... */ michael@0: PORT_Assert (0); michael@0: PORT_SetError (SEC_ERROR_INVALID_ALGORITHM); michael@0: keysize = -1; michael@0: break; michael@0: #endif michael@0: case SMIME_DES_CBC_56: michael@0: case SMIME_DES_EDE3_168: michael@0: /* michael@0: * These are special; since the key size is fixed, we actually michael@0: * want to *avoid* specifying a key size. michael@0: */ michael@0: keysize = 0; michael@0: break; michael@0: default: michael@0: keysize = -1; michael@0: break; michael@0: } michael@0: michael@0: return keysize; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Start an S/MIME encrypting context. michael@0: * michael@0: * "scert" is the cert for the sender. It will be checked for validity. michael@0: * "rcerts" are the certs for the recipients. They will also be checked. michael@0: * michael@0: * "certdb" is the cert database to use for verifying the certs. michael@0: * It can be NULL if a default database is available (like in the client). michael@0: * michael@0: * This function already does all of the stuff specific to S/MIME protocol michael@0: * and local policy; the return value just needs to be passed to michael@0: * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, michael@0: * and finally to SEC_PKCS7DestroyContentInfo(). michael@0: * michael@0: * An error results in a return value of NULL and an error set. michael@0: * (Retrieve specific errors via PORT_GetError()/XP_GetError().) michael@0: */ michael@0: SEC_PKCS7ContentInfo * michael@0: SECMIME_CreateEncrypted(CERTCertificate *scert, michael@0: CERTCertificate **rcerts, michael@0: CERTCertDBHandle *certdb, michael@0: SECKEYGetPasswordKey pwfn, michael@0: void *pwfn_arg) michael@0: { michael@0: SEC_PKCS7ContentInfo *cinfo; michael@0: long cipher; michael@0: SECOidTag encalg; michael@0: int keysize; michael@0: int mapi, rci; michael@0: michael@0: cipher = smime_choose_cipher (scert, rcerts); michael@0: if (cipher < 0) michael@0: return NULL; michael@0: michael@0: mapi = smime_mapi_by_cipher (cipher); michael@0: if (mapi < 0) michael@0: return NULL; michael@0: michael@0: /* michael@0: * XXX This is stretching it -- CreateEnvelopedData should probably michael@0: * take a cipher itself of some sort, because we cannot know what the michael@0: * future will bring in terms of parameters for each type of algorithm. michael@0: * For example, just an algorithm and keysize is *not* sufficient to michael@0: * fully specify the usage of RC5 (which also needs to know rounds and michael@0: * block size). Work this out into a better API! michael@0: */ michael@0: encalg = smime_cipher_maps[mapi].algtag; michael@0: keysize = smime_keysize_by_cipher (cipher); michael@0: if (keysize < 0) michael@0: return NULL; michael@0: michael@0: cinfo = SEC_PKCS7CreateEnvelopedData (scert, certUsageEmailRecipient, michael@0: certdb, encalg, keysize, michael@0: pwfn, pwfn_arg); michael@0: if (cinfo == NULL) michael@0: return NULL; michael@0: michael@0: for (rci = 0; rcerts[rci] != NULL; rci++) { michael@0: if (rcerts[rci] == scert) michael@0: continue; michael@0: if (SEC_PKCS7AddRecipient (cinfo, rcerts[rci], certUsageEmailRecipient, michael@0: NULL) != SECSuccess) { michael@0: SEC_PKCS7DestroyContentInfo (cinfo); michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: return cinfo; michael@0: } michael@0: michael@0: michael@0: static smime_capability **smime_capabilities; michael@0: static SECItem *smime_encoded_caps; michael@0: michael@0: michael@0: static SECStatus michael@0: smime_init_caps (void) michael@0: { michael@0: smime_capability *cap; michael@0: smime_cipher_map *map; michael@0: SECOidData *oiddata; michael@0: SECStatus rv; michael@0: int i; michael@0: michael@0: if (smime_encoded_caps != NULL && (! smime_prefs_changed)) michael@0: return SECSuccess; michael@0: michael@0: if (smime_encoded_caps != NULL) { michael@0: SECITEM_FreeItem (smime_encoded_caps, PR_TRUE); michael@0: smime_encoded_caps = NULL; michael@0: } michael@0: michael@0: if (smime_capabilities == NULL) { michael@0: smime_capabilities = (smime_capability**)PORT_ZAlloc ( michael@0: (smime_symmetric_count + 1) michael@0: * sizeof(smime_capability *)); michael@0: if (smime_capabilities == NULL) michael@0: return SECFailure; michael@0: } michael@0: michael@0: rv = SECFailure; michael@0: michael@0: /* michael@0: The process of creating the encoded PKCS7 cipher capability list michael@0: involves two basic steps: michael@0: michael@0: (a) Convert our internal representation of cipher preferences michael@0: (smime_prefs) into an array containing cipher OIDs and michael@0: parameter data (smime_capabilities). This step is michael@0: performed here. michael@0: michael@0: (b) Encode, using ASN.1, the cipher information in michael@0: smime_capabilities, leaving the encoded result in michael@0: smime_encoded_caps. michael@0: michael@0: (In the process of performing (a), Lisa put in some optimizations michael@0: which allow us to avoid needlessly re-populating elements in michael@0: smime_capabilities as we walk through smime_prefs.) michael@0: */ michael@0: for (i = 0; i < smime_current_pref_index; i++) { michael@0: int mapi; michael@0: michael@0: /* Get the next cipher preference in smime_prefs. */ michael@0: mapi = smime_mapi_by_cipher (smime_prefs[i]); michael@0: if (mapi < 0) michael@0: break; michael@0: michael@0: /* Find the corresponding entry in the cipher map. */ michael@0: PORT_Assert (mapi < smime_symmetric_count); michael@0: map = &(smime_cipher_maps[mapi]); michael@0: michael@0: /* michael@0: * Convert the next preference found in smime_prefs into an michael@0: * smime_capability. michael@0: */ michael@0: michael@0: cap = smime_capabilities[i]; michael@0: if (cap == NULL) { michael@0: cap = (smime_capability*)PORT_ZAlloc (sizeof(smime_capability)); michael@0: if (cap == NULL) michael@0: break; michael@0: smime_capabilities[i] = cap; michael@0: } else if (cap->cipher == smime_prefs[i]) { michael@0: continue; /* no change to this one */ michael@0: } michael@0: michael@0: cap->capIDTag = map->algtag; michael@0: oiddata = SECOID_FindOIDByTag (map->algtag); michael@0: if (oiddata == NULL) michael@0: break; michael@0: michael@0: if (cap->capabilityID.data != NULL) { michael@0: SECITEM_FreeItem (&(cap->capabilityID), PR_FALSE); michael@0: cap->capabilityID.data = NULL; michael@0: cap->capabilityID.len = 0; michael@0: } michael@0: michael@0: rv = SECITEM_CopyItem (NULL, &(cap->capabilityID), &(oiddata->oid)); michael@0: if (rv != SECSuccess) michael@0: break; michael@0: michael@0: if (map->parms == NULL) { michael@0: cap->parameters.data = NULL; michael@0: cap->parameters.len = 0; michael@0: } else { michael@0: cap->parameters.data = map->parms->data; michael@0: cap->parameters.len = map->parms->len; michael@0: } michael@0: michael@0: cap->cipher = smime_prefs[i]; michael@0: } michael@0: michael@0: if (i != smime_current_pref_index) michael@0: return rv; michael@0: michael@0: while (i < smime_symmetric_count) { michael@0: cap = smime_capabilities[i]; michael@0: if (cap != NULL) { michael@0: SECITEM_FreeItem (&(cap->capabilityID), PR_FALSE); michael@0: PORT_Free (cap); michael@0: } michael@0: smime_capabilities[i] = NULL; michael@0: i++; michael@0: } michael@0: smime_capabilities[i] = NULL; michael@0: michael@0: smime_encoded_caps = SEC_ASN1EncodeItem (NULL, NULL, &smime_capabilities, michael@0: smime_capabilities_template); michael@0: if (smime_encoded_caps == NULL) michael@0: return SECFailure; michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: michael@0: static SECStatus michael@0: smime_add_profile (CERTCertificate *cert, SEC_PKCS7ContentInfo *cinfo) michael@0: { michael@0: PORT_Assert (smime_prefs_complete); michael@0: if (! smime_prefs_complete) michael@0: return SECFailure; michael@0: michael@0: /* For that matter, if capabilities haven't been initialized yet, michael@0: do so now. */ michael@0: if (smime_encoded_caps == NULL || smime_prefs_changed) { michael@0: SECStatus rv; michael@0: michael@0: rv = smime_init_caps(); michael@0: if (rv != SECSuccess) michael@0: return rv; michael@0: michael@0: PORT_Assert (smime_encoded_caps != NULL); michael@0: } michael@0: michael@0: return SEC_PKCS7AddSignedAttribute (cinfo, SEC_OID_PKCS9_SMIME_CAPABILITIES, michael@0: smime_encoded_caps); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Start an S/MIME signing context. michael@0: * michael@0: * "scert" is the cert that will be used to sign the data. It will be michael@0: * checked for validity. michael@0: * michael@0: * "ecert" is the signer's encryption cert. If it is different from michael@0: * scert, then it will be included in the signed message so that the michael@0: * recipient can save it for future encryptions. michael@0: * michael@0: * "certdb" is the cert database to use for verifying the cert. michael@0: * It can be NULL if a default database is available (like in the client). michael@0: * michael@0: * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1). michael@0: * XXX There should be SECMIME functions for hashing, or the hashing should michael@0: * be built into this interface, which we would like because we would michael@0: * support more smartcards that way, and then this argument should go away.) michael@0: * michael@0: * "digest" is the actual digest of the data. It must be provided in michael@0: * the case of detached data or NULL if the content will be included. michael@0: * michael@0: * This function already does all of the stuff specific to S/MIME protocol michael@0: * and local policy; the return value just needs to be passed to michael@0: * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, michael@0: * and finally to SEC_PKCS7DestroyContentInfo(). michael@0: * michael@0: * An error results in a return value of NULL and an error set. michael@0: * (Retrieve specific errors via PORT_GetError()/XP_GetError().) michael@0: */ michael@0: michael@0: SEC_PKCS7ContentInfo * michael@0: SECMIME_CreateSigned (CERTCertificate *scert, michael@0: CERTCertificate *ecert, michael@0: CERTCertDBHandle *certdb, michael@0: SECOidTag digestalg, michael@0: SECItem *digest, michael@0: SECKEYGetPasswordKey pwfn, michael@0: void *pwfn_arg) michael@0: { michael@0: SEC_PKCS7ContentInfo *cinfo; michael@0: SECStatus rv; michael@0: michael@0: /* See note in header comment above about digestalg. */ michael@0: /* Doesn't explain this. PORT_Assert (digestalg == SEC_OID_SHA1); */ michael@0: michael@0: cinfo = SEC_PKCS7CreateSignedData (scert, certUsageEmailSigner, michael@0: certdb, digestalg, digest, michael@0: pwfn, pwfn_arg); michael@0: if (cinfo == NULL) michael@0: return NULL; michael@0: michael@0: if (SEC_PKCS7IncludeCertChain (cinfo, NULL) != SECSuccess) { michael@0: SEC_PKCS7DestroyContentInfo (cinfo); michael@0: return NULL; michael@0: } michael@0: michael@0: /* if the encryption cert and the signing cert differ, then include michael@0: * the encryption cert too. michael@0: */ michael@0: /* it is ok to compare the pointers since we ref count, and the same michael@0: * cert will always have the same pointer michael@0: */ michael@0: if ( ( ecert != NULL ) && ( ecert != scert ) ) { michael@0: rv = SEC_PKCS7AddCertificate(cinfo, ecert); michael@0: if ( rv != SECSuccess ) { michael@0: SEC_PKCS7DestroyContentInfo (cinfo); michael@0: return NULL; michael@0: } michael@0: } michael@0: /* michael@0: * Add the signing time. But if it fails for some reason, michael@0: * may as well not give up altogether -- just assert. michael@0: */ michael@0: rv = SEC_PKCS7AddSigningTime (cinfo); michael@0: PORT_Assert (rv == SECSuccess); michael@0: michael@0: /* michael@0: * Add the email profile. Again, if it fails for some reason, michael@0: * may as well not give up altogether -- just assert. michael@0: */ michael@0: rv = smime_add_profile (ecert, cinfo); michael@0: PORT_Assert (rv == SECSuccess); michael@0: michael@0: return cinfo; michael@0: }