Saltar al contenido

Fijación de certificados de iOS con Swift y NSURLSession

Nuestro equipo de especialistas luego de muchos días de investigación y de recopilar de datos, hallamos los datos necesarios, queremos que resulte de utilidad para tu proyecto.

Solución:

Swift 3+ Actualizar:

Simplemente defina una clase de delegado para NSURLSessionDelegate e implementar la función didReceiveChallenge (este código está adaptado del ejemplo de OWASP de objetivo-c):

class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate 

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) 

        // Adapted from OWASP https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#iOS

        if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) 
            if let serverTrust = challenge.protectionSpace.serverTrust 
                let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil)

                if(isServerTrusted) 
                    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) 
                        let serverCertificateData = SecCertificateCopyData(serverCertificate)
                        let data = CFDataGetBytePtr(serverCertificateData);
                        let size = CFDataGetLength(serverCertificateData);
                        let cert1 = NSData(bytes: data, length: size)
                        let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der")

                        if let file = file_der 
                            if let cert2 = NSData(contentsOfFile: file) 
                                if cert1.isEqual(to: cert2 as Data) 
                                    completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))
                                    return
                                
                            
                        
                    
                
            
        

        // Pinning failed
        completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    


(puede encontrar un Gist para Swift 2 aquí, de la respuesta inicial)

Luego crea el .der archivo para su sitio web usando openssl

openssl s_client -connect my-https-website.com:443 -showcerts < /dev/null | openssl x509 -outform DER > my-https-website.der

y agréguelo al proyecto xcode. Verifique que esté presente en el Build phases pestaña, dentro de la Copy Bundle Resources lista. De lo contrario, arrástrelo y suéltelo dentro de esta lista.

Finalmente utilícelo en su código para realizar solicitudes de URL:

if let url = NSURL(string: "https://my-https-website.com") 

    let session = URLSession(
            configuration: URLSessionConfiguration.ephemeral,
            delegate: NSURLSessionPinningDelegate(),
            delegateQueue: nil)


    let task = session.dataTask(with: url as URL, completionHandler:  (data, response, error) -> Void in
        if error != nil 
            print("error: (error!.localizedDescription): (error!)")
         else if data != nil 
            if let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) 
                print("Received data:n(str)")
             else 
                print("Unable to convert data to text")
            
        
    )

    task.resume()
 else 
    print("Unable to create NSURL")

Gracias al ejemplo que se encuentra en este sitio: https://www.bugsee.com/blog/ssl-certificate-pinning-in-mobile-applications/ construí una versión que fija al público key y no el certificado completo (más conveniente si renueva su certificado periódicamente).

Actualizar: Se eliminó el desenvolvimiento forzado y se reemplazó SecTrustEvaluate.

import Foundation
import CommonCrypto

class SessionDelegate : NSObject, URLSessionDelegate 

private static let rsa2048Asn1Header:[UInt8] = [
    0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
    0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
];

private static let google_com_pubkey = ["4xVxzbEegwDBoyoGoJlKcwGM7hyquoFg4l+9um5oPOI="];
private static let google_com_full = ["KjLxfxajzmBH0fTH1/oujb6R5fqBiLxl0zrl2xyFT2E="];

func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 

    guard let serverTrust = challenge.protectionSpace.serverTrust else 
        completionHandler(.cancelAuthenticationChallenge, nil);
        return;
    

    // Set SSL policies for domain name check
    let policies = NSMutableArray();
    policies.add(SecPolicyCreateSSL(true, (challenge.protectionSpace.host as CFString)));
    SecTrustSetPolicies(serverTrust, policies);

    var isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil);

    if(isServerTrusted && challenge.protectionSpace.host == "www.google.com") 
        let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
        //Compare public key
        if #available(iOS 10.0, *) 
            let policy = SecPolicyCreateBasicX509();
            let cfCertificates = [certificate] as CFArray;

            var trust: SecTrust?
            SecTrustCreateWithCertificates(cfCertificates, policy, &trust);

            guard trust != nil, let pubKey = SecTrustCopyPublicKey(trust!) else 
                completionHandler(.cancelAuthenticationChallenge, nil);
                return;
            

            var error:Unmanaged?
            if let pubKeyData = SecKeyCopyExternalRepresentation(pubKey, &error) 
                var keyWithHeader = Data(bytes: SessionDelegate.rsa2048Asn1Header);
                keyWithHeader.append(pubKeyData as Data);
                let sha256Key = sha256(keyWithHeader);
                if(!SessionDelegate.google_com_pubkey.contains(sha256Key)) 
                    isServerTrusted = false;
                
             else 
                isServerTrusted = false;
            
         else  //Compare full certificate
            let remoteCertificateData = SecCertificateCopyData(certificate!) as Data;
            let sha256Data = sha256(remoteCertificateData);
            if(!SessionDelegate.google_com_full.contains(sha256Data)) 
                isServerTrusted = false;
            
        
    

    if(isServerTrusted) 
        let credential = URLCredential(trust: serverTrust);
        completionHandler(.useCredential, credential);
     else 
        completionHandler(.cancelAuthenticationChallenge, nil);
    



func sha256(_ data : Data) -> String 
    var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))
    data.withUnsafeBytes 
        _ = CC_SHA256($0, CC_LONG(data.count), &hash)
    
    return Data(bytes: hash).base64EncodedString();



Aquí hay una versión actualizada para Swift 3

import Foundation
import Security

class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate 

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) 

        // Adapted from OWASP https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#iOS

        if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) 
            if let serverTrust = challenge.protectionSpace.serverTrust 
                var secresult = SecTrustResultType.invalid
                let status = SecTrustEvaluate(serverTrust, &secresult)

                if(errSecSuccess == status) 
                    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) 
                        let serverCertificateData = SecCertificateCopyData(serverCertificate)
                        let data = CFDataGetBytePtr(serverCertificateData);
                        let size = CFDataGetLength(serverCertificateData);
                        let cert1 = NSData(bytes: data, length: size)
                        let file_der = Bundle.main.path(forResource: "name-of-cert-file", ofType: "cer")

                        if let file = file_der 
                            if let cert2 = NSData(contentsOfFile: file) 
                                if cert1.isEqual(to: cert2 as Data) 
                                    completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))
                                    return
                                
                            
                        
                    
                
            
        

        // Pinning failed
        completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    


Tienes la opción de añadir valor a nuestro contenido informacional dando tu experiencia en las notas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *