]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSManifest.m
Security-58286.31.2.tar.gz
[apple/security.git] / keychain / ckks / CKKSManifest.m
1 /*
2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import "CKKSManifest.h"
27 #import "CKKSManifestLeafRecord.h"
28 #import "CKKS.h"
29 #import "CKKSItem.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>
42
43 NSString* const CKKSManifestZoneKey = @"zone";
44 NSString* const CKKSManifestSignerIDKey = @"signerID";
45 NSString* const CKKSManifestGenCountKey = @"gencount";
46
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";
52
53 static NSString* const CKKSManifestEC384SignatureKey = @"CKKSManifestEC384SignatureKey";
54
55 static NSString* const CKKSManifestErrorDomain = @"CKKSManifestErrorDomain";
56
57 #define NUM_MANIFEST_LEAF_RECORDS 72
58 #define BITS_PER_UUID_CHAR 36
59
60 static CKKSManifestInjectionPointHelper* __egoHelper = nil;
61 static NSMutableDictionary<NSString*, CKKSManifestInjectionPointHelper*>* __helpersDict = nil;
62 static BOOL __ignoreChanges = NO;
63
64 enum {
65 CKKSManifestErrorInvalidDigest = 1,
66 CKKSManifestErrorVerifyingKeyNotFound = 2,
67 CKKSManifestErrorManifestGenerationFailed = 3,
68 CKKSManifestErrorCurrentItemUUIDNotFound = 4
69 };
70
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
79 };
80
81 @interface CKKSAccountInfo : NSObject {
82 SFECKeyPair* _signingKey;
83 NSDictionary* _peerVerifyingKeys;
84 NSString* _egoPeerID;
85 NSError* _setupError;
86 }
87
88 @property SFECKeyPair* signingKey;
89 @property NSDictionary* peerVerifyingKeys;
90 @property NSString* egoPeerID;
91 @property NSError* setupError;
92 @end
93
94 static NSDictionary* __thisBuildsSchema = nil;
95 static CKKSAccountInfo* s_accountInfo = nil;
96
97 @interface CKKSManifest () {
98 @package
99 NSData* _derData;
100 NSData* _digestValue;
101 NSUInteger _generationCount;
102 NSString* _signerID;
103 NSString* _zoneName;
104 NSArray* _leafRecordIDs;
105 NSArray* _peerManifestIDs;
106 NSMutableDictionary* _currentItemsDict;
107 NSDictionary* _futureData;
108 NSDictionary* _signaturesDict;
109 NSDictionary* _schema;
110 CKKSManifestInjectionPointHelper* _helper;
111 }
112
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;
120
121 @property (nonatomic, readwrite) NSString* signerID;
122 @property (nonatomic) CKKSManifestInjectionPointHelper* helper;
123
124 + (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords;
125
126 - (void)clearDigest;
127
128 @end
129
130 @interface CKKSPendingManifest () {
131 NSMutableArray* _committedLeafRecordIDs;
132 }
133
134 @property (nonatomic, readonly) NSArray<NSString*>* committedLeafRecordIDs;
135
136 @end
137
138 @interface CKKSEgoManifest () {
139 NSArray* _leafRecords;
140 }
141
142 @property (class, readonly) CKKSManifestInjectionPointHelper* egoHelper;
143
144 @property (nonatomic, readwrite) NSDictionary* signatures;
145
146 @end
147
148 @interface CKKSManifestInjectionPointHelper ()
149
150 - (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer;
151
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;
155
156 @end
157
158 static NSData* ManifestDERData(NSString* zone, NSData* digestValue, NSArray<NSString*>* peerManifestIDs, NSDictionary<NSString*, NSString*>* currentItems, NSUInteger generationCount, NSDictionary* futureFields, NSDictionary* schema, NSError** error)
159 {
160 NSArray* sortedPeerManifestIDs = [peerManifestIDs sortedArrayUsingSelector:@selector(compare:)];
161
162 NSMutableDictionary* manifestDict = [NSMutableDictionary dictionary];
163 manifestDict[CKKSManifestDigestKey] = digestValue;
164 manifestDict[CKKSManifestPeerManifestsKey] = sortedPeerManifestIDs;
165 manifestDict[CKKSManifestCurrentItemsKey] = currentItems;
166 manifestDict[CKKSManifestGenerationCountKey] = [NSNumber numberWithUnsignedInteger:generationCount];
167
168 [futureFields enumerateKeysAndObjectsUsingBlock:^(NSString* futureKey, id futureValue, BOOL* stop) {
169 CKKSManifestFieldType fieldType = [schema[futureKey] integerValue];
170 if (fieldType == CKKSManifestFieldTypeStringRaw) {
171 manifestDict[futureKey] = futureValue;
172 }
173 else if (fieldType == CKKSManifestFieldTypeStringBase64Encoded) {
174 manifestDict[futureKey] = [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:futureValue options:0] encoding:NSUTF8StringEncoding];
175 }
176 else if (fieldType == CKKSManifestFieldTypeDataAsBase64String) {
177 manifestDict[futureKey] = [[NSData alloc] initWithBase64EncodedString:futureValue options:0];
178 }
179 else if (fieldType == CKKSManifestFieldTypeNumber) {
180 manifestDict[futureKey] = futureValue;
181 }
182 else if (fieldType == CKKSManifestFieldTypeArrayRaw) {
183 manifestDict[futureKey] = futureValue;
184 }
185 else if (fieldType == CKKSManifestFieldTypeArrayAsDERBase64String) {
186 manifestDict[futureKey] = (__bridge_transfer NSArray*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
187 }
188 else if (fieldType == CKKSManifestFieldTypeDictionaryAsDERBase64String) {
189 manifestDict[futureKey] = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)[[NSData alloc] initWithBase64EncodedData:futureValue options:0], 0, NULL, NULL);
190 }
191 else {
192 ckkserrorwithzonename("ckksmanifest", zone, "unrecognized field type in future schema: %ld", (long)fieldType);
193 }
194 }];
195
196 CFErrorRef cfError = NULL;
197 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)manifestDict, &cfError);
198 if (cfError) {
199 ckkserrorwithzonename("ckksmanifest", zone, "error creating manifest der data: %@", cfError);
200 if (error) {
201 *error = (__bridge_transfer NSError*)cfError;
202 }
203 return nil;
204 }
205
206 return derData;
207 }
208
209 static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
210 {
211 NSInteger prefixIntegerValue = 0;
212 for (NSInteger characterIndex = 0; characterIndex * BITS_PER_UUID_CHAR < NUM_MANIFEST_LEAF_RECORDS; characterIndex++) {
213 prefixIntegerValue += [uuid characterAtIndex:characterIndex];
214 }
215
216 return prefixIntegerValue % NUM_MANIFEST_LEAF_RECORDS;
217 }
218
219 @implementation CKKSManifest
220
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;
230
231 + (void)initialize
232 {
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) };
243 }
244 }
245
246 + (void)loadDefaults
247 {
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];
253
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;
259
260 if(shouldSync) {
261 SecCKKSEnableSyncManifests();
262 }
263 if(shouldEnforce) {
264 SecCKKSEnableEnforceManifests();
265 }
266 });
267 }
268
269 + (bool)shouldSyncManifests
270 {
271 [self loadDefaults];
272 return SecCKKSSyncManifests();
273 }
274
275 + (bool)shouldEnforceManifests
276 {
277 [self loadDefaults];
278 return SecCKKSEnforceManifests();
279 }
280
281 + (void)performWithAccountInfo:(void (^)(void))action
282 {
283 CKKSAccountInfo* accountInfo = [[CKKSAccountInfo alloc] init];
284
285 [[CKKSEgoManifest egoHelper] performWithSigningKey:^(_SFECKeyPair* signingKey, NSError* error) {
286 accountInfo.signingKey = signingKey;
287 if(error) {
288 secerror("ckksmanifest: cannot get signing key from account: %@", error);
289 if(accountInfo.setupError == nil) {
290 accountInfo.setupError = error;
291 }
292 }
293 }];
294
295 [[CKKSEgoManifest egoHelper] performWithEgoPeerID:^(NSString* egoPeerID, NSError* error) {
296 accountInfo.egoPeerID = egoPeerID;
297
298 if(error) {
299 secerror("ckksmanifest: cannot get ego peer ID from account: %@", error);
300 if(accountInfo.setupError == nil) {
301 accountInfo.setupError = error;
302 }
303 }
304 }];
305
306 [[CKKSEgoManifest egoHelper] performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* peerKeys, NSError* error) {
307 accountInfo.peerVerifyingKeys = peerKeys;
308 if(error) {
309 secerror("ckksmanifest: cannot get peer keys from account: %@", error);
310 if(accountInfo.setupError == nil) {
311 accountInfo.setupError = error;
312 }
313 }
314 }];
315
316 s_accountInfo = accountInfo;
317
318 action();
319
320 s_accountInfo = nil;
321 }
322
323 + (nullable instancetype)tryFromDatabaseWhere:(NSDictionary*)whereDict error:(NSError* __autoreleasing *)error
324 {
325 CKKSManifest* manifest = [super tryFromDatabaseWhere:whereDict error:error];
326 manifest.helper = __helpersDict[manifest.signerID];
327 return manifest;
328 }
329
330 + (nullable instancetype)manifestForZone:(NSString*)zone peerID:(NSString*)peerID error:(NSError**)error
331 {
332 NSDictionary* databaseWhereClause = @{ @"ckzone" : zone, @"signerID" : peerID };
333 return [self tryFromDatabaseWhere:databaseWhereClause error:error];
334 }
335
336 + (nullable instancetype)manifestForRecordName:(NSString*)recordName error:(NSError**)error
337 {
338 return [self tryFromDatabaseWhere:[self whereClauseForRecordName:recordName] error:error];
339 }
340
341 + (nullable instancetype)latestTrustedManifestForZone:(NSString*)zone error:(NSError**)error
342 {
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;
347
348 if (firstGenerationCount > secondGenerationCount) {
349 return NSOrderedDescending;
350 }
351 else if (firstGenerationCount < secondGenerationCount) {
352 return NSOrderedAscending;
353 }
354 else {
355 return NSOrderedSame;
356 }
357 }];
358
359 __block CKKSManifest* result = nil;
360 [manifests enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(CKKSManifest* _Nonnull manifest, NSUInteger index, BOOL* _Nonnull stop) {
361 if ([manifest validateWithError:nil]) {
362 result = manifest;
363 *stop = YES;
364 }
365 }];
366
367 // TODO: add error for when we didn't find anything
368 return result;
369 }
370
371 + (SFEC_X962SigningOperation*)signatureOperation
372 {
373 SFECKeySpecifier* keySpecifier = [[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384];
374 return [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:keySpecifier digestOperation:[[SFSHA384DigestOperation alloc] init]];
375 }
376
377 + (NSData*)digestForData:(NSData*)data
378 {
379 return [SFSHA384DigestOperation digest:data];
380 }
381
382 + (NSData*)digestValueForLeafRecords:(NSArray*)leafRecords
383 {
384 NSMutableData* concatenatedLeafNodeDigestData = [[NSMutableData alloc] init];
385 for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
386 [concatenatedLeafNodeDigestData appendData:leafRecord.digestValue];
387 }
388
389 return [self digestForData:concatenatedLeafNodeDigestData];
390 }
391
392 + (instancetype)manifestForPendingManifest:(CKKSPendingManifest*)pendingManifest
393 {
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];
395 }
396
397 + (instancetype)fromDatabaseRow:(NSDictionary*)row
398 {
399 NSString* digestBase64String = row[@"digest"];
400 NSData* digest = [digestBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0] : nil;
401
402 NSString* zone = row[@"ckzone"];
403 NSUInteger generationCount = [row[@"gencount"] integerValue];
404 NSString* signerID = row[@"signerID"];
405
406 NSString* encodedRecordBase64String = row[@"ckrecord"];
407 NSData* encodedRecord = [encodedRecordBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:encodedRecordBase64String options:0] : nil;
408
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];
413 }
414
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];
419 }
420
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];
425 }
426
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];
431 }
432
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];
437 }
438
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;
443 }
444
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];
446 }
447
448 + (NSArray<NSString*>*)sqlColumns
449 {
450 return @[@"ckzone", @"gencount", @"digest", @"signatures", @"signerID", @"leafIDs", @"peerManifests", @"currentItems", @"futureData", @"schema", @"ckrecord"];
451 }
452
453 + (NSString*)sqlTable
454 {
455 return @"ckmanifest";
456 }
457
458 + (NSUInteger)greatestKnownGenerationCount
459 {
460 __block NSUInteger result = 0;
461 [self queryMaxValueForField:@"gencount" inTable:self.sqlTable where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
462 result = [row[@"gencount"] integerValue];
463 }];
464
465 [CKKSPendingManifest queryMaxValueForField:@"gencount" inTable:[CKKSPendingManifest sqlTable] where:nil columns:@[@"gencount"] processRow:^(NSDictionary* row) {
466 result = MAX(result, (NSUInteger)[row[@"gencount"] integerValue]);
467 }];
468
469 return result;
470 }
471
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
473 {
474 if (self = [super init]) {
475 _digestValue = digestValue;
476 _zoneName = zone;
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;
484
485 if ([peerManifestIDs.firstObject isEqualToString:signerID]) {
486 _peerManifestIDs = peerManifestIDs;
487 }
488 else {
489 NSMutableArray* tempPeerManifests = [[NSMutableArray alloc] initWithObjects:signerID, nil];
490 if (peerManifestIDs) {
491 [tempPeerManifests addObjectsFromArray:peerManifestIDs];
492 }
493 _peerManifestIDs = tempPeerManifests;
494 }
495
496 _helper = helper ?: [self defaultHelperForSignerID:signerID];
497 if (!_helper) {
498 _helper = [[CKKSManifestInjectionPointHelper alloc] init];
499 }
500 }
501
502 return self;
503 }
504
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
506 {
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];
508 }
509
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
511 {
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;
514 }
515
516 return self;
517 }
518
519 - (instancetype)initWithCKRecord:(CKRecord*)record
520 {
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}];
526 return nil;
527 }
528 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:signatureBase64String options:0];
529 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
530 if (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}];
533 return nil;
534 }
535
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);
541 }
542 if (![schemaDict isKindOfClass:[NSDictionary class]]) {
543 schemaDict = __thisBuildsSchema;
544 }
545
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}];
550 return nil;
551 }
552 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0];
553
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"];
566 }
567 else {
568 [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
569 }
570
571 return self;
572 }
573
574 - (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
575 {
576 return __helpersDict[signerID];
577 }
578
579 - (NSDictionary*)signatureDictFromDERData:(NSData*)derData error:(NSError**)error
580 {
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);
585 if (error) {
586 *error = (__bridge_transfer NSError*)localError;
587 }
588 }
589
590 return signaturesDict;
591 }
592
593 - (NSData*)derDataFromSignatureDict:(NSDictionary*)signatureDict error:(NSError**)error
594 {
595 CFErrorRef localError = NULL;
596 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)signatureDict, &localError);
597 if (!derData) {
598 ckkserror("ckksmanifest", self, "failed to encode signatures dict to der with error: %@", localError);
599 if (error) {
600 *error = (__bridge_transfer NSError*)localError;
601 }
602 }
603
604 return derData;
605 }
606
607 - (NSArray*)peerManifestsFromDERData:(NSData*)derData error:(NSError**)error
608 {
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);
613 if (error) {
614 *error = (__bridge_transfer NSError*)localError;
615 }
616 }
617
618 return peerManifests;
619 }
620
621 - (NSData*)derDataFromPeerManifests:(NSArray*)peerManifests error:(NSError**)error
622 {
623 CFErrorRef localError = NULL;
624 NSData* derData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)peerManifests, &localError);
625 if (!derData) {
626 ckkserror("ckksmanifest", self, "failed to encode peer manifests to der with error: %@", localError);
627 if (error) {
628 *error = (__bridge_transfer NSError*)localError;
629 }
630 }
631
632 return derData;
633 }
634
635 - (NSDictionary*)futureDataDictFromRecord:(CKRecord*)record withSchema:(NSDictionary*)cloudSchema
636 {
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];
641 }
642 }];
643
644 return futureData;
645 }
646
647 - (BOOL)updateWithRecord:(CKRecord*)record error:(NSError**)error
648 {
649 if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
650 return YES; // don't set off any alarms here - just pretend we did it
651 }
652
653 NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestSignaturesKey] options:0];
654 NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:error];
655 if (!signaturesDict) {
656 return NO;
657 }
658
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;
663 }
664
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];
672 }
673 _futureData = [[self futureDataDictFromRecord:record withSchema:cloudSchemaDict] copy];
674 _signaturesDict = signaturesDict;
675 _signerID = record[SecCKRecordManifestSignerIDKey];
676 _schema = cloudSchemaDict;
677 self.storedCKRecord = record;
678
679 _derData = nil;
680 return YES;
681 }
682
683 - (NSDictionary<NSString*, NSString*>*)sqlValues
684 {
685 void (^addValueSafelyToDictionaryAndLogIfNil)(NSMutableDictionary*, NSString*, id) = ^(NSMutableDictionary* dictionary, NSString* key, id value) {
686 if (!value) {
687 value = [NSNull null];
688 secerror("CKKSManifest: saving manifest to database but %@ is nil", key);
689 }
690
691 dictionary[key] = value;
692 };
693
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]);
706
707 return sqlValues;
708 }
709
710 - (NSDictionary<NSString*, NSString*>*)whereClauseToFindSelf
711 {
712 return @{ @"ckzone" : CKKSNilToNSNull(_zoneName),
713 @"gencount" : [NSNumber numberWithUnsignedInteger:_generationCount],
714 @"signerID" : CKKSNilToNSNull(_signerID) };
715 }
716
717 - (NSString*)CKRecordName
718 {
719 return [NSString stringWithFormat:@"Manifest:-:%@:-:%@:-:%lu", _zoneName, _signerID, (unsigned long)_generationCount];
720 }
721
722 + (NSDictionary*)whereClauseForRecordName:(NSString*)recordName
723 {
724 NSArray* components = [recordName componentsSeparatedByString:@":-:"];
725 if (components.count < 4) {
726 secerror("CKKSManifest: could not parse components from record name: %@", recordName);
727 }
728
729 return @{ @"ckzone" : components[1],
730 @"signerID" : components[2],
731 @"gencount" : components[3] };
732 }
733
734 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID
735 {
736 if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
737 @throw [NSException exceptionWithName:@"WrongCKRecordTypeException" reason:[NSString stringWithFormat:@"CKRecorType (%@) was not %@", record.recordType, SecCKRecordManifestType] userInfo:nil];
738 }
739
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)}];
743 return record;
744 }
745
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];
754
755 [_futureData enumerateKeysAndObjectsUsingBlock:^(NSString* key, id futureField, BOOL* stop) {
756 record[key] = futureField;
757 }];
758
759 [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestUpdateRecord"];
760 return record;
761 }
762
763 - (bool)matchesCKRecord:(CKRecord*)record
764 {
765 if (![record.recordType isEqualToString:SecCKRecordManifestType]) {
766 return false;
767 }
768
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) {
773 return NO;
774 }
775
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];
782 }
783
784 - (void)setFromCKRecord:(CKRecord*)record
785 {
786 NSError* error = nil;
787 if (![self updateWithRecord:record error:&error]) {
788 ckkserror("ckksmanifest", self, "failed to update manifest from CKRecord with error: %@", error);
789 }
790 }
791
792 - (NSData*)derData
793 {
794 if (!_derData) {
795 NSError* error = nil;
796 _derData = ManifestDERData(_zoneName, self.digestValue, _peerManifestIDs, _currentItemsDict, _generationCount, _futureData, _schema, &error);
797 if (error) {
798 ckkserror("ckksmanifest", self, "error encoding manifest into DER: %@", error);
799 _derData = nil;
800 }
801 }
802
803 return _derData;
804 }
805
806 - (BOOL)validateWithError:(NSError**)error
807 {
808 __block BOOL verified = false;
809 NSData* manifestDerData = self.derData;
810 if (manifestDerData) {
811 __block NSError* localError = nil;
812
813 [_helper performWithPeerVerifyingKeys:^(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error) {
814 if(error) {
815 ckkserror("ckksmanifest", self, "Error fetching peer verifying keys: %@", error);
816 }
817 SFECPublicKey* verifyingKey = peerKeys[self->_signerID];
818 if (verifyingKey) {
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;
822
823 }
824 else {
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)}];
829 }
830 }];
831
832 if (error) {
833 *error = localError;
834 }
835 }
836
837 if (verified) {
838 [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestValidateSelf"];
839 }
840 else {
841 [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"CKKSManifestValidateSelf" withAttributes:@{CKKSManifestZoneKey : _zoneName, CKKSManifestSignerIDKey : _signerID, CKKSManifestGenerationCountKey : @(_generationCount)}];
842 }
843
844 return verified;
845 }
846
847 - (BOOL)validateItem:(CKKSItem*)item withError:(NSError**)error
848 {
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]) {
853 return YES;
854 }
855 else if (error) {
856 *error = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorInvalidDigest userInfo:@{NSLocalizedDescriptionKey : @"could not validate item because the digest is invalid"}];
857 }
858
859 return NO;
860 }
861
862 - (BOOL)validateCurrentItem:(CKKSCurrentItemPointer*)currentItem withError:(NSError**)error
863 {
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"}];
867 }
868
869 return result;
870 }
871
872 - (BOOL)itemUUIDExistsInManifest:(NSString*)uuid
873 {
874 CKKSManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:uuid];
875 return leafRecord.recordDigestDict[uuid] != nil;
876 }
877
878 - (BOOL)contentsAreEqualToManifest:(CKKSManifest*)otherManifest
879 {
880 return [_digestValue isEqual:otherManifest.digestValue];
881 }
882
883 - (CKKSManifestLeafRecord*)leafRecordForID:(NSString*)leafRecordID
884 {
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);
889 }
890
891 return leafRecord;
892 }
893
894 - (CKKSManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
895 {
896 NSInteger bucketIndex = LeafBucketIndexForUUID(uuid);
897 NSString* leafRecordID = _leafRecordIDs[bucketIndex];
898 return [self leafRecordForID:leafRecordID];
899 }
900
901 - (void)clearDigest
902 {
903 _digestValue = nil;
904 _derData = nil;
905 _signaturesDict = nil;
906 }
907
908 - (NSData*)digestValue
909 {
910 if (!_digestValue) {
911 _digestValue = [self.class digestValueForLeafRecords:self.leafRecords];
912 }
913
914 return _digestValue;
915 }
916
917 - (NSArray<CKKSManifestLeafRecord*>*)leafRecords
918 {
919 NSMutableArray* leafRecords = [[NSMutableArray alloc] initWithCapacity:_leafRecordIDs.count];
920 for (NSString* recordID in _leafRecordIDs) {
921 CKKSManifestLeafRecord* leafRecord = [self leafRecordForID:recordID];
922 if(leafRecord) {
923 [leafRecords addObject:leafRecord];
924 } else {
925 ckkserror("ckksmanifest", self, "failed to fetch leaf record from CKManifest for %@", recordID);
926 // TODO: auto bug capture?
927 }
928 }
929
930 return leafRecords;
931 }
932
933 - (NSString*)ckRecordType
934 {
935 return SecCKRecordManifestType;
936 }
937
938 - (void)nilAllIvars
939 {
940 _derData = nil;
941 _digestValue = nil;
942 _signerID = nil;
943 _zoneName = nil;
944 _leafRecordIDs = nil;
945 _peerManifestIDs = nil;
946 _currentItemsDict = nil;
947 _futureData = nil;
948 _signaturesDict = nil;
949 _schema = nil;
950 }
951
952 @end
953
954 @implementation CKKSPendingManifest
955
956 @synthesize committedLeafRecordIDs = _committedLeafRecordIDs;
957
958 + (NSString*)sqlTable
959 {
960 return @"pending_manifest";
961 }
962
963 - (BOOL)isReadyToCommit
964 {
965 for (NSString* leafRecordID in self.leafRecordIDs) {
966 if ([CKKSManifestLeafRecord recordExistsForID:leafRecordID] || [CKKSManifestPendingLeafRecord recordExistsForID:leafRecordID]) {
967 continue;
968 }
969 else {
970 ckksinfo("ckksmanifest", self, "Not ready to commit manifest, yet - missing leaf record ID: %@", leafRecordID);
971 return NO;
972 }
973 }
974
975 return YES;
976 }
977
978 - (CKKSManifest*)commitToDatabaseWithError:(NSError**)error
979 {
980 NSError* localError = nil;
981
982 _committedLeafRecordIDs = [[NSMutableArray alloc] init];
983
984 for (NSString* leafRecordID in self.leafRecordIDs) {
985 CKKSManifestPendingLeafRecord* pendingLeaf = [CKKSManifestPendingLeafRecord leafRecordForID:leafRecordID error:&localError];
986 if (pendingLeaf) {
987 CKKSManifestLeafRecord* committedLeaf = [pendingLeaf commitToDatabaseWithError:error];
988 if (committedLeaf) {
989 [_committedLeafRecordIDs addObject:committedLeaf.CKRecordName];
990 }
991 else {
992 return nil;
993 }
994 }
995 else {
996 CKKSManifestLeafRecord* existingLeaf = [CKKSManifestLeafRecord leafRecordForID:leafRecordID error:&localError];
997 if (existingLeaf) {
998 [_committedLeafRecordIDs addObject:existingLeaf.CKRecordName];
999 continue;
1000 }
1001 }
1002
1003 if (localError) {
1004 if (error) {
1005 *error = localError;
1006 }
1007 return nil;
1008 }
1009 }
1010
1011 CKKSManifest* manifest = [CKKSManifest manifestForPendingManifest:self];
1012 if ([manifest saveToDatabase:error]) {
1013 [self deleteFromDatabase:error];
1014 return manifest;
1015 }
1016 else {
1017 return nil;
1018 }
1019 }
1020
1021 @end
1022
1023 @implementation CKKSEgoManifest
1024
1025 + (CKKSManifestInjectionPointHelper*)egoHelper
1026 {
1027 return __egoHelper ?: [[CKKSManifestInjectionPointHelper alloc] init];
1028 }
1029
1030 + (NSArray*)leafRecordsForItems:(NSArray*)items manifestName:(NSString*)manifestName zone:(NSString*)zone
1031 {
1032 NSMutableArray* leafRecords = [[NSMutableArray alloc] init];
1033 for (NSInteger i = 0; i < NUM_MANIFEST_LEAF_RECORDS; i++) {
1034 [leafRecords addObject:[CKKSEgoManifestLeafRecord newLeafRecordInZone:zone]];
1035 }
1036
1037 for (CKKSItem* item in items) {
1038 CKKSEgoManifestLeafRecord* leafRecord = leafRecords[LeafBucketIndexForUUID(item.uuid)];
1039 [leafRecord addOrUpdateRecordUUID:item.uuid withEncryptedItemData:item.encitem];
1040 }
1041
1042 return leafRecords;
1043 }
1044
1045 + (nullable CKKSEgoManifest*)tryCurrentEgoManifestForZone:(NSString*)zone
1046 {
1047 __block CKKSEgoManifest* manifest = nil;
1048 [self.egoHelper performWithEgoPeerID:^(NSString * _Nullable egoPeerID, NSError * _Nullable error) {
1049 if(error) {
1050 ckkserrorwithzonename("ckksmanifest", zone, "Error getting peer ID: %@", error);
1051 return;
1052 }
1053 if (!egoPeerID) {
1054 ckkserrorwithzonename("ckksmanifest", zone, "can't get ego peer ID right now - the device probably hasn't been unlocked yet");
1055 return;
1056 }
1057
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];
1061 }];
1062 }];
1063
1064 return manifest;
1065 }
1066
1067 + (nullable instancetype)newFakeManifestForZone:(NSString*)zone withItemRecords:(NSArray<CKRecord*>*)itemRecords currentItems:(NSDictionary*)currentItems signerID:(NSString*)signerID keyPair:(SFECKeyPair*)keyPair error:(NSError**)error
1068 {
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:@[]];
1074 return manifest;
1075 }
1076
1077 + (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error
1078 {
1079 return [self newManifestForZone:zone withItems:items peerManifestIDs:peerManifestIDs currentItems:currentItems error:error helper:self.egoHelper];
1080 }
1081
1082 + (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error helper:(CKKSManifestInjectionPointHelper*)helper
1083 {
1084 __block NSError* localError = nil;
1085 NSArray* leafRecords = [self leafRecordsForItems:items manifestName:nil zone:zone];
1086 NSData* digestValue = [self digestValueForLeafRecords:leafRecords];
1087
1088 NSInteger generationCount = [self greatestKnownGenerationCount] + 1;
1089
1090 __block CKKSEgoManifest* result = nil;
1091 [helper performWithEgoPeerID:^(NSString* _Nullable egoPeerID, NSError* _Nullable err) {
1092 if (err) {
1093 localError = err;
1094 }
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];
1097 }
1098 else {
1099 localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest because egoPeerID is nil"}];
1100 }
1101 }];
1102
1103 if (!result && !localError) {
1104 localError = [NSError errorWithDomain:CKKSManifestErrorDomain code:CKKSManifestErrorManifestGenerationFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to generate ego manifest"}];
1105 }
1106 if (error) {
1107 *error = localError;
1108 }
1109
1110 return result;
1111 }
1112
1113 + (instancetype)fromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
1114 CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
1115 if(!manifest) {
1116 return nil;
1117 }
1118
1119 // Try to load leaf records
1120 if(![manifest loadLeafRecords:manifest.zoneID.zoneName error:error]) {
1121 return nil;
1122 }
1123
1124 return manifest;
1125 }
1126
1127 + (instancetype)tryFromDatabaseWhere:(NSDictionary *)whereDict error:(NSError * __autoreleasing *)error {
1128 CKKSEgoManifest* manifest = [super fromDatabaseWhere:whereDict error:error];
1129 if(!manifest) {
1130 return nil;
1131 }
1132
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]) {
1136 return nil;
1137 }
1138
1139 return manifest;
1140 }
1141
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];
1146 if (leafRecord) {
1147 [leafRecords addObject:leafRecord];
1148 } else {
1149 secerror("ckksmanifest: error loading leaf record from database: %@", error ? *error : nil);
1150 return false;
1151 }
1152 }
1153
1154 self->_leafRecords = leafRecords;
1155 return true;
1156 }
1157
1158 + (NSDictionary*)generateSignaturesWithHelper:(CKKSManifestInjectionPointHelper*)helper derData:(NSData*)manifestDerData error:(NSError**)error
1159 {
1160 __block NSData* signature = nil;
1161 __block NSError* localError = nil;
1162 [helper performWithSigningKey:^(SFECKeyPair* _Nullable signingKey, NSError* _Nullable err) {
1163 if (err) {
1164 localError = err;
1165 return;
1166 }
1167
1168 if (signingKey) {
1169 SFEC_X962SigningOperation* signingOperation = [self signatureOperation];
1170 SFSignedData* signedData = [signingOperation sign:manifestDerData withKey:signingKey error:&localError];
1171 signature = signedData.signature;
1172 }
1173 }];
1174
1175 if(error) {
1176 *error = localError;
1177 }
1178
1179 return signature ? @{CKKSManifestEC384SignatureKey : signature} : nil;
1180 }
1181
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
1183 {
1184 NSMutableArray* leafRecordIDs = [[NSMutableArray alloc] initWithCapacity:leafRecords.count];
1185 for (CKKSManifestLeafRecord* leafRecord in leafRecords) {
1186 [leafRecordIDs addObject:leafRecord.CKRecordName];
1187 }
1188
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;
1191 }
1192
1193 return self;
1194 }
1195
1196 - (void)updateWithNewOrChangedRecords:(NSArray<CKRecord*>*)newOrChangedRecords deletedRecordIDs:(NSArray<CKRecordID*>*)deletedRecordIDs
1197 {
1198 if ([CKKSManifestInjectionPointHelper ignoreChanges]) {
1199 return;
1200 }
1201
1202 for (CKRecordID* deletedRecord in deletedRecordIDs) {
1203 NSString* deletedUUID = deletedRecord.recordName;
1204 CKKSEgoManifestLeafRecord* leafRecord = [self leafRecordForItemUUID:deletedUUID];
1205 [leafRecord deleteItemWithUUID:deletedUUID];
1206 }
1207
1208 for (CKRecord* record in newOrChangedRecords) {
1209 CKKSEgoManifestLeafRecord* leafRecord = (CKKSEgoManifestLeafRecord*)[self leafRecordForItemUUID:record.recordID.recordName];
1210 [leafRecord addOrUpdateRecord:record];
1211 }
1212
1213 [self clearDigest];
1214 _generationCount = [self.class greatestKnownGenerationCount] + 1;
1215 }
1216
1217 - (void)setCurrentItemUUID:(NSString*)newCurrentItemUUID forIdentifier:(NSString*)currentPointerIdentifier
1218 {
1219 _currentItemsDict[currentPointerIdentifier] = newCurrentItemUUID;
1220 [self clearDigest];
1221 _generationCount = [self.class greatestKnownGenerationCount] + 1;
1222 }
1223
1224 - (CKKSEgoManifestLeafRecord*)leafRecordForItemUUID:(NSString*)uuid
1225 {
1226 NSUInteger leafBucket = LeafBucketIndexForUUID(uuid);
1227 if(_leafRecords.count > leafBucket) {
1228 return _leafRecords[leafBucket];
1229 } else {
1230 return nil;
1231 }
1232 }
1233
1234 - (NSArray<CKKSManifestLeafRecord*>*)leafRecords
1235 {
1236 return _leafRecords;
1237 }
1238
1239 - (NSArray<CKRecord*>*)allCKRecordsWithZoneID:(CKRecordZoneID*)zoneID
1240 {
1241 NSMutableArray* records = [[NSMutableArray alloc] initWithCapacity:_leafRecords.count + 1];
1242 [records addObject:[self CKRecordWithZoneID:zoneID]];
1243
1244 for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
1245 [records addObject:[leafRecord CKRecordWithZoneID:zoneID]];
1246 }
1247
1248 return records;
1249 }
1250
1251 - (bool)saveToDatabase:(NSError**)error
1252 {
1253 bool result = [super saveToDatabase:error];
1254 if (result) {
1255 for (CKKSManifestLeafRecord* leafRecord in _leafRecords) {
1256 result &= [leafRecord saveToDatabase:error];
1257 }
1258 }
1259
1260 return result;
1261 }
1262
1263 - (NSDictionary*)signatures
1264 {
1265 if (!_signaturesDict) {
1266 _signaturesDict = [self.class generateSignaturesWithHelper:self.helper derData:self.derData error:nil];
1267 }
1268
1269 return _signaturesDict;
1270 }
1271
1272 - (void)setSignatures:(NSDictionary*)signatures
1273 {
1274 _signaturesDict = signatures;
1275 }
1276
1277 - (CKKSManifestInjectionPointHelper*)defaultHelperForSignerID:(NSString*)signerID
1278 {
1279 CKKSManifestInjectionPointHelper* helper = __helpersDict[signerID];
1280 return helper ?: __egoHelper;
1281 }
1282
1283 @end
1284
1285 @implementation CKKSManifestInjectionPointHelper {
1286 NSString* _peerID;
1287 SFECKeyPair* _keyPair;
1288 }
1289
1290 + (void)registerHelper:(CKKSManifestInjectionPointHelper*)helper forPeer:(NSString*)peerID
1291 {
1292 if (!__helpersDict) {
1293 __helpersDict = [[NSMutableDictionary alloc] init];
1294 }
1295
1296 __helpersDict[peerID] = helper;
1297 }
1298
1299 + (void)registerEgoPeerID:(NSString*)egoPeerID keyPair:(SFECKeyPair*)keyPair
1300 {
1301 __egoHelper = [[self alloc] initWithPeerID:egoPeerID keyPair:keyPair isEgoPeer:YES];
1302 }
1303
1304 + (BOOL)ignoreChanges
1305 {
1306 return __ignoreChanges;
1307 }
1308
1309 + (void)setIgnoreChanges:(BOOL)ignoreChanges
1310 {
1311 __ignoreChanges = ignoreChanges ? YES : NO;
1312 }
1313
1314 - (instancetype)initWithPeerID:(NSString*)peerID keyPair:(SFECKeyPair*)keyPair isEgoPeer:(BOOL)isEgoPeer
1315 {
1316 if (self = [super init]) {
1317 _peerID = peerID;
1318 _keyPair = keyPair;
1319 if (isEgoPeer) {
1320 __egoHelper = self;
1321 }
1322 else {
1323 [self.class registerHelper:self forPeer:peerID];
1324 }
1325 }
1326
1327 return self;
1328 }
1329
1330 - (NSString*)description
1331 {
1332 return [NSString stringWithFormat:@"%@ peerID: (%@)", [super description], _peerID];
1333 }
1334
1335 - (void)performWithSigningKey:(void (^)(SFECKeyPair* _Nullable signingKey, NSError* _Nullable error))handler
1336 {
1337 if (s_accountInfo) {
1338 if(s_accountInfo.setupError) {
1339 handler(nil, s_accountInfo.setupError);
1340 } else {
1341 handler(s_accountInfo.signingKey, nil);
1342 }
1343 }
1344 else if (_keyPair) {
1345 handler(_keyPair, nil);
1346 }
1347 else {
1348 SOSCCPerformWithOctagonSigningKey(^(SecKeyRef signingSecKey, CFErrorRef err) {
1349 SFECKeyPair* key = nil;
1350 if (!err && signingSecKey) {
1351 key = [[SFECKeyPair alloc] initWithSecKey:signingSecKey];
1352 }
1353
1354 handler(key, (__bridge NSError*)err);
1355 });
1356 }
1357 }
1358
1359 - (void)performWithEgoPeerID:(void (^)(NSString* _Nullable egoPeerID, NSError* _Nullable error))handler
1360 {
1361 if (s_accountInfo) {
1362 if(s_accountInfo.setupError) {
1363 handler(nil, s_accountInfo.setupError);
1364 } else {
1365 handler(s_accountInfo.egoPeerID, nil);
1366 }
1367 }
1368 else if (_peerID) {
1369 handler(_peerID, nil);
1370 }
1371 else {
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);
1377 }
1378 }
1379
1380 - (void)performWithPeerVerifyingKeys:(void (^)(NSDictionary<NSString*, SFECPublicKey*>* _Nullable peerKeys, NSError* _Nullable error))handler
1381 {
1382 if (s_accountInfo) {
1383 if(s_accountInfo.setupError) {
1384 handler(nil, s_accountInfo.setupError);
1385 } else {
1386 handler(s_accountInfo.peerVerifyingKeys, nil);
1387 }
1388 }
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;
1393 }];
1394 if (__egoHelper.keyPair) {
1395 verifyingKeys[__egoHelper.peerID] = __egoHelper.keyPair.publicKey;
1396 }
1397 handler(verifyingKeys, nil);
1398 }
1399 else {
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);
1407 return;
1408 }
1409
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);
1416 return;
1417 }
1418
1419 SFECPublicKey* publicKey = [[SFECPublicKey alloc] initWithSecKey:secPublicKey];
1420 CFReleaseNull(secPublicKey);
1421 NSString* peerID = (__bridge NSString*)SOSPeerInfoGetPeerID(peerInfo);
1422 peerKeys[peerID] = publicKey;
1423 });
1424
1425 handler(peerKeys, nil);
1426 CFReleaseNull(peerInfos);
1427 }
1428 }
1429
1430 - (SFECKeyPair*)keyPair
1431 {
1432 return _keyPair;
1433 }
1434
1435 - (NSString*)peerID
1436 {
1437 return _peerID;
1438 }
1439
1440 @end
1441
1442 @implementation CKKSAccountInfo
1443
1444 @synthesize signingKey = _signingKey;
1445 @synthesize peerVerifyingKeys = _peerVerifyingKeys;
1446 @synthesize egoPeerID = _egoPeerID;
1447
1448 @end
1449
1450 #endif