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 static NSString * const manifestLeafRecordNameDelimiter = @":-:";
35 @interface CKKSManifestLeafRecord () {
37 NSMutableDictionary* _recordDigestDict;
43 @property (nonatomic, readonly) NSString* zoneName;
44 @property (nonatomic, readonly) NSData* derData;
50 @interface CKKSEgoManifestLeafRecord ()
52 @property (nonatomic, readonly) NSMutableDictionary* recordDigestDict;
56 static NSData* NodeDERData(NSDictionary* recordDigestDict, NSError** error)
58 return (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)recordDigestDict, (CFErrorRef*)(void*)error);
61 static NSDictionary* RecordDigestDictFromDER(NSData* data, NSError** error)
63 return (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)data, 0, NULL, (CFErrorRef*)(void*)error);
66 @implementation CKKSManifestLeafRecord
68 @synthesize zoneName = _zoneName;
69 @synthesize recordDigestDict = _recordDigestDict;
71 + (BOOL)recordExistsForID:(NSString*)recordID
73 __block BOOL result = NO;
75 [CKKSSQLDatabaseObject queryDatabaseTable:self.sqlTable where:@{@"UUID" : recordID} columns:@[@"UUID"] groupBy:nil orderBy:nil limit:1 processRow:^(NSDictionary* row) {
82 + (NSString*)leafUUIDForRecordID:(NSString*)recordID
84 NSArray* components = [recordID componentsSeparatedByString:manifestLeafRecordNameDelimiter];
85 return components.count > 1 ? components[1] : recordID;
88 + (instancetype)leafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error
90 return [self fromDatabaseWhere:@{@"UUID" : [self leafUUIDForRecordID:recordID]} error:error];
93 + (instancetype)tryLeafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error
95 return [self tryFromDatabaseWhere:@{@"UUID" : [self leafUUIDForRecordID:recordID]} error:error];
99 + (instancetype)leafRecordForPendingRecord:(CKKSManifestPendingLeafRecord*)pendingRecord
101 return [[self alloc] initWithUUID:pendingRecord.uuid digest:pendingRecord.digestValue recordDigestDict:pendingRecord.recordDigestDict zone:pendingRecord.zoneName encodedRecord:pendingRecord.encodedCKRecord];
104 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*, CKKSSQLResult*>*)row
106 NSString* zone = row[@"ckzone"].asString;
107 NSString* uuid = row[@"UUID"].asString;
109 NSData* digest = row[@"digest"].asBase64DecodedData;
111 NSData* encodedRecord = row[@"ckrecord"].asBase64DecodedData;
113 NSData* entryDigestData = row[@"entryDigests"].asBase64DecodedData;
114 NSDictionary* entryDigestsDict = entryDigestData ? RecordDigestDictFromDER(entryDigestData, nil) : @{};
116 return [[self alloc] initWithUUID:uuid digest:digest recordDigestDict:entryDigestsDict zone:zone encodedRecord:encodedRecord];
119 + (NSArray<NSString*>*)sqlColumns
121 return @[@"ckzone", @"UUID", @"digest", @"entryDigests", @"ckrecord"];
124 + (NSString*)sqlTable
126 return @"ckmanifest_leaf";
129 - (NSString*)description
131 return [NSString stringWithFormat:@"%@ %@ records: %lu", [super description], self.uuid, (unsigned long)self.recordDigestDict.allValues.count];
134 - (instancetype)initWithCKRecord:(CKRecord*)record
136 NSError* error = nil;
137 NSData* derData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDERKey] options:0];
138 NSDictionary<NSString*, NSData*>* recordDigestDict = RecordDigestDictFromDER(derData, &error);
139 if (!recordDigestDict) {
140 secerror("failed to decode manifest leaf node DER with error: %@", error);
144 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDigestKey] options:0];
145 return [self initWithUUID:[self.class leafUUIDForRecordID:record.recordID.recordName] digest:digestData recordDigestDict:recordDigestDict zone:record.recordID.zoneID.zoneName];
148 - (instancetype)initWithUUID:(NSString*)uuid digest:(NSData*)digest recordDigestDict:(NSDictionary<NSString*, NSData*>*)recordDigestDict zone:(NSString*)zone
150 if ([uuid containsString:manifestLeafRecordNameDelimiter]) {
151 secerror("uuid contains delimiter: %@", uuid);
155 if (self = [super init]) {
157 _digestValue = digest;
158 _recordDigestDict = [recordDigestDict mutableCopy];
165 - (instancetype)initWithUUID:(NSString*)uuid digest:(NSData*)digest recordDigestDict:(NSDictionary<NSString*, NSData*>*)recordDigestDict zone:(NSString*)zone encodedRecord:(NSData*)encodedRecord
167 if (self = [self initWithUUID:uuid digest:digest recordDigestDict:recordDigestDict zone:zone]) {
168 self.encodedCKRecord = encodedRecord;
174 - (NSDictionary<NSString*, NSString*>*)sqlValues
176 void (^addValueSafelyToDictionaryAndLogIfNil)(NSMutableDictionary*, NSString*, id) = ^(NSMutableDictionary* dictionary, NSString* key, id value) {
178 value = [NSNull null];
179 secerror("CKKSManifestLeafRecord: saving manifest leaf record to database but %@ is nil", key);
182 dictionary[key] = value;
185 NSMutableDictionary* sqlValues = [[NSMutableDictionary alloc] init];
186 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"ckzone", _zoneName);
187 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"UUID", _uuid);
188 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"digest", [self.digestValue base64EncodedStringWithOptions:0]);
189 addValueSafelyToDictionaryAndLogIfNil(sqlValues, @"entryDigests", [NodeDERData(_recordDigestDict, nil) base64EncodedStringWithOptions:0]);
190 sqlValues[@"ckrecord"] = CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]);
195 - (NSDictionary<NSString*, NSString*>*)whereClauseToFindSelf
197 return @{ @"ckzone" : CKKSNilToNSNull(_zoneName),
198 @"UUID" : CKKSNilToNSNull(_uuid) };
201 - (NSString*)CKRecordName
203 return [NSString stringWithFormat:@"ManifestLeafRecord%@%@",
204 manifestLeafRecordNameDelimiter, _uuid];
207 - (NSString*)ckRecordType
209 return SecCKRecordManifestLeafType;
212 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID
214 record[SecCKRecordManifestLeafDERKey] = [self.derData base64EncodedStringWithOptions:0];
215 record[SecCKRecordManifestLeafDigestKey] = [self.digestValue base64EncodedStringWithOptions:0];
219 - (void)setFromCKRecord:(CKRecord*)record
221 NSError* error = nil;
222 NSData* derData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDERKey] options:0];
223 NSDictionary<NSString*, NSData*>* recordDigestDict = RecordDigestDictFromDER(derData, &error);
224 if (!recordDigestDict || error) {
225 secerror("failed to decode manifest leaf node DER with error: %@", error);
229 self.storedCKRecord = record;
230 _uuid = [self.class leafUUIDForRecordID:[self.class leafUUIDForRecordID:record.recordID.recordName]];
231 _digestValue = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDigestKey] options:0];
232 _recordDigestDict = [recordDigestDict mutableCopy];
235 - (bool)matchesCKRecord:(CKRecord*)record
237 if (![record.recordType isEqualToString:SecCKRecordManifestLeafType]) {
241 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDigestKey] options:0];
242 return [digestData isEqual:self.digestValue];
248 NSError* error = nil;
249 _derData = NodeDERData(_recordDigestDict, &error);
250 NSAssert(!error, @"failed to encode manifest leaf node DER with error: %@", error);
256 - (NSData*)digestValue
259 _digestValue = [SFSHA384DigestOperation digest:self.derData];
273 @implementation CKKSManifestPendingLeafRecord
275 + (NSString*)sqlTable
277 return @"pending_manifest_leaf";
280 - (CKKSManifestLeafRecord*)commitToDatabaseWithError:(NSError**)error
282 CKKSManifestLeafRecord* leafRecord = [CKKSManifestLeafRecord leafRecordForPendingRecord:self];
283 if ([leafRecord saveToDatabase:error]) {
284 [self deleteFromDatabase:error];
294 @implementation CKKSEgoManifestLeafRecord
296 @dynamic recordDigestDict;
298 + (instancetype)newLeafRecordInZone:(NSString*)zone
300 return [[self alloc] initWithUUID:[[NSUUID UUID] UUIDString] digest:nil recordDigestDict:@{} zone:zone];
303 - (void)addOrUpdateRecordUUID:(NSString*)uuid withEncryptedItemData:(NSData*)itemData
305 self.recordDigestDict[uuid] = [SFSHA384DigestOperation digest:itemData];
309 - (void)addOrUpdateRecord:(CKRecord*)record
311 [self addOrUpdateRecordUUID:record.recordID.recordName withEncryptedItemData:record[SecCKRecordDataKey]];
314 - (void)deleteItemWithUUID:(NSString*)uuid
316 [self.recordDigestDict removeObjectForKey:uuid];