]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_pkcs12/lib/pkcs12Encode.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_pkcs12 / lib / pkcs12Encode.cpp
diff --git a/libsecurity_pkcs12/lib/pkcs12Encode.cpp b/libsecurity_pkcs12/lib/pkcs12Encode.cpp
new file mode 100644 (file)
index 0000000..a1ad288
--- /dev/null
@@ -0,0 +1,497 @@
+/*
+ * Copyright (c) 2003-2004 Apple Computer, 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 <Security/cssmerr.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <Security/oidsattr.h>
+
+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<NSS_P12_MacData>();
+       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(paramErr);
+       }
+       
+       NSS_P7_DecodedContentInfo **contents = 
+               (NSS_P7_DecodedContentInfo **)p12NssNullArray(numContents, 
+                       localCdr);
+       unsigned contentDex = 0;
+       
+       NSS_P12_SafeBag **safeBags;
+
+       /* certs & crls */
+       unsigned numBags = 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<mCerts.size(); dex++) {
+                       safeBags[bagDex++] = certBagBuild(mCerts[dex], localCdr);
+               } 
+               for(unsigned dex=0; dex<mCrls.size(); dex++) {
+                       safeBags[bagDex++] = crlBagBuild(mCrls[dex], localCdr);
+               } 
+               contents[contentDex++] = safeContentsBuild(safeBags, 
+                       CT_EncryptedData, &mWeakEncrAlg, mWeakEncrIterCount, localCdr);
+       }
+       
+       /* shrouded keys - encrypted at bag level */
+       numBags = mKeys.size();
+       if(numBags) {
+               p12EncodeLog("authSafeBuild : %u keys", numBags);
+               safeBags = (NSS_P12_SafeBag **)p12NssNullArray(numBags, localCdr);
+               unsigned bagDex = 0;
+               for(unsigned dex=0; dex<numBags; dex++) {
+                       safeBags[bagDex++] = keyBagBuild(mKeys[dex], localCdr);
+               } 
+               contents[contentDex++] = safeContentsBuild(safeBags, 
+                       CT_Data, NULL, 0, localCdr);
+       }
+       
+       /* opaque */
+       numBags = mOpaques.size();
+       if(numBags) {
+               p12EncodeLog("authSafeBuild : %u opaques", numBags);
+               safeBags = (NSS_P12_SafeBag **)p12NssNullArray(numBags, localCdr);
+               unsigned bagDex = 0;
+               for(unsigned dex=0; dex<numBags; dex++) {
+                       safeBags[bagDex++] = opaqueBagBuild(mOpaques[dex], localCdr);
+               } 
+               contents[contentDex++] = safeContentsBuild(safeBags, 
+                       CT_EncryptedData, &mStrongEncrAlg, mStrongEncrIterCount, localCdr);
+       }
+       
+       /*
+        * Encode the whole elements array into authSafe.content.data
+        */
+       NSS_P12_AuthenticatedSafe safe;
+       safe.info = contents;
+       CSSM_DATA *adata = localCdr.mallocn<CSSM_DATA>();
+       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<NSS_P7_DecodedContentInfo>();
+       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<CSSM_DATA>();
+               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>();
+               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<NSS_P12_SafeBag>();
+       safeBag->bagId = CSSMOID_PKCS12_certBag;
+       safeBag->type = BT_CertBag;
+       
+       NSS_P12_CertBag *certBag = localCdr.mallocn<NSS_P12_CertBag>();
+       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<NSS_P12_SafeBag>();
+       safeBag->bagId = CSSMOID_PKCS12_crlBag;
+       safeBag->type = BT_CrlBag;
+       
+       NSS_P12_CrlBag *crlBag = localCdr.mallocn<NSS_P12_CrlBag>();
+       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<NSS_P12_SafeBag>();
+       safeBag->bagId = CSSMOID_PKCS12_shroudedKeyBag;
+       safeBag->type = BT_ShroudedKeyBag;
+       
+       NSS_EncryptedPrivateKeyInfo *keyInfo = localCdr.
+               mallocn<NSS_EncryptedPrivateKeyInfo>();
+       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<NSS_P12_SafeBag>();
+       safeBag->bagId = opaque->oid();
+       safeBag->bagValue.secretBag = &opaque->blob();
+       safeBag->bagAttrs = opaque->getAllAttrs();
+       return safeBag;
+}