虽然提高,我们正在开发iOS应用程序的安全性,我们发现需要PIN(整个或部分)服务器的SSL证书,以防止人在这方面的中间人攻击。
尽管有各种各样的方法来做到这一点,当你搜索thisI只找到例子为牵制整个证书。 这种做法带来了一个问题:一旦证书更新,应用程序将无法再连接。 如果您选择引脚的公共密钥,而不是整个证书你会发现自己(我相信)在同样的安全情况,同时更加弹性的服务器证书更新。
但是你如何做到这一点?
虽然提高,我们正在开发iOS应用程序的安全性,我们发现需要PIN(整个或部分)服务器的SSL证书,以防止人在这方面的中间人攻击。
尽管有各种各样的方法来做到这一点,当你搜索thisI只找到例子为牵制整个证书。 这种做法带来了一个问题:一旦证书更新,应用程序将无法再连接。 如果您选择引脚的公共密钥,而不是整个证书你会发现自己(我相信)在同样的安全情况,同时更加弹性的服务器证书更新。
但是你如何做到这一点?
如果您需要了解如何在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;
}
}
至于我可以告诉你不能轻易地在iOS中直接创建预期的公钥,你需要通过一个证书来做到这一点。 因此,所需的步骤类似于钉住证书,但还需要提取从实际证书的公钥,并从基准证书(预期公钥)。
你需要做的是:
willSendRequestForAuthenticationChallenge
。 一些示例代码:
(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杀死开关和类似的工具。
您可以使用做到公开密钥SSL钉扎SecTrustCopyPublicKey
的Security.framework的功能。 见一个例子连接:willSendRequestForAuthenticationChallenge:在AFNetworking项目。
如果你需要OpenSSL,以便iOS版,使用https://gist.github.com/foozmeat/5154962它基于st3fan / IOS-OpenSSL的,目前不能正常工作。
你可以使用这里提到的PhoneGap(构建)插件: http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/734
该插件支持多个证书,因此服务器和客户机不需要在同一时间进行更新。 如果您的指纹每隔(说)2的增减幅度,然后实现一种机制来迫使客户端进行更新(添加版本到您的应用程序,并在服务器上创建一个“minimalRequiredVersion” API方法,告诉客户端更新,如果应用程序版本过低(网络连接时,新证书激活)。
如果你使用AFNetworking(更具体地说,AFSecurityPolicy),和您选择的模式AFSSLPinningModePublicKey,如果你的证书更改或不,只要公钥保持不变也没关系。 是的,这是事实,AFSecurityPolicy不提供一种方法让你直接设置你的公钥; 你只能通过调用设置您的证书setPinnedCertificates
。 但是,如果你看一下setPinnedCertificates的实现,你会看到,这个框架是从证书提取的公共密钥,然后比较关键。
总之,通过在证书,并且不用担心他们的未来变化。 该框架只关心这些证书的公钥。
下面的代码对我的作品。
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
这里的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文件中像这样 。
...用于牵制整个证书。 这种做法带来了一个问题...
此外,谷歌改变了证书每月(或左右),但保留或重新证明公众。 所以证书钉扎将导致大量虚假的警告,而公钥钉扎将通过关键的连续性测试。
我认为谷歌这么做能的CRL,OCSP和吊销列表管理,我希望别人会做它也。 对于我的网站,我通常重新认证的密钥,以便乡亲,以确保关键的连续性。
但是你如何做到这一点?
证书和公钥钢钉 。 本文讨论的做法,并提供示例代码的OpenSSL,安卓,iOS和.NET。 存在至少一个问题与iOS有义务在所讨论的框架的iOS:从NSURLConnection的didReceiveAuthenticationChallenge(证书失效)提供有意义的错误 。
此外,彼得古特曼在他的书关键的连续性和牵制有很大的治疗工程安全 。
如果你使用AFNetworking,使用AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];