如何在iOS上固定证书的公钥

Jav*_*edo 49 security iphone ssl ipad ios

在提高我们正在开发的iOS应用程序的安全性的同时,我们发现需要PIN(全部或部分)服务器的SSL证书来防止中间人攻击.

即使有各种方法可以做到这一点,当你搜索这个时我只找到了固定整个证书的例子.这种做法带来了一个问题:一旦证书更新,您的应用程序将无法再连接.如果您选择固定公钥而不是整个证书,您将发现自己(我相信)处于同样安全的情况,同时对服务器中的证书更新更具弹性.

但是你怎么做的?

Jav*_*edo 36

如果您需要知道如何从iOS代码中的证书中提取此信息,这里有一种方法可以执行此操作.

首先添加安全框架.

#import <Security/Security.h>
Run Code Online (Sandbox Code Playgroud)

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

#import <openssl/x509.h>
Run Code Online (Sandbox Code Playgroud)

NSURLConnectionDelegate协议允许您决定连接是否应该能够响应保护空间.简而言之,这是您可以查看来自服务器的证书,并决定允许连接继续或取消.您要在此处执行的操作是将证书公钥与您固定的证书进行比较.现在的问题是,你如何得到这样的公钥?看看下面的代码:

首先获得X509格式的证书(你需要ssl库)

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

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

ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];    
Run Code Online (Sandbox Code Playgroud)

此时,您可以遍历pubKey2字符串并以十六进制格式将字节提取为具有以下循环的字符串

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

打印公钥以查看它

 NSLog(@"%@", publicKeyString);
Run Code Online (Sandbox Code Playgroud)

完整的代码

- (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;
}

}
Run Code Online (Sandbox Code Playgroud)

  • 从哪里得到`serverCertificateData`? (15认同)

bee*_*tra 21

据我所知,您不能直接在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);
    }
 }
Run Code Online (Sandbox Code Playgroud)

免责声明:这只是示例代码,未经过全面测试.有关完整实现,请参阅OWASP证书锁定示例.

请记住,使用SSL Kill Switch和类似工具始终可以避免证书固定.

  • 干得好!允许在没有所有openSSL头痛的情况下进行公钥比较 (2认同)

Jan*_*ano 9

您可以使用SecTrustCopyPublicKeySecurity.framework 的功能执行公钥SSL固定.请参阅AFNetworking项目的连接示例:willSendRequestForAuthenticationChallenge : .

如果您需要iOS版openSSL ,请使用https://gist.github.com/foozmeat/5154962它基于st3fan/ios-openssl,目前无效.


Edd*_*gen 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).