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