TrustPin is a modern, lightweight, and secure iOS/macOS library designed to enforce SSL Certificate Pinning for native applications. Built with Swift Concurrency and following OWASP security recommendations, TrustPin prevents man-in-the-middle (MITM) attacks by ensuring server authenticity at the TLS level.
- β
Modern Swift Concurrency - Built with
async/awaitfor seamless integration - β Flexible Pinning Modes - Strict validation or permissive mode for development
- β Multiple Hash Algorithms - SHA-256 and SHA-512 certificate validation
- β Signed Configuration - Cryptographically signed pinning configurations
- β Multiple Integration Options - System-wide URLProtocol, URLSessionDelegate, or static helper methods
- β Intelligent Caching - 10-minute configuration cache with stale fallback
- β Comprehensive Logging - Configurable log levels for debugging and monitoring
- β Cross-Platform - iOS, macOS, watchOS, tvOS, and Mac Catalyst support
- β Enhanced Security - Advanced signature verification with multiple authentication methods
| Platform | Minimum Version | URLProtocol System-Wide Pinning |
|---|---|---|
| iOS | 13.0+ | β Supported |
| macOS | 13.0+ | β Supported |
| watchOS | 7.0+ | β Supported |
| tvOS | 13.0+ | β Supported |
| Mac Catalyst | 13.0+ | β Supported |
| visionOS | 2.0+ | β Supported |
Required: Swift 5.5+ for async/await support Note: URLProtocol-based features require iOS 13.0+ (available on all supported platforms)
Add TrustPin to your project using Xcode:
- File β Add Package Dependencies
- Enter repository URL:
https://github.com/trustpin-cloud/TrustPin-Swift.binary - Select version:
1.2.0or later
dependencies: [
.package(url: "https://github.com/trustpin-cloud/TrustPin-Swift.binary", from: "1.2.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "TrustPinKit", package: "TrustPin-Swift")
]
)
]Add TrustPin to your Podfile:
pod 'TrustPinKit'Then run:
pod installThe podspec is hosted at TrustPin-Swift.binary and published to the CocoaPods trunk.
import TrustPinKit
// Configure TrustPin with your project credentials
try await TrustPin.setup(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key",
mode: .strict // Recommended for production
)π‘ Find your credentials in the TrustPin Dashboard
β οΈ Important:TrustPin.setup()must be called only once during your app's lifecycle. Concurrent setup calls are not supported and will throwTrustPinErrors.invalidProjectConfig. If already initialized, subsequent calls will return immediately.
TrustPin offers two validation modes:
try await TrustPin.setup(
// ... your credentials
mode: .strict // Throws error for unregistered domains
)try await TrustPin.setup(
// ... your credentials
mode: .permissive // Allows unregistered domains to bypass pinning
)By default, TrustPin automatically enables certificate pinning for all URLSession requests in your app. This provides the broadest security coverage with zero additional configuration.
try await TrustPin.setup(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key",
mode: .strict
// autoRegisterURLProtocol: true (default)
)
// All URLSession instances now automatically use certificate pinning
// Including URLSession.shared and third-party networking librariesFor advanced scenarios where you need to control when system-wide pinning is active:
try await TrustPin.setup(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key",
mode: .strict,
autoRegisterURLProtocol: false // Disable automatic system-wide pinning
)
// Manually enable/disable system-wide pinning when needed
TrustPin.registerURLProtocol() // Enable
TrustPin.unregisterURLProtocol() // Disableπ‘ Recommendation: Use the default automatic protection unless you have specific requirements for controlling URLProtocol registration timing.
TrustPin offers three different ways to integrate certificate pinning into your application:
| Approach | Best For | Setup Complexity | Coverage |
|---|---|---|---|
| System-Wide URLProtocol (Recommended) | Most applications, zero-config after setup | π’ Minimal | All URLSession requests |
| URLSessionDelegate | Custom URLSession setups, granular control | π‘ Medium | Specific URLSession instances |
| Helper Methods | Explicit control, static method preference | π Per-request | Individual requests |
- β Broad protection: Automatically secures all URLSession requests
- β Zero configuration: Works with existing networking code
- β Third-party compatibility: Protects libraries using URLSession
- β Maintainability: Single setup call for entire app
- β Granular control: Only specific URLSession instances use pinning
- β Legacy compatibility: Works with older networking patterns
- β Custom delegation: Integrate with existing URLSessionDelegate code
- β Selective pinning: Mix pinned and non-pinned sessions in same app
- β Explicit requests: Clear intent for which requests use pinning
- β Static methods: Functional programming style
- β Migration friendly: Easy drop-in replacements for existing URLSession calls
- β Testing isolation: Test pinned vs non-pinned requests separately
The simplest approach - TrustPin automatically protects all HTTPS requests across your entire application:
import TrustPinKit
// In your AppDelegate or App struct
func configureApp() async throws {
// Setup TrustPin - URLProtocol is automatically registered
try await TrustPin.setup(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key",
mode: .strict
)
// That's it! All URLSession requests now use certificate pinning
}
// Anywhere in your app - pinning works automatically
class NetworkManager {
func fetchData() async throws -> Data {
// URLSession.shared automatically uses certificate pinning
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
func fetchWithCustomSession() async throws -> Data {
// Custom URLSessions also automatically use certificate pinning
let session = URLSession(configuration: .ephemeral)
let url = URL(string: "https://api.example.com/secure")!
let (data, _) = try await session.data(from: url)
return data
}
}
// Third-party libraries using URLSession are also protected!
// Alamofire, URLSession-based HTTP clients, etc. automatically get pinningπ― Benefits:
- Zero configuration after setup
- Protects all URLSession requests system-wide
- Works with third-party networking libraries
- Automatically secures
URLSession.sharedand custom sessions
For advanced scenarios where you need control over URLProtocol registration:
// Setup without auto-registration
try await TrustPin.setup(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key",
mode: .strict,
autoRegisterURLProtocol: false // Disable auto-registration
)
// Manually register when needed
TrustPin.registerURLProtocol()
// Unregister when no longer needed
TrustPin.unregisterURLProtocol()For scenarios where you prefer explicit control or want to use static helper methods:
import TrustPinKit
class NetworkManager {
// Async/await examples
func fetchDataWithHelpers() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await TrustPinURLProtocol.data(from: url)
return data
}
func downloadFileWithHelpers() async throws -> URL {
let request = URLRequest(url: URL(string: "https://api.example.com/file.pdf")!)
let (fileURL, _) = try await TrustPinURLProtocol.download(for: request)
return fileURL
}
// Completion handler examples
func fetchDataWithCompletionHandler() {
let url = URL(string: "https://api.example.com/data")!
let task = TrustPinURLProtocol.dataTask(with: url) { data, response, error in
if let error = error {
print("Error: \(error)")
return
}
if let data = data {
print("Received \(data.count) bytes")
}
}
task.resume()
}
// Custom session with pinning
func useCustomTrustPinSession() async throws -> Data {
let session = URLSession.trustPinSession(
configuration: .ephemeral
)
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await session.data(from: url)
return data
}
}π‘ When to use helper methods:
- When you need explicit control over individual requests
- For codebases that prefer static method calls
- When migrating from other networking libraries
- For testing scenarios where you want to isolate pinned requests
The traditional delegate-based approach (still fully supported):
import TrustPinKit
class NetworkManager {
private let trustPinDelegate = TrustPinURLSessionDelegate()
private lazy var session = URLSession(
configuration: .default,
delegate: trustPinDelegate,
delegateQueue: nil
)
func fetchData() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await session.data(from: url)
return data
}
}For custom networking stacks or certificate inspection:
import TrustPinKit
// Verify a PEM-encoded certificate for a specific domain
let domain = "api.example.com"
let pemCertificate = """
-----BEGIN CERTIFICATE-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END CERTIFICATE-----
"""
do {
try await TrustPin.verify(domain: domain, certificate: pemCertificate)
print("β
Certificate is valid and matches configured pins")
} catch TrustPinErrors.domainNotRegistered {
print("β οΈ Domain not configured for pinning")
} catch TrustPinErrors.pinsMismatch {
print("β Certificate doesn't match any configured pins")
} catch {
print("π₯ Verification failed: \(error)")
}import TrustPinKit
class AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Task {
do {
// Setup TrustPin once during app launch
try await TrustPin.setup(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-public-key",
mode: .strict
)
print("β
TrustPin initialized successfully")
} catch {
print("β TrustPin setup failed: \(error)")
}
}
return true
}
}import Alamofire
import TrustPinKit
// Create a custom SessionDelegate that extends TrustPinURLSessionDelegate
class TrustPinAlamofireDelegate: TrustPinURLSessionDelegate, SessionDelegate {
// TrustPinURLSessionDelegate handles the certificate validation
}
let trustPinDelegate = TrustPinAlamofireDelegate()
let session = Session(
configuration: .default,
delegate: trustPinDelegate,
rootQueue: DispatchQueue(label: "com.trustpin.alamofire.queue"),
startRequestsImmediately: true
)
// Use the session for your requests
let response = try await session.request("https://api.example.com/data")
.validate()
.serializingData()
.valueimport Foundation
import TrustPinKit
class SecureNetworkClient {
private let trustPinDelegate = TrustPinURLSessionDelegate()
private lazy var urlSession: URLSession = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
return URLSession(configuration: config, delegate: trustPinDelegate, delegateQueue: nil)
}()
func performSecureRequest(to url: URL) async throws -> (Data, URLResponse) {
return try await urlSession.data(from: url)
}
}| Mode | Behavior | Use Case |
|---|---|---|
.strict |
β Throws TrustPinErrors.domainNotRegistered for unregistered domains |
Production environments where all connections should be validated |
.permissive |
β Allows unregistered domains to bypass pinning | Development/Testing or apps connecting to dynamic domains |
- β Production applications
- β High-security environments
- β Known, fixed set of API endpoints
- β Compliance requirements
- β Development and staging
- β Applications with dynamic/unknown endpoints
- β Gradual migration to certificate pinning
- β Third-party SDK integrations
TrustPin provides detailed error types for proper handling:
do {
try await TrustPin.verify(domain: "api.example.com", certificate: pemCert)
} catch TrustPinErrors.domainNotRegistered {
// Domain not configured in TrustPin (only in strict mode)
handleUnregisteredDomain()
} catch TrustPinErrors.pinsMismatch {
// Certificate doesn't match configured pins - possible MITM
handleSecurityThreat()
} catch TrustPinErrors.allPinsExpired {
// All pins for domain have expired
handleExpiredPins()
} catch TrustPinErrors.invalidServerCert {
// Certificate format is invalid
handleInvalidCertificate()
} catch TrustPinErrors.invalidProjectConfig {
// Setup parameters are invalid
handleConfigurationError()
} catch TrustPinErrors.errorFetchingPinningInfo {
// Network error fetching configuration
handleNetworkError()
} catch TrustPinErrors.configurationValidationFailed {
// configuration signature validation failed
handleSignatureError()
}TrustPin provides comprehensive logging for debugging and monitoring:
// Set log level before setup
await TrustPin.set(logLevel: .debug)
// Available log levels:
// .none - No logging
// .error - Errors only
// .info - Errors and informational messages
// .debug - All messages including debug information- Call
TrustPin.setup()only once during app launch (typically inAppDelegate) - Handle setup errors gracefully - don't block app launch if TrustPin fails
- Set log level before setup for complete logging coverage
- Never call setup concurrently - it's not supported and will throw errors
- Use Task/async context for setup in synchronous app lifecycle methods
- Always use
.strictmode in production - Rotate pins before expiration
- Monitor pin validation failures
- Use HTTPS for all pinned domains
- Keep public keys secure and version-controlled
- Cache TrustPin configuration (handled automatically)
- Reuse URLSession instances with TrustPin delegate
- Use appropriate log levels (
.erroror.nonein production) - Initialize early to avoid setup delays during first network requests
- Start with
.permissivemode during development - Test all endpoints with pinning enabled
- Validate pin configurations in staging
- Switch to
.strictmode for production releases - Use debug logging to troubleshoot pinning issues
let trustPinDelegate = TrustPinURLSessionDelegate()
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 300
configuration.httpMaximumConnectionsPerHost = 4
let session = URLSession(
configuration: configuration,
delegate: trustPinDelegate,
delegateQueue: OperationQueue()
)func performNetworkRequest() async -> Data? {
do {
return try await secureNetworkRequest()
} catch TrustPinErrors.domainNotRegistered {
// Log security event but continue in permissive mode
logger.warning("Unregistered domain accessed")
return try await fallbackNetworkRequest()
} catch TrustPinErrors.pinsMismatch {
// This is a serious security issue - do not retry
logger.critical("Certificate pinning failed - possible MITM attack")
throw SecurityError.potentialMITMAttack
}
}TrustPin- Main SDK interface for setup and verificationTrustPinMode- Enum defining pinning behavior modes (.strict,.permissive)TrustPinURLSessionDelegate- URLSession delegate for automatic validationTrustPinURLProtocol- URLProtocol implementation for system-wide pinning (iOS 13.0+)TrustPinErrors- Error types for detailed error handlingTrustPinLogLevel- Logging configuration options (.none,.error,.info,.debug)
// Setup and configuration (standard)
static func setup(organizationId: String,
projectId: String,
publicKey: String,
mode: TrustPinMode = .strict,
autoRegisterURLProtocol: Bool = true) async throws
// Setup and configuration with custom CDN
static func setup(organizationId: String,
projectId: String,
publicKey: String,
configurationURL: URL,
mode: TrustPinMode = .strict,
autoRegisterURLProtocol: Bool = true) async throws
// Manual verification
static func verify(domain: String, certificate: String) async throws
// System-wide URLProtocol control
static func registerURLProtocol() // Enable system-wide pinning
static func unregisterURLProtocol() // Disable system-wide pinning
// Logging configuration
static func set(logLevel: TrustPinLogLevel) async// Async/await data methods with automatic pinning
TrustPinURLProtocol.data(for: URLRequest, using: URLSession? = nil) async throws -> (Data, URLResponse)
TrustPinURLProtocol.data(from: URL, using: URLSession? = nil) async throws -> (Data, URLResponse)
// Async/await download methods with automatic pinning
TrustPinURLProtocol.download(for: URLRequest, using: URLSession? = nil) async throws -> (URL, URLResponse)
TrustPinURLProtocol.download(from: URL, using: URLSession? = nil) async throws -> (URL, URLResponse)
// Completion handler methods with automatic pinning
TrustPinURLProtocol.dataTask(with: URLRequest, using: URLSession? = nil, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
TrustPinURLProtocol.dataTask(with: URL, using: URLSession? = nil, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
TrustPinURLProtocol.downloadTask(with: URLRequest, using: URLSession? = nil, completionHandler: @escaping @Sendable (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask
TrustPinURLProtocol.downloadTask(with: URL, using: URLSession? = nil, completionHandler: @escaping @Sendable (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask
// Create URLSession with pinning enabled
URLSession.trustPinSession(configuration: URLSessionConfiguration = .default,
delegate: URLSessionDelegate? = nil,
delegateQueue: OperationQueue? = nil) -> URLSession- β Verify organization ID, project ID, and public key are correct
- β Check for extra whitespace or newlines in credentials
- β Ensure public key is properly base64-encoded
- β
Avoid concurrent setup calls - only call
TrustPin.setup()once per app lifecycle - β Check for multiple setup attempts - if already initialized, subsequent calls return immediately
- β Confirm domain is registered in TrustPin dashboard
- β Check certificate format (must be PEM-encoded)
- β Verify pins haven't expired
- β
Test with
.permissivemode first
- β Ensure you're using the correct URLSession delegate
- β Check for retain cycles with URLSession
- β Verify network connectivity
- β Check if URLProtocol is properly registered (when using system-wide pinning)
- β
Verify
autoRegisterURLProtocol: truewas used during setup (default) - β Check that you're testing with HTTPS URLs (HTTP is ignored)
- β Ensure URLProtocol hasn't been unregistered elsewhere in the app
- β
Test with
TrustPin.registerURLProtocol()to manually re-register
- β Ensure you're targeting iOS 13.0+ or equivalent platform versions
- β Check that TrustPin has been set up before using helper methods
- β
Use
TrustPinURLProtocol.prefix for static helper methods - β
Import
TrustPinKitmodule
- Enable debug logging:
await TrustPin.set(logLevel: .debug) - Test with permissive mode first
- Verify credentials in TrustPin dashboard
- Check certificate expiration dates
- API Documentation: TrustPin iOS SDK Docs
- Dashboard: TrustPin Cloud Console
- Support: Contact TrustPin
This project is licensed under the TrustPin Binary License Agreement - see the LICENSE file for details.
Commercial License: For enterprise licensing or custom agreements, contact [email protected]
Attribution Required: When using this software, you must display "Uses TrustPinβ’ technology β https://trustpin.cloud" in your application.
We welcome your feedback and questions!
- π§ Email: [email protected]
- π Website: https://trustpin.cloud
- π Issues: GitHub Issues
Built with β€οΈ by the TrustPin team