我创建了一个概念验证Node.js HTTPS服务器,带有来自可信CA的服务器证书(my-server.crt和my-server.key),我创建了自己的CA来创建客户端证书(ca.crt,client.key,client.crt和client.p12)。使用cURL和客户端PEM证书,我可以使用这些客户端证书成功进行身份验证。但是,如果使用嵌入在主程序包中的client.p12证书进一步降低iOS客户端代码,我无法进行身份验证。
这是我简单的Node.js服务器
#!/usr/bin/env node
'use strict';
var https = require('https');
var http = require('http');
var fs = require('fs');
var port = process.argv[2] || 8043;
var insecurePort = process.argv[3] || 4080;
//
// Certificates
//
//Sync file reads- not for production
var options = {
key: fs.readFileSync("certs/my-server.key")
, cert: fs.readFileSync("certs/my-server.crt")
, ca: fs.readFileSync("certs/ca.crt")
, requestCert: true
, rejectUnauthorized: true
};
//
// HTTPS
//
var server = https.createServer(options, function (req, res) {
if (req.client.authorized) {
res.writeHead(200, {"Content-Type": "application/json"});
res.end('{"status":"approved"}');
} else {
res.writeHead(401, {"Content-Type": "application/json"});
res.end('{"status":"denied"}');
}
});
server.listen(port, function(){
console.log("\nListening for HTTPS traffic on port: " + port);
});
//
// HTTP
//
var insecureServer = http.createServer();
insecureServer.on('request', function (req, res) {
res.writeHead(200, {"Content-Type": "application/json"});
res.write('{"status":"insecure"}');
res.end();
});
insecureServer.listen(insecurePort, function(){
console.log("\nListening for HTTP traffic on port: " + insecurePort);
});
然后用这个cURL声明:
curl -v -s -k --key certs/client.key --cert certs/client.crt https://127.0.0.1:8043
我已成功通过身份验证(来自cURL的输出):
* Rebuilt URL to: https://127.0.0.1:8043/
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Adding handle: conn: 0x7fa115009a00
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7fa115009a00) send_pipe: 1, recv_pipe: 0
* Connected to 127.0.0.1 (127.0.0.1) port 8043 (#0)
* error setting certificate verify locations, continuing anyway:
* CAfile: /opt/local/share/curl/curl-ca-bundle.crt
CApath: none
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Request CERT (13):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS handshake, CERT verify (15):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using AES256-GCM-SHA384
* Server certificate:
* subject: OU=Domain Control Validated; CN=secure.wyldresearch.net
* start date: 2015-08-25 00:32:39 GMT
* expire date: 2016-08-25 00:32:39 GMT
* issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET / HTTP/1.1
> User-Agent: curl/7.34.0
> Host: 127.0.0.1:8043
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Sun, 30 Aug 2015 08:22:15 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
* Connection #0 to host 127.0.0.1 left intact
{"status":"approved"}
最后,我创建了一个PoC iOS应用程序,并使用下面的语句将我使用导出密码“DoubleTrouble”创建的client.p12证书包含在应用程序包中。
pkcs12 -export -in certs/client.crt -inkey keys/client.key -name "test cert" -out client.p12
以下是我在iOS应用中的代码片段:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSString *p12Path = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
NSData *p12Data = [[NSData alloc] initWithContentsOfFile:p12Path];
CFStringRef password = CFSTR("DoubleTrouble");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef p12Items;
OSStatus result = SecPKCS12Import((__bridge CFDataRef)p12Data, optionsDictionary, &p12Items);
if(result == noErr) {
NSLog(@"Certificate Imported OK");
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);
SecCertificateRef certRef;
SecIdentityCopyCertificate(identityApp, &certRef);
SecCertificateRef certArray[1] = { certRef };
CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
CFRelease(certRef);
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:(__bridge NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
CFRelease(myCerts);
}
}
这是我在XCode中在控制台中获得的内容:
2015-08-30 10:19:13.213 MutualTLS[3117:298681] Certificate Imported OK
2015-08-30 10:19:13.332 MutualTLS[3117:298763] CFNetwork SSLHandshake failed (-9806)
2015-08-30 10:19:13.394 MutualTLS[3117:298763] CFNetwork SSLHandshake failed (-9806)
2015-08-30 10:19:13.454 MutualTLS[3117:298763] CFNetwork SSLHandshake failed (-9806)
2015-08-30 10:19:13.455 MutualTLS[3117:298763] NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9806)
2015-08-30 10:19:13.457 MutualTLS[3117:298681] Error retrieving data, An SSL error has occurred and a secure connection to the server cannot be made.
当然,如果我在Node.js中创建HTTPS服务器的选项中设置了rejectUnauthorized = false,则该请求被接受但仍未经过身份验证。
2015-08-30 10:25:57.558 MutualTLS[3117:298681] Certificate Imported OK
2015-08-30 10:25:57.749 MutualTLS[3117:298681] {"status":"denied"}
我知道我可以继续拒绝,但是只需返回
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
但这不会受到MITM漏洞的影响。任何建议都将不胜感激。