如何为iOS

时间:2016-03-08 14:52:12

标签: ios certificate client keychain mutual-authentication

我最近经历了一个非常艰巨的过程来构建一些应该非常简单但在任何一个地方看起来基本上无法找到的东西。我想试着把所有内容都放在这里询问我是否做错了,如果没有,请帮助那些需要这些信息的人。

背景:我尝试提供安全性的产品/服务是围绕Windows服务器上的WCF服务构建的,只能通过PC或iPad上的自定义客户端应用程序访问。每个客户一台服务器,无浏览器访问权限使用Windows标准机制和商业CA的证书,所有内容都已通过身份验证和授权进行TLS保护。

为了进一步限制访问,使用自签名证书为Windows平台实施了客户端/服务器证书(在没有公共/浏览器访问的相互身份验证的情况下不需要商业CA--尽管声称相反 - 并且他们更难管理。)

让所有这些对iPad起作用是一个非常有记录的噩梦,其中包含大量的虚假信息或部分正确的建议。在接下来的内容中,我尝试链接到最佳来源,但如果我无意中错过了归因,我会道歉。如果这篇文章有任何错误/误导,请发表评论。

由于

2 个答案:

答案 0 :(得分:13)

主要步骤是:

  1. 创建一个用于生成证书的系统(如果是生产系统,则简单但非平凡)
  2. 将证书转移到iPad(未嵌入应用商店套装中!)
  3. 将所有收到的凭据保存在应用程序密钥链中(Apple称其属于)
  4. 从钥匙串中检索已保存的凭据,以便在NSURLConnections中使用
  5. 实际验证服务器证书并返回客户端凭据
  6. 步骤1.生成证书

    参考:http://developer-should-know.tumblr.com/post/127063737582/how-to-create-your-own-pki-with-openssl

    您可以使用其他方法,但OpenSSL for Windows [http://slproweb.com/products.html]非常棒,除了标准接口是cmdline并且文档很难遵循。

    我希望有人先前向我解释的是显而易见的,但不是: [a]应用程序安装到根级目录,并包含默认情况下用于未在命令行指定的设置的配置文件 [b]应在配置文件中指定中间文件和输出文件的位置 [c]在运行命令之前需要手动创建某些文件 [d]你应该构建一个适合你想要做的文件/文件夹结构,然后相应地自定义cfg文件。

    在我的情况下,这意味着我的公司有一个RootCA,每个客户一个中间证书(设置为只能制作客户证书),每个客户一个服务器证书,以及所需的客户证书。 (这是最小配置,从不使用CA /客户端对,将根保留在密码箱中) 这是我的文件结构:

    c:\sslcert
        root
        certs
            YourCompany (duplicate this structure as required)
                 intermediate
                 server
                 client
                 crl (optional)
    

    在顶级sslcert文件夹

    .rnd        (empty file)
    certindex.txt   (empty file)
    serial.txt  (Text file seeded with the text “01”, hold the quotes)
    

    在根文件夹中

    RootCA.cfg
    

    在certs \ template文件夹中

    IntermediateCA.cfg
    

    设置工作目录并启动OpenSSL     cd \ sslcert     C:\ OpenSSL的-的Win32 \ BIN \ openssl.exe

    一步创建根密钥和证书

    req -config ./root/RootCA.cfg -new -x509 -days 7300 -extensions v3_ca -keyout root/YourCompanyRootCAkey.pem -out root/YourCompanyRootCAcert.cer
    

    初学者的注意事项:-extensions允许您选择在同一个cfg文件中应用多个子部分之一。

    检查密钥和证书(可选)

    x509 -noout -text -in root/YourCompanyRootCAcert.cer
    

    申请新的中间证书

    req -config certs/YourCompany/IntermediateCA.cfg -new -keyout certs/YourCompany/intermediate/intermediateCAkey.pem -out certs/YourCompany/intermediate/intermediateCAreq.pem  
    

    使用根配置中的根证书

    签署中间证书
    ca -config root/RootCA.cfg -extensions v3_intermediate_ca -days 3650 -notext -in certs/YourCompany/intermediate/intermediateCAreq.pem -out certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer
    

    检查密钥和证书(可选)

    x509 -noout -text -in certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer
    

    通过连接中间证书和根证书来创建证书链文件(这只是命令行中的一个简单附加 - 新链将添加到最终的p12包中)

    c:\sslcert> type c:\sslcert\certs\YourCompany\intermediate\YourCompanyIntermediateCAcert.cer c:\sslcert\root\YourCompanyRootCAcert.cer > c:\sslcert\certs\YourCompany\intermediate\YourCompanyCAchain.cer
    

    申请新的客户密钥和证书

    genrsa -aes256 -out certs/YourCompany/client/YourCompanyClientkey.pem 2048
    req -config certs/YourCompany/IntermediateCA.cfg -key 
    certs/YourCompany/client/YourCompanyClientkey.pem -new -sha256 -out         certs/YourCompany/client/YourCompanyClientreq.pem
    

    签署并测试具有中间权限的客户端证书

    ca -config certs/YourCompany/IntermediateCA.cfg -extensions usr_cert -days 1095 -notext -md sha256 -in certs/YourCompany/client/YourCompanyClientreq.pem -out certs/YourCompany/client/YourCompanyClientcert.cer
    x509 -noout -text -in certs/YourCompany/client/YourCompanyClientcert.cer
    verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/client/YourCompanyClientcert.cer
    

    包客户端证书

    pkcs12 -export -in certs/YourCompany/client/YourCompanyClientcert.cer -name “YourCompany Smips Client” -inkey certs/YourCompany/client/YourCompanyClientkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/client/YourCompanyClientWithName.p12
    

    重命名pkcs以便从电子邮件/ iTunes导入iOS

    c:\sslcert> copy c:\sslcert\certs\YourCompany\client\YourCompanyClient.p12 c:\sslcert\certs\YourCompany\client\YourCompanyClient.yourext12
    

    申请新的服务器密钥和证书

    genrsa -aes256 -out certs/YourCompany/server/YourCompanyServerkey.pem 2048
    req -config certs/YourCompany/IntermediateCA.cfg -key certs/YourCompany/server/YourCompanyServerkey.pem -new -sha256 -out certs/YourCompany/server/YourCompanyServerreq.pem
    

    签署并测试具有中间权限的服务器证书

    ca -config certs/YourCompany/IntermediateCA.cfg -extensions server_cert -days 1095 -notext -md sha256 -in certs/YourCompany/server/YourCompanyServerreq.pem -out certs/YourCompany/server/YourCompanyServercert.cer
    x509 -noout -text -in certs/YourCompany/server/YourCompanyServercert.cer
    verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/server/YourCompanyServercert.cer
    

    包服务器证书

    pkcs12 -export -in certs/YourCompany/server/YourCompanyServercert.cer -name “YourCompany Smips Server” -inkey certs/YourCompany/server/YourCompanyServerkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/server/YourCompanyServer.p12
    

    以下是cfg文件: 根

    dir                 = .
    
    [ ca ]
    default_ca              = CA_default
    
    [ CA_default ]
    serial              = $dir/serial.txt
    database                = $dir/certindex.txt
    new_certs_dir           = $dir/certs
    certs                   = $dir/certs
    private_key             = $dir/root/yourcompanyRootCAkey.pem
    certificate             = $dir/root/yourcompanyRootCAcert.cer
    default_days            = 7300
    default_md              = sha256
    preserve                = no
    email_in_dn             = no
    nameopt             = default_ca 
    certopt             = default_ca 
    policy              = policy_strict
    
    [ policy_strict ]
    countryName                 = match
    stateOrProvinceName         = match
    organizationName            = match
    organizationalUnitName      = optional
    commonName                  = supplied
    emailAddress                = optional
    
    [ req ]
    default_bits            = 4096      # Size of keys
    default_keyfile         = key.pem       # name of generated keys
    default_md              = sha256        # message digest algorithm
    string_mask             = nombstr       # permitted characters
    distinguished_name      = req_distinguished_name
    x509_extensions         = v3_ca
    
    [ req_distinguished_name ]
    0.organizationName           = Organization Name
    organizationalUnitName       = Organizational Unit Name
    emailAddress                 = Email Address
    emailAddress_max            = 40
    localityName            = Locality Name (city, district)
    stateOrProvinceName     = State or Province Name (full name)
    countryName             = Country Name (2 letter code)
    countryName_min         = 2
    countryName_max         = 2
    commonName              = Common Name (hostname, IP, or your name)
    commonName_max          = 64
    
    0.organizationName_default  = yourcompany
    organizationalUnitName_default  = yourcompanyRoot Certification
    emailAddress_default        = info@yourcompany.com
    localityName_default        = Okeefenokee
    stateOrProvinceName_default = Wisconsin
    countryName_default     = US
    
    [ v3_ca ]
    subjectKeyIdentifier = hash
    authorityKeyIdentifier = keyid:always,issuer
    basicConstraints = critical, CA:true
    keyUsage = critical, digitalSignature, cRLSign, keyCertSign
    
    [ v3_intermediate_ca ]
    subjectKeyIdentifier = hash
    authorityKeyIdentifier = keyid:always,issuer
    basicConstraints = critical, CA:true, pathlen:0
    keyUsage = critical, digitalSignature, cRLSign, keyCertSign
    
    [ crl_ext ]
    authorityKeyIdentifier=keyid:always
    

    中间体

    dir = .
    
    # [For non-command-line folks, everything is keyed to the working directory here (.) so if your working prompt says c:\sslcerts> then the cfg will look for serial.txt at c:\sslcerts\serial.txt and bomb if it doesn’t find things laid out accordingly. Thats why you set up a directory structure to match these entries]
    
    [ ca ]
    default_ca              = CA_default
    
    [ CA_default ]
    serial                  = $dir/serial.txt
    database                = $dir/certindex.txt
    crl_dir                 = $dir/certs/yourcompany/crl
    new_certs_dir               = $dir/certs
    certs                   = $dir/certs
    private_key             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
    certificate             = $dir/certs/yourcompany/intermediate/yourcompanyIntermediateCAcert.cer
    default_days                = 3650
    default_md              = sha256
    preserve                = no
    email_in_dn             = no
    nameopt                 = default_ca
    certopt                 = default_ca 
    crlnumber               = $dir/certs/yourcompany/crl/crlnumber
    crl                 = $dir/certs/yourcompany/crl/crl.pem
    crl_extensions              = crl_ext
    default_crl_days            = 365
    policy                  = policy_loose
    
    [ policy_loose ]
    countryName                     = optional
    stateOrProvinceName             = optional
    localityName                    = optional
    organizationName                = optional
    organizationalUnitName          = optional
    commonName                      = supplied
    emailAddress                    = optional
    
    [ req ]
    default_bits                = 4096              # Size of keys
    default_keyfile             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
    default_md              = sha256            # message digest 
    
    # the old default was md1 - change this]
    
    algorithm
    string_mask             = nombstr           # permitted characters
    distinguished_name          = req_distinguished_name
    x509_extensions             = v3_intermediate_ca
    
    [ req_distinguished_name ]
    0.organizationName                  = Organization Name
    organizationalUnitName              = Organizational Unit Name
    emailAddress                        = Email Address
    emailAddress_max            = 40
    localityName                = Locality Name (city, district)
    stateOrProvinceName         = State or Province Name (full name)
    countryName             = Country Name (2 letter code)
    countryName_min             = 2
    countryName_max             = 2
    commonName              = Common Name (hostname, IP, or your name)
    commonName_max              = 64
    
    0.organizationName_default      = yourcompany
    organizationalUnitName_default      = yourcompany Intermediate Certification
    emailAddress_default            = info@yourcompany.com
    localityName_default            = Okeefenokee
    stateOrProvinceName_default     = Wisconsin [should be spelled out]
    countryName_default         = US
    
    [ v3_intermediate_ca ]
    subjectKeyIdentifier            = hash
    authorityKeyIdentifier          = keyid:always,issuer
    basicConstraints            = critical, CA:true, pathlen:0
    keyUsage                = critical, digitalSignature, cRLSign, keyCertSign
    
    # Important - the pathlen parameter prevents this cert from being used to create new intermediate certs. The subsequent subsections for server and client certs allows you to specify their type and intended usage, as distinct from the intermediate cert, in the same cfg file 
    
    [ usr_cert ]
    basicConstraints            = CA:FALSE
    nsCertType              = client, email
    nsComment               = "OpenSSL Generated Client Certificate"
    subjectKeyIdentifier            = hash
    authorityKeyIdentifier          = keyid,issuer
    keyUsage                = critical, nonRepudiation, digitalSignature, keyEncipherment
    extendedKeyUsage            = clientAuth, emailProtection
    
    [ server_cert ]
    basicConstraints            = CA:FALSE
    nsCertType              = server
    nsComment               = "OpenSSL Generated Server Certificate"
    subjectKeyIdentifier            = hash
    authorityKeyIdentifier          = keyid,issuer:always
    keyUsage                = critical, digitalSignature, keyEncipherment
    extendedKeyUsage            = serverAuth
    
    [ crl_ext ]
    authorityKeyIdentifier          = keyid:always
    

    <强> 2。将证书转移到iPad

    参考:how to register the app to open the pdf file in my app in ipad

    Apple建议注册您的应用处理的新文件类型,并将使用新自定义扩展重命名的p12文件传输到设备(手动或电子邮件)以安装客户端证书。 p12文件应包括公共证书链以及上面步骤1中定义的客户端证书信息。当您尝试打开这样的文件时,设备会向您需要处理的应用程序委托发送启动/唤醒(而不是在didload中,因为它可能是一个唤醒)。

    这已经改变了v8或9但我需要支持7所以这是针对已弃用的处理程序。虽然相同的解决方案,它开始添加到app plist文件,如下面的屏幕截图所示。

    请注意,您需要两个新图标和文件扩展名,这些图标和文件扩展名不太可能被其他应用程序声明

    enter image description here

    enter image description here

    接下来你需要代理/处理程序,这应该是不言自明的。由于这部分与普通控制流无关,我正在处理AppDelegate.m中的所有委托处理。 (这是错误的吗?)根据需要设置方法/变量,请忽略文件存在的偏执额外检查......

    参考:https://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1

    - (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication
               annotation:(id)annotation {
    
        if (url) {
    
            self.p12Data = [NSData dataWithContentsOfFile:[url path]];
    
            if (!p12Data) {
                [self messageBox:@"Warning" : @"Failed to read data file, cancelling certificate import"];
            }
            else {
                [self presentAlertViewForPassPhrase];
            }
    
            NSFileManager * fileManager = [NSFileManager defaultManager];
            if ( [fileManager fileExistsAtPath:[url path]] ) {
                [fileManager removeItemAtPath:[url path] error:NULL];
            }
        }
    
        return YES;
    }
    
    - (void)presentAlertViewForPassPhrase {
    
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Certificate Credentials"
                                                        message:@"Please enter the passphrase for your certificate"
                                                       delegate:self
                                              cancelButtonTitle:@"Cancel"
                                              otherButtonTitles:@"Done", nil];
        [alert setAlertViewStyle:UIAlertViewStyleSecureTextInput];
        [alert show];
    }
    
    - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    
        if (buttonIndex == 1) {                                             // User selected "Done"
            UITextField *ppField = [alertView textFieldAtIndex:0];
            if ([ppField.text length] > 0) {
                [self loadCertificates:ppField.text];
            }
            //Handle Else
        }
        else
        {                                                                   // User selected "Cancel"
            [self messageBox:@"Information" : @"Certificate import cancelled"];
        }
    }
    

    第3。将收到的凭据保存在应用程序密钥链中

    既然您拥有原始p12数据,那么应该很容易弄清楚下一步该做什么......不是。所有的文档似乎都是针对名称/密码存储的,并且一些可怕的海报建议将服务器证书保存到文件系统,这没关系,但是如果你有钥匙串,那就完全没有意义了,而苹果公司说这是什么&#39 #39; s for。最后但并非最不重要的是如何区分存储的证书以及如何更新它们?

    长话短说,我决定在尝试各种不起作用的东西之后做一个完整的删除/重新保存以检查它是否应该是更新或初始加载 - 除此之外我想做什么首先,因为它是我的应用程序链。所有这些都是CF的东西而且我没有使用ARC,因为我拒绝移植任何我不需要的东西。尽可能接近,只要你分配CF,施放给NS,并且在使用后CFRelease没有警告。

    这些是关键参考:

    Enumerate all Keychain items in my iOS application

    [帮助可视化你的钥匙串的样子至关重要]

    How to delete all keychain items accessible to an app?

    What makes a keychain item unique (in iOS)?

    http://help.sap.com/saphelp_smp307sdk/helpdata/en/7c/03830b70061014a937d8267bb3f358/content.htm

    [https://developer.apple.com/library/ios/samplecode/AdvancedURLConnections/Listings/Credentials_m.html,其中说:

    // IMPORTANT: SecCertificateRef's are not uniqued (that is, you can get two
    // different SecCertificateRef values that described the same fundamental
    // certificate in the keychain), nor can they be compared with CFEqual. So
    // we match up certificates based on their data values.
    

    总结是(duh)最简单的方法是为证书分配一个标签,这样你就可以独特地查找它,并意识到如果你保存一个身份,它将自动分成密钥和证书,可能 - 不确定 - 导致更换有些困难。

    代码(解释如下):

    - (void) loadCertificates:(NSString *)passPhrase {
    
        BOOL lastError = false;
        NSMutableDictionary * p12Options = [[NSMutableDictionary alloc] init];
        [p12Options setObject:passPhrase forKey:(id)kSecImportExportPassphrase];
        CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
        OSStatus err = SecPKCS12Import((CFDataRef)p12Data, (CFDictionaryRef)p12Options, &items);
        if (err != noErr) {
            [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
            lastError = true;
        }
        if (!lastError && err == noErr && CFArrayGetCount(items) > 0) {
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
            //Clean-up
    
            NSArray *secItemClasses = [NSArray arrayWithObjects:
                                       (id)kSecClassCertificate,
                                       (id)kSecClassKey,
                                       (id)kSecClassIdentity,
                                       nil];
    
            for (id secItemClass in secItemClasses) {
                NSDictionary *spec = @{(id)kSecClass: secItemClass};
                err = SecItemDelete((CFDictionaryRef)spec);
            }
    
            //Client Identity & Certificate
    
            SecIdentityRef clientIdentity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
    
            NSDictionary *addIdentityQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                              kClientIdentityLabel, kSecAttrLabel,
                                              (id)clientIdentity, kSecValueRef,
                                              nil];
            err = SecItemAdd((CFDictionaryRef)addIdentityQuery, NULL);
            if (err == errSecDuplicateItem) {
                NSLog(@"Duplicate identity");
            }
            if (err != noErr) {
                [self messageBox:@"Warning" : @"Failed to save the new identity"];
                lastError = true;
            }
            //Server Certificate
            CFArrayRef chain = CFDictionaryGetValue(identityDict, kSecImportItemCertChain);
            CFIndex N = CFArrayGetCount(chain);
            BOOL brk = false;
            for (CFIndex i=0; (i < N) && (brk == false); i++) {
                SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(chain, i);
                CFStringRef summary = SecCertificateCopySubjectSummary(cert);
                NSString* strSummary = [[NSString alloc] initWithString:(NSString *)summary];
                if ([strSummary containsString:@"Root"] || (i == N)) {
    
                    NSDictionary *addCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                                  kServerCertificateLabel, kSecAttrLabel,
                                                  (id)cert, kSecValueRef,
                                                  nil];
                    err = SecItemAdd((CFDictionaryRef)addCertQuery, NULL);
                    if (err == errSecDuplicateItem) {
                        NSLog(@"Duplicate root certificate");
                }
                if (err != noErr) {
                    [self messageBox:@"Warning" : @"Failed to save the new server certificate"];
                    lastError = true;
                }
                brk = true;
            }
            [strSummary release];
            CFRelease(summary);
        }
    }
    else {
        [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
        lastError = true;
    }
        [p12Options release];
        CFRelease(items);
        if (!lastError) [self messageBox:@"Information" : @"Certificate import succeeded"];
    }
    

    其中kClientIdentityLabel和kServerCertificateLabel是任意标签。

    kSec函数太多/太复杂,无法在此详细解释。只需说清除所有内容,然后保存提取的客户端标识,然后提取根CA,然后单独保存。为什么循环?因为我不知道假设根在链的末端是否在技术上是正确的,但是如果我生成p12那么代码就是现在。

    请注意,来自kSec的错误已编码,因此该网站必不可少:https://www.osstatus.com

    <强> 4。从钥匙串中检索已保存的凭据

    一旦凭证在钥匙串中,您就可以这样提取凭证(失败模式会留下一些需要的东西):

    - (void) reloadCredentials {
    
        self.clientCredential = nil;
        self.serverCertificateData = nil;
    
        if (self.useClientCertificateIfPresent) {
    
            NSDictionary* idQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                     kClientIdentityLabel,            kSecAttrLabel,
                                     (id)kSecClassIdentity,           kSecClass,
                                     kCFBooleanTrue,                  kSecReturnRef,
                                     kSecMatchLimitAll,               kSecMatchLimit,
                                     nil];
            CFArrayRef result = nil;
            OSStatus err = SecItemCopyMatching((CFDictionaryRef)idQuery, (CFTypeRef*)&result);
            if (err == errSecItemNotFound) {
                [self messageBox:@"Warning" : @"Client credentials not found. Server connection may fail"];
            }
            else if (err == noErr && result != nil ) {
    
                SecIdentityRef clientIdentity = (SecIdentityRef)CFArrayGetValueAtIndex(result, 0);
    
                SecCertificateRef clientCertificate;
                SecIdentityCopyCertificate(clientIdentity, &clientCertificate);
                const void *certs[] = { clientCertificate };
                CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
                self.clientCredential = [NSURLCredential credentialWithIdentity:clientIdentity certificates:(NSArray*)certsArray
                                                                          persistence:NSURLCredentialPersistenceNone];
                CFRelease(certsArray);
                CFRelease(clientCertificate);
                CFRelease(result);
            }
            else {
                [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
            }
    
            NSDictionary* serverCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                             kServerCertificateLabel,         kSecAttrLabel,
                                             (id)kSecClassCertificate,        kSecClass,
                                             kCFBooleanTrue,                  kSecReturnRef,
                                             kSecMatchLimitAll,               kSecMatchLimit,
                                             nil];
            CFArrayRef result1 = nil;
            err = SecItemCopyMatching((CFDictionaryRef)serverCertQuery, (CFTypeRef*)&result1);
            if (err == errSecItemNotFound) {
                [self messageBox:@"Warning" : @"Server certificate not found. Server connection may fail"];
            }
            else if (err == noErr && result1 != nil ) {
    
                SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(result1, 0);
                CFDataRef certRefData = SecCertificateCopyData(certRef);
                self.serverCertificateData = (NSData *)certRefData;
                CFRelease(certRefData);
                CFRelease(result1);
            }
            else {
                [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
            }
        }
    }
    

    <强> 5。验证服务器证书并返回客户端凭据

    小男孩。这是一个编辑,解释如何实际使用检索到的证书(它应该是很容易明显的部分...)

    首先,Apple已经可疑的文档被新的Application Transport Security框架淘汰(例如参见:http://useyourloaf.com/blog/app-transport-security/)。我不打算在此处进入,但我们的想法是强制每个人默认使用https和受信任的证书。对于我的方案,通过专用客户端和私有服务器之间的证书固定和相互身份验证,您可以通过向plist添加字典来安全地关闭此功能,如下所示:

    enter image description here

    接下来,在步骤4中,您已经拥有客户端凭据,以便在它遇到时立即响应该挑战,但服务器证书作为由SecCertificateCopyData创建的DER格式的NSData浮动,并且不清楚当该策略应该发生什么时挑战到来了。

    事实证明,您应该做的是在&#34; X.509标准&#34;的第6部分中实施算法。 (https://tools.ietf.org/html/rfc5280)。幸运的是,这是在iOS SecTrustEvaluate函数的幕后实现的,但是有脚手架来构建和奇怪的东西要理解。

    [轻微问题 - 空间不足!!添加了一个新问题,包括此步骤的结束。]

    https://stackoverflow.com/questions/35964632/correctly-use-a-pinned-self-signed-certificate-in-ios-9-2

    [继续发布其他帖子]

    这就是它。抱歉生产质量不是很好,但是我想把它拼凑在一起虽然它在我心中仍然很新鲜。如果发现错误,我会更新帖子。

    希望这会有所帮助,而且这是一本非常好的书的最后一个链接,除其他外,它将为您提供关于信任商业CA的小窍门......

    https://www.cs.auckland.ac.nz/~pgut001/pubs/book.pdf

答案 1 :(得分:0)

[我只是(!)意识到我可以添加另一个答案,因为延续链接被拒绝投票并关闭,并且有两个请求不适合上面的附加信息。下面的答案从删除的帖子中的问题开始]

...我仍然不清楚的部分是为什么我必须创建一个新的信任和策略来实现锚证书和固定。

如果我只是将锚点添加到从服务器收到的信任中,我无法成功地将指针返回到从服务器收到的NSURLCredential,它似乎被修改并被发送者拒绝(?)。

问题是,这真的是适当的处理还是可以浓缩?这有点令人厌烦,但我不想因为它“有效”而接受某些东西。我目前的解决方案如下所示。

在第4步中,您已经拥有客户端凭据,无需操作即可响应该类型的质询,但服务器证书作为由SecCertificateCopyData创建的DER格式的NSData浮动,并且不清楚当该质询到达时应该发生什么

原来你应该做的是在“X.509标准”(https://tools.ietf.org/html/rfc5280)的第6节中实现算法。幸运的是,这是在iOS SecTrustEvaluate函数的幕后实现的,但是有脚手架来构建和奇怪的东西要理解。首先是代码(按照我原始来源的帽子提示):

SecTrustEvaluate always returns kSecTrustResultRecoverableTrustFailure with SecPolicyCreateSSL

    - (void)_handleServerTrustChallenge {

    OSStatus status;
    BOOL trusted = false;
    SecTrustResultType trustResult;
    SecTrustRef serverTrust = self.challenge.protectionSpace.serverTrust;
    NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; //candidate credential for return to sender if valid

    if (svDelegate.serverCertificateData) {

        //locally stored information
        SecCertificateRef storedCertificate = SecCertificateCreateWithData(NULL, (CFDataRef)svDelegate.serverCertificateData);
        NSMutableArray *anchorCertArray = [NSMutableArray arrayWithCapacity:1];
        [anchorCertArray addObject:(id)storedCertificate];

        //incoming credentials from server
        NSMutableArray *receivedCertChain = [NSMutableArray array];
        for(int i = 0; i < SecTrustGetCertificateCount(serverTrust); i++)
            [receivedCertChain addObject:(id) SecTrustGetCertificateAtIndex(serverTrust,i))];

        //new custom policy object to use in creating new trust
        //YES indicates extendedKeyUsage is set to serverAuth; effectively ignore server name check by specifying incoming name
        SecPolicyRef newPolicyRef = SecPolicyCreateSSL(YES, (CFStringRef)self.challenge.protectionSpace.host);

        //create and evaluate new trust with pinned certificate
        SecTrustRef newTrustRef = NULL;
        SecTrustCreateWithCertificates((CFArrayRef) receivedCertChain, newPolicyRef, &newTrustRef);
        status = SecTrustSetAnchorCertificates(newTrustRef, (CFArrayRef) anchorCertArray);
        if (status == noErr) status = SecTrustSetAnchorCertificatesOnly(newTrustRef, TRUE);
        if (status == noErr) status = SecTrustEvaluate(newTrustRef, &trustResult);  

        //----- debug -------
        //CFShow(newPolicyRef);
        //NSLog(@"%@", receivedCertChain);     

        CFRelease(newTrustRef);
        CFRelease(newPolicyRef);
        CFRelease(storedCertificate);
    }
    else {  //Server certificate not stored, rely on standard trusted Root CA authorities

        status = SecTrustEvaluate(serverTrust, &trustResult);
    }

    trusted = (status == noErr) && (trustResult == kSecTrustResultUnspecified);

    if (!trusted) credential = nil;
    [self stopWithCredential:credential];
    [self.delegate challengeHandlerDidFinish:self];
}

首先,我检查服务器证书是否已加载(否则通过传统的可信CA方法处理)。

接下来,选择要评估的“信任对象”。如果我没有制作通过服务器接收到的信任对象的工作副本,我就无法做到这一点b / c如果我直接使用它,它会以某种方式弄乱'NSURLCredential * credential = [NSURLCredential credentialForTrust:serverTrust]'参考。从真正可怕的Apple文档中可以看出,这是一种犹太教的方法(我,如果您想了解其中任何一种,建议略读x.509 rfc)。

https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html

[https://developer.apple.com/library/ios/technotes/tn2232/_index.html#//apple_ref/doc/uid/DTS40012884-CH1-SECCUSTOMROOT][2]

信任需要一个'策略',要评估的传入证书链,以及一个或多个'锚证书',它基本上定义了任意坐标系中的原点 - 即使不是零点,所有内容都在零点的下游验证根证书。

因此,您将传入的链和存储的证书加载到要提供给新信任的数组中,并使用SecPolicyCreateSSL创建新策略 - 这将设置一个标志,指示应检查证书是否已为serverAuth颁发,以及应忽略传入服务器名称(以允许一些基础架构灵活性)。

接下来,使用新策略和要进行身份验证的证书阵列创建新信任。然后设置锚点并确保仅针对您的锚证书评估链,而不仅仅是iOS钥匙串中的任何内容。

当您评估信任时,您接受kSecTrustResultUnspecified并且不继续或者更积极的声音可能看起来很奇怪。实际上,继续意味着您正在跟踪用户界面的覆盖,因此实际上很糟糕;未指明只是意味着每个指定的政策都没有错。

最后,你从传入的信任对象(而不是新的信任对象)返回凭据,一切都应该是金色的......