Skip to content

How to pin SSL certificate with EventSource? #85

@houmie

Description

@houmie

Great project but there is a weakness, how do we pin a SSL certificate? Otherwise anyone could just use Proxyman and do a man-in-the-middle attack to see what the app is sending out (such as api key etc)

I can use this code below with a normal URL connection and pin the certificate down like that. But how can I integrate this urlSession with swift-EventSource? I couldn't find a way yet...

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // Helper function to check for subdomains
        func containsSubdomain(host: String, subdomains: [String]) -> Bool {
            // Split the host into components
            let hostComponents = host.split(separator: ".")
            
            // Ensure there are enough components to check for subdomain
            guard hostComponents.count > 2 else { return false }
            
            // The subdomain is the first component of the host
            let subdomain = String(hostComponents[0])
            
            // Check if the subdomain matches any of the given subdomains
            return subdomains.contains(subdomain)
        }

        // Extract the host from the protection space
        let host = challenge.protectionSpace.host
        
        // Check if the current server type is not production
        if Constants.currentServerType != .production {
            // If not in production, bypass the certificate pinning check
            LogService.shared.logInfo("Bypassing certificate pinning check for non-production server.")
            completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
            return
        }
        
        // Proceed with certificate pinning check for production server
        guard let serverTrust = challenge.protectionSpace.serverTrust else {
            LogService.shared.logCritical("No server trust provided")
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        var error: CFError?
        guard SecTrustEvaluateWithError(serverTrust, &error) else {
            if let cfError = error {
                let nsError = cfError as Error as NSError
                LogService.shared.logCritical("SecTrustEvaluateWithError failed.", nsError)
            } else {
                LogService.shared.logCritical("SecTrustEvaluateWithError failed.")
            }
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        guard let certificateChain = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate],
              let serverCertificate = certificateChain.first else {
            LogService.shared.logCritical("Failed to get server certificate from chain.")
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data
        let decryptedBase64Certificate = Keys.Global().apiCert
        
        guard let localCertificateData = Data(base64Encoded: decryptedBase64Certificate) else {
            LogService.shared.logCritical("Failed to decode local certificate from base64.")
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        if serverCertificateData == localCertificateData {
            LogService.shared.logInfo("Server certificate matches pinned certificate.")
            let credential = URLCredential(trust: serverTrust)
            completionHandler(.useCredential, credential)
            return
        } else {
            LogService.shared.logCritical("Server certificate does not match pinned certificate.")
            delegate?.didReceiveCertificateMismatchError() // Notify the delegate
        }
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
    ```

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions