]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSSQLDatabaseObject.m
Security-59306.140.5.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] == [CKKSSQLWhereValue class]) {
144 CKKSSQLWhereValue* obj = (CKKSSQLWhereValue*)value;
145 [whereClause appendFormat: @"%@%@(?)", key, CKKSSQLWhereComparatorAsString(obj.sqlOp)];
146
147 } else if([value class] == [CKKSSQLWhereColumn class]) {
148 CKKSSQLWhereColumn* obj = (CKKSSQLWhereColumn*)value;
149 [whereClause appendFormat: @"%@%@%@",
150 key,
151 CKKSSQLWhereComparatorAsString(obj.sqlOp),
152 CKKSSQLWhereColumnNameAsString(obj.columnName)];
153
154 } else {
155 [whereClause appendFormat: @"%@=(?)", key];
156 }
157
158 conjunction = true;
159 }];
160 return whereClause;
161 }
162
163 + (NSString*) groupByClause: (NSArray*) columns {
164 if(!columns) {
165 return @"";
166 }
167 NSMutableString * groupByClause = [[NSMutableString alloc] init];
168 __block bool conjunction = false;
169 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) {
170 if(!conjunction) {
171 [groupByClause appendFormat: @" GROUP BY "];
172 } else {
173 [groupByClause appendFormat: @", "];
174 }
175
176 [groupByClause appendFormat: @"%@", column];
177
178 conjunction = true;
179 }];
180 return groupByClause;
181 }
182
183 + (NSString*)orderByClause: (NSArray*) columns {
184 if(!columns || columns.count == 0u) {
185 return @"";
186 }
187 NSMutableString * orderByClause = [[NSMutableString alloc] init];
188 __block bool conjunction = false;
189 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) {
190 if(!conjunction) {
191 [orderByClause appendFormat: @" ORDER BY "];
192 } else {
193 [orderByClause appendFormat: @", "];
194 }
195
196 [orderByClause appendFormat: @"%@", column];
197
198 conjunction = true;
199 }];
200 return orderByClause;
201 }
202
203 + (void)bindWhereClause:(sqlite3_stmt*)stmt whereDict:(NSDictionary*)whereDict cferror:(CFErrorRef*)cferror
204 {
205 __block int whereObjectsSkipped = 0;
206 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) {
207 if([whereDict[key] class] == [CKKSSQLWhereValue class]) {
208 CKKSSQLWhereValue* obj = (CKKSSQLWhereValue*)whereDict[key];
209 SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef)obj.value, cferror);
210 } else if([whereDict[key] class] == [CKKSSQLWhereColumn class]) {
211 // skip
212 whereObjectsSkipped += 1;
213 } else {
214 SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef) whereDict[key], cferror);
215 }
216 }];
217 }
218
219 + (bool) deleteFromTable: (NSString*) table where: (NSDictionary*) whereDict connection:(SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error {
220 __block CFErrorRef cferror = NULL;
221
222 bool (^doWithConnection)(SecDbConnectionRef) = ^bool (SecDbConnectionRef dbconn) {
223 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict];
224
225 NSString * sql = [[NSString alloc] initWithFormat: @"DELETE FROM %@%@;", table, whereClause];
226 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
227 [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror];
228
229 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
230 });
231 });
232 return true;
233 };
234
235 if(dbconn) {
236 doWithConnection(dbconn);
237 } else {
238 kc_with_dbt(true, &cferror, doWithConnection);
239 }
240
241 // Deletes finish in a single step, so if we didn't get an error, the delete 'happened'
242 bool status = (cferror == nil);
243
244 if(error) {
245 *error = (NSError*) CFBridgingRelease(cferror);
246 } else {
247 CFReleaseNull(cferror);
248 }
249
250 return status;
251 }
252
253 + (bool)queryDatabaseTable:(NSString*)table
254 where:(NSDictionary*)whereDict
255 columns:(NSArray*)names
256 groupBy:(NSArray*)groupColumns
257 orderBy:(NSArray*)orderColumns
258 limit:(ssize_t)limit
259 processRow:(void (^)(NSDictionary<NSString*, CKKSSQLResult*>*)) processRow
260 error:(NSError * __autoreleasing *) error {
261 __block CFErrorRef cferror = NULL;
262
263 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbconn) {
264 NSString * columns = [names componentsJoinedByString:@", "];
265 NSString * whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict];
266 NSString * groupByClause = [CKKSSQLDatabaseObject groupByClause: groupColumns];
267 NSString * orderByClause = [CKKSSQLDatabaseObject orderByClause: orderColumns];
268 NSString * limitClause = (limit > 0 ? [NSString stringWithFormat:@" LIMIT %lu", limit] : @"");
269
270 NSString * sql = [[NSString alloc] initWithFormat: @"SELECT %@ FROM %@%@%@%@%@;", columns, table, whereClause, groupByClause, orderByClause, limitClause];
271 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
272 [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror];
273
274 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
275 __block NSMutableDictionary<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init];
276
277 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) {
278 const char * col = (const char *) sqlite3_column_text(stmt, (int)i);
279 row[name] = [[CKKSSQLResult alloc] init:col ? [NSString stringWithUTF8String:col] : nil];
280 }];
281
282 processRow(row);
283 });
284 });
285 return true;
286 });
287
288 bool ret = (cferror == NULL);
289 SecTranslateError(error, cferror);
290 return ret;
291 }
292
293 + (NSString *)quotedString:(NSString *)string
294 {
295 char *quotedMaxField = sqlite3_mprintf("%q", [string UTF8String]);
296 if (quotedMaxField == NULL) {
297 abort();
298 }
299 NSString *rstring = [NSString stringWithUTF8String:quotedMaxField];
300 sqlite3_free(quotedMaxField);
301 return rstring;
302 }
303
304 + (bool)queryMaxValueForField:(NSString*)maxField
305 inTable:(NSString*)table
306 where:(NSDictionary*)whereDict
307 columns:(NSArray*)names
308 processRow:(void (^)(NSDictionary<NSString*, CKKSSQLResult*>*))processRow
309 {
310 __block CFErrorRef cferror = NULL;
311
312 kc_with_dbt(false, &cferror, ^bool(SecDbConnectionRef dbconn) {
313 NSString *quotedMaxField = [self quotedString:maxField];
314 NSString *quotedTable = [self quotedString:table];
315
316 NSMutableArray<NSString *>* quotedNames = [NSMutableArray array];
317 for (NSString *element in names) {
318 [quotedNames addObject:[self quotedString:element]];
319 }
320
321 NSString* columns = [[quotedNames componentsJoinedByString:@", "] stringByAppendingFormat:@", %@", quotedMaxField];
322 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause:whereDict];
323
324 NSString* sql = [[NSString alloc] initWithFormat:@"SELECT %@ FROM %@%@", columns, quotedTable, whereClause];
325 SecDbPrepare(dbconn, (__bridge CFStringRef)sql, &cferror, ^(sqlite3_stmt* stmt) {
326 [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror];
327
328 SecDbStep(dbconn, stmt, &cferror, ^(bool*stop) {
329 __block NSMutableDictionary<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init];
330
331 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) {
332 const char * col = (const char *) sqlite3_column_text(stmt, (int)i);
333 row[name] = [[CKKSSQLResult alloc] init:col ? [NSString stringWithUTF8String:col] : nil];
334 }];
335
336 processRow(row);
337 });
338 });
339
340 return true;
341 });
342
343 bool ret = (cferror == NULL);
344 return ret;
345 }
346
347 #pragma mark - Instance methods
348
349 - (bool) saveToDatabase: (NSError * __autoreleasing *) error {
350 return [self saveToDatabaseWithConnection:nil error: error];
351 }
352
353 - (bool) saveToDatabaseWithConnection: (SecDbConnectionRef) conn error: (NSError * __autoreleasing *) error {
354 // Todo: turn this into a transaction
355
356 NSDictionary* currentWhereClause = [self whereClauseToFindSelf];
357
358 // First, if we were loaded from the database and the where clause has changed, delete the old record.
359 if(self.originalSelfWhereClause && ![self.originalSelfWhereClause isEqualToDictionary: currentWhereClause]) {
360 secdebug("ckkssql", "Primary key changed; removing old row at %@", self.originalSelfWhereClause);
361 if(![CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: self.originalSelfWhereClause connection:conn error: error]) {
362 return false;
363 }
364 }
365
366 bool ok = [CKKSSQLDatabaseObject saveToDatabaseTable: [[self class] sqlTable]
367 row: [self sqlValues]
368 connection: conn
369 error: error];
370
371 if(ok) {
372 secdebug("ckkssql", "Saved %@", self);
373 } else {
374 secdebug("ckkssql", "Couldn't save %@: %@", self, error ? *error : @"unknown");
375 }
376 return ok;
377 }
378
379 - (bool) deleteFromDatabase: (NSError * __autoreleasing *) error {
380 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: [self whereClauseToFindSelf] connection:nil error: error];
381
382 if(ok) {
383 secdebug("ckkssql", "Deleted %@", self);
384 } else {
385 secdebug("ckkssql", "Couldn't delete %@: %@", self, error ? *error : @"unknown");
386 }
387 return ok;
388 }
389
390 + (bool) deleteAll: (NSError * __autoreleasing *) error {
391 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[self sqlTable] where: nil connection:nil error: error];
392
393 if(ok) {
394 secdebug("ckkssql", "Deleted all %@", self);
395 } else {
396 secdebug("ckkssql", "Couldn't delete all %@: %@", self, error ? *error : @"unknown");
397 }
398 return ok;
399 }
400
401 + (instancetype) fromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
402 id ret = [self tryFromDatabaseWhere: whereDict error:error];
403
404 if(!ret && error) {
405 *error = [NSError errorWithDomain:@"securityd"
406 code:errSecItemNotFound
407 userInfo:@{NSLocalizedDescriptionKey:
408 [NSString stringWithFormat: @"%@ does not exist in database where %@", [self class], whereDict]}];
409 }
410
411 return ret;
412 }
413
414 + (instancetype _Nullable) tryFromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
415 __block id ret = nil;
416
417 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
418 where: whereDict
419 columns: [self sqlColumns]
420 groupBy: nil
421 orderBy:nil
422 limit: -1
423 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
424 ret = [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause];
425 }
426 error: error];
427
428 return ret;
429 }
430
431 + (NSArray*) all: (NSError * __autoreleasing *) error {
432 return [self allWhere: nil error:error];
433 }
434
435 + (NSArray*) allWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
436 __block NSMutableArray* items = [[NSMutableArray alloc] init];
437
438 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
439 where: whereDict
440 columns: [self sqlColumns]
441 groupBy: nil
442 orderBy:nil
443 limit: -1
444 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
445 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]];
446 }
447 error: error];
448
449 return items;
450 }
451
452 + (NSArray*)fetch: (size_t)count error: (NSError * __autoreleasing *) error {
453 return [self fetch: count where:nil orderBy:nil error:error];
454 }
455
456 + (NSArray*)fetch: (size_t)count where:(NSDictionary*)whereDict error: (NSError * __autoreleasing *) error {
457 return [self fetch: count where:whereDict orderBy:nil error:error];
458 }
459
460 + (NSArray*)fetch:(size_t)count
461 where:(NSDictionary*)whereDict
462 orderBy:(NSArray*) orderColumns
463 error:(NSError * __autoreleasing *) error {
464 __block NSMutableArray* items = [[NSMutableArray alloc] init];
465
466 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
467 where: whereDict
468 columns: [self sqlColumns]
469 groupBy:nil
470 orderBy:orderColumns
471 limit: (ssize_t) count
472 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
473 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]];
474 }
475 error: error];
476
477 return items;
478 }
479
480 - (instancetype) memoizeOriginalSelfWhereClause {
481 _originalSelfWhereClause = [self whereClauseToFindSelf];
482 return self;
483 }
484
485 #pragma mark - Subclass methods
486
487 + (instancetype)fromDatabaseRow:(NSDictionary<NSString *, CKKSSQLResult*>*)row {
488 @throw [NSException exceptionWithName:NSInternalInconsistencyException
489 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
490 userInfo:nil];
491 }
492
493 + (NSString*) sqlTable {
494 @throw [NSException exceptionWithName:NSInternalInconsistencyException
495 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
496 userInfo:nil];
497 }
498
499 + (NSArray<NSString*>*) sqlColumns {
500 @throw [NSException exceptionWithName:NSInternalInconsistencyException
501 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
502 userInfo:nil];
503 }
504
505 - (NSDictionary<NSString*,NSString*>*) sqlValues {
506 @throw [NSException exceptionWithName:NSInternalInconsistencyException
507 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
508 userInfo:nil];
509 }
510
511 - (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf {
512 @throw [NSException exceptionWithName:NSInternalInconsistencyException
513 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
514 userInfo:nil];
515 }
516
517 - (instancetype)copyWithZone:(NSZone *)zone {
518 CKKSSQLDatabaseObject *dbCopy = [[[self class] allocWithZone:zone] init];
519 dbCopy->_originalSelfWhereClause = _originalSelfWhereClause;
520 return dbCopy;
521 }
522 @end
523
524 NSString* CKKSSQLWhereComparatorAsString(CKKSSQLWhereComparator comparator)
525 {
526 switch(comparator) {
527 case CKKSSQLWhereComparatorEquals:
528 return @"=";
529 case CKKSSQLWhereComparatorNotEquals:
530 return @"<>";
531 case CKKSSQLWhereComparatorGreaterThan:
532 return @">";
533 case CKKSSQLWhereComparatorLessThan:
534 return @"<";
535 }
536 }
537
538 NSString* CKKSSQLWhereColumnNameAsString(CKKSSQLWhereColumnName columnName)
539 {
540 switch(columnName) {
541 case CKKSSQLWhereColumnNameUUID:
542 return @"uuid";
543 case CKKSSQLWhereColumnNameParentKeyUUID:
544 return @"parentKeyUUID";
545 }
546 }
547
548 #pragma mark - CKKSSQLWhereColumn
549
550 @implementation CKKSSQLWhereColumn
551 - (instancetype)initWithOperation:(CKKSSQLWhereComparator)op columnName:(CKKSSQLWhereColumnName)column
552 {
553 if((self = [super init])) {
554 _sqlOp = op;
555 _columnName = column;
556 }
557 return self;
558 }
559 + (instancetype)op:(CKKSSQLWhereComparator)op column:(CKKSSQLWhereColumnName)columnName
560 {
561 return [[CKKSSQLWhereColumn alloc] initWithOperation:op columnName:columnName];
562 }
563 @end
564
565 #pragma mark - CKKSSQLWhereObject
566
567 @implementation CKKSSQLWhereValue
568 - (instancetype)initWithOperation:(CKKSSQLWhereComparator)op value:(NSString*)value
569 {
570 if((self = [super init])) {
571 _sqlOp = op;
572 _value = value;
573 }
574 return self;
575 }
576 + (instancetype)op:(CKKSSQLWhereComparator)op value:(NSString*)value
577 {
578 return [[CKKSSQLWhereValue alloc] initWithOperation:op value:value];
579
580 }
581 @end