#include "cmslocal.h"
#include "secoid.h"
#include "secitem.h"
#include "cert.h"
#include "SecSMIMEPriv.h"
#include <security_asn1/secasn1.h>
#include <security_asn1/secerr.h>
#include <Security/SecSMIME.h>
#include <Security/SecKeyPriv.h>
SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate)
SEC_ASN1_MKSUB(SEC_OctetStringTemplate)
SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate)
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 };
static CSSM_DATA param_int40 = { sizeof(asn1_int40), asn1_int40 };
static CSSM_DATA param_int64 = { sizeof(asn1_int64), asn1_int64 };
static CSSM_DATA param_int128 = { sizeof(asn1_int128), asn1_int128 };
typedef struct {
CSSM_DATA capabilityID;
CSSM_DATA parameters;
long cipher;
} NSSSMIMECapability;
static const SecAsn1Template 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 SecAsn1Template NSSSMIMECapabilitiesTemplate[] = {
{ SEC_ASN1_SEQUENCE_OF, 0, NSSSMIMECapabilityTemplate }
};
typedef enum {
NSSSMIMEEncryptionKeyPref_IssuerSN,
NSSSMIMEEncryptionKeyPref_RKeyID,
NSSSMIMEEncryptionKeyPref_SubjectKeyID
} NSSSMIMEEncryptionKeyPrefSelector;
typedef struct {
NSSSMIMEEncryptionKeyPrefSelector selector;
union {
SecCmsIssuerAndSN *issuerAndSN;
SecCmsRecipientKeyIdentifier *recipientKeyID;
CSSM_DATA_PTR subjectKeyID;
} id;
} NSSSMIMEEncryptionKeyPreference;
extern const SecAsn1Template SecCmsRecipientKeyIdentifierTemplate[];
static const SecAsn1Template smime_encryptionkeypref_template[] = {
{ SEC_ASN1_CHOICE,
offsetof(NSSSMIMEEncryptionKeyPreference,selector), NULL,
sizeof(NSSSMIMEEncryptionKeyPreference) },
{ SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
offsetof(NSSSMIMEEncryptionKeyPreference,id.issuerAndSN),
SEC_ASN1_SUB(SecCmsIssuerAndSNTemplate),
NSSSMIMEEncryptionKeyPref_IssuerSN },
{ SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | 1,
offsetof(NSSSMIMEEncryptionKeyPreference,id.recipientKeyID),
SecCmsRecipientKeyIdentifierTemplate,
NSSSMIMEEncryptionKeyPref_IssuerSN },
{ SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2,
offsetof(NSSSMIMEEncryptionKeyPreference,id.subjectKeyID),
SEC_ASN1_SUB(kSecAsn1OctetStringTemplate),
NSSSMIMEEncryptionKeyPref_SubjectKeyID },
{ 0, }
};
typedef struct {
unsigned long cipher;
SECOidTag algtag;
CSSM_DATA_PTR parms;
Boolean enabled;
Boolean allowed;
} smime_cipher_map_entry;
static smime_cipher_map_entry smime_cipher_map[] = {
{ 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_FORTEZZA, SEC_OID_FORTEZZA_SKIPJACK, NULL, PR_TRUE, PR_TRUE }
};
static const int smime_cipher_map_count = sizeof(smime_cipher_map) / sizeof(smime_cipher_map_entry);
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;
}
return -1;
}
OSStatus
SecSMIMEEnableCipher(uint32 which, Boolean on)
{
unsigned long mask;
int mapi;
mask = which & CIPHER_FAMILYID_MASK;
PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
if (mask != CIPHER_FAMILYID_SMIME)
return SECFailure;
mapi = smime_mapi_by_cipher(which);
if (mapi < 0)
return SECFailure;
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;
}
OSStatus
SecSMIMEAllowCipher(uint32 which, Boolean on)
{
unsigned long mask;
int mapi;
mask = which & CIPHER_FAMILYID_MASK;
PORT_Assert (mask == CIPHER_FAMILYID_SMIME);
if (mask != CIPHER_FAMILYID_SMIME)
return SECFailure;
mapi = smime_mapi_by_cipher(which);
if (mapi < 0)
return SECFailure;
if (smime_cipher_map[mapi].allowed != on)
smime_cipher_map[mapi].allowed = on;
return SECSuccess;
}
static OSStatus
nss_smime_get_cipher_for_alg_and_key(SECAlgorithmID *algid, SecSymmetricKeyRef key, unsigned long *cipher)
{
SECOidTag algtag;
unsigned int keylen_bits;
unsigned long c;
algtag = SECOID_GetAlgorithmTag(algid);
switch (algtag) {
case SEC_OID_RC2_CBC:
if (SecKeyGetStrengthInBits(key, algid, &keylen_bits))
return SECFailure;
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_FORTEZZA_SKIPJACK:
c = SMIME_FORTEZZA;
break;
default:
return SECFailure;
}
*cipher = c;
return SECSuccess;
}
static Boolean
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;
}
Boolean
SecSMIMEDecryptionAllowed(SECAlgorithmID *algid, SecSymmetricKeyRef 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);
}
Boolean
SecSMIMEEncryptionPossible(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 unsigned long
nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap)
{
int i;
SECOidTag capIDTag;
capIDTag = SECOID_FindOIDTag(&(cap->capabilityID));
for (i = 0; i < smime_cipher_map_count; i++) {
if (smime_cipher_map[i].algtag != capIDTag)
continue;
if (cap->parameters.Data == NULL && smime_cipher_map[i].parms == NULL)
break;
if (cap->parameters.Data != NULL && smime_cipher_map[i].parms != NULL &&
cap->parameters.Length == smime_cipher_map[i].parms->Length &&
PORT_Memcmp (cap->parameters.Data, smime_cipher_map[i].parms->Data,
cap->parameters.Length) == 0)
{
break;
}
}
if (i == smime_cipher_map_count)
return 0;
else
return smime_cipher_map[i].cipher;
}
static long
smime_choose_cipher(SecCertificateRef scert, SecCertificateRef *rcerts)
{
PRArenaPool *poolp;
long cipher;
long chosen_cipher;
int *cipher_abilities;
int *cipher_votes;
int weak_mapi;
int strong_mapi;
int rcount, mapi, max, i;
#if 1
Boolean scert_is_fortezza = PR_FALSE;
#else
Boolean scert_is_fortezza = (scert == NULL) ? PR_FALSE : PK11_FortezzaHasKEA(scert);
#endif
chosen_cipher = SMIME_RC2_CBC_40;
weak_mapi = smime_mapi_by_cipher(chosen_cipher);
poolp = PORT_NewArena (1024);
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;
strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168);
if (scert_is_fortezza) {
mapi = smime_mapi_by_cipher(SMIME_FORTEZZA);
if (mapi >= 0 && smime_cipher_map[mapi].enabled)
strong_mapi = mapi;
}
for (rcount = 0; rcerts[rcount] != NULL; rcount++) {
CSSM_DATA_PTR profile;
NSSSMIMECapability **caps;
int pref;
pref = smime_cipher_map_count;
profile = CERT_FindSMimeProfile(rcerts[rcount]);
if (profile != NULL && profile->Data != NULL && profile->Length > 0) {
caps = NULL;
if (SEC_ASN1DecodeItem(poolp, &caps, NSSSMIMECapabilitiesTemplate, profile) == SECSuccess &&
caps != NULL)
{
for (i = 0; caps[i] != NULL; i++) {
cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]);
mapi = smime_mapi_by_cipher(cipher);
if (mapi >= 0) {
cipher_abilities[mapi]++;
cipher_votes[mapi] += pref;
--pref;
}
}
}
} else {
SecPublicKeyRef key;
unsigned int pklen_bits;
key = CERT_ExtractPublicKey(rcerts[rcount]);
pklen_bits = 0;
if (key != NULL) {
SecKeyGetStrengthInBits(key, NULL, &pklen_bits);
SECKEY_DestroyPublicKey (key);
}
if (pklen_bits > 512) {
cipher_abilities[strong_mapi]++;
cipher_votes[strong_mapi] += pref;
pref--;
}
cipher_abilities[weak_mapi]++;
cipher_votes[weak_mapi] += pref;
}
if (profile != NULL)
SECITEM_FreeItem(profile, PR_TRUE);
}
max = 0;
for (mapi = 0; mapi < smime_cipher_map_count; mapi++) {
if (cipher_abilities[mapi] != rcount)
continue;
if (!smime_cipher_map[mapi].enabled || !smime_cipher_map[mapi].allowed)
continue;
if (!scert_is_fortezza && (smime_cipher_map[mapi].cipher == SMIME_FORTEZZA))
continue;
if (cipher_votes[mapi] >= max) {
chosen_cipher = smime_cipher_map[mapi].cipher;
max = cipher_votes[mapi];
}
}
done:
if (poolp != NULL)
PORT_FreeArena (poolp, PR_FALSE);
return chosen_cipher;
}
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_DES_CBC_56:
keysize = 64;
break;
case SMIME_DES_EDE3_168:
keysize = 192;
break;
case SMIME_FORTEZZA:
keysize = 0;
break;
default:
keysize = -1;
break;
}
return keysize;
}
OSStatus
SecSMIMEFindBulkAlgForRecipients(SecCertificateRef *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;
}
OSStatus
SecSMIMECreateSMIMECapabilities(SecArenaPoolRef pool, CSSM_DATA_PTR dest, Boolean includeFortezzaCiphers)
{
PLArenaPool *poolp = (PLArenaPool *)pool;
NSSSMIMECapability *cap;
NSSSMIMECapability **smime_capabilities;
smime_cipher_map_entry *map;
SECOidData *oiddata;
CSSM_DATA_PTR dummy;
int i, capIndex;
smime_capabilities = (NSSSMIMECapability **)PORT_ZAlloc((smime_cipher_map_count + 1)
* sizeof(NSSSMIMECapability *));
if (smime_capabilities == NULL)
return SECFailure;
capIndex = 0;
for (i = smime_cipher_map_count - 1; i >= 0; i--) {
map = &(smime_cipher_map[i]);
if (!map->enabled)
continue;
if ((!includeFortezzaCiphers) && (map->cipher == SMIME_FORTEZZA))
continue;
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.Length = oiddata->oid.Length;
cap->parameters.Data = map->parms ? map->parms->Data : NULL;
cap->parameters.Length = map->parms ? map->parms->Length : 0;
cap->cipher = smime_cipher_map[i].cipher;
}
smime_capabilities[capIndex] = NULL;
dummy = SEC_ASN1EncodeItem(poolp, dest, &smime_capabilities, NSSSMIMECapabilitiesTemplate);
for (i = 0; smime_capabilities[i] != NULL; i++)
PORT_Free(smime_capabilities[i]);
PORT_Free(smime_capabilities);
return (dummy == NULL) ? SECFailure : SECSuccess;
}
OSStatus
SecSMIMECreateSMIMEEncKeyPrefs(SecArenaPoolRef pool, CSSM_DATA_PTR dest, SecCertificateRef cert)
{
PLArenaPool *poolp = (PLArenaPool *)pool;
NSSSMIMEEncryptionKeyPreference ekp;
CSSM_DATA_PTR dummy = NULL;
PLArenaPool *tmppoolp = NULL;
if (cert == NULL)
goto loser;
tmppoolp = PORT_NewArena(1024);
if (tmppoolp == NULL)
goto loser;
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;
}
OSStatus
SecSMIMECreateMSSMIMEEncKeyPrefs(SecArenaPoolRef pool, CSSM_DATA_PTR dest, SecCertificateRef cert)
{
PLArenaPool *poolp = (PLArenaPool *)pool;
CSSM_DATA_PTR dummy = NULL;
PLArenaPool *tmppoolp = NULL;
SecCmsIssuerAndSN *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(SecCmsIssuerAndSNTemplate));
loser:
if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
return (dummy == NULL) ? SECFailure : SECSuccess;
}
SecCertificateRef
SecSMIMEGetCertFromEncryptionKeyPreference(SecKeychainRef keychainOrArray, CSSM_DATA_PTR DERekp)
{
PLArenaPool *tmppoolp = NULL;
SecCertificateRef cert = NULL;
NSSSMIMEEncryptionKeyPreference ekp;
tmppoolp = PORT_NewArena(1024);
if (tmppoolp == NULL)
return NULL;
if (SEC_ASN1DecodeItem(tmppoolp, &ekp, smime_encryptionkeypref_template, DERekp) != SECSuccess)
goto loser;
switch (ekp.selector) {
case NSSSMIMEEncryptionKeyPref_IssuerSN:
cert = CERT_FindCertByIssuerAndSN(keychainOrArray, NULL, NULL, ekp.id.issuerAndSN);
break;
case NSSSMIMEEncryptionKeyPref_RKeyID:
case NSSSMIMEEncryptionKeyPref_SubjectKeyID:
break;
default:
PORT_Assert(0);
}
loser:
if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE);
return cert;
}
#if 0
extern const char __nss_smime_rcsid[];
extern const char __nss_smime_sccsid[];
#endif
#if 0
Boolean
NSSSMIME_VersionCheck(const char *importedVersion)
{
#if 1
return PR_TRUE;
#else
volatile char c;
c = __nss_smime_rcsid[0] + __nss_smime_sccsid[0];
return NSS_VersionCheck(importedVersion);
#endif
}
#endif