]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTLocalStore.m
Security-58286.200.222.tar.gz
[apple/security.git] / keychain / ot / OTLocalStore.m
1 /*
2 * Copyright (c) 2017 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 #if OCTAGON
24
25 #import <Foundation/Foundation.h>
26 #import <utilities/debugging.h>
27 #import <Prequelite/Prequelite.h>
28 #import <SystemConfiguration/SystemConfiguration.h>
29 #include <utilities/SecFileLocations.h>
30 #import "keychain/ot/OTDefines.h"
31 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
32 #include <MobileGestalt.h>
33 #else
34 #include <AppleSystemInfo/AppleSystemInfo.h>
35 #endif
36
37 #import "OTLocalStore.h"
38 #import "OTBottledPeerSigned.h"
39
40 static NSString* const contextSchema = @"create table if not exists context (contextIDAndDSID text primary key, contextID text, accountDSID text, contextName text, zoneCreated boolean, subscribedToChanges boolean, changeToken blob, egoPeerID text, egoPeerCreationDate date, recoverySigningSPKI text, recoveryEncryptionSPKI text);";
41
42 static NSString* const bottledPeerSchema = @"create table if not exists bp (bottledPeerRecordID text primary key, contextIDAndDSID text, escrowRecordID text, peerID text, spID text, bottle text, escrowSigningSPKI text, peerSigningSPKI text, signatureUsingEscrow text, signatureUsingPeerKey text, encodedRecord text, launched text);";
43
44 static const NSInteger user_version = 0;
45
46 /* Octagon Trust Local Context Record Constants */
47 static NSString* OTCKRecordContextAndDSID = @"contextIDAndDSID";
48 static NSString* OTCKRecordContextID = @"contextID";
49 static NSString* OTCKRecordDSID = @"accountDSID";
50 static NSString* OTCKRecordContextName = @"contextName";
51 static NSString* OTCKRecordZoneCreated = @"zoneCreated";
52 static NSString* OTCKRecordSubscribedToChanges = @"subscribedToChanges";
53 static NSString* OTCKRecordChangeToken = @"changeToken";
54 static NSString* OTCKRecordEgoPeerID = @"egoPeerID";
55 static NSString* OTCKRecordEgoPeerCreationDate = @"egoPeerCreationDate";
56 static NSString* OTCKRecordRecoverySigningSPKI = @"recoverySigningSPKI";
57 static NSString* OTCKRecordRecoveryEncryptionSPKI = @"recoveryEncryptionSPKI";
58 static NSString* OTCKRecordBottledPeerTableEntry = @"bottledPeer";
59
60 /* Octagon Trust Local Peer Record */
61 static NSString* OTCKRecordPeerID = @"peerID";
62 static NSString* OTCKRecordPermanentInfo = @"permanentInfo";
63 static NSString* OTCKRecordStableInfo = @"stableInfo";
64 static NSString* OTCKRecordDynamicInfo = @"dynamicInfo";
65 static NSString* OTCKRecordRecoveryVoucher = @"recoveryVoucher";
66 static NSString* OTCKRecordIsEgoPeer = @"isEgoPeer";
67
68 /* Octagon Trust BottledPeerSchema */
69 static NSString* OTCKRecordEscrowRecordID = @"escrowRecordID";
70 static NSString* OTCKRecordRecordID = @"bottledPeerRecordID";
71 static NSString* OTCKRecordSPID = @"spID";
72 static NSString* OTCKRecordBottle = @"bottle";
73 static NSString* OTCKRecordEscrowSigningSPKI = @"escrowSigningSPKI";
74 static NSString* OTCKRecordPeerSigningSPKI = @"peerSigningSPKI";
75 static NSString* OTCKRecordSignatureFromEscrow = @"signatureUsingEscrow";
76 static NSString* OTCKRecordSignatureFromPeerKey = @"signatureUsingPeerKey";
77 static NSString* OTCKRecordEncodedRecord = @"encodedRecord";
78 static NSString* OTCKRecordLaunched = @"launched";
79
80 /* Octagon Table Names */
81 static NSString* const contextTable = @"context";
82 static NSString* const peerTable = @"peer";
83 static NSString* const bottledPeerTable = @"bp";
84
85 /* Octagon Trust Schemas */
86 static NSString* const octagonZoctagonErrorDomainoneName = @"OctagonTrustZone";
87
88 /* Octagon Cloud Kit defines */
89 static NSString* OTCKContainerName = @"com.apple.security.keychain";
90 static NSString* OTCKZoneName = @"OctagonTrust";
91 static NSString* OTCKRecordName = @"bp-";
92 static NSString* OTCKRecordBottledPeerType = @"OTBottledPeer";
93
94 static NSArray* _Nullable selectAll(PQLResultSet *rs, Class class)
95 {
96 NSMutableArray *arr = [NSMutableArray array];
97 for (id o in [rs enumerateObjectsOfClass:class]) {
98 [arr addObject:o];
99 }
100 if (rs.error) {
101 return nil;
102 }
103 return arr;
104 }
105 #define selectArrays(db, sql, ...) \
106 selectAll([db fetch:sql, ##__VA_ARGS__], [NSArray class])
107
108 #define selectDictionaries(db, sql, ...) \
109 selectAll([db fetch:sql, ##__VA_ARGS__], [NSDictionary class])
110
111
112 @interface NSDictionary (PQLResultSetInitializer) <PQLResultSetInitializer>
113 @end
114 @implementation NSDictionary (PQLResultSetInitializer)
115 - (instancetype)initFromPQLResultSet:(PQLResultSet *)rs
116 error:(NSError **)error
117 {
118 NSUInteger cols = rs.columns;
119 NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:cols];
120
121 for (NSUInteger i = 0; i < cols; i++) {
122 id obj = rs[i];
123 if (obj) {
124 dict[[rs columnNameAtIndex:(int)i]] = obj;
125 }
126 }
127
128 return [self initWithDictionary:dict];
129 }
130 @end
131
132
133 @implementation OTLocalStore
134
135 -(instancetype) initWithContextID:(NSString*)contextID dsid:(NSString*)dsid path:(nullable NSString*)path error:(NSError**)error
136 {
137 self = [super init];
138 if(self){
139 if (!path) {
140 NSURL* urlPath = (__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)@"otdb.db");
141 path = [urlPath path];
142 }
143 _dbPath = [path copy];
144 _pDB = [[PQLConnection alloc] init];
145 _contextID = [contextID copy];
146 _dsid = [dsid copy];
147 _serialQ = dispatch_queue_create("com.apple.security.ot.db", DISPATCH_QUEUE_SERIAL);
148
149 NSError* localError = nil;
150 if(![self openDBWithError:&localError])
151 {
152 secerror("octagon: could not open db: %@", localError);
153 if(error){
154 *error = localError;
155 }
156 return nil;
157 }
158 }
159 return self;
160 }
161
162 - (BOOL) createDirectoryAtPath:(NSString*)path error:(NSError **)error
163 {
164 BOOL success = YES;
165 NSError *localError;
166 NSFileManager *fileManager = [NSFileManager defaultManager];
167
168 if (![fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&localError]) {
169 if (![localError.domain isEqualToString:NSCocoaErrorDomain] || localError.code != NSFileWriteFileExistsError) {
170 success = NO;
171 if(error){
172 *error = localError;
173 }
174 }
175 }
176
177 #if TARGET_OS_IPHONE
178 if (success) {
179 NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&localError];
180 if (![attributes[NSFileProtectionKey] isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) {
181 [fileManager setAttributes:@{ NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication }
182 ofItemAtPath:path error:&localError];
183 }
184 }
185 #endif
186 if (!success) {
187 if (error) *error = localError;
188 }
189 return success;
190 }
191
192 -(BOOL)openDBWithError:(NSError**)error
193 {
194 BOOL result = NO;
195 NSError *localError = nil;
196
197 if(!(result = [_pDB openAtURL:[NSURL URLWithString:_dbPath] sharedCache:NO error:&localError])){
198 secerror("octagon: could not open db: %@", localError);
199 if(error){
200 *error = localError;
201 }
202 return NO;
203 }
204 if(![_pDB execute:bottledPeerSchema]){
205 secerror("octagon: could not create bottled peer schema");
206 if(error){
207 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEntropyCreationFailure userInfo:@{NSLocalizedDescriptionKey: @"could not create bottled peer schema"}];
208 }
209 result = NO;
210 }
211 if(![_pDB execute:contextSchema]){
212 secerror("octagon: could not create contextschema");
213 if(error){
214 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"could not create context schema"}];
215 }
216 result = NO;
217 }
218 if(![_pDB setupPragmas]){
219 secerror("octagon: could not set up db pragmas");
220 if(error){
221 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"could not set up db pragmas"}];
222 }
223 result = NO;
224 }
225 if(![_pDB setUserVersion:user_version]){
226 secerror("octagon: could not set version");
227 if(error){
228 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"could not set version"}];
229 }
230 result = NO;
231 }
232 return result;
233 }
234
235 -(BOOL)closeDBWithError:(NSError**)error
236 {
237 BOOL result = NO;
238 NSError *localError = nil;
239
240 if(!(result =[_pDB close:&localError])){
241 secerror("octagon: could not close db: %@", localError);
242 if(error){
243 *error = localError;
244 }
245 }
246 return result;
247 }
248
249 -(BOOL)isProposedColumnNameInTable:(NSString*)proposedColumnName tableName:(NSString*)tableName
250 {
251 BOOL result = NO;
252
253 if([tableName isEqualToString:contextTable])
254 {
255 if([proposedColumnName isEqualToString:OTCKRecordContextAndDSID]){
256 result = YES;
257 }
258 else if([proposedColumnName isEqualToString:OTCKRecordContextID]){
259 result = YES;
260 }
261 else if([proposedColumnName isEqualToString:OTCKRecordDSID]){
262 result = YES;
263 }
264 else if([proposedColumnName isEqualToString:OTCKRecordContextName]){
265 result = YES;
266 }
267 else if([proposedColumnName isEqualToString:OTCKRecordZoneCreated]){
268 result = YES;
269 }
270 else if([proposedColumnName isEqualToString:OTCKRecordSubscribedToChanges]){
271 result = YES;
272 }
273 else if([proposedColumnName isEqualToString:OTCKRecordChangeToken]){
274 result = YES;
275 }
276 else if([proposedColumnName isEqualToString:OTCKRecordEgoPeerID]){
277 result = YES;
278 }
279 else if([proposedColumnName isEqualToString:OTCKRecordEgoPeerCreationDate]){
280 result = YES;
281 }
282 else if([proposedColumnName isEqualToString:OTCKRecordRecoverySigningSPKI]){
283 result = YES;
284 }
285 else if([proposedColumnName isEqualToString:OTCKRecordRecoveryEncryptionSPKI]){
286 result = YES;
287 }
288 else{
289 secerror("octagon: column name unknown: %@", proposedColumnName);
290 }
291 }
292 else if([tableName isEqualToString:peerTable]){ //not using yet!
293 result = NO;
294 secerror("octagon: not using this table yet!");
295 }
296 else if([tableName isEqualToString:bottledPeerTable])
297 {
298 if([proposedColumnName isEqualToString:OTCKRecordContextAndDSID]){
299 result = YES;
300 }
301 else if([proposedColumnName isEqualToString:OTCKRecordRecordID]){
302 result = YES;
303 }
304 else if([proposedColumnName isEqualToString:OTCKRecordEscrowRecordID]){
305 result = YES;
306 }
307 else if([proposedColumnName isEqualToString:OTCKRecordSPID]){
308 result = YES;
309 }
310 else if([proposedColumnName isEqualToString:OTCKRecordPeerID]){
311 result = YES;
312 }
313 else if([proposedColumnName isEqualToString:OTCKRecordBottle]){
314 result = YES;
315 }
316 else if([proposedColumnName isEqualToString:OTCKRecordSignatureFromEscrow]){
317 result = YES;
318 }
319 else if([proposedColumnName isEqualToString:OTCKRecordSignatureFromPeerKey]){
320 result = YES;
321 }
322 else if([proposedColumnName isEqualToString:OTCKRecordEncodedRecord]){
323 result = YES;
324 }
325 else if([proposedColumnName isEqualToString:OTCKRecordLaunched]){
326 result = YES;
327 }
328 else if([proposedColumnName isEqualToString:OTCKRecordPeerSigningSPKI]){
329 result = YES;
330 }
331 else if([proposedColumnName isEqualToString:OTCKRecordEscrowSigningSPKI]){
332 result = YES;
333 }
334 else{
335 secerror("octagon: column name unknown: %@", proposedColumnName);
336 }
337 }
338 else{
339 secerror("octagon: table name unknown: %@", tableName);
340 }
341 return result;
342 }
343
344 /////
345 // Local Context Record
346 /////
347 -(OTContextRecord* _Nullable)readLocalContextRecordForContextIDAndDSID:(NSString*)contextAndDSID error:(NSError**)error
348 {
349 OTContextRecord* record = [[OTContextRecord alloc]init];
350 NSDictionary* attributes = nil;
351 NSArray *selectArray = nil;
352
353 selectArray = selectDictionaries(_pDB, @"SELECT * from context WHERE contextIDAndDSID == %@;", PQLName(contextAndDSID));
354 if(selectArray && [selectArray count] > 0){
355 attributes = [selectArray objectAtIndex:0];
356 }
357 if(attributes && [attributes count] > 0){
358 record.contextID = attributes[OTCKRecordContextID];
359 record.dsid = attributes[OTCKRecordDSID];
360 record.contextName = attributes[OTCKRecordContextName];
361 record.zoneCreated = (BOOL)attributes[OTCKRecordZoneCreated];
362 record.subscribedToChanges = (BOOL)attributes[OTCKRecordSubscribedToChanges];
363 record.changeToken = attributes[OTCKRecordChangeToken];
364 record.egoPeerID = attributes[OTCKRecordEgoPeerID];
365 record.egoPeerCreationDate = attributes[OTCKRecordEgoPeerCreationDate];
366 record.recoverySigningSPKI = dataFromBase64(attributes[OTCKRecordRecoverySigningSPKI]);
367 record.recoveryEncryptionSPKI = dataFromBase64(attributes[OTCKRecordRecoveryEncryptionSPKI]);
368 }
369 else{
370 secerror("octagon: no context attributes found");
371 if(error){
372 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"no context attributes found"}];
373 }
374 }
375
376 return record;
377 }
378
379 -(BOOL)initializeContextTable:(NSString*)contextID dsid:(NSString*)dsid error:(NSError**)error
380 {
381 BOOL result = NO;
382 NSError* localError = nil;
383 NSString* contextName = nil;
384 #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
385 contextName = (__bridge_transfer NSString *)MGCopyAnswer(kMGQUserAssignedDeviceName, NULL);
386 #else
387 contextName = (__bridge_transfer NSString *)SCDynamicStoreCopyComputerName(NULL, NULL);
388 #endif
389
390 NSDictionary *contextAttributes = @{
391 OTCKRecordContextAndDSID : [NSString stringWithFormat:@"%@-%@", contextID, dsid],
392 OTCKRecordContextID : contextID,
393 OTCKRecordDSID : dsid,
394 OTCKRecordContextName : contextName,
395 OTCKRecordZoneCreated : @(NO),
396 OTCKRecordSubscribedToChanges : @(NO),
397 OTCKRecordChangeToken : [NSData data],
398 OTCKRecordEgoPeerID : @"ego peer id",
399 OTCKRecordEgoPeerCreationDate : [NSDate date],
400 OTCKRecordRecoverySigningSPKI : [NSData data],
401 OTCKRecordRecoveryEncryptionSPKI : [NSData data]};
402
403 result = [self insertLocalContextRecord:contextAttributes error:&localError];
404 if(!result || localError != nil){
405 secerror("octagon: context table init failed: %@", localError);
406 if(error){
407 *error = localError;
408 }
409 }
410 return result;
411 }
412
413 -(BOOL)insertLocalContextRecord:(NSDictionary*)attributes error:(NSError**)error
414 {
415 BOOL result = NO;
416
417 NSString* dsidAndContext = [NSString stringWithFormat:@"%@-%@", attributes[OTCKRecordContextID], attributes[OTCKRecordDSID]];
418 result = [_pDB execute:@"insert into context (contextIDAndDSID, contextID, accountDSID, contextName, zoneCreated, subscribedToChanges, changeToken, egoPeerID, egoPeerCreationDate, recoverySigningSPKI, recoveryEncryptionSPKI) values (%@,%@,%@,%@,%@,%@,%@,%@,%@,%@,%@)",
419 dsidAndContext, attributes[OTCKRecordContextID], attributes[OTCKRecordDSID], attributes[OTCKRecordContextName], attributes[OTCKRecordZoneCreated],
420 attributes[OTCKRecordSubscribedToChanges], attributes[OTCKRecordChangeToken],
421 attributes[OTCKRecordEgoPeerID], attributes[OTCKRecordEgoPeerCreationDate],
422 [attributes[OTCKRecordRecoverySigningSPKI] base64EncodedStringWithOptions:0], [attributes[OTCKRecordRecoveryEncryptionSPKI] base64EncodedStringWithOptions:0]];
423
424
425 if(_pDB.lastError){
426 secerror("octagon: failed to insert local context: %@", _pDB.lastError);
427 if(error){
428 *error = _pDB.lastError;
429 }
430 }
431 return result;
432 }
433
434 -(BOOL)updateLocalContextRecordRowWithContextID:(NSString*)contextIDAndDSID columnName:(NSString*)columnName newValue:(void*)newValue error:(NSError**)error
435 {
436 BOOL result = NO;
437 if([self isProposedColumnNameInTable:columnName tableName:contextTable]){
438 result = [_pDB execute:@"update context set %@ = %@ where contextIDAndDSID == %@",
439 PQLName(columnName), newValue, PQLName(_contextID)];
440 if(!result && error){
441 secerror("octagon: error updating table: %@", _pDB.lastError);
442 *error = _pDB.lastError;
443 }
444 }
445 else{
446 secerror("octagon: failed to update local context record: %@", _pDB.lastError);
447
448 if(error != nil){
449 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorNoColumn userInfo:nil];
450 }
451 }
452 return result;
453 }
454
455 -(BOOL) deleteLocalContext:(NSString*)contextIDAndDSID error:(NSError**)error
456 {
457 BOOL result = NO;
458 secnotice("octagon", "deleting local context: %@", contextIDAndDSID);
459
460 result = [_pDB execute:@"delete from context where contextIDAndDSID == %@",
461 PQLName(contextIDAndDSID)];
462
463 if(!result){
464 secerror("octagon: error updating table: %@", _pDB.lastError);
465 if(error){
466 *error = _pDB.lastError;
467 }
468 }
469 return result;
470 }
471
472 -(BOOL) deleteAllContexts:(NSError**)error
473 {
474 BOOL result = NO;
475 secnotice("octagon", "deleting all local context");
476
477 result = [_pDB execute:@"delete from context"];
478
479 if(!result){
480 secerror("octagon: error updating table: %@", _pDB.lastError);
481 if(error){
482 *error = _pDB.lastError;
483 }
484 }
485 return result;
486 }
487
488 /////
489 // Local Bottled Peer Record
490 /////
491
492 - (BOOL) insertBottledPeerRecord:(OTBottledPeerRecord *)rec
493 escrowRecordID:(NSString *)escrowRecordID
494 error:(NSError**)error
495 {
496 BOOL result;
497
498 result = [_pDB execute:@"insert or replace into bp (bottledPeerRecordID, contextIDAndDSID, escrowRecordID, peerID, spID, bottle, escrowSigningSPKI, peerSigningSPKI, signatureUsingEscrow, signatureUsingPeerKey, encodedRecord, launched) values (%@,%@,%@,%@,%@,%@,%@,%@,%@,%@,%@,%@)",
499 rec.recordName,
500 [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid],
501 escrowRecordID,
502 rec.peerID,
503 rec.spID,
504 [rec.bottle base64EncodedStringWithOptions:0],
505 [rec.escrowedSigningSPKI base64EncodedStringWithOptions:0],
506 [rec.peerSigningSPKI base64EncodedStringWithOptions:0],
507 [rec.signatureUsingEscrowKey base64EncodedStringWithOptions:0],
508 [rec.signatureUsingPeerKey base64EncodedStringWithOptions:0],
509 [rec.encodedRecord base64EncodedStringWithOptions:0],
510 rec.launched];
511
512 if (!result) {
513 secerror("octagon: error inserting bottled peer record: %@", _pDB.lastError);
514 if(error){
515 *error = _pDB.lastError;
516 }
517 }
518 return result;
519 }
520
521 -(BOOL) removeAllBottledPeerRecords:(NSError**)error
522 {
523 BOOL result = NO;
524
525 result = [_pDB execute:@"DELETE from bp WHERE contextIDAndDSID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid]];
526
527 if (!result) {
528 secerror("octagon: error removing bottled peer records: %@", _pDB.lastError);
529 if(error){
530 *error = _pDB.lastError;
531 }
532 }
533 return result;
534 }
535
536 -(BOOL) deleteBottledPeer:(NSString*) recordID
537 error:(NSError**)error
538 {
539 BOOL result = NO;
540
541 result = [_pDB execute:@"DELETE from bp WHERE contextIDAndDSID == %@ AND bottledPeerRecordID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid], recordID];
542
543 if (!result) {
544 secerror("octagon: error removing bottled peer record:%@, error: %@", recordID, _pDB.lastError);
545 if(error){
546 *error = _pDB.lastError;
547 }
548 }
549 return result;
550 }
551
552 -(BOOL) deleteBottledPeersForContextAndDSID:(NSString*) contextIDAndDSID
553 error:(NSError**)error
554 {
555 BOOL result = NO;
556
557 result = [_pDB execute:@"DELETE from bp WHERE contextIDAndDSID == %@;", contextIDAndDSID];
558
559 if (!result) {
560 secerror("octagon: error removing bottled peer record:%@, error: %@", contextIDAndDSID, _pDB.lastError);
561 if(error){
562 *error = _pDB.lastError;
563 }
564 }
565 return result;
566 }
567
568 - (nullable OTBottledPeerRecord *)readLocalBottledPeerRecordWithRecordID:(NSString *)recordID
569 error:(NSError**)error
570 {
571 NSArray *selectArray;
572
573 selectArray = selectDictionaries(_pDB, @"SELECT * from bp WHERE contextIDAndDSID == %@ AND bottledPeerRecordID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid], recordID);
574 if (!selectArray) {
575 if (error) {
576 secerror("octagon: failed to read local store entry for %@", recordID);
577 *error = self.pDB.lastError;
578 }
579 return nil;
580 }
581 if ([selectArray count] > 1) {
582 secerror("octagon: error multiple records exist in local store for %@", recordID);
583 if(error){
584 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"error multiple records exist in local store"}];
585 }
586 return nil;
587 }
588 else if([selectArray count] == 0){
589 secerror("octagon: record does not exist: %@", recordID);
590 return nil;
591 }
592 NSDictionary *attributes = [selectArray objectAtIndex:0];
593
594 OTBottledPeerRecord *rec = [[OTBottledPeerRecord alloc] init];
595 rec.escrowRecordID = attributes[OTCKRecordEscrowRecordID];
596 rec.peerID = attributes[OTCKRecordPeerID];
597 rec.spID = attributes[OTCKRecordSPID];
598 rec.bottle = dataFromBase64(attributes[OTCKRecordBottle]);
599 rec.escrowedSigningSPKI = dataFromBase64(attributes[OTCKRecordEscrowSigningSPKI]);
600 rec.peerSigningSPKI = dataFromBase64(attributes[OTCKRecordPeerSigningSPKI]);
601 rec.signatureUsingEscrowKey = dataFromBase64(attributes[OTCKRecordSignatureFromEscrow]);
602 rec.signatureUsingPeerKey = dataFromBase64(attributes[OTCKRecordSignatureFromPeerKey]);
603 rec.encodedRecord = dataFromBase64(attributes[OTCKRecordEncodedRecord]);
604 rec.launched = attributes[OTCKRecordLaunched];
605 return rec;
606 }
607
608 - (NSMutableArray<OTBottledPeerRecord *>*) convertResultsToBottles:(NSArray*) selectArray
609 {
610 NSMutableArray *arrayOfBottleRecords = [NSMutableArray<OTBottledPeerRecord *> array];
611 for(NSDictionary* bottle in selectArray){
612 OTBottledPeerRecord *rec = [[OTBottledPeerRecord alloc] init];
613 rec.escrowRecordID = bottle[OTCKRecordEscrowRecordID];
614 rec.peerID = bottle[OTCKRecordPeerID];
615 rec.spID = bottle[OTCKRecordSPID];
616 rec.bottle = dataFromBase64(bottle[OTCKRecordBottle]);
617 rec.escrowedSigningSPKI = dataFromBase64(bottle[OTCKRecordEscrowSigningSPKI]);
618 rec.peerSigningSPKI = dataFromBase64(bottle[OTCKRecordPeerSigningSPKI]);
619 rec.signatureUsingEscrowKey = dataFromBase64(bottle[OTCKRecordSignatureFromEscrow]);
620 rec.signatureUsingPeerKey = dataFromBase64(bottle[OTCKRecordSignatureFromPeerKey]);
621 rec.encodedRecord = dataFromBase64(bottle[OTCKRecordEncodedRecord]);
622 rec.launched = bottle[OTCKRecordLaunched];
623
624 [arrayOfBottleRecords addObject:rec];
625 }
626 return arrayOfBottleRecords;
627 }
628
629 - (nullable NSArray<OTBottledPeerRecord*>*) readAllLocalBottledPeerRecords:(NSError**)error
630 {
631 NSArray *selectArray;
632
633 selectArray = selectDictionaries(_pDB, @"SELECT * from bp where contextIDAndDSID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid]);
634 if (!selectArray) {
635 if (error) {
636 secerror("octagon: failed to read local store entries");
637 *error = self.pDB.lastError;
638 }
639 return nil;
640 }
641 if ([selectArray count] == 0) {
642 secerror("octagon: there are no bottled peer entries in local store");
643 if(error){
644 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"there are no bottled peer entries in local store"}];
645 }
646 return nil;
647 }
648
649 return [self convertResultsToBottles:selectArray];
650 }
651
652 - (nullable NSArray<OTBottledPeerRecord *>*) readLocalBottledPeerRecordsWithMatchingPeerID:(NSString*)peerID error:(NSError**)error
653 {
654 NSArray *selectArray;
655
656 selectArray = selectDictionaries(_pDB, @"SELECT * from bp where spID == %@;", peerID);
657 if (!selectArray) {
658 if (error) {
659 secerror("octagon: failed to read local store entries");
660 *error = self.pDB.lastError;
661 }
662 return nil;
663 }
664 if ([selectArray count] == 0) {
665 secerror("octagon: there are no bottled peer entries in local store");
666 if(error){
667 *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"there are no bottled peer entries in local store"}];
668 }
669 return nil;
670 }
671
672 return [self convertResultsToBottles:selectArray];
673 }
674
675 static NSData * _Nullable dataFromBase64(NSString * _Nullable base64)
676 {
677 if (base64 && [base64 length] > 0) {
678 return [[NSData alloc] initWithBase64EncodedString:base64 options:0];
679 }
680 return nil;
681 }
682
683 @end
684 #endif