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:
3.1.1or later
dependencies: [
.package(url: "https://github.com/trustpin-cloud/TrustPin-Swift.binary", from: "3.1.1")
],
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
var config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key"
)
config.mode = .strict // Recommended
try await TrustPin.setup(config)💡 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:
var config = TrustPinConfiguration(organizationId: "your-org-id", projectId: "your-project-id", publicKey: "your-base64-public-key")
config.mode = .strict // Throws error for unregistered domains
try await TrustPin.setup(config)var config = TrustPinConfiguration(organizationId: "your-org-id", projectId: "your-project-id", publicKey: "your-base64-public-key")
config.mode = .permissive // Allows unregistered domains to bypass pinning
try await TrustPin.setup(config)TrustPin requires you to explicitly choose how to integrate certificate pinning into your app. The recommended approach is to use TrustPinURLSessionDelegate for specific sessions, providing precise control over which connections are pinned.
let config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key"
)
try await TrustPin.setup(config)
// Use TrustPinURLSessionDelegate for specific URLSession instances
let trustPinDelegate = TrustPinURLSessionDelegate()
let session = URLSession(
configuration: .default,
delegate: trustPinDelegate,
delegateQueue: nil
)let config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key"
)
try await TrustPin.setup(config, autoRegisterURLProtocol: true)
// All URLSession instances now automatically use certificate pinning
// Including URLSession.shared and third-party networking librarieslet config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key"
)
try await TrustPin.setup(config)
// Manually enable/disable system-wide pinning when needed
TrustPin.registerURLProtocol() // Enable
TrustPin.unregisterURLProtocol() // Disable💡 Recommendation: Use URLSessionDelegate (default) for precise control and best practices. Use system-wide URLProtocol only when you need to protect third-party libraries or cannot modify URLSession creation code.
TrustPin offers three integration methods:
| Approach | Best For | Setup Complexity | Coverage |
|---|---|---|---|
| URLSessionDelegate (Recommended) | Most applications, precise control | 🟢 Minimal | Specific URLSession instances |
| System-Wide URLProtocol | Third-party library protection, legacy code | 🟡 Medium | All URLSession requests |
| Helper Methods | Explicit control, static method preference | 🟠 Per-request | Individual requests |
- ✅ Precise control: Only specific URLSession instances use pinning
- ✅ Best practices: Explicit delegate pattern following Apple guidelines
- ✅ Custom delegation: Integrate with existing URLSessionDelegate code
- ✅ Selective pinning: Mix pinned and non-pinned sessions in same app
- ✅ Predictable behavior: No global state changes
- ✅ Broad protection: Automatically secures all URLSession requests
- ✅ Zero configuration: Works with existing networking code without changes
- ✅ Third-party compatibility: Protects libraries using URLSession
⚠️ Global impact: Affects all URLSession instances in the 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 recommended approach - use TrustPinURLSessionDelegate for precise control over which URLSessions use certificate pinning:
import TrustPinKit
// In your AppDelegate or App struct
func configureApp() async throws {
// Setup TrustPin
let config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key"
)
try await TrustPin.setup(config)
}
// Create a network manager with pinned URLSession
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
}
func fetchWithCustomConfig() async throws -> Data {
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForRequest = 30
let customSession = URLSession(
configuration: config,
delegate: trustPinDelegate,
delegateQueue: nil
)
let url = URL(string: "https://api.example.com/secure")!
let (data, _) = try await customSession.data(from: url)
return data
}
}🎯 Benefits:
- Explicit control over which sessions use pinning
- No global state changes
- Follows Apple's recommended patterns
- Easy to test and debug
For scenarios where you need to protect third-party libraries or cannot modify URLSession creation:
import TrustPinKit
// In your AppDelegate or App struct
func configureApp() async throws {
// Setup TrustPin with system-wide URLProtocol registration
let config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key"
)
try await TrustPin.setup(config, autoRegisterURLProtocol: true)
// 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
}
}
// Third-party libraries using URLSession are also protected!
// Alamofire, URLSession-based HTTP clients, etc. automatically get pinning
⚠️ Note: This approach affects all URLSession instances globally. Use with caution.
For advanced scenarios where you need control over URLProtocol registration:
// Setup without auto-registration
let config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-base64-public-key"
)
try await TrustPin.setup(config)
// 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
let config = TrustPinConfiguration(
organizationId: "your-org-id",
projectId: "your-project-id",
publicKey: "your-public-key"
)
try await TrustPin.setup(config)
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; supports a default singleton and named multi-instancesTrustPinConfiguration- Value type grouping all setup options (v3 preferred)TrustPinMode- 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)
// ── Instance creation ──────────────────────────────────────────────────────
// Default instance (used by all static convenience methods)
static let `default`: TrustPin
// Named instance for library / multi-tenant use (process-global, thread-safe registry)
static func instance(id: String) -> TrustPin
// ── Setup (preferred — struct-based) ──────────────────────────────────────
// Instance method
func setup(_ configuration: TrustPinConfiguration) async throws
// Static convenience (delegates to TrustPin.default)
static func setup(_ configuration: TrustPinConfiguration,
autoRegisterURLProtocol: Bool = false) async throws
// ── Setup (compatibility overload — flat parameters, unchanged from v2) ───
static func setup(organizationId: String,
projectId: String,
publicKey: String,
mode: TrustPinMode = .strict,
logLevel: TrustPinLogLevel = .info,
configurationURL: URL? = nil,
autoRegisterURLProtocol: Bool = false) async throws
// ── Verification ──────────────────────────────────────────────────────────
func verify(domain: String, certificate: String) async throws // instance
static func verify(domain: String, certificate: String) async throws // → TrustPin.default
// ── Certificate fetch utility (v3 new) ───────────────────────────────────
// Opens an ephemeral TLS connection, returns the leaf certificate as PEM.
// Does NOT perform pin verification — use verify() after.
func fetchCertificate(host: String, port: Int = 443) async throws -> String
static func fetchCertificate(host: String, port: Int = 443) async throws -> String
// ── URLSessionDelegate (instance-bound) ──────────────────────────────────
func makeURLSessionDelegate() -> TrustPinURLSessionDelegate
// ── System-wide URLProtocol control (default instance only) ──────────────
static func registerURLProtocol() // Enable system-wide pinning
static func unregisterURLProtocol() // Disable system-wide pinning
// ── Logging ───────────────────────────────────────────────────────────────
func set(logLevel: TrustPinLogLevel) // instance
static func set(logLevel: TrustPinLogLevel) // → TrustPin.default// 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 (not enabled by 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 register if not done during setup
- ✅ 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 contact@trustpin.cloud
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: support@trustpin.cloud
- 🌐 Website: https://trustpin.cloud
- 📋 Issues: GitHub Issues
Built with ❤️ by the TrustPin team