tencent cloud

Feedback

HTTPS (SNI) Scenario

Last updated: 2023-06-12 14:45:53

    How It Works

    Server Name Indication (SNI) is an extension to SSL/TLS that allows a server to use multiple domains and certificates. It works as follows:
    Send the domain (hostname) of the site to be accessed before connecting to the server to establish an SSL connection.
    The server returns an appropriate certificate according to the domain.
    In the above process, when the client uses HTTPDNS to resolve a domain, the host in the request URL will be replaced with the IP resolved by HTTPDNS, so that the domain obtained by the server will be the resolved IP, and the client cannot find a matching certificate and can return only the default certificate or no certificate. As a result, an SSL/TLS handshake failure will occur.
    As iOS' upper level network libraries of NSURLConnection and NSURLSession don't provide an API to configure the SNI field, you can consider using NSURLProtocol to intercept network requests, using CFHTTPMessageRef to create an NSInputStream instance for socket communication, and setting the value of its kCFStreamSSLPeerName.
    Note that when you use NSURLProtocol to intercept a POST request made by NSURLSession, HTTPBody will be empty. There are two solutions to this:
    Send a POST request by using NSURLConnection.
    Place HTTPBody in the HTTP header field first and then get it from NSURLProtocol.

    Demo

    Register the NSURLProtocol subclass in SNIViewController.m of the demo before sending network requests.
    // Register `NSURLProtocol` to intercept requests
    [NSURLProtocol registerClass:[MSDKDnsHttpMessageTools class]];
    
    // Set the SNI URL
    NSString *originalUrl = @"your url";
    NSURL *url = [NSURL URLWithString:originalUrl];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    NSArray *result = [[MSDKDns sharedInstance] WGGetHostByName:url.host];
    NSString *ip = nil;
    if (result && result.count > 1) {
    if (![result[1] isEqualToString:@"0"]) {
    ip = result[1];
    } else {
    ip = result[0];
    }
    }
    // Get the IP successfully through HTTPDNS, replace the URL, and set the host header
    if (ip) {
    NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
    if (NSNotFound != hostFirstRange.location) {
    NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
    request.URL = [NSURL URLWithString:newUrl];
    [request setValue:url.host forHTTPHeaderField:@"host"];
    }
    }
    
    // Sample `NSURLConnection`
    self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [self.connection start];
    
    // Sample `NSURLSession`
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSArray *protocolArray = @[ [MSDKDnsHttpMessageTools class] ];
    configuration.protocolClasses = protocolArray;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    self.task = [session dataTaskWithRequest:request];
    [self.task resume];
    
    // Note: When you use `NSURLProtocol` to intercept a POST request made by `NSURLSession`, `HTTPBody` will be empty.
    // There are two solutions:
    // 1. Use `NSURLConnection` to send POST requests.
    // 2. Place `HTTPBody` in the HTTP header field first and then get it from `NSURLProtocol`.
    // The following mainly demonstrates the second solution
    // NSString *postStr = [NSString stringWithFormat:@"param1=%@&param2=%@", @"val1", @"val2"];
    // [_request addValue:postStr forHTTPHeaderField:@"originalBody"];
    // _request.HTTPMethod = @"POST";
    // NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    // NSArray *protocolArray = @[ [CFHttpMessageURLProtocol class] ];
    // configuration.protocolClasses = protocolArray;
    // NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    // NSURLSessionTask *task = [session dataTaskWithRequest:_request];
    // [task resume];

    Notes

    You need to call the following API to set the domains to be or not to be intercepted:
    #pragma mark - In SNI scenarios, call it only once
    /**
    Set the list of domains to be intercepted in SNI scenarios
    We recommend you call the API to intercept domains only in SNI scenarios but not other scenarios
    
    @param hijackDomainArray List of domains to be intercepted
    */
    - (void) WGSetHijackDomainArray:(NSArray *)hijackDomainArray;
    
    /**
    Set the list of domains not to be intercepted in SNI scenarios
    
    @param noHijackDomainArray List of domains not to be intercepted
    */
    - (void) WGSetNoHijackDomainArray:(NSArray *)noHijackDomainArray;
    If you set the list of domains to be intercepted, only HTTPS requests in the list will be intercepted and processed, while other domains will not.
    If you set the list of domains not to be intercepted, HTTPS requests in the list will not be intercepted and processed.
    Note
    We recommend you use WGSetHijackDomainArray to intercept domains only in SNI scenarios but not other scenarios.
    Contact Us

    Contact our sales team or business advisors to help your business.

    Technical Support

    Open a ticket if you're looking for further assistance. Our Ticket is 7x24 avaliable.

    7x24 Phone Support