iOS - SSL 握手 - 客户端证书 - NSURLSession - NSURLErrorDomain 代码=-1200 - 错误链
iOS - SSL Handshake - Client Certificate - NSURLSession - NSURLErrorDomain Code=-1200 - Wrong chain
我正在尝试使用带有 Objective C 的 iPhone 调用 Web 服务,但出现以下错误:
error -->Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has
occurred and a secure connection to the server cannot be made."
UserInfo={_kCFStreamErrorCodeKey=-9806,
NSLocalizedRecoverySuggestion=Would you like to connect to the server
anyway?, NSUnderlyingError=0x600000243690 {Error
Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)"
UserInfo={_kCFStreamPropertySSLClientCertificateState=0,
_kCFNetworkCFStreamSSLErrorOriginalValue=-9806, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9806}}, NSLocalizedDescription=An SSL error has occurred and a secure
connection to the server cannot be made.,
NSErrorFailingURLKey=https://./,
NSErrorFailingURLStringKey=https://./,
_kCFStreamErrorDomainKey=3}
Info.plist配置
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
服务器信息:
TLS1.2
客户端证书身份验证要求 开启
密码列表:
- TLS_RSA_WITH_AES_256_CBC_SHA256
- TLS_RSA_WITH_AES_128_CBC_SHA256
- TLS_RSA_WITH_AES_256_CBC_SHA
- TLS_RSA_WITH_AES_128_CBC_SHA
- TLS_RSA_WITH_3DES_EDE_CBC_SHA
注意:在客户端(iOS)和服务器中间添加一个管理客户端证书的charles代理后,请求成功完成。因此我可以断定我使用的客户端证书是有效的。
关于如何解决这个问题的任何建议或我做错了哪个配置的任何线索?
我的猜测是关于客户端证书配置的,因为我已经使用完全相同的配置和类似的服务(ssl layer/server 密码),不需要客户端证书身份验证。
使用代码:
- (void)callWebService:(NSString *)urlStr
operation:(NSString *)operation
body:(NSString *)body
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
NSLog(@"Starting callWebservice");
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:@"%lu", (unsigned long) [body length]];
[theRequest addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[theRequest addValue:msgLength forHTTPHeaderField:@"Content-Length"];
[theRequest addValue:operation forHTTPHeaderField:@"SOAPAction"];
[theRequest addValue:@"iOS" forHTTPHeaderField:@"User-Agent"];
[theRequest setHTTPMethod:@"POST"];
[theRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfigObject
delegate: self
delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:theRequest
completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *error)
{
NSLog(@"Completed request");
if (error != nil) {
NSLog(@"error -->%@", error);
reject(@"Auth error", @"There were authentication errors", error);
} else {
NSString *responseStr = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(@"-->%@", responseStr);
resolve(responseStr);
}
}];
[dataTask resume];
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSLog(@"didReceiveChallenge: %@", challenge.protectionSpace.authenticationMethod);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
NSLog(@"challenged for client certificate");
NSString *sslCertName = @"certificate_name";
NSString *path2 = [[NSBundle mainBundle] pathForResource:sslCertName ofType:@"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path2];
CFDataRef inP12data = (__bridge CFDataRef) p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = {myCertificate};
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
CFRelease(myCertificate);
secureCredential = [NSURLCredential credentialWithIdentity:myIdentity
certificates:(__bridge NSArray *) certsArray
persistence:NSURLCredentialPersistencePermanent];
CFRelease(certsArray);
[[challenge sender] useCredential:secureCredential forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential, secureCredential);
}
else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSLog(@"challenged for server trust");
[challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]
forAuthenticationChallenge: challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,
[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
} else {
NSLog(@"other challenge");
if ([challenge previousFailureCount] == 0) {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
}
}
}
OSStatus extractIdentityAndTrust(CFDataRef inP12data, SecIdentityRef *identity, SecTrustRef *trust)
{
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("certificate_password");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12data, options, &items);
if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
*trust = (SecTrustRef)tempTrust;
}
if (options) {
CFRelease(options);
}
return securityError;
}
使用额外信息进行编辑
经过一些 wireshark 调查后,iOS 似乎发送了两次客户端证书并且服务器关闭了连接。
尝试了此处指定的解决方案:http://oso.com.pl/?p=207&lang=en 但它破坏了我的所有代码
暂时解决
比较成功请求 (android) 和 iOS 失败请求之间的 wireshark 数据包后,我注意到 iOS 单独发送客户端证书,而 Android 和 Charles Proxy 正在发送客户端证书完整链。
我仍在调查未在 iOS 上发送完整链的原因。可能是因为它不信任签署客户端证书的中间证书和根证书?
无论如何,我通过使用以下代码敲击已发送的链,设法让 SSL 握手在 iOS 上工作:
NSString *sslCertName = @"teste_webservices";
NSString *path2 = [[NSBundle mainBundle] pathForResource:sslCertName ofType:@"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path2];
CFDataRef inP12data = (__bridge CFDataRef) p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
NSData *ca1CertData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ca1" ofType:@"cer"]];
SecCertificateRef ca1CertRef = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef) ca1CertData);
NSData *rootCertData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"rootCa" ofType:@"cer"]];
SecCertificateRef rootCertRef = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef) rootCertData);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = {ca1CertRef, rootCertRef};
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 2, NULL);
CFRelease(myCertificate);
secureCredential = [NSURLCredential credentialWithIdentity:myIdentity
certificates:(__bridge NSArray *) certsArray
persistence:NSURLCredentialPersistencePermanent];
我正在尝试使用带有 Objective C 的 iPhone 调用 Web 服务,但出现以下错误:
error -->Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9806, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x600000243690 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9806, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9806}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://./, NSErrorFailingURLStringKey=https://./, _kCFStreamErrorDomainKey=3}
Info.plist配置
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
服务器信息:
TLS1.2
客户端证书身份验证要求 开启
密码列表:
- TLS_RSA_WITH_AES_256_CBC_SHA256
- TLS_RSA_WITH_AES_128_CBC_SHA256
- TLS_RSA_WITH_AES_256_CBC_SHA
- TLS_RSA_WITH_AES_128_CBC_SHA
- TLS_RSA_WITH_3DES_EDE_CBC_SHA
注意:在客户端(iOS)和服务器中间添加一个管理客户端证书的charles代理后,请求成功完成。因此我可以断定我使用的客户端证书是有效的。
关于如何解决这个问题的任何建议或我做错了哪个配置的任何线索?
我的猜测是关于客户端证书配置的,因为我已经使用完全相同的配置和类似的服务(ssl layer/server 密码),不需要客户端证书身份验证。
使用代码:
- (void)callWebService:(NSString *)urlStr
operation:(NSString *)operation
body:(NSString *)body
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
NSLog(@"Starting callWebservice");
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:@"%lu", (unsigned long) [body length]];
[theRequest addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[theRequest addValue:msgLength forHTTPHeaderField:@"Content-Length"];
[theRequest addValue:operation forHTTPHeaderField:@"SOAPAction"];
[theRequest addValue:@"iOS" forHTTPHeaderField:@"User-Agent"];
[theRequest setHTTPMethod:@"POST"];
[theRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfigObject
delegate: self
delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:theRequest
completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *error)
{
NSLog(@"Completed request");
if (error != nil) {
NSLog(@"error -->%@", error);
reject(@"Auth error", @"There were authentication errors", error);
} else {
NSString *responseStr = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(@"-->%@", responseStr);
resolve(responseStr);
}
}];
[dataTask resume];
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSLog(@"didReceiveChallenge: %@", challenge.protectionSpace.authenticationMethod);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
NSLog(@"challenged for client certificate");
NSString *sslCertName = @"certificate_name";
NSString *path2 = [[NSBundle mainBundle] pathForResource:sslCertName ofType:@"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path2];
CFDataRef inP12data = (__bridge CFDataRef) p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = {myCertificate};
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
CFRelease(myCertificate);
secureCredential = [NSURLCredential credentialWithIdentity:myIdentity
certificates:(__bridge NSArray *) certsArray
persistence:NSURLCredentialPersistencePermanent];
CFRelease(certsArray);
[[challenge sender] useCredential:secureCredential forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential, secureCredential);
}
else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSLog(@"challenged for server trust");
[challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]
forAuthenticationChallenge: challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,
[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
} else {
NSLog(@"other challenge");
if ([challenge previousFailureCount] == 0) {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
}
}
}
OSStatus extractIdentityAndTrust(CFDataRef inP12data, SecIdentityRef *identity, SecTrustRef *trust)
{
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("certificate_password");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12data, options, &items);
if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
*trust = (SecTrustRef)tempTrust;
}
if (options) {
CFRelease(options);
}
return securityError;
}
使用额外信息进行编辑
经过一些 wireshark 调查后,iOS 似乎发送了两次客户端证书并且服务器关闭了连接。
尝试了此处指定的解决方案:http://oso.com.pl/?p=207&lang=en 但它破坏了我的所有代码
暂时解决
比较成功请求 (android) 和 iOS 失败请求之间的 wireshark 数据包后,我注意到 iOS 单独发送客户端证书,而 Android 和 Charles Proxy 正在发送客户端证书完整链。
我仍在调查未在 iOS 上发送完整链的原因。可能是因为它不信任签署客户端证书的中间证书和根证书?
无论如何,我通过使用以下代码敲击已发送的链,设法让 SSL 握手在 iOS 上工作:
NSString *sslCertName = @"teste_webservices";
NSString *path2 = [[NSBundle mainBundle] pathForResource:sslCertName ofType:@"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path2];
CFDataRef inP12data = (__bridge CFDataRef) p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
NSData *ca1CertData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ca1" ofType:@"cer"]];
SecCertificateRef ca1CertRef = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef) ca1CertData);
NSData *rootCertData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"rootCa" ofType:@"cer"]];
SecCertificateRef rootCertRef = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef) rootCertData);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = {ca1CertRef, rootCertRef};
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 2, NULL);
CFRelease(myCertificate);
secureCredential = [NSURLCredential credentialWithIdentity:myIdentity
certificates:(__bridge NSArray *) certsArray
persistence:NSURLCredentialPersistencePermanent];