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