diff options
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/smime/smimeutil.c | 795 |
1 files changed, 795 insertions, 0 deletions
diff --git a/security/nss/lib/smime/smimeutil.c b/security/nss/lib/smime/smimeutil.c new file mode 100644 index 0000000000..2056195dbd --- /dev/null +++ b/security/nss/lib/smime/smimeutil.c @@ -0,0 +1,795 @@ +/* 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; +} |