iOS 9 ATS 阻止对具有自签名证书的服务器的 HTTPS 请求

iOS 9 ATS blocking HTTPS request to server with Self-Signed Certficate

Stack Overflow 上到处都是这个问题,在过去的 2 天里,我尝试了无数种 ATP 配置组合,并让我的应用程序正常运行。我将彻底解决我的问题,因为看起来最微小的事情都会影响如何解决这个问题。

我最近刚刚设置了一个启用了 SSL 和 TLS 1.2 的 Ubuntu 14 服务器。在我的服务器上是我的应用程序所依赖的服务器端脚本。在我的应用程序中,我使用 NSMutableURLRequest 从服务器请求我的 API,如下所示:

NSString * noteDataString = [NSString stringWithFormat:@"email=%@&password=%@&type=%@", companyEmail, companyPassword, @"login"];
NSData * postData = [noteDataString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString * postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postData length]];

NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];

[request setURL:[NSURL URLWithString:@"https://mydomain.me:443/path/to/file.php"]];

[request setHTTPMethod:@"POST"];

[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];

NSHTTPURLResponse * urlResponse = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
NSString * result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];

NSLog(@"Response Code: %ld", (long)[urlResponse statusCode]);

当我将 url 复制到 Chrome 时,会显示正确的 PHP return。当在 iOS 9 上从 Xcode 7 请求时,我收到此错误:

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

我在类似问题中使用了无数 info.plist 设置。我试过禁用前向保密的需要,我试过启用任意负载,我试过使用异常域 mydomain.me 及其子域。我取得的唯一进展是错误代码 -9813 切换到 -9802。

我知道为 NSURLRequest 添加委托方法,但这些方法从未被调用,并且被认为对于解决此问题是多余的。

我使用 localhost 和 http 在 MAMP 服务器上构建了很多应用程序,当我启用任意加载时请求有效,所以我的 plist 没有问题。

令人难以置信的是为什么我有一个特殊情况,我知道 Stack Overflow 是处理这种情况的地方!

谢谢,我希望这在解决后能帮助我以外的更多开发人员。

打开Xcode,Command-Shift-2,输入9813,马上发现-9813 = errSSLNoRootCert,这是意料之中的,因为你的自签名证书没有根证书,而-9802 = errSSLFatalAlert (你真的把它搞砸了)。

问题似乎是出于安全原因,某些软件不喜欢自签名证书。这通常可以通过创建和安装您自己的根证书并拥有由您自己的根证书签名的证书来解决。

解决方案。感谢评论中的每个人为我指明了正确的方向。解决方案是创建一个 NSObject 来处理这种特殊情况的 NSURLRequests。

归功于:https://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/

以下内容几乎是 link 中教程的直接复制,但我认为留在此处会更容易。

所以,

我必须使用以下代码创建一个新的 NSObject class:

BWWebService.h

#import <Foundation/Foundation.h>

@interface BWWebService : NSObject{
    NSMutableData *receivedData;
    NSURLConnection *connection;
    NSStringEncoding encoding;
}

- (id)initWithURL:(NSURL *)url;

@end

BWWebService.m

#import "BWWebService.h"

@implementation BWWebService

- (id)initWithURL:(NSURL *)url{
    if (self = [super init]){
        NSURLRequest * request = [NSURLRequest requestWithURL:url];

        connection = [NSURLConnection connectionWithRequest:request delegate:self];

        [connection start];
    }

    return self;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    // Every response could mean a redirect
    receivedData = nil;

    CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)
                                                                               [response textEncodingName]);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    if (!receivedData){
        // No store yet, make one
        receivedData = [[NSMutableData alloc] initWithData:data];
    }else{
        // Append to previous chunks
        [receivedData appendData:data];
    }
}

// All worked
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    NSString * xml = [[NSString alloc] initWithData:receivedData encoding:encoding];
    NSLog(@"%@", xml);
}

// And error occurred
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
     NSLog(@"Error retrieving data, %@", [error localizedDescription]);
}

// To deal with self-signed certificates
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{
    return [protectionSpace.authenticationMethod
        isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
        // we only trust our own domain
        if ([challenge.protectionSpace.host isEqualToString:@"myuntrusteddomain.me"]){
           NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
        }
    }

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
@end

如果这还不够,为了发出请求,我使用以下内容替换了我的原始请求:

NSURL * url = [NSURL URLWithString:@"https://myuntrusteddomain.me:443/path/to/script.php"];
BWWebService * webService;
webService = [[BWWebService alloc] initWithURL:url];

我知道这不像原始数据那样 POST 数据,但那是后来的数据。我确信这将是处理 initWithURL 中的 POST 的问题。

谢谢大家

编辑:该解决方案的应用似乎仅适用于将“允许任意加载”设置为“是”的情况。