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.
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ự ... –
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
Lỗi giao thức iOS PITA. (PITA là viết tắt của đau đớn trong ....) – Josh