]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSManifestLeafRecord.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / ckks / CKKSManifestLeafRecord.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 "CKKSManifestLeafRecord.h"
27 #import "CKKSManifest.h"
28 #import "CKKSItem.h"
29 #import "utilities/der_plist.h"
30 #import <SecurityFoundation/SFDigestOperation.h>
31 #import <CloudKit/CloudKit.h>
32
33 @interface CKKSManifestLeafRecord () {
34 NSString* _uuid;
35 NSMutableDictionary* _recordDigestDict;
36 NSString* _zoneName;
37 NSData* _derData;
38 NSData* _digestValue;
39 }
40
41 @property (nonatomic, readonly) NSString* zoneName;
42 @property (nonatomic, readonly) NSData* derData;
43
44 - (void)clearDigest;
45
46 @end
47
48 @interface CKKSEgoManifestLeafRecord ()
49
50 @property (nonatomic, readonly) NSMutableDictionary* recordDigestDict;
51
52 @end
53
54 static NSData* NodeDERData(NSDictionary* recordDigestDict, NSError** error)
55 {
56 return (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFPropertyListRef)recordDigestDict, (CFErrorRef*)(void*)error);
57 }
58
59 static NSDictionary* RecordDigestDictFromDER(NSData* data, NSError** error)
60 {
61 return (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)data, 0, NULL, (CFErrorRef*)(void*)error);
62 }
63
64 @implementation CKKSManifestLeafRecord
65
66 @synthesize zoneName = _zoneName;
67 @synthesize recordDigestDict = _recordDigestDict;
68
69 + (BOOL)recordExistsForID:(NSString*)recordID
70 {
71 __block BOOL result = NO;
72
73 [CKKSSQLDatabaseObject queryDatabaseTable:self.sqlTable where:@{@"UUID" : recordID} columns:@[@"UUID"] groupBy:nil orderBy:nil limit:1 processRow:^(NSDictionary* row) {
74 result = YES;
75 } error:nil];
76
77 return result;
78 }
79
80 + (NSString*)leafUUIDForRecordID:(NSString*)recordID
81 {
82 NSArray* components = [recordID componentsSeparatedByString:@":-:"];
83 return components.count > 1 ? components[1] : recordID;
84 }
85
86 + (instancetype)leafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error
87 {
88 return [self fromDatabaseWhere:@{@"UUID" : [self leafUUIDForRecordID:recordID]} error:error];
89 }
90
91 + (instancetype)tryLeafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error
92 {
93 return [self tryFromDatabaseWhere:@{@"UUID" : [self leafUUIDForRecordID:recordID]} error:error];
94 }
95
96
97 + (instancetype)leafRecordForPendingRecord:(CKKSManifestPendingLeafRecord*)pendingRecord
98 {
99 return [[self alloc] initWithUUID:pendingRecord.uuid digest:pendingRecord.digestValue recordDigestDict:pendingRecord.recordDigestDict zone:pendingRecord.zoneName encodedRecord:pendingRecord.encodedCKRecord];
100 }
101
102 + (instancetype)fromDatabaseRow:(NSDictionary*)row
103 {
104 NSString* zone = row[@"ckzone"];
105 NSString* uuid = row[@"UUID"];
106
107 NSString* digestBase64String = row[@"digest"];
108 NSData* digest = [digestBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0] : nil;
109
110 NSString* encodedRecordBase64String = row[@"ckrecord"];
111 NSData* encodedRecord = [encodedRecordBase64String isKindOfClass:[NSString class]] ? [[NSData alloc] initWithBase64EncodedString:encodedRecordBase64String options:0] : nil;
112
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) : @{};
116
117 return [[self alloc] initWithUUID:uuid digest:digest recordDigestDict:entryDigestsDict zone:zone encodedRecord:encodedRecord];
118 }
119
120 + (NSArray<NSString*>*)sqlColumns
121 {
122 return @[@"ckzone", @"UUID", @"digest", @"entryDigests", @"ckrecord"];
123 }
124
125 + (NSString*)sqlTable
126 {
127 return @"ckmanifest_leaf";
128 }
129
130 - (NSString*)description
131 {
132 return [NSString stringWithFormat:@"%@ %@ records: %lu", [super description], self.uuid, (unsigned long)self.recordDigestDict.allValues.count];
133 }
134
135 - (instancetype)initWithCKRecord:(CKRecord*)record
136 {
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);
142 return nil;
143 }
144
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];
147 }
148
149 - (instancetype)initWithUUID:(NSString*)uuid digest:(NSData*)digest recordDigestDict:(NSDictionary<NSString*, NSData*>*)recordDigestDict zone:(NSString*)zone
150 {
151 if (self = [super init]) {
152 _uuid = uuid;
153 _digestValue = digest;
154 _recordDigestDict = [recordDigestDict mutableCopy];
155 _zoneName = zone;
156 }
157
158 return self;
159 }
160
161 - (instancetype)initWithUUID:(NSString*)uuid digest:(NSData*)digest recordDigestDict:(NSDictionary<NSString*, NSData*>*)recordDigestDict zone:(NSString*)zone encodedRecord:(NSData*)encodedRecord
162 {
163 if (self = [self initWithUUID:uuid digest:digest recordDigestDict:recordDigestDict zone:zone]) {
164 self.encodedCKRecord = encodedRecord;
165 }
166
167 return self;
168 }
169
170 - (NSDictionary<NSString*, NSString*>*)sqlValues
171 {
172 void (^addValueSafelyToDictionaryAndLogIfNil)(NSMutableDictionary*, NSString*, id) = ^(NSMutableDictionary* dictionary, NSString* key, id value) {
173 if (!value) {
174 value = [NSNull null];
175 secerror("CKKSManifestLeafRecord: saving manifest leaf record to database but %@ is nil", key);
176 }
177
178 dictionary[key] = value;
179 };
180
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]);
187
188 return sqlValues;
189 }
190
191 - (NSDictionary<NSString*, NSString*>*)whereClauseToFindSelf
192 {
193 return @{ @"ckzone" : CKKSNilToNSNull(_zoneName),
194 @"UUID" : CKKSNilToNSNull(_uuid) };
195 }
196
197 - (NSString*)CKRecordName
198 {
199 return [NSString stringWithFormat:@"ManifestLeafRecord:-:%@", _uuid];
200 }
201
202 - (NSString*)ckRecordType
203 {
204 return SecCKRecordManifestLeafType;
205 }
206
207 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID
208 {
209 record[SecCKRecordManifestLeafDERKey] = [self.derData base64EncodedStringWithOptions:0];
210 record[SecCKRecordManifestLeafDigestKey] = [self.digestValue base64EncodedStringWithOptions:0];
211 return record;
212 }
213
214 - (void)setFromCKRecord:(CKRecord*)record
215 {
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);
221 return;
222 }
223
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];
228 }
229
230 - (bool)matchesCKRecord:(CKRecord*)record
231 {
232 if (![record.recordType isEqualToString:SecCKRecordManifestLeafType]) {
233 return false;
234 }
235
236 NSData* digestData = [[NSData alloc] initWithBase64EncodedString:record[SecCKRecordManifestLeafDigestKey] options:0];
237 return [digestData isEqual:self.digestValue];
238 }
239
240 - (NSData*)derData
241 {
242 if (!_derData) {
243 NSError* error = nil;
244 _derData = NodeDERData(_recordDigestDict, &error);
245 NSAssert(!error, @"failed to encode manifest leaf node DER with error: %@", error);
246 }
247
248 return _derData;
249 }
250
251 - (NSData*)digestValue
252 {
253 if (!_digestValue) {
254 _digestValue = [SFSHA384DigestOperation digest:self.derData];
255 }
256
257 return _digestValue;
258 }
259
260 - (void)clearDigest
261 {
262 _digestValue = nil;
263 _derData = nil;
264 }
265
266 @end
267
268 @implementation CKKSManifestPendingLeafRecord
269
270 + (NSString*)sqlTable
271 {
272 return @"pending_manifest_leaf";
273 }
274
275 - (CKKSManifestLeafRecord*)commitToDatabaseWithError:(NSError**)error
276 {
277 CKKSManifestLeafRecord* leafRecord = [CKKSManifestLeafRecord leafRecordForPendingRecord:self];
278 if ([leafRecord saveToDatabase:error]) {
279 [self deleteFromDatabase:error];
280 return leafRecord;
281 }
282 else {
283 return nil;
284 }
285 }
286
287 @end
288
289 @implementation CKKSEgoManifestLeafRecord
290
291 @dynamic recordDigestDict;
292
293 + (instancetype)newLeafRecordInZone:(NSString*)zone
294 {
295 return [[self alloc] initWithUUID:[[NSUUID UUID] UUIDString] digest:nil recordDigestDict:@{} zone:zone];
296 }
297
298 - (void)addOrUpdateRecordUUID:(NSString*)uuid withEncryptedItemData:(NSData*)itemData
299 {
300 self.recordDigestDict[uuid] = [SFSHA384DigestOperation digest:itemData];
301 [self clearDigest];
302 }
303
304 - (void)addOrUpdateRecord:(CKRecord*)record
305 {
306 [self addOrUpdateRecordUUID:record.recordID.recordName withEncryptedItemData:record[SecCKRecordDataKey]];
307 }
308
309 - (void)deleteItemWithUUID:(NSString*)uuid
310 {
311 [self.recordDigestDict removeObjectForKey:uuid];
312 [self clearDigest];
313 }
314
315 @end
316
317 #endif