Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions TrustKit/Pinning/TSKSPKIHashCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,20 @@ - (NSData *)hashSubjectPublicKeyInfoFromCertificate:(SecCertificateRef)certifica
void (^updateCacheBlock)(void) = ^{

if (isProtectedDataAvailable()) {
NSData *serializedSpkiCache = [NSKeyedArchiver archivedDataWithRootObject:weakSelf.spkiCache requiringSecureCoding:YES error:nil];
if ([serializedSpkiCache writeToURL:[weakSelf SPKICachePath] atomically:YES] == NO) {
NSAssert(false, @"Failed to write cache");
TSKLog(@"Could not persist SPKI cache to the filesystem");
}
dispatch_queue_t lockQueue = weakSelf.lockQueue;
if (!lockQueue) return;

dispatch_sync(lockQueue, ^{
NSDictionary *spkiCache = weakSelf.spkiCache;
if (!spkiCache) return;
NSData *serializedSpkiCache = [NSKeyedArchiver archivedDataWithRootObject:spkiCache requiringSecureCoding:YES error:nil];
NSURL *cacheURL = [weakSelf SPKICachePath];
if (!cacheURL) return;
if ([serializedSpkiCache writeToURL:cacheURL atomically:YES] == NO) {
NSAssert(false, @"Failed to write cache");
TSKLog(@"Could not persist SPKI cache to the filesystem");
}
});
}
else {
TSKLog(@"Protected data not available, skipping SPKI cache persistence");
Expand Down
42 changes: 42 additions & 0 deletions TrustKitTests/TSKPublicKeyAlgorithmTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,46 @@ - (void)testSPKICacheThreadSafetyAndProtectedData
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}

// This test hardly manages to reproduce the crash, but it does reproduce it sometimes.
- (void)testSPKICacheThreadSafetyAndProtectedDataDoesntCrash
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Cache operations completed"];

id mockApplication = OCMClassMock([UIApplication class]);
OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
OCMStub([mockApplication isProtectedDataAvailable]).andReturn(YES);

// Perform multiple cache operations in parallel on a background queue
SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.globalsign.com"];
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 100; i++) {
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self->spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate];
});
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

NSDictionary *cache = [self->spkiCache getSubjectPublicKeyInfoHashesCache];
XCTAssertEqual(cache.count, 1, @"Cache should contain one entry");

BOOL yes = YES;
OCMStub([mockApplication isProtectedDataAvailable]).andReturn(YES).andDo(^(NSInvocation *invocation) {
self->spkiCache = nil;
[invocation setReturnValue:(void *)&yes];
});

// Perform one more cache operation to trigger filesystem write
[self->spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CFRelease(certificate);
[mockApplication stopMocking];
[expectation fulfill];
});
});

[self waitForExpectationsWithTimeout:5.0 handler:nil];
}

@end