#include "AppleManifest.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsDecoder.h>
#include <Security/SecCmsEncoder.h>
#include <Security/SecCmsMessage.h>
#include <Security/SecCmsSignedData.h>
#include <Security/SecCmsSignerInfo.h>
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)
{
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)
{
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 ()
{
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;
for (i = 0; i < numForks; ++i)
{
size_t length;
length = file->GetForkLength (i);
AppendUInt64 (manifest, length);
}
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)
{
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<ManifestDataBlobItem*>(item));
break;
}
case kManifestFileItemType:
{
AddFileToManifest (data, static_cast<ManifestFileItem*>(item));
break;
}
case kManifestDirectoryItemType:
{
AddDirectoryToManifest (data, static_cast<ManifestDirectoryItem*>(item));
break;
}
case kManifestSymLinkItemType:
{
AddSymLinkToManifest (data, static_cast<ManifestSymLinkItem*>(item));
break;
}
case kManifestOtherType:
{
AddOtherToManifest (data, static_cast<ManifestOtherItem*>(item));
break;
}
}
}
WriteLengthAndUpdate (data, CFDataGetLength (data) - currentIndex, currentIndex);
}
static const char gManifestHeader[] = {0x2F, 0xAA, 0x05, 0xB3, 0x64, 0x0E, 0x9D, 0x27}; static const char gManifestVersion[] = {0x01, 0x00, 0x00, 0x00};
void AppleManifest::CreateManifest (CFMutableDataRef manifest, ManifestInternal& internalManifest)
{
CFDataAppendBytes (manifest, (UInt8*) gManifestHeader, sizeof (gManifestHeader));
CFDataAppendBytes (manifest, (UInt8*) gManifestVersion, sizeof (gManifestVersion));
AddManifestItemListToManifest (manifest, internalManifest.GetItemList ());
}
void AppleManifest::AddSignersToCmsMessage (SecCmsMessageRef cmsMessage, SecCmsSignedDataRef signedData)
{
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)
{
if (mSignerList.size () == 0)
{
secdebug ("manifest", "No signers found");
MacOSError::throwMe (errSecManifestNoSigners);
}
CFMutableDataRef data = CFDataCreateMutable (kCFAllocatorDefault, 0);
CreateManifest (data, manifest);
SecCmsMessageRef cmsMessage;
cmsMessage = SecCmsMessageCreate (NULL);
if (cmsMessage == NULL) {
MacOSError::throwMe (errSecManifestCMSFailure);
}
SecCmsSignedDataRef signedData;
signedData = SecCmsSignedDataCreate (cmsMessage);
if (signedData == NULL)
{
SecCmsMessageDestroy (cmsMessage);
MacOSError::throwMe (errSecManifestCMSFailure);
}
SecCmsContentInfoRef contentInfo = SecCmsMessageGetContentInfo (cmsMessage);
int result = SecCmsContentInfoSetContentSignedData (cmsMessage, contentInfo, signedData);
if (result != 0)
{
SecCmsMessageDestroy (cmsMessage);
MacOSError::throwMe (errSecManifestCMSFailure);
}
contentInfo = SecCmsSignedDataGetContentInfo (signedData);
result = SecCmsContentInfoSetContentData (cmsMessage, contentInfo, NULL, false);
if (result != 0)
{
SecCmsMessageDestroy (cmsMessage);
MacOSError::throwMe (errSecManifestCMSFailure);
}
AddSignersToCmsMessage (cmsMessage, signedData);
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);
}
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)
{
u_int16_t length = ReconstructUInt16 (finger, data);
char name[length + 1];
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)
{
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.");
file = new ManifestFileItem ();
ReconstructFileSystemHeader (finger, data, file);
u_int16_t numForks = ReconstructUInt16 (finger, data);
file->SetNumberOfForks (numForks);
u_int16_t n;
for (n = 0; n < numForks; ++n)
{
u_int64_t length = ReconstructUInt64 (finger, data);
file->SetForkLength (n, (size_t) length);
}
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;
if (memcmp (data, gManifestHeader, sizeof (gManifestHeader)) != 0)
{
MacOSError::throwMe (errSecManifestDamaged);
}
finger += sizeof (gManifestHeader);
if (memcmp (data + finger, gManifestVersion, sizeof (gManifestVersion)) != 0)
{
MacOSError::throwMe (errSecManifestDamaged);
}
finger += sizeof (gManifestVersion);
ReconstructManifestItemList (finger, data, manifest.GetItemList ());
}
SecCmsMessageRef AppleManifest::GetCmsMessageFromData (CFDataRef data)
{
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)
{
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);
}
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);
}
result = SecCmsSignedDataImportCerts (signedData, NULL, certUsageObjectSigner, true);
if (result != 0)
{
MacOSError::throwMe (result);
}
int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData);
int j;
if (numberOfSigners == 0) {
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);
}