• nginx与ios实现https双向认证


    服务端配置

    nginx关键配置例如以下:

    listen       443;
    server_name  localhost;
    ssl on;
    ssl_certificate      /usr/local/opt/nginx/certificates/server.cer;
    ssl_certificate_key  /usr/local/opt/nginx/certificates/server.key.pem;
    ssl_client_certificate /usr/local/opt/nginx/certificates/ca.cer;
    ssl_verify_client    on;


    ssl开启https

    ssl_certificate是服务端证书的路径,ssl_certificate_key是服务端私钥的路径

    ssl_verify_client是配置双向认证(client certificate)

    ssl_client_certificate是签发客户端证书的根证书

    为什么是根证书。由于能够签发非常多client证书,仅仅要是由该根证书签发的,服务端都视为认证通过

    配置完毕以后,一般须要把80port的http请求跳转到443port,否则用户能够通过80port以http方式訪问,就失去了安全保护的意义

    client代码

    在网上找了非常多ios client certificate的帖子,要么是代码不全。要么是非常老的帖子,还是用的NSURLConnection,delegate method都deprecated了,最后找了一个代码片段,改了一下

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
        
    NSURL *url = [NSURL URLWithString:@"https://localhost/svc/portal/setting"];
        
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
    [request setHTTPShouldHandleCookies:NO];
    [request setTimeoutInterval:30];
    [request setHTTPMethod:@"GET"];
        
    NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", message);
    }];
        
    [task resume];

    上面的代码片段,是用NSURLSessionDataTask发起https请求。在ssl握手阶段,会调用2次以下的delegate method

    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
        NSString *method = challenge.protectionSpace.authenticationMethod;
        NSLog(@"%@", method);
        
        if([method isEqualToString:NSURLAuthenticationMethodServerTrust]){
            
            NSString *host = challenge.protectionSpace.host;
            NSLog(@"%@", host);
            
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
            return;
        }
    
        NSString *thePath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
        NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
        CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(PKCS12Data);
        SecIdentityRef identity;
        
        // 读取p12证书中的内容
        OSStatus result = [self extractP12Data:inPKCS12Data toIdentity:&identity];
        if(result != errSecSuccess){
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
            return;
        }
        
        SecCertificateRef certificate = NULL;
        SecIdentityCopyCertificate (identity, &certificate);
        
        const void *certs[] = {certificate};
        CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
        
        NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(NSArray*)CFBridgingRelease(certArray) persistence:NSURLCredentialPersistencePermanent];
        
        completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
    }
    
    -(OSStatus) extractP12Data:(CFDataRef)inP12Data toIdentity:(SecIdentityRef*)identity {
        
        OSStatus securityError = errSecSuccess;
        
        CFStringRef password = CFSTR("the_password");
        const void *keys[] = { kSecImportExportPassphrase };
        const void *values[] = { password };
        
        CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        
        CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
        securityError = SecPKCS12Import(inP12Data, options, &items);
        
        if (securityError == 0) {
            CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0);
            const void *tempIdentity = NULL;
            tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity);
            *identity = (SecIdentityRef)tempIdentity;
        }
        
        if (options) {
            CFRelease(options);
        }
        
        return securityError;
    }

    上面的代码片段。即拷即用,希望能有所帮助。

    这种方法会被调用2次,第一次是ios app验证server的阶段,第二次是server验证ios app的阶段。即client certificate。关键是每一个阶段的校验完毕以后。要调用completionHandler方法。

    网上的大部分帖子都是

    [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];

    这行代码是无效的

  • 相关阅读:
    《一起》Alpha版软件使用说明
    意见评论
    评论总结
    项目评审
    对大神联盟博客的检查结果
    SmartCoder每日站立会议10
    SmartCoder每日站立会议09
    mac php环境启动
    gulp工具rename
    gulp图片压缩
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5245042.html
Copyright © 2020-2023  润新知