]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSSQLDatabaseObject.m
Security-59306.41.2.tar.gz
[apple/security.git] / keychain / ckks / CKKSSQLDatabaseObject.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 #import <Foundation/Foundation.h>
25 #import "CKKSSQLDatabaseObject.h"
26 #include "keychain/securityd/SecItemServer.h"
27
28 #import "keychain/ckks/CKKS.h"
29 #import "CKKSKeychainView.h"
30
31 @interface CKKSSQLResult ()
32 @property (nullable) NSString* stringValue;
33 @end
34
35 @implementation CKKSSQLResult
36 - (instancetype)init:(NSString* _Nullable)value
37 {
38 if((self = [super init])) {
39 _stringValue = value;
40 }
41 return self;
42 }
43
44 - (BOOL)asBOOL
45 {
46 return [self.stringValue boolValue];
47 }
48
49 - (NSInteger)asNSInteger
50 {
51 return [self.stringValue integerValue];
52 }
53
54 - (NSString* _Nullable)asString
55 {
56 return self.stringValue;
57 }
58
59 - (NSNumber* _Nullable)asNSNumberInteger
60 {
61 if(self.stringValue == nil) {
62 return nil;
63 }
64 return [NSNumber numberWithInteger: [self.stringValue integerValue]];
65 }
66
67 - (NSDate* _Nullable)asISO8601Date
68 {
69 if(self.stringValue == nil) {
70 return nil;
71 }
72
73 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init];
74 return [dateFormat dateFromString:self.stringValue];
75 }
76
77 - (NSData* _Nullable)asBase64DecodedData
78 {
79 if(self.stringValue == nil) {
80 return nil;
81 }
82 return [[NSData alloc] initWithBase64EncodedString:self.stringValue options:0];
83 }
84 @end
85
86 @implementation CKKSSQLDatabaseObject
87
88 + (bool) saveToDatabaseTable: (NSString*) table row: (NSDictionary*) row connection: (SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error {
89 __block CFErrorRef cferror = NULL;
90
91 bool (^doWithConnection)(SecDbConnectionRef) = ^bool (SecDbConnectionRef dbconn) {
92 NSString * columns = [row.allKeys componentsJoinedByString:@", "];
93 NSMutableString * values = [[NSMutableString alloc] init];
94 for(NSUInteger i = 0; i < [row.allKeys count]; i++) {
95 if(i != 0) {
96 [values appendString: @",?"];
97 } else {
98 [values appendString: @"?"];
99 }
100 }
101
102 NSString *sql = [[NSString alloc] initWithFormat: @"INSERT OR REPLACE into %@ (%@) VALUES (%@);", table, columns, values];
103
104 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
105 [row.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) {
106 SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) row[key], &cferror);
107 }];
108
109 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
110 // don't do anything, I guess?
111 });
112 });
113
114 return true;
115 };
116
117 if(dbconn) {
118 doWithConnection(dbconn);
119 } else {
120 kc_with_dbt(true, &cferror, doWithConnection);
121 }
122
123 bool ret = cferror == NULL;
124
125 SecTranslateError(error, cferror);
126
127 return ret;
128 }
129
130 + (NSString*) makeWhereClause: (NSDictionary*) whereDict {
131 if(!whereDict) {
132 return @"";
133 }
134 NSMutableString * whereClause = [[NSMutableString alloc] init];
135 __block bool conjunction = false;
136 [whereDict enumerateKeysAndObjectsUsingBlock: ^(NSString* key, NSNumber* value, BOOL* stop) {
137 if(!conjunction) {
138 [whereClause appendFormat: @" WHERE "];
139 } else {
140 [whereClause appendFormat: @" AND "];
141 }
142
143 if([value class] == [CKKSSQLWhereObject class]) {
144 // Use this string verbatim
145 CKKSSQLWhereObject* whereob = (CKKSSQLWhereObject*) value;
146 [whereClause appendFormat: @"%@%@%@", key, whereob.sqlOp, whereob.contents];
147 } else {
148 [whereClause appendFormat: @"%@=(?)", key];
149 }
150
151 conjunction = true;
152 }];
153 return whereClause;
154 }
155
156 + (NSString*) groupByClause: (NSArray*) columns {
157 if(!columns) {
158 return @"";
159 }
160 NSMutableString * groupByClause = [[NSMutableString alloc] init];
161 __block bool conjunction = false;
162 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) {
163 if(!conjunction) {
164 [groupByClause appendFormat: @" GROUP BY "];
165 } else {
166 [groupByClause appendFormat: @", "];
167 }
168
169 [groupByClause appendFormat: @"%@", column];
170
171 conjunction = true;
172 }];
173 return groupByClause;
174 }
175
176 + (NSString*)orderByClause: (NSArray*) columns {
177 if(!columns || columns.count == 0u) {
178 return @"";
179 }
180 NSMutableString * orderByClause = [[NSMutableString alloc] init];
181 __block bool conjunction = false;
182 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) {
183 if(!conjunction) {
184 [orderByClause appendFormat: @" ORDER BY "];
185 } else {
186 [orderByClause appendFormat: @", "];
187 }
188
189 [orderByClause appendFormat: @"%@", column];
190
191 conjunction = true;
192 }];
193 return orderByClause;
194 }
195
196 + (bool) deleteFromTable: (NSString*) table where: (NSDictionary*) whereDict connection:(SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error {
197 __block CFErrorRef cferror = NULL;
198
199 bool (^doWithConnection)(SecDbConnectionRef) = ^bool (SecDbConnectionRef dbconn) {
200 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict];
201
202 NSString * sql = [[NSString alloc] initWithFormat: @"DELETE FROM %@%@;", table, whereClause];
203 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
204 __block int whereObjectsSkipped = 0;
205
206 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) {
207 if([whereDict[key] class] != [CKKSSQLWhereObject class]) {
208 SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef) whereDict[key], &cferror);
209 } else {
210 whereObjectsSkipped += 1;
211 }
212 }];
213
214 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
215 });
216 });
217 return true;
218 };
219
220 if(dbconn) {
221 doWithConnection(dbconn);
222 } else {
223 kc_with_dbt(true, &cferror, doWithConnection);
224 }
225
226 // Deletes finish in a single step, so if we didn't get an error, the delete 'happened'
227 bool status = (cferror == nil);
228
229 if(error) {
230 *error = (NSError*) CFBridgingRelease(cferror);
231 } else {
232 CFReleaseNull(cferror);
233 }
234
235 return status;
236 }
237
238 + (bool)queryDatabaseTable:(NSString*)table
239 where:(NSDictionary*)whereDict
240 columns:(NSArray*)names
241 groupBy:(NSArray*)groupColumns
242 orderBy:(NSArray*)orderColumns
243 limit:(ssize_t)limit
244 processRow:(void (^)(NSDictionary<NSString*, CKKSSQLResult*>*)) processRow
245 error:(NSError * __autoreleasing *) error {
246 __block CFErrorRef cferror = NULL;
247
248 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbconn) {
249 NSString * columns = [names componentsJoinedByString:@", "];
250 NSString * whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict];
251 NSString * groupByClause = [CKKSSQLDatabaseObject groupByClause: groupColumns];
252 NSString * orderByClause = [CKKSSQLDatabaseObject orderByClause: orderColumns];
253 NSString * limitClause = (limit > 0 ? [NSString stringWithFormat:@" LIMIT %lu", limit] : @"");
254
255 NSString * sql = [[NSString alloc] initWithFormat: @"SELECT %@ FROM %@%@%@%@%@;", columns, table, whereClause, groupByClause, orderByClause, limitClause];
256 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
257 __block int whereObjectsSkipped = 0;
258 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) {
259 if([whereDict[key] class] != [CKKSSQLWhereObject class]) {
260 SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef) whereDict[key], &cferror);
261 } else {
262 whereObjectsSkipped += 1;
263 }
264 }];
265
266 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
267 __block NSMutableDictionary<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init];
268
269 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) {
270 const char * col = (const char *) sqlite3_column_text(stmt, (int)i);
271 row[name] = [[CKKSSQLResult alloc] init:col ? [NSString stringWithUTF8String:col] : nil];
272 }];
273
274 processRow(row);
275 });
276 });
277 return true;
278 });
279
280 bool ret = (cferror == NULL);
281 SecTranslateError(error, cferror);
282 return ret;
283 }
284
285 + (NSString *)quotedString:(NSString *)string
286 {
287 char *quotedMaxField = sqlite3_mprintf("%q", [string UTF8String]);
288 if (quotedMaxField == NULL) {
289 abort();
290 }
291 NSString *rstring = [NSString stringWithUTF8String:quotedMaxField];
292 sqlite3_free(quotedMaxField);
293 return rstring;
294 }
295
296 + (bool)queryMaxValueForField:(NSString*)maxField
297 inTable:(NSString*)table
298 where:(NSDictionary*)whereDict
299 columns:(NSArray*)names
300 processRow:(void (^)(NSDictionary<NSString*, CKKSSQLResult*>*))processRow
301 {
302 __block CFErrorRef cferror = NULL;
303
304 kc_with_dbt(false, &cferror, ^bool(SecDbConnectionRef dbconn) {
305 NSString *quotedMaxField = [self quotedString:maxField];
306 NSString *quotedTable = [self quotedString:table];
307
308 NSMutableArray<NSString *>* quotedNames = [NSMutableArray array];
309 for (NSString *element in names) {
310 [quotedNames addObject:[self quotedString:element]];
311 }
312
313 NSString* columns = [[quotedNames componentsJoinedByString:@", "] stringByAppendingFormat:@", %@", quotedMaxField];
314 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause:whereDict];
315
316 NSString* sql = [[NSString alloc] initWithFormat:@"SELECT %@ FROM %@%@", columns, quotedTable, whereClause];
317 SecDbPrepare(dbconn, (__bridge CFStringRef)sql, &cferror, ^(sqlite3_stmt* stmt) {
318 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL* _Nonnull stop) {
319 if ([whereDict[key] class] != [CKKSSQLWhereObject class]) {
320 SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) whereDict[key], &cferror);
321 }
322 }];
323
324 SecDbStep(dbconn, stmt, &cferror, ^(bool*stop) {
325 __block NSMutableDictionary<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init];
326
327 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) {
328 const char * col = (const char *) sqlite3_column_text(stmt, (int)i);
329 row[name] = [[CKKSSQLResult alloc] init:col ? [NSString stringWithUTF8String:col] : nil];
330 }];
331
332 processRow(row);
333 });
334 });
335
336 return true;
337 });
338
339 bool ret = (cferror == NULL);
340 return ret;
341 }
342
343 #pragma mark - Instance methods
344
345 - (bool) saveToDatabase: (NSError * __autoreleasing *) error {
346 return [self saveToDatabaseWithConnection:nil error: error];
347 }
348
349 - (bool) saveToDatabaseWithConnection: (SecDbConnectionRef) conn error: (NSError * __autoreleasing *) error {
350 // Todo: turn this into a transaction
351
352 NSDictionary* currentWhereClause = [self whereClauseToFindSelf];
353
354 // First, if we were loaded from the database and the where clause has changed, delete the old record.
355 if(self.originalSelfWhereClause && ![self.originalSelfWhereClause isEqualToDictionary: currentWhereClause]) {
356 secdebug("ckkssql", "Primary key changed; removing old row at %@", self.originalSelfWhereClause);
357 if(![CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: self.originalSelfWhereClause connection:conn error: error]) {
358 return false;
359 }
360 }
361
362 bool ok = [CKKSSQLDatabaseObject saveToDatabaseTable: [[self class] sqlTable]
363 row: [self sqlValues]
364 connection: conn
365 error: error];
366
367 if(ok) {
368 secdebug("ckkssql", "Saved %@", self);
369 } else {
370 secdebug("ckkssql", "Couldn't save %@: %@", self, error ? *error : @"unknown");
371 }
372 return ok;
373 }
374
375 - (bool) deleteFromDatabase: (NSError * __autoreleasing *) error {
376 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: [self whereClauseToFindSelf] connection:nil error: error];
377
378 if(ok) {
379 secdebug("ckkssql", "Deleted %@", self);
380 } else {
381 secdebug("ckkssql", "Couldn't delete %@: %@", self, error ? *error : @"unknown");
382 }
383 return ok;
384 }
385
386 + (bool) deleteAll: (NSError * __autoreleasing *) error {
387 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[self sqlTable] where: nil connection:nil error: error];
388
389 if(ok) {
390 secdebug("ckkssql", "Deleted all %@", self);
391 } else {
392 secdebug("ckkssql", "Couldn't delete all %@: %@", self, error ? *error : @"unknown");
393 }
394 return ok;
395 }
396
397 + (instancetype) fromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
398 id ret = [self tryFromDatabaseWhere: whereDict error:error];
399
400 if(!ret && error) {
401 *error = [NSError errorWithDomain:@"securityd"
402 code:errSecItemNotFound
403 userInfo:@{NSLocalizedDescriptionKey:
404 [NSString stringWithFormat: @"%@ does not exist in database where %@", [self class], whereDict]}];
405 }
406
407 return ret;
408 }
409
410 + (instancetype _Nullable) tryFromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
411 __block id ret = nil;
412
413 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
414 where: whereDict
415 columns: [self sqlColumns]
416 groupBy: nil
417 orderBy:nil
418 limit: -1
419 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
420 ret = [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause];
421 }
422 error: error];
423
424 return ret;
425 }
426
427 + (NSArray*) all: (NSError * __autoreleasing *) error {
428 return [self allWhere: nil error:error];
429 }
430
431 + (NSArray*) allWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
432 __block NSMutableArray* items = [[NSMutableArray alloc] init];
433
434 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
435 where: whereDict
436 columns: [self sqlColumns]
437 groupBy: nil
438 orderBy:nil
439 limit: -1
440 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
441 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]];
442 }
443 error: error];
444
445 return items;
446 }
447
448 + (NSArray*)fetch: (size_t)count error: (NSError * __autoreleasing *) error {
449 return [self fetch: count where:nil orderBy:nil error:error];
450 }
451
452 + (NSArray*)fetch: (size_t)count where:(NSDictionary*)whereDict error: (NSError * __autoreleasing *) error {
453 return [self fetch: count where:whereDict orderBy:nil error:error];
454 }
455
456 + (NSArray*)fetch:(size_t)count
457 where:(NSDictionary*)whereDict
458 orderBy:(NSArray*) orderColumns
459 error:(NSError * __autoreleasing *) error {
460 __block NSMutableArray* items = [[NSMutableArray alloc] init];
461
462 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
463 where: whereDict
464 columns: [self sqlColumns]
465 groupBy:nil
466 orderBy:orderColumns
467 limit: (ssize_t) count
468 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
469 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]];
470 }
471 error: error];
472
473 return items;
474 }
475
476 - (instancetype) memoizeOriginalSelfWhereClause {
477 _originalSelfWhereClause = [self whereClauseToFindSelf];
478 return self;
479 }
480
481 #pragma mark - Subclass methods
482
483 + (instancetype)fromDatabaseRow:(NSDictionary<NSString *, CKKSSQLResult*>*)row {
484 @throw [NSException exceptionWithName:NSInternalInconsistencyException
485 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
486 userInfo:nil];
487 }
488
489 + (NSString*) sqlTable {
490 @throw [NSException exceptionWithName:NSInternalInconsistencyException
491 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
492 userInfo:nil];
493 }
494
495 + (NSArray<NSString*>*) sqlColumns {
496 @throw [NSException exceptionWithName:NSInternalInconsistencyException
497 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
498 userInfo:nil];
499 }
500
501 - (NSDictionary<NSString*,NSString*>*) sqlValues {
502 @throw [NSException exceptionWithName:NSInternalInconsistencyException
503 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
504 userInfo:nil];
505 }
506
507 - (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf {
508 @throw [NSException exceptionWithName:NSInternalInconsistencyException
509 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
510 userInfo:nil];
511 }
512
513 - (instancetype)copyWithZone:(NSZone *)zone {
514 CKKSSQLDatabaseObject *dbCopy = [[[self class] allocWithZone:zone] init];
515 dbCopy->_originalSelfWhereClause = _originalSelfWhereClause;
516 return dbCopy;
517 }
518 @end
519
520 #pragma mark - CKKSSQLWhereObject
521
522 @implementation CKKSSQLWhereObject
523 - (instancetype)initWithOperation:(NSString*)op string: (NSString*) str {
524 if(self = [super init]) {
525 _sqlOp = op;
526 _contents = str;
527 }
528 return self;
529 }
530
531 + (instancetype)op:(NSString*) op string: (NSString*) str {
532 return [[CKKSSQLWhereObject alloc] initWithOperation:op string: str];
533 }
534
535 + (instancetype)op:(NSString*) op stringValue: (NSString*) str {
536 return [[CKKSSQLWhereObject alloc] initWithOperation:op string:[NSString stringWithFormat:@"'%@'", str]];
537 }
538
539
540 @end