2015-09-15 100 views
9

我想使用自簽名證書向自定義服務器發出HTTPS請求。我使用NSURLConnection的類和加工認證的挑戰,但總是在控制檯收到錯誤消息:使用自簽名證書在iOS 9中進行HTTPS請求

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802) 

則方法「連接:didFailWithError:」被調用,出現以下錯誤:

Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x150094100>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
    0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> 
)}, NSUnderlyingError=0x1504ae170 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
    0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> 
)}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
    0 : <SecIdentityRef: 0x15012cd40> 
    1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> 
)}, _kCFStreamErrorDomainKey=3, NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), _kCFStreamErrorCodeKey=-9802}}, NSErrorClientCertificateChainKey=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
    0 : <SecIdentityRef: 0x15012cd40> 
    1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> 
)}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSErrorClientCertificateStateKey=2} 

應用接收兩個驗證質詢(NSURLAuthenticationMethodClientCertificate和NSURLAuthenticationMethodServerTrust),並以下面的方式處理它們:

- (void) connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
{ 
    if(challenge.proposedCredential && !challenge.error) 
    { 
     [challenge.sender useCredential:challenge.proposedCredential forAuthenticationChallenge:challenge]; 

     return; 
    } 

    NSString *strAuthenticationMethod = challenge.protectionSpace.authenticationMethod; 
    NSLog(@"authentication method: %@", strAuthenticationMethod); 

    NSURLCredential *credential = nil; 
    if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) 
    { 
     // get identity and certificate from p.12 
     NSData *PKCS12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]]; 

     NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"password" forKey:(__bridge id)kSecImportExportPassphrase]; 
     CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); 
     OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)PKCS12Data,(__bridge CFDictionaryRef)optionsDictionary, &items); 

     SecIdentityRef identity = NULL; 
     SecCertificateRef certificate = NULL; 
     if(securityError == errSecSuccess) 
     { 
      CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0); 
      identity = (SecIdentityRef)CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity); 

      CFArrayRef array = (CFArrayRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemCertChain); 
      certificate = (SecCertificateRef)CFArrayGetValueAtIndex(array, 0); 
     } 

     credential = [NSURLCredential credentialWithIdentity:identity certificates:[NSArray arrayWithObject:(__bridge id)(certificate)] persistence:NSURLCredentialPersistenceNone]; 

     CFRelease(items); 
    } 
    else if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) 
    {  
     int trustCertificateCount = (int)SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust); 
     NSMutableArray *trustCertificates = [[NSMutableArray alloc] initWithCapacity:trustCertificateCount]; 
     for(int i = 0; i < trustCertificateCount; i ++) 
     { 
      SecCertificateRef trustCertificate = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i); 
      [trustCertificates addObject:(__bridge id) trustCertificate]; 
     }    

     SecPolicyRef policyRef = NULL; 
     policyRef = SecPolicyCreateSSL(YES, (__bridge CFStringRef) challenge.protectionSpace.host); 

     SecTrustRef trustRef = NULL; 
     if(policyRef) 
     { 
      SecTrustCreateWithCertificates((__bridge CFArrayRef) trustCertificates, policyRef, &trustRef); 
      CFRelease(policyRef); 
     } 

     if(trustRef) 
     { 
//   SecTrustSetAnchorCertificates(trustRef, (__bridge CFArrayRef) [NSArray array]); 
//   SecTrustSetAnchorCertificatesOnly(trustRef, NO); 

      SecTrustResultType result; 
      OSStatus trustEvalStatus = SecTrustEvaluate(trustRef, &result); 
      if(trustEvalStatus == errSecSuccess) 
      { 
       // just temporary attempt to make it working. 
       // i hope, there is no such problem, when we have final working version of certificates. 
       if(result == kSecTrustResultRecoverableTrustFailure) 
       { 
        CFDataRef errDataRef = SecTrustCopyExceptions(trustRef); 
        SecTrustSetExceptions(trustRef, errDataRef); 

        SecTrustEvaluate(trustRef, &result); 
       } 

       if(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) 
        credential = [NSURLCredential credentialForTrust:trustRef]; 
      } 

      CFRelease(trustRef); 
     } 
    } 
    else 
    { 
     DDLogWarn(@"Unexpected authentication method. Cancelling authentication ..."); 
     [challenge.sender cancelAuthenticationChallenge:challenge]; 
    } 

    if(credential) 
     [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; 
    else 
     [challenge.sender cancelAuthenticationChallenge:challenge]; 
} 

在CFNetwork診斷日誌中,我可以看到握手過程即將開始。至少應用程序發送「ClientHello」消息,然後服務器發送其「ServerHello」消息並要求進行身份驗證。這裏的應用程序嘗試發送身份驗證響應,但立即收到錯誤。 (同時,在服務器日誌中,我根本沒有看到任何有關握手的消息)。這是診斷記錄的一部分:

Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:49] 10:51:49.185 { 
    Authentication Challenge 
     Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
    Challenge: challenge space https://217.92.80.156:9090/, ServerTrustEvaluationRequested (Hash f9810ad8165b3620) 
    } [3:49] 
Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:50] 10:51:49.189 { 
    Use Credential 
     Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
    Credential: Name: server, Persistence: session 
    } [3:50] 
Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:51] 10:51:49.190 { 
    touchConnection 
       Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
    Timeout Interval: 60.000 seconds 
    } [3:51] 
Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:52] 10:51:49.192 { 
    Response Error 
    Request: <CFURLRequest 0x14e5d02a0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
     Error: Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
       0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> 
      )}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
       0 : <SecIdentityRef: 0x15012cd40> 
       1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> 
      )}, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802} 
    } [3:52] 

我們的後端實例可以在客戶端進行安裝,所以我不能設置Info.plist文件中的任何域例外。此外,應用程序可以通過IPv4格式的IP地址請求服務器,但不能通過域名(如我的示例中所示)。

我有什麼企圖:

  • 使用NSURLSession代替NSURLConnection的,但沒有成功;
  • 檢查了Apple的ATS服務器實施要求here(後端開發人員確信他的實施符合所有要求);
  • 根據stackoverflow和Apple開發人員論壇上解決的各種問題,爲設置信任驗證設置了錨定證書;
  • 特別注意similar帖子及其相關solution在開發者論壇;

我在iOS 9 GM種子(Build 13A340)和xCode 7 GM種子(Build 7A218)上測試了iPad Air 2上的https請求。重要提示:此功能在iOS 8下正常工作。考慮到這一點,我可能會認爲,問題在於我們的服務器,但我們的後端開發人員向我保證,一切都很好。

現在我出來的想法。如果有人能夠給我一個提示,或者至少提出一些其他的診斷方法,我會很感激,它會揭示特定的錯誤,比「致命的警報」更具體。

謝謝。

編輯1:SecTrustEvaluate總是返回kSecTrustResultRecoverableTrustFailure,這就是爲什麼我不得不尋找某種解決方法。

+0

您是否找到解決方案?我也有這個問題,我想使用我的本地服務器進行測試,但有了自簽名證書,這是不可能的,我得到同樣的錯誤... –

+0

還沒有,我必須從事其他高優先級任務由於幾個原因。如果我有解決方案,我會聯繫你。 – alfared

+0

這是一個iOS PITA協議錯誤。 (PITA代表....中的痛苦) – Josh

回答

0

這個問題是前一段時間解決的。原來是無效的自簽名證書。它沒有達到Apple的所有要求。不幸的是,我不知道,究竟是什麼。

3

你使用nscurl來診斷連接問題嗎?如果你有一臺運行OS X v10.11的Mac,你可以這樣運行:

/usr/bin/nscurl --ats-diagnostics https://www.yourdomain.com 

或者,如果你沒有10。11,你可以在這裏下載示例代碼:https://developer.apple.com/library/mac/samplecode/SC1236/,並與XCode中建立並像這樣運行它(更改路徑適合您的機器):

/Users/somebody/Library/Developer/Xcode/DerivedData/TLSTool-hjuytnjaqebcfradighsrffxxyzq/Build/Products/Debug/TLSTool s_client -connect www.yourdomain.com:443 

(爲了找到上述的完整路徑,後你已經建好了,在你的Project Navigator中打開Products組,在TLSTool上點擊右鍵,然後在Finder中顯示。)

你已經鏈接到蘋果在這個主題上的技術,https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/,但你沒有說如果你是否運行nscurl。

+0

我的虛擬機端點通過/ usr/bin/nscurl中的所有測試--ats-diagnostics [https://mydomain.local:8888](https:// mydomain。本地:8888),但我仍然收到模擬器中的錯誤。 –

3

根據此:https://forums.developer.apple.com/message/36842#36842

最好的辦法來解決HTTP加載失敗(kCFStreamErrorDomainSSL,-9802)如下設置例外的Info.plist文件:

<key>NSAppTransportSecurity</key> 
<dict> 
    <key>NSExceptionDomains</key> 
    <dict> 
    <key>test.testdomain.com</key> 
    <dict> 
     <key>NSIncludesSubdomains</key> 
     <true/> 
     <key>NSExceptionAllowsInsecureHTTPLoads</key> 
     <true/> 
    </dict> 
    </dict> 
</dict> 

重要要指出的是,這不會比iOS8更安全,只是不如iOS9支持的完整ATS安全。

+1

謝謝 - 關閉字典標籤後工作得很好。 – dlw

+1

非常感謝,但是: 1.允許普通的HTTP下載,並且我需要HTTPS(出於安全原因)。 2.正如我寫的,我不能在例外列表中設置任何域名,因爲我們的客戶通常在自己的服務器上安裝後端。例如,它可以是「server.company1.com」和「server.company2.com」。您的答案假設我們必須重新構建應用程序,並在向第三方公司銷售應用程序後添加新的例外域「server.company3.com」。 – alfared

+0

您可以使用ATS 上的Apple文檔以及此博客文章中的信息組合在自簽名證書#5上,特別是來解決您的問題(你可能需要迭代幾次,直到你做對了)。 – spirographer

0

我只是ur's.Now遇到了同樣的問題,我修復it.It是因爲TLS版本和證書sign.As下面 apple's document

蘋果的文件說,所以我做這個事情 info.plist setting

它工作