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 "CKKSAnalyticsLogger.h"
32 #import "utilities/der_plist.h"
33 #import <securityd/SOSCloudCircleServer.h>
34 #import <securityd/SecItemServer.h>
35 #import <Security/SecureObjectSync/SOSPeerInfo.h>
36 #import <Security/SecureObjectSync/SOSCloudCircleInternal.h>
37 #import <SecurityFoundation/SFSigningOperation.h>
38 #import <SecurityFoundation/SFKey.h>
39 #import <SecurityFoundation/SFKey_Private.h>
40 #import <SecurityFoundation/SFDigestOperation.h>
41 #import <CloudKit/CloudKit.h>
43 NSString* const CKKSManifestZoneKey = @"zone";
44 NSString* const CKKSManifestSignerIDKey = @"signerID";
45 NSString* const CKKSManifestGenCountKey = @"gencount";
47 static NSString* const CKKSManifestDigestKey = @"CKKSManifestDigestKey";
48 static NSString* const CKKSManifestPeerManifestsKey = @"CKKSManifestPeerManifestsKey";
49 static NSString* const CKKSManifestCurrentItemsKey = @"CKKSManifestCurrentItemsKey";
50 static NSString* const CKKSManifestGenerationCountKey = @"CKKSManifestGenerationCountKey";
51 static NSString* const CKKSManifestSchemaVersionKey = @"CKKSManifestSchemaVersionKey";
53 static NSString* const CKKSManifestEC384SignatureKey = @"CKKSManifestEC384SignatureKey";
55 static NSString* const CKKSManifestErrorDomain = @"CKKSManifestErrorDomain";
57 #define NUM_MANIFEST_LEAF_RECORDS 72
58 #define BITS_PER_UUID_CHAR 36
60 static CKKSManifestInjectionPointHelper* __egoHelper = nil;
61 static NSMutableDictionary<NSString*, CKKSManifestInjectionPointHelper*>* __helpersDict = nil;
62 static BOOL __ignoreChanges = NO;
65 CKKSManifestErrorInvalidDigest = 1,
66 CKKSManifestErrorVerifyingKeyNotFound = 2,
67 CKKSManifestErrorManifestGenerationFailed = 3,
68 CKKSManifestErrorCurrentItemUUIDNotFound = 4
71 typedef NS_ENUM(NSInteger, CKKSManifestFieldType) {
72 CKKSManifestFieldTypeStringRaw = 0,
73 CKKSManifestFieldTypeStringBase64Encoded = 1,
74 CKKSManifestFieldTypeDataAsBase64String = 2,
75 CKKSManifestFieldTypeNumber = 3,
76 CKKSManifestFieldTypeArrayRaw = 4,
77 CKKSManifestFieldTypeArrayAsDERBase64String = 5,
78 CKKSManifestFieldTypeDictionaryAsDERBase64String = 6
81 @interface CKKSAccountInfo : NSObject {
82 SFECKeyPair* _signingKey;
83 NSDictionary* _peerVerifyingKeys;
88 @property SFECKeyPair* signingKey;
89 @property NSDictionary* peerVerifyingKeys;
90 @property NSString* egoPeerID;
91 @property NSError* setupError;
94 static NSDictionary* __thisBuildsSchema = nil;
95 static CKKSAccountInfo* s_accountInfo = nil;
97 @interface CKKSManifest () {
100 NSData* _digestValue;
101 NSUInteger _generationCount;
104 NSArray* _leafRecordIDs;
105 NSArray* _peerManifestIDs;
106 NSMutableDictionary* _currentItemsDict;
107 NSDictionary* _futureData;
108 NSDictionary* _signaturesDict;
109 NSDictionary* _schema;
110 CKKSManifestInjectionPointHelper* _helper;
113 @property (nonatomic, readonly) NSString* zoneName;
114 @property (nonatomic, readonly) NSArray<NSString*>* leafRecordIDs;
115 @property (nonatomic, readonly) NSArray<NSString*>* peerManifestIDs;
116 @property (nonatomic, readonly) NSDictionary* currentItems;
117 @property (nonatomic, readonly) NSDictionary* futureData;
118 @property (nonatomic, readonly) NSDictionary* signatures;
119 @property (nonatomic, readonly) NSDictionary* schema;
121 @property (nonatomic, readwrite) NSString* signerID;
122 @property (nonatomic) CKKSManifestInjectionPointHelper* helper;
124 + (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords;
130 @interface CKKSPendingManifest () {
131 NSMutableArray* _committedLeafRecordIDs;
134 @property (nonatomic, readonly) NSArray<NSString*>* committedLeafRecordIDs;
138 @interface CKKSEgoManifest () {
139 NSArray* _leafRecords;
142 @property (class, readonly) CKKSManifestInjectionPointHelper* egoHelper;
144 @property (nonatomic, readwrite) NSDictionary* signatures;
148 @interface CKKSManifestInjectionPointHelper ()
150 - (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer;
152 - (void)performWithSigningKey:(void (^)(SFECKeyPair* _Nullable signingKey, NSError* _Nullable error))handler;
153 - (void)performWithEgoPeerID:(void (^)(NSString* _Nullable egoPeerID, NSError* _Nullable error))handler;
154 - (void)performWithPeerVerifyingKeys:(void (^)(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error))handler;
158 static NSData* ManifestDERData(NSString* zone, NSData* digestValue, NSArray<NSString*>* peerManifestIDs, NSDictionary<NSString*, NSString*>* currentItems, NSUInteger generationCount, NSDictionary* futureFields, NSDictionary* schema, NSError** error)
160 NSArray* sortedPeerManifestIDs = [peerManifestIDs sortedArrayUsingSelector:@selector(compare:)];
162 NSMutableDictionary* manifestDict = [NSMutableDictionary dictionary];
163 manifestDict[CKKSManifestDigestKey] = digestValue;
164 manifestDict[CKKSManifestPeerManifestsKey] = sortedPeerManifestIDs;
165 manifestDict[CKKSManifestCurrentItemsKey] = currentItems;
166 manifestDict[CKKSManifestGenerationCountKey] = [NSNumber numberWithUnsignedInteger:generationCount];
168 [futureFields enumerateKeysAndObjectsUsingBlock:^(NSString* futureKey, id futureValue, BOOL* stop) {
169 CKKSManifestFieldType fieldType = [schema[futureKey] integerValue];
170 if (fieldType == CKKSManifestFieldTypeStringRaw) {
171 manifestDict[futureKey] = futureValue;
173 else if (fieldType == CKKSManifestFieldTypeStringBase64Encoded) {
174 manifestDict[futureKey] = [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:futureValue options:0] encoding:NSUTF8StringEncoding];
176 else if (fieldType == CKKSManifestFieldTypeDataAsBase64String) {
177 manifestDict[futureKey] = [[NSData alloc] initWithBase64EncodedString:futureValue options:0];
179 else if (fieldType == CKKSManifestFieldTypeNumber) {
180 manifestDict[futureKey] = futureValue;
182 else if (fieldType == CKKSManifestFieldTypeArrayRaw) {
183 manifestDict[futureKey] = futureValue;
185 else if (fieldType == CKKSManifestFieldTypeArrayAsDERBase64String) {
186 manifestDict[futureKey] = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
188 else if (fieldType == CKKSManifestFieldTypeDictionaryAsDERBase64String) {
189 manifestDict[futureKey] = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
192 ckkserrorwithzonename("ckksmanifest", zone, "unrecognized field type in future schema: %ld", (long)fieldType);
196 CFErrorRef cfError = NULL;
197 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)manifestDict, &cfError);
199 ckkserrorwithzonename("ckksmanifest", zone, "error creating manifest der data: %@", cfError);
201 *error = (__bridge_transfer NSError*)cfError;
209 static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
211 NSInteger prefixIntegerValue = 0;
212 for (NSInteger characterIndex = 0; characterIndex * BITS_PER_UUID_CHAR < NUM_MANIFEST_LEAF_RECORDS; characterIndex++) {
213 prefixIntegerValue += [uuid characterAtIndex:characterIndex];
216 return prefixIntegerValue % NUM_MANIFEST_LEAF_RECORDS;
219 @implementation CKKSManifest
221 @synthesize zoneName = _zoneName;
222 @synthesize leafRecordIDs = _leafRecordIDs;
223 @synthesize peerManifestIDs = _peerManifestIDs;
224 @synthesize currentItems = _currentItemsDict;
225 @synthesize futureData = _futureData;
226 @synthesize signatures = _signaturesDict;
227 @synthesize signerID = _signerID;
228 @synthesize schema = _schema;
229 @synthesize helper = _helper;
233 if (self == [CKKSManifest class]) {
234 __thisBuildsSchema = @{ CKKSManifestSchemaVersionKey : @(1),
235 SecCKRecordManifestDigestValueKey : @(CKKSManifestFieldTypeDataAsBase64String),
236 SecCKRecordManifestGenerationCountKey : @(CKKSManifestFieldTypeNumber),
237 SecCKRecordManifestLeafRecordIDsKey : @(CKKSManifestFieldTypeArrayRaw),
238 SecCKRecordManifestPeerManifestRecordIDsKey : @(CKKSManifestFieldTypeArrayRaw),
239 SecCKRecordManifestCurrentItemsKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String),
240 SecCKRecordManifestSignaturesKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String),
241 SecCKRecordManifestSignerIDKey : @(CKKSManifestFieldTypeStringRaw),
242 SecCKRecordManifestSchemaKey : @(CKKSManifestFieldTypeDictionaryAsDERBase64String) };
248 static dispatch_once_t onceToken;
249 dispatch_once(&onceToken, ^{
250 NSDictionary* systemDefaults = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle bundleWithPath:@"/System/Library/Frameworks/Security.framework"] pathForResource:@"CKKSLogging" ofType:@"plist"]];
251 bool shouldSync = !![[systemDefaults valueForKey:@"SyncManifests"] boolValue];
252 bool shouldEnforce = !![[systemDefaults valueForKey:@"EnforceManifests"] boolValue];
254 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
255 bool userDefaultsShouldSync = !![defaults boolForKey:@"SyncManifests"];
256 bool userDefaultsShouldEnforce = !![defaults boolForKey:@"EnforceManifests"];
257 shouldSync |= userDefaultsShouldSync;
258 shouldEnforce |= userDefaultsShouldEnforce;
261 SecCKKSEnableSyncManifests();
264 SecCKKSEnableEnforceManifests();
269 + (bool)shouldSyncManifests
272 return SecCKKSSyncManifests();
275 + (bool)shouldEnforceManifests
278 return SecCKKSEnforceManifests();
281 + (void)performWithAccountInfo:(void (^)(void))action
283 CKKSAccountInfo* accountInfo = [[CKKSAccountInfo alloc] init];
285 [[CKKSEgoManifest egoHelper] performWithSigningKey:^(_SFECKeyPair* signingKey, NSError* error) {
286 accountInfo.signingKey = signingKey;
288 secerror("ckksmanifest: cannot get signing key from account: %@", error);
289 if(accountInfo.setupError == nil) {
290 accountInfo.setupError = error;
295 [[CKKSEgoManifest egoHelper] performWithEgoPeerID:^(NSString* egoPeerID, NSError* error) {
296 accountInfo.egoPeerID = egoPeerID;
299 secerror("ckksmanifest: cannot get ego peer ID from account: %@", error);
300 if(accountInfo.setupError == nil) {
301 accountInfo.setupError = error;
306 [[CKKSEgoManifest egoHelper] performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* peerKeys, NSError* error) {
307 accountInfo.peerVerifyingKeys = peerKeys;
309 secerror("ckksmanifest: cannot get peer keys from account: %@", error);
310 if(accountInfo.setupError == nil) {
311 accountInfo.setupError = error;
316 s_accountInfo = accountInfo;
323 + (nullable instancetype)tryFromDatabaseWhere:(NSDictionary*)whereDict error:(NSError* __autoreleasing *)error
325 CKKSManifest* manifest = [super tryFromDatabaseWhere:whereDict error:error];
326 manifest.helper = __helpersDict[manifest.signerID];
330 + (nullable instancetype)manifestForZone:(NSString*)zone peerID:(NSString*)peerID error:(NSError**)error
332 NSDictionary* databaseWhereClause = @{ @"ckzone" : zone, @"signerID" : peerID };
333 return [self tryFromDatabaseWhere:databaseWhereClause error:error];
336 + (nullable instancetype)manifestForRecordName:(NSString*)recordName error:(NSError**)error
338 return [self tryFromDatabaseWhere:[self whereClauseForRecordName:recordName] error:error];
341 + (nullable instancetype)latestTrustedManifestForZone:(NSString*)zone error:(NSError**)error
343 NSDictionary* databaseWhereClause = @{ @"ckzone" : zone };
344 NSArray* manifests = [[self allWhere:databaseWhereClause error:error] sortedArrayUsingComparator:^NSComparisonResult(CKKSManifest* _Nonnull firstManifest, CKKSManifest* _Nonnull secondManifest) {
345 NSInteger firstGenerationCount = firstManifest.generationCount;
346 NSInteger secondGenerationCount = secondManifest.generationCount;
348 if (firstGenerationCount > secondGenerationCount) {
349 return NSOrderedDescending;
351 else if (firstGenerationCount < secondGenerationCount) {
352 return NSOrderedAscending;
355 return NSOrderedSame;
359 __block CKKSManifest* result = nil;
360 [manifests enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(CKKSManifest* _Nonnull manifest, NSUInteger index, BOOL* _Nonnull stop) {
361 if ([manifest validateWithError:nil]) {
367 // TODO: add error for when we didn't find anything
371 + (SFEC_X962SigningOperation*)signatureOperation
373 SFECKeySpecifier* keySpecifier = [[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384];
374 return [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:keySpecifier digestOperation:[[SFSHA384DigestOperation alloc] init]];
377 + (NSData*)digestForData:(NSData*)data
379 return [SFSHA384DigestOperation digest:data];
382 + (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords
384 NSMutableData* concatenatedLeafNodeDigestData = [[NSMutableData alloc] init];
385 for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
386 [concatenatedLeafNodeDigestData appendData:leafRecord.digestValue];
389 return [self digestForData:concatenatedLeafNodeDigestData];
392 + (instancetype)manifestForPendingManifest:(CKKSPendingManifest*)pendingManifest
394 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];
397 + (instancetype)fromDatabaseRow:(NSDictionary*)row
399 NSString* digestBase64String = row[@"digest"];
400 NSData* digest = [digestBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0] : nil;
402 NSString* zone = row[@"ckzone"];
403 NSUInteger generationCount = [row[@"gencount"] integerValue];
404 NSString* signerID = row[@"signerID"];
406 NSString* encodedRecordBase64String = row[@"ckrecord"];
407 NSData* encodedRecord = [encodedRecordBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:encodedRecordBase64String options:0] : nil;
409 NSData* leafRecordIDData = [[NSData alloc] initWithBase64EncodedString:row[@"leafIDs"] options:0];
410 NSArray* leafRecordIDs = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)leafRecordIDData, 0, NULL, NULL);
411 if (![leafRecordIDs isKindOfClass:[NSArray class]]) {
412 leafRecordIDs = [NSArray array];
415 NSData* peerManifestIDData = [[NSData alloc] initWithBase64EncodedString:row[@"peerManifests"] options:0];
416 NSArray* peerManifestIDs = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)peerManifestIDData, 0, NULL, NULL);
417 if (![peerManifestIDs isKindOfClass:[NSArray class]]) {
418 peerManifestIDs = [NSArray array];
421 NSData* currentItemsData = [[NSData alloc] initWithBase64EncodedString:row[@"currentItems"] options:0];
422 NSDictionary* currentItemsDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)currentItemsData, 0, NULL, NULL);
423 if (![currentItemsDict isKindOfClass:[NSDictionary class]]) {
424 currentItemsDict = [NSDictionary dictionary];
427 NSData* futureData = [[NSData alloc] initWithBase64EncodedString:row[@"futureData"] options:0];
428 NSDictionary* futureDataDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)futureData, 0, NULL, NULL);
429 if (![futureDataDict isKindOfClass:[NSDictionary class]]) {
430 futureDataDict = [NSDictionary dictionary];
433 NSData* signaturesData = [[NSData alloc] initWithBase64EncodedString:row[@"signatures"] options:0];
434 NSDictionary* signatures = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)signaturesData, 0, NULL, NULL);
435 if (![signatures isKindOfClass:[NSDictionary class]]) {
436 signatures = [NSDictionary dictionary];
439 NSData* schemaData = [[NSData alloc] initWithBase64EncodedString:row[@"schema"] options:0];
440 NSDictionary* schemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)schemaData, 0, NULL, NULL);
441 if (![schemaDict isKindOfClass:[NSDictionary class]]) {
442 schemaDict = __thisBuildsSchema;
445 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];
448 + (NSArray<NSString*>*)sqlColumns
450 return @[@"ckzone", @"gencount", @"digest", @"signatures", @"signerID", @"leafIDs", @"peerManifests", @"currentItems", @"futureData", @"schema", @"ckrecord"];
453 + (NSString*)sqlTable
455 return @"ckmanifest";
458 + (NSUInteger)greatestKnownGenerationCount
460 __block NSUInteger result = 0;
461 [self queryMaxValueForField:@"gencount" inTable:self.sqlTable where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
462 result = [row[@"gencount"] integerValue];
465 [CKKSPendingManifest queryMaxValueForField:@"gencount" inTable:[CKKSPendingManifest sqlTable] where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
466 result = MAX(result, (NSUInteger)[row[@"gencount"] integerValue]);
472 - (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
474 if (self = [super init]) {
475 _digestValue = digestValue;
477 _generationCount = generationCount;
478 _leafRecordIDs = [leafRecordIDs copy];
479 _currentItemsDict = currentItems ? [currentItems mutableCopy] : [NSMutableDictionary dictionary];
480 _futureData = futureData ? [futureData copy] : @{};
481 _signaturesDict = [signatures copy];
482 _signerID = signerID;
483 _schema = schema ? schema.copy : __thisBuildsSchema;
485 if ([peerManifestIDs.firstObject isEqualToString:signerID]) {
486 _peerManifestIDs = peerManifestIDs;
489 NSMutableArray* tempPeerManifests = [[NSMutableArray alloc] initWithObjects:signerID, nil];
490 if (peerManifestIDs) {
491 [tempPeerManifests addObjectsFromArray:peerManifestIDs];
493 _peerManifestIDs = tempPeerManifests;
496 _helper = helper ?: [self defaultHelperForSignerID:signerID];
498 _helper = [[CKKSManifestInjectionPointHelper alloc] init];
505 - (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
507 return [self initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:futureData signatures:signatures signerID:signerID schema:schema helper:nil];
510 - (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
512 if (self = [self initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecordIDs:leafRecordIDs peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:futureData signatures:signatures signerID:signerID schema:schema]) {
513 self.encodedCKRecord = encodedRecord;
519 - (instancetype)initWithCKRecord:(CKRecord*)record
521 NSError* error = nil;
522 NSString* signatureBase64String = record[SecCKRecordManifestSignaturesKey];
523 if (!signatureBase64String) {
524 ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have signatures attached: %@", record);
525 [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
528 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:signatureBase64String options:0];
529 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
531 ckkserror("ckksmanifest", record.recordID.zoneID, "failed to initialize CKKSManifest from CKRecord because we could not form a signature dict from the record: %@", record);
532 [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
536 NSDictionary* schemaDict = nil;
537 NSString* schemaBase64String = record[SecCKRecordManifestSchemaKey];
538 if (schemaBase64String) {
539 NSData* schemaData = [[NSData alloc] initWithBase64EncodedString:schemaBase64String options:0];
540 schemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)schemaData, 0, NULL, NULL);
542 if (![schemaDict isKindOfClass:[NSDictionary class]]) {
543 schemaDict = __thisBuildsSchema;
546 NSString* digestBase64String = record[SecCKRecordManifestDigestValueKey];
547 if (!digestBase64String) {
548 ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have a digest attached: %@", record);
549 [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
552 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0];
554 if (self = [self initWithDigestValue:digestData
555 zone:record.recordID.zoneID.zoneName
556 generationCount:[record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue]
557 leafRecordIDs:record[SecCKRecordManifestLeafRecordIDsKey]
558 peerManifestIDs:record[SecCKRecordManifestPeerManifestRecordIDsKey]
559 currentItems:record[SecCKRecordManifestCurrentItemsKey]
560 futureData:[self futureDataDictFromRecord:record withSchema:schemaDict]
561 signatures:signaturesDict
562 signerID:record[SecCKRecordManifestSignerIDKey]
563 schema:schemaDict]) {
564 self.storedCKRecord = record;
565 [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestCreateFromCKRecord"];
568 [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
574 - (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
576 return __helpersDict[signerID];
579 - (NSDictionary*)signatureDictFromDERData:(NSData*)derData error:(NSError**)error
581 CFErrorRef localError = NULL;
582 NSDictionary* signaturesDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)derData, 0, NULL, &localError);
583 if (![signaturesDict isKindOfClass:[NSDictionary class]]) {
584 ckkserror("ckksmanifest", self, "failed to decode signatures der dict with error: %@", localError);
586 *error = (__bridge_transfer NSError*)localError;
590 return signaturesDict;
593 - (NSData*)derDataFromSignatureDict:(NSDictionary*)signatureDict error:(NSError**)error
595 CFErrorRef localError = NULL;
596 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)signatureDict, &localError);
598 ckkserror("ckksmanifest", self, "failed to encode signatures dict to der with error: %@", localError);
600 *error = (__bridge_transfer NSError*)localError;
607 - (NSArray*)peerManifestsFromDERData:(NSData*)derData error:(NSError**)error
609 CFErrorRef localError = NULL;
610 NSArray* peerManifests = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)derData, 0, NULL, &localError);
611 if (![peerManifests isKindOfClass:[NSArray class]]) {
612 ckkserror("ckksmanifest", self, "failed to decode peer manifests der array with error: %@", localError);
614 *error = (__bridge_transfer NSError*)localError;
618 return peerManifests;
621 - (NSData*)derDataFromPeerManifests:(NSArray*)peerManifests error:(NSError**)error
623 CFErrorRef localError = NULL;
624 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)peerManifests, &localError);
626 ckkserror("ckksmanifest", self, "failed to encode peer manifests to der with error: %@", localError);
628 *error = (__bridge_transfer NSError*)localError;
635 - (NSDictionary*)futureDataDictFromRecord:(CKRecord*)record withSchema:(NSDictionary*)cloudSchema
637 NSMutableDictionary* futureData = [NSMutableDictionary dictionary];
638 [cloudSchema enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSData* obj, BOOL* stop) {
639 if (![__thisBuildsSchema.allKeys containsObject:key]) {
640 futureData[key] = record[key];
647 - (BOOL)updateWithRecord:(CKRecord*)record error:(NSError**)error
649 if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
650 return YES; // don't set off any alarms here - just pretend we did it
653 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestSignaturesKey] options:0];
654 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:error];
655 if (!signaturesDict) {
659 NSData* cloudSchemaData = record[SecCKRecordManifestSchemaKey];
660 NSDictionary* cloudSchemaDict = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)cloudSchemaData, 0, NULL, NULL);
661 if (![cloudSchemaDict isKindOfClass:[NSDictionary class]]) {
662 cloudSchemaDict = __thisBuildsSchema;
665 _digestValue = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestDigestValueKey] options:0];
666 _generationCount = [record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue];
667 _leafRecordIDs = record[SecCKRecordManifestLeafRecordIDsKey];
668 _peerManifestIDs = record[SecCKRecordManifestPeerManifestRecordIDsKey];
669 _currentItemsDict = [record[SecCKRecordManifestCurrentItemsKey] mutableCopy];
670 if (!_currentItemsDict) {
671 _currentItemsDict = [NSMutableDictionary dictionary];
673 _futureData = [[self futureDataDictFromRecord:record withSchema:cloudSchemaDict] copy];
674 _signaturesDict = signaturesDict;
675 _signerID = record[SecCKRecordManifestSignerIDKey];
676 _schema = cloudSchemaDict;
677 self.storedCKRecord = record;
683 - (NSDictionary<NSString*, NSString*>*)sqlValues
685 void (^addValueSafelyToDictionaryAndLogIfNil)(NSMutableDictionary*, NSString*, id) = ^(NSMutableDictionary* dictionary, NSString* key, id value) {
687 value = [NSNull null];
688 secerror("CKKSManifest: saving manifest to database but %@ is nil", key);
691 dictionary[key] = value;
694 NSMutableDictionary* sqlValues = [[NSMutableDictionary alloc] init];
695 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckzone", _zoneName);
696 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"gencount", [NSNumber numberWithUnsignedInteger:_generationCount]);
697 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"digest", self.digestValue);
698 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"signatures", [[self derDataFromSignatureDict:self.signatures error:nil] base64EncodedStringWithOptions:0]);
699 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"signerID", _signerID);
700 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"leafIDs", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_leafRecordIDs, NULL) base64EncodedStringWithOptions:0]);
701 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"peerManifests", [[self derDataFromPeerManifests:_peerManifestIDs error:nil] base64EncodedStringWithOptions:0]);
702 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"currentItems", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_currentItemsDict, NULL) base64EncodedStringWithOptions:0]);
703 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"futureData", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_futureData, NULL) base64EncodedStringWithOptions:0]);
704 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"schema", [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)_schema, NULL) base64EncodedStringWithOptions:0]);
705 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckrecord", [self.encodedCKRecord base64EncodedStringWithOptions:0]);
710 - (NSDictionary<NSString*, NSString*>*)whereClauseToFindSelf
712 return @{ @"ckzone" : CKKSNilToNSNull(_zoneName),
713 @"gencount" : [NSNumber numberWithUnsignedInteger:_generationCount],
714 @"signerID" : CKKSNilToNSNull(_signerID) };
717 - (NSString*)CKRecordName
719 return [NSString stringWithFormat:@"Manifest:-:%@:-:%@:-:%lu", _zoneName, _signerID, (unsigned long)_generationCount];
722 + (NSDictionary*)whereClauseForRecordName:(NSString*)recordName
724 NSArray* components = [recordName componentsSeparatedByString:@":-:"];
725 if (components.count < 4) {
726 secerror("CKKSManifest: could not parse components from record name: %@", recordName);
729 return @{ @"ckzone" : components[1],
730 @"signerID" : components[2],
731 @"gencount" : components[3] };
734 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID
736 if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
737 @throw [NSException exceptionWithName:@"WrongCKRecordTypeException" reason:[NSString stringWithFormat:@"CKRecorType (%@) was not %@", record.recordType, SecCKRecordManifestType] userInfo:nil];
740 NSData* signatureDERData = [self derDataFromSignatureDict:self.signatures error:nil];
741 if (!signatureDERData) {
742 [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestUpdateRecord" withAttributes:@{CKKSManifestZoneKey : zoneID.zoneName, CKKSManifestSignerIDKey : _signerID, CKKSManifestGenCountKey : @(_generationCount)}];
746 record[SecCKRecordManifestDigestValueKey] = [self.digestValue base64EncodedStringWithOptions:0];
747 record[SecCKRecordManifestGenerationCountKey] = [NSNumber numberWithUnsignedInteger:_generationCount];
748 record[SecCKRecordManifestLeafRecordIDsKey] = _leafRecordIDs;
749 record[SecCKRecordManifestPeerManifestRecordIDsKey] = _peerManifestIDs;
750 record[SecCKRecordManifestCurrentItemsKey] = [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)_currentItemsDict, NULL) base64EncodedStringWithOptions:0];
751 record[SecCKRecordManifestSignaturesKey] = [signatureDERData base64EncodedStringWithOptions:0];
752 record[SecCKRecordManifestSignerIDKey] = _signerID;
753 record[SecCKRecordManifestSchemaKey] = [(__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)_schema, NULL) base64EncodedStringWithOptions:0];
755 [_futureData enumerateKeysAndObjectsUsingBlock:^(NSString* key, id futureField, BOOL* stop) {
756 record[key] = futureField;
759 [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestUpdateRecord"];
763 - (bool)matchesCKRecord:(CKRecord*)record
765 if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
769 NSError* error = nil;
770 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestSignaturesKey] options:0];
771 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
772 if (!signaturesDict || error) {
776 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestDigestValueKey] options:0];
777 return [digestData isEqual:self.digestValue] &&
778 [record[SecCKRecordManifestGenerationCountKey] unsignedIntegerValue] == _generationCount &&
779 [record[SecCKRecordManifestPeerManifestRecordIDsKey] isEqual:_peerManifestIDs] &&
780 [signaturesDict isEqual:self.signatures] &&
781 [record[SecCKRecordManifestSignerIDKey] isEqual:_signerID];
784 - (void)setFromCKRecord:(CKRecord*)record
786 NSError* error = nil;
787 if (![self updateWithRecord:record error:&error]) {
788 ckkserror("ckksmanifest", self, "failed to update manifest from CKRecord with error: %@", error);
795 NSError* error = nil;
796 _derData = ManifestDERData(_zoneName, self.digestValue, _peerManifestIDs, _currentItemsDict, _generationCount, _futureData, _schema, &error);
798 ckkserror("ckksmanifest", self, "error encoding manifest into DER: %@", error);
806 - (BOOL)validateWithError:(NSError**)error
808 __block BOOL verified = false;
809 NSData* manifestDerData = self.derData;
810 if (manifestDerData) {
811 __block NSError* localError = nil;
813 [_helper performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error) {
815 ckkserror("ckksmanifest", self, "Error fetching peer verifying keys: %@", error);
817 SFECPublicKey* verifyingKey = peerKeys[self->_signerID];
819 SFEC_X962SigningOperation* signingOperation = [self.class signatureOperation];
820 SFSignedData* signedData = [[SFSignedData alloc] initWithData:manifestDerData signature:self.signatures[CKKSManifestEC384SignatureKey]];
821 verified = [signingOperation verify:signedData withKey:verifyingKey error:&localError] == NULL ? false : true;
825 localError = [NSError errorWithDomain:CKKSManifestErrorDomain
826 code:CKKSManifestErrorVerifyingKeyNotFound
827 userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"could not find manifest public key for peer %@", self->_signerID],
828 NSUnderlyingErrorKey: CKKSNilToNSNull(error)}];
838 [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestValidateSelf"];
841 [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"CKKSManifestValidateSelf" withAttributes:@{CKKSManifestZoneKey : _zoneName, CKKSManifestSignerIDKey : _signerID, CKKSManifestGenerationCountKey : @(_generationCount)}];
847 - (BOOL)validateItem:(CKKSItem*)item withError:(NSError**)error
849 NSString* uuid = item.uuid;
850 CKKSManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:uuid];
851 NSData* expectedItemDigest = leafRecord.recordDigestDict[uuid];
852 if ([[self.class digestForData:item.encitem] isEqual:expectedItemDigest]) {
856 *error = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorInvalidDigest userInfo:@{NSLocalizedDescriptionKey : @"could not validate item because the digest is invalid"}];
862 - (BOOL)validateCurrentItem:(CKKSCurrentItemPointer*)currentItem withError:(NSError**)error
864 BOOL result = [currentItem.currentItemUUID isEqualToString:[_currentItemsDict valueForKey:currentItem.identifier]];
865 if (!result && error) {
866 *error = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorCurrentItemUUIDNotFound userInfo:@{NSLocalizedDescriptionKey :@"could not validate current item because the UUID does not match the manifest"}];
872 - (BOOL)itemUUIDExistsInManifest:(NSString*)uuid
874 CKKSManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:uuid];
875 return leafRecord.recordDigestDict[uuid] != nil;
878 - (BOOL)contentsAreEqualToManifest:(CKKSManifest*)otherManifest
880 return [_digestValue isEqual:otherManifest.digestValue];
883 - (CKKSManifestLeafRecord*)leafRecordForID:(NSString*)leafRecordID
885 NSError* error = nil;
886 CKKSManifestLeafRecord* leafRecord = [CKKSManifestLeafRecord leafRecordForID:leafRecordID error:&error];
887 if (error || !leafRecord) {
888 ckkserror("ckksmanifest", self, "failed to lookup manifest leaf record with id: %@ error: %@", leafRecordID, error);
894 - (CKKSManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
896 NSInteger bucketIndex = LeafBucketIndexForUUID(uuid);
897 NSString* leafRecordID = _leafRecordIDs[bucketIndex];
898 return [self leafRecordForID:leafRecordID];
905 _signaturesDict = nil;
908 - (NSData*)digestValue
911 _digestValue = [self.class digestValueForLeafRecords:self.leafRecords];
917 - (NSArray<CKKSManifestLeafRecord*>*)leafRecords
919 NSMutableArray* leafRecords = [[NSMutableArray alloc] initWithCapacity:_leafRecordIDs.count];
920 for (NSString* recordID in _leafRecordIDs) {
921 CKKSManifestLeafRecord* leafRecord = [self leafRecordForID:recordID];
923 [leafRecords addObject:leafRecord];
925 ckkserror("ckksmanifest", self, "failed to fetch leaf record from CKManifest for %@", recordID);
926 // TODO: auto bug capture?
933 - (NSString*)ckRecordType
935 return SecCKRecordManifestType;
944 _leafRecordIDs = nil;
945 _peerManifestIDs = nil;
946 _currentItemsDict = nil;
948 _signaturesDict = nil;
954 @implementation CKKSPendingManifest
956 @synthesize committedLeafRecordIDs = _committedLeafRecordIDs;
958 + (NSString*)sqlTable
960 return @"pending_manifest";
963 - (BOOL)isReadyToCommit
965 for (NSString* leafRecordID in self.leafRecordIDs) {
966 if ([CKKSManifestLeafRecord recordExistsForID:leafRecordID] || [CKKSManifestPendingLeafRecord recordExistsForID:leafRecordID]) {
970 ckksinfo("ckksmanifest", self, "Not ready to commit manifest, yet - missing leaf record ID: %@", leafRecordID);
978 - (CKKSManifest*)commitToDatabaseWithError:(NSError**)error
980 NSError* localError = nil;
982 _committedLeafRecordIDs = [[NSMutableArray alloc] init];
984 for (NSString* leafRecordID in self.leafRecordIDs) {
985 CKKSManifestPendingLeafRecord* pendingLeaf = [CKKSManifestPendingLeafRecord leafRecordForID:leafRecordID error:&localError];
987 CKKSManifestLeafRecord* committedLeaf = [pendingLeaf commitToDatabaseWithError:error];
989 [_committedLeafRecordIDs addObject:committedLeaf.CKRecordName];
996 CKKSManifestLeafRecord* existingLeaf = [CKKSManifestLeafRecord leafRecordForID:leafRecordID error:&localError];
998 [_committedLeafRecordIDs addObject:existingLeaf.CKRecordName];
1005 *error = localError;
1011 CKKSManifest* manifest = [CKKSManifest manifestForPendingManifest:self];
1012 if ([manifest saveToDatabase:error]) {
1013 [self deleteFromDatabase:error];
1023 @implementation CKKSEgoManifest
1025 + (CKKSManifestInjectionPointHelper*)egoHelper
1027 return __egoHelper ?: [[CKKSManifestInjectionPointHelper alloc] init];
1030 + (NSArray*)leafRecordsForItems:(NSArray*)items manifestName:(NSString*)manifestName zone:(NSString*)zone
1032 NSMutableArray* leafRecords = [[NSMutableArray alloc] init];
1033 for (NSInteger i = 0; i < NUM_MANIFEST_LEAF_RECORDS; i++) {
1034 [leafRecords addObject:[CKKSEgoManifestLeafRecord newLeafRecordInZone:zone]];
1037 for (CKKSItem* item in items) {
1038 CKKSEgoManifestLeafRecord* leafRecord = leafRecords[LeafBucketIndexForUUID(item.uuid)];
1039 [leafRecord addOrUpdateRecordUUID:item.uuid withEncryptedItemData:item.encitem];
1045 + (nullable CKKSEgoManifest*)tryCurrentEgoManifestForZone:(NSString*)zone
1047 __block CKKSEgoManifest* manifest = nil;
1048 [self.egoHelper performWithEgoPeerID:^(NSString * _Nullable egoPeerID, NSError * _Nullable error) {
1050 ckkserrorwithzonename("ckksmanifest", zone, "Error getting peer ID: %@", error);
1054 ckkserrorwithzonename("ckksmanifest", zone, "can't get ego peer ID right now - the device probably hasn't been unlocked yet");
1058 NSDictionary* whereDict = @{ @"ckzone" : zone, @"signerID" : egoPeerID };
1059 [self queryMaxValueForField:@"gencount" inTable:self.sqlTable where:whereDict columns:self.sqlColumns processRow:^(NSDictionary* row) {
1060 manifest = [self fromDatabaseRow:row];
1067 + (nullable instancetype)newFakeManifestForZone:(NSString*)zone withItemRecords:(NSArray<CKRecord*>*)itemRecords currentItems:(NSDictionary*)currentItems signerID:(NSString*)signerID keyPair:(SFECKeyPair*)keyPair error:(NSError**)error
1069 CKKSManifestInjectionPointHelper* helper = [[CKKSManifestInjectionPointHelper alloc] initWithPeerID:signerID keyPair:keyPair isEgoPeer:NO];
1070 CKKSEgoManifest* manifest = [self newManifestForZone:zone withItems:@[] peerManifestIDs:@[] currentItems:currentItems error:error helper:helper];
1071 manifest.signerID = signerID;
1072 manifest.helper = helper;
1073 [manifest updateWithNewOrChangedRecords:itemRecords deletedRecordIDs:@[]];
1077 + (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error
1079 return [self newManifestForZone:zone withItems:items peerManifestIDs:peerManifestIDs currentItems:currentItems error:error helper:self.egoHelper];
1082 + (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error helper:(CKKSManifestInjectionPointHelper*)helper
1084 __block NSError* localError = nil;
1085 NSArray* leafRecords = [self leafRecordsForItems:items manifestName:nil zone:zone];
1086 NSData* digestValue = [self digestValueForLeafRecords:leafRecords];
1088 NSInteger generationCount = [self greatestKnownGenerationCount] + 1;
1090 __block CKKSEgoManifest* result = nil;
1091 [helper performWithEgoPeerID:^(NSString* _Nullable egoPeerID, NSError* _Nullable err) {
1095 else if (egoPeerID) {
1096 result = [[self alloc] initWithDigestValue:digestValue zone:zone generationCount:generationCount leafRecords:leafRecords peerManifestIDs:peerManifestIDs currentItems:currentItems futureData:[NSDictionary dictionary] signatures:nil signerID:egoPeerID schema:__thisBuildsSchema];
1099 localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest because egoPeerID is nil"}];
1103 if (!result && !localError) {
1104 localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest"}];
1107 *error = localError;
1113 + (instancetype)fromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
1114 CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
1119 // Try to load leaf records
1120 if(![manifest loadLeafRecords:manifest.zoneID.zoneName error:error]) {
1127 + (instancetype)tryFromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
1128 CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
1133 // Try to load leaf records
1134 // Failing to load leaf records on a manifest that exists is an error, even in tryFromDatabaseWhere.
1135 if(![manifest loadLeafRecords:manifest.zoneID.zoneName error:error]) {
1142 - (bool)loadLeafRecords:(NSString*)ckzone error:(NSError * __autoreleasing *)error {
1143 NSMutableArray* leafRecords = [[NSMutableArray alloc] initWithCapacity:self.leafRecordIDs.count];
1144 for (NSString* leafID in self.leafRecordIDs) {
1145 CKKSEgoManifestLeafRecord* leafRecord = [CKKSEgoManifestLeafRecord fromDatabaseWhere:@{@"uuid" : [CKKSManifestLeafRecord leafUUIDForRecordID:leafID], @"ckzone" : ckzone} error:error];
1147 [leafRecords addObject:leafRecord];
1149 secerror("ckksmanifest: error loading leaf record from database: %@", error ? *error : nil);
1154 self->_leafRecords = leafRecords;
1158 + (NSDictionary*)generateSignaturesWithHelper:(CKKSManifestInjectionPointHelper*)helper derData:(NSData*)manifestDerData error:(NSError**)error
1160 __block NSData* signature = nil;
1161 __block NSError* localError = nil;
1162 [helper performWithSigningKey:^(SFECKeyPair* _Nullable signingKey, NSError* _Nullable err) {
1169 SFEC_X962SigningOperation* signingOperation = [self signatureOperation];
1170 SFSignedData* signedData = [signingOperation sign:manifestDerData withKey:signingKey error:&localError];
1171 signature = signedData.signature;
1176 *error = localError;
1179 return signature ? @{CKKSManifestEC384SignatureKey : signature} : nil;
1182 - (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
1184 NSMutableArray* leafRecordIDs = [[NSMutableArray alloc] initWithCapacity:leafRecords.count];
1185 for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
1186 [leafRecordIDs addObject:leafRecord.CKRecordName];
1189 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]]) {
1190 _leafRecords = leafRecords.copy;
1196 - (void)updateWithNewOrChangedRecords:(NSArray<CKRecord*>*)newOrChangedRecords deletedRecordIDs:(NSArray<CKRecordID*>*)deletedRecordIDs
1198 if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
1202 for (CKRecordID* deletedRecord in deletedRecordIDs) {
1203 NSString* deletedUUID = deletedRecord.recordName;
1204 CKKSEgoManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:deletedUUID];
1205 [leafRecord deleteItemWithUUID:deletedUUID];
1208 for (CKRecord* record in newOrChangedRecords) {
1209 CKKSEgoManifestLeafRecord* leafRecord = (CKKSEgoManifestLeafRecord*)[self leafRecordForItemUUID:record.recordID.recordName];
1210 [leafRecord addOrUpdateRecord:record];
1214 _generationCount = [self.class greatestKnownGenerationCount] + 1;
1217 - (void)setCurrentItemUUID:(NSString*)newCurrentItemUUID forIdentifier:(NSString*)currentPointerIdentifier
1219 _currentItemsDict[currentPointerIdentifier] = newCurrentItemUUID;
1221 _generationCount = [self.class greatestKnownGenerationCount] + 1;
1224 - (CKKSEgoManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
1226 NSUInteger leafBucket = LeafBucketIndexForUUID(uuid);
1227 if(_leafRecords.count > leafBucket) {
1228 return _leafRecords[leafBucket];
1234 - (NSArray<CKKSManifestLeafRecord*>*)leafRecords
1236 return _leafRecords;
1239 - (NSArray<CKRecord*>*)allCKRecordsWithZoneID:(CKRecordZoneID*)zoneID
1241 NSMutableArray* records = [[NSMutableArray alloc] initWithCapacity:_leafRecords.count + 1];
1242 [records addObject:[self CKRecordWithZoneID:zoneID]];
1244 for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
1245 [records addObject:[leafRecord CKRecordWithZoneID:zoneID]];
1251 - (bool)saveToDatabase:(NSError**)error
1253 bool result = [super saveToDatabase:error];
1255 for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
1256 result &= [leafRecord saveToDatabase:error];
1263 - (NSDictionary*)signatures
1265 if (!_signaturesDict) {
1266 _signaturesDict = [self.class generateSignaturesWithHelper:self.helper derData:self.derData error:nil];
1269 return _signaturesDict;
1272 - (void)setSignatures:(NSDictionary*)signatures
1274 _signaturesDict = signatures;
1277 - (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
1279 CKKSManifestInjectionPointHelper* helper = __helpersDict[signerID];
1280 return helper ?: __egoHelper;
1285 @implementation CKKSManifestInjectionPointHelper {
1287 SFECKeyPair* _keyPair;
1290 + (void)registerHelper:(CKKSManifestInjectionPointHelper*)helper forPeer:(NSString*)peerID
1292 if (!__helpersDict) {
1293 __helpersDict = [[NSMutableDictionary alloc] init];
1296 __helpersDict[peerID] = helper;
1299 + (void)registerEgoPeerID:(NSString*)egoPeerID keyPair:(SFECKeyPair*)keyPair
1301 __egoHelper = [[self alloc] initWithPeerID:egoPeerID keyPair:keyPair isEgoPeer:YES];
1304 + (BOOL)ignoreChanges
1306 return __ignoreChanges;
1309 + (void)setIgnoreChanges:(BOOL)ignoreChanges
1311 __ignoreChanges = ignoreChanges ? YES : NO;
1314 - (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer
1316 if (self = [super init]) {
1323 [self.class registerHelper:self forPeer:peerID];
1330 - (NSString*)description
1332 return [NSString stringWithFormat:@"%@ peerID: (%@)", [super description], _peerID];
1335 - (void)performWithSigningKey:(void (^)(SFECKeyPair* _Nullable signingKey, NSError* _Nullable error))handler
1337 if (s_accountInfo) {
1338 if(s_accountInfo.setupError) {
1339 handler(nil, s_accountInfo.setupError);
1341 handler(s_accountInfo.signingKey, nil);
1344 else if (_keyPair) {
1345 handler(_keyPair, nil);
1348 SOSCCPerformWithOctagonSigningKey(^(SecKeyRef signingSecKey, CFErrorRef err) {
1349 SFECKeyPair* key = nil;
1350 if (!err && signingSecKey) {
1351 key = [[SFECKeyPair alloc] initWithSecKey:signingSecKey];
1354 handler(key, (__bridge NSError*)err);
1359 - (void)performWithEgoPeerID:(void (^)(NSString* _Nullable egoPeerID, NSError* _Nullable error))handler
1361 if (s_accountInfo) {
1362 if(s_accountInfo.setupError) {
1363 handler(nil, s_accountInfo.setupError);
1365 handler(s_accountInfo.egoPeerID, nil);
1369 handler(_peerID, nil);
1372 NSError* error = nil;
1373 SOSPeerInfoRef egoPeerInfo = SOSCCCopyMyPeerInfo(NULL);
1374 NSString* egoPeerID = egoPeerInfo ? (__bridge NSString*)SOSPeerInfoGetPeerID(egoPeerInfo) : nil;
1375 handler(egoPeerID, error);
1376 CFReleaseNull(egoPeerInfo);
1380 - (void)performWithPeerVerifyingKeys:(void (^)(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error))handler
1382 if (s_accountInfo) {
1383 if(s_accountInfo.setupError) {
1384 handler(nil, s_accountInfo.setupError);
1386 handler(s_accountInfo.peerVerifyingKeys, nil);
1389 else if (__egoHelper || __helpersDict) {
1390 NSMutableDictionary* verifyingKeys = [[NSMutableDictionary alloc] init];
1391 [__helpersDict enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull peer, CKKSManifestInjectionPointHelper* _Nonnull helper, BOOL* _Nonnull stop) {
1392 verifyingKeys[peer] = helper.keyPair.publicKey;
1394 if (__egoHelper.keyPair) {
1395 verifyingKeys[__egoHelper.peerID] = __egoHelper.keyPair.publicKey;
1397 handler(verifyingKeys, nil);
1400 CFErrorRef error = NULL;
1401 NSMutableDictionary* peerKeys = [NSMutableDictionary dictionary];
1402 CFArrayRef peerInfos = SOSCCCopyValidPeerPeerInfo(&error);
1403 if (!peerInfos || error) {
1404 handler(nil, (__bridge NSError*)error);
1405 CFReleaseNull(peerInfos);
1406 CFReleaseNull(error);
1410 CFArrayForEach(peerInfos, ^(const void* peerInfoPtr) {
1411 SOSPeerInfoRef peerInfo = (SOSPeerInfoRef)peerInfoPtr;
1412 CFErrorRef blockError = NULL;
1413 SecKeyRef secPublicKey = SOSPeerInfoCopyOctagonSigningPublicKey(peerInfo, &blockError);
1414 if (!secPublicKey || error) {
1415 CFReleaseNull(blockError);
1419 SFECPublicKey* publicKey = [[SFECPublicKey alloc] initWithSecKey:secPublicKey];
1420 CFReleaseNull(secPublicKey);
1421 NSString* peerID = (__bridge NSString*)SOSPeerInfoGetPeerID(peerInfo);
1422 peerKeys[peerID] = publicKey;
1425 handler(peerKeys, nil);
1426 CFReleaseNull(peerInfos);
1430 - (SFECKeyPair*)keyPair
1442 @implementation CKKSAccountInfo
1444 @synthesize signingKey = _signingKey;
1445 @synthesize peerVerifyingKeys = _peerVerifyingKeys;
1446 @synthesize egoPeerID = _egoPeerID;