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: */ 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: #include "cms.h" michael@0: #include "nss.h" michael@0: michael@0: SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate) michael@0: SEC_ASN1_MKSUB(SEC_OctetStringTemplate) michael@0: SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate) michael@0: michael@0: /* various integer's ASN.1 encoding */ michael@0: static unsigned char asn1_int40[] = { SEC_ASN1_INTEGER, 0x01, 0x28 }; michael@0: static unsigned char asn1_int64[] = { SEC_ASN1_INTEGER, 0x01, 0x40 }; michael@0: static unsigned char asn1_int128[] = { SEC_ASN1_INTEGER, 0x02, 0x00, 0x80 }; michael@0: michael@0: /* RC2 algorithm parameters (used in smime_cipher_map) */ michael@0: static SECItem param_int40 = { siBuffer, asn1_int40, sizeof(asn1_int40) }; michael@0: static SECItem param_int64 = { siBuffer, asn1_int64, sizeof(asn1_int64) }; michael@0: static SECItem param_int128 = { siBuffer, asn1_int128, sizeof(asn1_int128) }; 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 { michael@0: SECItem capabilityID; michael@0: SECItem parameters; michael@0: long cipher; /* optimization */ michael@0: } NSSSMIMECapability; michael@0: michael@0: static const SEC_ASN1Template NSSSMIMECapabilityTemplate[] = { michael@0: { SEC_ASN1_SEQUENCE, michael@0: 0, NULL, sizeof(NSSSMIMECapability) }, michael@0: { SEC_ASN1_OBJECT_ID, michael@0: offsetof(NSSSMIMECapability,capabilityID), }, michael@0: { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, michael@0: offsetof(NSSSMIMECapability,parameters), }, michael@0: { 0, } michael@0: }; michael@0: michael@0: static const SEC_ASN1Template NSSSMIMECapabilitiesTemplate[] = { michael@0: { SEC_ASN1_SEQUENCE_OF, 0, NSSSMIMECapabilityTemplate } michael@0: }; michael@0: michael@0: /* michael@0: * NSSSMIMEEncryptionKeyPreference - if we find one of these, it needs to prompt us michael@0: * to store this and only this certificate permanently for the sender email address. michael@0: */ michael@0: typedef enum { michael@0: NSSSMIMEEncryptionKeyPref_IssuerSN, michael@0: NSSSMIMEEncryptionKeyPref_RKeyID, michael@0: NSSSMIMEEncryptionKeyPref_SubjectKeyID michael@0: } NSSSMIMEEncryptionKeyPrefSelector; michael@0: michael@0: typedef struct { michael@0: NSSSMIMEEncryptionKeyPrefSelector selector; michael@0: union { michael@0: CERTIssuerAndSN *issuerAndSN; michael@0: NSSCMSRecipientKeyIdentifier *recipientKeyID; michael@0: SECItem *subjectKeyID; michael@0: } id; michael@0: } NSSSMIMEEncryptionKeyPreference; michael@0: michael@0: extern const SEC_ASN1Template NSSCMSRecipientKeyIdentifierTemplate[]; michael@0: michael@0: static const SEC_ASN1Template smime_encryptionkeypref_template[] = { michael@0: { SEC_ASN1_CHOICE, michael@0: offsetof(NSSSMIMEEncryptionKeyPreference,selector), NULL, michael@0: sizeof(NSSSMIMEEncryptionKeyPreference) }, michael@0: { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0 michael@0: | SEC_ASN1_CONSTRUCTED, michael@0: offsetof(NSSSMIMEEncryptionKeyPreference,id.issuerAndSN), michael@0: SEC_ASN1_SUB(CERT_IssuerAndSNTemplate), michael@0: NSSSMIMEEncryptionKeyPref_IssuerSN }, michael@0: { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | 1 michael@0: | SEC_ASN1_CONSTRUCTED, michael@0: offsetof(NSSSMIMEEncryptionKeyPreference,id.recipientKeyID), michael@0: NSSCMSRecipientKeyIdentifierTemplate, michael@0: NSSSMIMEEncryptionKeyPref_RKeyID }, michael@0: { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2 michael@0: | SEC_ASN1_CONSTRUCTED, michael@0: offsetof(NSSSMIMEEncryptionKeyPreference,id.subjectKeyID), michael@0: SEC_ASN1_SUB(SEC_OctetStringTemplate), michael@0: NSSSMIMEEncryptionKeyPref_SubjectKeyID }, michael@0: { 0, } michael@0: }; michael@0: michael@0: /* smime_cipher_map - map of SMIME symmetric "ciphers" to algtag & parameters */ michael@0: typedef struct { michael@0: unsigned long cipher; michael@0: SECOidTag algtag; michael@0: SECItem *parms; michael@0: PRBool enabled; /* in the user's preferences */ michael@0: PRBool allowed; /* per export policy */ michael@0: } smime_cipher_map_entry; michael@0: michael@0: /* global: list of supported SMIME symmetric ciphers, ordered roughly by increasing strength */ michael@0: static smime_cipher_map_entry smime_cipher_map[] = { michael@0: /* cipher algtag parms enabled allowed */ michael@0: /* ---------------------------------------------------------------------------------- */ michael@0: { SMIME_RC2_CBC_40, SEC_OID_RC2_CBC, ¶m_int40, PR_TRUE, PR_TRUE }, michael@0: { SMIME_DES_CBC_56, SEC_OID_DES_CBC, NULL, PR_TRUE, PR_TRUE }, michael@0: { SMIME_RC2_CBC_64, SEC_OID_RC2_CBC, ¶m_int64, PR_TRUE, PR_TRUE }, michael@0: { SMIME_RC2_CBC_128, SEC_OID_RC2_CBC, ¶m_int128, PR_TRUE, PR_TRUE }, michael@0: { SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC, NULL, PR_TRUE, PR_TRUE }, michael@0: { SMIME_AES_CBC_128, SEC_OID_AES_128_CBC, NULL, PR_TRUE, PR_TRUE }, michael@0: { SMIME_AES_CBC_256, SEC_OID_AES_256_CBC, NULL, PR_TRUE, PR_TRUE } michael@0: }; michael@0: static const int smime_cipher_map_count = sizeof(smime_cipher_map) / sizeof(smime_cipher_map_entry); michael@0: michael@0: /* michael@0: * smime_mapi_by_cipher - find index into smime_cipher_map by cipher 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_cipher_map_count; i++) { michael@0: if (smime_cipher_map[i].cipher == cipher) michael@0: return i; /* bingo */ michael@0: } michael@0: return -1; /* should not happen if we're consistent, right? */ michael@0: } michael@0: michael@0: /* michael@0: * NSS_SMIME_EnableCipher - this function locally records the user's preference michael@0: */ michael@0: SECStatus michael@0: NSS_SMIMEUtil_EnableCipher(unsigned long which, PRBool on) michael@0: { michael@0: unsigned long mask; michael@0: int mapi; michael@0: michael@0: mask = which & CIPHER_FAMILYID_MASK; 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: mapi = smime_mapi_by_cipher(which); michael@0: if (mapi < 0) michael@0: /* XXX set an error */ michael@0: return SECFailure; michael@0: michael@0: /* do we try to turn on a forbidden cipher? */ michael@0: if (!smime_cipher_map[mapi].allowed && on) { michael@0: PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (smime_cipher_map[mapi].enabled != on) michael@0: smime_cipher_map[mapi].enabled = on; 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: NSS_SMIMEUtil_AllowCipher(unsigned long which, PRBool on) michael@0: { michael@0: unsigned long mask; michael@0: int mapi; michael@0: michael@0: mask = which & CIPHER_FAMILYID_MASK; 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: mapi = smime_mapi_by_cipher(which); michael@0: if (mapi < 0) michael@0: /* XXX set an error */ michael@0: return SECFailure; michael@0: michael@0: if (smime_cipher_map[mapi].allowed != on) michael@0: smime_cipher_map[mapi].allowed = on; michael@0: michael@0: return SECSuccess; 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 SECStatus michael@0: nss_smime_get_cipher_for_alg_and_key(SECAlgorithmID *algid, PK11SymKey *key, unsigned long *cipher) michael@0: { michael@0: SECOidTag algtag; michael@0: unsigned int keylen_bits; michael@0: unsigned long c; michael@0: michael@0: algtag = SECOID_GetAlgorithmTag(algid); michael@0: switch (algtag) { michael@0: case SEC_OID_RC2_CBC: michael@0: keylen_bits = PK11_GetKeyStrength(key, algid); michael@0: switch (keylen_bits) { michael@0: case 40: michael@0: c = SMIME_RC2_CBC_40; michael@0: break; michael@0: case 64: michael@0: c = SMIME_RC2_CBC_64; michael@0: break; michael@0: case 128: michael@0: c = SMIME_RC2_CBC_128; michael@0: break; michael@0: default: michael@0: return SECFailure; michael@0: } michael@0: break; michael@0: case SEC_OID_DES_CBC: michael@0: c = SMIME_DES_CBC_56; michael@0: break; michael@0: case SEC_OID_DES_EDE3_CBC: michael@0: c = SMIME_DES_EDE3_168; michael@0: break; michael@0: case SEC_OID_AES_128_CBC: michael@0: c = SMIME_AES_CBC_128; michael@0: break; michael@0: case SEC_OID_AES_256_CBC: michael@0: c = SMIME_AES_CBC_256; michael@0: break; michael@0: default: michael@0: PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); michael@0: return SECFailure; michael@0: } michael@0: *cipher = c; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: static PRBool michael@0: nss_smime_cipher_allowed(unsigned long which) michael@0: { michael@0: int mapi; michael@0: michael@0: mapi = smime_mapi_by_cipher(which); michael@0: if (mapi < 0) michael@0: return PR_FALSE; michael@0: return smime_cipher_map[mapi].allowed; michael@0: } michael@0: michael@0: PRBool michael@0: NSS_SMIMEUtil_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key) michael@0: { michael@0: unsigned long which; michael@0: michael@0: if (nss_smime_get_cipher_for_alg_and_key(algid, key, &which) != SECSuccess) michael@0: return PR_FALSE; michael@0: michael@0: return nss_smime_cipher_allowed(which); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * NSS_SMIME_EncryptionPossible - check if any encryption is allowed 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: NSS_SMIMEUtil_EncryptionPossible(void) michael@0: { michael@0: int i; michael@0: michael@0: for (i = 0; i < smime_cipher_map_count; i++) { michael@0: if (smime_cipher_map[i].allowed) michael@0: return PR_TRUE; michael@0: } michael@0: return PR_FALSE; michael@0: } michael@0: michael@0: michael@0: static int michael@0: nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap) michael@0: { michael@0: int i; michael@0: SECOidTag capIDTag; michael@0: michael@0: /* we need the OIDTag here */ michael@0: capIDTag = SECOID_FindOIDTag(&(cap->capabilityID)); michael@0: michael@0: /* go over all the SMIME ciphers we know and see if we find a match */ michael@0: for (i = 0; i < smime_cipher_map_count; i++) { michael@0: if (smime_cipher_map[i].algtag != capIDTag) 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 (!smime_cipher_map[i].parms) { michael@0: if (!cap->parameters.data || !cap->parameters.len) michael@0: break; /* both empty: bingo */ michael@0: if (cap->parameters.len == 2 && michael@0: cap->parameters.data[0] == SEC_ASN1_NULL && michael@0: cap->parameters.data[1] == 0) michael@0: break; /* DER NULL == NULL, bingo */ michael@0: } else if (cap->parameters.data != NULL && michael@0: cap->parameters.len == smime_cipher_map[i].parms->len && michael@0: PORT_Memcmp (cap->parameters.data, smime_cipher_map[i].parms->data, michael@0: cap->parameters.len) == 0) michael@0: { michael@0: break; /* both not empty, same length & equal content: bingo */ michael@0: } michael@0: } michael@0: michael@0: if (i == smime_cipher_map_count) michael@0: return 0; /* no match found */ michael@0: return smime_cipher_map[i].cipher; /* match found, point to cipher */ michael@0: } michael@0: michael@0: /* michael@0: * smime_choose_cipher - choose a cipher that works for all the recipients michael@0: * michael@0: * "scert" - sender's certificate michael@0: * "rcerts" - recipient's certificates michael@0: */ michael@0: static long michael@0: smime_choose_cipher(CERTCertificate *scert, CERTCertificate **rcerts) michael@0: { michael@0: PLArenaPool *poolp; michael@0: long cipher; michael@0: long chosen_cipher; michael@0: int *cipher_abilities; michael@0: int *cipher_votes; michael@0: int weak_mapi; michael@0: int strong_mapi; michael@0: int aes128_mapi; michael@0: int aes256_mapi; michael@0: int rcount, mapi, max, i; michael@0: michael@0: chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */ michael@0: weak_mapi = smime_mapi_by_cipher(chosen_cipher); michael@0: aes128_mapi = smime_mapi_by_cipher(SMIME_AES_CBC_128); michael@0: aes256_mapi = smime_mapi_by_cipher(SMIME_AES_CBC_256); 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, smime_cipher_map_count * sizeof(int)); michael@0: cipher_votes = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int)); michael@0: if (cipher_votes == NULL || cipher_abilities == NULL) michael@0: goto done; 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: /* walk all the recipient's certs */ michael@0: for (rcount = 0; rcerts[rcount] != NULL; rcount++) { michael@0: SECItem *profile; michael@0: NSSSMIMECapability **caps; michael@0: int pref; michael@0: michael@0: /* the first cipher that matches in the user's SMIME profile gets michael@0: * "smime_cipher_map_count" votes; the next one gets "smime_cipher_map_count" - 1 michael@0: * and so on. If every cipher matches, the last one gets 1 (one) vote */ michael@0: pref = smime_cipher_map_count; michael@0: michael@0: /* find recipient's SMIME profile */ michael@0: profile = CERT_FindSMimeProfile(rcerts[rcount]); michael@0: michael@0: if (profile != NULL && profile->data != NULL && profile->len > 0) { michael@0: /* we have a profile (still DER-encoded) */ michael@0: caps = NULL; michael@0: /* decode it */ michael@0: if (SEC_QuickDERDecodeItem(poolp, &caps, michael@0: NSSSMIMECapabilitiesTemplate, profile) == SECSuccess && michael@0: caps != NULL) michael@0: { michael@0: /* walk the SMIME capabilities for this recipient */ michael@0: for (i = 0; caps[i] != NULL; i++) { michael@0: cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]); michael@0: mapi = smime_mapi_by_cipher(cipher); michael@0: if (mapi >= 0) { michael@0: /* found the cipher */ 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: /* no profile found - so we can only assume that the user can do michael@0: * the mandatory algorithms which are RC2-40 (weak crypto) and michael@0: * 3DES (strong crypto), unless the user has an elliptic curve michael@0: * key. For elliptic curve keys, RFC 5753 mandates support michael@0: * for AES 128 CBC. */ michael@0: SECKEYPublicKey *key; michael@0: unsigned int pklen_bits; michael@0: KeyType key_type; michael@0: michael@0: /* michael@0: * if recipient's public key length is > 512, vote for a strong cipher michael@0: * please not that the side effect of this is that if only one recipient michael@0: * has an export-level public key, the strong cipher is disabled. 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: pklen_bits = 0; michael@0: if (key != NULL) { michael@0: pklen_bits = SECKEY_PublicKeyStrengthInBits (key); michael@0: key_type = SECKEY_GetPublicKeyType(key); michael@0: SECKEY_DestroyPublicKey (key); michael@0: } michael@0: michael@0: if (key_type == ecKey) { michael@0: /* While RFC 5753 mandates support for AES-128 CBC, should use michael@0: * AES 256 if user's key provides more than 128 bits of michael@0: * security strength so that symmetric key is not weak link. */ michael@0: michael@0: /* RC2-40 is not compatible with elliptic curve keys. */ michael@0: chosen_cipher = SMIME_DES_EDE3_168; michael@0: if (pklen_bits > 256) { michael@0: cipher_abilities[aes256_mapi]++; michael@0: cipher_votes[aes256_mapi] += pref; michael@0: pref--; michael@0: } michael@0: cipher_abilities[aes128_mapi]++; michael@0: cipher_votes[aes128_mapi] += pref; michael@0: pref--; michael@0: cipher_abilities[strong_mapi]++; michael@0: cipher_votes[strong_mapi] += pref; michael@0: pref--; michael@0: } else { michael@0: if (pklen_bits > 512) { michael@0: /* cast votes for the strong algorithm */ michael@0: cipher_abilities[strong_mapi]++; michael@0: cipher_votes[strong_mapi] += pref; michael@0: pref--; michael@0: } michael@0: michael@0: /* always cast (possibly less) votes for the weak algorithm */ michael@0: cipher_abilities[weak_mapi]++; michael@0: cipher_votes[weak_mapi] += pref; michael@0: } michael@0: } michael@0: if (profile != NULL) michael@0: SECITEM_FreeItem(profile, PR_TRUE); michael@0: } michael@0: michael@0: /* find cipher that is agreeable by all recipients and that has the most votes */ michael@0: max = 0; michael@0: for (mapi = 0; mapi < smime_cipher_map_count; mapi++) { michael@0: /* if not all of the recipients can do this, forget it */ michael@0: if (cipher_abilities[mapi] != rcount) michael@0: continue; michael@0: /* if cipher is not enabled or not allowed by policy, forget it */ michael@0: if (!smime_cipher_map[mapi].enabled || !smime_cipher_map[mapi].allowed) michael@0: continue; michael@0: /* now see if this one has more votes than the last best one */ michael@0: if (cipher_votes[mapi] >= max) { michael@0: /* if equal number of votes, prefer the ones further down in the list */ michael@0: /* with the expectation that these are higher rated ciphers */ michael@0: chosen_cipher = smime_cipher_map[mapi].cipher; michael@0: max = cipher_votes[mapi]; michael@0: } michael@0: } michael@0: /* if no common cipher was found, chosen_cipher stays at the default */ 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: * 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: case SMIME_AES_CBC_128: michael@0: keysize = 128; michael@0: break; michael@0: case SMIME_AES_CBC_256: michael@0: keysize = 256; michael@0: break; 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: * NSS_SMIMEUtil_FindBulkAlgForRecipients - find bulk algorithm suitable for all recipients michael@0: * michael@0: * it would be great for UI purposes if there would be a way to find out which recipients michael@0: * prevented a strong cipher from being used... michael@0: */ michael@0: SECStatus michael@0: NSS_SMIMEUtil_FindBulkAlgForRecipients(CERTCertificate **rcerts, SECOidTag *bulkalgtag, int *keysize) michael@0: { michael@0: unsigned long cipher; michael@0: int mapi; michael@0: michael@0: cipher = smime_choose_cipher(NULL, rcerts); michael@0: mapi = smime_mapi_by_cipher(cipher); michael@0: michael@0: *bulkalgtag = smime_cipher_map[mapi].algtag; michael@0: *keysize = smime_keysize_by_cipher(smime_cipher_map[mapi].cipher); michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_SMIMEUtil_CreateSMIMECapabilities - get S/MIME capabilities for this instance of NSS michael@0: * michael@0: * scans the list of allowed and enabled ciphers and construct a PKCS9-compliant michael@0: * S/MIME capabilities attribute value. michael@0: * michael@0: * XXX Please note that, in contradiction to RFC2633 2.5.2, the capabilities only include michael@0: * symmetric ciphers, NO signature algorithms or key encipherment algorithms. michael@0: * michael@0: * "poolp" - arena pool to create the S/MIME capabilities data on michael@0: * "dest" - SECItem to put the data in michael@0: */ michael@0: SECStatus michael@0: NSS_SMIMEUtil_CreateSMIMECapabilities(PLArenaPool *poolp, SECItem *dest) michael@0: { michael@0: NSSSMIMECapability *cap; michael@0: NSSSMIMECapability **smime_capabilities; michael@0: smime_cipher_map_entry *map; michael@0: SECOidData *oiddata; michael@0: SECItem *dummy; michael@0: int i, capIndex; michael@0: michael@0: /* if we have an old NSSSMIMECapability array, we'll reuse it (has the right size) */ michael@0: /* smime_cipher_map_count + 1 is an upper bound - we might end up with less */ michael@0: smime_capabilities = (NSSSMIMECapability **)PORT_ZAlloc((smime_cipher_map_count + 1) michael@0: * sizeof(NSSSMIMECapability *)); michael@0: if (smime_capabilities == NULL) michael@0: return SECFailure; michael@0: michael@0: capIndex = 0; michael@0: michael@0: /* Add all the symmetric ciphers michael@0: * We walk the cipher list backwards, as it is ordered by increasing strength, michael@0: * we prefer the stronger cipher over a weaker one, and we have to list the michael@0: * preferred algorithm first */ michael@0: for (i = smime_cipher_map_count - 1; i >= 0; i--) { michael@0: /* Find the corresponding entry in the cipher map. */ michael@0: map = &(smime_cipher_map[i]); michael@0: if (!map->enabled) michael@0: continue; michael@0: michael@0: /* get next SMIME capability */ michael@0: cap = (NSSSMIMECapability *)PORT_ZAlloc(sizeof(NSSSMIMECapability)); michael@0: if (cap == NULL) michael@0: break; michael@0: smime_capabilities[capIndex++] = cap; michael@0: michael@0: oiddata = SECOID_FindOIDByTag(map->algtag); michael@0: if (oiddata == NULL) michael@0: break; michael@0: michael@0: cap->capabilityID.data = oiddata->oid.data; michael@0: cap->capabilityID.len = oiddata->oid.len; michael@0: cap->parameters.data = map->parms ? map->parms->data : NULL; michael@0: cap->parameters.len = map->parms ? map->parms->len : 0; michael@0: cap->cipher = smime_cipher_map[i].cipher; michael@0: } michael@0: michael@0: /* XXX add signature algorithms */ michael@0: /* XXX add key encipherment algorithms */ michael@0: michael@0: smime_capabilities[capIndex] = NULL; /* last one - now encode */ michael@0: dummy = SEC_ASN1EncodeItem(poolp, dest, &smime_capabilities, NSSSMIMECapabilitiesTemplate); michael@0: michael@0: /* now that we have the proper encoded SMIMECapabilities (or not), michael@0: * free the work data */ michael@0: for (i = 0; smime_capabilities[i] != NULL; i++) michael@0: PORT_Free(smime_capabilities[i]); michael@0: PORT_Free(smime_capabilities); michael@0: michael@0: return (dummy == NULL) ? SECFailure : SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value michael@0: * michael@0: * "poolp" - arena pool to create the attr value on michael@0: * "dest" - SECItem to put the data in michael@0: * "cert" - certificate that should be marked as preferred encryption key michael@0: * cert is expected to have been verified for EmailRecipient usage. michael@0: */ michael@0: SECStatus michael@0: NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs(PLArenaPool *poolp, SECItem *dest, CERTCertificate *cert) michael@0: { michael@0: NSSSMIMEEncryptionKeyPreference ekp; michael@0: SECItem *dummy = NULL; michael@0: PLArenaPool *tmppoolp = NULL; michael@0: michael@0: if (cert == NULL) michael@0: goto loser; michael@0: michael@0: tmppoolp = PORT_NewArena(1024); michael@0: if (tmppoolp == NULL) michael@0: goto loser; michael@0: michael@0: /* XXX hardcoded IssuerSN choice for now */ michael@0: ekp.selector = NSSSMIMEEncryptionKeyPref_IssuerSN; michael@0: ekp.id.issuerAndSN = CERT_GetCertIssuerAndSN(tmppoolp, cert); michael@0: if (ekp.id.issuerAndSN == NULL) michael@0: goto loser; michael@0: michael@0: dummy = SEC_ASN1EncodeItem(poolp, dest, &ekp, smime_encryptionkeypref_template); michael@0: michael@0: loser: michael@0: if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); michael@0: michael@0: return (dummy == NULL) ? SECFailure : SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs - create S/MIME encryption key preferences attr value using MS oid michael@0: * michael@0: * "poolp" - arena pool to create the attr value on michael@0: * "dest" - SECItem to put the data in michael@0: * "cert" - certificate that should be marked as preferred encryption key michael@0: * cert is expected to have been verified for EmailRecipient usage. michael@0: */ michael@0: SECStatus michael@0: NSS_SMIMEUtil_CreateMSSMIMEEncKeyPrefs(PLArenaPool *poolp, SECItem *dest, CERTCertificate *cert) michael@0: { michael@0: SECItem *dummy = NULL; michael@0: PLArenaPool *tmppoolp = NULL; michael@0: CERTIssuerAndSN *isn; michael@0: michael@0: if (cert == NULL) michael@0: goto loser; michael@0: michael@0: tmppoolp = PORT_NewArena(1024); michael@0: if (tmppoolp == NULL) michael@0: goto loser; michael@0: michael@0: isn = CERT_GetCertIssuerAndSN(tmppoolp, cert); michael@0: if (isn == NULL) michael@0: goto loser; michael@0: michael@0: dummy = SEC_ASN1EncodeItem(poolp, dest, isn, SEC_ASN1_GET(CERT_IssuerAndSNTemplate)); michael@0: michael@0: loser: michael@0: if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); michael@0: michael@0: return (dummy == NULL) ? SECFailure : SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference - michael@0: * find cert marked by EncryptionKeyPreference attribute michael@0: * michael@0: * "certdb" - handle for the cert database to look in michael@0: * "DERekp" - DER-encoded value of S/MIME Encryption Key Preference attribute michael@0: * michael@0: * if certificate is supposed to be found among the message's included certificates, michael@0: * they are assumed to have been imported already. michael@0: */ michael@0: CERTCertificate * michael@0: NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference(CERTCertDBHandle *certdb, SECItem *DERekp) michael@0: { michael@0: PLArenaPool *tmppoolp = NULL; michael@0: CERTCertificate *cert = NULL; michael@0: NSSSMIMEEncryptionKeyPreference ekp; michael@0: michael@0: tmppoolp = PORT_NewArena(1024); michael@0: if (tmppoolp == NULL) michael@0: return NULL; michael@0: michael@0: /* decode DERekp */ michael@0: if (SEC_QuickDERDecodeItem(tmppoolp, &ekp, smime_encryptionkeypref_template, michael@0: DERekp) != SECSuccess) michael@0: goto loser; michael@0: michael@0: /* find cert */ michael@0: switch (ekp.selector) { michael@0: case NSSSMIMEEncryptionKeyPref_IssuerSN: michael@0: cert = CERT_FindCertByIssuerAndSN(certdb, ekp.id.issuerAndSN); michael@0: break; michael@0: case NSSSMIMEEncryptionKeyPref_RKeyID: michael@0: case NSSSMIMEEncryptionKeyPref_SubjectKeyID: michael@0: /* XXX not supported yet - we need to be able to look up certs by SubjectKeyID */ michael@0: break; michael@0: default: michael@0: PORT_Assert(0); michael@0: } michael@0: loser: michael@0: if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); michael@0: michael@0: return cert; michael@0: } michael@0: michael@0: extern const char __nss_smime_rcsid[]; michael@0: extern const char __nss_smime_sccsid[]; michael@0: michael@0: PRBool michael@0: NSSSMIME_VersionCheck(const char *importedVersion) michael@0: { michael@0: /* michael@0: * This is the secret handshake algorithm. michael@0: * michael@0: * This release has a simple version compatibility michael@0: * check algorithm. This release is not backward michael@0: * compatible with previous major releases. It is michael@0: * not compatible with future major, minor, or michael@0: * patch releases. michael@0: */ michael@0: volatile char c; /* force a reference that won't get optimized away */ michael@0: michael@0: c = __nss_smime_rcsid[0] + __nss_smime_sccsid[0]; michael@0: michael@0: return NSS_VersionCheck(importedVersion); michael@0: } michael@0: michael@0: const char * michael@0: NSSSMIME_GetVersion(void) michael@0: { michael@0: return NSS_VERSION; michael@0: }