如何寄托在iOS版证书的公钥(How to pin the Public key of a cert

2019-08-18 09:17发布

虽然提高,我们正在开发iOS应用程序的安全性,我们发现需要PIN(整个或部分)服务器的SSL证书,以防止人在这方面的中间人攻击。

尽管有各种各样的方法来做到这一点,当你搜索thisI只找到例子为牵制整个证书。 这种做法带来了一个问题:一旦证书更新,应用程序将无法再连接。 如果您选择引脚的公共密钥,而不是整个证书你会发现自己(我相信)在同样的安全情况,同时更加弹性的服务器证书更新。

但是你如何做到这一点?

Answer 1:

如果您需要了解如何在iOS代码提取证书信息的,在这里你必须做这件事。

首先,增加了安全框架。

#import <Security/Security.h>

在添加OpenSSL库。 您可以下载它们https://github.com/st3fan/ios-openssl

#import <openssl/x509.h>

该NSURLConnectionDelegate协议,可以决定连接是否应该能够到保护空间响应。 概括地说,这是当你可以看看是来自服务器的证书,并决定允许继续或取消连接。 什么你想在这里做的是比较证书与你压住了一个公共密钥。 现在的问题是,你如何获得这样的公钥呢? 看看下面的代码:

首先得到X509格式的证书(您将需要为这个SSL库)

const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);

现在,我们将准备读的公钥数据

ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];    

此时你可以通过pubkey2到本地字符串迭代,并与下面的循环提取HEX格式字节转换成字符串

 for (int i = 0; i < pubKey2->length; i++)
{
    NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
    publicKeyString = [publicKeyString stringByAppendingString:aString];
}

打印公钥看到它

 NSLog(@"%@", publicKeyString);

完整的代码

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];    

for (int i = 0; i < pubKey2->length; i++)
 {
     NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
     publicKeyString = [publicKeyString stringByAppendingString:aString];
 }

if ([publicKeyString isEqual:myPinnedPublicKeyString]){
    NSLog(@"YES THEY ARE EQUAL, PROCEED");
    return YES;
}else{
   NSLog(@"Security Breach");
   [connection cancel];
   return NO;
}

}


Answer 2:

至于我可以告诉你不能轻易地在iOS中直接创建预期的公钥,你需要通过一个证书来做到这一点。 因此,所需的步骤类似于钉住证书,但还需要提取从实际证书的公钥,并从基准证书(预期公钥)。

你需要做的是:

  1. 使用NSURLConnectionDelegate检索数据,并实现willSendRequestForAuthenticationChallenge
  2. 包括在参考证书DER格式。 在这个例子中,我用一个简单的资源文件。
  3. 提取由服务器提供的公共密钥
  4. 提取参考证书的公钥
  5. 比较两个
  6. 如果它们匹配,继续定期检查(主机名,证书签名,等等)
  7. 如果它们不匹配,失败。

一些示例代码:

 (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    // get the public key offered by the server
    SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
    SecKeyRef actualKey = SecTrustCopyPublicKey(serverTrust);

    // load the reference certificate
    NSString *certFile = [[NSBundle mainBundle] pathForResource:@"ref-cert" ofType:@"der"];
    NSData* certData = [NSData dataWithContentsOfFile:certFile];
    SecCertificateRef expectedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);

    // extract the expected public key
    SecKeyRef expectedKey = NULL;
    SecCertificateRef certRefs[1] = { expectedCertificate };
    CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *) certRefs, 1, NULL);
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    SecTrustRef expTrust = NULL;
    OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &expTrust);
    if (status == errSecSuccess) {
      expectedKey = SecTrustCopyPublicKey(expTrust);
    }
    CFRelease(expTrust);
    CFRelease(policy);
    CFRelease(certArray);

    // check a match
    if (actualKey != NULL && expectedKey != NULL && [(__bridge id) actualKey isEqual:(__bridge id)expectedKey]) {
      // public keys match, continue with other checks
      [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
    } else {
      // public keys do not match
      [challenge.sender cancelAuthenticationChallenge:challenge];
    }
    if(actualKey) {
      CFRelease(actualKey);
    }
    if(expectedKey) {
      CFRelease(expectedKey);
    }
 }

声明:此仅是示例代码,而不是彻底的测试。 对于全面实施启动与由OWASP证书钉扎例子 。

请记住,证书钉扎可以随时使用避免SSL杀死开关和类似的工具。



Answer 3:

您可以使用做到公开密钥SSL钉扎SecTrustCopyPublicKey的Security.framework的功能。 见一个例子连接:willSendRequestForAuthenticationChallenge:在AFNetworking项目。

如果你需要OpenSSL,以便iOS版,使用https://gist.github.com/foozmeat/5154962它基于st3fan / IOS-OpenSSL的,目前不能正常工作。



Answer 4:

你可以使用这里提到的PhoneGap(构建)插件: http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/734

该插件支持多个证书,因此服务器和客户机不需要在同一时间进行更新。 如果您的指纹每隔(说)2的增减幅度,然后实现一种机制来迫使客户端进行更新(添加版本到您的应用程序,并在服务器上创建一个“minimalRequiredVersion” API方法,告诉客户端更新,如果应用程序版本过低(网络连接时,新证书激活)。



Answer 5:

如果你使用AFNetworking(更具体地说,AFSecurityPolicy),和您选择的模式AFSSLPinningModePublicKey,如果你的证书更改或不,只要公钥保持不变也没关系。 是的,这是事实,AFSecurityPolicy不提供一种方法让你直接设置你的公钥; 你只能通过调用设置您的证书setPinnedCertificates 。 但是,如果你看一下setPinnedCertificates的实现,你会看到,这个框架是从证书提取的公共密钥,然后比较关键。

总之,通过在证书,并且不用担心他们的未来变化。 该框架只关心这些证书的公钥。

下面的代码对我的作品。

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];


Answer 6:

这里的SWIFTY答案。 保存您的网站的证书(如.cer文件)在主束。 然后使用这个 URLSessionDelegate方法:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    guard
        challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
        let serverTrust = challenge.protectionSpace.serverTrust,
        SecTrustEvaluate(serverTrust, nil) == errSecSuccess,
        let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {

            reject(with: completionHandler)
            return
    }

    let serverCertData = SecCertificateCopyData(serverCert) as Data

    guard
        let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"),
        let localCertData = NSData(contentsOfFile: localCertPath) as Data?,

        localCertData == serverCertData else {

            reject(with: completionHandler)
            return
    }

    accept(with: serverTrust, completionHandler)

}

...

func reject(with completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.cancelAuthenticationChallenge, nil)
}

func accept(with serverTrust: SecTrust, _ completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.useCredential, URLCredential(trust: serverTrust))
}

您可以使用Chrome得到.CER文件中像这样 。



Answer 7:

...用于牵制整个证书。 这种做法带来了一个问题...

此外,谷歌改变了证书每月(或左右),但保留或重新证明公众。 所以证书钉扎将导致大量虚假的警告,而公钥钉扎将通过关键的连续性测试。

我认为谷歌这么做能的CRL,OCSP和吊销列表管理,我希望别人会做它也。 对于我的网站,我通常重新认证的密钥,以便乡亲,以确保关键的连续性。

但是你如何做到这一点?

证书和公钥钢钉 。 本文讨论的做法,并提供示例代码的OpenSSL,安卓,iOS和.NET。 存在至少一个问题与iOS有义务在所讨论的框架的iOS:从NSURLConnection的didReceiveAuthenticationChallenge(证书失效)提供有意义的错误 。

此外,彼得古特曼在他的书关键的连续性和牵制有很大的治疗工程安全 。



Answer 8:

如果你使用AFNetworking,使用AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];



文章来源: How to pin the Public key of a certificate on iOS