All Products
Search
Document Center

Alibaba Cloud DNS:Connect an iOS app to an IP address over HTTPS

Last Updated:Jun 01, 2023

This topic describes how to connect an iOS app to an IP address over HTTPS. You can also specify the Server Name Indication (SNI) in HTTPS requests.

Overview

HTTPS is an extension of HTTP. It is used to implement secure communication over computer networks. Transport Layer Security (TLS), or formerly Secure Sockets Layer (SSL), is used to establish secure channels and encrypt data. HTTPS provides identity authentication for website servers. It also ensures the confidentiality and integrity of exchanged data. The SNI is an optional configuration for HTTPS requests. It is an extension to the TLS or SSL protocol and indicates the hostname to which a client attempts to connect. It allows a server to host multiple domain names under the same IP address.

  • Common HTTPS requests

    Developers can easily connect an iOS app to an IP address over HTTPS by replacing the Host header value in the URL of the HTTPS request with the IP address. However, you need to replace the IP address with the original Host header value for certificate verification. The original Host header value indicates the original domain name.

  • HTTPS requests with the SNI

    When the SNI is specified, websites can use their own HTTPS certificates while still being hosted on a shared IP address. In this case, you cannot call operations to configure the SNI field for an NSURLConnection or NSURLSession object in the upper-layer network library of iOS. Therefore, the socket-level bottom-layer network library, such as CFNetwork, is required to initiate requests by using IP addresses. CFNetwork is a bottom-layer network framework. If you want to connect an iOS app to an IP address based on CFNetwork, you need to consider issues related to data sending, data receiving, redirection, decoding, and caching. We recommend that you evaluate risks and resolve issues in this scenario by following the instructions that are described in Native encrypted DNS in iOS 14.

Solutions

  • Common HTTPS requests

After you use an IP address in the URL of an HTTPS request, the website to which the IP address points may not pass the certificate verification. In this case, you can replace the IP address with the original domain name, and then verify the website certificate.

NSURLSession and NSURLConnection are used in the following example.

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    /*
     * Create a certificate verification policy.
     */
    NSMutableArray *policies = [NSMutableArray array];
    if (domain) {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }
    /*
     * Attach the verification policy to the certificate of the server.
     */
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
    /*
     * Check whether the certificate is trustworthy based on the information about serverTrust. 
     * Apple Inc. recommends that a certificate be allowed to pass the verification if the result is kSecTrustResultUnspecified or kSecTrustResultProceed in serverTrust.
     For more information, visit https://developer.apple.com/library/ios/technotes/tn2232/_index.html.
     * For more information about SecTrustResultType, see the SecTrust.h file.
     */
    SecTrustResultType result;
    SecTrustEvaluate(serverTrust, &result);
    return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
/*
 * NSURLConnection
 */
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (!challenge) {
        return;
    }
    /*
     * Obtain the actual domain name from the HTTP header. When HTTPDNS is used, the value of the Host header in the URL is replaced by an IP address.
     */
    NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
    if (!host) {
        host = self.request.URL.host;
    }
    /*
     * Check whether the identity authentication method NSURLAuthenticationMethodServerTrust is used for the challenge object. The identity authentication method is used in HTTPS mode. 
     * If the identity authentication method is not used, the system initiates the default network request process. 
     */
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
            /*
             * After the verification is completed, construct an NSURLCredential object and send the object to the host that initiated the request.
             */
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        } else {
            /*
             * If the verification fails, the system initiates the default network request process.
             */
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
    } else {
        /*
         * If a different verification method is used, the system initiates the network request process.
         */
        [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
    }
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
/*
 * NSURLSession
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
    if (!challenge) {
        return;
    }
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    NSURLCredential *credential = nil;
    /*
     * Obtain the original domain name. 
     */
    NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
    if (!host) {
        host = self.request.URL.host;
    }
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
            disposition = NSURLSessionAuthChallengeUseCredential;
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    } else {
        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    }
    // Use the default verification method for other challenge objects.
    completionHandler(disposition,credential);
}
Important

When you implement this method and initiate a network request, an SSL verification error may be reported on iOS, such as kCFStreamErrorDomainSSL, -9813; The certificate for this server is invalid. In this case, you can check whether the SNI is specified.

  • HTTPS requests with the SNI

For the solution, see the source code for a demo project. This section describes the issue that occurs when POST requests are processed and the method to resolve this issue.

Support for POST requests

If NSURLProtocol is used to intercept an NSURLSession request, the request body is missing. You can use the following method to resolve this issue:

Use HTTPBodyStream to obtain the request body and assign a value to the body. Sample code:

//
//  NSURLRequest+NSURLProtocolExtension.h
//
//
#import <Foundation/Foundation.h>
@interface NSURLRequest (NSURLProtocolExtension)
- (NSURLRequest *)alidns_getPostRequestIncludeBody;
@end
//
//  NSURLRequest+NSURLProtocolExtension.h
//
//
#import "NSURLRequest+NSURLProtocolExtension.h"
@implementation NSURLRequest (NSURLProtocolExtension)
- (NSURLRequest *)alidns_getPostRequestIncludeBody {
    return [[self alidns_getMutablePostRequestIncludeBody] copy];
}
- (NSMutableURLRequest *)alidns_getMutablePostRequestIncludeBody {
    NSMutableURLRequest * req = [self mutableCopy];
    if ([self.HTTPMethod isEqualToString:@"POST"]) {
        if (!self.HTTPBody) {
            NSInteger maxLength = 1024;
            uint8_t d[maxLength];
            NSInputStream *stream = self.HTTPBodyStream;
            NSMutableData *data = [[NSMutableData alloc] init];
            [stream open];
            BOOL endOfStreamReached = NO;
            // [stream hasBytesAvailable] cannot be used to determine a while loop. When the system processes image files, the system returns YES for [stream hasBytesAvailable]. As a result, an infinite while loop occurs. 
            while (!endOfStreamReached) {
                NSInteger bytesRead = [stream read:d maxLength:maxLength];
                if (bytesRead == 0) { // The system reads to the end of the file.
                    endOfStreamReached = YES;
                } else if (bytesRead == -1) { // An error occurs when the system reads the file.
                    endOfStreamReached = YES;
                } else if (stream.streamError == nil) {
                    [data appendBytes:(void *)d length:bytesRead];
                }
            }
            req.HTTPBody = [data copy];
            [stream close];
        }
    }
    return req;
}
@end

Method:

Use the +canonicalRequestForRequest: method in the subclass of NSURLProtocol to intercept requests and process the requests.

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return [request alidns_getPostRequestIncludeBody];
}

Procedure:

//NSURLProtocol.h
/*!
 *  @method: Create an NSURLProtocol instance. After the NSURLProtocol instance is registered, the system uses the method to check whether all NSURLConnection objects have the HTTP request. 
 @parma :
 @return: If the return value is YES, NSURLConnection has the HTTP request. If the return value is NO, NSURLConnection does not have the HTTP request.
 */
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
/*!
 *  @method: The NSURLProtocol abstract class must be implemented. In most cases, the protocols that are used by input and output requests must be consistent. This is a basic requirement. This way, the request can be directly returned. In most cases, you do not need to modify the request. If you modify a request, a new request is returned. For example, if you add a title to an HTTP request to generate a new request, the new request is returned. 
 @parma: Local HTTP request: request
 @return: Directly forward the request.
 */
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest *)request

  • +[NSURLProtocol canInitWithRequest:]: specifies the network requests that you want to intercept.

  • +[NSURLProtocol canonicalRequestForRequest:]: reconstructs the network requests NSURLRequest that you want to intercept.

+[NSURLProtocol canonicalRequestForRequest:] takes effect only when the return value of +[NSURLProtocol canInitWithRequest:] is YES.

If you want to intercept the requests that are initiated by using NSURLSession, you must add the subclass of NSURLProtocol to NSURLSessionConfiguration. Sample code:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSArray *protocolArray = @[ [CUSTOMEURLProtocol class] ];
configuration.protocolClasses = protocolArray;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];