Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ jobs:
platform: [ios-simulator, macos, maccatalyst, tvos-simulator, visionos-simulator]
steps:
- uses: actions/checkout@v7
- if: matrix.platform == 'watchos-simulator'
run: ./scripts/install_simulator_runtime.sh ${{ matrix.platform }}
- run: ./scripts/run_unit_tests.sh ${{ matrix.platform }} -retry-tests-on-failure -test-iterations 3
- uses: actions/upload-artifact@v7
if: failure()
Expand Down
2 changes: 1 addition & 1 deletion TrustKit/Reporting/vendor_identifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
#import <Foundation/Foundation.h>
#endif

// Will return the IDFV on platforms that support it (iOS, tvOS) and a randomly generated UUID on other platforms (macOS, watchOS)
// Not the IDFV: a random UUID generated once per install and stored in the App's user defaults, on every platform
NSString *identifier_for_vendor(void);
4 changes: 2 additions & 2 deletions TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ + (void)swizzleNSURLConnectionConstructors:(TrustKit *)trustKit {
RSSWReplacement({
NSURLConnection *connection;

if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"]) {
if ([delegate isKindOfClass:[TSKNSURLConnectionDelegateProxy class]]) {
// Don't proxy ourselves
connection = RSSWCallOriginal(request, delegate);
} else {
Expand All @@ -62,7 +62,7 @@ + (void)swizzleNSURLConnectionConstructors:(TrustKit *)trustKit {
RSSWReplacement({
NSURLConnection *connection;

if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"]) {
if ([delegate isKindOfClass:[TSKNSURLConnectionDelegateProxy class]]) {
// Don't proxy ourselves
connection = RSSWCallOriginal(request, delegate, startImmediately);
} else {
Expand Down
3 changes: 2 additions & 1 deletion TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#import "TSKNSURLSessionDelegateProxy.h"
#import "../Dependencies/RSSwizzle/RSSwizzle.h"
#import "../Reporting/TSKBackgroundReporter.h"
#import "../TSKLog.h"
#import "../public/TrustKit/TSKPinningValidator.h"
#import "../public/TrustKit/TSKTrustDecision.h"
Expand Down Expand Up @@ -48,7 +49,7 @@ + (void)swizzleNSURLSessionConstructors:(TrustKit *)trustKit {
}

// Do not swizzle TrustKit objects (such as the reporter)
else if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"]) {
else if ([delegate isKindOfClass:[TSKBackgroundReporter class]] || [delegate isKindOfClass:[TSKNSURLSessionDelegateProxy class]]) {
session = RSSWCallOriginal(configuration, delegate, queue);
} else {
// Replace the delegate with our own so we can intercept and handle authentication challenges
Expand Down
17 changes: 5 additions & 12 deletions TrustKit/TSKPinningValidator.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,6 @@ to platforms that support a user trust store (Mac OS).

@implementation TSKPinningValidator

+ (BOOL)allowsAdditionalTrustAnchors {
static const BOOL allowAdditionalTrustAnchors = {
#if DEBUG == 1
YES
#else
NO
#endif
};
return allowAdditionalTrustAnchors;
}

#pragma mark Instance Methods

- (instancetype _Nullable)initWithDomainPinningPolicies:(NSDictionary<NSString *, TKSDomainPinningPolicy *> *_Nonnull)domainPinningPolicies
Expand Down Expand Up @@ -127,7 +116,11 @@ - (TSKTrustDecision)evaluateTrust:(SecTrustRef _Nonnull)serverTrust forHostname:
} else
#endif
{
if (validationResult == TSKTrustEvaluationFailedNoMatchingPin) {
BOOL noMatchingPin = (validationResult == TSKTrustEvaluationFailedNoMatchingPin);
#if !TARGET_OS_IPHONE
noMatchingPin = noMatchingPin || (validationResult == TSKTrustEvaluationFailedUserDefinedTrustAnchor);
#endif
if (noMatchingPin) {
// Is pinning enforced?
if ([domainConfig[kTSKEnforcePinning] boolValue] == YES) {
// Yes - Block the connection
Expand Down
2 changes: 1 addition & 1 deletion TrustKit/TSKTrustKitConfig.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// Keys for each domain within the TSKPinnedDomains entry
const TSKDomainConfigurationKey kTSKPublicKeyHashes = @"TSKPublicKeyHashes";
const TSKDomainConfigurationKey kTSKEnforcePinning = @"TSKEnforcePinning";
const TSKDomainConfigurationKey kTSKExcludeSubdomainFromParentPolicy = @"kSKExcludeSubdomainFromParentPolicy";
const TSKDomainConfigurationKey kTSKExcludeSubdomainFromParentPolicy = @"TSKExcludeSubdomainFromParentPolicy";
Comment thread
alexanderdibenedetto marked this conversation as resolved.
Resolved

const TSKDomainConfigurationKey kTSKIncludeSubdomains = @"TSKIncludeSubdomains";
const TSKDomainConfigurationKey kTSKReportUris = @"TSKReportUris";
Expand Down
2 changes: 1 addition & 1 deletion TrustKit/TrustKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ - (void)sendValidationReport:(TSKPinningValidatorResult *)result
// Send a report only if the there was a pinning failure
if (validationResult != TSKTrustEvaluationSuccess) {
#if !TARGET_OS_IPHONE
if (validationResult != TSKTrustEvaluationFailedUserDefinedTrustAnchor)
if ((validationResult != TSKTrustEvaluationFailedUserDefinedTrustAnchor) || ![self.configuration[kTSKIgnorePinningForUserDefinedTrustAnchors] boolValue])
#endif
{
// Pin validation failed: retrieve the list of configured report URLs
Expand Down
15 changes: 10 additions & 5 deletions TrustKit/parse_configuration.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
@"TSKPinnedDomains key within TSKConfiguration."];
}

NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
dateFormat.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dateFormat.dateFormat = @"yyyy-MM-dd";
dateFormat.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];

for (NSString *configuredDomainName in trustKitArguments[kTSKPinnedDomains]) {
// Sanity checks on the domain name
NSString *domainName = canonicalHostname(configuredDomainName);
Expand Down Expand Up @@ -106,11 +111,11 @@
NSString *expirationDateStr = domainPinningPolicy[kTSKExpirationDate];
if (expirationDateStr != nil) {
// Convert the string in the yyyy-MM-dd format into an actual date in UTC
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
dateFormat.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dateFormat.dateFormat = @"yyyy-MM-dd";
dateFormat.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
NSDate *expirationDate = [dateFormat dateFromString:expirationDateStr];
NSDate *expirationDate = [expirationDateStr isKindOfClass:[NSString class]] ? [dateFormat dateFromString:expirationDateStr] : nil;
if (expirationDate == nil) {
[NSException raise:@"TrustKit configuration invalid"
format:@"TrustKit was initialized with an invalid value for %@ for domain %@; expected a date in yyyy-MM-dd format", kTSKExpirationDate, domainName];
}
domainFinalConfiguration[kTSKExpirationDate] = expirationDate;
}

Expand Down
7 changes: 4 additions & 3 deletions TrustKit/public/TrustKit/TSKTrustKitConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,16 @@ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKReportUris;
By default, pin failure reports are sent to a report server hosted by Data Theorem, for
detecting potential CA compromises and man-in-the-middle attacks, as well as providing a
free dashboard for developers; email info@datatheorem.com if you'd like a dashboard for
your App. Only pin failure reports are sent, which contain the App's bundle ID, the IDFV,
and the server's hostname and certificate chain that failed validation.
your App. Only pin failure reports are sent, which contain the App's bundle ID, a random UUID
that TrustKit generates once per install, and the server's hostname and certificate chain that
failed validation.
*/
FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKDisableDefaultReportUri;

/**
A string containing the date, in yyyy-MM-dd format, on which the domain's configured SSL
pins expire, thus disabling pinning validation. If the key is not set, then the pins do
not expire.
not expire. Any other value is a configuration error.

Expiration helps prevent connectivity issues in Apps which do not get updates to their
pin set, such as when the user disables App updates.
Expand Down
1 change: 1 addition & 0 deletions TrustKit/public/TrustKit/TrustKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ NS_ASSUME_NONNULL_BEGIN
| ____ TSKEnforcePinning | Boolean |
| ____ TSKReportUris | Array |
| ____ TSKDisableDefaultReportUri | Boolean |
| ____ TSKExpirationDate | String |
```

When setting the pinning policy programmatically, it has to be supplied to the
Expand Down
21 changes: 21 additions & 0 deletions TrustKitTests/TSKPinConfigurationTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ - (void)testDisablePinningForSubdomainAndNoPublicKey {

NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"unsecured.good.com", trustKitConfig[kTSKPinnedDomains]);
XCTAssertEqualObjects(serverConfigKey, @"unsecured.good.com", @"Did not receive a configuration for pinned subdomain");
XCTAssertEqualObjects(kTSKExcludeSubdomainFromParentPolicy, @"TSKExcludeSubdomainFromParentPolicy", @"Info.plist configurations use the documented key");
}

- (void)testExplicitNotDisablePinningForSubdomainAdditionalDomainKeys {
Expand Down Expand Up @@ -243,6 +244,26 @@ - (void)testExpirationDateIgnoresUserCalendar {
XCTAssertEqualObjects(trustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKExpirationDate], [gregorian dateFromComponents:components]);
}

- (void)testMalformedExpirationDateIsRejected {
for (id expirationDate in @[@"12/31/2026", @"2026-31-12", @"", @20261231, [NSDate date]]) {
XCTAssertThrows(
parseTrustKitConfiguration(@{
kTSKPinnedDomains: @ {
@"www.good.com": @ {
kTSKExpirationDate: expirationDate,
kTSKPublicKeyHashes: @[
@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=",
@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
]
}
}
}),
@"Expiration date %@ must be rejected",
expirationDate
);
}
}

- (void)testGetConfigurationPinningDisabled {
NSDictionary *trustKitConfig;
trustKitConfig = parseTrustKitConfiguration(@{
Expand Down
9 changes: 0 additions & 9 deletions TrustKitTests/TSKPinningValidatorTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,6 @@ - (void)resetSubjectPublicKeyInfoDiskCache;
- (void)waitForPendingSubjectPublicKeyInfoDiskWrites;
@end

static BOOL AllowsAdditionalTrustAnchors = YES; // toggle in tests if needed
@interface TestPinningValidator : TSKPinningValidator
@end
@implementation TestPinningValidator
+ (BOOL)allowsAdditionalTrustAnchors {
return AllowsAdditionalTrustAnchors;
}
@end

@interface TSKPinningValidatorTests : XCTestCase
@end

Expand Down
41 changes: 40 additions & 1 deletion TrustKitTests/TSKReporterTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ - (void)testSendReportFromValidationReport {
#if !TARGET_OS_IPHONE
// TEST USER-DEFINED SUCCESS
// Send a notification for a successful validation and ensure no report gets sent
pinReporterMock = OCMClassMock([TSKBackgroundReporter class]);
pinReporterMock = OCMStrictClassMock([TSKBackgroundReporter class]);
_trustKit.pinFailureReporter = pinReporterMock;

res = [[TSKPinningValidatorResult alloc] initWithServerHostname:@"www.test.com"
Expand All @@ -178,6 +178,45 @@ - (void)testSendReportFromValidationReport {
#endif
}

#if !TARGET_OS_IPHONE
- (void)testReportUserDefinedTrustAnchorWhenNotIgnored {
_trustKit = [[TrustKit alloc] initWithConfiguration:@{
kTSKIgnorePinningForUserDefinedTrustAnchors: @NO,
kTSKPinnedDomains: @ {
@"www.test.com": @ {
kTSKPublicKeyHashes: @[
@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key
@"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Fake key 2
]
}
}
}];

id pinReporterMock = OCMClassMock([TSKBackgroundReporter class]);
_trustKit.pinFailureReporter = pinReporterMock;
OCMExpect([pinReporterMock pinValidationFailedForHostname:@"www.test.com"
port:nil
certificateChain:OCMOCK_ANY
notedHostname:@"www.test.com"
reportURIs:OCMOCK_ANY
includeSubdomains:NO
enforcePinning:YES
knownPins:OCMOCK_ANY
validationResult:TSKTrustEvaluationFailedUserDefinedTrustAnchor
expirationDate:nil]);

TSKPinningValidatorResult *res = [[TSKPinningValidatorResult alloc] initWithServerHostname:@"www.test.com"
serverTrust:_testTrust
validationResult:TSKTrustEvaluationFailedUserDefinedTrustAnchor
finalTrustDecision:TSKTrustDecisionShouldBlockConnection
validationDuration:1.0];
[_trustKit sendValidationReport:res notedHostname:@"www.test.com" pinningPolicy:_trustKit.configuration[kTSKPinnedDomains][@"www.test.com"]];

[pinReporterMock verify];
[pinReporterMock stopMocking];
}
#endif

- (void)testReporter {
// Just try a simple valid case to see if we can post this to the default report URL
TSKBackgroundReporter *reporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:NO sharedContainerIdentifier:nil];
Expand Down
12 changes: 12 additions & 0 deletions TrustKitTests/TSKSwizzlingTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ @interface TSKSwizzlingTests : XCTestCase

@end

@interface TSKAppSessionDelegate : NSObject <NSURLSessionDelegate>
@end

@implementation TSKAppSessionDelegate
@end

/* Basic tests to ensure RSSwizzle does not trigger a crash because a method we are hooking has changed
*/
@implementation TSKSwizzlingTests
Expand Down Expand Up @@ -42,6 +48,12 @@ - (void)testNSURLSession {
TrustKit *trustKit = [[TrustKit alloc] initWithConfiguration:trustKitConfig];

[TSKNSURLSessionDelegateProxy swizzleNSURLSessionConstructors:trustKit];

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]
delegate:[[TSKAppSessionDelegate alloc] init]
delegateQueue:nil];
XCTAssertTrue([session.delegate isKindOfClass:[TSKNSURLSessionDelegateProxy class]], @"An app delegate whose class name starts with TSK must still be proxied");
[session invalidateAndCancel];
}

- (void)testNSURLConnection {
Expand Down
Binary file modified docs/images/linking3_dynamic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 0 additions & 54 deletions scripts/install_simulator_runtime.sh

This file was deleted.