]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSOutgoingQueueEntry.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / ckks / CKKSOutgoingQueueEntry.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 #include <AssertMacros.h>
25
26 #import <Foundation/Foundation.h>
27
28 #import "CKKSKeychainView.h"
29
30 #include <Security/SecItemPriv.h>
31
32 #include <utilities/SecDb.h>
33 #include <securityd/SecDbItem.h>
34 #include <securityd/SecItemSchema.h>
35
36 #if OCTAGON
37
38 #import <CloudKit/CloudKit.h>
39 #import "CKKSOutgoingQueueEntry.h"
40 #import "CKKSItemEncrypter.h"
41 #import "CKKSKey.h"
42
43
44 @implementation CKKSOutgoingQueueEntry
45
46 - (NSString*)description {
47 return [NSString stringWithFormat: @"<%@(%@): %@ %@ (%@)>",
48 NSStringFromClass([self class]),
49 self.item.zoneID.zoneName,
50 self.action,
51 self.item.uuid,
52 self.state];
53 }
54
55 - (instancetype) initWithCKKSItem:(CKKSItem*) item
56 action: (NSString*) action
57 state: (NSString*) state
58 waitUntil: (NSDate*) waitUntil
59 accessGroup: (NSString*) accessgroup
60 {
61 if((self = [super init])) {
62 _item = item;
63 _action = action;
64 _state = state;
65 _accessgroup = accessgroup;
66 _waitUntil = waitUntil;
67 }
68
69 return self;
70 }
71
72 - (BOOL)isEqual: (id) object {
73 if(![object isKindOfClass:[CKKSOutgoingQueueEntry class]]) {
74 return NO;
75 }
76
77 CKKSOutgoingQueueEntry* obj = (CKKSOutgoingQueueEntry*) object;
78
79 return ([self.item isEqual: obj.item] &&
80 [self.action isEqual: obj.action] &&
81 [self.state isEqual: obj.state] &&
82 ((self.waitUntil == nil && obj.waitUntil == nil) || (fabs([self.waitUntil timeIntervalSinceDate: obj.waitUntil]) < 1)) &&
83 [self.accessgroup isEqual: obj.accessgroup] &&
84 true) ? YES : NO;
85 }
86
87 + (instancetype)withItem: (SecDbItemRef) item action: (NSString*) action ckks:(CKKSKeychainView*) ckks error: (NSError * __autoreleasing *) error {
88 CFErrorRef cferror = NULL;
89 CKKSKey* key = nil;
90 NSString* uuid = nil;
91 NSString* accessgroup = nil;
92
93 NSInteger newGenerationCount = -1;
94
95
96 NSMutableDictionary* objd = nil;
97
98 key = [ckks keyForItem: item error:error];
99 if(!key) {
100 return nil;
101 }
102
103 objd = (__bridge_transfer NSMutableDictionary*) SecDbItemCopyPListWithMask(item, kSecDbSyncFlag, &cferror);
104 if(!objd) {
105 SecTranslateError(error, cferror);
106 return nil;
107 }
108
109 // Object classes aren't in the item plist, set them specifically
110 [objd setObject: (__bridge NSString*) item->class->name forKey: (__bridge NSString*) kSecClass];
111
112 uuid = (__bridge_transfer NSString*) CFRetain(SecDbItemGetValue(item, &v10itemuuid, &cferror));
113 if(!uuid || cferror) {
114 SecTranslateError(error, cferror);
115 return nil;
116 }
117 if([uuid isKindOfClass:[NSNull class]]) {
118 NSError* localerror = [NSError errorWithDomain:@"securityd"
119 code:CKKSNoUUIDOnItem
120 userInfo:@{NSLocalizedDescriptionKey: @"UUID not found in object"}];
121
122 secerror("ckksitem: couldn't fetch UUID: %@ %@", localerror, item);
123 if(error) {
124 *error = localerror;
125 }
126 return nil;
127 }
128
129 accessgroup = (__bridge_transfer NSString*) CFRetain(SecDbItemGetValue(item, &v6agrp, &cferror));
130 if(!accessgroup || cferror) {
131 SecTranslateError(error, cferror);
132 return nil;
133 }
134 if([accessgroup isKindOfClass:[NSNull class]]) {
135 // That's okay; this is only used for rate limiting.
136 secerror("ckksitem: couldn't fetch accessgroup: %@", item);
137 accessgroup = @"no-group";
138 }
139
140
141 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:uuid zoneID:ckks.zoneID error:error];
142
143 // The action this change should be depends on any existing pending action, if any
144 // Particularly, we need to coalesce (existing action, new action) to:
145 // (add, modify) => add
146 // (add, delete) => no-op
147 // (delete, add) => modify
148 NSString* actualAction = action;
149
150 CKKSOutgoingQueueEntry* existingOQE = [CKKSOutgoingQueueEntry tryFromDatabase:uuid state:SecCKKSStateNew zoneID:ckks.zoneID error:error];
151 if(existingOQE) {
152 if([existingOQE.action isEqual: SecCKKSActionAdd]) {
153 if([action isEqual:SecCKKSActionModify]) {
154 actualAction = SecCKKSActionAdd;
155 } else if([action isEqual:SecCKKSActionDelete]) {
156 // we're deleting an add. If there's a ckme, keep as a delete
157 if(!ckme) {
158 // Otherwise, remove from outgoingqueue and don't make a new OQE.
159 [existingOQE deleteFromDatabase:error];
160 return nil;
161 }
162 }
163 }
164
165 if([existingOQE.action isEqual: SecCKKSActionDelete] && [action isEqual:SecCKKSActionAdd]) {
166 actualAction = SecCKKSActionModify;
167 }
168
169 }
170
171 newGenerationCount = ckme ? ckme.item.generationCount : (NSInteger) 0; // TODO: this is wrong
172
173 // Pull out any unencrypted fields
174 NSNumber* pcsServiceIdentifier = objd[(id)kSecAttrPCSPlaintextServiceIdentifier];
175 objd[(id)kSecAttrPCSPlaintextServiceIdentifier] = nil;
176
177 NSData* pcsPublicKey = objd[(id)kSecAttrPCSPlaintextPublicKey];
178 objd[(id)kSecAttrPCSPlaintextPublicKey] = nil;
179
180 NSData* pcsPublicIdentity = objd[(id)kSecAttrPCSPlaintextPublicIdentity];
181 objd[(id)kSecAttrPCSPlaintextPublicIdentity] = nil;
182
183 CKKSItem* baseitem = [[CKKSItem alloc] initWithUUID:uuid
184 parentKeyUUID:key.uuid
185 zoneID:ckks.zoneID
186 encodedCKRecord:nil
187 encItem:nil
188 wrappedkey:nil
189 generationCount:newGenerationCount
190 encver:currentCKKSItemEncryptionVersion
191 plaintextPCSServiceIdentifier:pcsServiceIdentifier
192 plaintextPCSPublicKey:pcsPublicKey
193 plaintextPCSPublicIdentity:pcsPublicIdentity];
194
195 CKKSItem* encryptedItem = [CKKSItemEncrypter encryptCKKSItem:baseitem
196 dataDictionary:objd
197 updatingCKKSItem:ckme.item
198 parentkey:key
199 error:error];
200
201 if(!encryptedItem) {
202 return nil;
203 }
204
205 return [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:encryptedItem
206 action:actualAction
207 state:SecCKKSStateNew
208 waitUntil:nil
209 accessGroup:accessgroup];
210 }
211
212 #pragma mark - Property access to underlying CKKSItem
213
214 -(NSString*)uuid {
215 return self.item.uuid;
216 }
217
218 -(void)setUuid:(NSString *)uuid {
219 self.item.uuid = uuid;
220 }
221
222 #pragma mark - Database Operations
223
224 + (instancetype) fromDatabase: (NSString*) uuid state: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
225 return [self fromDatabaseWhere: @{@"UUID": CKKSNilToNSNull(uuid), @"state": CKKSNilToNSNull(state), @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error: error];
226 }
227
228 + (instancetype) tryFromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
229 return [self tryFromDatabaseWhere: @{@"UUID": CKKSNilToNSNull(uuid), @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error: error];
230 }
231
232 + (instancetype) tryFromDatabase: (NSString*) uuid state: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
233 return [self tryFromDatabaseWhere: @{@"UUID": CKKSNilToNSNull(uuid), @"state":CKKSNilToNSNull(state), @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error: error];
234 }
235
236 + (NSArray<CKKSOutgoingQueueEntry*>*) fetch:(ssize_t) n state: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
237 return [self fetch:n where: @{@"state":CKKSNilToNSNull(state), @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error:error];
238 }
239
240 + (NSArray<CKKSOutgoingQueueEntry*>*) allInState: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
241 return [self allWhere: @{@"state":CKKSNilToNSNull(state), @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error:error];
242 }
243
244
245 #pragma mark - CKKSSQLDatabaseObject methods
246
247 + (NSString*)sqlTable {
248 return @"outgoingqueue";
249 }
250
251 + (NSArray<NSString*>*)sqlColumns {
252 return [[CKKSItem sqlColumns] arrayByAddingObjectsFromArray: @[@"action", @"state", @"waituntil", @"accessgroup"]];
253 }
254
255 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
256 return @{@"UUID": self.uuid, @"state": self.state, @"ckzone":self.item.zoneID.zoneName};
257 }
258
259 - (NSDictionary<NSString*,NSString*>*)sqlValues {
260 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init];
261
262 NSMutableDictionary* values = [[self.item sqlValues] mutableCopy];
263 values[@"action"] = self.action;
264 values[@"state"] = self.state;
265 values[@"waituntil"] = CKKSNilToNSNull(self.waitUntil ? [dateFormat stringFromDate: self.waitUntil] : nil);
266 values[@"accessgroup"] = self.accessgroup;
267
268 return values;
269 }
270
271
272 + (instancetype)fromDatabaseRow: (NSDictionary*) row {
273 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init];
274
275 return [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:[CKKSItem fromDatabaseRow: row]
276 action:row[@"action"]
277 state:row[@"state"]
278 waitUntil:[row[@"waituntil"] isEqual: [NSNull null]] ? nil : [dateFormat dateFromString: row[@"waituntil"]]
279 accessGroup:row[@"accessgroup"]];
280 }
281
282 + (NSDictionary<NSString*,NSNumber*>*)countsByState:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
283 NSMutableDictionary* results = [[NSMutableDictionary alloc] init];
284
285 [CKKSSQLDatabaseObject queryDatabaseTable: [[self class] sqlTable]
286 where: @{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)}
287 columns: @[@"state", @"count(rowid)"]
288 groupBy: @[@"state"]
289 orderBy:nil
290 limit: -1
291 processRow: ^(NSDictionary* row) {
292 results[row[@"state"]] = [NSNumber numberWithInteger: [row[@"count(rowid)"] integerValue]];
293 }
294 error: error];
295 return results;
296 }
297
298 @end
299
300 #endif