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