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