A robust, protocol-oriented Swift package for implementing SSL certificate pinning in iOS and macOS applications. This package follows Clean Architecture principles and provides multiple pinning strategies with strong type safety and concurrency support.
- Multiple pinning strategies:
- Certificate pinning
- Public key pinning
- SPKI (Subject Public Key Info) pinning
- Thread-safe implementation using Swift actors
- Protocol-oriented design for easy testing and mocking
- Configurable logging (Console or OSLog)
- Comprehensive error handling
- Support for iOS 13+ and macOS 10.15+
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/LovaRK/SwiftSSLPinning.git", from: "1.0.0")
]Or add it through Xcode:
- File > Swift Packages > Add Package Dependency
- Enter the repository URL
- Select the version you want to use
import SwiftSSLPinning
// Load your certificate from the app bundle
guard let certificateURL = Bundle.main.url(forResource: "your-certificate", withExtension: "cer"),
let certificateData = try? Data(contentsOf: certificateURL) else {
fatalError("Failed to load certificate")
}
// Create a certificate pinning strategy
let strategy = CertificatePinningStrategy(certificateData: certificateData)
// Create the pinning manager
let manager = SSLPinningManager(strategy: strategy)
// Create a URLSession with the pinning delegate
let delegate = SSLPinningDelegate(pinningManager: manager)
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
// Use the session for network requests
let task = session.dataTask(with: URL(string: "https://api.example.com")!) { data, response, error in
// Handle response
}
task.resume()import SwiftSSLPinning
// Your pre-computed public key hashes
let keyHashes: [Data] = // ... your SHA256 hashes of public keys
// Create a public key pinning strategy
let hasher = SHA256HashingService()
let strategy = PublicKeyPinningStrategy(pinnedKeyHashes: keyHashes, hasher: hasher)
// Create and use the manager as shown aboveimport SwiftSSLPinning
// Create a custom logger
let logger = OSLogger(subsystem: "com.yourapp", category: "Networking")
// Use it with the manager
let manager = SSLPinningManager(strategy: strategy, logger: logger)The package provides detailed error types through PinningError:
do {
try await manager.validateServerTrust(serverTrust)
} catch PinningError.certificateMismatch {
// Handle certificate mismatch
} catch PinningError.publicKeyMismatch {
// Handle public key mismatch
} catch {
// Handle other errors
}The package is designed for easy testing through protocol abstractions. You can mock the PinningStrategy, Logger, and HashingService for your tests.
Here's a step-by-step guide to implement SSL pinning in your app:
-
Export your server's SSL certificate:
openssl s_client -servername api.example.com -connect api.example.com:443 < /dev/null | openssl x509 -outform DER -out api.example.com.cer
-
Add the certificate to your Xcode project:
- Drag the .cer file into your Xcode project
- Make sure "Copy items if needed" is checked
- Add to your target
- Verify the certificate is included in "Copy Bundle Resources" in Build Phases
import SwiftSSLPinning
import Foundation
class NetworkManager {
private let session: URLSession
private let pinningManager: SSLPinningManager
init() throws {
// 1. Load certificate
guard let certificateURL = Bundle.main.url(forResource: "api.example.com", withExtension: "cer"),
let certificateData = try? Data(contentsOf: certificateURL) else {
throw PinningError.certificateNotFound
}
// 2. Create pinning strategy
let strategy = CertificatePinningStrategy(certificateData: certificateData)
// 3. Create pinning manager with logging
let logger = OSLogger(subsystem: "com.yourapp", category: "Networking")
pinningManager = SSLPinningManager(strategy: strategy, logger: logger)
// 4. Create URLSession with pinning delegate
let delegate = SSLPinningDelegate(pinningManager: pinningManager)
session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
}
func fetchData() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
do {
let (data, response) = try await session.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw PinningError.invalidResponse
}
return data
} catch let error as PinningError {
// Handle specific pinning errors
switch error {
case .certificateMismatch:
// Handle certificate mismatch (potential security breach)
logger.error("Certificate mismatch detected!")
case .publicKeyMismatch:
// Handle public key mismatch
logger.error("Public key mismatch detected!")
default:
// Handle other pinning errors
logger.error("Pinning error: \(error)")
}
throw error
}
}
}class YourViewController: UIViewController {
private var networkManager: NetworkManager?
override func viewDidLoad() {
super.viewDidLoad()
do {
networkManager = try NetworkManager()
} catch {
// Handle initialization error
showError("Failed to initialize network manager: \(error)")
}
}
func fetchData() {
Task {
do {
let data = try await networkManager?.fetchData()
// Handle successful response
updateUI(with: data)
} catch {
// Handle error
showError("Failed to fetch data: \(error)")
}
}
}
}-
Test with valid certificate:
func testValidCertificate() async throws { let manager = try NetworkManager() let data = try await manager.fetchData() XCTAssertNotNil(data) }
-
Test with invalid certificate:
func testInvalidCertificate() async { // Replace certificate with invalid one let manager = try? NetworkManager() do { _ = try await manager?.fetchData() XCTFail("Should throw certificate mismatch error") } catch PinningError.certificateMismatch { // Expected error } }
-
Certificate Management:
- Keep certificates up to date
- Implement certificate rotation strategy
- Store certificates securely
- Consider using multiple certificates for different environments
-
Error Handling:
- Log all pinning failures
- Implement proper error recovery
- Consider fallback strategies
- Monitor for security breaches
-
Testing:
- Test with valid and invalid certificates
- Test certificate rotation
- Test error scenarios
- Use mock certificates in development
-
Security:
- Never disable pinning in production
- Monitor for certificate changes
- Implement proper logging
- Consider using multiple pinning strategies
This package is available under the MIT license. See the LICENSE file for more info.