2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 #import "CKKSManifest.h"
27 #import "CKKSManifestLeafRecord.h"
30 #import "CKKSCurrentItemPointer.h"
31 #import "utilities/der_plist.h"
32 #import <securityd/SOSCloudCircleServer.h>
33 #import <securityd/SecItemServer.h>
34 #import <Security/SecureObjectSync/SOSPeerInfo.h>
35 #import <Security/SecureObjectSync/SOSCloudCircleInternal.h>
36 #import <SecurityFoundation/SFSigningOperation.h>
37 #import <SecurityFoundation/SFKey.h>
38 #import <SecurityFoundation/SFKey_Private.h>
39 #import <SecurityFoundation/SFDigestOperation.h>
40 #import <CloudKit/CloudKit.h>
42 NSString* const CKKSManifestZoneKey = @"zone";
43 NSString* const CKKSManifestSignerIDKey = @"signerID";
44 NSString* const CKKSManifestGenCountKey = @"gencount";
46 static NSString* const CKKSManifestDigestKey = @"CKKSManifestDigestKey";
47 static NSString* const CKKSManifestPeerManifestsKey = @"CKKSManifestPeerManifestsKey";
48 static NSString* const CKKSManifestCurrentItemsKey = @"CKKSManifestCurrentItemsKey";
49 static NSString* const CKKSManifestGenerationCountKey = @"CKKSManifestGenerationCountKey";
50 static NSString* const CKKSManifestSchemaVersionKey = @"CKKSManifestSchemaVersionKey";
52 static NSString* const CKKSManifestEC384SignatureKey = @"CKKSManifestEC384SignatureKey";
54 static NSString* const CKKSManifestErrorDomain = @"CKKSManifestErrorDomain";
56 #define NUM_MANIFEST_LEAF_RECORDS 72
57 #define BITS_PER_UUID_CHAR 36
59 static CKKSManifestInjectionPointHelper* __egoHelper = nil;
60 static NSMutableDictionary<NSString*, CKKSManifestInjectionPointHelper*>* __helpersDict = nil;
61 static BOOL __ignoreChanges = NO;
64 CKKSManifestErrorInvalidDigest = 1,
65 CKKSManifestErrorVerifyingKeyNotFound = 2,
66 CKKSManifestErrorManifestGenerationFailed = 3,
67 CKKSManifestErrorCurrentItemUUIDNotFound = 4
70 typedef NS_ENUM(NSInteger, CKKSManifestFieldType) {
71 CKKSManifestFieldTypeStringRaw = 0,
72 CKKSManifestFieldTypeStringBase64Encoded = 1,
73 CKKSManifestFieldTypeDataAsBase64String = 2,
74 CKKSManifestFieldTypeNumber = 3,
75 CKKSManifestFieldTypeArrayRaw = 4,
76 CKKSManifestFieldTypeArrayAsDERBase64String = 5,
77 CKKSManifestFieldTypeDictionaryAsDERBase64String = 6
80 @interface CKKSAccountInfo : NSObject {
81 SFECKeyPair* _signingKey;
82 NSDictionary* _peerVerifyingKeys;
87 @property SFECKeyPair* signingKey;
88 @property NSDictionary* peerVerifyingKeys;
89 @property NSString* egoPeerID;
90 @property NSError* setupError;
93 static NSDictionary* __thisBuildsSchema = nil;
94 static CKKSAccountInfo* s_accountInfo = nil;
96 @interface CKKSManifest () {
100 NSUInteger _generationCount;
103 NSArray* _leafRecordIDs;
104 NSArray* _peerManifestIDs;
105 NSMutableDictionary* _currentItemsDict;
106 NSDictionary* _futureData;
107 NSDictionary* _signaturesDict;
108 NSDictionary* _schema;
109 CKKSManifestInjectionPointHelper* _helper;
112 @property (nonatomic, readonly) NSString* zoneName;
113 @property (nonatomic, readonly) NSArray<NSString*>* leafRecordIDs;
114 @property (nonatomic, readonly) NSArray<NSString*>* peerManifestIDs;
115 @property (nonatomic, readonly) NSDictionary* currentItems;
116 @property (nonatomic, readonly) NSDictionary* futureData;
117 @property (nonatomic, readonly) NSDictionary* signatures;
118 @property (nonatomic, readonly) NSDictionary* schema;
120 @property (nonatomic, readwrite) NSString* signerID;
121 @property (nonatomic) CKKSManifestInjectionPointHelper* helper;
123 + (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords;
129 @interface CKKSPendingManifest () {
130 NSMutableArray* _committedLeafRecordIDs;
133 @property (nonatomic, readonly) NSArray<NSString*>* committedLeafRecordIDs;
137 @interface CKKSEgoManifest () {
138 NSArray* _leafRecords;
141 @property (class, readonly) CKKSManifestInjectionPointHelper* egoHelper;
143 @property (nonatomic, readwrite) NSDictionary* signatures;
147 @interface CKKSManifestInjectionPointHelper ()
149 - (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer;
151 - (void)performWithSigningKey:(void (^)(SFECKeyPair* _Nullable signingKey, NSError* _Nullable error))handler;
152 - (void)performWithEgoPeerID:(void (^)(NSString* _Nullable egoPeerID, NSError* _Nullable error))handler;
153 - (void)performWithPeerVerifyingKeys:(void (^)(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error))handler;
157 static NSData* ManifestDERData(NSString* zone, NSData* digestValue, NSArray<NSString*>* peerManifestIDs, NSDictionary<NSString*, NSString*>* currentItems, NSUInteger generationCount, NSDictionary* futureFields, NSDictionary* schema, NSError** error)
159 NSArray* sortedPeerManifestIDs = [peerManifestIDs sortedArrayUsingSelector:@selector(compare:)];
161 NSMutableDictionary* manifestDict = [NSMutableDictionary dictionary];
162 manifestDict[CKKSManifestDigestKey] = digestValue;
163 manifestDict[CKKSManifestPeerManifestsKey] = sortedPeerManifestIDs;
164 manifestDict[CKKSManifestCurrentItemsKey] = currentItems;
165 manifestDict[CKKSManifestGenerationCountKey] = [NSNumber numberWithUnsignedInteger:generationCount];
167 [futureFields enumerateKeysAndObjectsUsingBlock:^(NSString* futureKey, id futureValue, BOOL* stop) {
168 CKKSManifestFieldType fieldType = [schema[futureKey] integerValue];
169 if (fieldType == CKKSManifestFieldTypeStringRaw) {
170 manifestDict[futureKey] = futureValue;
172 else if (fieldType == CKKSManifestFieldTypeStringBase64Encoded) {
173 manifestDict[futureKey] = [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:futureValue options:0] encoding:NSUTF8StringEncoding];
175 else if (fieldType == CKKSManifestFieldTypeDataAsBase64String) {
176 manifestDict[futureKey] = [[NSData alloc] initWithBase64EncodedString:futureValue options:0];
178 else if (fieldType == CKKSManifestFieldTypeNumber) {
179 manifestDict[futureKey] = futureValue;
181 else if (fieldType == CKKSManifestFieldTypeArrayRaw) {
182 manifestDict[futureKey] = futureValue;
184 else if (fieldType == CKKSManifestFieldTypeArrayAsDERBase64String) {
185 manifestDict[futureKey] = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
187 else if (fieldType == CKKSManifestFieldTypeDictionaryAsDERBase64String) {
188 manifestDict[futureKey] = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
191 ckkserrorwithzonename("ckksmanifest", zone, "unrecognized field type in future schema: %ld", (long)fieldType);
195 CFErrorRef cfError = NULL;
196 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)manifestDict, &cfError);
198 ckkserrorwithzonename("ckksmanifest", zone, "error creating manifest der data: %@", cfError);
200 *error = (__bridge_transfer NSError*)cfError;
208 static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
210 NSInteger prefixIntegerValue = 0;
211 for (NSInteger characterIndex = 0; characterIndex * BITS_PER_UUID_CHAR < NUM_MANIFEST_LEAF_RECORDS; characterIndex++) {
212 prefixIntegerValue += [uuid characterAtIndex:characterIndex];
215 return prefixIntegerValue % NUM_MANIFEST_LEAF_RECORDS;
218 @implementation CKKSManifest
220 @synthesize zoneName = _zoneName;
221 @synthesize leafRecordIDs = _leafRecordIDs;
222 @synthesize peerManifestIDs = _peerManifestIDs;
223 @synthesize currentItems = _currentItemsDict;
224 @synthesize futureData = _futureData;
225 @synthesize signatures = _signaturesDict;
226 @synthesize signerID = _signerID;
227 @synthesize schema = _schema;
228 @synthesize helper = _helper;
232 if (self == [CKKSManifest class]) {
233 __thisBuildsSchema = @{ CKKSManifestSchemaVersionKey : @(1),
234 SecCKRecordManifestDigestValueKey : @(CKKSManifestFieldTypeDataAsBase64String),
235 SecCKRecordManifestGenerationCountKey : @(CKKSManifestFieldTypeNumber),
236 SecCKRecordManifestLeafRecordIDsKey : @(CKKSManifestFieldTypeArrayRaw),
237 SecCKRecordManifestPeerManifestRecordIDsKey : @(CKKSManifestFieldTypeArrayRaw),
238 SecCKRecordManifestCurrentItemsKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String),
239 SecCKRecordManifestSignaturesKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String),
240 SecCKRecordManifestSignerIDKey : @(CKKSManifestFieldTypeStringRaw),
241 SecCKRecordManifestSchemaKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String) };
247 static dispatch_once_t onceToken;
248 dispatch_once(&onceToken, ^{
249 NSDictionary* systemDefaults = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle bundleWithPath:@"/System/Library/Frameworks/Security.framework"] pathForResource:@"CKKSLogging" ofType:@"plist"]];
250 bool shouldSync = !![[systemDefaults valueForKey:@"SyncManifests"] boolValue];
251 bool shouldEnforce = !![[systemDefaults valueForKey:@"EnforceManifests"] boolValue];
253 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
254 bool userDefaultsShouldSync = !![defaults boolForKey:@"SyncManifests"];
255 bool userDefaultsShouldEnforce = !![defaults boolForKey:@"EnforceManifests"];
256 shouldSync |= userDefaultsShouldSync;
257 shouldEnforce |= userDefaultsShouldEnforce;
260 SecCKKSEnableSyncManifests();
263 SecCKKSEnableEnforceManifests();
268 + (bool)shouldSyncManifests
271 return SecCKKSSyncManifests();
274 + (bool)shouldEnforceManifests
277 return SecCKKSEnforceManifests();
280 + (void)performWithAccountInfo:(void (^)(void))action
282 CKKSAccountInfo* accountInfo = [[CKKSAccountInfo alloc] init];
284 [[CKKSEgoManifest egoHelper] performWithSigningKey:^(SFECKeyPair* signingKey, NSError* error) {
285 accountInfo.signingKey = signingKey;
287 secerror("ckksmanifest: cannot get signing key from account: %@", error);
288 if(accountInfo.setupError == nil) {
289 accountInfo.setupError = error;
294 [[CKKSEgoManifest egoHelper] performWithEgoPeerID:^(NSString* egoPeerID, NSError* error) {
295 accountInfo.egoPeerID = egoPeerID;
298 secerror("ckksmanifest: cannot get ego peer ID from account: %@", error);
299 if(accountInfo.setupError == nil) {
300 accountInfo.setupError = error;
305 [[CKKSEgoManifest egoHelper] performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* peerKeys, NSError* error) {
306 accountInfo.peerVerifyingKeys = peerKeys;
308 secerror("ckksmanifest: cannot get peer keys from account: %@", error);
309 if(accountInfo.setupError == nil) {
310 accountInfo.setupError = error;
315 s_accountInfo = accountInfo;
322 + (nullable instancetype)tryFromDatabaseWhere:(NSDictionary*)whereDict error:(NSError* __autoreleasing *)error
324 CKKSManifest* manifest = [super tryFromDatabaseWhere:whereDict error:error];
325 manifest.helper = __helpersDict[manifest.signerID];
329 + (nullable instancetype)manifestForZone:(NSString*)zone peerID:(NSString*)peerID error:(NSError**)error
331 NSDictionary* databaseWhereClause = @{ @"ckzone" : zone, @"signerID" : peerID };
332 return [self tryFromDatabaseWhere:databaseWhereClause error:error];
335 + (nullable instancetype)manifestForRecordName:(NSString*)recordName error:(NSError**)error
337 return [self tryFromDatabaseWhere:[self whereClauseForRecordName:recordName] error:error];
340 + (nullable instancetype)latestTrustedManifestForZone:(NSString*)zone error:(NSError**)error
342 NSDictionary* databaseWhereClause = @{ @"ckzone" : zone };
343 NSArray* manifests = [[self allWhere:databaseWhereClause error:error] sortedArrayUsingComparator:^NSComparisonResult(CKKSManifest* _Nonnull firstManifest, CKKSManifest* _Nonnull secondManifest) {
344 NSInteger firstGenerationCount = firstManifest.generationCount;
345 NSInteger secondGenerationCount = secondManifest.generationCount;
347 if (firstGenerationCount > secondGenerationCount) {
348 return NSOrderedDescending;
350 else if (firstGenerationCount < secondGenerationCount) {
351 return NSOrderedAscending;
354 return NSOrderedSame;
358 __block CKKSManifest* result = nil;
359 [manifests enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(CKKSManifest* _Nonnull manifest, NSUInteger index, BOOL* _Nonnull stop) {
360 if ([manifest validateWithError:nil]) {
366 // TODO: add error for when we didn't find anything
370 + (SFEC_X962SigningOperation*)signatureOperation
372 SFECKeySpecifier* keySpecifier = [[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384];
373 return [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:keySpecifier digestOperation:[[SFSHA384DigestOperation alloc] init]];
376 + (NSData*)digestForData:(NSData*)data
378 return [SFSHA384DigestOperation digest:data];
381 + (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords
383 NSMutableData* concatenatedLeafNodeDigestData = [[NSMutableData alloc] init];
384 for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
385 [concatenatedLeafNodeDigestData appendData:leafRecord.digestValue];
388 return [self digestForData:concatenatedLeafNodeDigestData];
391 + (instancetype)manifestForPendingManifest:(CKKSPendingManifest*)pendingManifest
393 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];
396 + (instancetype)fromDatabaseRow:(NSDictionary*)row
398 NSString* digestBase64String = row[@"digest"];
399 NSData* digest = [digestBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0] : nil;
401 NSString* zone = row[@"ckzone"];
402 NSUInteger generationCount = [row[@"gencount"] integerValue];
403 NSString* signerID = row[@"signerID"];
405 NSString* encodedRecordBase64String = row[@"ckrecord"];
406 NSData* encodedRecord = [encodedRecordBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:encodedRecordBase64String options:0] : nil;
408 NSData* leafRecordIDData = [[NSData alloc] initWithBase64EncodedString:row[@"leafIDs"] options:0];
409 NSArray* leafRecordIDs = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)leafRecordIDData, 0, NULL, NULL);
410 if (![leafRecordIDs isKindOfClass:[NSArray class]]) {
411 leafRecordIDs = [NSArray array];
414 NSData* peerManifestIDData = [[NSData alloc] initWithBase64EncodedString:row[@"peerManifests"] options:0];
415 NSArray* peerManifestIDs = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)peerManifestIDData, 0, NULL, NULL);
416 if (![peerManifestIDs isKindOfClass:[NSArray class]]) {
417 peerManifestIDs = [NSArray array];
420 NSData* currentItemsData = [[NSData alloc] initWithBase64EncodedString:row[@"currentItems"] options:0];
421 NSDictionary* currentItemsDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)currentItemsData, 0, NULL, NULL);
422 if (![currentItemsDict isKindOfClass:[NSDictionary class]]) {
423 currentItemsDict = [NSDictionary dictionary];
426 NSData* futureData = [[NSData alloc] initWithBase64EncodedString:row[@"futureData"] options:0];
427 NSDictionary* futureDataDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)futureData, 0, NULL, NULL);
428 if (![futureDataDict isKindOfClass:[NSDictionary class]]) {
429 futureDataDict = [NSDictionary dictionary];
432 NSData* signaturesData = [[NSData alloc] initWithBase64EncodedString:row[@"signatures"] options:0];
433 NSDictionary* signatures = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)signaturesData, 0, NULL, NULL);
434 if (![signatures isKindOfClass:[NSDictionary class]]) {
435 signatures = [NSDictionary dictionary];
438 NSData* schemaData = [[NSData alloc] initWithBase64EncodedString:row[@"schema"] options:0];
439 NSDictionary* schemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)schemaData, 0, NULL, NULL);
440 if (![schemaDict isKindOfClass:[NSDictionary class]]) {
441 schemaDict = __thisBuildsSchema;
444 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];
447 + (NSArray<NSString*>*)sqlColumns
449 return @[@"ckzone", @"gencount", @"digest", @"signatures", @"signerID", @"leafIDs", @"peerManifests", @"currentItems", @"futureData", @"schema", @"ckrecord"];
452 + (NSString*)sqlTable
454 return @"ckmanifest";
457 + (NSUInteger)greatestKnownGenerationCount
459 __block NSUInteger result = 0;
460 [self queryMaxValueForField:@"gencount" inTable:self.sqlTable where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
461 result = [row[@"gencount"] integerValue];
464 [CKKSPendingManifest queryMaxValueForField:@"gencount" inTable:[CKKSPendingManifest sqlTable] where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
465 result = MAX(result, (NSUInteger)[row[@"gencount"] integerValue]);
471 - (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
473 if (self = [super init]) {
474 _digestValue = digestValue;
476 _generationCount = generationCount;
477 _leafRecordIDs = [leafRecordIDs copy];
478 _currentItemsDict = currentItems ? [currentItems mutableCopy] : [NSMutableDictionary dictionary];
479 _futureData = futureData ? [futureData copy] : @{};
480 _signaturesDict = [signatures copy];
481 _signerID = signerID;
482 _schema = schema ? schema.copy : __thisBuildsSchema;
484 if ([peerManifestIDs.firstObject isEqualToString:signerID]) {
485 _peerManifestIDs = peerManifestIDs;
488 NSMutableArray* tempPeerManifests = [[NSMutableArray alloc] initWithObjects:signerID, nil];
489 if (peerManifestIDs) {
490 [tempPeerManifests addObjectsFromArray:peerManifestIDs];
492 _peerManifestIDs = tempPeerManifests;
495 _helper = helper ?: [self defaultHelperForSignerID:signerID];
497 _helper = [[CKKSManifestInjectionPointHelper alloc] init];
504 - (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
506 return [self initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:futureData signatures:signatures signerID:signerID schema:schema helper:nil];
509 - (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
511 if (self = [self initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:futureData signatures:signatures signerID:signerID schema:schema]) {
512 self.encodedCKRecord = encodedRecord;
518 - (instancetype)initWithCKRecord:(CKRecord*)record
520 NSError* error = nil;
521 NSString* signatureBase64String = record[SecCKRecordManifestSignaturesKey];
522 if (!signatureBase64String) {
523 ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have signatures attached: %@", record);
526 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:signatureBase64String options:0];
527 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
529 ckkserror("ckksmanifest", record.recordID.zoneID, "failed to initialize CKKSManifest from CKRecord because we could not form a signature dict from the record: %@", record);
533 NSDictionary* schemaDict = nil;
534 NSString* schemaBase64String = record[SecCKRecordManifestSchemaKey];
535 if (schemaBase64String) {
536 NSData* schemaData = [[NSData alloc] initWithBase64EncodedString:schemaBase64String options:0];
537 schemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)schemaData, 0, NULL, NULL);
539 if (![schemaDict isKindOfClass:[NSDictionary class]]) {
540 schemaDict = __thisBuildsSchema;
543 NSString* digestBase64String = record[SecCKRecordManifestDigestValueKey];
544 if (!digestBase64String) {
545 ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have a digest attached: %@", record);
548 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0];
550 if (self = [self initWithDigestValue:digestData
551 zone:record.recordID.zoneID.zoneName
552 generationCount:[record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue]
553 leafRecordIDs:record[SecCKRecordManifestLeafRecordIDsKey]
554 peerManifestIDs:record[SecCKRecordManifestPeerManifestRecordIDsKey]
555 currentItems:record[SecCKRecordManifestCurrentItemsKey]
556 futureData:[self futureDataDictFromRecord:record withSchema:schemaDict]
557 signatures:signaturesDict
558 signerID:record[SecCKRecordManifestSignerIDKey]
559 schema:schemaDict]) {
560 self.storedCKRecord = record;
566 - (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
568 return __helpersDict[signerID];
571 - (NSDictionary*)signatureDictFromDERData:(NSData*)derData error:(NSError**)error
573 CFErrorRef localError = NULL;
574 NSDictionary* signaturesDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)derData, 0, NULL, &localError);
575 if (![signaturesDict isKindOfClass:[NSDictionary class]]) {
576 ckkserror("ckksmanifest", self, "failed to decode signatures der dict with error: %@", localError);
578 *error = (__bridge_transfer NSError*)localError;
582 return signaturesDict;
585 - (NSData*)derDataFromSignatureDict:(NSDictionary*)signatureDict error:(NSError**)error
587 CFErrorRef localError = NULL;
588 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)signatureDict, &localError);
590 ckkserror("ckksmanifest", self, "failed to encode signatures dict to der with error: %@", localError);
592 *error = (__bridge_transfer NSError*)localError;
599 - (NSArray*)peerManifestsFromDERData:(NSData*)derData error:(NSError**)error
601 CFErrorRef localError = NULL;
602 NSArray* peerManifests = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)derData, 0, NULL, &localError);
603 if (![peerManifests isKindOfClass:[NSArray class]]) {
604 ckkserror("ckksmanifest", self, "failed to decode peer manifests der array with error: %@", localError);
606 *error = (__bridge_transfer NSError*)localError;
610 return peerManifests;
613 - (NSData*)derDataFromPeerManifests:(NSArray*)peerManifests error:(NSError**)error
615 CFErrorRef localError = NULL;
616 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)peerManifests, &localError);
618 ckkserror("ckksmanifest", self, "failed to encode peer manifests to der with error: %@", localError);
620 *error = (__bridge_transfer NSError*)localError;
627 - (NSDictionary*)futureDataDictFromRecord:(CKRecord*)record withSchema:(NSDictionary*)cloudSchema
629 NSMutableDictionary* futureData = [NSMutableDictionary dictionary];
630 [cloudSchema enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSData* obj, BOOL* stop) {
631 if (![__thisBuildsSchema.allKeys containsObject:key]) {
632 futureData[key] = record[key];
639 - (BOOL)updateWithRecord:(CKRecord*)record error:(NSError**)error
641 if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
642 return YES; // don't set off any alarms here - just pretend we did it
645 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestSignaturesKey] options:0];
646 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:error];
647 if (!signaturesDict) {
651 NSData* cloudSchemaData = record[SecCKRecordManifestSchemaKey];
652 NSDictionary* cloudSchemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)cloudSchemaData, 0, NULL, NULL);
653 if (![cloudSchemaDict isKindOfClass:[NSDictionary class]]) {
654 cloudSchemaDict = __thisBuildsSchema;
657 _digestValue = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestDigestValueKey] options:0];
658 _generationCount = [record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue];
659 _leafRecordIDs = record[SecCKRecordManifestLeafRecordIDsKey];
660 _peerManifestIDs = record[SecCKRecordManifestPeerManifestRecordIDsKey];
661 _currentItemsDict = [record[SecCKRecordManifestCurrentItemsKey] mutableCopy];
662 if (!_currentItemsDict) {
663 _currentItemsDict = [NSMutableDictionary dictionary];
665 _futureData = [[self futureDataDictFromRecord:record withSchema:cloudSchemaDict] copy];
666 _signaturesDict = signaturesDict;
667 _signerID = record[SecCKRecordManifestSignerIDKey];
668 _schema = cloudSchemaDict;
669 self.storedCKRecord = record;
675 - (NSDictionary<NSString*, NSString*>*)sqlValues
677 void (^addValueSafelyToDictionaryAndLogIfNil)(NSMutableDictionary*, NSString*, id) = ^(NSMutableDictionary* dictionary, NSString* key, id value) {
679 value = [NSNull null];
680 secerror("CKKSManifest: saving manifest to database but %@ is nil", key);
683 dictionary[key] = value;
686 NSMutableDictionary* sqlValues = [[NSMutableDictionary alloc] init];
687 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckzone", _zoneName);
688 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"gencount", [NSNumber numberWithUnsignedInteger:_generationCount]);
689 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"digest", self.digestValue);
690 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"signatures", [[self derDataFromSignatureDict:self.signatures error:nil] base64EncodedStringWithOptions:0]);
691 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"signerID", _signerID);
692 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"leafIDs", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_leafRecordIDs, NULL) base64EncodedStringWithOptions:0]);
693 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"peerManifests", [[self derDataFromPeerManifests:_peerManifestIDs error:nil] base64EncodedStringWithOptions:0]);
694 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"currentItems", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_currentItemsDict, NULL) base64EncodedStringWithOptions:0]);
695 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"futureData", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_futureData, NULL) base64EncodedStringWithOptions:0]);
696 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"schema", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_schema, NULL) base64EncodedStringWithOptions:0]);
697 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckrecord", [self.encodedCKRecord base64EncodedStringWithOptions:0]);
702 - (NSDictionary<NSString*, id>*)whereClauseToFindSelf
704 return @{ @"ckzone" : CKKSNilToNSNull(_zoneName),
705 @"gencount" : [NSNumber numberWithUnsignedInteger:_generationCount],
706 @"signerID" : CKKSNilToNSNull(_signerID) };
709 - (NSString*)CKRecordName
711 return [NSString stringWithFormat:@"Manifest:-:%@:-:%@:-:%lu", _zoneName, _signerID, (unsigned long)_generationCount];
714 + (NSDictionary*)whereClauseForRecordName:(NSString*)recordName
716 NSArray* components = [recordName componentsSeparatedByString:@":-:"];
717 if (components.count < 4) {
718 secerror("CKKSManifest: could not parse components from record name: %@", recordName);
721 return @{ @"ckzone" : components[1],
722 @"signerID" : components[2],
723 @"gencount" : components[3] };
726 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID
728 if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
729 @throw [NSException exceptionWithName:@"WrongCKRecordTypeException" reason:[NSString stringWithFormat:@"CKRecorType (%@) was not %@", record.recordType, SecCKRecordManifestType] userInfo:nil];
732 NSData* signatureDERData = [self derDataFromSignatureDict:self.signatures error:nil];
733 if (!signatureDERData) {
737 record[SecCKRecordManifestDigestValueKey] = [self.digestValue base64EncodedStringWithOptions:0];
738 record[SecCKRecordManifestGenerationCountKey] = [NSNumber numberWithUnsignedInteger:_generationCount];
739 record[SecCKRecordManifestLeafRecordIDsKey] = _leafRecordIDs;
740 record[SecCKRecordManifestPeerManifestRecordIDsKey] = _peerManifestIDs;
741 record[SecCKRecordManifestCurrentItemsKey] = [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)_currentItemsDict, NULL) base64EncodedStringWithOptions:0];
742 record[SecCKRecordManifestSignaturesKey] = [signatureDERData base64EncodedStringWithOptions:0];
743 record[SecCKRecordManifestSignerIDKey] = _signerID;
744 record[SecCKRecordManifestSchemaKey] = [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)_schema, NULL) base64EncodedStringWithOptions:0];
746 [_futureData enumerateKeysAndObjectsUsingBlock:^(NSString* key, id futureField, BOOL* stop) {
747 record[key] = futureField;
753 - (bool)matchesCKRecord:(CKRecord*)record
755 if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
759 NSError* error = nil;
760 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestSignaturesKey] options:0];
761 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
762 if (!signaturesDict || error) {
766 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestDigestValueKey] options:0];
767 return [digestData isEqual:self.digestValue] &&
768 [record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue] == _generationCount &&
769 [record[SecCKRecordManifestPeerManifestRecordIDsKey] isEqual:_peerManifestIDs] &&
770 [signaturesDict isEqual:self.signatures] &&
771 [record[SecCKRecordManifestSignerIDKey] isEqual:_signerID];
774 - (void)setFromCKRecord:(CKRecord*)record
776 NSError* error = nil;
777 if (![self updateWithRecord:record error:&error]) {
778 ckkserror("ckksmanifest", self, "failed to update manifest from CKRecord with error: %@", error);
785 NSError* error = nil;
786 _derData = ManifestDERData(_zoneName, self.digestValue, _peerManifestIDs, _currentItemsDict, _generationCount, _futureData, _schema, &error);
788 ckkserror("ckksmanifest", self, "error encoding manifest into DER: %@", error);
796 - (BOOL)validateWithError:(NSError**)error
798 __block BOOL verified = false;
799 NSData* manifestDerData = self.derData;
800 if (manifestDerData) {
801 __block NSError* localError = nil;
803 [_helper performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error) {
805 ckkserror("ckksmanifest", self, "Error fetching peer verifying keys: %@", error);
807 SFECPublicKey* verifyingKey = peerKeys[self->_signerID];
809 SFEC_X962SigningOperation* signingOperation = [self.class signatureOperation];
810 SFSignedData* signedData = [[SFSignedData alloc] initWithData:manifestDerData signature:self.signatures[CKKSManifestEC384SignatureKey]];
811 verified = [signingOperation verify:signedData withKey:verifyingKey error:&localError] == NULL ? false : true;
815 localError = [NSError errorWithDomain:CKKSManifestErrorDomain
816 code:CKKSManifestErrorVerifyingKeyNotFound
817 userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"could not find manifest public key for peer %@", self->_signerID],
818 NSUnderlyingErrorKey: CKKSNilToNSNull(error)}];
830 - (BOOL)validateItem:(CKKSItem*)item withError:(NSError**)error
832 NSString* uuid = item.uuid;
833 CKKSManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:uuid];
834 NSData* expectedItemDigest = leafRecord.recordDigestDict[uuid];
835 if ([[self.class digestForData:item.encitem] isEqual:expectedItemDigest]) {
839 *error = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorInvalidDigest userInfo:@{NSLocalizedDescriptionKey : @"could not validate item because the digest is invalid"}];
845 - (BOOL)validateCurrentItem:(CKKSCurrentItemPointer*)currentItem withError:(NSError**)error
847 BOOL result = [currentItem.currentItemUUID isEqualToString:[_currentItemsDict valueForKey:currentItem.identifier]];
848 if (!result && error) {
849 *error = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorCurrentItemUUIDNotFound userInfo:@{NSLocalizedDescriptionKey :@"could not validate current item because the UUID does not match the manifest"}];
855 - (BOOL)itemUUIDExistsInManifest:(NSString*)uuid
857 CKKSManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:uuid];
858 return leafRecord.recordDigestDict[uuid] != nil;
861 - (BOOL)contentsAreEqualToManifest:(CKKSManifest*)otherManifest
863 return [_digestValue isEqual:otherManifest.digestValue];
866 - (CKKSManifestLeafRecord*)leafRecordForID:(NSString*)leafRecordID
868 NSError* error = nil;
869 CKKSManifestLeafRecord* leafRecord = [CKKSManifestLeafRecord leafRecordForID:leafRecordID error:&error];
870 if (error || !leafRecord) {
871 ckkserror("ckksmanifest", self, "failed to lookup manifest leaf record with id: %@ error: %@", leafRecordID, error);
877 - (CKKSManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
879 NSInteger bucketIndex = LeafBucketIndexForUUID(uuid);
880 NSString* leafRecordID = _leafRecordIDs[bucketIndex];
881 return [self leafRecordForID:leafRecordID];
888 _signaturesDict = nil;
891 - (NSData*)digestValue
894 _digestValue = [self.class digestValueForLeafRecords:self.leafRecords];
900 - (NSArray<CKKSManifestLeafRecord*>*)leafRecords
902 NSMutableArray* leafRecords = [[NSMutableArray alloc] initWithCapacity:_leafRecordIDs.count];
903 for (NSString* recordID in _leafRecordIDs) {
904 CKKSManifestLeafRecord* leafRecord = [self leafRecordForID:recordID];
906 [leafRecords addObject:leafRecord];
908 ckkserror("ckksmanifest", self, "failed to fetch leaf record from CKManifest for %@", recordID);
909 // TODO: auto bug capture?
916 - (NSString*)ckRecordType
918 return SecCKRecordManifestType;
927 _leafRecordIDs = nil;
928 _peerManifestIDs = nil;
929 _currentItemsDict = nil;
931 _signaturesDict = nil;
937 @implementation CKKSPendingManifest
939 @synthesize committedLeafRecordIDs = _committedLeafRecordIDs;
941 + (NSString*)sqlTable
943 return @"pending_manifest";
946 - (BOOL)isReadyToCommit
948 for (NSString* leafRecordID in self.leafRecordIDs) {
949 if ([CKKSManifestLeafRecord recordExistsForID:leafRecordID] || [CKKSManifestPendingLeafRecord recordExistsForID:leafRecordID]) {
953 ckksinfo("ckksmanifest", self, "Not ready to commit manifest, yet - missing leaf record ID: %@", leafRecordID);
961 - (CKKSManifest*)commitToDatabaseWithError:(NSError**)error
963 NSError* localError = nil;
965 _committedLeafRecordIDs = [[NSMutableArray alloc] init];
967 for (NSString* leafRecordID in self.leafRecordIDs) {
968 CKKSManifestPendingLeafRecord* pendingLeaf = [CKKSManifestPendingLeafRecord leafRecordForID:leafRecordID error:&localError];
970 CKKSManifestLeafRecord* committedLeaf = [pendingLeaf commitToDatabaseWithError:error];
972 [_committedLeafRecordIDs addObject:committedLeaf.CKRecordName];
979 CKKSManifestLeafRecord* existingLeaf = [CKKSManifestLeafRecord leafRecordForID:leafRecordID error:&localError];
981 [_committedLeafRecordIDs addObject:existingLeaf.CKRecordName];
994 CKKSManifest* manifest = [CKKSManifest manifestForPendingManifest:self];
995 if ([manifest saveToDatabase:error]) {
996 [self deleteFromDatabase:error];
1006 @implementation CKKSEgoManifest
1008 + (CKKSManifestInjectionPointHelper*)egoHelper
1010 return __egoHelper ?: [[CKKSManifestInjectionPointHelper alloc] init];
1013 + (NSArray*)leafRecordsForItems:(NSArray*)items manifestName:(NSString*)manifestName zone:(NSString*)zone
1015 NSMutableArray* leafRecords = [[NSMutableArray alloc] init];
1016 for (NSInteger i = 0; i < NUM_MANIFEST_LEAF_RECORDS; i++) {
1017 [leafRecords addObject:[CKKSEgoManifestLeafRecord newLeafRecordInZone:zone]];
1020 for (CKKSItem* item in items) {
1021 CKKSEgoManifestLeafRecord* leafRecord = leafRecords[LeafBucketIndexForUUID(item.uuid)];
1022 [leafRecord addOrUpdateRecordUUID:item.uuid withEncryptedItemData:item.encitem];
1028 + (nullable CKKSEgoManifest*)tryCurrentEgoManifestForZone:(NSString*)zone
1030 __block CKKSEgoManifest* manifest = nil;
1031 [self.egoHelper performWithEgoPeerID:^(NSString * _Nullable egoPeerID, NSError * _Nullable error) {
1033 ckkserrorwithzonename("ckksmanifest", zone, "Error getting peer ID: %@", error);
1037 ckkserrorwithzonename("ckksmanifest", zone, "can't get ego peer ID right now - the device probably hasn't been unlocked yet");
1041 NSDictionary* whereDict = @{ @"ckzone" : zone, @"signerID" : egoPeerID };
1042 [self queryMaxValueForField:@"gencount" inTable:self.sqlTable where:whereDict columns:self.sqlColumns processRow:^(NSDictionary* row) {
1043 manifest = [self fromDatabaseRow:row];
1050 + (nullable instancetype)newFakeManifestForZone:(NSString*)zone withItemRecords:(NSArray<CKRecord*>*)itemRecords currentItems:(NSDictionary*)currentItems signerID:(NSString*)signerID keyPair:(SFECKeyPair*)keyPair error:(NSError**)error
1052 CKKSManifestInjectionPointHelper* helper = [[CKKSManifestInjectionPointHelper alloc] initWithPeerID:signerID keyPair:keyPair isEgoPeer:NO];
1053 CKKSEgoManifest* manifest = [self newManifestForZone:zone withItems:@[] peerManifestIDs:@[] currentItems:currentItems error:error helper:helper];
1054 manifest.signerID = signerID;
1055 manifest.helper = helper;
1056 [manifest updateWithNewOrChangedRecords:itemRecords deletedRecordIDs:@[]];
1060 + (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error
1062 return [self newManifestForZone:zone withItems:items peerManifestIDs:peerManifestIDs currentItems:currentItems error:error helper:self.egoHelper];
1065 + (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error helper:(CKKSManifestInjectionPointHelper*)helper
1067 __block NSError* localError = nil;
1068 NSArray* leafRecords = [self leafRecordsForItems:items manifestName:nil zone:zone];
1069 NSData* digestValue = [self digestValueForLeafRecords:leafRecords];
1071 NSInteger generationCount = [self greatestKnownGenerationCount] + 1;
1073 __block CKKSEgoManifest* result = nil;
1074 [helper performWithEgoPeerID:^(NSString* _Nullable egoPeerID, NSError* _Nullable err) {
1078 else if (egoPeerID) {
1079 result = [[self alloc] initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecords:leafRecords peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:[NSDictionary dictionary] signatures:nil signerID:egoPeerID schema:__thisBuildsSchema];
1082 localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest because egoPeerID is nil"}];
1086 if (!result && !localError) {
1087 localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest"}];
1090 *error = localError;
1096 + (instancetype)fromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
1097 CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
1102 // Try to load leaf records
1103 if(![manifest loadLeafRecords:manifest.zoneID.zoneName error:error]) {
1110 + (instancetype)tryFromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
1111 CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
1116 // Try to load leaf records
1117 // Failing to load leaf records on a manifest that exists is an error, even in tryFromDatabaseWhere.
1118 if(![manifest loadLeafRecords:manifest.zoneID.zoneName error:error]) {
1125 - (bool)loadLeafRecords:(NSString*)ckzone error:(NSError * __autoreleasing *)error {
1126 NSMutableArray* leafRecords = [[NSMutableArray alloc] initWithCapacity:self.leafRecordIDs.count];
1127 for (NSString* leafID in self.leafRecordIDs) {
1128 CKKSEgoManifestLeafRecord* leafRecord = [CKKSEgoManifestLeafRecord fromDatabaseWhere:@{@"uuid" : [CKKSManifestLeafRecord leafUUIDForRecordID:leafID], @"ckzone" : ckzone} error:error];
1130 [leafRecords addObject:leafRecord];
1132 secerror("ckksmanifest: error loading leaf record from database: %@", error ? *error : nil);
1137 self->_leafRecords = leafRecords;
1141 + (NSDictionary*)generateSignaturesWithHelper:(CKKSManifestInjectionPointHelper*)helper derData:(NSData*)manifestDerData error:(NSError**)error
1143 __block NSData* signature = nil;
1144 __block NSError* localError = nil;
1145 [helper performWithSigningKey:^(SFECKeyPair* _Nullable signingKey, NSError* _Nullable err) {
1152 SFEC_X962SigningOperation* signingOperation = [self signatureOperation];
1153 SFSignedData* signedData = [signingOperation sign:manifestDerData withKey:signingKey error:&localError];
1154 signature = signedData.signature;
1159 *error = localError;
1162 return signature ? @{CKKSManifestEC384SignatureKey : signature} : nil;
1165 - (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
1167 NSMutableArray* leafRecordIDs = [[NSMutableArray alloc] initWithCapacity:leafRecords.count];
1168 for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
1169 [leafRecordIDs addObject:leafRecord.CKRecordName];
1172 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]]) {
1173 _leafRecords = leafRecords.copy;
1179 - (void)updateWithNewOrChangedRecords:(NSArray<CKRecord*>*)newOrChangedRecords deletedRecordIDs:(NSArray<CKRecordID*>*)deletedRecordIDs
1181 if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
1185 for (CKRecordID* deletedRecord in deletedRecordIDs) {
1186 NSString* deletedUUID = deletedRecord.recordName;
1187 CKKSEgoManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:deletedUUID];
1188 [leafRecord deleteItemWithUUID:deletedUUID];
1191 for (CKRecord* record in newOrChangedRecords) {
1192 CKKSEgoManifestLeafRecord* leafRecord = (CKKSEgoManifestLeafRecord*)[self leafRecordForItemUUID:record.recordID.recordName];
1193 [leafRecord addOrUpdateRecord:record];
1197 _generationCount = [self.class greatestKnownGenerationCount] + 1;
1200 - (void)setCurrentItemUUID:(NSString*)newCurrentItemUUID forIdentifier:(NSString*)currentPointerIdentifier
1202 _currentItemsDict[currentPointerIdentifier] = newCurrentItemUUID;
1204 _generationCount = [self.class greatestKnownGenerationCount] + 1;
1207 - (CKKSEgoManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
1209 NSUInteger leafBucket = LeafBucketIndexForUUID(uuid);
1210 if(_leafRecords.count > leafBucket) {
1211 return _leafRecords[leafBucket];
1217 - (NSArray<CKKSManifestLeafRecord*>*)leafRecords
1219 return _leafRecords;
1222 - (NSArray<CKRecord*>*)allCKRecordsWithZoneID:(CKRecordZoneID*)zoneID
1224 NSMutableArray* records = [[NSMutableArray alloc] initWithCapacity:_leafRecords.count + 1];
1225 [records addObject:[self CKRecordWithZoneID:zoneID]];
1227 for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
1228 [records addObject:[leafRecord CKRecordWithZoneID:zoneID]];
1234 - (bool)saveToDatabase:(NSError**)error
1236 bool result = [super saveToDatabase:error];
1238 for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
1239 result &= [leafRecord saveToDatabase:error];
1246 - (NSDictionary*)signatures
1248 if (!_signaturesDict) {
1249 _signaturesDict = [self.class generateSignaturesWithHelper:self.helper derData:self.derData error:nil];
1252 return _signaturesDict;
1255 - (void)setSignatures:(NSDictionary*)signatures
1257 _signaturesDict = signatures;
1260 - (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
1262 CKKSManifestInjectionPointHelper* helper = __helpersDict[signerID];
1263 return helper ?: __egoHelper;
1268 @implementation CKKSManifestInjectionPointHelper {
1270 SFECKeyPair* _keyPair;
1273 + (void)registerHelper:(CKKSManifestInjectionPointHelper*)helper forPeer:(NSString*)peerID
1275 if (!__helpersDict) {
1276 __helpersDict = [[NSMutableDictionary alloc] init];
1279 __helpersDict[peerID] = helper;
1282 + (void)registerEgoPeerID:(NSString*)egoPeerID keyPair:(SFECKeyPair*)keyPair
1284 __egoHelper = [[self alloc] initWithPeerID:egoPeerID keyPair:keyPair isEgoPeer:YES];
1287 + (BOOL)ignoreChanges
1289 return __ignoreChanges;
1292 + (void)setIgnoreChanges:(BOOL)ignoreChanges
1294 __ignoreChanges = ignoreChanges ? YES : NO;
1297 - (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer
1299 if (self = [super init]) {
1306 [self.class registerHelper:self forPeer:peerID];
1313 - (NSString*)description
1315 return [NSString stringWithFormat:@"%@ peerID: (%@)", [super description], _peerID];
1318 - (void)performWithSigningKey:(void (^)(SFECKeyPair* _Nullable signingKey, NSError* _Nullable error))handler
1320 if (s_accountInfo) {
1321 if(s_accountInfo.setupError) {
1322 handler(nil, s_accountInfo.setupError);
1324 handler(s_accountInfo.signingKey, nil);
1327 else if (_keyPair) {
1328 handler(_keyPair, nil);
1331 SOSCCPerformWithOctagonSigningKey(^(SecKeyRef signingSecKey, CFErrorRef err) {
1332 SFECKeyPair* key = nil;
1333 if (!err && signingSecKey) {
1334 key = [[SFECKeyPair alloc] initWithSecKey:signingSecKey];
1337 handler(key, (__bridge NSError*)err);
1342 - (void)performWithEgoPeerID:(void (^)(NSString* _Nullable egoPeerID, NSError* _Nullable error))handler
1344 if (s_accountInfo) {
1345 if(s_accountInfo.setupError) {
1346 handler(nil, s_accountInfo.setupError);
1348 handler(s_accountInfo.egoPeerID, nil);
1352 handler(_peerID, nil);
1355 NSError* error = nil;
1356 SOSPeerInfoRef egoPeerInfo = SOSCCCopyMyPeerInfo(NULL);
1357 NSString* egoPeerID = egoPeerInfo ? (__bridge NSString*)SOSPeerInfoGetPeerID(egoPeerInfo) : nil;
1358 handler(egoPeerID, error);
1359 CFReleaseNull(egoPeerInfo);
1363 - (void)performWithPeerVerifyingKeys:(void (^)(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error))handler
1365 if (s_accountInfo) {
1366 if(s_accountInfo.setupError) {
1367 handler(nil, s_accountInfo.setupError);
1369 handler(s_accountInfo.peerVerifyingKeys, nil);
1372 else if (__egoHelper || __helpersDict) {
1373 NSMutableDictionary* verifyingKeys = [[NSMutableDictionary alloc] init];
1374 [__helpersDict enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull peer, CKKSManifestInjectionPointHelper* _Nonnull helper, BOOL* _Nonnull stop) {
1375 verifyingKeys[peer] = helper.keyPair.publicKey;
1377 if (__egoHelper.keyPair) {
1378 verifyingKeys[__egoHelper.peerID] = __egoHelper.keyPair.publicKey;
1380 handler(verifyingKeys, nil);
1383 CFErrorRef error = NULL;
1384 NSMutableDictionary* peerKeys = [NSMutableDictionary dictionary];
1385 CFArrayRef peerInfos = SOSCCCopyValidPeerPeerInfo(&error);
1386 if (!peerInfos || error) {
1387 handler(nil, (__bridge NSError*)error);
1388 CFReleaseNull(peerInfos);
1389 CFReleaseNull(error);
1393 CFArrayForEach(peerInfos, ^(const void* peerInfoPtr) {
1394 SOSPeerInfoRef peerInfo = (SOSPeerInfoRef)peerInfoPtr;
1395 CFErrorRef blockError = NULL;
1396 SecKeyRef secPublicKey = SOSPeerInfoCopyOctagonSigningPublicKey(peerInfo, &blockError);
1397 if (!secPublicKey || blockError) {
1398 CFReleaseNull(secPublicKey);
1399 CFReleaseNull(blockError);
1403 SFECPublicKey* publicKey = [[SFECPublicKey alloc] initWithSecKey:secPublicKey];
1404 CFReleaseNull(secPublicKey);
1405 NSString* peerID = (__bridge NSString*)SOSPeerInfoGetPeerID(peerInfo);
1406 peerKeys[peerID] = publicKey;
1409 handler(peerKeys, nil);
1410 CFReleaseNull(peerInfos);
1414 - (SFECKeyPair*)keyPair
1426 @implementation CKKSAccountInfo
1428 @synthesize signingKey = _signingKey;
1429 @synthesize peerVerifyingKeys = _peerVerifyingKeys;
1430 @synthesize egoPeerID = _egoPeerID;