#include "AppleManifest.h" #include #include #include #include #include #include #include /* * 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@ */ const int kLengthLength = 8; static void ConvertUInt64ToBytes (UInt64 length, UInt8* bytes) { int i; for (i = kLengthLength - 1; i >= 0; i--) { bytes[i] = length & 0xFF; length >>= 8; } } static void WriteLengthAndUpdate (CFMutableDataRef data, UInt64 length, CFIndex location) { // back patch the length of the list secdebug ("manifest", "Length was %lld, patched at location %lld", length, (UInt64) location); UInt8 lengthBytes[kLengthLength]; ConvertUInt64ToBytes (length, lengthBytes); CFRange range = {location, kLengthLength}; CFDataReplaceBytes (data, range, lengthBytes, kLengthLength); } static CFIndex GetCurrentLengthAndExtend (CFMutableDataRef data) { CFIndex currentIndex = CFDataGetLength (data); CFDataIncreaseLength (data, kLengthLength); return currentIndex; } static void AppendUInt16 (CFMutableDataRef data, UInt16 num) { UInt8 n[2]; n[0] = num >> 8; n[1] = num & 0xFF; CFDataAppendBytes (data, n, sizeof (n)); } static void AppendUInt32 (CFMutableDataRef data, UInt32 num) { UInt8 n[4]; n[0] = (num >> 24) & 0xFF; n[1] = (num >> 16) & 0xFF; n[2] = (num >> 8) & 0xFF; n[3] = num & 0xFF; CFDataAppendBytes (data, n, sizeof (n)); } static void AppendUInt64 (CFMutableDataRef data, UInt64 num) { UInt8 n[8]; n[0] = (num >> 56) & 0xFF; n[1] = (num >> 48) & 0xFF; n[2] = (num >> 40) & 0xFF; n[3] = (num >> 32) & 0xFF; n[4] = (num >> 24) & 0xFF; n[5] = (num >> 16) & 0xFF; n[6] = (num >> 8) & 0xFF; n[7] = num & 0xFF; CFDataAppendBytes (data, n, sizeof (n)); } static void WriteFileSystemItemHeader (CFMutableDataRef data, const FileSystemEntryItem *fsi) { // write the name const char* name = fsi->GetName (); secdebug ("manifest", "\tAdding header for %s", name); int len = strlen (name); AppendUInt16 (data, len); CFDataAppendBytes (data, (UInt8*) name, len); AppendUInt32 (data, fsi->GetUID ()); AppendUInt32 (data, fsi->GetGID ()); AppendUInt32 (data, fsi->GetMode ()); } AppleManifest::AppleManifest () { } AppleManifest::~AppleManifest () { // release our interest in the signers int signerCount = mSignerList.size (); int i; for (i = 0; i < signerCount; ++i) { CFRelease (mSignerList[i]); } } void AppleManifest::AddDirectoryToManifest (CFMutableDataRef manifest, ManifestDirectoryItem* directory) { secdebug ("manifest", "Adding directory %s to manifest", directory->GetName ()); CFIndex currentIndex = GetCurrentLengthAndExtend (manifest); AppendUInt16 (manifest, (UInt16) kManifestDirectoryItemType); WriteFileSystemItemHeader (manifest, directory); AddManifestItemListToManifest (manifest, directory->GetItemList ()); WriteLengthAndUpdate (manifest, CFDataGetLength (manifest) - currentIndex, currentIndex); } void AppleManifest::AddFileToManifest (CFMutableDataRef manifest, ManifestFileItem* file) { CFIndex currentIndex = GetCurrentLengthAndExtend (manifest); AppendUInt16 (manifest, (UInt16) kManifestFileItemType); WriteFileSystemItemHeader (manifest, file); int numForks = file->GetNumberOfForks (); AppendUInt16 (manifest, (UInt16) numForks); int i; // write the file lengths for (i = 0; i < numForks; ++i) { size_t length; length = file->GetForkLength (i); AppendUInt64 (manifest, length); } // write the digests for (i = 0; i < numForks; ++i) { void* sha1Digest; size_t size; file->GetItemRepresentation (i, sha1Digest, size); CFDataAppendBytes (manifest, (UInt8*) sha1Digest, size); } WriteLengthAndUpdate (manifest, CFDataGetLength (manifest) - currentIndex, currentIndex); } void AppleManifest::AddSymLinkToManifest (CFMutableDataRef manifest, ManifestSymLinkItem* file) { CFIndex currentIndex = GetCurrentLengthAndExtend (manifest); AppendUInt16 (manifest, (UInt16) kManifestSymLinkItemType); WriteFileSystemItemHeader (manifest, file); const SHA1Digest* digest = file->GetDigest (); CFDataAppendBytes (manifest, (const UInt8*) digest, kSHA1DigestSize); WriteLengthAndUpdate (manifest, CFDataGetLength (manifest) - currentIndex, currentIndex); } void AppleManifest::AddOtherToManifest (CFMutableDataRef manifest, ManifestOtherItem* other) { CFIndex currentIndex = GetCurrentLengthAndExtend (manifest); AppendUInt16 (manifest, (UInt16) kManifestSymLinkItemType); WriteFileSystemItemHeader (manifest, other); WriteLengthAndUpdate (manifest, CFDataGetLength (manifest) - currentIndex, currentIndex); } void AppleManifest::AddDataBlobToManifest (CFMutableDataRef manifest, ManifestDataBlobItem* item) { CFIndex currentIndex = GetCurrentLengthAndExtend (manifest); AppendUInt16 (manifest, (UInt16) kManifestDataBlobItemType); AppendUInt64 (manifest, (UInt64) item->GetLength ()); const SHA1Digest* sha1Digest = item->GetDigest (); CFDataAppendBytes (manifest, (UInt8*) sha1Digest, sizeof (SHA1Digest)); WriteLengthAndUpdate (manifest, CFDataGetLength (manifest) - currentIndex, currentIndex); } void AppleManifest::AddManifestItemListToManifest (CFMutableDataRef data, ManifestItemList &itemList) { // save the current position CFIndex currentIndex = GetCurrentLengthAndExtend (data); unsigned i; for (i = 0; i < itemList.size (); ++i) { ManifestItem* item = itemList[i]; switch (item->GetItemType ()) { case kManifestDataBlobItemType: { AddDataBlobToManifest (data, static_cast(item)); break; } case kManifestFileItemType: { AddFileToManifest (data, static_cast(item)); break; } case kManifestDirectoryItemType: { AddDirectoryToManifest (data, static_cast(item)); break; } case kManifestSymLinkItemType: { AddSymLinkToManifest (data, static_cast(item)); break; } case kManifestOtherType: { AddOtherToManifest (data, static_cast(item)); break; } } } WriteLengthAndUpdate (data, CFDataGetLength (data) - currentIndex, currentIndex); } static const char gManifestHeader[] = {0x2F, 0xAA, 0x05, 0xB3, 0x64, 0x0E, 0x9D, 0x27}; // why these numbers? These were picked at random static const char gManifestVersion[] = {0x01, 0x00, 0x00, 0x00}; void AppleManifest::CreateManifest (CFMutableDataRef data, ManifestInternal& manifest) { // create the manifest header CFDataAppendBytes (data, (UInt8*) gManifestHeader, sizeof (gManifestHeader)); CFDataAppendBytes (data, (UInt8*) gManifestVersion, sizeof (gManifestVersion)); AddManifestItemListToManifest (data, manifest.GetItemList ()); } void AppleManifest::AddSignersToCmsMessage (SecCmsMessageRef cmsMessage, SecCmsSignedDataRef signedData) { // add signers for each of our signers int numSigners = mSignerList.size (); int i; for (i = 0; i < numSigners; ++i) { SecIdentityRef id = mSignerList[i]; SecCmsSignerInfoRef signerInfo = SecCmsSignerInfoCreate (cmsMessage, id, SEC_OID_SHA1); if (signerInfo == NULL) { SecCmsMessageDestroy (cmsMessage); MacOSError::throwMe (errSecManifestCMSFailure); } int result = SecCmsSignerInfoIncludeCerts (signerInfo, SecCmsCMCertChain, certUsageObjectSigner); if (result != 0) { SecCmsMessageDestroy (cmsMessage); MacOSError::throwMe (errSecManifestCMSFailure); } SecCmsSignedDataAddSignerInfo (signedData, signerInfo); } } CFDataRef AppleManifest::Export (ManifestInternal& manifest) { // there had better be at least one signer if (mSignerList.size () == 0) { secdebug ("manifest", "No signers found"); MacOSError::throwMe (errSecManifestNoSigners); } // create a CFMutableDataRef to hold the manifest object CFMutableDataRef data = CFDataCreateMutable (kCFAllocatorDefault, 0); // make the manifest CreateManifest (data, manifest); // make the PKCS #7 wrapper SecCmsMessageRef cmsMessage; cmsMessage = SecCmsMessageCreate (NULL); if (cmsMessage == NULL) // did something go wrong? { MacOSError::throwMe (errSecManifestCMSFailure); } // create a signed data holder SecCmsSignedDataRef signedData; signedData = SecCmsSignedDataCreate (cmsMessage); if (signedData == NULL) { SecCmsMessageDestroy (cmsMessage); MacOSError::throwMe (errSecManifestCMSFailure); } // link the signed data and the CMS message SecCmsContentInfoRef contentInfo = SecCmsMessageGetContentInfo (cmsMessage); int result = SecCmsContentInfoSetContentSignedData (cmsMessage, contentInfo, signedData); if (result != 0) { SecCmsMessageDestroy (cmsMessage); MacOSError::throwMe (errSecManifestCMSFailure); } // attach the content information from the signature to the data contentInfo = SecCmsSignedDataGetContentInfo (signedData); result = SecCmsContentInfoSetContentData (cmsMessage, contentInfo, NULL, false); if (result != 0) { SecCmsMessageDestroy (cmsMessage); MacOSError::throwMe (errSecManifestCMSFailure); } AddSignersToCmsMessage (cmsMessage, signedData); // make an encoder context SecArenaPoolRef arena; result = SecArenaPoolCreate(1024, &arena); if (result) { MacOSError::throwMe (errSecManifestCMSFailure); } CSSM_DATA finalMessage = {0, NULL}; SecCmsEncoderRef encoderContext; result = SecCmsEncoderCreate (cmsMessage, NULL, NULL, &finalMessage, arena, NULL, NULL, NULL, NULL, NULL, NULL, &encoderContext); if (result) { MacOSError::throwMe (errSecManifestCMSFailure); } result = SecCmsEncoderUpdate (encoderContext, CFDataGetBytePtr (data), CFDataGetLength (data)); if (result != 0) { SecCmsMessageDestroy (cmsMessage); MacOSError::throwMe (errSecManifestCMSFailure); } result = SecCmsEncoderFinish (encoderContext); if (result != 0) { MacOSError::throwMe (errSecManifestCMSFailure); } // create a CFData from the results CFDataRef retData = CFDataCreate (kCFAllocatorDefault, (UInt8*) finalMessage.Data, finalMessage.Length); SecArenaPoolFree(arena, false); CFRelease (data); return retData; } static u_int64_t ReconstructUInt64 (uint32& finger, const uint8* data) { unsigned i; u_int64_t r = 0; for (i = 0; i < sizeof (u_int64_t); ++i) { r = (r << 8) | data[finger++]; } return r; } static u_int32_t ReconstructUInt32 (uint32& finger, const uint8* data) { unsigned i; u_int64_t r = 0; for (i = 0; i < sizeof (u_int32_t); ++i) { r = (r << 8) | data[finger++]; } return r; } static u_int16_t ReconstructUInt16 (uint32& finger, const uint8* data) { unsigned i; u_int64_t r = 0; for (i = 0; i < sizeof (u_int16_t); ++i) { r = (r << 8) | data[finger++]; } return r; } static void ReconstructFileSystemHeader (uint32& finger, const uint8* data, FileSystemEntryItem* item) { // get the number of bytes for the name u_int16_t length = ReconstructUInt16 (finger, data); char name[length + 1]; // make a c-string for the name memcpy (name, data + finger, length); name[length] = 0; item->SetName (name); secdebug ("manifest", " File item name is %s", name); finger += length; uid_t uid = (uid_t) ReconstructUInt32 (finger, data); gid_t gid = (gid_t) ReconstructUInt32 (finger, data); mode_t mode = (mode_t) ReconstructUInt32 (finger, data); secdebug ("manifest", " File item uid is %d", uid); secdebug ("manifest", " File item gid is %d", gid); secdebug ("manifest", " File item mode is %d", mode); item->SetUID (uid); item->SetGID (gid); item->SetMode (mode); } static void ParseItemHeader (uint32 &finger, const uint8* data, ManifestItemType &itemType, u_int64_t &end) { u_int64_t start = finger; u_int64_t length = ReconstructUInt64 (finger, data); itemType = (ManifestItemType) ReconstructUInt16 (finger, data); end = start + length; } void AppleManifest::ReconstructDataBlob (uint32 &finger, const uint8* data, ManifestDataBlobItem*& item) { secdebug ("manifest", "Reconstructing data blob."); item = new ManifestDataBlobItem (); u_int64_t length = ReconstructUInt64 (finger, data); item->SetLength (length); item->SetDigest ((SHA1Digest*) (data + finger)); finger += kSHA1DigestSize; } void AppleManifest::ReconstructDirectory (uint32 &finger, const uint8* data, ManifestDirectoryItem*& directory) { // make the directory secdebug ("manifest", "Reconstructing directory."); directory = new ManifestDirectoryItem (); ReconstructFileSystemHeader (finger, data, directory); ReconstructManifestItemList (finger, data, directory->GetItemList ()); } void AppleManifest::ReconstructFile (uint32& finger, const uint8* data, ManifestFileItem *& file) { secdebug ("manifest", "Reconstructing file."); // make the file file = new ManifestFileItem (); ReconstructFileSystemHeader (finger, data, file); u_int16_t numForks = ReconstructUInt16 (finger, data); file->SetNumberOfForks (numForks); // reconstruct the lengths u_int16_t n; for (n = 0; n < numForks; ++n) { u_int64_t length = ReconstructUInt64 (finger, data); file->SetForkLength (n, (size_t) length); } // reconstruct the digests for (n = 0; n < numForks; ++n) { file->SetItemRepresentation (n, data + finger, kSHA1DigestSize); finger += kSHA1DigestSize; } } void AppleManifest::ReconstructSymLink (uint32& finger, const uint8* data, ManifestSymLinkItem*& file) { secdebug ("manifest", "Reconstructing symlink."); file = new ManifestSymLinkItem (); ReconstructFileSystemHeader (finger, data, file); file->SetDigest ((const SHA1Digest*) (data + finger)); finger += kSHA1DigestSize; } void AppleManifest::ReconstructOther (uint32& finger, const uint8* data, ManifestOtherItem*& other) { secdebug ("manifest", "Reconstructing other."); other = new ManifestOtherItem (); ReconstructFileSystemHeader (finger, data, other); } void AppleManifest::ReconstructManifestItemList (uint32 &finger, const uint8* data, ManifestItemList &itemList) { uint32 start = finger; u_int64_t length = ReconstructUInt64 (finger, data); uint32 end = start + length; while (finger < end) { u_int64_t itemEnd; ManifestItemType itemType; ParseItemHeader (finger, data, itemType, itemEnd); switch (itemType) { case kManifestFileItemType: { ManifestFileItem* file; ReconstructFile (finger, data, file); itemList.push_back (file); } break; case kManifestDirectoryItemType: { ManifestDirectoryItem* directory; ReconstructDirectory (finger, data, directory); itemList.push_back (directory); } break; case kManifestSymLinkItemType: { ManifestSymLinkItem* symLink; ReconstructSymLink (finger, data, symLink); itemList.push_back (symLink); } break; case kManifestOtherType: { ManifestOtherItem* other; ReconstructOther (finger, data, other); itemList.push_back (other); } break; case kManifestDataBlobItemType: { ManifestDataBlobItem* item; ReconstructDataBlob (finger, data, item); itemList.push_back (item); } break; } if (finger != itemEnd) { MacOSError::throwMe (errSecManifestDamaged); } } } void AppleManifest::ReconstructManifest (uint8* data, uint32 length, ManifestInternal& manifest) { uint32 finger = 0; // make sure the passed-in header starts with our magic number if (memcmp (data, gManifestHeader, sizeof (gManifestHeader)) != 0) { MacOSError::throwMe (errSecManifestDamaged); } finger += sizeof (gManifestHeader); // for now, the version had better be 0x01000000 if (memcmp (data + finger, gManifestVersion, sizeof (gManifestVersion)) != 0) { MacOSError::throwMe (errSecManifestDamaged); } finger += sizeof (gManifestVersion); ReconstructManifestItemList (finger, data, manifest.GetItemList ()); } SecCmsMessageRef AppleManifest::GetCmsMessageFromData (CFDataRef data) { // setup decoding SecCmsDecoderRef decoderContext; int result = SecCmsDecoderCreate (NULL, NULL, NULL, NULL, NULL, NULL, NULL, &decoderContext); if (result) { MacOSError::throwMe (errSecManifestCMSFailure); } result = SecCmsDecoderUpdate (decoderContext, CFDataGetBytePtr (data), CFDataGetLength (data)); if (result) { SecCmsDecoderDestroy(decoderContext); MacOSError::throwMe (errSecManifestCMSFailure); } SecCmsMessageRef message; result = SecCmsDecoderFinish (decoderContext, &message); if (result) { MacOSError::throwMe (errSecManifestCMSFailure); } return message; } void AppleManifest::Verify (CFDataRef data, SecManifestTrustSetupCallback setupCallback, void* setupContext, SecManifestTrustEvaluateCallback evaluateCallback, void* evaluateContext, SecPolicyRef policy, ManifestInternal *manifest) { SecCmsMessageRef cmsMessage = NULL; try { cmsMessage = GetCmsMessageFromData (data); SecPolicySearchRef search; OSStatus result; if (policy == NULL) { // get a basic SecPolicy result = SecPolicySearchCreate (CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &search); MacOSError::check (result); result = SecPolicySearchCopyNext (search, &policy); if (result != noErr) { MacOSError::throwMe (errSecManifestNoPolicy); } CFRelease (search); } // process the results int contentLevelCount = SecCmsMessageContentLevelCount (cmsMessage); SecCmsSignedDataRef signedData; int i = 0; while (i < contentLevelCount) { SecCmsContentInfoRef contentInfo = SecCmsMessageContentLevel (cmsMessage, i++); SECOidTag contentTypeTag = SecCmsContentInfoGetContentTypeTag (contentInfo); if (contentTypeTag != SEC_OID_PKCS7_SIGNED_DATA) { continue; } signedData = (SecCmsSignedDataRef) SecCmsContentInfoGetContent (contentInfo); if (signedData == NULL) { MacOSError::throwMe (errSecManifestDidNotVerify); } // import the certificates found in the cms message result = SecCmsSignedDataImportCerts (signedData, NULL, certUsageObjectSigner, true); if (result != 0) { MacOSError::throwMe (errSecManifestDidNotVerify); } int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData); int j; if (numberOfSigners == 0) // no signers? This is a possible attack { MacOSError::throwMe (errSecManifestNoSignersFound); } for (j = 0; j < numberOfSigners; ++j) { SecTrustResultType resultType; SecTrustRef trustRef; result = SecCmsSignedDataVerifySignerInfo (signedData, j, NULL, policy, &trustRef); if (result != 0) { MacOSError::throwMe (errSecManifestDidNotVerify); } SecManifestTrustCallbackResult tcResult = setupCallback (trustRef, setupContext); switch (tcResult) { case kSecManifestDoNotVerify: continue; case kSecManifestSignerVerified: continue; case kSecManifestFailed: MacOSError::throwMe (errSecManifestDidNotVerify); case kSecManifestContinue: break; } result = SecTrustEvaluate (trustRef, &resultType); if (result != noErr) { MacOSError::throwMe (errSecManifestDidNotVerify); } if (resultType != kSecTrustResultProceed) { if (evaluateCallback (trustRef, resultType, evaluateContext) != kSecManifestSignerVerified) { MacOSError::throwMe (errSecManifestDidNotVerify); } } } } if (manifest != NULL) { CSSM_DATA_PTR message = SecCmsMessageGetContent (cmsMessage); ReconstructManifest (message->Data, message->Length, *manifest); } SecCmsMessageDestroy (cmsMessage); } catch (...) { if (cmsMessage != NULL) { SecCmsMessageDestroy (cmsMessage); } throw; } } void AppleManifest::AddSigner (SecIdentityRef identityRef) { CFRetain (identityRef); mSignerList.push_back (identityRef); }