1440 lines
45 KiB
C
1440 lines
45 KiB
C
/* 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 "sechash.h"
|
|
#include "cert.h"
|
|
#include "keyhi.h"
|
|
#include "secerr.h"
|
|
#include "cms.h"
|
|
#include "nss.h"
|
|
#include "prerror.h"
|
|
#include "prinit.h"
|
|
|
|
SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate)
|
|
SEC_ASN1_MKSUB(SEC_OctetStringTemplate)
|
|
SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate)
|
|
|
|
/*
|
|
* 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 }
|
|
};
|
|
|
|
/* table of implemented key exchange algorithms. As we add algorithms,
|
|
* update this table */
|
|
static const SECOidTag implemented_key_encipherment[] = {
|
|
SEC_OID_PKCS1_RSA_ENCRYPTION,
|
|
SEC_OID_DHSINGLEPASS_STDDH_SHA1KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_STDDH_SHA224KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_STDDH_SHA256KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_STDDH_SHA384KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_STDDH_SHA512KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_COFACTORDH_SHA1KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_COFACTORDH_SHA224KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_COFACTORDH_SHA256KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_COFACTORDH_SHA384KDF_SCHEME,
|
|
SEC_OID_DHSINGLEPASS_COFACTORDH_SHA512KDF_SCHEME,
|
|
};
|
|
static const int implemented_key_encipherment_len =
|
|
PR_ARRAY_SIZE(implemented_key_encipherment);
|
|
|
|
/* smime_cipher_map - map of SMIME symmetric "ciphers" to algtag & parameters */
|
|
typedef struct {
|
|
unsigned long cipher;
|
|
SECOidTag policytag;
|
|
} smime_legacy_map_entry;
|
|
|
|
/* legacy array of S/MIME values to map old SMIME entries to modern
|
|
* algtags. */
|
|
static const smime_legacy_map_entry smime_legacy_map[] = {
|
|
/* cipher, algtag, policy */
|
|
/* --------------------------------------- */
|
|
{ SMIME_RC2_CBC_40, SEC_OID_RC2_40_CBC },
|
|
{ SMIME_DES_CBC_56, SEC_OID_DES_CBC },
|
|
{ SMIME_RC2_CBC_64, SEC_OID_RC2_64_CBC },
|
|
{ SMIME_RC2_CBC_128, SEC_OID_RC2_128_CBC },
|
|
{ SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC },
|
|
{ SMIME_AES_CBC_128, SEC_OID_AES_128_CBC },
|
|
{ SMIME_AES_CBC_256, SEC_OID_AES_256_CBC },
|
|
};
|
|
static const int smime_legacy_map_count = PR_ARRAY_SIZE(smime_legacy_map);
|
|
|
|
static int
|
|
smime_legacy_pref(SECOidTag algtag)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < smime_legacy_map_count; i++) {
|
|
if (smime_legacy_map[i].policytag == algtag)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* smime_legacy_to policy - find policy algtag from a legacy input
|
|
*/
|
|
static SECOidTag
|
|
smime_legacy_to_policy(unsigned long which)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < smime_legacy_map_count; i++) {
|
|
if (smime_legacy_map[i].cipher == which)
|
|
return smime_legacy_map[i].policytag;
|
|
}
|
|
return SEC_OID_UNKNOWN;
|
|
}
|
|
|
|
/* map the old legacy values to modern oids. If the value isn't a recognized
|
|
* legacy value, assume it's a SECOidTag and continue. This allows us to use
|
|
* the old query and set interfaces with modern oids. */
|
|
SECOidTag
|
|
smime_legacy_to_oid(unsigned long which)
|
|
{
|
|
unsigned long mask;
|
|
|
|
/* NOTE: all the legacy values and a CIPHER_FAMILYID of 0x00010000,
|
|
* (CIPHER_FAMILYID_MASK is 0xffff0000). SECOidTags start at 0 and
|
|
* increase monotonically, so as long as there is less than 16K of
|
|
* tags, we can distinguish between values intended to be SMIME ciphers
|
|
* and values intended to be SECOidTags */
|
|
mask = which & CIPHER_FAMILYID_MASK;
|
|
if (mask == CIPHER_FAMILYID_SMIME) {
|
|
return smime_legacy_to_policy(which);
|
|
}
|
|
return (SECOidTag)which;
|
|
}
|
|
|
|
/* SEC_OID_RC2_CBC is actually 3 ciphers with different key lengths. All modern
|
|
* symmetric ciphers include the key length with the oid. To handle policy for
|
|
* the different keylengths, we include fake oids that let us map the policy based
|
|
* on key length */
|
|
static SECOidTag
|
|
smime_get_policy_tag_from_key_length(SECOidTag algtag, unsigned long keybits)
|
|
{
|
|
if (algtag == SEC_OID_RC2_CBC) {
|
|
switch (keybits) {
|
|
case 40:
|
|
return SEC_OID_RC2_40_CBC;
|
|
case 64:
|
|
return SEC_OID_RC2_64_CBC;
|
|
case 128:
|
|
return SEC_OID_RC2_128_CBC;
|
|
default:
|
|
break;
|
|
}
|
|
return SEC_OID_UNKNOWN;
|
|
}
|
|
return algtag;
|
|
}
|
|
|
|
PRBool
|
|
smime_allowed_by_policy(SECOidTag algtag, PRUint32 neededPolicy)
|
|
{
|
|
PRUint32 policyFlags;
|
|
|
|
/* some S/MIME algs map to the same underlying KEA mechanism,
|
|
* collaps them here */
|
|
if ((neededPolicy & (NSS_USE_ALG_IN_SMIME_KX | NSS_USE_ALG_IN_SMIME_KX_LEGACY)) != 0) {
|
|
CK_MECHANISM_TYPE mechType = PK11_AlgtagToMechanism(algtag);
|
|
switch (mechType) {
|
|
case CKM_ECDH1_DERIVE:
|
|
case CKM_ECDH1_COFACTOR_DERIVE:
|
|
algtag = SEC_OID_ECDH_KEA;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((NSS_GetAlgorithmPolicy(algtag, &policyFlags) == SECFailure) ||
|
|
((policyFlags & neededPolicy) != neededPolicy)) {
|
|
PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM);
|
|
return PR_FALSE;
|
|
}
|
|
return PR_TRUE;
|
|
}
|
|
|
|
/*
|
|
* We'll need this for the fake policy oids for RC2, but the
|
|
* rest of these should be moved to pk11wrap for generic
|
|
* algtag to key size values. We already need this for
|
|
* sec_pkcs5v2_key_length_by oid.
|
|
*/
|
|
static int
|
|
smime_keysize_by_cipher(SECOidTag algtag)
|
|
{
|
|
int keysize;
|
|
|
|
switch (algtag) {
|
|
case SEC_OID_RC2_40_CBC:
|
|
keysize = 40;
|
|
break;
|
|
case SEC_OID_RC2_64_CBC:
|
|
keysize = 64;
|
|
break;
|
|
case SEC_OID_RC2_128_CBC:
|
|
case SEC_OID_AES_128_CBC:
|
|
case SEC_OID_CAMELLIA_128_CBC:
|
|
keysize = 128;
|
|
break;
|
|
case SEC_OID_AES_192_CBC:
|
|
case SEC_OID_CAMELLIA_192_CBC:
|
|
keysize = 192;
|
|
break;
|
|
case SEC_OID_AES_256_CBC:
|
|
case SEC_OID_CAMELLIA_256_CBC:
|
|
keysize = 256;
|
|
break;
|
|
default:
|
|
keysize = 0;
|
|
break;
|
|
}
|
|
|
|
return keysize;
|
|
}
|
|
|
|
static int
|
|
smime_max_keysize_by_cipher(SECOidTag algtag)
|
|
{
|
|
int keysize = smime_keysize_by_cipher(algtag);
|
|
|
|
if (keysize == 0) {
|
|
CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(algtag);
|
|
return PK11_GetMaxKeyLength(mech) * PR_BITS_PER_BYTE;
|
|
}
|
|
return keysize;
|
|
}
|
|
|
|
SECOidTag
|
|
smime_get_alg_from_policy(SECOidTag policy)
|
|
{
|
|
switch (policy) {
|
|
case SEC_OID_RC2_40_CBC:
|
|
case SEC_OID_RC2_64_CBC:
|
|
case SEC_OID_RC2_128_CBC:
|
|
return SEC_OID_RC2_CBC;
|
|
default:
|
|
break;
|
|
}
|
|
return policy;
|
|
}
|
|
|
|
typedef struct SMIMEListStr {
|
|
SECOidTag *tags;
|
|
size_t space_len;
|
|
size_t array_len;
|
|
} SMIMEList;
|
|
|
|
static SMIMEList *smime_algorithm_list = NULL;
|
|
static PZLock *algorithm_list_lock = NULL;
|
|
static PRCallOnceType smime_init_arg = { 0 };
|
|
|
|
/* return the number of algorithms in the list */
|
|
size_t
|
|
smime_list_length(const SMIMEList *list)
|
|
{
|
|
if ((list == NULL) || (list->tags == NULL)) {
|
|
return 0;
|
|
}
|
|
return list->array_len;
|
|
}
|
|
|
|
/* find the index of the algtag in the list. If the algtag isn't on the list,
|
|
* return the size of the list */
|
|
size_t
|
|
smime_list_index_find(const SMIMEList *list, SECOidTag algtag)
|
|
{
|
|
int i;
|
|
if ((list == NULL) || (list->tags == NULL)) {
|
|
return 0;
|
|
}
|
|
for (i = 0; i < list->array_len; i++) {
|
|
if (algtag == list->tags[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
return list->array_len;
|
|
}
|
|
|
|
#define SMIME_CHUNK_COUNT 10
|
|
/* initialize and grow the list if necessary */
|
|
static SECStatus
|
|
smime_list_grow(SMIMEList **list)
|
|
{
|
|
/* first make sure the inital list is created */
|
|
if (*list == NULL) {
|
|
*list = PORT_ZNew(SMIMEList);
|
|
if (*list == NULL) {
|
|
return SECFailure;
|
|
}
|
|
}
|
|
/* make sure the tag array is intialized */
|
|
if ((*list)->tags == NULL) {
|
|
(*list)->tags = PORT_ZNewArray(SECOidTag, SMIME_CHUNK_COUNT);
|
|
if ((*list)->tags == NULL) {
|
|
return SECFailure;
|
|
}
|
|
(*list)->space_len = SMIME_CHUNK_COUNT;
|
|
}
|
|
/* grow the tag array if necessary */
|
|
if ((*list)->array_len == (*list)->space_len) {
|
|
SECOidTag *new_space;
|
|
size_t new_len = (*list)->space_len + SMIME_CHUNK_COUNT;
|
|
new_space = (SECOidTag *)PORT_Realloc((*list)->tags,
|
|
new_len * sizeof(SECOidTag));
|
|
if (new_space) {
|
|
return SECFailure;
|
|
}
|
|
(*list)->tags = new_space;
|
|
(*list)->space_len = new_len;
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* add a new algtag to the list. if the algtag is already on the list,
|
|
* do nothing */
|
|
static SECStatus
|
|
smime_list_add(SMIMEList **list, SECOidTag algtag)
|
|
{
|
|
SECStatus rv;
|
|
size_t array_len = smime_list_length(*list);
|
|
size_t c_index = smime_list_index_find(*list, algtag);
|
|
|
|
if (array_len != c_index) {
|
|
/* already on the list */
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* go the list if necessary */
|
|
rv = smime_list_grow(list);
|
|
if (rv != SECSuccess) {
|
|
return rv;
|
|
}
|
|
(*list)->tags[(*list)->array_len++] = algtag;
|
|
return SECSuccess;
|
|
}
|
|
|
|
static SECStatus
|
|
smime_list_remove(SMIMEList *list, SECOidTag algtag)
|
|
{
|
|
size_t c_index, i;
|
|
size_t cipher_count = smime_list_length(list);
|
|
|
|
if (cipher_count == 0) {
|
|
return SECSuccess;
|
|
}
|
|
c_index = smime_list_index_find(list, algtag);
|
|
if (c_index == cipher_count) {
|
|
/* already removed from the list */
|
|
return SECSuccess;
|
|
}
|
|
for (i = c_index; i < cipher_count - 1; i++) {
|
|
list->tags[i] = list->tags[i + 1];
|
|
}
|
|
list->array_len--;
|
|
list->tags[i] = 0;
|
|
return SECSuccess;
|
|
}
|
|
|
|
static SECOidTag
|
|
smime_list_fetch_by_index(const SMIMEList *list, size_t c_index)
|
|
{
|
|
size_t cipher_count = smime_list_length(list);
|
|
|
|
if (c_index >= cipher_count) {
|
|
return SEC_OID_UNKNOWN;
|
|
}
|
|
/* we know this is safe because list cipher_count is non-zero (if it were
|
|
* any value of c_index will cause the above if to trigger */
|
|
return list->tags[c_index];
|
|
}
|
|
|
|
static void
|
|
smime_free_list(SMIMEList **list)
|
|
{
|
|
if (*list) {
|
|
if ((*list)->tags) {
|
|
PORT_Free((*list)->tags);
|
|
}
|
|
PORT_Free(*list);
|
|
}
|
|
*list = NULL;
|
|
}
|
|
|
|
static void
|
|
smime_lock_algorithm_list(void)
|
|
{
|
|
PORT_Assert(algorithm_list_lock);
|
|
if (algorithm_list_lock) {
|
|
PZ_Lock(algorithm_list_lock);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
smime_unlock_algorithm_list(void)
|
|
{
|
|
PORT_Assert(algorithm_list_lock);
|
|
if (algorithm_list_lock) {
|
|
PZ_Unlock(algorithm_list_lock);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static SECStatus
|
|
smime_shutdown(void *appData, void *nssData)
|
|
{
|
|
if (algorithm_list_lock) {
|
|
PZ_DestroyLock(algorithm_list_lock);
|
|
algorithm_list_lock = NULL;
|
|
}
|
|
smime_free_list(&smime_algorithm_list);
|
|
memset(&smime_init_arg, 0, sizeof(smime_init_arg));
|
|
return SECSuccess;
|
|
}
|
|
|
|
static PRStatus
|
|
smime_init_once(void *arg)
|
|
{
|
|
SECOidTag *tags = NULL;
|
|
SECStatus rv;
|
|
int tagCount;
|
|
int i;
|
|
int *error = (int *)arg;
|
|
int *lengths = NULL;
|
|
int *legacy_prefs = NULL;
|
|
|
|
rv = NSS_RegisterShutdown(smime_shutdown, NULL);
|
|
if (rv != SECSuccess) {
|
|
*error = PORT_GetError();
|
|
return PR_FAILURE;
|
|
}
|
|
algorithm_list_lock = PZ_NewLock(nssILockCache);
|
|
if (algorithm_list_lock == NULL) {
|
|
*error = PORT_GetError();
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
/* At initialization time, we need to set up the defaults. We first
|
|
* look to see if the system or application has set up certain algorithms
|
|
* by policy. If they have set up values by policy we'll only allow those
|
|
* algorithms. We'll then look to see if any algorithms are enabled by
|
|
* the application. */
|
|
rv = NSS_GetAlgorithmPolicyAll(NSS_USE_ALG_IN_SMIME_LEGACY,
|
|
NSS_USE_ALG_IN_SMIME_LEGACY,
|
|
&tags, &tagCount);
|
|
if (tags) {
|
|
PORT_Free(tags);
|
|
tags = NULL;
|
|
}
|
|
if ((rv != SECSuccess) || (tagCount == 0)) {
|
|
/* No algorithms have been enabled by policy (either by the system
|
|
* or by the application, we then will use the traditional default
|
|
* algorithms from the policy map */
|
|
for (i = smime_legacy_map_count - 1; i >= 0; i--) {
|
|
SECOidTag policytag = smime_legacy_map[i].policytag;
|
|
/* this enables the algorithm by policy. We need this or
|
|
* the policy code will reject attempts to use it */
|
|
NSS_SetAlgorithmPolicy(policytag, NSS_USE_ALG_IN_SMIME, 0);
|
|
/* We also need to enable the algorithm. This is usually unde
|
|
* application control once the defaults are set up, so the
|
|
* application can turn off a policy that is already on, but
|
|
* not turn on a policy that is already off */
|
|
smime_list_add(&smime_algorithm_list, policytag);
|
|
}
|
|
return PR_SUCCESS;
|
|
}
|
|
/* We have a system supplied policy, do we also have
|
|
* system supplied defaults? If we do we will only actually
|
|
* turn on the algorithms that have been specified. */
|
|
rv = NSS_GetAlgorithmPolicyAll(NSS_USE_DEFAULT_NOT_VALID |
|
|
NSS_USE_DEFAULT_SMIME_ENABLE,
|
|
NSS_USE_DEFAULT_SMIME_ENABLE,
|
|
&tags, &tagCount);
|
|
/* if none found, enable the default algorithms */
|
|
if ((rv != SECSuccess) || (tagCount == 0)) {
|
|
if (tags) {
|
|
PORT_Free(tags);
|
|
tags = NULL;
|
|
}
|
|
for (i = smime_legacy_map_count - 1; i >= 0; i--) {
|
|
SECOidTag policytag = smime_legacy_map[i].policytag;
|
|
/* we only enable the default algorithm, we don't change
|
|
* it's policy, which the system has already set. NOTE:
|
|
* what 'enable' means in the S/MIME sense is we advertise
|
|
* that we can do the given algorithm in our smime capabilities. */
|
|
smime_list_add(&smime_algorithm_list, policytag);
|
|
}
|
|
return PR_SUCCESS;
|
|
}
|
|
|
|
/* Sort tags by key strength here */
|
|
lengths = PORT_ZNewArray(int, tagCount);
|
|
if (lengths == NULL) {
|
|
*error = PORT_GetError();
|
|
goto loser;
|
|
}
|
|
legacy_prefs = PORT_ZNewArray(int, tagCount);
|
|
if (lengths == NULL) {
|
|
*error = PORT_GetError();
|
|
goto loser;
|
|
}
|
|
/* Sort the tags array, highest preference at index 0 */
|
|
for (i = 0; i < tagCount; i++) {
|
|
int len = smime_max_keysize_by_cipher(tags[i]);
|
|
int lpref = smime_legacy_pref(tags[i]);
|
|
SECOidTag current = tags[i];
|
|
PRBool shift = PR_FALSE;
|
|
int j;
|
|
/* Determine best position for tags[i].
|
|
* For each position j, check if tags [i] has a higher preference.
|
|
* If yes, store tags[i] at position j, and move all following
|
|
* entries one position to the back of the array.
|
|
*/
|
|
for (j = 0; j < i; j++) {
|
|
int tlen = lengths[j];
|
|
int tpref = legacy_prefs[j];
|
|
SECOidTag ttag = tags[j];
|
|
/* we prefer ciphers with bigger keysizes, then
|
|
* we prefer ciphers in our historical list,
|
|
* then we prefer ciphers that show up first
|
|
* from the oid table */
|
|
if (shift || (len > tlen) || ((len == tlen) && (lpref > tpref))) {
|
|
tags[j] = current;
|
|
lengths[j] = len;
|
|
legacy_prefs[j] = lpref;
|
|
current = ttag;
|
|
len = tlen;
|
|
lpref = tpref;
|
|
shift = PR_TRUE;
|
|
}
|
|
}
|
|
tags[i] = current;
|
|
lengths[i] = len;
|
|
legacy_prefs[i] = lpref;
|
|
}
|
|
|
|
/* put them in the enable list */
|
|
for (i = 0; i < tagCount; i++) {
|
|
smime_list_add(&smime_algorithm_list, tags[i]);
|
|
}
|
|
PORT_Free(lengths);
|
|
PORT_Free(legacy_prefs);
|
|
PORT_Free(tags);
|
|
return PR_SUCCESS;
|
|
loser:
|
|
if (lengths)
|
|
PORT_Free(lengths);
|
|
if (legacy_prefs)
|
|
PORT_Free(legacy_prefs);
|
|
if (tags)
|
|
PORT_Free(tags);
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
static SECStatus
|
|
smime_init(void)
|
|
{
|
|
static PRBool smime_policy_initted = PR_FALSE;
|
|
static int error = 0;
|
|
PRStatus nrv;
|
|
|
|
/* has NSS been initialized? */
|
|
if (!NSS_IsInitialized()) {
|
|
PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
|
|
return SECFailure;
|
|
}
|
|
if (smime_policy_initted) {
|
|
return SECSuccess;
|
|
}
|
|
nrv = PR_CallOnceWithArg(&smime_init_arg, smime_init_once, &error);
|
|
if (nrv == PR_SUCCESS) {
|
|
smime_policy_initted = PR_TRUE;
|
|
return SECSuccess;
|
|
}
|
|
PORT_SetError(error);
|
|
return SECFailure;
|
|
}
|
|
|
|
/*
|
|
* NSS_SMIME_EnableCipher - this function locally records the user's preference
|
|
*/
|
|
SECStatus
|
|
NSS_SMIMEUtil_EnableCipher(unsigned long which, PRBool on)
|
|
{
|
|
SECOidTag algtag;
|
|
|
|
SECStatus rv = smime_init();
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
algtag = smime_legacy_to_oid(which);
|
|
if (!smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME)) {
|
|
PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM);
|
|
return SECFailure;
|
|
}
|
|
|
|
smime_lock_algorithm_list();
|
|
if (on) {
|
|
rv = smime_list_add(&smime_algorithm_list, algtag);
|
|
} else {
|
|
rv = smime_list_remove(smime_algorithm_list, algtag);
|
|
}
|
|
smime_unlock_algorithm_list();
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* this function locally records the export policy
|
|
*/
|
|
SECStatus
|
|
NSS_SMIMEUtil_AllowCipher(unsigned long which, PRBool on)
|
|
{
|
|
SECOidTag algtag = smime_legacy_to_oid(which);
|
|
PRUint32 set = on ? NSS_USE_ALG_IN_SMIME : 0;
|
|
PRUint32 clear = on ? 0 : NSS_USE_ALG_IN_SMIME;
|
|
/* make sure we are inited before setting, so
|
|
* the defaults are correct */
|
|
SECStatus rv = smime_init();
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
return NSS_SetAlgorithmPolicy(algtag, set, clear);
|
|
}
|
|
|
|
PRBool
|
|
NSS_SMIMEUtil_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
|
|
{
|
|
SECOidTag algtag;
|
|
/* make sure we are inited before checking policy, so
|
|
* the defaults are correct */
|
|
SECStatus rv = smime_init();
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
algtag = smime_get_policy_tag_from_key_length(SECOID_GetAlgorithmTag(algid),
|
|
PK11_GetKeyStrength(key, algid));
|
|
return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_LEGACY);
|
|
}
|
|
|
|
PRBool
|
|
NSS_SMIMEUtil_EncryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
|
|
{
|
|
SECOidTag algtag;
|
|
/* make sure we are inited before checking policy, so
|
|
* the defaults are correct */
|
|
SECStatus rv = smime_init();
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
algtag = smime_get_policy_tag_from_key_length(SECOID_GetAlgorithmTag(algid),
|
|
PK11_GetKeyStrength(key, algid));
|
|
return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME);
|
|
}
|
|
|
|
PRBool
|
|
NSS_SMIMEUtil_SigningAllowed(SECAlgorithmID *algid)
|
|
{
|
|
SECOidTag algtag;
|
|
/* we don't adjust SIGNATURE policy based on defaults, so no need
|
|
* to call smime_init() */
|
|
|
|
algtag = SECOID_GetAlgorithmTag(algid);
|
|
return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_SIGNATURE);
|
|
}
|
|
|
|
static PRBool
|
|
nss_smime_enforce_key_size(void)
|
|
{
|
|
PRInt32 optFlags;
|
|
|
|
if (NSS_OptionGet(NSS_KEY_SIZE_POLICY_FLAGS, &optFlags) != SECFailure) {
|
|
if (optFlags & NSS_KEY_SIZE_POLICY_SMIME_FLAG) {
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
PRBool
|
|
NSS_SMIMEUtil_KeyEncodingAllowed(SECAlgorithmID *algid, CERTCertificate *cert,
|
|
SECKEYPublicKey *key)
|
|
{
|
|
SECOidTag algtag;
|
|
/* we don't adjust KEA policy based on defaults, so no need
|
|
* to call smime_init() */
|
|
|
|
/* if required, make sure the key lengths are enforced */
|
|
if (nss_smime_enforce_key_size()) {
|
|
SECStatus rv;
|
|
PRBool freeKey = PR_FALSE;
|
|
|
|
if (!key) {
|
|
/* either the public key or the cert must be supplied. If the
|
|
* key wasn't supplied, get it from the certificate */
|
|
if (!cert) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return PR_FALSE;
|
|
}
|
|
key = CERT_ExtractPublicKey(cert);
|
|
freeKey = PR_TRUE;
|
|
}
|
|
rv = SECKEY_EnforceKeySize(key->keyType,
|
|
SECKEY_PublicKeyStrengthInBits(key),
|
|
SEC_ERROR_BAD_EXPORT_ALGORITHM);
|
|
if (freeKey) {
|
|
SECKEY_DestroyPublicKey(key);
|
|
}
|
|
if (rv != SECSuccess) {
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
algtag = SECOID_GetAlgorithmTag(algid);
|
|
return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_KX);
|
|
}
|
|
|
|
PRBool
|
|
NSS_SMIMEUtil_KeyDecodingAllowed(SECAlgorithmID *algid, SECKEYPrivateKey *key)
|
|
{
|
|
SECOidTag algtag;
|
|
/* we don't adjust KEA policy based on defaults, so no need
|
|
* to call smime_init() */
|
|
|
|
/* if required, make sure the key lengths are enforced */
|
|
if (nss_smime_enforce_key_size()) {
|
|
SECStatus rv;
|
|
rv = SECKEY_EnforceKeySize(key->keyType,
|
|
SECKEY_PrivateKeyStrengthInBits(key),
|
|
SEC_ERROR_BAD_EXPORT_ALGORITHM);
|
|
if (rv != SECSuccess) {
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
algtag = SECOID_GetAlgorithmTag(algid);
|
|
return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME_KX_LEGACY);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
SECStatus rv = smime_init();
|
|
size_t len;
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
smime_lock_algorithm_list();
|
|
len = smime_list_length(smime_algorithm_list);
|
|
smime_unlock_algorithm_list();
|
|
return len != 0 ? PR_TRUE : PR_FALSE;
|
|
}
|
|
|
|
PRBool
|
|
NSS_SMIMEUtil_EncryptionEnabled(int which)
|
|
{
|
|
SECOidTag algtag;
|
|
size_t c_index, len;
|
|
|
|
SECStatus rv = smime_init();
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
algtag = smime_legacy_to_oid(which);
|
|
|
|
smime_lock_algorithm_list();
|
|
len = smime_list_length(smime_algorithm_list);
|
|
c_index = smime_list_index_find(smime_algorithm_list, algtag);
|
|
smime_unlock_algorithm_list();
|
|
|
|
if (len >= c_index) {
|
|
return PR_FALSE;
|
|
}
|
|
|
|
return smime_allowed_by_policy(algtag, NSS_USE_ALG_IN_SMIME);
|
|
}
|
|
|
|
static SECOidTag
|
|
nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap)
|
|
{
|
|
SECOidTag capIDTag;
|
|
|
|
/* we need the OIDTag here */
|
|
capIDTag = SECOID_FindOIDTag(&(cap->capabilityID));
|
|
|
|
/* RC2 used a generic oid and encoded the key length in the
|
|
* parameters */
|
|
if (capIDTag == SEC_OID_RC2_CBC) {
|
|
SECStatus rv;
|
|
unsigned long key_bits;
|
|
SECItem keyItem = { siBuffer, NULL, 0 };
|
|
|
|
rv = SEC_ASN1DecodeItem(NULL, &keyItem,
|
|
SEC_ASN1_GET(SEC_IntegerTemplate), &cap->parameters);
|
|
if (rv != SECSuccess) {
|
|
return SEC_OID_UNKNOWN;
|
|
}
|
|
rv = SEC_ASN1DecodeInteger(&keyItem, &key_bits);
|
|
SECITEM_FreeItem(&keyItem, PR_FALSE);
|
|
if (rv != SECSuccess) {
|
|
return SEC_OID_UNKNOWN;
|
|
}
|
|
return smime_get_policy_tag_from_key_length(capIDTag, key_bits);
|
|
}
|
|
|
|
/* everything else uses a null parameter */
|
|
if (!cap->parameters.data || !cap->parameters.len) {
|
|
return capIDTag;
|
|
}
|
|
if (cap->parameters.len == 2 &&
|
|
cap->parameters.data[0] == SEC_ASN1_NULL &&
|
|
cap->parameters.data[1] == 0) {
|
|
return capIDTag;
|
|
}
|
|
return SEC_OID_UNKNOWN;
|
|
}
|
|
|
|
/*
|
|
* smime_choose_cipher - choose a cipher that works for all the recipients
|
|
*
|
|
* "rcerts" - recipient's certificates
|
|
*/
|
|
static SECOidTag
|
|
smime_choose_cipher(CERTCertificate **rcerts)
|
|
{
|
|
PLArenaPool *poolp = NULL;
|
|
SECOidTag chosen_cipher = SEC_OID_UNKNOWN;
|
|
size_t cipher_count;
|
|
SECOidTag cipher;
|
|
int *cipher_abilities;
|
|
int *cipher_votes;
|
|
size_t weak_index;
|
|
size_t strong_index;
|
|
size_t aes128_index;
|
|
size_t aes256_index;
|
|
size_t c_index;
|
|
int rcount, max;
|
|
|
|
smime_lock_algorithm_list();
|
|
cipher_count = smime_list_length(smime_algorithm_list);
|
|
if (cipher_count == 0) {
|
|
goto done;
|
|
}
|
|
|
|
chosen_cipher = SEC_OID_RC2_40_CBC; /* the default, LCD */
|
|
weak_index = smime_list_index_find(smime_algorithm_list, chosen_cipher);
|
|
strong_index = smime_list_index_find(smime_algorithm_list, SEC_OID_DES_EDE3_CBC);
|
|
aes128_index = smime_list_index_find(smime_algorithm_list, SEC_OID_AES_128_CBC);
|
|
aes256_index = smime_list_index_find(smime_algorithm_list, SEC_OID_AES_256_CBC);
|
|
/* make sure the default selected cipher is enabled */
|
|
if (weak_index == cipher_count) {
|
|
chosen_cipher = SEC_OID_DES_EDE3_CBC;
|
|
if (strong_index == cipher_count) {
|
|
chosen_cipher = SEC_OID_AES_128_CBC;
|
|
if (aes128_index == cipher_count) {
|
|
chosen_cipher = SEC_OID_AES_256_CBC;
|
|
if (aes256_index == cipher_count) {
|
|
/* none of the standard algorithms are enabled, If the
|
|
* recipients don't explicitly include a better cipher
|
|
* then fail */
|
|
chosen_cipher = SEC_OID_UNKNOWN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
poolp = PORT_NewArena(1024); /* XXX what is right value? */
|
|
if (poolp == NULL)
|
|
goto done;
|
|
|
|
cipher_abilities = PORT_ArenaZNewArray(poolp, int, cipher_count + 1);
|
|
cipher_votes = PORT_ArenaZNewArray(poolp, int, cipher_count + 1);
|
|
if (cipher_votes == NULL || cipher_abilities == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
/* Make triple-DES the strong cipher. */
|
|
|
|
/* 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
|
|
* "cipher_count" votes; the next one gets "cipher_count" - 1
|
|
* and so on. If every cipher matches, the last one gets 1 (one) vote */
|
|
pref = cipher_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) {
|
|
int i;
|
|
/* walk the SMIME capabilities for this recipient */
|
|
for (i = 0; caps[i] != NULL; i++) {
|
|
cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]);
|
|
c_index = smime_list_index_find(smime_algorithm_list, cipher);
|
|
if (c_index < cipher_count) {
|
|
/* found the cipher */
|
|
cipher_abilities[c_index]++;
|
|
cipher_votes[c_index] += 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. */
|
|
if (chosen_cipher == SEC_OID_RC2_40_CBC) {
|
|
chosen_cipher = SEC_OID_AES_128_CBC;
|
|
}
|
|
if (pklen_bits > 256) {
|
|
cipher_abilities[aes256_index]++;
|
|
cipher_votes[aes256_index] += pref;
|
|
pref--;
|
|
}
|
|
cipher_abilities[aes128_index]++;
|
|
cipher_votes[aes128_index] += pref;
|
|
pref--;
|
|
cipher_abilities[strong_index]++;
|
|
cipher_votes[strong_index] += 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_index]++;
|
|
cipher_votes[aes256_index] += 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_index]++;
|
|
cipher_votes[aes128_index] += pref;
|
|
pref--;
|
|
}
|
|
if (pklen_bits > 512) {
|
|
/* cast votes for the strong algorithm */
|
|
cipher_abilities[strong_index]++;
|
|
cipher_votes[strong_index] += pref;
|
|
pref--;
|
|
}
|
|
|
|
/* always cast (possibly less) votes for the weak algorithm */
|
|
cipher_abilities[weak_index]++;
|
|
cipher_votes[weak_index] += 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 (c_index = 0; c_index < cipher_count; c_index++) {
|
|
/* if not all of the recipients can do this, forget it */
|
|
if (cipher_abilities[c_index] != rcount)
|
|
continue;
|
|
cipher = smime_list_fetch_by_index(smime_algorithm_list, c_index);
|
|
/* if cipher is allowed by policy, forget it */
|
|
if (!smime_allowed_by_policy(cipher, NSS_USE_ALG_IN_SMIME)) {
|
|
continue;
|
|
}
|
|
/* now see if this one has more votes than the last best one */
|
|
if (cipher_votes[c_index] >= 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 = cipher;
|
|
max = cipher_votes[c_index];
|
|
}
|
|
}
|
|
/* if no common cipher was found, chosen_cipher stays at the default */
|
|
|
|
done:
|
|
smime_unlock_algorithm_list();
|
|
if (poolp != NULL)
|
|
PORT_FreeArena(poolp, PR_FALSE);
|
|
|
|
return chosen_cipher;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
SECOidTag cipher;
|
|
|
|
SECStatus rv = smime_init();
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
cipher = smime_choose_cipher(rcerts);
|
|
if (cipher == SEC_OID_UNKNOWN) {
|
|
PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM);
|
|
return SECFailure;
|
|
}
|
|
|
|
*bulkalgtag = smime_get_alg_from_policy(cipher);
|
|
*keysize = smime_keysize_by_cipher(cipher);
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
/*
|
|
* Create a new Capability from an oid tag
|
|
*/
|
|
static NSSSMIMECapability *
|
|
smime_create_capability(SECOidTag cipher)
|
|
{
|
|
NSSSMIMECapability *cap = NULL;
|
|
SECOidData *oiddata = NULL;
|
|
SECItem *dummy = NULL;
|
|
|
|
oiddata = SECOID_FindOIDByTag(smime_get_alg_from_policy(cipher));
|
|
if (oiddata == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
cap = PORT_ZNew(NSSSMIMECapability);
|
|
if (cap == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
cap->capabilityID.data = oiddata->oid.data;
|
|
cap->capabilityID.len = oiddata->oid.len;
|
|
if (cipher == SEC_OID_RC2_CBC) {
|
|
SECItem keyItem = { siBuffer, NULL, 0 };
|
|
unsigned long keybits = smime_get_alg_from_policy(cipher);
|
|
dummy = SEC_ASN1EncodeInteger(NULL, &keyItem, keybits);
|
|
if (dummy == NULL) {
|
|
PORT_Free(cap);
|
|
return NULL;
|
|
}
|
|
dummy = SEC_ASN1EncodeItem(NULL, &cap->parameters,
|
|
&keyItem, SEC_ASN1_GET(SEC_IntegerTemplate));
|
|
SECITEM_FreeItem(&keyItem, PR_FALSE);
|
|
if (dummy == NULL) {
|
|
PORT_Free(cap);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
cap->parameters.data = NULL;
|
|
cap->parameters.len = 0;
|
|
}
|
|
return cap;
|
|
}
|
|
/*
|
|
* 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 = NULL;
|
|
NSSSMIMECapability **smime_capabilities = NULL;
|
|
SECItem *dummy = NULL;
|
|
int i, capIndex;
|
|
int cap_count;
|
|
int cipher_count;
|
|
int hash_count;
|
|
|
|
SECStatus rv = smime_init();
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
/* First get the hash count */
|
|
for (i = HASH_AlgNULL + 1;; i++) {
|
|
if (HASH_GetHashOidTagByHashType(i) == SEC_OID_UNKNOWN) {
|
|
break;
|
|
}
|
|
}
|
|
hash_count = i - 1;
|
|
|
|
smime_lock_algorithm_list();
|
|
/* now get the cipher count */
|
|
cipher_count = smime_list_length(smime_algorithm_list);
|
|
if (cipher_count == 0) {
|
|
smime_unlock_algorithm_list();
|
|
PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
|
|
return SECFailure;
|
|
}
|
|
|
|
cap_count = cipher_count + hash_count + implemented_key_encipherment_len;
|
|
|
|
/* cipher_count + 1 is an upper bound - we might end up with less */
|
|
smime_capabilities = PORT_ZNewArray(NSSSMIMECapability *, cap_count + 1);
|
|
if (smime_capabilities == NULL) {
|
|
smime_unlock_algorithm_list();
|
|
return SECFailure;
|
|
}
|
|
|
|
capIndex = 0;
|
|
|
|
/* Add all the symmetric ciphers
|
|
* We walk the cipher list, as it is ordered by decreasing strength,
|
|
* we prefer the stronger cipher over a weaker one, and we have to list the
|
|
* preferred algorithm first */
|
|
for (i = 0; i < cipher_count; i++) {
|
|
SECOidTag cipher = smime_list_fetch_by_index(smime_algorithm_list, i);
|
|
|
|
/* is it allowed by policy? */
|
|
if (!smime_allowed_by_policy(cipher, NSS_USE_ALG_IN_SMIME)) {
|
|
continue;
|
|
}
|
|
cipher = smime_get_alg_from_policy(cipher);
|
|
|
|
cap = smime_create_capability(cipher);
|
|
if (cap == NULL)
|
|
break;
|
|
smime_capabilities[capIndex++] = cap;
|
|
}
|
|
/* add signature algorithms = hash algs.
|
|
* probably also need to figure how what
|
|
* actual signatures we support in secvfy
|
|
* as well. We currently don't look a these
|
|
* when choosing hash and signature (hash is
|
|
* chosen by the application and signature
|
|
* type is chosen by the signing cert/key) */
|
|
smime_unlock_algorithm_list();
|
|
for (i = HASH_AlgNULL + 1; i < hash_count + 1; i++) {
|
|
SECOidTag hash_alg = HASH_GetHashOidTagByHashType(i);
|
|
|
|
if (!smime_allowed_by_policy(hash_alg,
|
|
NSS_USE_ALG_IN_SMIME_SIGNATURE | NSS_USE_ALG_IN_SIGNATURE)) {
|
|
continue;
|
|
}
|
|
cap = smime_create_capability(hash_alg);
|
|
/* get next SMIME capability */
|
|
if (cap == NULL)
|
|
break;
|
|
smime_capabilities[capIndex++] = cap;
|
|
}
|
|
|
|
/* add key encipherment algorithms . These are static
|
|
* to the s/mime library, so we can just use the table.
|
|
* new kea algs should be implemented. We don't use these
|
|
* because the senders key pretty much selects what time
|
|
* of kea we are going to implement */
|
|
for (i = 0; i < implemented_key_encipherment_len; i++) {
|
|
SECOidTag kea_alg = implemented_key_encipherment[i];
|
|
|
|
if (!smime_allowed_by_policy(kea_alg, NSS_USE_ALG_IN_SMIME_KX)) {
|
|
continue;
|
|
}
|
|
cap = smime_create_capability(kea_alg);
|
|
/* get next SMIME capability */
|
|
if (cap == NULL)
|
|
break;
|
|
smime_capabilities[capIndex++] = cap;
|
|
}
|
|
|
|
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++) {
|
|
if (smime_capabilities[i]->parameters.data) {
|
|
PORT_Free(smime_capabilities[i]->parameters.data);
|
|
}
|
|
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;
|
|
}
|