Adapting ATS from server to iOS client

Adapting ATS from server to iOS client

iOS 9 has been released, and everyone has to experience the ATS (App Transport Security) feature in the network. This blog post will talk about the adaptation of ATS from the server to the iOS client based on my experience in recent days.

1. Briefly talk about ATS (App Transport Security)

ATS (App Transport Security) is a feature designed to improve the security of data transmission between apps and servers. This feature has been available since iOS 9 and OS X 10.11. By default, it needs to meet the following conditions:

The server TLS version is at least 1.2

Connection encryption only allows a few advanced encryptions

The certificate must be signed using a SHA256 or better hash algorithm, and either an RSA key of 2048 bits or longer, or an ECC key of 256 bits or longer.

If you want to know which advanced encryption is allowed, please see the official document App Transport Security Technote for details.

2. Build an HTTPS server

There are two ways to build an HTTPS server. One is to create a certificate request, then go to an authority for authentication, and then configure it to the server; the other is to build a certificate yourself and then configure it to the server. The HTTPS server built in the first way is of course the best. If you build a website, it will be trusted directly, and when it is used as a server for a mobile app, there is no need to make too many adaptations for ATS. Although it costs money to authenticate with an authoritative organization, there are also free third-party certification agencies nowadays; the HTTPS server built in the second way is completely unfeasible for websites. When users open it, a warning pops up directly, saying that this is an untrusted website, asking users whether to continue. The experience is very poor, and users feel that the website is unsafe. For mobile terminals, this was no problem before iOS9 came out, but after iOS9 came out, the second way could not pass the ATS feature, and NSAllowsArbitraryLoads needed to be set to YES. Therefore, I recommend using the first way to build an HTTPS server.

Next, let’s talk about how to operate these two methods.

*** Type: Use a CA-certified certificate to build an HTTPS server

1. Create a certificate request and submit it to the CA for certification

  1. #Private key
  2. openssl genrsa -des3 -out private .key 2048  
  3. #Generate the server's private key and remove the key password
  4. openssl rsa -in private .key -out server.key
  5. #Generate a certificate request
  6. openssl req -new -key private .key -out server.csr

Submit the generated server.csr to the CA organization. After the CA organization signs it, it will generate a signed root certificate and server certificate and send them to you. At this time, the certificate is the certificate after CA certification. Here we rename the root certificate and server certificate to ca.crt and serve.crt respectively.

2. Configure Apache server

Upload ca.crt, server.key, and server.crt to the Alibaba Cloud server, use SSH to log in to the directory of these three files, and execute the following commands

mkdir ssl
cp server.crt /alidata/server/httpd/conf/ssl/server.crt
cp server.key /alidata/server/httpd/conf/ssl/server.key
cp demoCA/cacert.pem /alidata/server/httpd/conf/ssl/ca.crt
cp -r ssl /alidata/server/httpd/conf/

Edit the /alidata/server/httpd/conf/extra/httpd-ssl.conf file, find SSLCertificateFile, SSLCertificateKeyFile, SSLCACertificatePath, and SSLCACertificateFile and modify them:

  1. # Specify the server certificate location
  2. SSLCertificateFile "/alidata/server/httpd/conf/ssl/server.crt"  
  3. # Specify the server certificate key location
  4. SSLCertificateKeyFile "/alidata/server/httpd/conf/ssl/server.key"  
  5. # Certificate Directory
  6. SSLCACertificatePath "/alidata/server/httpd/conf/ssl"  
  7. # Root certificate location
  8. SSLCACertificateFile "/alidata/server/httpd/conf/ssl/ca.crt"  
  9.  
  10. Modify the vhost configuration vim /alidata/server/httpd/conf/vhosts/phpwind.conf

  1. SSLCertificateFile /alidata/server/httpd/conf/ssl/server.crt
  2. SSLCertificateKeyFile /alidata/server/httpd/conf/ssl/server.key
  3. SSLCACertificatePath /alidata/server/httpd/conf/ssl
  4. SSLCACertificateFile /alidata/server/httpd/conf/ssl/ca.crt
  5. ServerName www.casetree.cn
  6. DocumentRoot /alidata/www

***, restart the Apache server, enter the URL in the browser to check whether the configuration is successful. I am using it for personal use and applied for a free certificate. The website I applied for the certificate is WoSign.

Construction results: https://www.casetree.cn

The second method is to build your own certificate to configure the HTTPS server

Please refer to my previous article to configure HTTPS server with self-built certificate

3. Use nscurl to detect the server

After building the HTTPS server, you can use the nscurl command to check whether the established HTTPS server can pass the ATS feature.

  1. nscurl --ats-diagnostics --verbose https: //casetree.cn  

If the HTTPS server can pass the ATS feature, all the test cases above are PASS; if the result of a certain item is FAIL, find the ATS Dictionary to check, and you will know which ATS condition the HTTPS server does not meet. Here I encountered a problem before, that is, when I built my own certificate, when I tested it through this command, I found that the Result was all FAIL, and a very strange phenomenon also appeared in the iOS code test, that is, the same code, requesting data is completely normal on iOS8.4, but on iOS9, the connection fails directly. In the end, it was discovered that it was because the self-built certificate was not trusted and could not pass ATS unless NSAllowsArbitraryLoads was set to YES.

4. iOS Client

In the second step above, the HTTPS server meets the default conditions of ATS, and the SSL certificate is certified by an authoritative CA organization. When we use Xcode7 to develop, we don't need to do anything about the network adaptation, and we can communicate with the server normally. However, when we have higher requirements for security or we build our own certificates, we need to import the certificate locally for verification.

So, how do you import a certificate locally for verification?

Let me mention here that since the iOS client supports certificates in DER format, we need to create a client certificate. To create a client certificate, just export the server's CA root certificate into DER format.

  1. openssl x509 -inform PEM -outform DER -in ca.crt -out ca.cer

After importing the certificate, let's talk about using NSURLSession and AFNetworking for local verification.

First, let's talk about using NSURLSession verification

The verification steps are as follows:

Import the CA root certificate into the project, that is, the ca.cer we created

Get the trust object, read the imported certificate data through the SecCertificateCreateWithData method to generate a certificate object, and then set this certificate as the trusted root certificate (trusted anchor) of the trust object through SecTrustSetAnchorCertificates

Verify the trust object through the SecTrustEvaluate method

The following is the main OC implementation code. I have also put the Demo project on github. There are two languages ​​​​in OC and Swift. To download the Demo, please click HTTPSConnectDemo.

  1. - ( void )viewDidLoad {
  2. [ super viewDidLoad];
  3. //Import the client certificate  
  4. NSString *cerPath = [[NSBundle mainBundle] pathForResource:@ "ca" ofType:@ "cer" ];
  5. NSData *data = [NSData dataWithContentsOfFile:cerPath];
  6. SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) data);
  7. self.trustedCerArr = @[(__bridge_transfer id)certificate];
  8. //Send request  
  9. NSURL *testURL = [NSURL URLWithString:@ "https://casetree.cn/web/test/demo.php" ];
  10. NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
  11. NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:testURL]];
  12. [task resume];
  13. // Do any additional setup after loading the view, typically from a nib.  
  14. }
  15.  
  16. #pragma mark - NSURLSessionDelegate
  17. - ( void )URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  18. completionHandler:( void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
  19.  
  20. OSStatus err;
  21. NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  22. SecTrustResultType trustResult = kSecTrustResultInvalid;
  23. NSURLCredential *credential = nil;
  24.  
  25. //Get the server's trust object  
  26. SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
  27. //Set the read certificate as the root certificate of serverTrust  
  28. err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)self.trustedCerArr);
  29.  
  30. if (err == noErr){
  31. //Use the locally imported certificate to verify whether the server's certificate is credible. If SecTrustSetAnchorCertificatesOnly is set to NO, it only needs to be authenticated by either the local or system certificate chain.  
  32. err = SecTrustEvaluate(serverTrust, &trustResult);
  33. }
  34.  
  35. if (err == errSecSuccess && (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified)){
  36. //If the authentication is successful, a credential is created and returned to the server  
  37. disposition = NSURLSessionAuthChallengeUseCredential;
  38. credential = [NSURLCredential credentialForTrust:serverTrust];
  39. }
  40. else {
  41. disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
  42. }
  43.  
  44. //Callback credentials, passed to the server  
  45. if (completionHandler){
  46. completionHandler(disposition, credential);
  47. }
  48. }

Notice:

1. The SecTrustSetAnchorCertificates method will set a flag to block the trust object from trusting other root certificates. If you also want to trust the system default root certificate, please call the SecTrustSetAnchorCertificatesOnly method and clear this flag (set to NO). 2. There are more verification methods than this one. For more verification methods, please refer to HTTPS Server Trust Evaluation

Next, let’s talk about how AFNetworking is authenticated and how we use AFNetworking.

AFNetworking's certificate verification is done by AFSecurityPolicy, so here we mainly learn about AFSecurityPolicy. Note: I use AFNetworking 2.6.0 here, which is different from 2.5.0.

When it comes to AFSecurityPolicy, we must mention its three important properties, as follows:

  1. @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
  2. @property (nonatomic, assign) BOOL allowInvalidCertificates;
  3. @property (nonatomic, assign) BOOL validatesDomainName;

SSLPingMode is the most important attribute, which indicates how AFSecurityPolicy is verified. It is an enumeration type with three values: AFSSLPinningModeNone, AFSSLPinningModePublicKey, and AFSSLPinningModeCertificate. Among them, AFSSLPinningModeNone means that AFSecurityPolicy does not perform stricter verification. As long as the certificate is trusted by the system, it can pass the verification. However, it is affected by allowInvalidCertificates and validatesDomainName; AFSSLPinningModePublicKey verifies by comparing the public key (PublicKey) part of the certificate, obtains the local certificate and the server certificate through the SecTrustCopyPublicKey method, and then compares them. If one of them is the same, it passes the verification. This method is mainly suitable for HTTPS servers built with self-built certificates and verifications that require higher security requirements; AFSSLPinningModeCertificate directly sets the local certificate as the trusted root certificate, and then makes a judgment, and compares whether the contents of the local certificate and the server certificate are the same, for secondary judgment. This method is suitable for verifications with higher security requirements.

The allowInvalidCertificates attribute indicates whether untrusted certificates are allowed to pass verification. The default value is NO.

The validatesDomainName attribute indicates whether to validate the host name. The default value is YES.

Next, let's talk about the verification process. The verification process is mainly placed in the - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain method of AFSecurityPolicy.

c

  1. - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
  2. forDomain:(NSString *)domain
  3. {
  4. //When using a self-built certificate to verify a domain name, you need to use AFSSLPinningModePublicKey or AFSSLPinningModeCertificate  
  5. if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == ​​0 )) {
  6. NSLog(@ "In order to validate a domain name for self signed certificates, you MUST use pinning." );
  7. return NO;
  8. }
  9.  
  10. NSMutableArray *policies = [NSMutableArray array];
  11. //When you need to verify the domain name, you need to add a strategy to verify the domain name  
  12. if (self.validatesDomainName) {
  13. [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL( true , (__bridge CFStringRef)domain)];
  14. } else {
  15. [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
  16. }
  17.  
  18. //Set the verification strategy, which can be multiple  
  19. SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
  20. //When SSLPinningMode is AFSSLPinningModeNone, allowInvalidCertificates is YES, which means that any server certificate can be verified; if it is NO, it is necessary to determine whether this server certificate is a certificate trusted by the system  
  21. if (self.SSLPinningMode == AFSSLPinningModeNone) {
  22. if (self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust)){
  23. return YES;
  24. } else {
  25. return NO;
  26. }
  27. } else   if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
  28. return NO;
  29. }
  30.  
  31. //Get the content of the server certificate  
  32. NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
  33. switch (self.SSLPinningMode) {
  34. case AFSSLPinningModeNone:
  35. default :
  36. return NO;
  37. case AFSSLPinningModeCertificate: {
  38. //AFSSLPinningModeCertificate directly sets the local certificate as the trusted root certificate, and then makes a judgment and compares whether the content of the local certificate is the same as the server certificate. If one of them is the same, it returns YES  
  39.  
  40. NSMutableArray *pinnedCertificates = [NSMutableArray array];
  41. for (NSData *certificateData in self.pinnedCertificates) {
  42. [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
  43. }
  44. //Set the local certificate as the root certificate  
  45. SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
  46.  
  47. //Use the local certificate to determine whether the server certificate is credible. If it is not credible, the verification fails.  
  48. if (!AFServerTrustIsValid(serverTrust)) {
  49. return NO;
  50. }
  51.  
  52. // Check whether the contents of the local certificate and the server certificate are the same  
  53. NSUInteger trustedCertificateCount = 0 ;
  54. for (NSData *trustChainCertificate in serverCertificates) {
  55. if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
  56. trustedCertificateCount++;
  57. }
  58. }
  59. return trustedCertificateCount > 0 ;
  60. }
  61. case AFSSLPinningModePublicKey: {
  62. //AFSSLPinningModePublicKey verifies by comparing the public key (PublicKey) part of the certificate. The local certificate and server certificate are obtained through the SecTrustCopyPublicKey method, and then compared. If one is the same, it is verified.  
  63. NSUInteger trustedPublicKeyCount = 0 ;
  64. NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
  65. //Judge whether the public key of the server certificate is the same as the local certificate public key. If they are the same, the client authentication is successful.  
  66. for (id trustChainPublicKey in publicKeys) {
  67. for (id pinnedPublicKey in self.pinnedPublicKeys) {
  68. if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
  69. trustedPublicKeyCount += 1 ;
  70. }
  71. }
  72. }
  73. return trustedPublicKeyCount > 0 ;
  74. }
  75. }
  76. return NO;
  77. }

Having said the verification process, let's take a look at how to use AFNetworking. The code is as follows:

  1. _httpClient = [[BGAFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
  2. AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
  3. //Whether to allow certificates that CA does not trust to pass  
  4. policy.allowInvalidCertificates = YES;
  5. // Whether to verify the host name  
  6. policy.validatesDomainName = YES;
  7. _httpClient.securityPolicy = policy;

I have not created a demo here. If you want to see it, you can take a look at a framework I wrote, BGNetwork. The demo in it is adapted to ATS. The use of AFNetworking is placed in the - (instancetype)initWithBaseURL:(NSString *)baseURL delegate:(id)delegate initialization method in the BGNetworkConnector class.

5. Adapting ATS

The previous content describes the situation that meets the ATS characteristics, but if the server is built with a self-built certificate, or the TLS version is 1.0, and the server cannot be easily changed, how can our client adapt? Don't worry, we can set it in the Info.plist file in the project, mainly refer to the following figure:

If the certificate is self-built and not certified by an authority, you need to set NSAllowsArbitraryLoads to YES for it to pass. If NSAllowsArbitraryLoads is YES, previous HTTP requests can also pass.

If it is a certified certificate, you can use commands such as nscurl --ats-diagnostics --verbose https://casetree.cn to view the ATS Dictionary supported by the server and then make corresponding settings.

For the adaptation part, you can also refer to Demo1_iOS9 Network Adaptation_ATS: Use the more secure HTTPS

<<:  Jack Ma's speech in London: China will have 500 million middle class people in 10 years

>>:  iOS 9.1 version update: fix bugs and make details more intelligent

Recommend

Samsung S6 in iPhone's underwear reveals six surprises

Many details about Samsung's latest smartphon...

The secret in the refrigerator: Who is secretly creating the crisis?

Modern families cannot live without refrigerators...

How to develop the internet of white goods

Tiege’s previous article on the e-commerce of the...

If you don't pick up "trash" in autumn, you've wasted your whole year on earth

In autumn, we see a lot of fallen leaves, fruits,...

Why did the great "BT download" technology fail?

Last April, Bob Delamar and Jeremy Johnson became ...

Is the “Internet-free tool” reliable?

"What does it feel like to like someone?&quo...