简体   繁体   English

iOS使用自签名TLS / SSL证书连接不起作用

[英]iOS connect with self-signed TLS/SSL certificate not working

I would like to encrypt and secure a network connection agains MITM attacks for an iOS application. 我想对iOS应用程序的MITM攻击进行加密和保护网络连接。 Since the application will only connect to one server, there is no need to have the certificate signed by a CA like VeriSign. 由于该应用程序将仅连接到一台服务器,因此不需要由诸如VeriSign的CA签署证书。 I want to self-sign the certificate and distribute it with the application. 我想对证书进行自我签名并随应用程序一起分发。

I tried this but end up with kSecTrustResultRecoverableTrustFailure and can not figure out where I went wrong. 我尝试了此操作,但最终以kSecTrustResultRecoverableTrustFailure结束,无法弄清楚哪里出了问题。 Can someone look over it and identify the problem or point me into a direction on how to debug this? 有人可以查看一下并找出问题所在,或者向我指出如何进行调试的方向吗? Is it a problem because I use/test on localhost? 是因为我在本地主机上使用/测试而引起的问题吗?

I think it is a problem in the creation of the certificate or setup of the server but I don't know what it is. 我认为证书创建或服务器设置中存在问题,但我不知道它是什么。 I tested it with openssl s_client and it seems to work but iOS does not accept it (see below). 我用openssl s_client测试了它,它似乎可以工作,但是iOS不接受(请参阅下文)。 I could accept kSecTrustResultRecoverableTrustFailure as success but would rather avoid it. 我可以接受kSecTrustResultRecoverableTrustFailure作为成功,但宁愿避免这种情况。

Creation of certificate 证书创建

My openssl.cnf. 我的openssl.cnf。 The last line specifies subjectAltName and should be the only important one. 最后一行指定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

This is how I create the certificate. 这就是我创建证书的方式。 I use sha256 since md5 seems not supported. 我使用sha256,因为似乎不支持md5。 Afterwards I transform the certificate to DER format which iOS needs. 之后,我将证书转换为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

Server 服务器

The server is a node.js server that accepts https requests. 该服务器是一个接受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);

I tested this server with the following output: 我用以下输出测试了该服务器:

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 app iOS应用

The app uses this NSURLConnectionDelegate https://gist.github.com/dhoerl/2051599 该应用程序使用此NSURLConnectionDelegate https://gist.github.com/dhoerl/2051599

The iOS code you linked to expects the server's certificate to be in the device's trusted root certificate store, or at least signed by a trusted root certificate authority. 链接到iOS代码期望服务器的证书位于设备的受信任的根证书存储中,或至少由受信任的根证书颁发机构签名。 The error you are getting suggests that this is not the case. 您收到的错误表明事实并非如此。

That error means the certificate is not trusted. 该错误表示证书不受信任。 By definition, a self-signed certificate is not trusted because it isn't signed by a trusted root certificate authority (so there's no way to verify that the signer of the certificate is who they say they are). 根据定义,自签名证书不受信任,因为它不是由受信任的根证书颁发机构签名的(因此无法验证证书的签署者是他们所说的身份)。

If you just want the benefits of SSL encryption without the protection from MITM attacks, you can bypass the server check by doing something like the following in the NSURLConnection delegate's didReceiveAuthenticationChallenge method: 如果只想享受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];
}

Note that this will not prevent MITM attacks, since you are now allowing a connection to any SSL host, but if you truly want that kind of protection you shouldn't use a self-signed certificate. 请注意,这不会阻止MITM攻击,因为您现在允许连接到任何SSL主机,但是,如果您确实想要这种保护,则不应使用自签名证书。 If you just want the encryption offered by SSL, a self-signed certificate is fine. 如果只需要SSL提供的加密,则可以使用自签名证书。

That said, you can do server authentication if you are bundling the server certificate in with your application - this is known as certificate pinning. 也就是说,如果您将服务器证书与应用程序捆绑在一起,则可以进行服务器身份验证-这称为证书固定。 You would need to add code in the didReceiveAuthenticationChallenge method above to compare the server's certificate with the one that's embedded in your application and have it trust ONLY that specific certificate and no other. 您可能需要在上面的didReceiveAuthenticationChallenge方法中添加代码,以将服务器的证书与应用程序中嵌入的证书进行比较,并使它仅信任该特定证书,而不信任其他证书。 This of course means that if the certificate on your server ever expires or changes, your clients will no longer be able to connect (until you rebuild and redistribute your application with the new certificate). 当然,这意味着如果服务器上的证书过期或更改,则客户端将不再能够连接(直到您使用新证书重建并重新分发应用程序)。 It also means that if your server's private key is ever stolen or compromised, you won't be able to revoke it and issue a new one, and the Bad Guys will be able to impersonate your server to any clients that try to connect using the compromised key. 这也意味着,如果服务器的私钥曾经被盗或被盗,则您将无法撤消它并发出新的私钥,并且Bad Guys可以将您的服务器模拟给尝试使用该服务器进行连接的任何客户端。密钥泄露。 Using a certificate issued by a trusted root CA avoids both problems, and is still the recommended way to go if you truly need server authentication. 使用由受信任的根CA颁发的证书可以避免这两个问题,并且如果您确实需要服务器身份验证,仍然推荐使用该方法。 That way you can revoke the certificate if you ever need to, issue a new one, and everything will still work. 这样,您可以根据需要吊销证书,签发新证书,一切仍然可以进行。

@1703536 's solution almost did it, I only had to implement another URLConnection's delegate message : @ 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];
}

Concerns about security doing that are well explained in the other answer ;-) 关于安全性的担忧在另一个答案中得到了很好的解释;-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM