X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_manifest/lib/AppleManifest.cpp?ds=inline diff --git a/Security/libsecurity_manifest/lib/AppleManifest.cpp b/Security/libsecurity_manifest/lib/AppleManifest.cpp new file mode 100644 index 00000000..31db9426 --- /dev/null +++ b/Security/libsecurity_manifest/lib/AppleManifest.cpp @@ -0,0 +1,858 @@ +#include "AppleManifest.h" +#include +#include +#include +#include +#include +#include +#include + + +/* + * 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@ + */ + + + +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); + uint16_t len = (uint16_t)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 = (int)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 manifest, ManifestInternal& internalManifest) +{ + // create the manifest header + CFDataAppendBytes (manifest, (UInt8*) gManifestHeader, sizeof (gManifestHeader)); + CFDataAppendBytes (manifest, (UInt8*) gManifestVersion, sizeof (gManifestVersion)); + AddManifestItemListToManifest (manifest, internalManifest.GetItemList ()); +} + + + +void AppleManifest::AddSignersToCmsMessage (SecCmsMessageRef cmsMessage, SecCmsSignedDataRef signedData) +{ + // add signers for each of our signers + int numSigners = (int)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); + SecCmsMessageDestroy (cmsMessage); + + 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_int32_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_int16_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 ((size_t)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); +#warning Casting from uint64 to uint32, this is ripe for overflow. + uint32 end = (uint32)(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; + + SecPolicyRef originalPolicy = policy; + + 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 != errSecSuccess) + { + 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 (result); + } + + 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 = NULL; + + try + { + result = SecCmsSignedDataVerifySignerInfo (signedData, j, NULL, policy, &trustRef); + + if (result != 0) + { + MacOSError::throwMe (result); + } + + 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 != errSecSuccess) + { + MacOSError::throwMe (result); + } + + if (resultType != kSecTrustResultProceed) + { + if (evaluateCallback (trustRef, resultType, evaluateContext) != kSecManifestSignerVerified) + { + MacOSError::throwMe (errSecManifestDidNotVerify); + } + } + + CFRelease (trustRef); + } + catch (...) + { + if (trustRef != NULL) + { + CFRelease (trustRef); + } + + throw; + } + } + } + + if (manifest != NULL) + { + CSSM_DATA_PTR message = SecCmsMessageGetContent (cmsMessage); + ReconstructManifest (message->Data, (uint32)message->Length, *manifest); + } + + SecCmsMessageDestroy (cmsMessage); + if (originalPolicy == NULL) + { + CFRelease (policy); + } + } + catch (...) + { + if (cmsMessage != NULL) + { + SecCmsMessageDestroy (cmsMessage); + } + + throw; + } +} + + + +void AppleManifest::AddSigner (SecIdentityRef identityRef) +{ + CFRetain (identityRef); + mSignerList.push_back (identityRef); +} +