2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
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 #import "keychain/ot/OTConstants.h"
32 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
33 #include <MobileGestalt.h>
35 #include <AppleSystemInfo/AppleSystemInfo.h>
38 #import "OTLocalStore.h"
39 #import "OTBottledPeerSigned.h"
41 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);";
43 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);";
45 static const NSInteger user_version = 0;
47 /* Octagon Trust Local Context Record Constants */
48 static NSString* OTCKRecordContextAndDSID = @"contextIDAndDSID";
49 static NSString* OTCKRecordContextID = @"contextID";
50 static NSString* OTCKRecordDSID = @"accountDSID";
51 static NSString* OTCKRecordContextName = @"contextName";
52 static NSString* OTCKRecordZoneCreated = @"zoneCreated";
53 static NSString* OTCKRecordSubscribedToChanges = @"subscribedToChanges";
54 static NSString* OTCKRecordChangeToken = @"changeToken";
55 static NSString* OTCKRecordEgoPeerID = @"egoPeerID";
56 static NSString* OTCKRecordEgoPeerCreationDate = @"egoPeerCreationDate";
57 static NSString* OTCKRecordRecoverySigningSPKI = @"recoverySigningSPKI";
58 static NSString* OTCKRecordRecoveryEncryptionSPKI = @"recoveryEncryptionSPKI";
59 static NSString* OTCKRecordBottledPeerTableEntry = @"bottledPeer";
61 /* Octagon Trust Local Peer Record */
62 static NSString* OTCKRecordPeerID = @"peerID";
63 static NSString* OTCKRecordPermanentInfo = @"permanentInfo";
64 static NSString* OTCKRecordStableInfo = @"stableInfo";
65 static NSString* OTCKRecordDynamicInfo = @"dynamicInfo";
66 static NSString* OTCKRecordRecoveryVoucher = @"recoveryVoucher";
67 static NSString* OTCKRecordIsEgoPeer = @"isEgoPeer";
69 /* Octagon Trust BottledPeerSchema */
70 static NSString* OTCKRecordEscrowRecordID = @"escrowRecordID";
71 static NSString* OTCKRecordRecordID = @"bottledPeerRecordID";
72 static NSString* OTCKRecordSPID = @"spID";
73 static NSString* OTCKRecordBottle = @"bottle";
74 static NSString* OTCKRecordEscrowSigningSPKI = @"escrowSigningSPKI";
75 static NSString* OTCKRecordPeerSigningSPKI = @"peerSigningSPKI";
76 static NSString* OTCKRecordSignatureFromEscrow = @"signatureUsingEscrow";
77 static NSString* OTCKRecordSignatureFromPeerKey = @"signatureUsingPeerKey";
78 static NSString* OTCKRecordEncodedRecord = @"encodedRecord";
79 static NSString* OTCKRecordLaunched = @"launched";
81 /* Octagon Table Names */
82 static NSString* const contextTable = @"context";
83 static NSString* const peerTable = @"peer";
84 static NSString* const bottledPeerTable = @"bp";
86 /* Octagon Trust Schemas */
87 static NSString* const octagonZoctagonErrorDomainoneName = @"OctagonTrustZone";
89 /* Octagon Cloud Kit defines */
90 static NSString* OTCKZoneName = @"OctagonTrust";
91 static NSString* OTCKRecordName = @"bp-";
92 static NSString* OTCKRecordBottledPeerType = @"OTBottledPeer";
94 static NSArray* _Nullable selectAll(PQLResultSet *rs, Class class)
96 NSMutableArray *arr = [NSMutableArray array];
97 for (id o in [rs enumerateObjectsOfClass:class]) {
105 #define selectArrays(db, sql, ...) \
106 selectAll([db fetch:sql, ##__VA_ARGS__], [NSArray class])
108 #define selectDictionaries(db, sql, ...) \
109 selectAll([db fetch:sql, ##__VA_ARGS__], [NSDictionary class])
112 @interface NSDictionary (PQLResultSetInitializer) <PQLResultSetInitializer>
114 @implementation NSDictionary (PQLResultSetInitializer)
115 - (instancetype)initFromPQLResultSet:(PQLResultSet *)rs
116 error:(NSError **)error
118 NSUInteger cols = rs.columns;
119 NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:cols];
121 for (NSUInteger i = 0; i < cols; i++) {
124 dict[[rs columnNameAtIndex:(int)i]] = obj;
128 return [self initWithDictionary:dict];
133 @implementation OTLocalStore
135 -(instancetype) initWithContextID:(NSString*)contextID dsid:(NSString*)dsid path:(nullable NSString*)path error:(NSError**)error
140 NSURL* urlPath = (__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)@"otdb.db");
141 path = [urlPath path];
143 _dbPath = [path copy];
144 _pDB = [[PQLConnection alloc] init];
145 _contextID = [contextID copy];
147 _serialQ = dispatch_queue_create("com.apple.security.ot.db", DISPATCH_QUEUE_SERIAL);
149 NSError* localError = nil;
150 if(![self openDBWithError:&localError])
152 secerror("octagon: could not open db: %@", localError);
162 - (BOOL) createDirectoryAtPath:(NSString*)path error:(NSError **)error
166 NSFileManager *fileManager = [NSFileManager defaultManager];
168 if (![fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&localError]) {
169 if (![localError.domain isEqualToString:NSCocoaErrorDomain] || localError.code != NSFileWriteFileExistsError) {
179 NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&localError];
180 if (![attributes[NSFileProtectionKey] isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) {
181 [fileManager setAttributes:@{ NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication }
182 ofItemAtPath:path error:&localError];
187 if (error) *error = localError;
192 -(BOOL)openDBWithError:(NSError**)error
195 NSError *localError = nil;
197 if(!(result = [_pDB openAtURL:[NSURL URLWithString:_dbPath] sharedCache:NO error:&localError])){
198 secerror("octagon: could not open db: %@", localError);
204 if(![_pDB execute:bottledPeerSchema]){
205 secerror("octagon: could not create bottled peer schema");
207 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorEntropyCreationFailure userInfo:@{NSLocalizedDescriptionKey: @"could not create bottled peer schema"}];
211 if(![_pDB execute:contextSchema]){
212 secerror("octagon: could not create contextschema");
214 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"could not create context schema"}];
218 if(![_pDB setupPragmas]){
219 secerror("octagon: could not set up db pragmas");
221 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"could not set up db pragmas"}];
225 if(![_pDB setUserVersion:user_version]){
226 secerror("octagon: could not set version");
228 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"could not set version"}];
235 -(BOOL)closeDBWithError:(NSError**)error
238 NSError *localError = nil;
240 if(!(result =[_pDB close:&localError])){
241 secerror("octagon: could not close db: %@", localError);
249 -(BOOL)isProposedColumnNameInTable:(NSString*)proposedColumnName tableName:(NSString*)tableName
253 if([tableName isEqualToString:contextTable])
255 if([proposedColumnName isEqualToString:OTCKRecordContextAndDSID]){
258 else if([proposedColumnName isEqualToString:OTCKRecordContextID]){
261 else if([proposedColumnName isEqualToString:OTCKRecordDSID]){
264 else if([proposedColumnName isEqualToString:OTCKRecordContextName]){
267 else if([proposedColumnName isEqualToString:OTCKRecordZoneCreated]){
270 else if([proposedColumnName isEqualToString:OTCKRecordSubscribedToChanges]){
273 else if([proposedColumnName isEqualToString:OTCKRecordChangeToken]){
276 else if([proposedColumnName isEqualToString:OTCKRecordEgoPeerID]){
279 else if([proposedColumnName isEqualToString:OTCKRecordEgoPeerCreationDate]){
282 else if([proposedColumnName isEqualToString:OTCKRecordRecoverySigningSPKI]){
285 else if([proposedColumnName isEqualToString:OTCKRecordRecoveryEncryptionSPKI]){
289 secerror("octagon: column name unknown: %@", proposedColumnName);
292 else if([tableName isEqualToString:peerTable]){ //not using yet!
294 secerror("octagon: not using this table yet!");
296 else if([tableName isEqualToString:bottledPeerTable])
298 if([proposedColumnName isEqualToString:OTCKRecordContextAndDSID]){
301 else if([proposedColumnName isEqualToString:OTCKRecordRecordID]){
304 else if([proposedColumnName isEqualToString:OTCKRecordEscrowRecordID]){
307 else if([proposedColumnName isEqualToString:OTCKRecordSPID]){
310 else if([proposedColumnName isEqualToString:OTCKRecordPeerID]){
313 else if([proposedColumnName isEqualToString:OTCKRecordBottle]){
316 else if([proposedColumnName isEqualToString:OTCKRecordSignatureFromEscrow]){
319 else if([proposedColumnName isEqualToString:OTCKRecordSignatureFromPeerKey]){
322 else if([proposedColumnName isEqualToString:OTCKRecordEncodedRecord]){
325 else if([proposedColumnName isEqualToString:OTCKRecordLaunched]){
328 else if([proposedColumnName isEqualToString:OTCKRecordPeerSigningSPKI]){
331 else if([proposedColumnName isEqualToString:OTCKRecordEscrowSigningSPKI]){
335 secerror("octagon: column name unknown: %@", proposedColumnName);
339 secerror("octagon: table name unknown: %@", tableName);
345 // Local Context Record
347 -(OTContextRecord* _Nullable)readLocalContextRecordForContextIDAndDSID:(NSString*)contextAndDSID error:(NSError**)error
349 OTContextRecord* record = [[OTContextRecord alloc]init];
350 NSDictionary* attributes = nil;
351 NSArray *selectArray = nil;
353 selectArray = selectDictionaries(_pDB, @"SELECT * from context WHERE contextIDAndDSID == %@;", PQLName(contextAndDSID));
354 if(selectArray && [selectArray count] > 0){
355 attributes = [selectArray objectAtIndex:0];
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]);
370 secerror("octagon: no context attributes found");
372 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"no context attributes found"}];
379 -(BOOL)initializeContextTable:(NSString*)contextID dsid:(NSString*)dsid error:(NSError**)error
382 NSError* localError = nil;
383 NSString* contextName = nil;
384 #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
385 contextName = (__bridge_transfer NSString *)MGCopyAnswer(kMGQUserAssignedDeviceName, NULL);
387 contextName = (__bridge_transfer NSString *)SCDynamicStoreCopyComputerName(NULL, NULL);
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]};
403 result = [self insertLocalContextRecord:contextAttributes error:&localError];
404 if(!result || localError != nil){
405 secerror("octagon: context table init failed: %@", localError);
413 -(BOOL)insertLocalContextRecord:(NSDictionary*)attributes error:(NSError**)error
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]];
426 secerror("octagon: failed to insert local context: %@", _pDB.lastError);
428 *error = _pDB.lastError;
434 -(BOOL)updateLocalContextRecordRowWithContextID:(NSString*)contextIDAndDSID columnName:(NSString*)columnName newValue:(void*)newValue error:(NSError**)error
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;
446 secerror("octagon: failed to update local context record: %@", _pDB.lastError);
449 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorNoColumn userInfo:nil];
455 -(BOOL) deleteLocalContext:(NSString*)contextIDAndDSID error:(NSError**)error
458 secnotice("octagon", "deleting local context: %@", contextIDAndDSID);
460 result = [_pDB execute:@"delete from context where contextIDAndDSID == %@",
461 PQLName(contextIDAndDSID)];
464 secerror("octagon: error updating table: %@", _pDB.lastError);
466 *error = _pDB.lastError;
472 -(BOOL) deleteAllContexts:(NSError**)error
475 secnotice("octagon", "deleting all local context");
477 result = [_pDB execute:@"delete from context"];
480 secerror("octagon: error updating table: %@", _pDB.lastError);
482 *error = _pDB.lastError;
489 // Local Bottled Peer Record
492 - (BOOL) insertBottledPeerRecord:(OTBottledPeerRecord *)rec
493 escrowRecordID:(NSString *)escrowRecordID
494 error:(NSError**)error
498 result = [_pDB execute:@"insert or replace into bp (bottledPeerRecordID, contextIDAndDSID, escrowRecordID, peerID, spID, bottle, escrowSigningSPKI, peerSigningSPKI, signatureUsingEscrow, signatureUsingPeerKey, encodedRecord, launched) values (%@,%@,%@,%@,%@,%@,%@,%@,%@,%@,%@,%@)",
500 [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid],
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],
513 secerror("octagon: error inserting bottled peer record: %@", _pDB.lastError);
515 *error = _pDB.lastError;
521 -(BOOL) removeAllBottledPeerRecords:(NSError**)error
525 result = [_pDB execute:@"DELETE from bp WHERE contextIDAndDSID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid]];
528 secerror("octagon: error removing bottled peer records: %@", _pDB.lastError);
530 *error = _pDB.lastError;
536 -(BOOL) deleteBottledPeer:(NSString*) recordID
537 error:(NSError**)error
541 result = [_pDB execute:@"DELETE from bp WHERE contextIDAndDSID == %@ AND bottledPeerRecordID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid], recordID];
544 secerror("octagon: error removing bottled peer record:%@, error: %@", recordID, _pDB.lastError);
546 *error = _pDB.lastError;
552 -(BOOL) deleteBottledPeersForContextAndDSID:(NSString*) contextIDAndDSID
553 error:(NSError**)error
557 result = [_pDB execute:@"DELETE from bp WHERE contextIDAndDSID == %@;", contextIDAndDSID];
560 secerror("octagon: error removing bottled peer record:%@, error: %@", contextIDAndDSID, _pDB.lastError);
562 *error = _pDB.lastError;
568 - (nullable OTBottledPeerRecord *)readLocalBottledPeerRecordWithRecordID:(NSString *)recordID
569 error:(NSError**)error
571 NSArray *selectArray;
573 selectArray = selectDictionaries(_pDB, @"SELECT * from bp WHERE contextIDAndDSID == %@ AND bottledPeerRecordID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid], recordID);
576 secerror("octagon: failed to read local store entry for %@", recordID);
577 *error = self.pDB.lastError;
581 if ([selectArray count] > 1) {
582 secerror("octagon: error multiple records exist in local store for %@", recordID);
584 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"error multiple records exist in local store"}];
588 else if([selectArray count] == 0){
589 secerror("octagon: record does not exist: %@", recordID);
592 NSDictionary *attributes = [selectArray objectAtIndex:0];
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];
608 - (NSMutableArray<OTBottledPeerRecord *>*) convertResultsToBottles:(NSArray*) selectArray
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];
624 [arrayOfBottleRecords addObject:rec];
626 return arrayOfBottleRecords;
629 - (nullable NSArray<OTBottledPeerRecord*>*) readAllLocalBottledPeerRecords:(NSError**)error
631 NSArray *selectArray;
633 selectArray = selectDictionaries(_pDB, @"SELECT * from bp where contextIDAndDSID == %@;", [NSString stringWithFormat:@"%@-%@", self.contextID, self.dsid]);
636 secerror("octagon: failed to read local store entries");
637 *error = self.pDB.lastError;
641 if ([selectArray count] == 0) {
642 secerror("octagon: there are no bottled peer entries in local store");
644 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"there are no bottled peer entries in local store"}];
649 return [self convertResultsToBottles:selectArray];
652 - (nullable NSArray<OTBottledPeerRecord *>*) readLocalBottledPeerRecordsWithMatchingPeerID:(NSString*)peerID error:(NSError**)error
654 NSArray *selectArray;
656 selectArray = selectDictionaries(_pDB, @"SELECT * from bp where spID == %@;", peerID);
659 secerror("octagon: failed to read local store entries");
660 *error = self.pDB.lastError;
664 if ([selectArray count] == 0) {
665 secerror("octagon: there are no bottled peer entries in local store");
667 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTLocalStore userInfo:@{NSLocalizedDescriptionKey: @"there are no bottled peer entries in local store"}];
672 return [self convertResultsToBottles:selectArray];
675 static NSData * _Nullable dataFromBase64(NSString * _Nullable base64)
677 if (base64 && [base64 length] > 0) {
678 return [[NSData alloc] initWithBase64EncodedString:base64 options:0];