/* * Copyright (c) 2003-2004,2011-2014 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * pkcs12Encode.h - P12Coder encoding engine. * * Unlike the decoding side of P12Coder, which can parse PFXs with * more or less arbitrary structures, this encoding engine has * a specific layout for what bags go where in the jungle of * SafeContents and ContentInfos. It would be impractical to allow * (or to expect) the app to specify this structure. * * The knowledge of how a PFX is built out of various components * is encapsulated in the authSafeBuild() member function. The rest * of the functions in this file are pretty much "PFX-structure- * agnostic", so if one wanted to change the overall PFX structure, * one would only have to focus on the authSafeBuild() function. */ #include "pkcs12Coder.h" #include "pkcs12Debug.h" #include "pkcs12Crypto.h" #include "pkcs12Templates.h" #include "pkcs12Utils.h" #include #include #include void P12Coder::encode( CFDataRef *cpfx) // RETURNED { p12EncodeLog("encode top"); SecNssCoder localCdr; NSS_P12_DecodedPFX pfx; memset(&pfx, 0, sizeof(pfx)); p12IntToData(3, pfx.version, localCdr); authSafeBuild(pfx.authSafe, localCdr); macSignPfx(pfx, localCdr); CSSM_DATA derPfx = {0, NULL}; if(localCdr.encodeItem(&pfx, NSS_P12_DecodedPFXTemplate, derPfx)) { p12ErrorLog("Error encoding top-level pfx\n"); P12_THROW_ENCODE; } CFDataRef cp = CFDataCreate(NULL, derPfx.Data, derPfx.Length); *cpfx = cp; } void P12Coder::macSignPfx( NSS_P12_DecodedPFX &pfx, SecNssCoder &localCdr) { p12EncodeLog("macSignPfx"); NSS_P12_MacData *macData = localCdr.mallocn(); pfx.macData = macData; p12GenSalt(macData->macSalt, localCdr); p12IntToData(mMacIterCount, macData->iterations, localCdr); NSS_P7_DigestInfo &digInfo = macData->mac; /* this is not negotiable; it's the only one P12 allows */ localCdr.allocCopyItem(CSSMOID_SHA1, digInfo.digestAlgorithm.algorithm); /* null algorithm parameters */ p12NullAlgParams(digInfo.digestAlgorithm); const CSSM_DATA *macPhrase = getMacPassPhrase(); const CSSM_KEY *macPassKey = getMacPassKey(); if((macPhrase == NULL) && (macPassKey == NULL)) { p12ErrorLog("no passphrase set\n"); CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_PASSPHRASE); } CSSM_RETURN crtn = p12GenMac(mCspHand, *pfx.authSafe.content.data, CSSM_ALGID_SHA1, mMacIterCount, macData->macSalt, macPhrase, macPassKey, localCdr, digInfo.digest); if(crtn) { p12ErrorLog("Error generating PFX MAC\n"); CssmError::throwMe(crtn); } } /* * This is the heart of the encoding engine. All knowledge of * "what bags go where" is here. The PFX structure implemented here * is derived from empirical observation of PFXs obtained from * Mozilla 1.2b and from the DoD test vectors for "Conformance * Testing of Relying Party Client Certificate Path Processing * Logic", written by Cygnacom, Septemtber 28, 2001. * * The PFX structure is as follows: * * -- One AuthenticatedSafe element (a PKCS7 ContentInfo) containing * all certificates and CRLs. * * ContentInfo.type = CT_EncryptedData * Encryption algorithm is our "weak" encryption Alg, default * of CSSMOID_PKCS12_pbeWithSHAAnd40BitRC4. * * -- One AuthenticatedSafe element containing all private keys in * the form of ShroudedKeyBags. * * ContentInfo.type = CT_Data * Encryption algorithm for shrouded key bags is our "strong" * encryption, default CSSMOID_PKCS12_pbeWithSHAAnd3Key3DESCBC * * -- Everything else goes in another AuthenticatedSafe element. * * ContentInfo.type = CT_EncryptedData * Encryption algorithm is our "strong" encryption Alg */ void P12Coder::authSafeBuild( NSS_P7_DecodedContentInfo &authSafe, SecNssCoder &localCdr) { p12EncodeLog("authSafeBuild top"); /* how many contentInfos are we going to build? */ unsigned numContents = 0; if(mCerts.size() || mCrls.size()) { numContents++; } if(mKeys.size()) { numContents++; } if(mOpaques.size()) { numContents++; } if(numContents == 0) { p12ErrorLog("authSafeBuild: no contents\n"); MacOSError::throwMe(errSecParam); } NSS_P7_DecodedContentInfo **contents = (NSS_P7_DecodedContentInfo **)p12NssNullArray(numContents, localCdr); unsigned contentDex = 0; NSS_P12_SafeBag **safeBags; /* certs & crls */ unsigned numBags = (unsigned)(mCerts.size() + mCrls.size()); p12EncodeLog("authSafeBuild : %u certs + CRLS", numBags); if(numBags) { safeBags = (NSS_P12_SafeBag **)p12NssNullArray(numBags, localCdr); unsigned bagDex = 0; for(unsigned dex=0; dex(); authSafe.content.data = adata; adata->Data = NULL; adata->Length = 0; if(localCdr.encodeItem(&safe, NSS_P12_AuthenticatedSafeTemplate, *adata)) { p12ErrorLog("authSafeBuild: error encoding auth safe\n"); P12_THROW_ENCODE; } authSafe.type = CT_Data; authSafe.contentType = CSSMOID_PKCS7_Data; } /* * Build a AuthSafe element of specified type out of the * specified array of bags. */ NSS_P7_DecodedContentInfo *P12Coder::safeContentsBuild( NSS_P12_SafeBag **bags, NSS_P7_CI_Type type, // CT_Data, CT_EncryptedData CSSM_OID *encrOid, // only for CT_EncryptedData unsigned iterCount, // ditto SecNssCoder &localCdr) { p12EncodeLog("safeContentsBuild type %u", (unsigned)type); /* * First, encode the bag array as a SafeContents */ CSSM_DATA encSafeContents = {0, NULL}; NSS_P12_SafeContents safeContents = {bags}; if(localCdr.encodeItem(&safeContents, NSS_P12_SafeContentsTemplate, encSafeContents)) { p12ErrorLog("error encoding SafeContents\n"); P12_THROW_ENCODE; } NSS_P7_DecodedContentInfo *dci = localCdr.mallocn(); dci->type = type; if(type == CT_Data) { /* plaintext gets encoded as an octet string */ localCdr.allocCopyItem(CSSMOID_PKCS7_Data, dci->contentType); dci->content.data = localCdr.mallocn(); localCdr.allocCopyItem(encSafeContents, *dci->content.data); } else if(type == CT_EncryptedData) { /* encrypt the encoded SafeContents */ localCdr.allocCopyItem(CSSMOID_PKCS7_EncryptedData, dci->contentType); dci->content.encryptData = localCdr.mallocn(); NSS_P7_EncryptedData *ed = dci->content.encryptData; assert(encrOid != NULL); encryptData(encSafeContents, *encrOid, iterCount, *ed, localCdr); } else { p12ErrorLog("bad type in safeContentsBuild\n"); CssmError::throwMe(CSSMERR_CL_INTERNAL_ERROR); } return dci; } /* * Encrypt the specified plaintext with specified algorithm. * Drop result and other interesting info into an NSS_P7_EncryptedData. */ void P12Coder::encryptData( const CSSM_DATA &ptext, CSSM_OID &encrOid, unsigned iterCount, NSS_P7_EncryptedData &ed, SecNssCoder &localCdr) { p12EncodeLog("encryptData"); /* do the raw encrypt first to make sure we can do it... */ CSSM_ALGORITHMS keyAlg; // e.g., CSSM_ALGID_DES CSSM_ALGORITHMS encrAlg; // e.g., CSSM_ALGID_3DES_3KEY_EDE CSSM_ALGORITHMS pbeHashAlg; // SHA1 or MD5 uint32 keySizeInBits; uint32 blockSizeInBytes; // for IV, optional CSSM_PADDING padding; // CSSM_PADDING_PKCS7, etc. CSSM_ENCRYPT_MODE mode; // CSSM_ALGMODE_CBCPadIV8, etc. PKCS_Which pkcs; bool found = pkcsOidToParams(&encrOid, keyAlg, encrAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes, padding, mode, pkcs); if(!found || (pkcs != PW_PKCS12)) { p12ErrorLog("encryptData encrAlg not understood\n"); CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); } /* Salt: we generate random bytes */ CSSM_DATA salt; p12GenSalt(salt, localCdr); const CSSM_DATA *pwd = getEncrPassPhrase(); const CSSM_KEY *passKey = getEncrPassKey(); if((pwd == NULL) && (passKey == NULL)) { p12ErrorLog("no passphrase set\n"); CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_PASSPHRASE); } CSSM_DATA ctext = {0, NULL}; CSSM_RETURN crtn = p12Encrypt(mCspHand, ptext, keyAlg, encrAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes, padding, mode, iterCount, salt, pwd, passKey, localCdr, ctext); if(crtn) { CssmError::throwMe(crtn); } /* Now fill in the NSS_P7_EncryptedData */ p12IntToData(0, ed.version, localCdr); NSS_P7_EncrContentInfo &eci = ed.contentInfo; localCdr.allocCopyItem(CSSMOID_PKCS7_Data, eci.contentType); algIdBuild(eci.encrAlg, encrOid, salt, iterCount, localCdr); eci.encrContent = ctext; } /* * Fill in an CSSM_X509_ALGORITHM_IDENTIFIER with parameters in * the form of an encoded NSS_P12_PBE_Params */ void P12Coder::algIdBuild( CSSM_X509_ALGORITHM_IDENTIFIER &algId, const CSSM_OID &algOid, const CSSM_DATA &salt, unsigned iterCount, SecNssCoder &localCdr) { p12EncodeLog("algIdBuild"); localCdr.allocCopyItem(algOid, algId.algorithm); NSS_P12_PBE_Params pbeParams; pbeParams.salt = salt; p12IntToData(iterCount, pbeParams.iterations, localCdr); if(localCdr.encodeItem(&pbeParams, NSS_P12_PBE_ParamsTemplate, algId.parameters)) { p12ErrorLog("error encoding NSS_P12_PBE_Params\n"); P12_THROW_ENCODE; } } #pragma mark --- Individual Bag Builders --- NSS_P12_SafeBag *P12Coder::certBagBuild( P12CertBag *cert, SecNssCoder &localCdr) { p12EncodeLog("certBagBuild"); NSS_P12_SafeBag *safeBag = localCdr.mallocn(); safeBag->bagId = CSSMOID_PKCS12_certBag; safeBag->type = BT_CertBag; NSS_P12_CertBag *certBag = localCdr.mallocn(); safeBag->bagValue.certBag = certBag; const CSSM_OID *certTypeOid = NULL; switch(cert->certType()) { case CT_X509: certTypeOid = &CSSMOID_PKCS9_X509Certificate; break; case CT_SDSI: certTypeOid = &CSSMOID_PKCS9_SdsiCertificate; break; default: p12ErrorLog("unknown certType on encode\n"); CssmError::throwMe(CSSMERR_CSP_INTERNAL_ERROR); } /* copies not needed, same scope as P12CertBag */ certBag->bagType = *certTypeOid; certBag->type = cert->certType(); certBag->certValue = cert->certData(); safeBag->bagAttrs = cert->getAllAttrs(); return safeBag; } NSS_P12_SafeBag *P12Coder::crlBagBuild( P12CrlBag *crl, SecNssCoder &localCdr) { p12EncodeLog("crlBagBuild"); NSS_P12_SafeBag *safeBag = localCdr.mallocn(); safeBag->bagId = CSSMOID_PKCS12_crlBag; safeBag->type = BT_CrlBag; NSS_P12_CrlBag *crlBag = localCdr.mallocn(); safeBag->bagValue.crlBag = crlBag; const CSSM_OID *crlTypeOid = NULL; switch(crl->crlType()) { case CRT_X509: crlTypeOid = &CSSMOID_PKCS9_X509Crl; break; default: p12ErrorLog("unknown crlType on encode\n"); CssmError::throwMe(CSSMERR_CSP_INTERNAL_ERROR); } /* copies not needed, same scope as P12CrlBag */ crlBag->bagType = *crlTypeOid; crlBag->type = crl->crlType(); crlBag->crlValue = crl->crlData(); safeBag->bagAttrs = crl->getAllAttrs(); return safeBag; } NSS_P12_SafeBag *P12Coder::keyBagBuild( P12KeyBag *key, SecNssCoder &localCdr) { p12EncodeLog("keyBagBuild"); NSS_P12_SafeBag *safeBag = localCdr.mallocn(); safeBag->bagId = CSSMOID_PKCS12_shroudedKeyBag; safeBag->type = BT_ShroudedKeyBag; NSS_EncryptedPrivateKeyInfo *keyInfo = localCdr. mallocn(); safeBag->bagValue.shroudedKeyBag = keyInfo; safeBag->bagAttrs = key->getAllAttrs(); /* Prepare for key wrap */ CSSM_DATA salt; p12GenSalt(salt, localCdr); algIdBuild(keyInfo->algorithm, mStrongEncrAlg, salt, mStrongEncrIterCount, localCdr); CSSM_ALGORITHMS keyAlg; // e.g., CSSM_ALGID_DES CSSM_ALGORITHMS encrAlg; // e.g., CSSM_ALGID_3DES_3KEY_EDE CSSM_ALGORITHMS pbeHashAlg; // SHA1 or MD5 uint32 keySizeInBits; uint32 blockSizeInBytes; // for IV, optional CSSM_PADDING padding; // CSSM_PADDING_PKCS7, etc. CSSM_ENCRYPT_MODE mode; // CSSM_ALGMODE_CBCPadIV8, etc. PKCS_Which pkcs; bool found = pkcsOidToParams(&mStrongEncrAlg, keyAlg, encrAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes, padding, mode, pkcs); if(!found || (pkcs != PW_PKCS12)) { /* app config error - they gave us bogus algorithm */ p12ErrorLog("keyBagBuild encrAlg not understood\n"); CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); } const CSSM_DATA *encrPhrase = getEncrPassPhrase(); const CSSM_KEY *passKey = getEncrPassKey(); if((encrPhrase == NULL) && (passKey == NULL)) { p12ErrorLog("no passphrase set\n"); CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_PASSPHRASE); } CSSM_DATA shroudedBits = {0, NULL}; CSSM_RETURN crtn = p12WrapKey(mCspHand, key->key(), key->privKeyCreds(), keyAlg, encrAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes, padding, mode, mStrongEncrIterCount, salt, encrPhrase, passKey, localCdr, shroudedBits); if(crtn) { p12ErrorLog("Error wrapping private key\n"); CssmError::throwMe(crtn); } keyInfo->encryptedData = shroudedBits; return safeBag; } NSS_P12_SafeBag *P12Coder::opaqueBagBuild( P12OpaqueBag *opaque, SecNssCoder &localCdr) { p12EncodeLog("opaqueBagBuild"); NSS_P12_SafeBag *safeBag = localCdr.mallocn(); safeBag->bagId = opaque->oid(); safeBag->bagValue.secretBag = &opaque->blob(); safeBag->bagAttrs = opaque->getAllAttrs(); return safeBag; }