/*
* Copyright (c) 2016 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@
*/
#if OCTAGON
#import "CKKSManifest.h"
#import "CKKSManifestLeafRecord.h"
#import "CKKS.h"
#import "CKKSItem.h"
#import "CKKSCurrentItemPointer.h"
#import "utilities/der_plist.h"
#import <securityd/SOSCloudCircleServer.h>
#import <securityd/SecItemServer.h>
#import <Security/SecureObjectSync/SOSPeerInfo.h>
#import <Security/SecureObjectSync/SOSCloudCircleInternal.h>
#import <SecurityFoundation/SFSigningOperation.h>
#import <SecurityFoundation/SFKey.h>
#import <SecurityFoundation/SFKey_Private.h>
#import <SecurityFoundation/SFDigestOperation.h>
#import <CloudKit/CloudKit.h>
NSString* const CKKSManifestZoneKey = @"zone";
NSString* const CKKSManifestSignerIDKey = @"signerID";
NSString* const CKKSManifestGenCountKey = @"gencount";
static NSString* const CKKSManifestDigestKey = @"CKKSManifestDigestKey";
static NSString* const CKKSManifestPeerManifestsKey = @"CKKSManifestPeerManifestsKey";
static NSString* const CKKSManifestCurrentItemsKey = @"CKKSManifestCurrentItemsKey";
static NSString* const CKKSManifestGenerationCountKey = @"CKKSManifestGenerationCountKey";
static NSString* const CKKSManifestSchemaVersionKey = @"CKKSManifestSchemaVersionKey";
static NSString* const CKKSManifestEC384SignatureKey = @"CKKSManifestEC384SignatureKey";
static NSString* const CKKSManifestErrorDomain = @"CKKSManifestErrorDomain";
#define NUM_MANIFEST_LEAF_RECORDS 72
#define BITS_PER_UUID_CHAR 36
static CKKSManifestInjectionPointHelper* __egoHelper = nil;
static NSMutableDictionary<NSString*, CKKSManifestInjectionPointHelper*>* __helpersDict = nil;
static BOOL __ignoreChanges = NO;
enum {
CKKSManifestErrorInvalidDigest = 1,
CKKSManifestErrorVerifyingKeyNotFound = 2,
CKKSManifestErrorManifestGenerationFailed = 3,
CKKSManifestErrorCurrentItemUUIDNotFound = 4
};
typedef NS_ENUM(NSInteger, CKKSManifestFieldType) {
CKKSManifestFieldTypeStringRaw = 0,
CKKSManifestFieldTypeStringBase64Encoded = 1,
CKKSManifestFieldTypeDataAsBase64String = 2,
CKKSManifestFieldTypeNumber = 3,
CKKSManifestFieldTypeArrayRaw = 4,
CKKSManifestFieldTypeArrayAsDERBase64String = 5,
CKKSManifestFieldTypeDictionaryAsDERBase64String = 6
};
@interface CKKSAccountInfo : NSObject {
SFECKeyPair* _signingKey;
NSDictionary* _peerVerifyingKeys;
NSString* _egoPeerID;
NSError* _setupError;
}
@property SFECKeyPair* signingKey;
@property NSDictionary* peerVerifyingKeys;
@property NSString* egoPeerID;
@property NSError* setupError;
@end
static NSDictionary* __thisBuildsSchema = nil;
static CKKSAccountInfo* s_accountInfo = nil;
@interface CKKSManifest () {
@package
NSData* _derData;
NSData* _digestValue;
NSUInteger _generationCount;
NSString* _signerID;
NSString* _zoneName;
NSArray* _leafRecordIDs;
NSArray* _peerManifestIDs;
NSMutableDictionary* _currentItemsDict;
NSDictionary* _futureData;
NSDictionary* _signaturesDict;
NSDictionary* _schema;
CKKSManifestInjectionPointHelper* _helper;
}
@property (nonatomic, readonly) NSString* zoneName;
@property (nonatomic, readonly) NSArray<NSString*>* leafRecordIDs;
@property (nonatomic, readonly) NSArray<NSString*>* peerManifestIDs;
@property (nonatomic, readonly) NSDictionary* currentItems;
@property (nonatomic, readonly) NSDictionary* futureData;
@property (nonatomic, readonly) NSDictionary* signatures;
@property (nonatomic, readonly) NSDictionary* schema;
@property (nonatomic, readwrite) NSString* signerID;
@property (nonatomic) CKKSManifestInjectionPointHelper* helper;
+ (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords;
- (void)clearDigest;
@end
@interface CKKSPendingManifest () {
NSMutableArray* _committedLeafRecordIDs;
}
@property (nonatomic, readonly) NSArray<NSString*>* committedLeafRecordIDs;
@end
@interface CKKSEgoManifest () {
NSArray* _leafRecords;
}
@property (class, readonly) CKKSManifestInjectionPointHelper* egoHelper;
@property (nonatomic, readwrite) NSDictionary* signatures;
@end
@interface CKKSManifestInjectionPointHelper ()
- (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer;
- (void)performWithSigningKey:(void (^)(SFECKeyPair* _Nullable signingKey, NSError* _Nullable error))handler;
- (void)performWithEgoPeerID:(void (^)(NSString* _Nullable egoPeerID, NSError* _Nullable error))handler;
- (void)performWithPeerVerifyingKeys:(void (^)(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error))handler;
@end
static NSData* ManifestDERData(NSString* zone, NSData* digestValue, NSArray<NSString*>* peerManifestIDs, NSDictionary<NSString*, NSString*>* currentItems, NSUInteger generationCount, NSDictionary* futureFields, NSDictionary* schema, NSError** error)
{
NSArray* sortedPeerManifestIDs = [peerManifestIDs sortedArrayUsingSelector:@selector(compare:)];
NSMutableDictionary* manifestDict = [NSMutableDictionary dictionary];
manifestDict[CKKSManifestDigestKey] = digestValue;
manifestDict[CKKSManifestPeerManifestsKey] = sortedPeerManifestIDs;
manifestDict[CKKSManifestCurrentItemsKey] = currentItems;
manifestDict[CKKSManifestGenerationCountKey] = [NSNumber numberWithUnsignedInteger:generationCount];
[futureFields enumerateKeysAndObjectsUsingBlock:^(NSString* futureKey, id futureValue, BOOL* stop) {
CKKSManifestFieldType fieldType = [schema[futureKey] integerValue];
if (fieldType == CKKSManifestFieldTypeStringRaw) {
manifestDict[futureKey] = futureValue;
}
else if (fieldType == CKKSManifestFieldTypeStringBase64Encoded) {
manifestDict[futureKey] = [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:futureValue options:0] encoding:NSUTF8StringEncoding];
}
else if (fieldType == CKKSManifestFieldTypeDataAsBase64String) {
manifestDict[futureKey] = [[NSData alloc] initWithBase64EncodedString:futureValue options:0];
}
else if (fieldType == CKKSManifestFieldTypeNumber) {
manifestDict[futureKey] = futureValue;
}
else if (fieldType == CKKSManifestFieldTypeArrayRaw) {
manifestDict[futureKey] = futureValue;
}
else if (fieldType == CKKSManifestFieldTypeArrayAsDERBase64String) {
manifestDict[futureKey] = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
}
else if (fieldType == CKKSManifestFieldTypeDictionaryAsDERBase64String) {
manifestDict[futureKey] = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
}
else {
ckkserrorwithzonename("ckksmanifest", zone, "unrecognized field type in future schema: }
}];
CFErrorRef cfError = NULL;
NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)manifestDict, &cfError);
if (cfError) {
ckkserrorwithzonename("ckksmanifest", zone, "error creating manifest der data: if (error) {
*error = (__bridge_transfer NSError*)cfError;
}
return nil;
}
return derData;
}
static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
{
NSInteger prefixIntegerValue = 0;
for (NSInteger characterIndex = 0; characterIndex * BITS_PER_UUID_CHAR < NUM_MANIFEST_LEAF_RECORDS; characterIndex++) {
prefixIntegerValue += [uuid characterAtIndex:characterIndex];
}
return prefixIntegerValue }
@implementation CKKSManifest
@synthesize zoneName = _zoneName;
@synthesize leafRecordIDs = _leafRecordIDs;
@synthesize peerManifestIDs = _peerManifestIDs;
@synthesize currentItems = _currentItemsDict;
@synthesize futureData = _futureData;
@synthesize signatures = _signaturesDict;
@synthesize signerID = _signerID;
@synthesize schema = _schema;
@synthesize helper = _helper;
+ (void)initialize
{
if (self == [CKKSManifest class]) {
__thisBuildsSchema = @{ CKKSManifestSchemaVersionKey : @(1),
SecCKRecordManifestDigestValueKey : @(CKKSManifestFieldTypeDataAsBase64String),
SecCKRecordManifestGenerationCountKey : @(CKKSManifestFieldTypeNumber),
SecCKRecordManifestLeafRecordIDsKey : @(CKKSManifestFieldTypeArrayRaw),
SecCKRecordManifestPeerManifestRecordIDsKey : @(CKKSManifestFieldTypeArrayRaw),
SecCKRecordManifestCurrentItemsKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String),
SecCKRecordManifestSignaturesKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String),
SecCKRecordManifestSignerIDKey : @(CKKSManifestFieldTypeStringRaw),
SecCKRecordManifestSchemaKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String) };
}
}
+ (void)loadDefaults
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary* systemDefaults = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle bundleWithPath:@"/System/Library/Frameworks/Security.framework"] pathForResource:@"CKKSLogging" ofType:@"plist"]];
bool shouldSync = !![[systemDefaults valueForKey:@"SyncManifests"] boolValue];
bool shouldEnforce = !![[systemDefaults valueForKey:@"EnforceManifests"] boolValue];
NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
bool userDefaultsShouldSync = !![defaults boolForKey:@"SyncManifests"];
bool userDefaultsShouldEnforce = !![defaults boolForKey:@"EnforceManifests"];
shouldSync |= userDefaultsShouldSync;
shouldEnforce |= userDefaultsShouldEnforce;
if(shouldSync) {
SecCKKSEnableSyncManifests();
}
if(shouldEnforce) {
SecCKKSEnableEnforceManifests();
}
});
}
+ (bool)shouldSyncManifests
{
[self loadDefaults];
return SecCKKSSyncManifests();
}
+ (bool)shouldEnforceManifests
{
[self loadDefaults];
return SecCKKSEnforceManifests();
}
+ (void)performWithAccountInfo:(void (^)(void))action
{
CKKSAccountInfo* accountInfo = [[CKKSAccountInfo alloc] init];
[[CKKSEgoManifest egoHelper] performWithSigningKey:^(SFECKeyPair* signingKey, NSError* error) {
accountInfo.signingKey = signingKey;
if(error) {
secerror("ckksmanifest: cannot get signing key from account: if(accountInfo.setupError == nil) {
accountInfo.setupError = error;
}
}
}];
[[CKKSEgoManifest egoHelper] performWithEgoPeerID:^(NSString* egoPeerID, NSError* error) {
accountInfo.egoPeerID = egoPeerID;
if(error) {
secerror("ckksmanifest: cannot get ego peer ID from account: if(accountInfo.setupError == nil) {
accountInfo.setupError = error;
}
}
}];
[[CKKSEgoManifest egoHelper] performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* peerKeys, NSError* error) {
accountInfo.peerVerifyingKeys = peerKeys;
if(error) {
secerror("ckksmanifest: cannot get peer keys from account: if(accountInfo.setupError == nil) {
accountInfo.setupError = error;
}
}
}];
s_accountInfo = accountInfo;
action();
s_accountInfo = nil;
}
+ (nullable instancetype)tryFromDatabaseWhere:(NSDictionary*)whereDict error:(NSError* __autoreleasing *)error
{
CKKSManifest* manifest = [super tryFromDatabaseWhere:whereDict error:error];
manifest.helper = __helpersDict[manifest.signerID];
return manifest;
}
+ (nullable instancetype)manifestForZone:(NSString*)zone peerID:(NSString*)peerID error:(NSError**)error
{
NSDictionary* databaseWhereClause = @{ @"ckzone" : zone, @"signerID" : peerID };
return [self tryFromDatabaseWhere:databaseWhereClause error:error];
}
+ (nullable instancetype)manifestForRecordName:(NSString*)recordName error:(NSError**)error
{
return [self tryFromDatabaseWhere:[self whereClauseForRecordName:recordName] error:error];
}
+ (nullable instancetype)latestTrustedManifestForZone:(NSString*)zone error:(NSError**)error
{
NSDictionary* databaseWhereClause = @{ @"ckzone" : zone };
NSArray* manifests = [[self allWhere:databaseWhereClause error:error] sortedArrayUsingComparator:^NSComparisonResult(CKKSManifest* _Nonnull firstManifest, CKKSManifest* _Nonnull secondManifest) {
NSInteger firstGenerationCount = firstManifest.generationCount;
NSInteger secondGenerationCount = secondManifest.generationCount;
if (firstGenerationCount > secondGenerationCount) {
return NSOrderedDescending;
}
else if (firstGenerationCount < secondGenerationCount) {
return NSOrderedAscending;
}
else {
return NSOrderedSame;
}
}];
__block CKKSManifest* result = nil;
[manifests enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(CKKSManifest* _Nonnull manifest, NSUInteger index, BOOL* _Nonnull stop) {
if ([manifest validateWithError:nil]) {
result = manifest;
*stop = YES;
}
}];
// TODO: add error for when we didn't find anything
return result;
}
+ (SFEC_X962SigningOperation*)signatureOperation
{
SFECKeySpecifier* keySpecifier = [[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384];
return [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:keySpecifier digestOperation:[[SFSHA384DigestOperation alloc] init]];
}
+ (NSData*)digestForData:(NSData*)data
{
return [SFSHA384DigestOperation digest:data];
}
+ (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords
{
NSMutableData* concatenatedLeafNodeDigestData = [[NSMutableData alloc] init];
for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
[concatenatedLeafNodeDigestData appendData:leafRecord.digestValue];
}
return [self digestForData:concatenatedLeafNodeDigestData];
}
+ (instancetype)manifestForPendingManifest:(CKKSPendingManifest*)pendingManifest
{
return [[self alloc] initWithDigestValue:pendingManifest.digestValue zone:pendingManifest.zoneName generationCount:pendingManifest.generationCount leafRecordIDs:pendingManifest.committedLeafRecordIDs peerManifestIDs:pendingManifest.peerManifestIDs currentItems:pendingManifest.currentItems futureData:pendingManifest.futureData signatures:pendingManifest.signatures signerID:pendingManifest.signerID schema:pendingManifest.schema encodedRecord:pendingManifest.encodedCKRecord];
}
+ (instancetype)fromDatabaseRow:(NSDictionary*)row
{
NSString* digestBase64String = row[@"digest"];
NSData* digest = [digestBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0] : nil;
NSString* zone = row[@"ckzone"];
NSUInteger generationCount = [row[@"gencount"] integerValue];
NSString* signerID = row[@"signerID"];
NSString* encodedRecordBase64String = row[@"ckrecord"];
NSData* encodedRecord = [encodedRecordBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:encodedRecordBase64String options:0] : nil;
NSData* leafRecordIDData = [[NSData alloc] initWithBase64EncodedString:row[@"leafIDs"] options:0];
NSArray* leafRecordIDs = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)leafRecordIDData, 0, NULL, NULL);
if (![leafRecordIDs isKindOfClass:[NSArray class]]) {
leafRecordIDs = [NSArray array];
}
NSData* peerManifestIDData = [[NSData alloc] initWithBase64EncodedString:row[@"peerManifests"] options:0];
NSArray* peerManifestIDs = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)peerManifestIDData, 0, NULL, NULL);
if (![peerManifestIDs isKindOfClass:[NSArray class]]) {
peerManifestIDs = [NSArray array];
}
NSData* currentItemsData = [[NSData alloc] initWithBase64EncodedString:row[@"currentItems"] options:0];
NSDictionary* currentItemsDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)currentItemsData, 0, NULL, NULL);
if (![currentItemsDict isKindOfClass:[NSDictionary class]]) {
currentItemsDict = [NSDictionary dictionary];
}
NSData* futureData = [[NSData alloc] initWithBase64EncodedString:row[@"futureData"] options:0];
NSDictionary* futureDataDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)futureData, 0, NULL, NULL);
if (![futureDataDict isKindOfClass:[NSDictionary class]]) {
futureDataDict = [NSDictionary dictionary];
}
NSData* signaturesData = [[NSData alloc] initWithBase64EncodedString:row[@"signatures"] options:0];
NSDictionary* signatures = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)signaturesData, 0, NULL, NULL);
if (![signatures isKindOfClass:[NSDictionary class]]) {
signatures = [NSDictionary dictionary];
}
NSData* schemaData = [[NSData alloc] initWithBase64EncodedString:row[@"schema"] options:0];
NSDictionary* schemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)schemaData, 0, NULL, NULL);
if (![schemaDict isKindOfClass:[NSDictionary class]]) {
schemaDict = __thisBuildsSchema;
}
return [[self alloc] initWithDigestValue:digest zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItemsDict futureData:futureDataDict signatures:signatures signerID:signerID schema:schemaDict encodedRecord:encodedRecord];
}
+ (NSArray<NSString*>*)sqlColumns
{
return @[@"ckzone", @"gencount", @"digest", @"signatures", @"signerID", @"leafIDs", @"peerManifests", @"currentItems", @"futureData", @"schema", @"ckrecord"];
}
+ (NSString*)sqlTable
{
return @"ckmanifest";
}
+ (NSUInteger)greatestKnownGenerationCount
{
__block NSUInteger result = 0;
[self queryMaxValueForField:@"gencount" inTable:self.sqlTable where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
result = [row[@"gencount"] integerValue];
}];
[CKKSPendingManifest queryMaxValueForField:@"gencount" inTable:[CKKSPendingManifest sqlTable] where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
result = MAX(result, (NSUInteger)[row[@"gencount"] integerValue]);
}];
return result;
}
- (instancetype)initWithDigestValue:(NSData*)digestValue zone:(NSString*)zone generationCount:(NSUInteger)generationCount leafRecordIDs:(NSArray<NSString*>*)leafRecordIDs peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems futureData:(NSDictionary*)futureData signatures:(NSDictionary*)signatures signerID:(NSString*)signerID schema:(NSDictionary*)schema helper:(CKKSManifestInjectionPointHelper*)helper
{
if (self = [super init]) {
_digestValue = digestValue;
_zoneName = zone;
_generationCount = generationCount;
_leafRecordIDs = [leafRecordIDs copy];
_currentItemsDict = currentItems ? [currentItems mutableCopy] : [NSMutableDictionary dictionary];
_futureData = futureData ? [futureData copy] : @{};
_signaturesDict = [signatures copy];
_signerID = signerID;
_schema = schema ? schema.copy : __thisBuildsSchema;
if ([peerManifestIDs.firstObject isEqualToString:signerID]) {
_peerManifestIDs = peerManifestIDs;
}
else {
NSMutableArray* tempPeerManifests = [[NSMutableArray alloc] initWithObjects:signerID, nil];
if (peerManifestIDs) {
[tempPeerManifests addObjectsFromArray:peerManifestIDs];
}
_peerManifestIDs = tempPeerManifests;
}
_helper = helper ?: [self defaultHelperForSignerID:signerID];
if (!_helper) {
_helper = [[CKKSManifestInjectionPointHelper alloc] init];
}
}
return self;
}
- (instancetype)initWithDigestValue:(NSData*)digestValue zone:(NSString*)zone generationCount:(NSUInteger)generationCount leafRecordIDs:(NSArray<NSString*>*)leafRecordIDs peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems futureData:(NSDictionary*)futureData signatures:(NSDictionary*)signatures signerID:(NSString*)signerID schema:(NSDictionary*)schema
{
return [self initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:futureData signatures:signatures signerID:signerID schema:schema helper:nil];
}
- (instancetype)initWithDigestValue:(NSData*)digestValue zone:(NSString*)zone generationCount:(NSUInteger)generationCount leafRecordIDs:(NSArray<NSString*>*)leafRecordIDs peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems futureData:(NSDictionary*)futureData signatures:(NSDictionary*)signatures signerID:(NSString*)signerID schema:(NSDictionary*)schema encodedRecord:(NSData*)encodedRecord
{
if (self = [self initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:futureData signatures:signatures signerID:signerID schema:schema]) {
self.encodedCKRecord = encodedRecord;
}
return self;
}
- (instancetype)initWithCKRecord:(CKRecord*)record
{
NSError* error = nil;
NSString* signatureBase64String = record[SecCKRecordManifestSignaturesKey];
if (!signatureBase64String) {
ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have signatures attached: return nil;
}
NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:signatureBase64String options:0];
NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
if (error) {
ckkserror("ckksmanifest", record.recordID.zoneID, "failed to initialize CKKSManifest from CKRecord because we could not form a signature dict from the record: return nil;
}
NSDictionary* schemaDict = nil;
NSString* schemaBase64String = record[SecCKRecordManifestSchemaKey];
if (schemaBase64String) {
NSData* schemaData = [[NSData alloc] initWithBase64EncodedString:schemaBase64String options:0];
schemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)schemaData, 0, NULL, NULL);
}
if (![schemaDict isKindOfClass:[NSDictionary class]]) {
schemaDict = __thisBuildsSchema;
}
NSString* digestBase64String = record[SecCKRecordManifestDigestValueKey];
if (!digestBase64String) {
ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have a digest attached: return nil;
}
NSData* digestData = [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0];
if (self = [self initWithDigestValue:digestData
zone:record.recordID.zoneID.zoneName
generationCount:[record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue]
leafRecordIDs:record[SecCKRecordManifestLeafRecordIDsKey]
peerManifestIDs:record[SecCKRecordManifestPeerManifestRecordIDsKey]
currentItems:record[SecCKRecordManifestCurrentItemsKey]
futureData:[self futureDataDictFromRecord:record withSchema:schemaDict]
signatures:signaturesDict
signerID:record[SecCKRecordManifestSignerIDKey]
schema:schemaDict]) {
self.storedCKRecord = record;
}
return self;
}
- (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
{
return __helpersDict[signerID];
}
- (NSDictionary*)signatureDictFromDERData:(NSData*)derData error:(NSError**)error
{
CFErrorRef localError = NULL;
NSDictionary* signaturesDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)derData, 0, NULL, &localError);
if (![signaturesDict isKindOfClass:[NSDictionary class]]) {
ckkserror("ckksmanifest", self, "failed to decode signatures der dict with error: if (error) {
*error = (__bridge_transfer NSError*)localError;
}
}
return signaturesDict;
}
- (NSData*)derDataFromSignatureDict:(NSDictionary*)signatureDict error:(NSError**)error
{
CFErrorRef localError = NULL;
NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)signatureDict, &localError);
if (!derData) {
ckkserror("ckksmanifest", self, "failed to encode signatures dict to der with error: if (error) {
*error = (__bridge_transfer NSError*)localError;
}
}
return derData;
}
- (NSArray*)peerManifestsFromDERData:(NSData*)derData error:(NSError**)error
{
CFErrorRef localError = NULL;
NSArray* peerManifests = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)derData, 0, NULL, &localError);
if (![peerManifests isKindOfClass:[NSArray class]]) {
ckkserror("ckksmanifest", self, "failed to decode peer manifests der array with error: if (error) {
*error = (__bridge_transfer NSError*)localError;
}
}
return peerManifests;
}
- (NSData*)derDataFromPeerManifests:(NSArray*)peerManifests error:(NSError**)error
{
CFErrorRef localError = NULL;
NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)peerManifests, &localError);
if (!derData) {
ckkserror("ckksmanifest", self, "failed to encode peer manifests to der with error: if (error) {
*error = (__bridge_transfer NSError*)localError;
}
}
return derData;
}
- (NSDictionary*)futureDataDictFromRecord:(CKRecord*)record withSchema:(NSDictionary*)cloudSchema
{
NSMutableDictionary* futureData = [NSMutableDictionary dictionary];
[cloudSchema enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSData* obj, BOOL* stop) {
if (![__thisBuildsSchema.allKeys containsObject:key]) {
futureData[key] = record[key];
}
}];
return futureData;
}
- (BOOL)updateWithRecord:(CKRecord*)record error:(NSError**)error
{
if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
return YES; // don't set off any alarms here - just pretend we did it
}
NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestSignaturesKey] options:0];
NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:error];
if (!signaturesDict) {
return NO;
}
NSData* cloudSchemaData = record[SecCKRecordManifestSchemaKey];
NSDictionary* cloudSchemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)cloudSchemaData, 0, NULL, NULL);
if (![cloudSchemaDict isKindOfClass:[NSDictionary class]]) {
cloudSchemaDict = __thisBuildsSchema;
}
_digestValue = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestDigestValueKey] options:0];
_generationCount = [record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue];
_leafRecordIDs = record[SecCKRecordManifestLeafRecordIDsKey];
_peerManifestIDs = record[SecCKRecordManifestPeerManifestRecordIDsKey];
_currentItemsDict = [record[SecCKRecordManifestCurrentItemsKey] mutableCopy];
if (!_currentItemsDict) {
_currentItemsDict = [NSMutableDictionary dictionary];
}
_futureData = [[self futureDataDictFromRecord:record withSchema:cloudSchemaDict] copy];
_signaturesDict = signaturesDict;
_signerID = record[SecCKRecordManifestSignerIDKey];
_schema = cloudSchemaDict;
self.storedCKRecord = record;
_derData = nil;
return YES;
}
- (NSDictionary<NSString*, NSString*>*)sqlValues
{
void (^addValueSafelyToDictionaryAndLogIfNil)(NSMutableDictionary*, NSString*, id) = ^(NSMutableDictionary* dictionary, NSString* key, id value) {
if (!value) {
value = [NSNull null];
secerror("CKKSManifest: saving manifest to database but }
dictionary[key] = value;
};
NSMutableDictionary* sqlValues = [[NSMutableDictionary alloc] init];
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckzone", _zoneName);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"gencount", [NSNumber numberWithUnsignedInteger:_generationCount]);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"digest", self.digestValue);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"signatures", [[self derDataFromSignatureDict:self.signatures error:nil] base64EncodedStringWithOptions:0]);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"signerID", _signerID);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"leafIDs", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_leafRecordIDs, NULL) base64EncodedStringWithOptions:0]);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"peerManifests", [[self derDataFromPeerManifests:_peerManifestIDs error:nil] base64EncodedStringWithOptions:0]);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"currentItems", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_currentItemsDict, NULL) base64EncodedStringWithOptions:0]);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"futureData", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_futureData, NULL) base64EncodedStringWithOptions:0]);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"schema", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_schema, NULL) base64EncodedStringWithOptions:0]);
addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckrecord", [self.encodedCKRecord base64EncodedStringWithOptions:0]);
return sqlValues;
}
- (NSDictionary<NSString*, NSString*>*)whereClauseToFindSelf
{
return @{ @"ckzone" : CKKSNilToNSNull(_zoneName),
@"gencount" : [NSNumber numberWithUnsignedInteger:_generationCount],
@"signerID" : CKKSNilToNSNull(_signerID) };
}
- (NSString*)CKRecordName
{
return [NSString stringWithFormat:@"Manifest:-:}
+ (NSDictionary*)whereClauseForRecordName:(NSString*)recordName
{
NSArray* components = [recordName componentsSeparatedByString:@":-:"];
if (components.count < 4) {
secerror("CKKSManifest: could not parse components from record name: }
return @{ @"ckzone" : components[1],
@"signerID" : components[2],
@"gencount" : components[3] };
}
- (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID
{
if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
@throw [NSException exceptionWithName:@"WrongCKRecordTypeException" reason:[NSString stringWithFormat:@"CKRecorType ( }
NSData* signatureDERData = [self derDataFromSignatureDict:self.signatures error:nil];
if (!signatureDERData) {
return record;
}
record[SecCKRecordManifestDigestValueKey] = [self.digestValue base64EncodedStringWithOptions:0];
record[SecCKRecordManifestGenerationCountKey] = [NSNumber numberWithUnsignedInteger:_generationCount];
record[SecCKRecordManifestLeafRecordIDsKey] = _leafRecordIDs;
record[SecCKRecordManifestPeerManifestRecordIDsKey] = _peerManifestIDs;
record[SecCKRecordManifestCurrentItemsKey] = [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)_currentItemsDict, NULL) base64EncodedStringWithOptions:0];
record[SecCKRecordManifestSignaturesKey] = [signatureDERData base64EncodedStringWithOptions:0];
record[SecCKRecordManifestSignerIDKey] = _signerID;
record[SecCKRecordManifestSchemaKey] = [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)_schema, NULL) base64EncodedStringWithOptions:0];
[_futureData enumerateKeysAndObjectsUsingBlock:^(NSString* key, id futureField, BOOL* stop) {
record[key] = futureField;
}];
return record;
}
- (bool)matchesCKRecord:(CKRecord*)record
{
if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
return false;
}
NSError* error = nil;
NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestSignaturesKey] options:0];
NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
if (!signaturesDict || error) {
return NO;
}
NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestDigestValueKey] options:0];
return [digestData isEqual:self.digestValue] &&
[record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue] == _generationCount &&
[record[SecCKRecordManifestPeerManifestRecordIDsKey] isEqual:_peerManifestIDs] &&
[signaturesDict isEqual:self.signatures] &&
[record[SecCKRecordManifestSignerIDKey] isEqual:_signerID];
}
- (void)setFromCKRecord:(CKRecord*)record
{
NSError* error = nil;
if (![self updateWithRecord:record error:&error]) {
ckkserror("ckksmanifest", self, "failed to update manifest from CKRecord with error: }
}
- (NSData*)derData
{
if (!_derData) {
NSError* error = nil;
_derData = ManifestDERData(_zoneName, self.digestValue, _peerManifestIDs, _currentItemsDict, _generationCount, _futureData, _schema, &error);
if (error) {
ckkserror("ckksmanifest", self, "error encoding manifest into DER: _derData = nil;
}
}
return _derData;
}
- (BOOL)validateWithError:(NSError**)error
{
__block BOOL verified = false;
NSData* manifestDerData = self.derData;
if (manifestDerData) {
__block NSError* localError = nil;
[_helper performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error) {
if(error) {
ckkserror("ckksmanifest", self, "Error fetching peer verifying keys: }
SFECPublicKey* verifyingKey = peerKeys[self->_signerID];
if (verifyingKey) {
SFEC_X962SigningOperation* signingOperation = [self.class signatureOperation];
SFSignedData* signedData = [[SFSignedData alloc] initWithData:manifestDerData signature:self.signatures[CKKSManifestEC384SignatureKey]];
verified = [signingOperation verify:signedData withKey:verifyingKey error:&localError] == NULL ? false : true;
}
else {
localError = [NSError errorWithDomain:CKKSManifestErrorDomain
code:CKKSManifestErrorVerifyingKeyNotFound
userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"could not find manifest public key for peer NSUnderlyingErrorKey: CKKSNilToNSNull(error)}];
}
}];
if (error) {
*error = localError;
}
}
return verified;
}
- (BOOL)validateItem:(CKKSItem*)item withError:(NSError**)error
{
NSString* uuid = item.uuid;
CKKSManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:uuid];
NSData* expectedItemDigest = leafRecord.recordDigestDict[uuid];
if ([[self.class digestForData:item.encitem] isEqual:expectedItemDigest]) {
return YES;
}
else if (error) {
*error = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorInvalidDigest userInfo:@{NSLocalizedDescriptionKey : @"could not validate item because the digest is invalid"}];
}
return NO;
}
- (BOOL)validateCurrentItem:(CKKSCurrentItemPointer*)currentItem withError:(NSError**)error
{
BOOL result = [currentItem.currentItemUUID isEqualToString:[_currentItemsDict valueForKey:currentItem.identifier]];
if (!result && error) {
*error = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorCurrentItemUUIDNotFound userInfo:@{NSLocalizedDescriptionKey :@"could not validate current item because the UUID does not match the manifest"}];
}
return result;
}
- (BOOL)itemUUIDExistsInManifest:(NSString*)uuid
{
CKKSManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:uuid];
return leafRecord.recordDigestDict[uuid] != nil;
}
- (BOOL)contentsAreEqualToManifest:(CKKSManifest*)otherManifest
{
return [_digestValue isEqual:otherManifest.digestValue];
}
- (CKKSManifestLeafRecord*)leafRecordForID:(NSString*)leafRecordID
{
NSError* error = nil;
CKKSManifestLeafRecord* leafRecord = [CKKSManifestLeafRecord leafRecordForID:leafRecordID error:&error];
if (error || !leafRecord) {
ckkserror("ckksmanifest", self, "failed to lookup manifest leaf record with id: }
return leafRecord;
}
- (CKKSManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
{
NSInteger bucketIndex = LeafBucketIndexForUUID(uuid);
NSString* leafRecordID = _leafRecordIDs[bucketIndex];
return [self leafRecordForID:leafRecordID];
}
- (void)clearDigest
{
_digestValue = nil;
_derData = nil;
_signaturesDict = nil;
}
- (NSData*)digestValue
{
if (!_digestValue) {
_digestValue = [self.class digestValueForLeafRecords:self.leafRecords];
}
return _digestValue;
}
- (NSArray<CKKSManifestLeafRecord*>*)leafRecords
{
NSMutableArray* leafRecords = [[NSMutableArray alloc] initWithCapacity:_leafRecordIDs.count];
for (NSString* recordID in _leafRecordIDs) {
CKKSManifestLeafRecord* leafRecord = [self leafRecordForID:recordID];
if(leafRecord) {
[leafRecords addObject:leafRecord];
} else {
ckkserror("ckksmanifest", self, "failed to fetch leaf record from CKManifest for // TODO: auto bug capture?
}
}
return leafRecords;
}
- (NSString*)ckRecordType
{
return SecCKRecordManifestType;
}
- (void)nilAllIvars
{
_derData = nil;
_digestValue = nil;
_signerID = nil;
_zoneName = nil;
_leafRecordIDs = nil;
_peerManifestIDs = nil;
_currentItemsDict = nil;
_futureData = nil;
_signaturesDict = nil;
_schema = nil;
}
@end
@implementation CKKSPendingManifest
@synthesize committedLeafRecordIDs = _committedLeafRecordIDs;
+ (NSString*)sqlTable
{
return @"pending_manifest";
}
- (BOOL)isReadyToCommit
{
for (NSString* leafRecordID in self.leafRecordIDs) {
if ([CKKSManifestLeafRecord recordExistsForID:leafRecordID] || [CKKSManifestPendingLeafRecord recordExistsForID:leafRecordID]) {
continue;
}
else {
ckksinfo("ckksmanifest", self, "Not ready to commit manifest, yet - missing leaf record ID: return NO;
}
}
return YES;
}
- (CKKSManifest*)commitToDatabaseWithError:(NSError**)error
{
NSError* localError = nil;
_committedLeafRecordIDs = [[NSMutableArray alloc] init];
for (NSString* leafRecordID in self.leafRecordIDs) {
CKKSManifestPendingLeafRecord* pendingLeaf = [CKKSManifestPendingLeafRecord leafRecordForID:leafRecordID error:&localError];
if (pendingLeaf) {
CKKSManifestLeafRecord* committedLeaf = [pendingLeaf commitToDatabaseWithError:error];
if (committedLeaf) {
[_committedLeafRecordIDs addObject:committedLeaf.CKRecordName];
}
else {
return nil;
}
}
else {
CKKSManifestLeafRecord* existingLeaf = [CKKSManifestLeafRecord leafRecordForID:leafRecordID error:&localError];
if (existingLeaf) {
[_committedLeafRecordIDs addObject:existingLeaf.CKRecordName];
continue;
}
}
if (localError) {
if (error) {
*error = localError;
}
return nil;
}
}
CKKSManifest* manifest = [CKKSManifest manifestForPendingManifest:self];
if ([manifest saveToDatabase:error]) {
[self deleteFromDatabase:error];
return manifest;
}
else {
return nil;
}
}
@end
@implementation CKKSEgoManifest
+ (CKKSManifestInjectionPointHelper*)egoHelper
{
return __egoHelper ?: [[CKKSManifestInjectionPointHelper alloc] init];
}
+ (NSArray*)leafRecordsForItems:(NSArray*)items manifestName:(NSString*)manifestName zone:(NSString*)zone
{
NSMutableArray* leafRecords = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < NUM_MANIFEST_LEAF_RECORDS; i++) {
[leafRecords addObject:[CKKSEgoManifestLeafRecord newLeafRecordInZone:zone]];
}
for (CKKSItem* item in items) {
CKKSEgoManifestLeafRecord* leafRecord = leafRecords[LeafBucketIndexForUUID(item.uuid)];
[leafRecord addOrUpdateRecordUUID:item.uuid withEncryptedItemData:item.encitem];
}
return leafRecords;
}
+ (nullable CKKSEgoManifest*)tryCurrentEgoManifestForZone:(NSString*)zone
{
__block CKKSEgoManifest* manifest = nil;
[self.egoHelper performWithEgoPeerID:^(NSString * _Nullable egoPeerID, NSError * _Nullable error) {
if(error) {
ckkserrorwithzonename("ckksmanifest", zone, "Error getting peer ID: return;
}
if (!egoPeerID) {
ckkserrorwithzonename("ckksmanifest", zone, "can't get ego peer ID right now - the device probably hasn't been unlocked yet");
return;
}
NSDictionary* whereDict = @{ @"ckzone" : zone, @"signerID" : egoPeerID };
[self queryMaxValueForField:@"gencount" inTable:self.sqlTable where:whereDict columns:self.sqlColumns processRow:^(NSDictionary* row) {
manifest = [self fromDatabaseRow:row];
}];
}];
return manifest;
}
+ (nullable instancetype)newFakeManifestForZone:(NSString*)zone withItemRecords:(NSArray<CKRecord*>*)itemRecords currentItems:(NSDictionary*)currentItems signerID:(NSString*)signerID keyPair:(SFECKeyPair*)keyPair error:(NSError**)error
{
CKKSManifestInjectionPointHelper* helper = [[CKKSManifestInjectionPointHelper alloc] initWithPeerID:signerID keyPair:keyPair isEgoPeer:NO];
CKKSEgoManifest* manifest = [self newManifestForZone:zone withItems:@[] peerManifestIDs:@[] currentItems:currentItems error:error helper:helper];
manifest.signerID = signerID;
manifest.helper = helper;
[manifest updateWithNewOrChangedRecords:itemRecords deletedRecordIDs:@[]];
return manifest;
}
+ (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error
{
return [self newManifestForZone:zone withItems:items peerManifestIDs:peerManifestIDs currentItems:currentItems error:error helper:self.egoHelper];
}
+ (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error helper:(CKKSManifestInjectionPointHelper*)helper
{
__block NSError* localError = nil;
NSArray* leafRecords = [self leafRecordsForItems:items manifestName:nil zone:zone];
NSData* digestValue = [self digestValueForLeafRecords:leafRecords];
NSInteger generationCount = [self greatestKnownGenerationCount] + 1;
__block CKKSEgoManifest* result = nil;
[helper performWithEgoPeerID:^(NSString* _Nullable egoPeerID, NSError* _Nullable err) {
if (err) {
localError = err;
}
else if (egoPeerID) {
result = [[self alloc] initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecords:leafRecords peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:[NSDictionary dictionary] signatures:nil signerID:egoPeerID schema:__thisBuildsSchema];
}
else {
localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest because egoPeerID is nil"}];
}
}];
if (!result && !localError) {
localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest"}];
}
if (error) {
*error = localError;
}
return result;
}
+ (instancetype)fromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
if(!manifest) {
return nil;
}
// Try to load leaf records
if(![manifest loadLeafRecords:manifest.zoneID.zoneName error:error]) {
return nil;
}
return manifest;
}
+ (instancetype)tryFromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
if(!manifest) {
return nil;
}
// Try to load leaf records
// Failing to load leaf records on a manifest that exists is an error, even in tryFromDatabaseWhere.
if(![manifest loadLeafRecords:manifest.zoneID.zoneName error:error]) {
return nil;
}
return manifest;
}
- (bool)loadLeafRecords:(NSString*)ckzone error:(NSError * __autoreleasing *)error {
NSMutableArray* leafRecords = [[NSMutableArray alloc] initWithCapacity:self.leafRecordIDs.count];
for (NSString* leafID in self.leafRecordIDs) {
CKKSEgoManifestLeafRecord* leafRecord = [CKKSEgoManifestLeafRecord fromDatabaseWhere:@{@"uuid" : [CKKSManifestLeafRecord leafUUIDForRecordID:leafID], @"ckzone" : ckzone} error:error];
if (leafRecord) {
[leafRecords addObject:leafRecord];
} else {
secerror("ckksmanifest: error loading leaf record from database: return false;
}
}
self->_leafRecords = leafRecords;
return true;
}
+ (NSDictionary*)generateSignaturesWithHelper:(CKKSManifestInjectionPointHelper*)helper derData:(NSData*)manifestDerData error:(NSError**)error
{
__block NSData* signature = nil;
__block NSError* localError = nil;
[helper performWithSigningKey:^(SFECKeyPair* _Nullable signingKey, NSError* _Nullable err) {
if (err) {
localError = err;
return;
}
if (signingKey) {
SFEC_X962SigningOperation* signingOperation = [self signatureOperation];
SFSignedData* signedData = [signingOperation sign:manifestDerData withKey:signingKey error:&localError];
signature = signedData.signature;
}
}];
if(error) {
*error = localError;
}
return signature ? @{CKKSManifestEC384SignatureKey : signature} : nil;
}
- (instancetype)initWithDigestValue:(NSData*)digestValue zone:(NSString*)zone generationCount:(NSUInteger)generationCount leafRecords:(NSArray<CKKSManifestLeafRecord*>*)leafRecords peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems futureData:(NSDictionary*)futureData signatures:(NSDictionary*)signatures signerID:(NSString*)signerID schema:(NSDictionary*)schema
{
NSMutableArray* leafRecordIDs = [[NSMutableArray alloc] initWithCapacity:leafRecords.count];
for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
[leafRecordIDs addObject:leafRecord.CKRecordName];
}
if (self = [super initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:futureData signatures:signatures signerID:signerID schema:schema helper:[CKKSEgoManifest egoHelper]]) {
_leafRecords = leafRecords.copy;
}
return self;
}
- (void)updateWithNewOrChangedRecords:(NSArray<CKRecord*>*)newOrChangedRecords deletedRecordIDs:(NSArray<CKRecordID*>*)deletedRecordIDs
{
if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
return;
}
for (CKRecordID* deletedRecord in deletedRecordIDs) {
NSString* deletedUUID = deletedRecord.recordName;
CKKSEgoManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:deletedUUID];
[leafRecord deleteItemWithUUID:deletedUUID];
}
for (CKRecord* record in newOrChangedRecords) {
CKKSEgoManifestLeafRecord* leafRecord = (CKKSEgoManifestLeafRecord*)[self leafRecordForItemUUID:record.recordID.recordName];
[leafRecord addOrUpdateRecord:record];
}
[self clearDigest];
_generationCount = [self.class greatestKnownGenerationCount] + 1;
}
- (void)setCurrentItemUUID:(NSString*)newCurrentItemUUID forIdentifier:(NSString*)currentPointerIdentifier
{
_currentItemsDict[currentPointerIdentifier] = newCurrentItemUUID;
[self clearDigest];
_generationCount = [self.class greatestKnownGenerationCount] + 1;
}
- (CKKSEgoManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
{
NSUInteger leafBucket = LeafBucketIndexForUUID(uuid);
if(_leafRecords.count > leafBucket) {
return _leafRecords[leafBucket];
} else {
return nil;
}
}
- (NSArray<CKKSManifestLeafRecord*>*)leafRecords
{
return _leafRecords;
}
- (NSArray<CKRecord*>*)allCKRecordsWithZoneID:(CKRecordZoneID*)zoneID
{
NSMutableArray* records = [[NSMutableArray alloc] initWithCapacity:_leafRecords.count + 1];
[records addObject:[self CKRecordWithZoneID:zoneID]];
for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
[records addObject:[leafRecord CKRecordWithZoneID:zoneID]];
}
return records;
}
- (bool)saveToDatabase:(NSError**)error
{
bool result = [super saveToDatabase:error];
if (result) {
for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
result &= [leafRecord saveToDatabase:error];
}
}
return result;
}
- (NSDictionary*)signatures
{
if (!_signaturesDict) {
_signaturesDict = [self.class generateSignaturesWithHelper:self.helper derData:self.derData error:nil];
}
return _signaturesDict;
}
- (void)setSignatures:(NSDictionary*)signatures
{
_signaturesDict = signatures;
}
- (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
{
CKKSManifestInjectionPointHelper* helper = __helpersDict[signerID];
return helper ?: __egoHelper;
}
@end
@implementation CKKSManifestInjectionPointHelper {
NSString* _peerID;
SFECKeyPair* _keyPair;
}
+ (void)registerHelper:(CKKSManifestInjectionPointHelper*)helper forPeer:(NSString*)peerID
{
if (!__helpersDict) {
__helpersDict = [[NSMutableDictionary alloc] init];
}
__helpersDict[peerID] = helper;
}
+ (void)registerEgoPeerID:(NSString*)egoPeerID keyPair:(SFECKeyPair*)keyPair
{
__egoHelper = [[self alloc] initWithPeerID:egoPeerID keyPair:keyPair isEgoPeer:YES];
}
+ (BOOL)ignoreChanges
{
return __ignoreChanges;
}
+ (void)setIgnoreChanges:(BOOL)ignoreChanges
{
__ignoreChanges = ignoreChanges ? YES : NO;
}
- (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer
{
if (self = [super init]) {
_peerID = peerID;
_keyPair = keyPair;
if (isEgoPeer) {
__egoHelper = self;
}
else {
[self.class registerHelper:self forPeer:peerID];
}
}
return self;
}
- (NSString*)description
{
return [NSString stringWithFormat:@"}
- (void)performWithSigningKey:(void (^)(SFECKeyPair* _Nullable signingKey, NSError* _Nullable error))handler
{
if (s_accountInfo) {
if(s_accountInfo.setupError) {
handler(nil, s_accountInfo.setupError);
} else {
handler(s_accountInfo.signingKey, nil);
}
}
else if (_keyPair) {
handler(_keyPair, nil);
}
else {
SOSCCPerformWithOctagonSigningKey(^(SecKeyRef signingSecKey, CFErrorRef err) {
SFECKeyPair* key = nil;
if (!err && signingSecKey) {
key = [[SFECKeyPair alloc] initWithSecKey:signingSecKey];
}
handler(key, (__bridge NSError*)err);
});
}
}
- (void)performWithEgoPeerID:(void (^)(NSString* _Nullable egoPeerID, NSError* _Nullable error))handler
{
if (s_accountInfo) {
if(s_accountInfo.setupError) {
handler(nil, s_accountInfo.setupError);
} else {
handler(s_accountInfo.egoPeerID, nil);
}
}
else if (_peerID) {
handler(_peerID, nil);
}
else {
NSError* error = nil;
SOSPeerInfoRef egoPeerInfo = SOSCCCopyMyPeerInfo(NULL);
NSString* egoPeerID = egoPeerInfo ? (__bridge NSString*)SOSPeerInfoGetPeerID(egoPeerInfo) : nil;
handler(egoPeerID, error);
CFReleaseNull(egoPeerInfo);
}
}
- (void)performWithPeerVerifyingKeys:(void (^)(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error))handler
{
if (s_accountInfo) {
if(s_accountInfo.setupError) {
handler(nil, s_accountInfo.setupError);
} else {
handler(s_accountInfo.peerVerifyingKeys, nil);
}
}
else if (__egoHelper || __helpersDict) {
NSMutableDictionary* verifyingKeys = [[NSMutableDictionary alloc] init];
[__helpersDict enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull peer, CKKSManifestInjectionPointHelper* _Nonnull helper, BOOL* _Nonnull stop) {
verifyingKeys[peer] = helper.keyPair.publicKey;
}];
if (__egoHelper.keyPair) {
verifyingKeys[__egoHelper.peerID] = __egoHelper.keyPair.publicKey;
}
handler(verifyingKeys, nil);
}
else {
CFErrorRef error = NULL;
NSMutableDictionary* peerKeys = [NSMutableDictionary dictionary];
CFArrayRef peerInfos = SOSCCCopyValidPeerPeerInfo(&error);
if (!peerInfos || error) {
handler(nil, (__bridge NSError*)error);
CFReleaseNull(peerInfos);
CFReleaseNull(error);
return;
}
CFArrayForEach(peerInfos, ^(const void* peerInfoPtr) {
SOSPeerInfoRef peerInfo = (SOSPeerInfoRef)peerInfoPtr;
CFErrorRef blockError = NULL;
SecKeyRef secPublicKey = SOSPeerInfoCopyOctagonSigningPublicKey(peerInfo, &blockError);
if (!secPublicKey || blockError) {
CFReleaseNull(secPublicKey);
CFReleaseNull(blockError);
return;
}
SFECPublicKey* publicKey = [[SFECPublicKey alloc] initWithSecKey:secPublicKey];
CFReleaseNull(secPublicKey);
NSString* peerID = (__bridge NSString*)SOSPeerInfoGetPeerID(peerInfo);
peerKeys[peerID] = publicKey;
});
handler(peerKeys, nil);
CFReleaseNull(peerInfos);
}
}
- (SFECKeyPair*)keyPair
{
return _keyPair;
}
- (NSString*)peerID
{
return _peerID;
}
@end
@implementation CKKSAccountInfo
@synthesize signingKey = _signingKey;
@synthesize peerVerifyingKeys = _peerVerifyingKeys;
@synthesize egoPeerID = _egoPeerID;
@end
#endif