在我们开发的iOS应用程序中提高安全性时,我们发现需要将服务器的SSL证书(全部或部分)进行PIN码以防止中间人攻击。
尽管有各种方法可以实现此目的,但当您搜索此时,我们只找到了一些针对整个证书进行PIN码的示例。这种做法存在一个问题:一旦证书更新,您的应用程序将无法再连接。如果您选择固定公钥而不是整个证书,则会发现自己处于同样安全的情况下,同时更能抵御服务器证书更新所带来的影响。
但是如何做到这一点呢?
在我们开发的iOS应用程序中提高安全性时,我们发现需要将服务器的SSL证书(全部或部分)进行PIN码以防止中间人攻击。
尽管有各种方法可以实现此目的,但当您搜索此时,我们只找到了一些针对整个证书进行PIN码的示例。这种做法存在一个问题:一旦证书更新,您的应用程序将无法再连接。如果您选择固定公钥而不是整个证书,则会发现自己处于同样安全的情况下,同时更能抵御服务器证书更新所带来的影响。
但是如何做到这一点呢?
如果您需要知道如何从iOS代码中的证书中提取此信息,这里有一种方法。
首先添加安全框架。
#import <Security/Security.h>
添加openssl库。您可以从https://github.com/st3fan/ios-openssl下载它们。
#import <openssl/x509.h>
NSURLConnectionDelegate 协议允许您决定连接是否应该能够响应保护空间。简而言之,这是当您可以查看来自服务器的证书并决定是否允许连接继续或取消时。在这里,您要做的是将证书的公钥与您固定的公钥进行比较。现在问题是,如何获取这样的公钥?请参阅以下代码: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;
}
}
serverCertificateData
? - Muruganandham K (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 Kill Switch等工具始终可以避免证书固定。
SecTrustCopyPublicKey
函数来实现公钥SSL Pinning。在AFNetworking项目的connection:willSendRequestForAuthenticationChallenge:中有一个示例。setPinnedCertificates
来设置证书。但是,如果您查看setPinnedCertificates的实现,您会发现该框架正在从证书中提取公钥,然后比较这些公钥。AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
const BOOL equal = [cert1 isEqualToData:cert2];
他们基本上是在比较整个证书!嗯,要么是这样,或者我漏掉了什么... - Javier QuevedoAFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
。