如何在 iOS 上固定证书的公钥
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15728636/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to pin the Public key of a certificate on iOS
提问by Javier Quevedo
While improving the security of an iOS application that we are developing, we found the need to PIN (the entire or parts of) the SSL certificate of server to prevent man-in-the-middle attacks.
在提高我们正在开发的 iOS 应用程序的安全性的同时,我们发现需要对服务器的 SSL 证书(全部或部分)进行 PIN 码以防止中间人攻击。
Even though there are various approaches to do this, when you searching for thisI only found examples for pinning the entire certificate. Such practice poses a problem: As soon as the certificate is updated, your application will not be able to connect anymore. If you choose to pin the public key instead of the entire certificate you will find yourself (I believe) in an equally secure situation, while being more resilient to certificate updates in the server.
尽管有多种方法可以做到这一点,但在您搜索时,我只找到了固定整个证书的示例。这种做法带来了一个问题:一旦证书更新,您的应用程序将无法再连接。如果您选择固定公钥而不是整个证书,您会发现自己(我相信)处于同样安全的情况,同时对服务器中的证书更新更有弹性。
But how do you do this?
但是你怎么做呢?
采纳答案by Javier Quevedo
In case you are in need of knowing how to extract this information from the certificate in your iOS code, here you have one way to do it.
如果您需要知道如何从 iOS 代码的证书中提取此信息,这里有一种方法可以做到。
First of all add the security framework.
首先添加安全框架。
#import <Security/Security.h>
The add the openssl libraries. You can download them from https://github.com/st3fan/ios-openssl
添加 openssl 库。您可以从https://github.com/st3fan/ios-openssl下载它们
#import <openssl/x509.h>
The NSURLConnectionDelegate Protocol allows you to decide whether the connection should be able to respond to a protection space. In a nutshell, this is when you can have a look at the certificate that is coming from the server, and decide to allow the connection to proceed or to cancel. What you want to do here is compare the certificates public key with the one you've pinned. Now the question is, how do you get such public key? Have a look at the following code:
NSURLConnectionDelegate 协议允许您决定连接是否应该能够响应保护空间。简而言之,此时您可以查看来自服务器的证书,并决定允许连接继续还是取消。您在这里要做的是将证书公钥与您固定的公钥进行比较。现在的问题是,你如何获得这样的公钥?看看下面的代码:
First get the certificate in X509 format (you will need the ssl libraries for this)
首先获取 X509 格式的证书(为此您需要 ssl 库)
const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);
Now we will prepare to read the public key data
现在我们将准备读取公钥数据
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);
NSString *publicKeyString = [[NSString alloc] init];
At this point you can iterate through the pubKey2 string and extract the bytes in HEX format into a string with the following loop
此时可以遍历 pubKey2 字符串,将 HEX 格式的字节提取为具有以下循环的字符串
for (int i = 0; i < pubKey2->length; i++)
{
NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
publicKeyString = [publicKeyString stringByAppendingString:aString];
}
Print the public key to see it
打印公钥查看
NSLog(@"%@", publicKeyString);
The complete code
完整的代码
- (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;
}
}
回答by beetstra
As far as I can tell you cannot easily create the expected public key directly in iOS, you need to do it via a certificate. So the steps needed are similar to pinning the certificate, but additionally you need to extract the public key from the actual certificate, and from a reference certificate (the expected public key).
据我所知,您无法直接在 iOS 中轻松创建预期的公钥,您需要通过证书来完成。因此所需的步骤类似于固定证书,但另外您需要从实际证书和参考证书(预期公钥)中提取公钥。
What you need to do is:
你需要做的是:
- Use a NSURLConnectionDelegate to retrieve the data, and implement
willSendRequestForAuthenticationChallenge
. - Include a reference certificate in DERformat. In the example I've used a simple resource file.
- Extract the public key presented by the server
- Extract the public key from your reference certificate
- Compare the two
- If they match, continue with the regular checks (hostname, certificate signing, etc)
- If they don't match, fail.
- 使用 NSURLConnectionDelegate 来检索数据,并实现
willSendRequestForAuthenticationChallenge
. - 包括DER格式的参考证书。在示例中,我使用了一个简单的资源文件。
- 提取服务器提供的公钥
- 从您的参考证书中提取公钥
- 比较两者
- 如果它们匹配,请继续进行常规检查(主机名、证书签名等)
- 如果它们不匹配,则失败。
Some example code:
一些示例代码:
(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);
}
}
Disclaimer: this is example code only, and not thoroughly tested. For a full implementation start with the certificate pinning example by OWASP.
免责声明:这只是示例代码,并未经过彻底测试。对于完整的实现,请从OWASP的证书锁定示例开始。
And remember that certificate pinning can always be avoided using SSL Kill Switchand similar tools.
请记住,使用SSL Kill Switch和类似工具始终可以避免证书锁定。
回答by Jano
You can do public key SSL pinning using the SecTrustCopyPublicKey
function of the Security.framework. See an example at connection:willSendRequestForAuthenticationChallenge:of the AFNetworking project.
您可以使用SecTrustCopyPublicKey
Security.framework的功能进行公钥 SSL 固定。请参阅AFNetworking 项目的connection:willSendRequestForAuthenticationChallenge:中的示例。
If you need openSSL for iOS, use https://gist.github.com/foozmeat/5154962It's based on st3fan/ios-openssl, which currently doesn't work.
如果你需要 iOS的 openSSL ,使用https://gist.github.com/foozmeat/5154962它是基于 st3fan/ios-openssl,目前不起作用。
回答by Eddy Verbruggen
You could use the PhoneGap (Build) plugin mentioned here: http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/734
您可以使用此处提到的 PhoneGap (Build) 插件:http: //www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/734
The plugin supports multiple certificates, so the server and client don't need to be updated at the same time. If your fingerprint changes every (say) 2 year, then implement a mechanism for forcing the clients to update (add a version to your app and create a 'minimalRequiredVersion' API method on the server. Tell the client to update if the app version is too low (f.i. when the new certificate is activate).
该插件支持多个证书,因此服务器和客户端不需要同时更新。如果您的指纹每(例如)2 年更改一次,则实施一种强制客户端更新的机制(向您的应用程序添加一个版本并在服务器上创建一个“minimalRequiredVersion”API 方法。如果应用程序版本是,则告诉客户端更新太低(激活新证书时为fi)。
回答by SeaJelly
If you use AFNetworking (more specifically, AFSecurityPolicy), and you choose the mode AFSSLPinningModePublicKey, it doesn't matter if your certificates change or not, as long as the public keys stay the same. Yes, it is true that AFSecurityPolicy doesn't provide a method for you to directly set your public keys; you can only set your certificates by calling setPinnedCertificates
. However, if you look at the implementation of setPinnedCertificates, you'll see that the framework is extracting the public keys from the certificates and then comparing the keys.
如果您使用 AFNetworking(更具体地说,AFSecurityPolicy),并且您选择了 AFSSLPinningModePublicKey 模式,那么您的证书是否更改都没有关系,只要公钥保持不变即可。是的,AFSecurityPolicy 确实没有为您提供直接设置公钥的方法;您只能通过调用来设置您的证书setPinnedCertificates
。但是,如果您查看 setPinnedCertificates 的实现,您会看到该框架正在从证书中提取公钥,然后比较这些密钥。
In short, pass in the certificates, and don't worry about them changing in the future. The framework only cares about the public keys in those certificates.
总之,把证书传进去,不用担心以后会变。该框架只关心那些证书中的公钥。
The following code works for me.
以下代码对我有用。
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
回答by jww
...for pinning the entire certificate. Such practice poses a problem...
...用于固定整个证书。这样的做法有问题……
Also, Google changes the certificate monthly (or so) but retains or re-certifies the public. So certificate pinning will result in a lot of spurious warnings, while public key pinning will pass key continuity tests.
此外,谷歌每月(左右)更改证书,但保留或重新认证公众。所以证书锁定会导致很多虚假警告,而公钥锁定会通过密钥连续性测试。
I believe Google does it to keep CRLs, OCSP and Revocation Lists manageable, and I expect others will do it also. For my sites, I usually re-certify the keys so folks to ensure key continuity.
我相信 Google 这样做是为了使 CRL、OCSP 和吊销列表易于管理,我希望其他人也会这样做。对于我的站点,我通常会重新认证密钥,以便人们确保密钥的连续性。
But how do you do this?
但是你怎么做呢?
Certificate and Public Key Pinning. The article discusses the practice and offers sample code for OpenSSL, Android, iOS, and .Net. There is at least one problem with iOS incumbent to the framework discussed at iOS: Provide Meaningful Error from NSUrlConnection didReceiveAuthenticationChallenge (Certificate Failure).
证书和公钥固定。本文讨论了该实践并提供了适用于 OpenSSL、Android、iOS 和 .Net 的示例代码。iOS 上讨论的框架至少存在一个问题:从 NSUrlConnection didReceiveAuthenticationChallenge (Certificate Failure) 提供有意义的错误。
Also, Peter Gutmann has a great treatment of key continuity and pinning in his book Engineering Security.
此外,彼得·古特曼 (Peter Gutmann) 在他的《工程安全》一书中对关键的连续性和固定有很好的处理。
回答by Oshitha Wimalasuriya
If you use AFNetworking, use AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
如果您使用 AFNetworking,请使用 AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];