Skip to content

TrustKit 4.0 - #378

Open
alexanderdibenedetto wants to merge 10 commits into
masterfrom
v4-0-0
Open

alexanderdibenedetto wants to merge 10 commits into
masterfrom
v4-0-0

Conversation

@alexanderdibenedetto

Copy link
Copy Markdown
Contributor

TrustKit 4.0 is a major release. It raises the minimum OS versions, drops CocoaPods, adds visionOS, logs through os_log, and accepts any domain name or IP address in the pinning configuration. Some of these changes affect which connections get pinned, so test this beta before you ship it. Please report problems in Issues.

Breaking changes

Requirements

  • Minimum OS versions are now iOS 15, macOS 12, Mac Catalyst 15, tvOS 15, watchOS 9 and visionOS 1. They were iOS 12, macOS 10.13, tvOS 12 and watchOS 4.
  • Swift Package Manager needs Xcode 16 or later (swift-tools-version:6.0). So does building from the Xcode project, which now uses the Xcode 16 project format. TrustKit itself is built and tested with Xcode 27.
  • CocoaPods is no longer supported. TrustKit 4.0 and later aren't published to CocoaPods, but 3.x is still available there. Use Swift Package Manager, or the static XCFramework (TrustKit-static.zip) attached to each release.
  • The TrustKit_Static Xcode target is removed. Use TrustKit-static.zip instead.

Removed APIs

  • +[TrustKit setLoggerBlock:] is removed. TrustKit now logs through os_log (see Logging). To act on validation results in code, set pinningValidatorCallback.
  • kTSKPublicKeyAlgorithms, TSKSupportedAlgorithm, kTSKAlgorithmRsa2048, kTSKAlgorithmRsa4096, kTSKAlgorithmEcDsaSecp256r1 and kTSKAlgorithmEcDsaSecp384r1 are removed. They have been deprecated since 1.6.0, so remove them from your code. A configuration or Info.plist that still sets TSKPublicKeyAlgorithms keeps working, because the key is ignored.

macOS-only symbols no longer compile on other platforms

kTSKIgnorePinningForUserDefinedTrustAnchors and TSKTrustEvaluationFailedUserDefinedTrustAnchor are now marked unavailable on iOS, Mac Catalyst, tvOS, watchOS and visionOS. Using them there is a compile error. In code shared across platforms, wrap them in #if TARGET_OS_OSX (Objective-C) or #if os(macOS) (Swift).

Header location

The public headers moved to TrustKit/public/TrustKit/. #import <TrustKit/TrustKit.h> and @import TrustKit; now work the same way with Swift Package Manager and with the framework. If you have header search paths pointing into a TrustKit checkout, update them.

Static XCFramework in apps without Swift

Part of TrustKit is now written in Swift. This is internal only, and the public API hasn't changed. If your app has no Swift code of its own and targets an OS earlier than iOS 16, tvOS 16, Mac Catalyst 16 or macOS 13, linking the XCFramework fails with Undefined symbols … __swift_FORCE_LOAD_$_swiftCompatibility56. To fix it, add an empty Swift file to your app target.

Swift contributions

Starting with 4.0, TrustKit accepts contributions in Swift. This release ships TrustKit's first Swift code. Over time we plan to move the Objective-C classes to Swift that Objective-C can still call, so existing apps keep working unchanged.

  • Put new Swift files in TrustKit/Swift/ and add them to each framework target in TrustKit.xcodeproj.
  • Swift code builds in Swift 6 language mode, with strict concurrency checking. scripts/lint.sh runs swift-format and SwiftLint, and CI fails if lint fails.
  • For now, Swift code is internal only. Swift Package Manager builds it as a separate target, which doesn't support public Swift API yet. Open an issue before you add a public Swift type.

Pinning configuration (#333, #341)

These changes affect which connections are pinned, so review your configuration when you upgrade.

  • Any domain name or IP address can be pinned. This includes internal names such as api.internal or localhost, and IPv4 or IPv6 addresses. 3.x rejected names that didn't end in an entry of its bundled public suffix list. That list is now removed.
  • Keys that can never match now throw an exception at startup. Examples are *.example.com, or any key containing :, /, @ or a space. 3.x loaded these keys, and the domain silently stayed unpinned. Like any other configuration error, this raises an NSException from initSharedInstanceWithConfiguration:. If your app downloads its configuration at runtime, validate it before you pass it to TrustKit.
  • Matching now ignores case and a trailing dot. IPv6 addresses match with or without brackets, and in any notation. In 3.x, a key such as API.example.com never matched, and the host stayed unpinned without any warning. In 4.0 that key matches and the host is pinned.
  • kTSKIncludeSubdomains now uses plain suffix matching. It throws for an IP address or a single-label domain such as com. Otherwise it is allowed on any domain, including public suffixes such as co.uk or appspot.com, which 3.x rejected.

Any key type can be pinned

TrustKit now hashes each certificate's Subject Public Key Info exactly as it is encoded in the certificate. Before, it rebuilt the Subject Public Key Info, and that only worked for a fixed list of key types.

  • Key types that 3.x couldn't handle now work, ECDSA P-521 for example. In 3.x, those certificates failed with TSKTrustEvaluationErrorCouldNotGenerateSpkiHash, and the connection was blocked.
  • Pins for standard RSA 2048, 3072 and 4096 keys and for ECDSA P-256 and P-384 keys are unchanged. If a certificate encodes its key in a non-standard way, its 4.0 pin can differ from its 3.x pin. Regenerate the pin to check.
  • get_pin_from_certificate.sh replaces the Python script. It computes pins the same way TrustKit does, and takes a PEM or DER file: ./get_pin_from_certificate.sh ca.pem. Don't re-encode the key first, for example with openssl pkey. For some certificates that changes the bytes, and the pin won't match.

Logging

TrustKit logs through the unified logging system (#128), under the subsystem com.datatheorem.trustkit and the category TrustKit.

  • Validation failures and other errors are logged at the error level. Successful validations are logged at the info level, and the detail of each step at the debug level.
  • Release builds now log too. 3.x didn't log anything in Release builds. Pinned hostnames are logged as public data, so they appear in device logs.
  • To watch the logs, run log stream --level debug --predicate 'subsystem == "com.datatheorem.trustkit"'. For the simulator, put xcrun simctl spawn booted in front of it.

Pin failure reports

The app-platform field now reports VISIONOS for visionOS apps, MAC_CATALYST for Mac Catalyst apps, and IOS_ON_MAC for iPhone or iPad apps running on a Mac. 3.x reported the last two as IOS. If your report server filters on this field, update it. The other report fields haven't changed.

New platforms

  • visionOS is supported through Swift Package Manager, the Xcode project and the static XCFramework.
  • Mac Catalyst is now officially supported. It is declared in Package.swift, tested in CI and has its own slice in the XCFramework.
  • The static XCFramework now also includes arm64e slices.

Docs and demos

  • In the getting started guide, the UIWebView section is replaced by a WKWebView example in Objective-C and Swift. The example validates off the main thread, and both demo apps include it.
  • The demo apps now use TrustKit as a local Swift package and use the UIScene lifecycle.

For contributors

  • CI runs the unit tests on macOS, Mac Catalyst, and the iOS, tvOS and visionOS simulators. It also runs UI tests for both demo apps. watchOS is turned off for now, until a runner image issue is fixed.
  • scripts/lint.sh runs swift-format, SwiftLint and clang-format, and CI fails when lint fails.
  • OCMock now comes from Swift Package Manager instead of bundled binaries.

What's Changed

Full Changelog: 3.0.8...4.0.0-beta.1

alexanderdibenedetto and others added 9 commits October 19, 2026 10:22
…de 27; Drop Cocoapods support; Add visionOS support (#366)

* Remove TrustKit_Static product

* Apply hardening settings.

* Update min versions to align to Xcode 27 building.

* Remove custom settings; align min versions; align C/C++ versions.

* Remove target build settings duplicated at the project level

* Update license text to latest MIT template and update year.

* Build CI and releases with Xcode 27; move OCMock to SPM

Drop project-level staticlib and LTO, disable pointer auth in Debug,
pin OCMock by commit, and remove the vendored OCMock references.

* Use SPM for the demo project and remove CocoaPods

Reference the repo root as a local Swift package, delete the Podfile,
Pods and workspace, and raise the demo's iOS deployment target to 15.0.

* Drop CocoaPods; make <TrustKit/TrustKit.h> work through SPM

Move the public headers into public/TrustKit/ so SPM consumers can use
the framework-style import, and check it in validate_swift_package.sh.
Remove the podspec, and update the README and getting-started guide.

* Set minimums to Xcode 27's floors; fix demo launch on iOS 27

Lower tvOS to 15 and declare iOS 15, macOS 12, tvOS 15 and watchOS 9
in Package.swift (tools 5.7) and the README. Drop availability checks
and deprecated Security fallbacks that the new minimums make dead, adopt
the UIScene lifecycle in both demos, and remove the stale circle.yml
reference.

* Add visionOS support, watchOS tests and demo UI tests

Add visionOS framework and test targets (visionOS 1.0), include visionOS
in Package.swift, the XCFramework, CI and pin failure reports, and run
the unit test suite on watchOS. Give both demos good-pin, bad-pin and
unpinned buttons with UI tests run in a new CI job, and evaluate trust
off the main thread.

* Remove the stale TrustKit_Static scheme

* Make CI run on every PR.

* Set UI Tests team to DT.

* Download missing simulator runtime if needed; fixes watchos-simulator tests.

* Support Mac Catalyst and report Catalyst and iOS-on-Mac separately

Enable Mac Catalyst on the iOS framework and test targets, declare it in
Package.swift, and add it to CI, the XCFramework and package validation.
Pin failure reports now send MAC_CATALYST and IOS_ON_MAC.

* Enable module verifier for all C/C++ emitted and supported versions of TrustKit.

* Verify the module as Objective-C++ too
(TrustKit.xcodeproj/project.pbxproj)

Stop demo UI test failures from hanging CI and report the alert contents
(scripts/run_demo_ui_tests.sh, TrustKitDemo/TrustKitDemoUITests/TrustKitDemoUITests.swift)

* Add the simulator runtime image directly when the device is unavailable

* Install the watchOS simulator runtime explicitly on CI

Replace the runtime check in the test scripts with an install step that
runs before the watchOS tests: warm up CoreSimulator, export the runtime
image and add it with simctl.

* Reinstall the watchOS runtime on CI only when the simulator is unusable

Always warm up CoreSimulator. If the simulator still isn't available,
replace the registered runtime image with a fresh export, since adding
a second copy is rejected as a duplicate.

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
* Finish the 4.0 cleanup and add a WKWebView pinning example

Move both Xcode projects to the current file format (objectVersion 77)
and apply Xcode 27's recommended settings to the demo project. Mark the
two macOS-only symbols API_UNAVAILABLE instead of NS_AVAILABLE_MAC(10_9),
drop the always-true OS version check in the reporter, and remove or
correct comments that described code paths for OS versions TrustKit no
longer supports. Replace the UIWebView section of the getting started
guide with a WKWebView example that validates off the main thread, and
add that example to both demos with UI tests for a pinned page that
loads and one that is blocked.

* Only report Blocked in the demo web views when TrustKit blocked the load

Record the host whenever TrustKit cancels a challenge and show Blocked
only for failed navigations to that host, naming the failing URL. Other
cancelled navigations, such as an interrupted page load, no longer show
an alert.

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
* Replace TSKLog and +setLoggerBlock: with os_log (#128)

TrustKit now logs through os_log under the com.datatheorem.trustkit
subsystem and the TrustKit category. Pin validation failures and other
errors use the error level, successful validations info, and per-step
details debug. Pinned hostnames are logged as public data, and pins are
logged in base64 to match the configuration format.

+[TrustKit setLoggerBlock:] is removed; use pinningValidatorCallback to
handle validation results in code. Release builds now emit logs.

* Log nil-delegate NSURLSession creation at debug level

* Keep the logged configuration private and poll the log store in TSKLoggerTests

Report URIs can carry credentials or tokens, so the configuration dump
uses os_log's default private redaction. Log entries reach OSLogStore
asynchronously, so the test waits for the pin failure entry instead of
querying once.

* Check simulator availability with xcodebuild before skipping the runtime repair

simctl can list the watchOS simulator as available while xcodebuild sees
its runtime as (null), so the repair was skipped and the tests failed.

* Query the log store off the main thread in TSKLoggerTests

A blocked OSLogStore query on the main thread also blocked the test's
timeout. Polling on a background queue and waiting on a semaphore keeps
the 30-second limit, and a late result after a timeout is harmless.

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
#341) (#371)

* Drop watchOS from the CI test matrix until actions/runner-images#14946 is fixed

* Allow any domain name or IP address in the pinning configuration (#333, #341)

Removes the bundled public suffix list. Keys are validated by shape, IP addresses are canonicalized with inet_pton/inet_ntop, and includeSubdomains uses plain suffix matching.

* Allow repeated pin validation callbacks in end-to-end tests

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
* Hash each certificate's SPKI as encoded instead of rebuilding it (#236)

Read the Subject Public Key Info straight from the certificate's DER and hash it as is. This removes the five hardcoded ASN.1 headers and the extra trust evaluation per certificate on a cache miss, so any key type can be pinned. Adds a P-521 test certificate and malformed-input tests for the DER reader.

* Stop building arm64e and arm64e.x1 simulator slices

No simulator runtime can run them (all are arm64-only), and Xcode 27 ships no Swift standard library for them. Device, macOS and Mac Catalyst slices keep arm64e and arm64e.x1.

* Read the certificate's SPKI in Swift (#236)

First Swift code in TrustKit. The DER reader moves to TrustKit/Swift/SubjectPublicKeyInfo.swift, exposed to Objective-C as TSKSubjectPublicKeyInfo. Xcode compiles it into the framework targets; SwiftPM builds it as a TrustKitSwift target. Uses Swift 6 language mode and swift-tools-version 6.0.

Removes the recursive HEADER_SEARCH_PATHS, which resolved <TrustKit/…> to source headers and broke the framework module. Documents the empty Swift file that Objective-C-only apps using the static XCFramework need below iOS 16 / macOS 13.

* Replace the Python helper scripts

get_pin_from_certificate.sh replaces get_pin_from_certificate.py. It hashes any key type and reads PEM or DER, including with macOS's built-in LibreSSL. Removes generate_test_certificates.py; the test certificates are committed and nothing regenerates them.

* Hash the SPKI as encoded in the pin script; version the SPKI cache

openssl pkey re-encodes the key, so for some certificates the script's pin never matched what TrustKit computes. The script now hashes the SPKI bytes straight from the certificate. The shared SPKI cache file is renamed so that hashes cached by TrustKit 3.x are recomputed.

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
* Import what TSKPinningValidator_Private.h uses

* Add SwiftLint, swift-format and clang-format checks

* Apply lint formatting

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
* Skip files deleted from the working tree in lint.sh

* Remove the deprecated public key algorithm API

kTSKPublicKeyAlgorithms, TSKSupportedAlgorithm and the kTSKAlgorithm*
constants have been deprecated since 1.6.0. Configs that still set
TSKPublicKeyAlgorithms keep working because the parser ignores unknown keys.

Also delete the unused internal TSKPublicKeyAlgorithm.h and rename
TSKPublicKeyAlgorithmTests to TSKSPKIHashCacheTests, after the class it tests.

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
@alexanderdibenedetto
alexanderdibenedetto requested a review from a team October 26, 2026 17:48
Comment thread scripts/install_simulator_runtime.sh Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Besides the inline finding, I checked whether SPKI hashes cached by 3.x could be reused after the upgrade, which would block connections pinned with 4.0 pins. They can't: the shared cache identifier in TrustKit/Pinning/TSKSPKIHashCache.h changes from spki-hash.cache to spki-hash-v2.cache, so 4.0 starts with an empty cache.

Extended reasoning...

The release changes SPKI hashing from rebuilding the key with per-algorithm ASN.1 headers to hashing the SPKI exactly as encoded. Because the base branch's cache file name differs from this one, old cache entries are never loaded.

Comment thread TrustKit/parse_configuration.m Resolved
EthanArbuckle
EthanArbuckle previously approved these changes Oct 26, 2026
NSExceptional
NSExceptional previously approved these changes Oct 26, 2026
* Reject malformed expiration dates, report user-anchor failures on macOS, fix the exclude-subdomain key, and stop skipping TSK-prefixed delegates

* Remove the watchOS runtime workaround, correct the report ID docs, and refresh the Info.plist screenshot

* Update TrustKit/parse_configuration.m

Co-authored-by: uroboro <uroboro845@gmail.com>

* Fix linting.

* Fix apply suggestion from Fede

---------

Co-authored-by: Alexander DiBenedetto <adb@datatheorem.io>
Co-authored-by: uroboro <uroboro845@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

4 participants