RN集成自签名https及双向认证-ios(1)

这里不对https和双向认证做理论解释,直接写具体的操作.

问题分析 :

RN中的网络部分都得进行证书认证处理

  1. Fetch
  2. react-native-fetch-blob
  3. webview
    这三者都得单独对对应的ios和android源码进行处理,这篇文章先讲ios的.

首先将client.p12(认证客户端用的)server.cer(服务端的自签名证书,ios这边我没用它)导入工程里

一 : Fetch

27BF2A9B-BF9D-4CE4-BD04-7A015DE8D044.png
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#import "RCTHTTPRequestHandler.h"

#import <mutex>

#import "RCTNetworking.h"

@interface RCTHTTPRequestHandler () <NSURLSessionDataDelegate>

@end

@implementation RCTHTTPRequestHandler
{
  NSMapTable *_delegates;
  NSURLSession *_session;
  std::mutex _mutex;
}

@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

- (void)invalidate
{
  [_session invalidateAndCancel];
  _session = nil;
}

- (BOOL)isValid
{
  // if session == nil and delegates != nil, we've been invalidated
  return _session || !_delegates;
}

#pragma mark - NSURLRequestHandler

- (BOOL)canHandleRequest:(NSURLRequest *)request
{
  static NSSet<NSString *> *schemes = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    // technically, RCTHTTPRequestHandler can handle file:// as well,
    // but it's less efficient than using RCTFileRequestHandler
    schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil];
  });
  return [schemes containsObject:request.URL.scheme.lowercaseString];
}

- (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request
                         withDelegate:(id<RCTURLRequestDelegate>)delegate
{
  // Lazy setup
  if (!_session && [self isValid]) {
    NSOperationQueue *callbackQueue = [NSOperationQueue new];
    callbackQueue.maxConcurrentOperationCount = 1;
    callbackQueue.underlyingQueue = [[_bridge networking] methodQueue];
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    _session = [NSURLSession sessionWithConfiguration:configuration
                                             delegate:self
                                        delegateQueue:callbackQueue];
    
    std::lock_guard<std::mutex> lock(_mutex);
    _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
                                           valueOptions:NSPointerFunctionsStrongMemory
                                               capacity:0];
  }
  
  NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
  {
    std::lock_guard<std::mutex> lock(_mutex);
    [_delegates setObject:delegate forKey:task];
  }
  [task resume];
  return task;
}

- (void)cancelRequest:(NSURLSessionDataTask *)task
{
  {
    std::lock_guard<std::mutex> lock(_mutex);
    [_delegates removeObjectForKey:task];
  }
  [task cancel];
}

#pragma mark - NSURLSession delegate

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
  id<RCTURLRequestDelegate> delegate;
  {
    std::lock_guard<std::mutex> lock(_mutex);
    delegate = [_delegates objectForKey:task];
  }
  [delegate URLRequest:task didSendDataWithProgress:totalBytesSent];
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)task
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
  id<RCTURLRequestDelegate> delegate;
  {
    std::lock_guard<std::mutex> lock(_mutex);
    delegate = [_delegates objectForKey:task];
  }
  [delegate URLRequest:task didReceiveResponse:response];
  completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)task
    didReceiveData:(NSData *)data
{
  id<RCTURLRequestDelegate> delegate;
  {
    std::lock_guard<std::mutex> lock(_mutex);
    delegate = [_delegates objectForKey:task];
  }
  [delegate URLRequest:task didReceiveData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
  id<RCTURLRequestDelegate> delegate;
  {
    std::lock_guard<std::mutex> lock(_mutex);
    delegate = [_delegates objectForKey:task];
    [_delegates removeObjectForKey:task];
  }
  [delegate URLRequest:task didCompleteWithError:error];
}
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  //挑战处理类型为 默认
  /*
   NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
   NSURLSessionAuthChallengeUseCredential:使用指定的证书
   NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
   */
  __weak typeof(self) weakSelf = self;
  NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  __block NSURLCredential *credential = nil;
  
  // sessionDidReceiveAuthenticationChallenge是自定义方法,用来如何应对服务器端的认证挑战
  
  // 而这个证书就需要使用credentialForTrust:来创建一个NSURLCredential对象
  if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
    
    // 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)
    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
    // 确定挑战的方式
    if (credential) {
      //证书挑战  设计policy,none,则跑到这里
      disposition = NSURLSessionAuthChallengeUseCredential;
    } else {
      disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    }
  } else {
    // client authentication
    SecIdentityRef identity = NULL;
    SecTrustRef trust = NULL;
    NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
    NSFileManager *fileManager =[NSFileManager defaultManager];
    
    if(![fileManager fileExistsAtPath:p12])
    {
      NSLog(@"client.p12:not exist");
    }
    else
    {
      NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
      
      if ([[weakSelf class]extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data])
      {
        SecCertificateRef certificate = NULL;
        SecIdentityCopyCertificate(identity, &certificate);
        const void*certs[] = {certificate};
        CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
        credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge  NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
        disposition =NSURLSessionAuthChallengeUseCredential;
      }
    }
    
  }
  
  //完成挑战
  if (completionHandler) {
    completionHandler(disposition, credential);
  }
}
+(BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
  OSStatus securityError = errSecSuccess;
  //client certificate password
  NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"mima"
                                                               forKey:(__bridge id)kSecImportExportPassphrase];
  
  CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
  securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items);
  
  if(securityError == 0) {
    CFDictionaryRef myIdentityAndTrust = (CFDictionaryRef)CFArrayGetValueAtIndex(items,0);
    const void*tempIdentity =NULL;
    tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity);
    *outIdentity = (SecIdentityRef)tempIdentity;
    const void*tempTrust =NULL;
    tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);
    *outTrust = (SecTrustRef)tempTrust;
  } else {
    NSLog(@"Failedwith error code %d",(int)securityError);
    return NO;
  }
  return YES;
}

@end

二 : react-native-fetch-blob

28ACF9B1-E9B8-4BEF-8CA1-9B3EFF61CD6A.png
//
//  RNFetchBlobNetwork.m
//  RNFetchBlob
//
//  Created by wkh237 on 2016/6/6.
//  Copyright © 2016 wkh237. All rights reserved.
//


#import <Foundation/Foundation.h>
#import "RNFetchBlob.h"
#import "RNFetchBlobFS.h"
#import "RNFetchBlobNetwork.h"
#import "RNFetchBlobConst.h"
#import "RNFetchBlobReqBuilder.h"
#import "IOS7Polyfill.h"
#import <CommonCrypto/CommonDigest.h>
#import "RNFetchBlobProgress.h"

#if __has_include(<React/RCTAssert.h>)
#import <React/RCTRootView.h>
#import <React/RCTLog.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTBridge.h>
#else
#import "RCTRootView.h"
#import "RCTLog.h"
#import "RCTEventDispatcher.h"
#import "RCTBridge.h"
#endif

////////////////////////////////////////
//
//  HTTP request handler
//
////////////////////////////////////////

NSMapTable * taskTable;
NSMapTable * expirationTable;
NSMutableDictionary * progressTable;
NSMutableDictionary * uploadProgressTable;

__attribute__((constructor))
static void initialize_tables() {
    if(expirationTable == nil)
    {
        expirationTable = [[NSMapTable alloc] init];
    }
    if(taskTable == nil)
    {
        taskTable = [[NSMapTable alloc] init];
    }
    if(progressTable == nil)
    {
        progressTable = [[NSMutableDictionary alloc] init];
    }
    if(uploadProgressTable == nil)
    {
        uploadProgressTable = [[NSMutableDictionary alloc] init];
    }
}


typedef NS_ENUM(NSUInteger, ResponseFormat) {
    UTF8,
    BASE64,
    AUTO
};


@interface RNFetchBlobNetwork ()
{
    BOOL * respFile;
    BOOL isNewPart;
    BOOL * isIncrement;
    NSMutableData * partBuffer;
    NSString * destPath;
    NSOutputStream * writeStream;
    long bodyLength;
    NSMutableDictionary * respInfo;
    NSInteger respStatus;
    NSMutableArray * redirects;
    ResponseFormat responseFormat;
    BOOL * followRedirect;
    BOOL backgroundTask;
}

@end

@implementation RNFetchBlobNetwork

NSOperationQueue *taskQueue;
@synthesize taskId;
@synthesize expectedBytes;
@synthesize receivedBytes;
@synthesize respData;
@synthesize callback;
@synthesize bridge;
@synthesize options;
@synthesize fileTaskCompletionHandler;
@synthesize dataTaskCompletionHandler;
@synthesize error;


// constructor
- (id)init {
    self = [super init];
    if(taskQueue == nil) {
        taskQueue = [[NSOperationQueue alloc] init];
        taskQueue.maxConcurrentOperationCount = 10;
    }
    return self;
}

+ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config
{
    if(progressTable == nil)
    {
        progressTable = [[NSMutableDictionary alloc] init];
    }
    [progressTable setValue:config forKey:taskId];
}

+ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config
{
    if(uploadProgressTable == nil)
    {
        uploadProgressTable = [[NSMutableDictionary alloc] init];
    }
    [uploadProgressTable setValue:config forKey:taskId];
}

// removing case from headers
+ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers
{
    
    NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
    for(NSString * key in headers) {
        [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
    }
    
    return mheaders;
}

- (NSString *)md5:(NSString *)input {
    const char* str = [input UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), result);
    
    NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
    for(int i = 0; i<CC_MD5_DIGEST_LENGTH; i++) {
        [ret appendFormat:@"%02x",result[I]];
    }
    return ret;
}

// send HTTP request
- (void) sendRequest:(__weak NSDictionary  * _Nullable )options
       contentLength:(long) contentLength
              bridge:(RCTBridge * _Nullable)bridgeRef
              taskId:(NSString * _Nullable)taskId
         withRequest:(__weak NSURLRequest * _Nullable)req
            callback:(_Nullable RCTResponseSenderBlock) callback
{
    self.taskId = taskId;
    self.respData = [[NSMutableData alloc] initWithLength:0];
    self.callback = callback;
    self.bridge = bridgeRef;
    self.expectedBytes = 0;
    self.receivedBytes = 0;
    self.options = options;
    
    backgroundTask = [options valueForKey:@"IOSBackgroundTask"] == nil ? NO : [[options valueForKey:@"IOSBackgroundTask"] boolValue];
    followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue];
    isIncrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue];
    redirects = [[NSMutableArray alloc] init];
    if(req.URL != nil)
        [redirects addObject:req.URL.absoluteString];
    
    // set response format
    NSString * rnfbResp = [req.allHTTPHeaderFields valueForKey:@"RNFB-Response"];
    if([[rnfbResp lowercaseString] isEqualToString:@"base64"])
        responseFormat = BASE64;
    else if([[rnfbResp lowercaseString] isEqualToString:@"utf8"])
        responseFormat = UTF8;
    else
        responseFormat = AUTO;
    
    NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
    NSString * ext = [self.options valueForKey:CONFIG_FILE_EXT];
    NSString * key = [self.options valueForKey:CONFIG_KEY];
    __block NSURLSession * session;
    
    bodyLength = contentLength;
    
    // the session trust any SSL certification
    NSURLSessionConfiguration *defaultConfigObject;
    
    defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
    
    if(backgroundTask)
    {
        defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
    }
    
    // set request timeout
    float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
    if(timeout > 0)
    {
        defaultConfigObject.timeoutIntervalForRequest = timeout/1000;
    }
    defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
    session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:taskQueue];
    if(path != nil || [self.options valueForKey:CONFIG_USE_TEMP]!= nil)
    {
        respFile = YES;
        
        NSString* cacheKey = taskId;
        if (key != nil) {
            cacheKey = [self md5:key];
            if (cacheKey == nil) {
                cacheKey = taskId;
            }
            
            destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
            if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) {
                callback(@[[NSNull null], RESP_TYPE_PATH, destPath]);
                return;
            }
        }
        
        if(path != nil)
            destPath = path;
        else
            destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
    }
    else
    {
        respData = [[NSMutableData alloc] init];
        respFile = NO;
    }
    
    __block NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
    [taskTable setObject:task forKey:taskId];
    [task resume];
    
    // network status indicator
    if([[options objectForKey:CONFIG_INDICATOR] boolValue] == YES)
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    __block UIApplication * app = [UIApplication sharedApplication];
    
}

// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
+ (void) emitExpiredTasks
{
    NSEnumerator * emu =  [expirationTable keyEnumerator];
    NSString * key;
    
    while((key = [emu nextObject]))
    {
        RCTBridge * bridge = [RNFetchBlob getRCTBridge];
        NSData * args = @{ @"taskId": key };
        [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args];
        
    }
    
    // clear expired task entries
    [expirationTable removeAllObjects];
    expirationTable = [[NSMapTable alloc] init];
    
}

////////////////////////////////////////
//
//  NSURLSession delegates
//
////////////////////////////////////////


#pragma mark NSURLSession delegate methods


#pragma mark - Received Response
// set expected content length on response received
- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    expectedBytes = [response expectedContentLength];
    
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
    NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
    NSString * respType = @"";
    respStatus = statusCode;
    if ([response respondsToSelector:@selector(allHeaderFields)])
    {
        NSDictionary *headers = [httpResponse allHeaderFields];
        NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString];
        if(self.isServerPush == NO)
        {
            self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
        }
        if(self.isServerPush)
        {
            if(partBuffer != nil)
            {
                [self.bridge.eventDispatcher
                 sendDeviceEventWithName:EVENT_SERVER_PUSH
                 body:@{
                        @"taskId": taskId,
                        @"chunk": [partBuffer base64EncodedStringWithOptions:0],
                        }
                 ];
            }
            partBuffer = [[NSMutableData alloc] init];
            completionHandler(NSURLSessionResponseAllow);
            return;
        }
        if(respCType != nil)
        {
            NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
            if([respCType RNFBContainsString:@"text/"])
            {
                respType = @"text";
            }
            else if([respCType RNFBContainsString:@"application/json"])
            {
                respType = @"json";
            }
            // If extra blob content type is not empty, check if response type matches
            else if( extraBlobCTypes !=  nil) {
                for(NSString * substr in extraBlobCTypes)
                {
                    if([respCType RNFBContainsString:[substr lowercaseString]])
                    {
                        respType = @"blob";
                        respFile = YES;
                        destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil];
                        break;
                    }
                }
            }
            else
            {
                respType = @"blob";
                // for XMLHttpRequest, switch response data handling strategy automatically
                if([options valueForKey:@"auto"] == YES) {
                    respFile = YES;
                    destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""];
                }
            }
        }
        else
            respType = @"text";
        respInfo = @{
                     @"taskId": taskId,
                     @"state": @"2",
                     @"headers": headers,
                     @"redirects": redirects,
                     @"respType" : respType,
                     @"timeout" : @NO,
                     @"status": [NSNumber numberWithInteger:statusCode]
                     };
        
#pragma mark - handling cookies
        // # 153 get cookies
        if(response.URL != nil)
        {
            NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage];
            NSArray<NSHTTPCookie *> * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL];
            if(cookies != nil && [cookies count] > 0) {
                [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil];
            }
        }
        
        [self.bridge.eventDispatcher
         sendDeviceEventWithName: EVENT_STATE_CHANGE
         body:respInfo
         ];
        headers = nil;
        respInfo = nil;
        
    }
    else
        NSLog(@"oops");
    
    if(respFile == YES)
    {
        @try{
            NSFileManager * fm = [NSFileManager defaultManager];
            NSString * folder = [destPath stringByDeletingLastPathComponent];
            if(![fm fileExistsAtPath:folder])
            {
                [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
            }
            BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
            BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
            
            appendToExistingFile = !overwrite;
            
            // For solving #141 append response data if the file already exists
            // base on PR#139 @kejinliang
            if(appendToExistingFile)
            {
                destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""];
            }
            if (![fm fileExistsAtPath:destPath])
            {
                [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
            }
            writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile];
            [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            [writeStream open];
        }
        @catch(NSException * ex)
        {
            NSLog(@"write file error");
        }
    }
    
    completionHandler(NSURLSessionResponseAllow);
}


// download progress handler
- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    // For #143 handling multipart/x-mixed-replace response
    if(self.isServerPush)
    {
        [partBuffer appendData:data];
        return ;
    }
    
    NSNumber * received = [NSNumber numberWithLong:[data length]];
    receivedBytes += [received longValue];
    NSString * chunkString = @"";
    
    if(isIncrement == YES)
    {
        chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    }
    
    if(respFile == NO)
    {
        [respData appendData:data];
    }
    else
    {
        [writeStream write:[data bytes] maxLength:[data length]];
    }
    RNFetchBlobProgress * pconfig = [progressTable valueForKey:taskId];
    if(expectedBytes == 0)
        return;
    NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)];
    if(pconfig != nil && [pconfig shouldReport:now])
    {
        [self.bridge.eventDispatcher
         sendDeviceEventWithName:EVENT_PROGRESS
         body:@{
                @"taskId": taskId,
                @"written": [NSString stringWithFormat:@"%d", receivedBytes],
                @"total": [NSString stringWithFormat:@"%d", expectedBytes],
                @"chunk": chunkString
                }
         ];
    }
    received = nil;
    
}

- (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
{
    if([session isEqual:session])
        session = nil;
}


- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    
    self.error = error;
    NSString * errMsg = [NSNull null];
    NSString * respStr = [NSNull null];
    NSString * rnfbRespType = @"";
    
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    
    if(respInfo == nil)
    {
        respInfo = [NSNull null];
    }
    
    if(error != nil)
    {
        errMsg = [error localizedDescription];
    }
    
    if(respFile == YES)
    {
        [writeStream close];
        rnfbRespType = RESP_TYPE_PATH;
        respStr = destPath;
    }
    // base64 response
    else {
        // #73 fix unicode data encoding issue :
        // when response type is BASE64, we should first try to encode the response data to UTF8 format
        // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
        // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
        NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
        
        if(responseFormat == BASE64)
        {
            rnfbRespType = RESP_TYPE_BASE64;
            respStr = [respData base64EncodedStringWithOptions:0];
        }
        else if (responseFormat == UTF8)
        {
            rnfbRespType = RESP_TYPE_UTF8;
            respStr = utf8;
        }
        else
        {
            if(utf8 != nil)
            {
                rnfbRespType = RESP_TYPE_UTF8;
                respStr = utf8;
            }
            else
            {
                rnfbRespType = RESP_TYPE_BASE64;
                respStr = [respData base64EncodedStringWithOptions:0];
            }
        }
    }
    
    
    callback(@[ errMsg, rnfbRespType, respStr]);
    
    @synchronized(taskTable, uploadProgressTable, progressTable)
    {
        if([taskTable objectForKey:taskId] == nil)
            NSLog(@"object released by ARC.");
        else
            [taskTable removeObjectForKey:taskId];
        [uploadProgressTable removeObjectForKey:taskId];
        [progressTable removeObjectForKey:taskId];
    }
    
    respData = nil;
    receivedBytes = 0;
    [session finishTasksAndInvalidate];
    
}

// upload progress handler
- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite
{
    RNFetchBlobProgress * pconfig = [uploadProgressTable valueForKey:taskId];
    if(totalBytesExpectedToWrite == 0)
        return;
    NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
    if(pconfig != nil && [pconfig shouldReport:now]) {
        [self.bridge.eventDispatcher
         sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
         body:@{
                @"taskId": taskId,
                @"written": [NSString stringWithFormat:@"%d", totalBytesWritten],
                @"total": [NSString stringWithFormat:@"%d", totalBytesExpectedToWrite]
                }
         ];
    }
}

+ (void) cancelRequest:(NSString *)taskId
{
    NSURLSessionDataTask * task = [taskTable objectForKey:taskId];
    if(task != nil && task.state == NSURLSessionTaskStateRunning)
        [task cancel];
}


- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
{
    BOOL trusty = [options valueForKey:CONFIG_TRUSTY];
    if(!trusty)
    {
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
    }
    else
    {
        //        completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
        
        [self ZQURLSession:session didReceiveChallenge:challenge completionHandler:completionHandler];
    }
}


- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    NSLog(@"sess done in background");
}

- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
{
    
    if(followRedirect)
    {
        if(request.URL != nil)
            [redirects addObject:[request.URL absoluteString]];
        completionHandler(request);
    }
    else
    {
        completionHandler(nil);
    }
}
- (void)ZQURLSession:(NSURLSession *)session
 didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
   completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //挑战处理类型为 默认
    /*
     NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
     NSURLSessionAuthChallengeUseCredential:使用指定的证书
     NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
     */
    __weak typeof(self) weakSelf = self;
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    
    // sessionDidReceiveAuthenticationChallenge是自定义方法,用来如何应对服务器端的认证挑战
    
    // 而这个证书就需要使用credentialForTrust:来创建一个NSURLCredential对象
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        
        // 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        // 确定挑战的方式
        if (credential) {
            //证书挑战  设计policy,none,则跑到这里
            disposition = NSURLSessionAuthChallengeUseCredential;
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    } else {
        // client authentication
        SecIdentityRef identity = NULL;
        SecTrustRef trust = NULL;
        NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
        NSFileManager *fileManager =[NSFileManager defaultManager];
        
        if(![fileManager fileExistsAtPath:p12])
        {
            NSLog(@"client.p12:not exist");
        }
        else
        {
            NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
            
            if ([[weakSelf class]extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data])
            {
                SecCertificateRef certificate = NULL;
                SecIdentityCopyCertificate(identity, &certificate);
                const void*certs[] = {certificate};
                CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
                credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge  NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
                disposition =NSURLSessionAuthChallengeUseCredential;
            }
        }
        
    }
    
    //完成挑战
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}
+(BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
    OSStatus securityError = errSecSuccess;
    //client certificate password
    NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"mima"
                                                                 forKey:(__bridge id)kSecImportExportPassphrase];
    
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items);
    
    if(securityError == 0) {
        CFDictionaryRef myIdentityAndTrust = (CFDictionaryRef)CFArrayGetValueAtIndex(items,0);
        const void*tempIdentity =NULL;
        tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity);
        *outIdentity = (SecIdentityRef)tempIdentity;
        const void*tempTrust =NULL;
        tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);
        *outTrust = (SecTrustRef)tempTrust;
    } else {
        NSLog(@"Failedwith error code %d",(int)securityError);
        return NO;
    }
    return YES;
}

@end

注意: 在RN里使用fetch-blob时得配置trusty : true

三 : webview

D81CCB48-FEE5-48CE-8E4C-2031D01AC704.png

9CA02E39-BCD3-4116-BA0A-F668777D59F0.png
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#import "RCTWebView.h"

#import <UIKit/UIKit.h>

#import "RCTAutoInsetsProtocol.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "UIView+React.h"

NSString *const RCTJSNavigationScheme = @"react-js-navigation";
NSString *const RCTJSPostMessageHost = @"postMessage";

@interface RCTWebView () <UIWebViewDelegate, RCTAutoInsetsProtocol,NSURLConnectionDataDelegate>

@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
@property (nonatomic, copy) RCTDirectEventBlock onMessage;

//  https验证
@property (nonatomic,strong) NSURLConnection *urlConnection;
@property (nonatomic,strong) NSURLRequest *requestW;
@property (nonatomic) SSLAuthenticate authenticated;

@end

@implementation RCTWebView
{
  UIWebView *_webView;
  NSString *_injectedJavaScript;
  
}

- (void)dealloc
{
  _webView.delegate = nil;
}

- (instancetype)initWithFrame:(CGRect)frame
{
  if ((self = [super initWithFrame:frame])) {
    super.backgroundColor = [UIColor clearColor];
    _automaticallyAdjustContentInsets = YES;
    _contentInset = UIEdgeInsetsZero;
    _webView = [[UIWebView alloc] initWithFrame:self.bounds];
    _webView.delegate = self;
    [self addSubview:_webView];
  }
  return self;
}

RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)

- (void)goForward
{
  [_webView goForward];
}

- (void)goBack
{
  [_webView goBack];
}

- (void)reload
{
  NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
  if (request.URL && !_webView.request.URL.absoluteString.length) {
    _requestW = request;
    [_webView loadRequest:request];
  }
  else {
    [_webView reload];
  }
}

- (void)stopLoading
{
  [_webView stopLoading];
}

- (void)postMessage:(NSString *)message
{
  NSDictionary *eventInitDict = @{
                                  @"data": message,
                                  };
  NSString *source = [NSString
                      stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
                      RCTJSONStringify(eventInitDict, NULL)
                      ];
  [_webView stringByEvaluatingJavaScriptFromString:source];
}

- (void)injectJavaScript:(NSString *)script
{
  [_webView stringByEvaluatingJavaScriptFromString:script];
}

- (void)setSource:(NSDictionary *)source
{
  if (![_source isEqualToDictionary:source]) {
    _source = [source copy];
    
    // Check for a static html source first
    NSString *html = [RCTConvert NSString:source[@"html"]];
    if (html) {
      NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]];
      if (!baseURL) {
        baseURL = [NSURL URLWithString:@"about:blank"];
      }
      [_webView loadHTMLString:html baseURL:baseURL];
      return;
    }
    
    NSURLRequest *request = [RCTConvert NSURLRequest:source];
    _requestW = request;
    // Because of the way React works, as pages redirect, we actually end up
    // passing the redirect urls back here, so we ignore them if trying to load
    // the same url. We'll expose a call to 'reload' to allow a user to load
    // the existing page.
    if ([request.URL isEqual:_webView.request.URL]) {
      return;
    }
    if (!request.URL) {
      // Clear the webview
      
      [_webView loadHTMLString:@"" baseURL:nil];
      return;
    }
    [_webView loadRequest:request];
  }
}

- (void)layoutSubviews
{
  [super layoutSubviews];
  _webView.frame = self.bounds;
}

- (void)setContentInset:(UIEdgeInsets)contentInset
{
  _contentInset = contentInset;
  [RCTView autoAdjustInsetsForView:self
                    withScrollView:_webView.scrollView
                      updateOffset:NO];
}

- (void)setScalesPageToFit:(BOOL)scalesPageToFit
{
  if (_webView.scalesPageToFit != scalesPageToFit) {
    _webView.scalesPageToFit = scalesPageToFit;
    [_webView reload];
  }
}

- (BOOL)scalesPageToFit
{
  return _webView.scalesPageToFit;
}

- (void)setBackgroundColor:(UIColor *)backgroundColor
{
  CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
  self.opaque = _webView.opaque = (alpha == 1.0);
  _webView.backgroundColor = backgroundColor;
}

- (UIColor *)backgroundColor
{
  return _webView.backgroundColor;
}

- (NSMutableDictionary<NSString *, id> *)baseEvent
{
  NSMutableDictionary<NSString *, id> *event = [[NSMutableDictionary alloc] initWithDictionary:@{
                                                                                                 @"url": _webView.request.URL.absoluteString ?: @"",
                                                                                                 @"loading" : @(_webView.loading),
                                                                                                 @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"],
                                                                                                 @"canGoBack": @(_webView.canGoBack),
                                                                                                 @"canGoForward" : @(_webView.canGoForward),
                                                                                                 }];
  
  return event;
}

- (void)refreshContentInset
{
  [RCTView autoAdjustInsetsForView:self
                    withScrollView:_webView.scrollView
                      updateOffset:YES];
}

#pragma mark - UIWebViewDelegate methods

- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
 navigationType:(UIWebViewNavigationType)navigationType
{
  if ([request.URL.scheme rangeOfString:@"https"].location != NSNotFound) {
    if (!_authenticated) {
      //    _authenticated = NO;
      //    __weak typeof(self) weakSelf = self;
      //开启同步的请求去双向认证
      NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
      
      [conn start];
      
      [webView stopLoading];
      
      return NO;
    }else{
      
      BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
      
      static NSDictionary<NSNumber *, NSString *> *navigationTypes;
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
        navigationTypes = @{
                            @(UIWebViewNavigationTypeLinkClicked): @"click",
                            @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit",
                            @(UIWebViewNavigationTypeBackForward): @"backforward",
                            @(UIWebViewNavigationTypeReload): @"reload",
                            @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit",
                            @(UIWebViewNavigationTypeOther): @"other",
                            };
      });
      
      // skip this for the JS Navigation handler
      if (!isJSNavigation && _onShouldStartLoadWithRequest) {
        NSMutableDictionary<NSString *, id> *event = [self baseEvent];
        [event addEntriesFromDictionary: @{
                                           @"url": (request.URL).absoluteString,
                                           @"navigationType": navigationTypes[@(navigationType)]
                                           }];
        if (![self.delegate webView:self
          shouldStartLoadForRequest:event
                       withCallback:_onShouldStartLoadWithRequest]) {
          return NO;
        }
      }
      
      if (_onLoadingStart) {
        // We have this check to filter out iframe requests and whatnot
        BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
        if (isTopFrame) {
          NSMutableDictionary<NSString *, id> *event = [self baseEvent];
          [event addEntriesFromDictionary: @{
                                             @"url": (request.URL).absoluteString,
                                             @"navigationType": navigationTypes[@(navigationType)]
                                             }];
          _onLoadingStart(event);
        }
      }
      
      if (isJSNavigation && [request.URL.host isEqualToString:RCTJSPostMessageHost]) {
        NSString *data = request.URL.query;
        data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "];
        data = [data stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        
        NSMutableDictionary<NSString *, id> *event = [self baseEvent];
        [event addEntriesFromDictionary: @{
                                           @"data": data,
                                           }];
        _onMessage(event);
      }
      
      // JS Navigation handler
      return !isJSNavigation;
    }
    
  }else{
    BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
    
    static NSDictionary<NSNumber *, NSString *> *navigationTypes;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      navigationTypes = @{
                          @(UIWebViewNavigationTypeLinkClicked): @"click",
                          @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit",
                          @(UIWebViewNavigationTypeBackForward): @"backforward",
                          @(UIWebViewNavigationTypeReload): @"reload",
                          @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit",
                          @(UIWebViewNavigationTypeOther): @"other",
                          };
    });
    
    // skip this for the JS Navigation handler
    if (!isJSNavigation && _onShouldStartLoadWithRequest) {
      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
      [event addEntriesFromDictionary: @{
                                         @"url": (request.URL).absoluteString,
                                         @"navigationType": navigationTypes[@(navigationType)]
                                         }];
      if (![self.delegate webView:self
        shouldStartLoadForRequest:event
                     withCallback:_onShouldStartLoadWithRequest]) {
        return NO;
      }
    }
    
    if (_onLoadingStart) {
      // We have this check to filter out iframe requests and whatnot
      BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
      if (isTopFrame) {
        NSMutableDictionary<NSString *, id> *event = [self baseEvent];
        [event addEntriesFromDictionary: @{
                                           @"url": (request.URL).absoluteString,
                                           @"navigationType": navigationTypes[@(navigationType)]
                                           }];
        _onLoadingStart(event);
      }
    }
    
    if (isJSNavigation && [request.URL.host isEqualToString:RCTJSPostMessageHost]) {
      NSString *data = request.URL.query;
      data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "];
      data = [data stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
      
      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
      [event addEntriesFromDictionary: @{
                                         @"data": data,
                                         }];
      _onMessage(event);
    }
    
    // JS Navigation handler
    return !isJSNavigation;
  }
  
  
}

- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error
{
  if (_onLoadingError) {
    if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
      // NSURLErrorCancelled is reported when a page has a redirect OR if you load
      // a new URL in the WebView before the previous one came back. We can just
      // ignore these since they aren't real errors.
      // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
      return;
    }
    
    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
    [event addEntriesFromDictionary:@{
                                      @"domain": error.domain,
                                      @"code": @(error.code),
                                      @"description": error.localizedDescription,
                                      }];
    _onLoadingError(event);
  }
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
  if (_messagingEnabled) {
#if RCT_DEV
    // See isNative in lodash
    NSString *testPostMessageNative = @"String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
    BOOL postMessageIsNative = [
                                [webView stringByEvaluatingJavaScriptFromString:testPostMessageNative]
                                isEqualToString:@"true"
                                ];
    if (!postMessageIsNative) {
      RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
    }
#endif
    NSString *source = [NSString stringWithFormat:
                        @"window.originalPostMessage = window.postMessage;"
                        "window.postMessage = function(data) {"
                        "window.location = '%@://%@?' + encodeURIComponent(String(data));"
                        "};", RCTJSNavigationScheme, RCTJSPostMessageHost
                        ];
    [webView stringByEvaluatingJavaScriptFromString:source];
  }
  if (_injectedJavaScript != nil) {
    NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript];
    
    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
    event[@"jsEvaluationValue"] = jsEvaluationValue;
    
    _onLoadingFinish(event);
  }
  // we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
  else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
    _onLoadingFinish([self baseEvent]);
  }
}



#pragma mark ***NSURLConnection代理方法***
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  
  NSURLCredential * credential;
  
  assert(challenge != nil);
  
  credential = nil;
  
  NSLog(@"----收到质询----");
  
  NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
  
  if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
    
    NSLog(@"----服务器验证客户端----");
    
    NSString *host = challenge.protectionSpace.host;
    
    SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
    
    
    NSMutableArray *polices = [NSMutableArray array];
    
    if (NO) {
      
      [polices addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)host)];
      
    }else{
      
      [polices addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
      
    }
    
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)polices);
    
    //导入证书
    NSString *path = [[NSBundle mainBundle] pathForResource:@"CNPCCA" ofType:@"cer"];
    
    NSData *certData = [NSData dataWithContentsOfFile:path];
    
    NSMutableArray *pinnedCerts = [NSMutableArray arrayWithObjects:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData), nil];
    
    SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCerts);
    
    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
    
  } else {
    
    NSLog(@"----客户端验证服务端----");
    
    SecIdentityRef identity = NULL;
    
    SecTrustRef trust = NULL;
    
    NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    if (![fileManager fileExistsAtPath:p12]) {
      
      NSLog(@"客户端.p12证书 不存在!");
      
    }else{
      
      NSData *pkcs12Data = [NSData dataWithContentsOfFile:p12];
      
      if ([self extractIdentity:&identity andTrust:&trust fromPKCS12Data:pkcs12Data]) {
        
        SecCertificateRef certificate = NULL;
        
        SecIdentityCopyCertificate(identity, &certificate);
        
        const void *certs[] = {certificate};
        
        CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
        
        credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray *)certArray persistence:NSURLCredentialPersistencePermanent];
      }
      
    }
    
  }
  
  [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
  
}

- (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
  
  OSStatus securityErr = errSecSuccess;
  
  //输入客户端证书密码
  NSDictionary *optionsDic = [NSDictionary dictionaryWithObject:@"mima"  forKey:(__bridge id)kSecImportExportPassphrase];
  
  CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
  
  securityErr = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data, (__bridge CFDictionaryRef)optionsDic, &items);
  
  if (securityErr == errSecSuccess) {
    
    CFDictionaryRef mineIdentAndTrust = CFArrayGetValueAtIndex(items, 0);
    
    const void *tmpIdentity = NULL;
    
    tmpIdentity = CFDictionaryGetValue(mineIdentAndTrust, kSecImportItemIdentity);
    
    *outIdentity = (SecIdentityRef)tmpIdentity;
    
    const void *tmpTrust = NULL;
    
    tmpTrust = CFDictionaryGetValue(mineIdentAndTrust, kSecImportItemTrust);
    
    *outTrust = (SecTrustRef)tmpTrust;
    
  }else{
    
    return false;
    
  }
  
  return true;
  
}


-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse {
  
  _authenticated = YES;
  
  //webview 重新加载请求。
  //
  //  NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
  //
  //  [[session dataTaskWithRequest:_requestW completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  //
  //    NSLog(@"%s",__FUNCTION__);
  //    NSLog(@"RESPONSE:%@",response);
  //    NSLog(@"ERROR:%@",error);
  //
  //    NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  //    NSLog(@"dataString:%@",dataString);
  //
  //    [self loadHTMLString:dataString baseURL:nil];
  //  }] resume];
  
  [_webView loadRequest:_requestW];
  
  [connection cancel];
  
}

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容