2 * Copyright (c) 2016-2018 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@
29 #include <AssertMacros.h>
30 #import <Foundation/Foundation.h>
35 #import <MobileAsset/MAAsset.h>
36 #import <MobileAsset/MAAssetQuery.h>
40 #import <MobileAsset/MobileAsset.h>
44 #import <Security/SecInternalReleasePriv.h>
46 #import <securityd/OTATrustUtilities.h>
47 #import <securityd/SecPinningDb.h>
48 #import <securityd/SecTrustLoggingServer.h>
50 #include "utilities/debugging.h"
51 #include "utilities/sqlutils.h"
52 #include "utilities/iOSforOSX.h"
53 #include <utilities/SecCFError.h>
54 #include <utilities/SecCFRelease.h>
55 #include <utilities/SecCFWrappers.h>
56 #include <utilities/SecDb.h>
57 #include <utilities/SecFileLocations.h>
58 #include "utilities/sec_action.h"
60 #define kSecPinningBasePath "/Library/Keychains/"
61 #define kSecPinningDbFileName "pinningrules.sqlite3"
63 const uint64_t PinningDbSchemaVersion = 2;
64 const NSString *PinningDbPolicyNameKey = @"policyName"; /* key for a string value */
65 const NSString *PinningDbDomainsKey = @"domains"; /* key for an array of dictionaries */
66 const NSString *PinningDbPoliciesKey = @"rules"; /* key for an array of dictionaries */
67 const NSString *PinningDbDomainSuffixKey = @"suffix"; /* key for a string */
68 const NSString *PinningDbLabelRegexKey = @"labelRegex"; /* key for a regex string */
70 const CFStringRef kSecPinningDbKeyHostname = CFSTR("PinningHostname");
71 const CFStringRef kSecPinningDbKeyPolicyName = CFSTR("PinningPolicyName");
72 const CFStringRef kSecPinningDbKeyRules = CFSTR("PinningRules");
74 @interface SecPinningDb : NSObject
75 @property (assign) SecDbRef db;
76 @property dispatch_queue_t queue;
77 @property NSURL *dbPath;
78 - (instancetype) init;
79 - ( NSDictionary * _Nullable ) queryForDomain:(NSString *)domain;
80 - ( NSDictionary * _Nullable ) queryForPolicyName:(NSString *)policyName;
83 static inline bool isNSNumber(id nsType) {
84 return nsType && [nsType isKindOfClass:[NSNumber class]];
87 static inline bool isNSArray(id nsType) {
88 return nsType && [nsType isKindOfClass:[NSArray class]];
91 static inline bool isNSDictionary(id nsType) {
92 return nsType && [nsType isKindOfClass:[NSDictionary class]];
95 @implementation SecPinningDb
96 #define getSchemaVersionSQL CFSTR("PRAGMA user_version")
97 #define selectVersionSQL CFSTR("SELECT ival FROM admin WHERE key='version'")
98 #define insertAdminSQL CFSTR("INSERT OR REPLACE INTO admin (key,ival,value) VALUES (?,?,?)")
99 #define selectDomainSQL CFSTR("SELECT DISTINCT labelRegex,policyName,policies FROM rules WHERE domainSuffix=?")
100 #define selectPolicyNameSQL CFSTR("SELECT DISTINCT policies FROM rules WHERE policyName=?")
101 #define insertRuleSQL CFSTR("INSERT OR REPLACE INTO rules (policyName,domainSuffix,labelRegex,policies) VALUES (?,?,?,?) ")
102 #define removeAllRulesSQL CFSTR("DELETE FROM rules;")
104 - (NSNumber *)getSchemaVersion:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
105 __block bool ok = true;
106 __block NSNumber *version = nil;
107 ok &= SecDbWithSQL(dbconn, getSchemaVersionSQL, error, ^bool(sqlite3_stmt *selectVersion) {
108 ok &= SecDbStep(dbconn, selectVersion, error, ^(bool *stop) {
109 int ival = sqlite3_column_int(selectVersion, 0);
110 version = [NSNumber numberWithInt:ival];
117 - (BOOL)setSchemaVersion:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
119 NSString *setVersion = [NSString stringWithFormat:@"PRAGMA user_version = %llu", PinningDbSchemaVersion];
120 ok &= SecDbExec(dbconn,
121 (__bridge CFStringRef)setVersion,
124 secerror("SecPinningDb: failed to create admin table: %@", error ? *error : nil);
129 - (NSNumber *)getContentVersion:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
130 __block bool ok = true;
131 __block NSNumber *version = nil;
132 ok &= SecDbWithSQL(dbconn, selectVersionSQL, error, ^bool(sqlite3_stmt *selectVersion) {
133 ok &= SecDbStep(dbconn, selectVersion, error, ^(bool *stop) {
134 uint64_t ival = sqlite3_column_int64(selectVersion, 0);
135 version = [NSNumber numberWithUnsignedLongLong:ival];
142 - (BOOL)setContentVersion:(NSNumber *)version dbConnection:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
143 __block BOOL ok = true;
144 ok &= SecDbWithSQL(dbconn, insertAdminSQL, error, ^bool(sqlite3_stmt *insertAdmin) {
145 const char *versionKey = "version";
146 ok &= SecDbBindText(insertAdmin, 1, versionKey, strlen(versionKey), SQLITE_TRANSIENT, error);
147 ok &= SecDbBindInt64(insertAdmin, 2, [version unsignedLongLongValue], error);
148 ok &= SecDbStep(dbconn, insertAdmin, error, NULL);
152 secerror("SecPinningDb: failed to set version %@ from pinning list: %@", version, error ? *error : nil);
157 - (BOOL) shouldUpdateContent:(NSNumber *)new_version {
158 __block CFErrorRef error = NULL;
159 __block BOOL ok = YES;
160 __block BOOL newer = NO;
161 ok &= SecDbPerformRead(_db, &error, ^(SecDbConnectionRef dbconn) {
162 NSNumber *db_version = [self getContentVersion:dbconn error:&error];
163 if (!db_version || [new_version compare:db_version] == NSOrderedDescending) {
165 secnotice("pinningDb", "Pinning database should update from version %@ to version %@", db_version, new_version);
170 secerror("SecPinningDb: error reading content version from database %@", error);
172 CFReleaseNull(error);
176 - (BOOL) insertRuleWithName:(NSString *)policyName
177 domainSuffix:(NSString *)domainSuffix
178 labelRegex:(NSString *)labelRegex
179 policies:(NSArray *)policies
180 dbConnection:(SecDbConnectionRef)dbconn
181 error:(CFErrorRef *)error{
182 /* @@@ This insertion mechanism assumes that the input is trusted -- namely, that the new rules
183 * are allowed to replace existing rules. For third-party inputs, this assumption isn't true. */
185 secdebug("pinningDb", "inserting new rule: %@ for %@.%@", policyName, labelRegex, domainSuffix);
187 __block bool ok = true;
188 ok &= SecDbWithSQL(dbconn, insertRuleSQL, error, ^bool(sqlite3_stmt *insertRule) {
189 ok &= SecDbBindText(insertRule, 1, [policyName UTF8String], [policyName length], SQLITE_TRANSIENT, error);
190 ok &= SecDbBindText(insertRule, 2, [domainSuffix UTF8String], [domainSuffix length], SQLITE_TRANSIENT, error);
191 ok &= SecDbBindText(insertRule, 3, [labelRegex UTF8String], [labelRegex length], SQLITE_TRANSIENT, error);
192 NSData *xmlPolicies = [NSPropertyListSerialization dataWithPropertyList:policies
193 format:NSPropertyListXMLFormat_v1_0
197 secerror("SecPinningDb: failed to serialize policies");
200 ok &= SecDbBindBlob(insertRule, 4, [xmlPolicies bytes], [xmlPolicies length], SQLITE_TRANSIENT, error);
201 ok &= SecDbStep(dbconn, insertRule, error, NULL);
205 secerror("SecPinningDb: failed to insert rule %@ for %@.%@ with error %@", policyName, labelRegex, domainSuffix, error ? *error : nil);
210 - (BOOL) populateDbFromBundle:(NSArray *)pinningList dbConnection:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
211 __block BOOL ok = true;
212 [pinningList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
213 if (idx ==0) { return; } // Skip the first value which is the version
214 if (!isNSDictionary(obj)) {
215 secerror("SecPinningDb: rule entry in pinning plist is wrong class");
219 NSDictionary *rule = obj;
220 __block NSString *policyName = [rule objectForKey:PinningDbPolicyNameKey];
221 NSArray *domains = [rule objectForKey:PinningDbDomainsKey];
222 __block NSArray *policies = [rule objectForKey:PinningDbPoliciesKey];
224 if (!policyName || !domains || !policies) {
225 secerror("SecPinningDb: failed to get required fields from rule entry %lu", (unsigned long)idx);
230 [domains enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
231 if (!isNSDictionary(obj)) {
232 secerror("SecPinningDb: domain entry %lu for %@ in pinning rule is wrong class", (unsigned long)idx, policyName);
236 NSDictionary *domain = obj;
237 NSString *suffix = [domain objectForKey:PinningDbDomainSuffixKey];
238 NSString *labelRegex = [domain objectForKey:PinningDbLabelRegexKey];
240 if (!suffix || !labelRegex) {
241 secerror("SecPinningDb: failed to get required fields for entry %lu for %@", (unsigned long)idx, policyName);
245 ok &= [self insertRuleWithName:policyName domainSuffix:suffix labelRegex:labelRegex policies:policies
246 dbConnection:dbconn error:error];
250 secerror("SecPinningDb: failed to populate DB from pinning list: %@", error ? *error : nil);
255 - (BOOL) removeAllRulesFromDb:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
256 __block BOOL ok = true;
257 ok &= SecDbWithSQL(dbconn, removeAllRulesSQL, error, ^bool(sqlite3_stmt *deleteRules) {
258 ok &= SecDbStep(dbconn, deleteRules, error, NULL);
262 secerror("SecPinningDb: failed to delete old values: %@", error ? *error :nil);
268 - (BOOL) createOrAlterAdminTable:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
270 ok &= SecDbExec(dbconn,
271 CFSTR("CREATE TABLE IF NOT EXISTS admin("
272 "key TEXT PRIMARY KEY NOT NULL,"
273 "ival INTEGER NOT NULL,"
278 secerror("SecPinningDb: failed to create admin table: %@", error ? *error : nil);
283 - (BOOL) createOrAlterRulesTable:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error {
285 ok &= SecDbExec(dbconn,
286 CFSTR("CREATE TABLE IF NOT EXISTS rules("
287 "policyName TEXT NOT NULL,"
288 "domainSuffix TEXT NOT NULL,"
289 "labelRegex TEXT NOT NULL,"
290 "policies BLOB NOT NULL,"
291 "UNIQUE(policyName, domainSuffix, labelRegex)"
294 ok &= SecDbExec(dbconn, CFSTR("CREATE INDEX IF NOT EXISTS idomain ON rules(domainSuffix);"), error);
295 ok &= SecDbExec(dbconn, CFSTR("CREATE INDEX IF NOT EXISTS ipolicy ON rules(policyName);"), error);
297 secerror("SecPinningDb: failed to create rules table: %@", error ? *error : nil);
302 #if !TARGET_OS_BRIDGE
303 - (BOOL) installDbFromURL:(NSURL *)localURL {
305 secerror("SecPinningDb: missing url for downloaded asset");
308 NSURL *fileLoc = [NSURL URLWithString:@"CertificatePinning.plist"
309 relativeToURL:localURL];
310 __block NSArray *pinningList = [NSArray arrayWithContentsOfURL:fileLoc];
312 secerror("SecPinningDb: unable to create pinning list from asset file: %@", fileLoc);
316 NSNumber *plist_version = [pinningList objectAtIndex:0];
317 if (![self shouldUpdateContent:plist_version]) {
318 /* We got a new plist but we already have that version installed. */
323 __block CFErrorRef error = NULL;
324 __block BOOL ok = YES;
325 dispatch_sync(self->_queue, ^{
326 ok &= SecDbPerformWrite(self->_db, &error, ^(SecDbConnectionRef dbconn) {
327 ok &= [self updateDb:dbconn error:&error pinningList:pinningList updateSchema:NO updateContent:YES];
332 secerror("SecPinningDb: error installing updated pinning list version %@: %@", [pinningList objectAtIndex:0], error);
333 [[TrustdHealthAnalytics logger] logHardError:(__bridge NSError *)error
334 withEventName:TrustdHealthAnalyticsEventDatabaseEvent
335 withAttributes:@{TrustdHealthAnalyticsAttributeAffectedDatabase : @(TAPinningDb),
336 TrustdHealthAnalyticsAttributeDatabaseOperation : @(TAOperationWrite) }];
337 CFReleaseNull(error);
342 #endif /* !TARGET_OS_BRIDGE */
344 - (NSArray *) copySystemPinningList {
345 NSArray *pinningList = nil;
346 NSURL *pinningListURL = nil;
347 /* Get the pinning list shipped with the OS */
348 SecOTAPKIRef otapkiref = SecOTAPKICopyCurrentOTAPKIRef();
350 pinningListURL = CFBridgingRelease(SecOTAPKICopyPinningList(otapkiref));
351 CFReleaseNull(otapkiref);
352 if (!pinningListURL) {
353 secerror("SecPinningDb: failed to get pinning plist URL");
355 NSError *error = nil;
356 pinningList = [NSArray arrayWithContentsOfURL:pinningListURL error:&error];
358 secerror("SecPinningDb: failed to read pinning plist from bundle: %@", error);
365 - (BOOL) updateDb:(SecDbConnectionRef)dbconn error:(CFErrorRef *)error pinningList:(NSArray *)pinningList
366 updateSchema:(BOOL)updateSchema updateContent:(BOOL)updateContent
368 if (!SecOTAPKIIsSystemTrustd()) { return false; }
369 secdebug("pinningDb", "updating or creating database");
371 __block bool ok = true;
372 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
374 /* update the tables */
375 ok &= [self createOrAlterAdminTable:dbconn error:error];
376 ok &= [self createOrAlterRulesTable:dbconn error:error];
377 ok &= [self setSchemaVersion:dbconn error:error];
381 /* remove the old data */
382 /* @@@ This behavior assumes that we have all the rules we want to populate
383 * elsewhere on disk and that the DB doesn't contain the sole copy of that data. */
384 ok &= [self removeAllRulesFromDb:dbconn error:error];
386 /* read the new data */
387 NSNumber *version = [pinningList objectAtIndex:0];
389 /* populate the tables */
390 ok &= [self populateDbFromBundle:pinningList dbConnection:dbconn error:error];
391 ok &= [self setContentVersion:version dbConnection:dbconn error:error];
400 - (SecDbRef) createAtPath {
401 bool readWrite = SecOTAPKIIsSystemTrustd();
403 mode_t mode = 0644; // Root trustd can rw. All other trustds need to read.
405 mode_t mode = 0600; // Only one trustd.
408 CFStringRef path = CFStringCreateWithCString(NULL, [_dbPath fileSystemRepresentation], kCFStringEncodingUTF8);
409 SecDbRef result = SecDbCreateWithOptions(path, mode, readWrite, readWrite, false,
410 ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
411 if (!SecOTAPKIIsSystemTrustd()) {
412 /* Non-owner process can't update the db, but it should get a db connection.
413 * @@@ Revisit if new schema version is needed by reader processes. */
417 __block BOOL ok = true;
418 dispatch_sync(self->_queue, ^{
419 bool updateSchema = false;
420 bool updateContent = false;
422 /* Get the pinning plist */
423 NSArray *pinningList = [self copySystemPinningList];
425 secerror("SecPinningDb: failed to find pinning plist in bundle");
430 /* Check latest data and schema versions against existing table. */
431 if (!isNSNumber([pinningList objectAtIndex:0])) {
432 secerror("SecPinningDb: pinning plist in wrong format");
433 return; // Don't change status. We can continue to use old DB.
435 NSNumber *plist_version = [pinningList objectAtIndex:0];
436 NSNumber *db_version = [self getContentVersion:dbconn error:error];
437 secnotice("pinningDb", "Opening db with version %@", db_version);
438 if (!db_version || [plist_version compare:db_version] == NSOrderedDescending) {
439 secnotice("pinningDb", "Updating pinning database content from version %@ to version %@",
440 db_version ? db_version : 0, plist_version);
441 updateContent = true;
443 NSNumber *schema_version = [self getSchemaVersion:dbconn error:error];
444 NSNumber *current_version = [NSNumber numberWithUnsignedLongLong:PinningDbSchemaVersion];
445 if (!schema_version || ![schema_version isEqualToNumber:current_version]) {
446 secnotice("pinningDb", "Updating pinning database schema from version %@ to version %@",
447 schema_version, current_version);
451 if (updateContent || updateSchema) {
452 ok &= [self updateDb:dbconn error:error pinningList:pinningList updateSchema:updateSchema updateContent:updateContent];
453 /* Since we updated the DB to match the list that shipped with the system,
454 * reset the OTAPKI Asset version to the system asset version */
455 (void)SecOTAPKIResetCurrentAssetVersion(NULL);
458 secerror("SecPinningDb: %s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
459 [[TrustdHealthAnalytics logger] logHardError:(error ? (__bridge NSError *)*error : nil)
460 withEventName:TrustdHealthAnalyticsEventDatabaseEvent
461 withAttributes:@{TrustdHealthAnalyticsAttributeAffectedDatabase : @(TAPinningDb),
462 TrustdHealthAnalyticsAttributeDatabaseOperation : didCreate ? @(TAOperationCreate) : @(TAOperationOpen)}];
472 static void verify_create_path(const char *path)
474 int ret = mkpath_np(path, 0755);
475 if (!(ret == 0 || ret == EEXIST)) {
476 secerror("could not create path: %s (%s)", path, strerror(ret));
480 - (NSURL *)pinningDbPath {
481 /* Make sure the /Library/Keychains directory is there */
483 NSURL *directory = CFBridgingRelease(SecCopyURLForFileInKeychainDirectory(nil));
485 NSURL *directory = [NSURL fileURLWithFileSystemRepresentation:"/Library/Keychains/" isDirectory:YES relativeToURL:nil];
487 verify_create_path([directory fileSystemRepresentation]);
489 /* Get the full path of the pinning DB */
490 return [directory URLByAppendingPathComponent:@"pinningrules.sqlite3"];
493 - (void) initializedDb {
494 dispatch_sync(_queue, ^{
496 self->_dbPath = [self pinningDbPath];
497 self->_db = [self createAtPath];
502 - (instancetype) init {
503 if (self = [super init]) {
504 _queue = dispatch_queue_create("Pinning DB Queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
505 [self initializedDb];
514 - (BOOL) isPinningDisabled:(NSString * _Nullable)policy {
515 static dispatch_once_t once;
516 static sec_action_t action;
518 BOOL pinningDisabled = NO;
519 if (SecIsInternalRelease()) {
520 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.security"];
521 pinningDisabled = [defaults boolForKey:@"AppleServerAuthenticationNoPinning"];
522 if (!pinningDisabled && policy) {
523 NSMutableString *policySpecificKey = [NSMutableString stringWithString:@"AppleServerAuthenticationNoPinning"];
524 [policySpecificKey appendString:policy];
525 pinningDisabled = [defaults boolForKey:policySpecificKey];
526 secinfo("pinningQA", "%@ disable pinning = %d", policy, pinningDisabled);
531 dispatch_once(&once, ^{
532 /* Only log system-wide pinning status once every five minutes */
533 action = sec_action_create("pinning logging charles", 5*60.0);
534 sec_action_set_handler(action, ^{
535 if (!SecIsInternalRelease()) {
536 secnotice("pinningQA", "could not disable pinning: not an internal release");
538 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.security"];
539 secnotice("pinningQA", "generic pinning disable = %d", [defaults boolForKey:@"AppleServerAuthenticationNoPinning"]);
543 sec_action_perform(action);
545 return pinningDisabled;
548 - ( NSDictionary * _Nullable ) queryForDomain:(NSString *)domain {
549 if (!_queue) { (void)[self init]; }
550 if (!_db) { [self initializedDb]; }
552 /* Check for general no-pinning setting */
553 if ([self isPinningDisabled:nil]) {
557 /* parse the domain into suffix and 1st label */
558 NSRange firstDot = [domain rangeOfString:@"."];
559 if (firstDot.location == NSNotFound) { return nil; } // Probably not a legitimate domain name
560 __block NSString *firstLabel = [domain substringToIndex:firstDot.location];
561 __block NSString *suffix = [domain substringFromIndex:(firstDot.location + 1)];
564 __block bool ok = true;
565 __block CFErrorRef error = NULL;
566 __block NSMutableArray *resultRules = [NSMutableArray array];
567 __block NSString *resultName = nil;
568 ok &= SecDbPerformRead(_db, &error, ^(SecDbConnectionRef dbconn) {
569 ok &= SecDbWithSQL(dbconn, selectDomainSQL, &error, ^bool(sqlite3_stmt *selectDomain) {
570 ok &= SecDbBindText(selectDomain, 1, [suffix UTF8String], [suffix length], SQLITE_TRANSIENT, &error);
571 ok &= SecDbStep(dbconn, selectDomain, &error, ^(bool *stop) {
572 /* Match the labelRegex */
573 const uint8_t *regex = sqlite3_column_text(selectDomain, 0);
574 if (!regex) { return; }
575 NSString *regexStr = [NSString stringWithUTF8String:(const char *)regex];
576 if (!regexStr) { return; }
577 NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:regexStr
578 options:NSRegularExpressionCaseInsensitive
580 if (!regularExpression) { return; }
581 NSUInteger numMatches = [regularExpression numberOfMatchesInString:firstLabel
583 range:NSMakeRange(0, [firstLabel length])];
584 if (numMatches == 0) {
587 secdebug("SecPinningDb", "found matching rule for %@.%@", firstLabel, suffix);
589 /* Check the policyName for no-pinning settings */
590 const uint8_t *policyName = sqlite3_column_text(selectDomain, 1);
591 NSString *policyNameStr = [NSString stringWithUTF8String:(const char *)policyName];
592 if ([self isPinningDisabled:policyNameStr]) {
596 /* Deserialize the policies and return.
597 * @@@ Assumes there is only one rule with matching suffix/label pairs. */
598 NSData *xmlPolicies = [NSData dataWithBytes:sqlite3_column_blob(selectDomain, 2) length:sqlite3_column_bytes(selectDomain, 2)];
599 if (!xmlPolicies) { return; }
600 id policies = [NSPropertyListSerialization propertyListWithData:xmlPolicies options:0 format:nil error:nil];
601 if (!isNSArray(policies)) {
604 [resultRules addObjectsFromArray:(NSArray *)policies];
605 resultName = policyNameStr;
612 secerror("SecPinningDb: error querying DB for hostname: %@", error);
613 [[TrustdHealthAnalytics logger] logHardError:(__bridge NSError *)error
614 withEventName:TrustdHealthAnalyticsEventDatabaseEvent
615 withAttributes:@{TrustdHealthAnalyticsAttributeAffectedDatabase : @(TAPinningDb),
616 TrustdHealthAnalyticsAttributeDatabaseOperation : @(TAOperationRead)}];
617 CFReleaseNull(error);
620 if ([resultRules count] > 0) {
621 NSDictionary *results = @{(__bridge NSString*)kSecPinningDbKeyRules:resultRules,
622 (__bridge NSString*)kSecPinningDbKeyPolicyName:resultName};
628 - (NSDictionary * _Nullable) queryForPolicyName:(NSString *)policyName {
629 if (!_queue) { (void)[self init]; }
630 if (!_db) { [self initializedDb]; }
632 /* Skip the "sslServer" policyName, which is not a pinning policy */
633 if ([policyName isEqualToString:@"sslServer"]) {
637 /* Check for general no-pinning setting */
638 if ([self isPinningDisabled:nil] || [self isPinningDisabled:policyName]) {
642 secinfo("SecPinningDb", "Fetching rules for policy named %@", policyName);
645 __block bool ok = true;
646 __block CFErrorRef error = NULL;
647 __block NSMutableArray *resultRules = [NSMutableArray array];
648 ok &= SecDbPerformRead(_db, &error, ^(SecDbConnectionRef dbconn) {
649 ok &= SecDbWithSQL(dbconn, selectPolicyNameSQL, &error, ^bool(sqlite3_stmt *selectPolicyName) {
650 ok &= SecDbBindText(selectPolicyName, 1, [policyName UTF8String], [policyName length], SQLITE_TRANSIENT, &error);
651 ok &= SecDbStep(dbconn, selectPolicyName, &error, ^(bool *stop) {
652 secdebug("SecPinningDb", "found matching rule for %@ policy", policyName);
654 /* Deserialize the policies and return */
655 NSData *xmlPolicies = [NSData dataWithBytes:sqlite3_column_blob(selectPolicyName, 0) length:sqlite3_column_bytes(selectPolicyName, 0)];
656 if (!xmlPolicies) { return; }
657 id policies = [NSPropertyListSerialization propertyListWithData:xmlPolicies options:0 format:nil error:nil];
658 if (!isNSArray(policies)) {
661 [resultRules addObjectsFromArray:(NSArray *)policies];
668 secerror("SecPinningDb: error querying DB for policyName: %@", error);
669 [[TrustdHealthAnalytics logger] logHardError:(__bridge NSError *)error
670 withEventName:TrustdHealthAnalyticsEventDatabaseEvent
671 withAttributes:@{TrustdHealthAnalyticsAttributeAffectedDatabase : @(TAPinningDb),
672 TrustdHealthAnalyticsAttributeDatabaseOperation : @(TAOperationRead)}];
673 CFReleaseNull(error);
676 if ([resultRules count] > 0) {
677 NSDictionary *results = @{(__bridge NSString*)kSecPinningDbKeyRules:resultRules,
678 (__bridge NSString*)kSecPinningDbKeyPolicyName:policyName};
687 static SecPinningDb *pinningDb = nil;
688 void SecPinningDbInitialize(void) {
689 /* Create the pinning object once per launch */
690 static dispatch_once_t onceToken;
691 dispatch_once(&onceToken, ^{
693 pinningDb = [[SecPinningDb alloc] init];
694 __block CFErrorRef error = NULL;
695 BOOL ok = SecDbPerformRead([pinningDb db], &error, ^(SecDbConnectionRef dbconn) {
696 NSNumber *contentVersion = [pinningDb getContentVersion:dbconn error:&error];
697 NSNumber *schemaVersion = [pinningDb getSchemaVersion:dbconn error:&error];
698 secinfo("pinningDb", "Database Schema: %@ Content: %@", schemaVersion, contentVersion);
701 secerror("SecPinningDb: unable to initialize db: %@", error);
702 [[TrustdHealthAnalytics logger] logHardError:(__bridge NSError *)error
703 withEventName:TrustdHealthAnalyticsEventDatabaseEvent
704 withAttributes:@{TrustdHealthAnalyticsAttributeAffectedDatabase : @(TAPinningDb),
705 TrustdHealthAnalyticsAttributeDatabaseOperation : @(TAOperationRead)}];
707 CFReleaseNull(error);
712 CFDictionaryRef _Nullable SecPinningDbCopyMatching(CFDictionaryRef query) {
714 SecPinningDbInitialize();
716 NSDictionary *nsQuery = (__bridge NSDictionary*)query;
717 NSString *hostname = [nsQuery objectForKey:(__bridge NSString*)kSecPinningDbKeyHostname];
719 NSDictionary *results = [pinningDb queryForDomain:hostname];
720 if (results) { return CFBridgingRetain(results); }
721 NSString *policyName = [nsQuery objectForKey:(__bridge NSString*)kSecPinningDbKeyPolicyName];
722 results = [pinningDb queryForPolicyName:policyName];
723 if (!results) { return nil; }
724 return CFBridgingRetain(results);
728 #if !TARGET_OS_BRIDGE
729 bool SecPinningDbUpdateFromURL(CFURLRef url) {
730 SecPinningDbInitialize();
732 return [pinningDb installDbFromURL:(__bridge NSURL*)url];
736 CFNumberRef SecPinningDbCopyContentVersion(void) {
738 __block CFErrorRef error = NULL;
739 __block NSNumber *contentVersion = nil;
740 BOOL ok = SecDbPerformRead([pinningDb db], &error, ^(SecDbConnectionRef dbconn) {
741 contentVersion = [pinningDb getContentVersion:dbconn error:&error];
744 secerror("SecPinningDb: unable to get content version: %@", error);
746 CFReleaseNull(error);
747 if (!contentVersion) {
748 contentVersion = [NSNumber numberWithInteger:0];
750 return CFBridgingRetain(contentVersion);