// Swift iOS - SECURE: Proper certificate validation with pinning
import Foundation
import Network
import Security
import CryptoKit
import CommonCrypto
class SecureNetworkManager {
private let session: URLSession
private let pinnedCertificates: [String] // SHA-256 hashes
private let trustedDomains: [String]
init(pinnedCertHashes: [String] = [], trustedDomains: [String] = []) {
self.pinnedCertificates = pinnedCertHashes
self.trustedDomains = trustedDomains
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 60
// Enable TLS 1.2+ only
config.tlsMinimumSupportedProtocolVersion = .TLSv12
self.session = URLSession(
configuration: config,
delegate: self,
delegateQueue: nil
)
}
func makeSecureRequest(to url: URL, completion: @escaping (Result<Data, NetworkError>) -> Void) {
// Validate URL scheme
guard url.scheme == "https" else {
completion(.failure(.insecureScheme))
return
}
// Validate domain if restricted
if !trustedDomains.isEmpty {
guard let host = url.host, isDomainTrusted(host) else {
completion(.failure(.untrustedDomain))
return
}
}
let task = session.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.networkError(error)))
return
}
// Validate HTTPS response
guard let httpResponse = response as? HTTPURLResponse,
let data = data else {
completion(.failure(.invalidResponse))
return
}
// Check for secure connection
if let url = httpResponse.url, url.scheme != "https" {
completion(.failure(.insecureResponse))
return
}
completion(.success(data))
}
task.resume()
}
private func isDomainTrusted(_ domain: String) -> Bool {
return trustedDomains.contains { trusted in
if trusted.hasPrefix("*.") {
let wildcardDomain = String(trusted.dropFirst(2))
return domain.hasSuffix(wildcardDomain)
}
return domain == trusted
}
}
}
// SECURE: Comprehensive certificate validation
extension SecureNetworkManager: URLSessionDelegate {
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// Only handle server trust challenges
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else {
completionHandler(.performDefaultHandling, nil)
return
}
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// Perform comprehensive certificate validation
let validationResult = validateServerTrust(serverTrust, for: challenge.protectionSpace.host)
switch validationResult {
case .success:
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
case .failure(let error):
print("Certificate validation failed: \(error.localizedDescription)")
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
private func validateServerTrust(_ serverTrust: SecTrust, for hostname: String) -> Result<Void, CertificateError> {
// Step 1: Set SSL policy for hostname verification
let policy = SecPolicyCreateSSL(true, hostname as CFString)
let status = SecTrustSetPolicies(serverTrust, policy)
guard status == errSecSuccess else {
return .failure(.policySetFailed)
}
// Step 2: Evaluate trust
var trustResult: SecTrustResultType = .invalid
let evaluationStatus = SecTrustEvaluate(serverTrust, &trustResult)
guard evaluationStatus == errSecSuccess else {
return .failure(.trustEvaluationFailed)
}
// Step 3: Check trust result
guard trustResult == .unspecified || trustResult == .proceed else {
return .failure(.trustValidationFailed(trustResult))
}
// Step 4: Validate certificate details
guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
return .failure(.certificateNotFound)
}
// Step 5: Check certificate expiration
if let expirationDate = getCertificateExpirationDate(serverCertificate) {
let now = Date()
if expirationDate < now {
return .failure(.certificateExpired)
}
// Warn if certificate expires soon (30 days)
if expirationDate.timeIntervalSince(now) < 30 * 24 * 60 * 60 {
print("Warning: Certificate for \(hostname) expires soon: \(expirationDate)")
}
}
// Step 6: Perform certificate pinning if configured
if !pinnedCertificates.isEmpty {
let pinningResult = validateCertificatePinning(serverTrust)
if case .failure(let error) = pinningResult {
return .failure(error)
}
}
// Step 7: Additional certificate validation
return validateCertificateExtended(serverCertificate, hostname: hostname)
}
private func validateCertificatePinning(_ serverTrust: SecTrust) -> Result<Void, CertificateError> {
let certificateCount = SecTrustGetCertificateCount(serverTrust)
for i in 0..<certificateCount {
guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, i) else {
continue
}
if let hash = getCertificatePublicKeyHash(certificate),
pinnedCertificates.contains(hash) {
return .success(())
}
}
return .failure(.certificatePinningFailed)
}
private func getCertificatePublicKeyHash(_ certificate: SecCertificate) -> String? {
guard let publicKey = SecCertificateCopyKey(certificate) else {
return nil
}
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) else {
return nil
}
let data = publicKeyData as Data
let hash = SHA256.hash(data: data)
return "sha256:" + Data(hash).base64EncodedString()
}
private func getCertificateExpirationDate(_ certificate: SecCertificate) -> Date? {
guard let certificateData = SecCertificateCopyData(certificate) else {
return nil
}
let data = certificateData as Data
// Parse X.509 certificate (simplified - use proper ASN.1 parsing in production)
// This is a basic implementation - consider using a proper certificate parsing library
return nil // Placeholder - implement proper X.509 parsing
}
private func validateCertificateExtended(_ certificate: SecCertificate, hostname: String) -> Result<Void, CertificateError> {
// Validate certificate subject
guard let subject = getCertificateSubject(certificate) else {
return .failure(.invalidCertificateSubject)
}
// Check for critical extensions
if !hasCriticalExtensions(certificate) {
return .failure(.missingCriticalExtensions)
}
return .success(())
}
private func getCertificateSubject(_ certificate: SecCertificate) -> String? {
var commonName: CFString?
let status = SecCertificateCopyCommonName(certificate, &commonName)
guard status == errSecSuccess, let cn = commonName else {
return nil
}
return cn as String
}
private func hasCriticalExtensions(_ certificate: SecCertificate) -> Bool {
// Check for required extensions like Subject Alternative Name, Key Usage, etc.
// This is a simplified check - implement proper extension validation
return true // Placeholder
}
}
// SECURE: API client with proper certificate validation
class SecureAPIClient {
private let networkManager: SecureNetworkManager
init() {
// Configure with certificate pinning and trusted domains
self.networkManager = SecureNetworkManager(
pinnedCertHashes: [
"sha256:YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", // Example pin
"sha256:Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=" // Backup pin
],
trustedDomains: [
"api.example.com",
"*.example.com"
]
)
}
func loginUser(username: String, password: String, completion: @escaping (Result<AuthResponse, NetworkError>) -> Void) {
guard let url = URL(string: "https://api.example.com/login") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let credentials = LoginRequest(username: username, password: password)
do {
request.httpBody = try JSONEncoder().encode(credentials)
} catch {
completion(.failure(.encodingError))
return
}
networkManager.makeSecureRequest(to: url) { result in
switch result {
case .success(let data):
do {
let authResponse = try JSONDecoder().decode(AuthResponse.self, from: data)
completion(.success(authResponse))
} catch {
completion(.failure(.decodingError))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
// Supporting types
struct LoginRequest: Codable {
let username: String
let password: String
}
struct AuthResponse: Codable {
let token: String
let expiresIn: Int
}
enum NetworkError: Error {
case insecureScheme
case untrustedDomain
case networkError(Error)
case invalidResponse
case insecureResponse
case invalidURL
case encodingError
case decodingError
}
enum CertificateError: Error {
case policySetFailed
case trustEvaluationFailed
case trustValidationFailed(SecTrustResultType)
case certificateNotFound
case certificateExpired
case certificatePinningFailed
case invalidCertificateSubject
case missingCriticalExtensions
}