iOS连接自签名TLS / SSL证书无法正常工作

时间:2014-02-24 15:52:41

标签: ios node.js ssl certificate self-signed

我想为iOS应用程序再次加密和保护MITM攻击的网络连接。由于应用程序只能连接到一台服务器,因此无需像VeriSign那样通过CA签署证书。我想对证书进行自签名并将其与应用程序一起分发。

我尝试了这个但最终得到了kSecTrustResultRecoverableTrustFailure并且无法弄清楚我哪里出错了。有人可以查看它并确定问题或指出我如何调试这个问题的方向?这是一个问题,因为我在localhost上使用/ test?

我认为在创建服务器的证书或设置时存在问题,但我不知道它是什么。我用openssl s_client对它进行了测试,它似乎有效,但iOS不接受它(见下文)。我可以接受kSecTrustResultRecoverableTrustFailure成功,但宁愿避免它。

创建证书

我的openssl.cnf。最后一行指定subjectAltName,应该是唯一重要的一行。

[ req ]
default_bits = 2048 # Size of keys
default_keyfile = key.pem # name of generated keys
default_md = sha256 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name

[ req_distinguished_name ]
# Variable name   Prompt string
# #----------------------   ----------------------------------
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64

# Default values for the above, for consistency and less typing.
# Variable name   Value
#------------------------------   ------------------------------
0.organizationName_default = The Sample Company
localityName_default = Metropolis
stateOrProvinceName_default = New York
countryName_default = US

[ server ]
basicConstraints = critical,CA:FALSE
keyUsage = digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
nsCertType = server
subjectAltName = IP:127.0.0.1,DNS:localhost

这就是我创建证书的方式。我使用sha256,因为似乎不支持md5。然后我将证书转换为iOS所需的DER格式。

macbook:~/Documents/app/https-test/cert$ openssl req -x509 -sha256 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 356 -nodes -config openssl.cnf
Generating a 2048 bit RSA private key
..+++
............................................................+++
writing new private key to 'key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:com
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
macbook:~/Documents/app/https-test/cert$ ls
cert.der    cert.pem    key.pem     openssl.cnf
macbook:~/Documents/app/https-test/cert$ openssl x509 -in cert.pem -outform der -out cert.der

服务器

服务器是一个接受https请求的node.js服务器。

var https = require('https');
var fs = require('fs');

var options = {
    key: fs.readFileSync('../cert/key.pem'),
    cert: fs.readFileSync('../cert/cert.pem')
};

https.createServer(options, function (req, res) {
    res.writeHead(200);
    res.end("test return\n");
}).listen(8443);

我使用以下输出测试了此服务器:

macbook:~/Documents/app/https-test/server$ openssl s_client -showcerts -host localhost -port 8443 -CAfile ../cert/cert.pem
CONNECTED(00000003)
depth=0 /O=The Sample Company/L=Metropolis/ST=New York/C=US
verify return:1
---
Certificate chain
 0 s:/O=The Sample Company/L=Metropolis/ST=New York/C=US
   i:/O=The Sample Company/L=Metropolis/ST=New York/C=US
-----BEGIN CERTIFICATE-----
MIIDIDCCAggCCQClnXQ2tGOF1jANBgkqhkiG9w0BAQsFADBSMRswGQYDVQQKExJU
aGUgU2FtcGxlIENvbXBhbnkxEzARBgNVBAcTCk1ldHJvcG9saXMxETAPBgNVBAgT
CE5ldyBZb3JrMQswCQYDVQQGEwJVUzAeFw0xNDAyMjQwMDEwMTJaFw0xNTAyMTUw
MDEwMTJaMFIxGzAZBgNVBAoTElRoZSBTYW1wbGUgQ29tcGFueTETMBEGA1UEBxMK
TWV0cm9wb2xpczERMA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzudBEZmW78S9EnxQzObf778qIBRf
/pUPVKSC31/8iVLM3w71GtHxI39Gt+WwAhMRRKmO+EhsFWDmQZfg3GNsws4Uj/uO
8I6Xp9rF7IWBIAZ37X2nUPD/qEU4+SFmiNi8POaXPt+5mQQLYFfun5YzpZPoiPpi
wuIkcgY8mOJlNv7Hr4AnyMtMMnscZis+ELVky5Q/mDsiamHzPPGjjKYKDMfwYj8S
yAz0GLKrcHBBm4Re++mefJU0sdapAYEliAJdTs0aBA5lxcRBzkKlFwxgsQrhtwL9
xBY+RC/PbnVWRF/YVrd7o6JvXmWOPFDlbL99v9tGGjoUyFDeLoIMaqaGmwIDAQAB
MA0GCSqGSIb3DQEBCwUAA4IBAQAdnvu4W6GoWkAALpvpEgXBMKq2sApLHib+i8Be
+LrAS/zA1GxlMqswUBUvtGuQq88oGWC/eU3n3PvRE2tuIARg4ZSGo2/KdYfvOFYy
O7hnwdlAYirdj3XKUnomj0sVgeAjJV4xSha7aOzs9mNyLquJvewBEAvQdJnPRYfS
LwSUq5kbbiHyFWHmJnTUfLpfKj0w+LNO4Jrb0GdFs7ZWq3R0Mscig668Htue4xST
jWEh0f/ZcWLK+UVvTvpMb9DTM8oOV94EHt+slaIMEzD2hWjtLcwGfUzX5qYU450v
Kt1b40tBHRHi8ytstg4qdLlwf0NpXejcLQiW1CgNZoEIBtP+
-----END CERTIFICATE-----
---
Server certificate
subject=/O=The Sample Company/L=Metropolis/ST=New York/C=US
issuer=/O=The Sample Company/L=Metropolis/ST=New York/C=US
---
No client certificate CA names sent
---
SSL handshake has read 983 bytes and written 468 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : AES256-SHA
    Session-ID: C8901BBE04CB24444E0DDEA60EB7A72A64822E652973AD1D16E27D1E1F29F828
    Session-ID-ctx:
    Master-Key: D143A0F58C848B0E1BCA7BDF22EEBC326F811961CC10FF3A653715A8D8F96F5825AFC6D200F334D2E1581BFECA940111
    Key-Arg   : None
    Start Time: 1393256956
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
^C

iOS应用

该应用使用此NSURLConnectionDelegate https://gist.github.com/dhoerl/2051599

2 个答案:

答案 0 :(得分:1)

iOS code you linked to期望服务器的证书位于设备的受信任根证书存储区中,或者至少由受信任的根证书颁发机构签名。你得到的错误表明情况并非如此。

该错误表示证书不受信任。根据定义,自签名证书不受信任,因为它没有受信任的根证书颁发机构签名(因此无法验证证书的签名者是否是他们所说的人。)

如果您只是希望获得SSL加密的好处而不受MITM攻击的保护,您可以通过在NSURLConnection委托的didReceiveAuthenticationChallenge方法中执行以下操作来绕过服务器检查:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

请注意,这不会阻止MITM攻击,因为您现在允许连接到任何SSL主机,但如果您真的需要这种保护,则不应使用自签名证书。如果您只想要SSL提供的加密,则可以使用自签名证书。

也就是说,如果要将服务器证书与应用程序捆绑在一起,可以进行服务器身份验证 - 这称为证书锁定。您需要在上面的didReceiveAuthenticationChallenge方法中添加代码,以将服务器的证书与应用程序中嵌入的证书进行比较,并使其仅信任该特定证书而不是其他证书。这当然意味着如果服务器上的证书过期或更改,您的客户端将无法再连接(直到您使用新证书重新构建和重新分发您的应用程序)。这也意味着如果您的服务器的私钥被盗或被盗,您将无法撤销它并发布新的私钥,并且Bad Guys将能够模拟您的服务器到任何尝试使用该服务器连接的客户端。妥协的钥匙。使用受信任的根CA颁发的证书可以避免这两个问题,如果您确实需要服务器身份验证,仍然是推荐的方法。这样,如果您需要,您可以撤销证书,发布新证书,一切都将继续有效。

答案 1 :(得分:0)

@ 1703536的解决方案几乎完成了,我只需要实现另一个URLConnection的委托消息:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

关于安全性的担忧在另一个答案中得到了很好的解释; - )