2015-09-15 19 views
9

Tôi muốn thực hiện yêu cầu HTTPS đối với máy chủ tùy chỉnh có chứng chỉ tự ký. Tôi đang sử dụng NSURLConnection lớp và xác thực xử lý những thách thức, nhưng luôn nhận được thông báo lỗi trong một giao diện điều khiển:Thực hiện yêu cầu HTTPS trong iOS 9 với chứng chỉ tự ký

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

sau đó phương pháp "Kết nối: didFailWithError:" được gọi với các lỗi sau:

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} 

App nhận được hai thách thức xác thực (NSURLAuthenticationMethodClientCertificate và NSURLAuthenticationMethodServerTrust) và xử lý chúng theo cách sau:

- (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]; 
} 

Trong nhật ký chẩn đoán CFNetwork tôi có thể thấy rằng thủ tục bắt tay sắp được bắt đầu. Ít nhất ứng dụng gửi thông báo "ClientHello", sau đó máy chủ gửi thông báo "ServerHello" của nó và yêu cầu xác thực. Và ở đây ứng dụng cố gắng gửi phản hồi xác thực, nhưng ngay lập tức nhận được lỗi. (Đồng thời, trong nhật ký máy chủ, tôi không thấy bất kỳ thông báo nào về việc bắt tay). Đây là một phần của nhật ký chẩn đoán:

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] 

Ví dụ phía sau của chúng tôi có thể được cài đặt ở phía khách hàng, vì vậy tôi không thể đặt bất kỳ ngoại lệ tên miền nào trong tệp Info.plist. Ngoài ra ứng dụng có thể yêu cầu máy chủ theo địa chỉ IP dưới dạng IPv4, nhưng không phải theo tên miền (như trong ví dụ của tôi).

gì có tôi đã cố gắng:

  • sử dụng NSURLSession thay vì NSURLConnection, nhưng không có bất kỳ thành công;
  • kiểm tra các yêu cầu ATS của Apple để triển khai máy chủ here (nhà phát triển back-end chắc chắn rằng việc triển khai của anh ấy đáp ứng tất cả chúng);
  • được phát bằng cách đặt chứng chỉ neo để xác thực tin cậy theo các vấn đề được giải quyết khác nhau từ stackoverflow và diễn đàn nhà phát triển của Apple;
  • chú ý đặc biệt đến similar bài đăng và liên quan đến bài đăng solution tại diễn đàn nhà phát triển;

Tôi đang thử nghiệm yêu cầu https trên iPad Air 2 với Hạt giống GM 9 của iOS 9 (Build 13A340) và xCode 7 GM Seed (Build 7A218). Lưu ý quan trọng: chức năng này hoạt động tốt với iOS 8. Tính đến điều đó tôi có thể giả định, vấn đề đó nằm trong máy chủ của chúng tôi, nhưng nhà phát triển back-end của chúng tôi đảm bảo với tôi rằng mọi thứ đều ổn.

Bây giờ tôi đã hết ý tưởng. Tôi sẽ đánh giá cao nếu bất cứ ai có thể cho tôi một gợi ý, hoặc ít nhất đề nghị một số chẩn đoán khác, mà sẽ tiết lộ lỗi cụ thể, cụ thể hơn "cảnh báo chết người".

Cảm ơn.

EDIT 1: SecTrustEvaluate luôn trả về kSecTrustResultRecoverableTrustFailure, đó là lý do tại sao tôi phải tìm một số cách giải quyết khác.

+0

Bạn đã tìm thấy giải pháp chưa? Tôi cũng gặp vấn đề này và tôi muốn sử dụng máy chủ cục bộ để thử nghiệm nhưng với chứng chỉ tự ký thì không thể làm việc và tôi gặp lỗi tương tự ... –

+0

Chưa, tôi phải làm việc với các tác vụ ưu tiên cao khác vì nhiều lý do. Tôi sẽ liên lạc với bạn nếu tôi có một giải pháp. – alfared

+0

Lỗi giao thức iOS PITA. (PITA là viết tắt của đau đớn trong ....) – Josh

Trả lời

0

Sự cố này đã được giải quyết một thời gian trước đây. Hóa ra đó là chứng chỉ tự ký không hợp lệ. Nó không đáp ứng tất cả các yêu cầu từ Apple. Thật không may tôi không biết, chính xác nó là gì.

3

Bạn đã sử dụng nscurl để chẩn đoán sự cố kết nối chưa? Nếu bạn có máy Mac chạy OS X v10.11, bạn có thể chạy một cái gì đó như thế này:

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

Hoặc, nếu bạn không có 10.11, bạn có thể tải về các mẫu mã ở đây: https://developer.apple.com/library/mac/samplecode/SC1236/ và xây dựng nó với XCode và chạy nó như thế này (thay đổi con đường phù hợp cho máy tính của bạn):

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

(Để tìm đường dẫn đầy đủ cho ở trên, sau khi bạn đã xây dựng, mở nhóm Sản phẩm trong Project Navigator của bạn, nhấp chuột phải vào TLSTool và "Show in Finder".)

Bạn đã liên kết với kỹ thuật của Apple về chủ đề này, https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/ nhưng bạn không nói nếu bạn chạy nscurl hay không.

+0

Điểm cuối VM của tôi vượt qua tất cả các thử nghiệm trong/usr/bin/nscurl --ats-diagnostics [https: //mydomain.local: 8888] (https: // mydomain. địa phương: 8888) nhưng tôi vẫn nhận được lỗi trong Trình mô phỏng. –

3

Theo này: https://forums.developer.apple.com/message/36842#36842

Cách tiếp cận tốt nhất để sửa chữa tải HTTP thất bại (kCFStreamErrorDomainSSL, -9802) là để thiết lập một ngoại lệ trong file info.plist như sau:

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

Điều quan trọng điểm là điều này không kém an toàn hơn iOS8, không an toàn như ATS đầy đủ được iOS9 hỗ trợ.

+1

Cảm ơn - đã làm việc tuyệt vời sau khi đóng thẻ dict. – dlw

+1

Cảm ơn rất nhiều, nhưng: 1. Điều đó cho phép tải xuống HTTP thông thường và tôi cần HTTPS (vì lý do an toàn). 2. Như tôi đã viết, tôi không thể đặt bất kỳ tên miền nào trong danh sách ngoại lệ, bởi vì khách hàng của chúng tôi thường cài đặt back-end trên máy chủ của riêng họ. Ví dụ: nó có thể là "server.company1.com" và "server.company2.com". Câu trả lời của bạn giả định rằng tôi phải xây dựng lại ứng dụng và thêm miền ngoại lệ mới "server.company3.com" sau khi chúng tôi bán ứng dụng cho công ty thứ ba. – alfared

+0

Bạn có thể sử dụng kết hợp thông tin từ tài liệu của Apple trên ATS cũng như bài đăng trên blog này trên chứng chỉ tự ký # 5 đặc biệt để giải quyết vấn đề của bạn (có thể bạn sẽ cần phải lặp lại một vài lần cho đến khi bạn làm đúng). – spirographer

0

Tôi chỉ gặp vấn đề tương tự với ur's.Now tôi sửa chữa it.It là vì phiên bản tls và giấy chứng nhận sign.As tài liệu của táo nói dưới đây apple's document

Vì vậy, tôi làm điều này info.plist setting

và hoạt động

Các vấn đề liên quan