X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_smime/lib/cmspubkey.c diff --git a/Security/libsecurity_smime/lib/cmspubkey.c b/Security/libsecurity_smime/lib/cmspubkey.c new file mode 100644 index 00000000..0f4d9f42 --- /dev/null +++ b/Security/libsecurity_smime/lib/cmspubkey.c @@ -0,0 +1,1449 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS public key crypto + */ + +#include "cmslocal.h" + +#include "secitem.h" +#include "secoid.h" +#include "cryptohi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ====== RSA ======================================================================= */ + +/* + * SecCmsUtilEncryptSymKeyRSA - wrap a symmetric key with RSA + * + * this function takes a symmetric key and encrypts it using an RSA public key + * according to PKCS#1 and RFC2633 (S/MIME) + */ +OSStatus +SecCmsUtilEncryptSymKeyRSA(PLArenaPool *poolp, SecCertificateRef cert, + SecSymmetricKeyRef bulkkey, + CSSM_DATA_PTR encKey) +{ + OSStatus rv; + SecPublicKeyRef publickey; + + rv = SecCertificateCopyPublicKey(cert,&publickey); + if (publickey == NULL) + return SECFailure; + + rv = SecCmsUtilEncryptSymKeyRSAPubKey(poolp, publickey, bulkkey, encKey); + CFRelease(publickey); + return rv; +} + +OSStatus +SecCmsUtilEncryptSymKeyRSAPubKey(PLArenaPool *poolp, + SecPublicKeyRef publickey, + SecSymmetricKeyRef bulkkey, CSSM_DATA_PTR encKey) +{ + OSStatus rv; + unsigned int data_len; + //KeyType keyType; + void *mark = NULL; + + mark = PORT_ArenaMark(poolp); + if (!mark) + goto loser; + +#if 0 + /* sanity check */ + keyType = SECKEY_GetPublicKeyType(publickey); + PORT_Assert(keyType == rsaKey); + if (keyType != rsaKey) { + goto loser; + } +#endif + /* allocate memory for the encrypted key */ + rv = SecKeyGetStrengthInBits(publickey, NULL, &data_len); + if (rv) + goto loser; + + // Convert length to bytes; + data_len >>= 2; + encKey->Data = (unsigned char*)PORT_ArenaAlloc(poolp, data_len); + encKey->Length = data_len; + if (encKey->Data == NULL) + goto loser; + + /* encrypt the key now */ + rv = WRAP_PubWrapSymKey(publickey, bulkkey, encKey); + if (rv != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + if (mark) { + PORT_ArenaRelease(poolp, mark); + } + return SECFailure; +} + +/* + * SecCmsUtilDecryptSymKeyRSA - unwrap a RSA-wrapped symmetric key + * + * this function takes an RSA-wrapped symmetric key and unwraps it, returning a symmetric + * key handle. Please note that the actual unwrapped key data may not be allowed to leave + * a hardware token... + */ +SecSymmetricKeyRef +SecCmsUtilDecryptSymKeyRSA(SecPrivateKeyRef privkey, CSSM_DATA_PTR encKey, SECOidTag bulkalgtag) +{ + /* that's easy */ + return WRAP_PubUnwrapSymKey(privkey, encKey, bulkalgtag); +} + +#if 0 +// @@@ Implement Fortezza and Diffie hellman support + +/* ====== MISSI (Fortezza) ========================================================== */ + +extern const SecAsn1Template NSS_SMIMEKEAParamTemplateAllParams[]; + +OSStatus +SecCmsUtilEncryptSymKeyMISSI(PLArenaPool *poolp, SecCertificateRef cert, SecSymmetricKeyRef bulkkey, + SECOidTag symalgtag, CSSM_DATA_PTR encKey, CSSM_DATA_PTR *pparams, void *pwfn_arg) +{ + SECOidTag certalgtag; /* the certificate's encryption algorithm */ + SECOidTag encalgtag; /* the algorithm used for key exchange/agreement */ + OSStatus rv = SECFailure; + CSSM_DATA_PTR params = NULL; + OSStatus err; + SecSymmetricKeyRef tek; + SecCertificateRef ourCert; + SecPublicKeyRef ourPubKey, *publickey = NULL; + SecPrivateKeyRef ourPrivKey = NULL; + SecCmsKEATemplateSelector whichKEA = SecCmsKEAInvalid; + SecCmsSMIMEKEAParameters keaParams; + PLArenaPool *arena = NULL; + extern const SecAsn1Template *nss_cms_get_kea_template(SecCmsKEATemplateSelector whichTemplate); + const SECAlgorithmID *algid; + + /* Clear keaParams, since cleanup code checks the lengths */ + (void) memset(&keaParams, 0, sizeof(keaParams)); + + SecCertificateGetAlgorithmID(cert,&algid); + certalgtag = SECOID_GetAlgorithmTag(algid); + PORT_Assert(certalgtag == SEC_OID_MISSI_KEA_DSS_OLD || + certalgtag == SEC_OID_MISSI_KEA_DSS || + certalgtag == SEC_OID_MISSI_KEA); + +#define SMIME_FORTEZZA_RA_LENGTH 128 +#define SMIME_FORTEZZA_IV_LENGTH 24 +#define SMIME_FORTEZZA_MAX_KEY_SIZE 256 + + /* We really want to show our KEA tag as the key exchange algorithm tag. */ + encalgtag = SEC_OID_NETSCAPE_SMIME_KEA; + + /* Get the public key of the recipient. */ + publickey = CERT_ExtractPublicKey(cert); + if (publickey == NULL) goto loser; + + /* Find our own cert, and extract its keys. */ + ourCert = PK11_FindBestKEAMatch(cert, pwfn_arg); + if (ourCert == NULL) goto loser; + + arena = PORT_NewArena(1024); + if (arena == NULL) + goto loser; + + ourPubKey = CERT_ExtractPublicKey(ourCert); + if (ourPubKey == NULL) { + CERT_DestroyCertificate(ourCert); + goto loser; + } + + /* While we're here, copy the public key into the outgoing + * KEA parameters. */ + SECITEM_CopyItem(arena, &(keaParams.originatorKEAKey), &(ourPubKey->u.fortezza.KEAKey)); + SECKEY_DestroyPublicKey(ourPubKey); + ourPubKey = NULL; + + /* Extract our private key in order to derive the KEA key. */ + ourPrivKey = PK11_FindKeyByAnyCert(ourCert, pwfn_arg); + CERT_DestroyCertificate(ourCert); /* we're done with this */ + if (!ourPrivKey) + goto loser; + + /* Prepare raItem with 128 bytes (filled with zeros). */ + keaParams.originatorRA.Data = (unsigned char *)PORT_ArenaAlloc(arena,SMIME_FORTEZZA_RA_LENGTH); + keaParams.originatorRA.Length = SMIME_FORTEZZA_RA_LENGTH; + + /* Generate the TEK (token exchange key) which we use + * to wrap the bulk encryption key. (keaparams.originatorRA) will be + * filled with a random seed which we need to send to + * the recipient. (user keying material in RFC2630/DSA speak) */ + tek = PK11_PubDerive(ourPrivKey, publickey, PR_TRUE, + &keaParams.originatorRA, NULL, + CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, pwfn_arg); + + SECKEY_DestroyPublicKey(publickey); + SECKEY_DestroyPrivateKey(ourPrivKey); + publickey = NULL; + ourPrivKey = NULL; + + if (!tek) + goto loser; + + /* allocate space for the wrapped key data */ + encKey->Data = (unsigned char *)PORT_ArenaAlloc(poolp, SMIME_FORTEZZA_MAX_KEY_SIZE); + encKey->Length = SMIME_FORTEZZA_MAX_KEY_SIZE; + + if (encKey->Data == NULL) { + CFRelease(tek); + goto loser; + } + + /* Wrap the bulk key. What we do with the resulting data + depends on whether we're using Skipjack to wrap the key. */ + switch (PK11_AlgtagToMechanism(symalgtag)) { + case CKM_SKIPJACK_CBC64: + case CKM_SKIPJACK_ECB64: + case CKM_SKIPJACK_OFB64: + case CKM_SKIPJACK_CFB64: + case CKM_SKIPJACK_CFB32: + case CKM_SKIPJACK_CFB16: + case CKM_SKIPJACK_CFB8: + /* SKIPJACK, we use the wrap mechanism because we can do it on the hardware */ + err = PK11_WrapSymKey(CKM_SKIPJACK_WRAP, NULL, tek, bulkkey, encKey); + whichKEA = SecCmsKEAUsesSkipjack; + break; + default: + /* Not SKIPJACK, we encrypt the raw key data */ + keaParams.nonSkipjackIV.Data = + (unsigned char *)PORT_ArenaAlloc(arena, SMIME_FORTEZZA_IV_LENGTH); + keaParams.nonSkipjackIV.Length = SMIME_FORTEZZA_IV_LENGTH; + err = PK11_WrapSymKey(CKM_SKIPJACK_CBC64, &keaParams.nonSkipjackIV, tek, bulkkey, encKey); + if (err != SECSuccess) + goto loser; + + if (encKey->Length != PK11_GetKeyLength(bulkkey)) { + /* The size of the encrypted key is not the same as + that of the original bulk key, presumably due to + padding. Encode and store the real size of the + bulk key. */ + if (SEC_ASN1EncodeInteger(arena, &keaParams.bulkKeySize, PK11_GetKeyLength(bulkkey)) == NULL) + err = (OSStatus)PORT_GetError(); + else + /* use full template for encoding */ + whichKEA = SecCmsKEAUsesNonSkipjackWithPaddedEncKey; + } + else + /* enc key length == bulk key length */ + whichKEA = SecCmsKEAUsesNonSkipjack; + break; + } + + CFRelease(tek); + + if (err != SECSuccess) + goto loser; + + PORT_Assert(whichKEA != SecCmsKEAInvalid); + + /* Encode the KEA parameters into the recipient info. */ + params = SEC_ASN1EncodeItem(poolp, NULL, &keaParams, nss_cms_get_kea_template(whichKEA)); + if (params == NULL) + goto loser; + + /* pass back the algorithm params */ + *pparams = params; + + rv = SECSuccess; + +loser: + if (arena) + PORT_FreeArena(arena, PR_FALSE); + if (publickey) + SECKEY_DestroyPublicKey(publickey); + if (ourPrivKey) + SECKEY_DestroyPrivateKey(ourPrivKey); + return rv; +} + +SecSymmetricKeyRef +SecCmsUtilDecryptSymKeyMISSI(SecPrivateKeyRef privkey, CSSM_DATA_PTR encKey, SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg) +{ + /* fortezza: do a key exchange */ + OSStatus err; + CK_MECHANISM_TYPE bulkType; + SecSymmetricKeyRef tek; + SecPublicKeyRef originatorPubKey; + SecCmsSMIMEKEAParameters keaParams; + SecSymmetricKeyRef bulkkey; + int bulkLength; + + (void) memset(&keaParams, 0, sizeof(keaParams)); + + /* NOTE: this uses the SMIME v2 recipientinfo for compatibility. + All additional KEA parameters are DER-encoded in the encryption algorithm parameters */ + + /* Decode the KEA algorithm parameters. */ + err = SEC_ASN1DecodeItem(NULL, &keaParams, NSS_SMIMEKEAParamTemplateAllParams, + &(keyEncAlg->parameters)); + if (err != SECSuccess) + goto loser; + + /* get originator's public key */ + originatorPubKey = PK11_MakeKEAPubKey(keaParams.originatorKEAKey.Data, + keaParams.originatorKEAKey.Length); + if (originatorPubKey == NULL) + goto loser; + + /* Generate the TEK (token exchange key) which we use to unwrap the bulk encryption key. + The Derive function generates a shared secret and combines it with the originatorRA + data to come up with an unique session key */ + tek = PK11_PubDerive(privkey, originatorPubKey, PR_FALSE, + &keaParams.originatorRA, NULL, + CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, pwfn_arg); + SECKEY_DestroyPublicKey(originatorPubKey); /* not needed anymore */ + if (tek == NULL) + goto loser; + + /* Now that we have the TEK, unwrap the bulk key + with which to decrypt the message. We have to + do one of two different things depending on + whether Skipjack was used for *bulk* encryption + of the message. */ + bulkType = PK11_AlgtagToMechanism(bulkalgtag); + switch (bulkType) { + case CKM_SKIPJACK_CBC64: + case CKM_SKIPJACK_ECB64: + case CKM_SKIPJACK_OFB64: + case CKM_SKIPJACK_CFB64: + case CKM_SKIPJACK_CFB32: + case CKM_SKIPJACK_CFB16: + case CKM_SKIPJACK_CFB8: + /* Skipjack is being used as the bulk encryption algorithm.*/ + /* Unwrap the bulk key. */ + bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_WRAP, NULL, + encKey, CKM_SKIPJACK_CBC64, CKA_DECRYPT, 0); + break; + default: + /* Skipjack was not used for bulk encryption of this + message. Use Skipjack CBC64, with the nonSkipjackIV + part of the KEA key parameters, to decrypt + the bulk key. If the optional parameter bulkKeySize is present, + bulk key size is different than the encrypted key size */ + if (keaParams.bulkKeySize.Length > 0) { + err = SEC_ASN1DecodeItem(NULL, &bulkLength, + SEC_ASN1_GET(SEC_IntegerTemplate), + &keaParams.bulkKeySize); + if (err != SECSuccess) + goto loser; + } + + bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_CBC64, &keaParams.nonSkipjackIV, + encKey, bulkType, CKA_DECRYPT, bulkLength); + break; + } + return bulkkey; +loser: + return NULL; +} + +/* ====== ESDH (Ephemeral-Static Diffie-Hellman) ==================================== */ + +OSStatus +SecCmsUtilEncryptSymKeyESDH(PLArenaPool *poolp, SecCertificateRef cert, SecSymmetricKeyRef key, + CSSM_DATA_PTR encKey, CSSM_DATA_PTR ukm, SECAlgorithmID *keyEncAlg, + CSSM_DATA_PTR pubKey) +{ +#if 0 /* not yet done */ + SECOidTag certalgtag; /* the certificate's encryption algorithm */ + SECOidTag encalgtag; /* the algorithm used for key exchange/agreement */ + OSStatus rv; + CSSM_DATA_PTR params = NULL; + int data_len; + OSStatus err; + SecSymmetricKeyRef tek; + SecCertificateRef ourCert; + SecPublicKeyRef ourPubKey; + SecCmsKEATemplateSelector whichKEA = SecCmsKEAInvalid; + + certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + PORT_Assert(certalgtag == SEC_OID_X942_DIFFIE_HELMAN_KEY); + + /* We really want to show our KEA tag as the key exchange algorithm tag. */ + encalgtag = SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN; + + /* Get the public key of the recipient. */ + publickey = CERT_ExtractPublicKey(cert); + if (publickey == NULL) goto loser; + + /* XXXX generate a DH key pair on a PKCS11 module (XXX which parameters?) */ + /* XXXX */ourCert = PK11_FindBestKEAMatch(cert, wincx); + if (ourCert == NULL) goto loser; + + arena = PORT_NewArena(1024); + if (arena == NULL) goto loser; + + /* While we're here, extract the key pair's public key data and copy it into */ + /* the outgoing parameters. */ + /* XXXX */ourPubKey = CERT_ExtractPublicKey(ourCert); + if (ourPubKey == NULL) + { + goto loser; + } + SECITEM_CopyItem(arena, pubKey, /* XXX */&(ourPubKey->u.fortezza.KEAKey)); + SECKEY_DestroyPublicKey(ourPubKey); /* we only need the private key from now on */ + ourPubKey = NULL; + + /* Extract our private key in order to derive the KEA key. */ + ourPrivKey = PK11_FindKeyByAnyCert(ourCert,wincx); + CERT_DestroyCertificate(ourCert); /* we're done with this */ + if (!ourPrivKey) goto loser; + + /* If ukm desired, prepare it - allocate enough space (filled with zeros). */ + if (ukm) { + ukm->Data = (unsigned char*)PORT_ArenaZAlloc(arena,/* XXXX */); + ukm->Length = /* XXXX */; + } + + /* Generate the KEK (key exchange key) according to RFC2631 which we use + * to wrap the bulk encryption key. */ + kek = PK11_PubDerive(ourPrivKey, publickey, PR_TRUE, + ukm, NULL, + /* XXXX */CKM_KEA_KEY_DERIVE, /* XXXX */CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, wincx); + + SECKEY_DestroyPublicKey(publickey); + SECKEY_DestroyPrivateKey(ourPrivKey); + publickey = NULL; + ourPrivKey = NULL; + + if (!kek) + goto loser; + + /* allocate space for the encrypted CEK (bulk key) */ + encKey->Data = (unsigned char*)PORT_ArenaAlloc(poolp, SMIME_FORTEZZA_MAX_KEY_SIZE); + encKey->Length = SMIME_FORTEZZA_MAX_KEY_SIZE; + + if (encKey->Data == NULL) + { + CFRelease(kek); + goto loser; + } + + + /* Wrap the bulk key using CMSRC2WRAP or CMS3DESWRAP, depending on the */ + /* bulk encryption algorithm */ + switch (/* XXXX */PK11_AlgtagToMechanism(enccinfo->encalg)) + { + case /* XXXX */CKM_SKIPJACK_CFB8: + err = PK11_WrapSymKey(/* XXXX */CKM_CMS3DES_WRAP, NULL, kek, bulkkey, encKey); + whichKEA = SecCmsKEAUsesSkipjack; + break; + case /* XXXX */CKM_SKIPJACK_CFB8: + err = PK11_WrapSymKey(/* XXXX */CKM_CMSRC2_WRAP, NULL, kek, bulkkey, encKey); + whichKEA = SecCmsKEAUsesSkipjack; + break; + default: + /* XXXX what do we do here? Neither RC2 nor 3DES... */ + err = SECFailure; + /* set error */ + break; + } + + CFRelease(kek); /* we do not need the KEK anymore */ + if (err != SECSuccess) + goto loser; + + PORT_Assert(whichKEA != SecCmsKEAInvalid); + + /* see RFC2630 12.3.1.1 "keyEncryptionAlgorithm must be ..." */ + /* params is the DER encoded key wrap algorithm (with parameters!) (XXX) */ + params = SEC_ASN1EncodeItem(arena, NULL, &keaParams, sec_pkcs7_get_kea_template(whichKEA)); + if (params == NULL) + goto loser; + + /* now set keyEncAlg */ + rv = SECOID_SetAlgorithmID(poolp, keyEncAlg, SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN, params); + if (rv != SECSuccess) + goto loser; + + /* XXXXXXX this is not right yet */ +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + if (publickey) { + SECKEY_DestroyPublicKey(publickey); + } + if (ourPrivKey) { + SECKEY_DestroyPrivateKey(ourPrivKey); + } +#endif + return SECFailure; +} + +SecSymmetricKeyRef +SecCmsUtilDecryptSymKeyESDH(SecPrivateKeyRef privkey, CSSM_DATA_PTR encKey, SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg) +{ +#if 0 /* not yet done */ + OSStatus err; + CK_MECHANISM_TYPE bulkType; + SecSymmetricKeyRef tek; + SecPublicKeyRef originatorPubKey; + SecCmsSMIMEKEAParameters keaParams; + + /* XXXX get originator's public key */ + originatorPubKey = PK11_MakeKEAPubKey(keaParams.originatorKEAKey.Data, + keaParams.originatorKEAKey.Length); + if (originatorPubKey == NULL) + goto loser; + + /* Generate the TEK (token exchange key) which we use to unwrap the bulk encryption key. + The Derive function generates a shared secret and combines it with the originatorRA + data to come up with an unique session key */ + tek = PK11_PubDerive(privkey, originatorPubKey, PR_FALSE, + &keaParams.originatorRA, NULL, + CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, pwfn_arg); + SECKEY_DestroyPublicKey(originatorPubKey); /* not needed anymore */ + if (tek == NULL) + goto loser; + + /* Now that we have the TEK, unwrap the bulk key + with which to decrypt the message. */ + /* Skipjack is being used as the bulk encryption algorithm.*/ + /* Unwrap the bulk key. */ + bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_WRAP, NULL, + encKey, CKM_SKIPJACK_CBC64, CKA_DECRYPT, 0); + + return bulkkey; + +loser: +#endif + return NULL; +} + +#endif /* Fortezza, DIffie-Hellman */ + +#define CFRELEASE(cf) if(cf != NULL) { CFRelease(cf); } + +/* ====== ECDH (Ephemeral-Static Diffie-Hellman) ==================================== */ + +#pragma mark ---- ECDH support functions ---- + +#ifdef NDEBUG +#define CSSM_PERROR(f, r) +#define dprintf(args...) +#else +#define CSSM_PERROR(f, r) cssmPerror(f, r) +#define dprintf(args...) printf(args) +#endif + +/* Length of KeyAgreeRecipientInfo.ukm we create */ +#define UKM_LENGTH 8 + +/* KEK algorithm info we generate */ +#define ECDH_KEK_ALG_TAG SEC_OID_DES_EDE3_CBC +#define ECDH_KEK_KEY_CSSM_ALGID CSSM_ALGID_3DES_3KEY +#define ECDH_KEK_ENCR_CSSM_ALGID CSSM_ALGID_3DES_3KEY_EDE +#define ECDH_KEK_KEY_LEN_BYTES 24 +#define ECDH_KEK_IV_LEN_BYTES 8 + +#define CMS_DUMP_BUFS 0 +#if CMS_DUMP_BUFS + +static void dumpBuf( + const char *label, + const CSSM_DATA *cd) +{ + unsigned dex; + + printf("%s:\n ", label); + for(dex=0; dexLength; dex++) { + printf("%02X ", cd->Data[dex]); + if(((dex % 16) == 15) && (dex != (cd->Length - 1))) { + printf("\n "); + } + } + putchar('\n'); +} + +#else +#define dumpBuf(l, d) +#endif /* CMS_DUMP_BUFS */ + +/* + * The ECC-CMS-SharedInfo struct, as defined in RFC 3278 8.2, and the + * template for DER encoding and decoding it. + */ +typedef struct { + SECAlgorithmID algId; /* KEK alg, NULL params */ + CSSM_DATA entityUInfo; /* optional, ukm */ + CSSM_DATA suppPubInfo; /* length of KEK in bits as 4-byte integer */ +} ECC_CMS_SharedInfo; + +static const SecAsn1Template ECC_CMS_SharedInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ECC_CMS_SharedInfo) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ECC_CMS_SharedInfo,entityUInfo), + kSecAsn1OctetStringTemplate }, + { SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | SEC_ASN1_CONTEXT_SPECIFIC | 2, + offsetof(ECC_CMS_SharedInfo,suppPubInfo), + kSecAsn1OctetStringTemplate }, + { 0 } +}; + +/* + * Given a context specified via a CSSM_CC_HANDLE, add a new + * CSSM_CONTEXT_ATTRIBUTE to the context as specified by AttributeType, + * AttributeLength, and an untyped pointer. + */ +/* specify either 32-bit integer or a pointer as an added attribute value */ +typedef enum { + CAT_Uint32, + CAT_Ptr +} ContextAttrType; + +static CSSM_RETURN cmsAddContextAttribute( + CSSM_CC_HANDLE CCHandle, + uint32 AttributeType, + uint32 AttributeLength, + ContextAttrType attrType, + /* specify exactly one of these */ + const void *AttributePtr, + uint32 attributeInt) +{ + CSSM_CONTEXT_ATTRIBUTE newAttr; + CSSM_RETURN crtn; + + newAttr.AttributeType = AttributeType; + newAttr.AttributeLength = AttributeLength; + if(attrType == CAT_Uint32) { + newAttr.Attribute.Uint32 = attributeInt; + } + else { + /* this is a union of a bunch of different pointers...*/ + newAttr.Attribute.Data = (CSSM_DATA_PTR)AttributePtr; + } + crtn = CSSM_UpdateContextAttributes(CCHandle, 1, &newAttr); + if(crtn) { + CSSM_PERROR("CSSM_UpdateContextAttributes", crtn); + } + return crtn; +} + +static CSSM_RETURN cmsGenRand( + CSSM_CSP_HANDLE cspHand, + CSSM_SIZE len, + uint8 *randOut) +{ + CSSM_CC_HANDLE ccHand = 0; + CSSM_DATA randData = {len, randOut}; + + CSSM_RETURN crtn = CSSM_CSP_CreateRandomGenContext(cspHand, + CSSM_ALGID_APPLE_YARROW, + NULL, /* seed*/ + len, + &ccHand); + if(crtn) { + CSSM_PERROR("CSSM_CSP_CreateRandomGenContext", crtn); + return crtn; + } + crtn = CSSM_GenerateRandom(ccHand, &randData); + CSSM_DeleteContext(ccHand); + if(crtn) { + CSSM_PERROR("CSSM_GenerateRandom", crtn); + } + return crtn; +} + +/* convert uint32 to big-endian 4 bytes */ +static void int32ToBytes( + uint32_t i, + unsigned char *b) +{ + int dex; + for(dex=3; dex>=0; dex--) { + b[dex] = i; + i >>= 8; + } +} + +/* + * NULL wrap a ref key to raw key in default format. + */ +static OSStatus cmsNullWrapKey( + CSSM_CSP_HANDLE cspHand, + const CSSM_KEY *refKey, + CSSM_KEY_PTR rawKey) +{ + CSSM_DATA descData = {0, 0}; + CSSM_RETURN crtn; + CSSM_CC_HANDLE ccHand; + CSSM_ACCESS_CREDENTIALS creds; + uint32 keyAttr; + + memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); + memset(rawKey, 0, sizeof(CSSM_KEY)); + + crtn = CSSM_CSP_CreateSymmetricContext(cspHand, + CSSM_ALGID_NONE, + CSSM_ALGMODE_NONE, + &creds, + NULL, // unwrappingKey + NULL, // initVector + CSSM_PADDING_NONE, + 0, // Params + &ccHand); + if(crtn) { + CSSM_PERROR("CSSM_CSP_CreateSymmetricContext", crtn); + return crtn; + } + + keyAttr = rawKey->KeyHeader.KeyAttr; + keyAttr &= ~(CSSM_KEYATTR_ALWAYS_SENSITIVE | CSSM_KEYATTR_NEVER_EXTRACTABLE | + CSSM_KEYATTR_MODIFIABLE); + keyAttr |= CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE; + crtn = CSSM_WrapKey(ccHand, + &creds, + refKey, + &descData, + rawKey); + if(crtn != CSSM_OK) { + CSSM_PERROR("CSSM_WrapKey", crtn); + } + CSSM_DeleteContext(ccHand); + return crtn; +} + +/* + * Free memory via specified plugin's app-level allocator + */ +static void cmsFreeCssmMemory( + CSSM_HANDLE hand, + void *p) +{ + CSSM_API_MEMORY_FUNCS memFuncs; + CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs); + if(crtn) { + return; + } + memFuncs.free_func(p, memFuncs.AllocRef); +} + +/* + * Given an OID tag, return key size and mode. + * NOTE: ciphers with variable key sizes, like RC2, RC4, and RC5 cannot + * be used here because the message does not contain a key size + * indication. + */ +static OSStatus encrAlgInfo( + SECOidTag oidTag, + uint32 *keySizeBits, /* RETURNED */ + CSSM_ENCRYPT_MODE *mode) /* RETURNED */ +{ + *keySizeBits = 64; /* default */ + *mode = CSSM_ALGMODE_CBCPadIV8; /* default */ + + switch(oidTag) { + case SEC_OID_RC2_CBC: + case SEC_OID_RC4: + case SEC_OID_RC5_CBC_PAD: + dprintf("encrAlgInfo: key size unknowable\n"); + return errSecDataNotAvailable; + + case SEC_OID_DES_EDE3_CBC: + *keySizeBits = 192; + break; + case SEC_OID_DES_EDE: + /* Not sure about this; SecCmsCipherContextStart() treats this + * like SEC_OID_DES_EDE3_CBC... */ + case SEC_OID_DES_ECB: + *mode = CSSM_ALGMODE_ECB; + break; + case SEC_OID_DES_CBC: + *mode = CSSM_ALGMODE_CBC; + break; + case SEC_OID_AES_128_CBC: + *keySizeBits = 128; + break; + case SEC_OID_AES_192_CBC: + *keySizeBits = 192; + break; + case SEC_OID_AES_256_CBC: + *keySizeBits = 256; + break; + case SEC_OID_AES_128_ECB: + *keySizeBits = 128; + *mode = CSSM_ALGMODE_ECB; + break; + case SEC_OID_AES_192_ECB: + *keySizeBits = 192; + *mode = CSSM_ALGMODE_ECB; + break; + case SEC_OID_AES_256_ECB: + *keySizeBits = 256; + *mode = CSSM_ALGMODE_ECB; + break; + case SEC_OID_DES_OFB: + *mode = CSSM_ALGMODE_OFB; + break; + case SEC_OID_DES_CFB: + *mode = CSSM_ALGMODE_CFB; + break; + default: + dprintf("encrAlgInfo: unknown alg tag (%d)\n", (int)oidTag); + return errSecDataNotAvailable; + } + return noErr; +} + +#pragma mark ---- ECDH CEK key wrap ---- + +/* + * Encrypt bulk encryption key (a.k.a. content encryption key, CEK) using ECDH + */ +OSStatus +SecCmsUtilEncryptSymKeyECDH( + PLArenaPool *poolp, + SecCertificateRef cert, /* recipient's cert */ + SecSymmetricKeyRef key, /* bulk key */ + /* remaining fields RETURNED */ + CSSM_DATA_PTR encKey, /* encrypted key --> recipientEncryptedKeys[0].EncryptedKey */ + CSSM_DATA_PTR ukm, /* random UKM --> KeyAgreeRecipientInfo.ukm */ + SECAlgorithmID *keyEncAlg, /* alg := dhSinglePass-stdDH-sha1kdf-scheme + * params := another encoded AlgId, with the KEK alg and IV */ + CSSM_DATA_PTR pubKey) /* our pub key as ECPoint --> + * KeyAgreeRecipientInfo.originator.OriginatorPublicKey */ +{ + OSStatus rv = noErr; + CSSM_KEY ourPrivKeyCssm; + CSSM_KEY ourPubKeyCssm; + SecKeyRef theirPubKeyRef = NULL; + CSSM_KEY_PTR theirPubKeyCssm = NULL; + const CSSM_KEY *cekCssmRef = NULL; + uint32 ecdhKeySizeBits; + CSSM_CSP_HANDLE rawCspHand = SecCspHandleForAlgorithm(CSSM_ALGID_ECDH); + CSSM_CC_HANDLE ccHand = 0; + CSSM_RETURN crtn; + CSSM_DATA keyLabel = {8, (uint8 *)"tempKey"}; + SECAlgorithmID kekAlgId; + uint8 iv[ECDH_KEK_IV_LEN_BYTES]; + CSSM_DATA ivData = {ECDH_KEK_IV_LEN_BYTES, iv}; + SECOidData *kekOid; + ECC_CMS_SharedInfo sharedInfo; + CSSM_DATA sharedInfoEnc = {0, NULL}; + uint8 nullData[2] = {SEC_ASN1_NULL, 0}; + uint8 keyLenAsBytes[4]; + CSSM_KEY kekDerive; + CSSM_DATA certData; + CSSM_CL_HANDLE clHand; + CSSM_ACCESS_CREDENTIALS creds; + CSSM_DATA paramData = {0, NULL}; + CSSM_KEY cekCssm; + CSSM_CSP_HANDLE refCspHand; + CSSM_SIZE bytesEncrypted; + CSSM_DATA remData = {0, NULL}; + CSSM_DATA ctext = {0, NULL}; + CSSM_X509_SUBJECT_PUBLIC_KEY_INFO subjPubKey; + + if(rawCspHand == 0) { + return internalComponentErr; + } + + memset(&ourPrivKeyCssm, 0, sizeof(CSSM_KEY)); + memset(&ourPubKeyCssm, 0, sizeof(CSSM_KEY)); + memset(&cekCssm, 0, sizeof(CSSM_KEY)); + memset(&kekDerive, 0, sizeof(kekDerive)); + + encKey->Data = NULL; + encKey->Length = 0; + + /* + * Create our ECDH key pair matching the recipient's key. + * Get the public key in "read-only" OCTET_STRING format, which + * is the ECPoint we put in + * KeyAgreeRecipientInfo.originator.OriginatorPublicKey. + */ + rv = SecCertificateGetData(cert, &certData); + if(rv) { + CSSM_PERROR("SecCertificateGetData", rv); + return rv; + } + rv = SecCertificateGetCLHandle(cert, &clHand); + if(rv) { + CSSM_PERROR("SecCertificateGetCLHandle", rv); + return rv; + } + rv = CSSM_CL_CertGetKeyInfo(clHand, &certData, &theirPubKeyCssm); + if(rv) { + CSSM_PERROR("CSSM_CL_CertGetKeyInfo", rv); + return rv; + } + + /* + * Verify the EC curve of the recipient's public key. It's in the + * public key's AlgId.parameters as an OID. The key we were + * given is in CSSM_X509_SUBJECT_PUBLIC_KEY_INFO form. + */ + memset(&subjPubKey, 0, sizeof(subjPubKey)); + if(SEC_ASN1DecodeItem(poolp, &subjPubKey, kSecAsn1SubjectPublicKeyInfoTemplate, + &theirPubKeyCssm->KeyData)) { + dprintf("SecCmsUtilEncryptSymKeyECDH: error decoding SubjPubKey\n"); + /* oh well, keep going */ + } + else { + if(subjPubKey.algorithm.parameters.Data != NULL) { + CSSM_DATA curveOid; + if(SEC_ASN1DecodeItem(poolp, &curveOid, kSecAsn1ObjectIDTemplate, + &subjPubKey.algorithm.parameters)) { + dprintf("SecCmsUtilEncryptSymKeyECDH: error decoding curveOid\n"); + /* oh well, keep going */ + } + else { + /* We have the curve OID. Any other errors are fatal. */ + SECOidTag oidTag = SECOID_FindOIDTag(&curveOid); + switch(oidTag) { + case SEC_OID_SECP_256_R1: + case SEC_OID_SECP_384_R1: + case SEC_OID_SECP_521_R1: + break; + default: + dprintf("SecCmsUtilEncryptSymKeyECDH: unsupported curveOid\n"); + rv = CSSMERR_CSP_INVALID_KEY; + goto loser; + } + } + } + } + + ecdhKeySizeBits = theirPubKeyCssm->KeyHeader.LogicalKeySizeInBits; + crtn = CSSM_CSP_CreateKeyGenContext(rawCspHand, + CSSM_ALGID_ECDSA, + ecdhKeySizeBits, + NULL, // Seed + NULL, // Salt + NULL, // StartDate + NULL, // EndDate + NULL, // Params + &ccHand); + if(crtn) { + CSSM_PERROR("CSSM_CSP_CreateKeyGenContext", crtn); + rv = crtn; + goto loser; + } + crtn = cmsAddContextAttribute(ccHand, + CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT, + sizeof(uint32), + CAT_Uint32, + NULL, + CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING); + if(crtn) { + CSSM_PERROR("AddContextAttribute(CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT)", crtn); + rv = crtn; + goto loser; + } + + crtn = CSSM_GenerateKeyPair(ccHand, + CSSM_KEYUSE_DERIVE, + CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE, + &keyLabel, + &ourPubKeyCssm, + CSSM_KEYUSE_DERIVE, + CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE, + &keyLabel, + NULL, // CredAndAclEntry + &ourPrivKeyCssm); + CSSM_DeleteContext(ccHand); + ccHand = 0; + if(crtn) { + CSSM_PERROR("CSSM_GenerateKeyPair", crtn); + rv = crtn; + goto loser; + } + pubKey->Length = ourPubKeyCssm.KeyData.Length; + pubKey->Data = (uint8 *)PORT_ArenaAlloc(poolp, pubKey->Length); + memmove(pubKey->Data, ourPubKeyCssm.KeyData.Data, pubKey->Length); + dumpBuf("sender's public key", pubKey); + + /* + * Cook up random UKM + */ + ukm->Data = (uint8 *)PORT_ArenaAlloc(poolp, UKM_LENGTH); + ukm->Length = UKM_LENGTH; + crtn = cmsGenRand(rawCspHand, UKM_LENGTH, ukm->Data); + if(crtn) { + goto loser; + } + dumpBuf("sender UKM", ukm); + + /* + * OK, we have to set up a weird SECAlgorithmID. + * algorithm = dhSinglePass-stdDH-sha1kdf-scheme + * params = an encoded SECAlgorithmID representing the KEK algorithm, with + * algorithm = whatever we pick + * parameters = IV as octet string (though I haven't seen that specified + * anywhere; it's how the CEK IV is encoded) + * + * First, the 8-byte random IV, encoded as octet string + */ + crtn = cmsGenRand(rawCspHand, ECDH_KEK_IV_LEN_BYTES, iv); + if(crtn) { + goto loser; + } + dumpBuf("sender IV", &ivData); + + memset(&kekAlgId, 0, sizeof(kekAlgId)); + if (!SEC_ASN1EncodeItem(poolp, &kekAlgId.parameters, + &ivData, kSecAsn1OctetStringTemplate)) { + rv = internalComponentErr; + goto loser; + } + + /* Drop in the KEK OID and encode the whole thing */ + kekOid = SECOID_FindOIDByTag(ECDH_KEK_ALG_TAG); + if(kekOid == NULL) { + dprintf("SecCmsUtilEncryptSymKeyECDH: OID screwup\n"); + rv = internalComponentErr; + goto loser; + } + kekAlgId.algorithm = kekOid->oid; + memset(keyEncAlg, 0, sizeof(*keyEncAlg)); + if (!SEC_ASN1EncodeItem(poolp, &keyEncAlg->parameters, + &kekAlgId, SECOID_AlgorithmIDTemplate)) { + rv = internalComponentErr; + goto loser; + } + kekOid = SECOID_FindOIDByTag(SEC_OID_DH_SINGLE_STD_SHA1KDF); + if(kekOid == NULL) { + dprintf("SecCmsUtilEncryptSymKeyECDH: OID screwup\n"); + rv = internalComponentErr; + goto loser; + } + keyEncAlg->algorithm = kekOid->oid; + + /* + * Now in order to derive the KEK proper, we have to create a + * ECC-CMS-SharedInfo, which does not appear in the message, and DER + * encode that struct, the result of which is used as the + * SharedInfo value in the KEK key derive. + */ + memset(&sharedInfo, 0, sizeof(sharedInfo)); + kekOid = SECOID_FindOIDByTag(ECDH_KEK_ALG_TAG); + sharedInfo.algId.algorithm = kekOid->oid; + sharedInfo.algId.parameters.Data = nullData; + sharedInfo.algId.parameters.Length = 2; + sharedInfo.entityUInfo = *ukm; + int32ToBytes(ECDH_KEK_KEY_LEN_BYTES << 3, keyLenAsBytes); + sharedInfo.suppPubInfo.Length = 4; + sharedInfo.suppPubInfo.Data = keyLenAsBytes; + if (!SEC_ASN1EncodeItem(poolp, &sharedInfoEnc, + &sharedInfo, ECC_CMS_SharedInfoTemplate)) { + rv = internalComponentErr; + goto loser; + } + dumpBuf("sender encoded SharedInfo", &sharedInfoEnc); + + /* + * Since we're using the raw CSP here, we can provide the "other" public + * key as an actual CSSM_KEY. When unwrapping, we won't be able to do that + * since we'll be using our private key obtained from a SecIdentityRef. + */ + memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); + crtn = CSSM_CSP_CreateDeriveKeyContext(rawCspHand, + CSSM_ALGID_ECDH_X963_KDF, + ECDH_KEK_KEY_CSSM_ALGID, // algorithm of the KEK + ECDH_KEK_KEY_LEN_BYTES * 8, + &creds, + &ourPrivKeyCssm, // BaseKey + 0, // IterationCount + &sharedInfoEnc, // Salt + 0, // Seed + &ccHand); + if(crtn) { + CSSM_PERROR("CSSM_CSP_CreateDeriveKeyContext", crtn); + rv = crtn; + goto loser; + } + + /* add recipient's pub key as a context attr */ + crtn = cmsAddContextAttribute(ccHand, + CSSM_ATTRIBUTE_PUBLIC_KEY, + sizeof(CSSM_KEY), + CAT_Ptr, + (void *)theirPubKeyCssm, + 0); + if(crtn) { + rv = crtn; + goto loser; + } + + /* Derive the KEK */ + crtn = CSSM_DeriveKey(ccHand, + ¶mData, + CSSM_KEYUSE_ANY, + CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE, + &keyLabel, + NULL, // cread/acl + &kekDerive); + if(crtn) { + CSSM_PERROR("CSSM_DeriveKey", crtn); + rv = crtn; + goto loser; + } + CSSM_DeleteContext(ccHand); + ccHand = 0; + + /* + * Obtain the raw CEK bits. + */ + rv = SecKeyGetCSSMKey(key, &cekCssmRef); + if(rv) { + CSSM_PERROR("SecKeyGetCSSMKey", rv); + goto loser; + } + rv = SecKeyGetCSPHandle(key, &refCspHand); + if(rv) { + CSSM_PERROR("SecKeyGetCSPHandle", rv); + goto loser; + } + rv = cmsNullWrapKey(refCspHand, cekCssmRef, &cekCssm); + if(rv) { + goto loser; + } + + /* + * Finally, encrypt the raw CEK bits with the KEK we just derived + */ + crtn = CSSM_CSP_CreateSymmetricContext(rawCspHand, + ECDH_KEK_ENCR_CSSM_ALGID, + CSSM_ALGMODE_CBCPadIV8, + NULL, // access cred + &kekDerive, + &ivData, // InitVector + CSSM_PADDING_PKCS7, + NULL, // Params + &ccHand); + if(rv) { + CSSM_PERROR("CSSM_CSP_CreateSymmetricContext", rv); + goto loser; + } + rv = CSSM_EncryptData(ccHand, + &cekCssm.KeyData, + 1, + &ctext, + 1, + &bytesEncrypted, + &remData); + if(rv) { + CSSM_PERROR("CSSM_EncryptData", rv); + goto loser; + } + encKey->Data = PORT_ArenaAlloc(poolp, bytesEncrypted); + encKey->Length = bytesEncrypted; + memmove(encKey->Data, ctext.Data, ctext.Length); + if(bytesEncrypted != ctext.Length) { + memmove(encKey->Data + ctext.Length, remData.Data, remData.Length); + } + dumpBuf("sender encKey", encKey); + +loser: + if(ccHand) { + CSSM_DeleteContext(ccHand); + } + CFRELEASE(theirPubKeyRef); + if(ourPubKeyCssm.KeyData.Data) { + CSSM_FreeKey(rawCspHand, NULL, &ourPubKeyCssm, CSSM_FALSE); + } + if(ourPrivKeyCssm.KeyData.Data) { + CSSM_FreeKey(rawCspHand, NULL, &ourPrivKeyCssm, CSSM_FALSE); + } + if(ctext.Data) { + cmsFreeCssmMemory(rawCspHand, ctext.Data); + } + if(remData.Data) { + cmsFreeCssmMemory(rawCspHand, remData.Data); + } + if(cekCssm.KeyData.Data) { + CSSM_FreeKey(refCspHand, NULL, &cekCssm, CSSM_FALSE); + } + if(kekDerive.KeyData.Data) { + CSSM_FreeKey(rawCspHand, NULL, &kekDerive, CSSM_FALSE); + } + if(theirPubKeyCssm) { + /* Allocated by CL */ + cmsFreeCssmMemory(clHand, theirPubKeyCssm->KeyData.Data); + cmsFreeCssmMemory(clHand, theirPubKeyCssm); + } + return rv; +} + +#pragma mark ---- ECDH CEK key unwrap ---- + +SecSymmetricKeyRef +SecCmsUtilDecryptSymKeyECDH( + SecPrivateKeyRef privkey, /* our private key */ + CSSM_DATA_PTR encKey, /* encrypted CEK */ + CSSM_DATA_PTR ukm, /* random UKM from KeyAgreeRecipientInfo.ukm */ + SECAlgorithmID *keyEncAlg, /* alg := dhSinglePass-stdDH-sha1kdf-scheme + * params := another encoded AlgId, with the KEK alg and IV */ + SECOidTag bulkalgtag, /* algorithm of returned key */ + CSSM_DATA_PTR pubKey) /* sender's pub key as ECPoint from + * KeyAgreeRecipientInfo.originator.OriginatorPublicKey */ + +{ + SecSymmetricKeyRef outKey = NULL; + OSStatus rv = noErr; + const CSSM_KEY *ourPrivKeyCssm; + PLArenaPool *pool = NULL; + SECAlgorithmID keyAlgParam; + SECOidData *kekOid = NULL; + CSSM_DATA iv = {0, NULL}; + ECC_CMS_SharedInfo sharedInfo; + CSSM_DATA sharedInfoEnc = {0, NULL}; + uint8 nullData[2] = {SEC_ASN1_NULL, 0}; + uint8 keyLenAsBytes[4]; + CSSM_ENCRYPT_MODE kekMode; + uint32 kekSizeBits; + CSSM_KEY kekDerive; + CSSM_RETURN crtn; + CSSM_ACCESS_CREDENTIALS creds; + CSSM_CSP_HANDLE refCspHand; + CSSM_CC_HANDLE ccHand = 0; + CSSM_DATA keyLabel = {8, (uint8 *)"tempKey"}; + const CSSM_ACCESS_CREDENTIALS *accessCred; + CSSM_KEY wrappedKey; + CSSM_KEY unwrappedKey; + CSSM_ALGORITHMS bulkAlg; + CSSM_DATA descriptiveData = {0, NULL}; + + dumpBuf("receiver encKey", encKey); + + memset(&kekDerive, 0, sizeof(kekDerive)); + + /* our private key in CSSM form */ + rv = SecKeyGetCSSMKey(privkey, &ourPrivKeyCssm); + if(rv) { + CSSM_PERROR("SecKeyGetCSSMKey", rv); + goto loser; + } + + /* + * Decode keyEncAlg.params to get KEK algorithm and IV + */ + pool = PORT_NewArena(1024); + if(pool == NULL) { + goto loser; + } + memset(&keyAlgParam, 0, sizeof(keyAlgParam)); + if(SEC_ASN1DecodeItem(pool, &keyAlgParam, SECOID_AlgorithmIDTemplate, + &keyEncAlg->parameters)) { + dprintf("SecCmsUtilDecryptSymKeyECDH: error decoding keyAlgParams\n"); + goto loser; + } + kekOid = SECOID_FindOID(&keyAlgParam.algorithm); + if(kekOid == NULL) { + dprintf("SecCmsUtilDecryptSymKeyECDH: unknown KEK enc OID\n"); + goto loser; + } + rv = encrAlgInfo(kekOid->offset, &kekSizeBits, &kekMode); + if(rv) { + goto loser; + } + /* IV is OCTET STRING in the alg params */ + if(SEC_ASN1DecodeItem(pool, &iv, kSecAsn1OctetStringTemplate, + &keyAlgParam.parameters)) { + /* + * Not sure here - is it legal to have no IV? I haven't seen this + * addressed in any spec. Maybe we should condition the behavior + * here on the KEK algorithm. + */ + dprintf("SecCmsUtilDecryptSymKeyECDH: no KEK IV\n"); + goto loser; + } + + /* + * Now in order to derive the KEK proper, we have to create a + * ECC-CMS-SharedInfo, which does not appear in the message, and DER + * encode that struct, the result of which is used as the + * SharedInfo value in the KEK key derive. + */ + memset(&sharedInfo, 0, sizeof(sharedInfo)); + sharedInfo.algId.algorithm = kekOid->oid; + sharedInfo.algId.parameters.Data = nullData; + sharedInfo.algId.parameters.Length = 2; + sharedInfo.entityUInfo = *ukm; + int32ToBytes(kekSizeBits, keyLenAsBytes); + sharedInfo.suppPubInfo.Length = 4; + sharedInfo.suppPubInfo.Data = keyLenAsBytes; + if (!SEC_ASN1EncodeItem(pool, &sharedInfoEnc, + &sharedInfo, ECC_CMS_SharedInfoTemplate)) { + rv = internalComponentErr; + goto loser; + } + dumpBuf("receiver encoded SharedInfo", &sharedInfoEnc); + dumpBuf("receiver IV", &iv); + dumpBuf("receiver UKM", ukm); + dumpBuf("sender's public key", pubKey); + + /* + * Using the Sec-layer CSPDL, "other's" public key specified as ECPOint param. Which + * is fortunate because that's what we have... + */ + memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); + rv = SecKeyGetCSPHandle(privkey, &refCspHand); + if(rv) { + CSSM_PERROR("SecKeyGetCSPHandle", rv); + goto loser; + } + rv = SecKeyGetCredentials(privkey, + CSSM_ACL_AUTHORIZATION_DERIVE, + kSecCredentialTypeDefault, + &accessCred); + if (rv) { + CSSM_PERROR("SecKeyGetCredentials", rv); + goto loser; + } + crtn = CSSM_CSP_CreateDeriveKeyContext(refCspHand, + CSSM_ALGID_ECDH_X963_KDF, + kekOid->cssmAlgorithm, // algorithm of the KEK + kekSizeBits, + &creds, + ourPrivKeyCssm, // BaseKey + 0, // IterationCount + &sharedInfoEnc, // Salt + 0, // Seed + &ccHand); + if(crtn) { + CSSM_PERROR("CSSM_CSP_CreateDeriveKeyContext", crtn); + goto loser; + } + crtn = CSSM_DeriveKey(ccHand, + pubKey, // param + CSSM_KEYUSE_ANY, + CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE, + &keyLabel, + NULL, // cred/acl + &kekDerive); + CSSM_DeleteContext(ccHand); + ccHand = 0; + if(crtn) { + CSSM_PERROR("CSSM_DeriveKey", crtn); + goto loser; + } + + /* + * Decrypt the encrypted key bits with the KEK key. + */ + crtn = CSSM_CSP_CreateSymmetricContext(refCspHand, + kekOid->cssmAlgorithm, + kekMode, + NULL, // access cred + &kekDerive, + &iv, // InitVector + /* FIXME is this variable too? */ + CSSM_PADDING_PKCS7, + NULL, // Params + &ccHand); + if(rv) { + CSSM_PERROR("CSSM_CSP_CreateSymmetricContext", rv); + goto loser; + } + + memset(&wrappedKey, 0, sizeof(CSSM_KEY)); + memset(&unwrappedKey, 0, sizeof(CSSM_KEY)); + + bulkAlg = SECOID_FindyCssmAlgorithmByTag(bulkalgtag); + if(bulkAlg == CSSM_ALGID_NONE) { + dprintf("SecCmsUtilDecryptSymKeyECDH: unknown bulk alg\n"); + goto loser; + } + + wrappedKey.KeyHeader.HeaderVersion = CSSM_KEYHEADER_VERSION; + wrappedKey.KeyHeader.BlobType = CSSM_KEYBLOB_WRAPPED; + wrappedKey.KeyHeader.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7; + wrappedKey.KeyHeader.AlgorithmId = bulkAlg; + wrappedKey.KeyHeader.KeyClass = CSSM_KEYCLASS_SESSION_KEY; + wrappedKey.KeyHeader.WrapAlgorithmId = kekOid->cssmAlgorithm; + wrappedKey.KeyHeader.WrapMode = CSSM_ALGMODE_NONE; + wrappedKey.KeyData = *encKey; + + crtn = CSSM_UnwrapKey(ccHand, + NULL, /* publicKey */ + &wrappedKey, + CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_EXTRACTABLE, + &keyLabel, + NULL, /* rcc */ + &unwrappedKey, + &descriptiveData); + CSSM_DeleteContext(ccHand); + ccHand = 0; + if(crtn) { + CSSM_PERROR("CSSM_UnwrapKey", crtn); + goto loser; + } + rv = SecKeyCreateWithCSSMKey(&unwrappedKey, &outKey); + if (rv) { + CSSM_PERROR("SecKeyCreateWithCSSMKey", rv); + } + +loser: + if(pool != NULL) { + PORT_FreeArena(pool, PR_FALSE); + } + if(kekDerive.KeyData.Data) { + CSSM_FreeKey(refCspHand, NULL, &kekDerive, CSSM_FALSE); + } + if(outKey == NULL) { + PORT_SetError(SEC_ERROR_NO_KEY); + } + return outKey; +} + + +