2015-12-11 5 views

ответ

17

Swift 3 Update:

Просто определить класс делегата для NSURLSessionDelegate и реализовать функцию didReceiveChallenge (этот код адаптирован из Objective-C OWASP например):

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: "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) 
    } 

} 

(вы можете найти Gist for Swift 2 here - from the initial answer)

Затем создайте файл .der для ваш сайт использует openssl

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

и добавить его в проект xcode. Дважды проверьте, что он присутствует на вкладке Build phases, внутри списка Copy Bundle Resources. В противном случае перетащите его внутри этого списка.

Наконец использовать в своем коде, чтобы сделать запросы 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") 
} 
+0

Просто примечание, 'repeat {...} while (false)' здесь не имеет смысла. В коде OWASP Obj-C он функционирует, чтобы «вырваться» из кода в случае сбоя, но поскольку вы структурировали операторы 'if' по-разному, у него больше нет цели. –

+0

спасибо @MarcoMiltenburg. Я обновил свой ответ (и мой код). – lifeisfoo

+0

@lifeisfoo Обратите внимание, что сохранение сертификата в качестве актива действительно опасно. Действительно просто украсть сертификат, и злоумышленник может просто использовать его. – BilalReffas

1

openssl команда в ответ @ lifeisfoo будет давать ошибку в OS X для некоторых SSL сертификатов, которые используют новые шифры, как ECDSA.

Если вы получаете следующее сообщение об ошибке при запуске openssl команду в ответ @ lifeisfoo в:

write:errno=54 
    unable to load certificate 
    1769:error:0906D06C:PEM routines:PEM_read_bio:no start 
    line:/BuildRoot/Library/Caches/com.apple.xbs/Sources/OpenSSL098/OpenSSL09   
    8-59.60.1/src/crypto/pem/pem_lib.c:648:Expecting: TRUSTED CERTIFICATE 

Вы сертификат SSL веб-сайта, вероятно, использует алгоритм, который не поддерживается в значении по умолчанию OS X в openssl версия (v0.9.X, которая НЕ поддерживает ECDSA, среди прочих).

Вот исправление:

Чтобы получить правильный .der файл, вы должны сначала brew install openssl, а затем заменить команду openssl от @ lifeisfoo Ответим с:

/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl [rest of the above command]

Руководство по установке домохозяйства:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 

надеюсь, что поможет.

+0

Кроме того, если у вас возникли проблемы с ошибками, связанными с управлением сеансом, может потребоваться изменить: 'configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration()', 'config: NSURLSessionConfiguration.defaultSessionConfiguration()' – weltan

7

Вот обновленная версия для 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) 
    } 

} 
1

Благодаря примеру нашел на этом сайте: https://www.bugsee.com/blog/ssl-certificate-pinning-in-mobile-applications/ Я построил версию, Pins открытого ключа, а не весь сертификат (более удобно, если вы обновляете свой сертификат периодически).

import Foundation 

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) { 

    let serverTrust = challenge.protectionSpace.serverTrust; 

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

    // Evaluate server certificate 
    var result = SecTrustResultType.invalid; 
    SecTrustEvaluate(serverTrust!, &result) 
    var isServerTrusted = result == .unspecified || result == .proceed ? true : false; 

    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); 

      let pubKey = SecTrustCopyPublicKey(trust!); 

      var error:Unmanaged<CFError>? 
      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(); 
} 

} 
0

Сохраните сертификат (как .cer-файл) вашего веб-сайта в главном комплекте.Затем используйте this URLSessionDelegate метод:

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

    guard 
     challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, 
     let serverTrust = challenge.protectionSpace.serverTrust, 
     SecTrustEvaluate(serverTrust, nil) == errSecSuccess, 
     let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else { 

      reject(with: completionHandler) 
      return 
    } 

    let serverCertData = SecCertificateCopyData(serverCert) as Data 

    guard 
     let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"), 
     let localCertData = NSData(contentsOfFile: localCertPath) as Data?, 

     localCertData == serverCertData else { 

      reject(with: completionHandler) 
      return 
    } 

    accept(with: serverTrust, completionHandler) 

} 

...

func reject(with completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) { 
    completionHandler(.cancelAuthenticationChallenge, nil) 
} 

func accept(with serverTrust: SecTrust, _ completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) { 
    completionHandler(.useCredential, URLCredential(trust: serverTrust)) 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^