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 "CKKSManifestLeafRecord.h"
27 #import "CKKSManifest.h"
29 #import "utilities/der_plist.h"
30 #import <SecurityFoundation/SFDigestOperation.h>
31 #import <CloudKit/CloudKit.h>
33 @interface CKKSManifestLeafRecord () {
35 NSMutableDictionary* _recordDigestDict;
41 @property (nonatomic, readonly) NSString* zoneName;
42 @property (nonatomic, readonly) NSData* derData;
48 @interface CKKSEgoManifestLeafRecord ()
50 @property (nonatomic, readonly) NSMutableDictionary* recordDigestDict;
54 static NSData* NodeDERData(NSDictionary* recordDigestDict, NSError** error)
56 return (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)recordDigestDict, (CFErrorRef*)(void*)error);
59 static NSDictionary* RecordDigestDictFromDER(NSData* data, NSError** error)
61 return (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)data, 0, NULL, (CFErrorRef*)(void*)error);
64 @implementation CKKSManifestLeafRecord
66 @synthesize zoneName = _zoneName;
67 @synthesize recordDigestDict = _recordDigestDict;
69 + (BOOL)recordExistsForID:(NSString*)recordID
71 __block BOOL result = NO;
73 [CKKSSQLDatabaseObject queryDatabaseTable:self.sqlTable where:@{@"UUID" : recordID} columns:@[@"UUID"] groupBy:nil orderBy:nil limit:1 processRow:^(NSDictionary* row) {
80 + (NSString*)leafUUIDForRecordID:(NSString*)recordID
82 NSArray* components = [recordID componentsSeparatedByString:@":-:"];
83 return components.count > 1 ? components[1] : recordID;
86 + (instancetype)leafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error
88 return [self fromDatabaseWhere:@{@"UUID" : [self leafUUIDForRecordID:recordID]} error:error];
91 + (instancetype)tryLeafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error
93 return [self tryFromDatabaseWhere:@{@"UUID" : [self leafUUIDForRecordID:recordID]} error:error];
97 + (instancetype)leafRecordForPendingRecord:(CKKSManifestPendingLeafRecord*)pendingRecord
99 return [[self alloc] initWithUUID:pendingRecord.uuid digest:pendingRecord.digestValue recordDigestDict:pendingRecord.recordDigestDict zone:pendingRecord.zoneName encodedRecord:pendingRecord.encodedCKRecord];
102 + (instancetype)fromDatabaseRow:(NSDictionary*)row
104 NSString* zone = row[@"ckzone"];
105 NSString* uuid = row[@"UUID"];
107 NSString* digestBase64String = row[@"digest"];
108 NSData* digest = [digestBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0] : nil;
110 NSString* encodedRecordBase64String = row[@"ckrecord"];
111 NSData* encodedRecord = [encodedRecordBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:encodedRecordBase64String options:0] : nil;
113 NSString* entryDigestBase64String = row[@"entryDigests"];
114 NSData* entryDigestData = [entryDigestBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:row[@"entryDigests"] options:0] : nil;
115 NSDictionary* entryDigestsDict = entryDigestData ? RecordDigestDictFromDER(entryDigestData, nil) : @{};
117 return [[self alloc] initWithUUID:uuid digest:digest recordDigestDict:entryDigestsDict zone:zone encodedRecord:encodedRecord];
120 + (NSArray<NSString*>*)sqlColumns
122 return @[@"ckzone", @"UUID", @"digest", @"entryDigests", @"ckrecord"];
125 + (NSString*)sqlTable
127 return @"ckmanifest_leaf";
130 - (NSString*)description
132 return [NSString stringWithFormat:@"%@ %@ records: %lu", [super description], self.uuid, (unsigned long)self.recordDigestDict.allValues.count];
135 - (instancetype)initWithCKRecord:(CKRecord*)record
137 NSError* error = nil;
138 NSData* derData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDERKey] options:0];
139 NSDictionary<NSString*, NSData*>* recordDigestDict = RecordDigestDictFromDER(derData, &error);
140 if (!recordDigestDict) {
141 secerror("failed to decode manifest leaf node DER with error: %@", error);
145 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDigestKey] options:0];
146 return [self initWithUUID:[self.class leafUUIDForRecordID:record.recordID.recordName] digest:digestData recordDigestDict:recordDigestDict zone:record.recordID.zoneID.zoneName];
149 - (instancetype)initWithUUID:(NSString*)uuid digest:(NSData*)digest recordDigestDict:(NSDictionary<NSString*, NSData*>*)recordDigestDict zone:(NSString*)zone
151 if (self = [super init]) {
153 _digestValue = digest;
154 _recordDigestDict = [recordDigestDict mutableCopy];
161 - (instancetype)initWithUUID:(NSString*)uuid digest:(NSData*)digest recordDigestDict:(NSDictionary<NSString*, NSData*>*)recordDigestDict zone:(NSString*)zone encodedRecord:(NSData*)encodedRecord
163 if (self = [self initWithUUID:uuid digest:digest recordDigestDict:recordDigestDict zone:zone]) {
164 self.encodedCKRecord = encodedRecord;
170 - (NSDictionary<NSString*, NSString*>*)sqlValues
172 void (^addValueSafelyToDictionaryAndLogIfNil)(NSMutableDictionary*, NSString*, id) = ^(NSMutableDictionary* dictionary, NSString* key, id value) {
174 value = [NSNull null];
175 secerror("CKKSManifestLeafRecord: saving manifest leaf record to database but %@ is nil", key);
178 dictionary[key] = value;
181 NSMutableDictionary* sqlValues = [[NSMutableDictionary alloc] init];
182 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckzone", _zoneName);
183 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"UUID", _uuid);
184 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"digest", [self.digestValue base64EncodedStringWithOptions:0]);
185 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"entryDigests", [NodeDERData(_recordDigestDict, nil) base64EncodedStringWithOptions:0]);
186 sqlValues[@"ckrecord"] = CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]);
191 - (NSDictionary<NSString*, NSString*>*)whereClauseToFindSelf
193 return @{ @"ckzone" : CKKSNilToNSNull(_zoneName),
194 @"UUID" : CKKSNilToNSNull(_uuid) };
197 - (NSString*)CKRecordName
199 return [NSString stringWithFormat:@"ManifestLeafRecord:-:%@", _uuid];
202 - (NSString*)ckRecordType
204 return SecCKRecordManifestLeafType;
207 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID
209 record[SecCKRecordManifestLeafDERKey] = [self.derData base64EncodedStringWithOptions:0];
210 record[SecCKRecordManifestLeafDigestKey] = [self.digestValue base64EncodedStringWithOptions:0];
214 - (void)setFromCKRecord:(CKRecord*)record
216 NSError* error = nil;
217 NSData* derData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDERKey] options:0];
218 NSDictionary<NSString*, NSData*>* recordDigestDict = RecordDigestDictFromDER(derData, &error);
219 if (!recordDigestDict || error) {
220 secerror("failed to decode manifest leaf node DER with error: %@", error);
224 self.storedCKRecord = record;
225 _uuid = [self.class leafUUIDForRecordID:[self.class leafUUIDForRecordID:record.recordID.recordName]];
226 _digestValue = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDigestKey] options:0];
227 _recordDigestDict = [recordDigestDict mutableCopy];
230 - (bool)matchesCKRecord:(CKRecord*)record
232 if (![record.recordType isEqualToString:SecCKRecordManifestLeafType]) {
236 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDigestKey] options:0];
237 return [digestData isEqual:self.digestValue];
243 NSError* error = nil;
244 _derData = NodeDERData(_recordDigestDict, &error);
245 NSAssert(!error, @"failed to encode manifest leaf node DER with error: %@", error);
251 - (NSData*)digestValue
254 _digestValue = [SFSHA384DigestOperation digest:self.derData];
268 @implementation CKKSManifestPendingLeafRecord
270 + (NSString*)sqlTable
272 return @"pending_manifest_leaf";
275 - (CKKSManifestLeafRecord*)commitToDatabaseWithError:(NSError**)error
277 CKKSManifestLeafRecord* leafRecord = [CKKSManifestLeafRecord leafRecordForPendingRecord:self];
278 if ([leafRecord saveToDatabase:error]) {
279 [self deleteFromDatabase:error];
289 @implementation CKKSEgoManifestLeafRecord
291 @dynamic recordDigestDict;
293 + (instancetype)newLeafRecordInZone:(NSString*)zone
295 return [[self alloc] initWithUUID:[[NSUUID UUID] UUIDString] digest:nil recordDigestDict:@{} zone:zone];
298 - (void)addOrUpdateRecordUUID:(NSString*)uuid withEncryptedItemData:(NSData*)itemData
300 self.recordDigestDict[uuid] = [SFSHA384DigestOperation digest:itemData];
304 - (void)addOrUpdateRecord:(CKRecord*)record
306 [self addOrUpdateRecordUUID:record.recordID.recordName withEncryptedItemData:record[SecCKRecordDataKey]];
309 - (void)deleteItemWithUUID:(NSString*)uuid
311 [self.recordDigestDict removeObjectForKey:uuid];