Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to pin SSL certificate with EventSource? #85

Open
houmie opened this issue Oct 11, 2024 · 1 comment
Open

How to pin SSL certificate with EventSource? #85

houmie opened this issue Oct 11, 2024 · 1 comment
Labels
enhancement New feature or request

Comments

@houmie
Copy link

houmie commented Oct 11, 2024

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)
    }
    ```
@tanderson-ld
Copy link
Contributor

Hi @houmie, thank you for posting. This repo is mainly intended for consumption in our other SDKs where certificate pinning is not required. We are always open to public contributions if you see a clean way to integrate pinning into the configuration. Another option is you fork the repo and perhaps tweak this region of the code.

@tanderson-ld tanderson-ld added the enhancement New feature or request label Oct 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants