在提高我们正在开发的iOS应用程序的安全性的同时,我们发现需要PIN(全部或部分)服务器的SSL证书来防止中间人攻击。
尽管有各种方法可以做到这一点,但是当你搜索这个时我只找到了固定整个证书的例子。这种做法带来了一个问题:一旦证书更新,您的应用程序将无法再连接。 如果您选择固定公钥而不是整个证书,您将发现自己(我相信)处于同样安全的情况,同时对服务器中的证书更新更具弹性。
但你是怎么做到的?
答案 0 :(得分:36)
如果您需要知道如何从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字符串并以十六进制格式将字节提取为具有以下循环的字符串
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;
}
}
答案 1 :(得分:21)
据我所知,您无法直接在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);
}
}
免责声明:这只是示例代码,未经过全面测试。 要使用certificate pinning example by OWASP开始完整实施。
请记住,使用SSL Kill Switch和类似工具可以始终避免使用证书固定。
答案 2 :(得分:9)
您可以使用Security.framework的SecTrustCopyPublicKey
功能执行公钥SSL锁定。请参阅AFNetworking项目的connection:willSendRequestForAuthenticationChallenge:处的示例。
如果您需要iOS版openSSL,请使用https://gist.github.com/foozmeat/5154962它基于st3fan / ios-openssl,目前无效。
答案 3 :(得分:5)
您可以使用此处提到的PhoneGap(Build)插件:http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/734
该插件支持多个证书,因此不需要同时更新服务器和客户端。如果您的指纹每隔(比如说)2年发生变化,那么实施一种强制客户端更新的机制(在您的应用中添加一个版本并在服务器上创建'minimalRequiredVersion'API方法。如果应用版本是,请告诉客户更新太低(激活新证书时为fi)。
答案 4 :(得分:3)
如果您使用AFNetworking(更具体地说,AFSecurityPolicy),并选择模式AFSSLPinningModePublicKey,只要公钥保持不变,您的证书是否更改就无关紧要。是的,AFSecurityPolicy确实没有为您提供直接设置公钥的方法;您只能通过致电setPinnedCertificates
来设置证书。但是,如果查看setPinnedCertificates的实现,您将看到框架正在从证书中提取公钥,然后比较密钥。
简而言之,通过证书,并且不用担心将来会发生变化。该框架仅关注这些证书中的公钥。
以下代码适用于我。
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
答案 5 :(得分:2)
这里是Swifty的回答。在主包中保存您网站的证书(作为.cer文件)。然后使用this 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 this获取.cer文件。
答案 6 :(得分:1)
...用于固定整个证书。这种做法带来了一个问题...
此外,Google每月(或左右)更改证书,但保留或重新认证公众。因此,证书锁定将导致大量虚假警告,而公钥锁定将通过密钥连续性测试。
我相信Google会这样做,以保持CRL,OCSP和撤销列表的可管理性,我希望其他人也会这样做。对于我的网站,我通常会重新验证密钥,以便确保密钥的连续性。
但你是怎么做到的?
Certificate and Public Key Pinning。本文讨论了这种做法,并提供了OpenSSL,Android,iOS和.Net的示例代码。在iOS: Provide Meaningful Error from NSUrlConnection didReceiveAuthenticationChallenge (Certificate Failure)讨论的框架中,iOS至少存在一个问题。
另外,Peter Gutmann在他的书Engineering Security中对关键连续性和钉扎有很好的处理。
答案 7 :(得分:0)
如果您使用AFNetworking,请使用querySelectorAll