当你进行HTTPS连接时,客户端必须评估服务器是否可信。如果评估失败,客服端应该取消连接。这种情况可能由于各种原因,例如,服务器使用自己签名的证书,中间证书缺失等。还有一些恶意情况,服务器可能是个为了盗窃用户数据的冒充服务器。
• 验证咨询(authentication challenge) - 它是一个HTTP或者HTTPS的响应,指示服务器请求客服端验证信息。Foundation框架中用NSURLAuthenticationChallenge表示。它同样支持HTTPS的服务器信任评估。它是保护空间的验证咨询。
• 证书(certificate)- 摘要证书。
• 证书认证机构(certificate authority)- 一个可靠的派发证书机构。每个CA会派发一个或者多个根证书,这些根证书用于信任评估该授权机构派发的证书。
• 证书认证机构锁定(certificate authority pinning)- 服务器需要提供指定的CA派发的证书。
• 证书锁定(certificate pinning)- 服务器需要提供指定的证书或者证书必须包含指定的公钥。
• 撤销证书列表(certificate revocation list,CRL)- 一个被撤销的证书列表,它们不应该被信任。
• 数字证书(digital certificate)- 一种最通用的证书(certificate)它使用公钥加密实体的数字签名来关联信息。在TLS中,所有数字证书都是X.509数字证书。
• 数字ID(digital identity)- 证书和关联该证书公钥的私钥的结合。
• 数字签名(digital signature)- 用于证明一些数据的真实性。你可以使用私钥来验证签名来确保是对应的私钥加密生成的。
• 扩展验证(extended validation)- 使用扩展证书验证。
• HTTP - 超文本传输协议。
• HTTPS - 在TLS上的HTTP。
• HTTPS服务器信任评估 - (HTTPS server trust evaluation)HTTPS是TLS上的HTTP,所以等价于TLS服务器信任评估。
• 中间证书 - (intermediate certificate)服务器证书到根证书之前的中间证书。
• 发行人 -(issuer)X.509数字证书签名的实体。
• OCSP -(Online Certificate Status Protocol)一个检查证书是否被撤销的协议。
• 私钥 -(private key)用于解密数据和生成数字签名。
• 保护空间 -(protection space,realm)一个请求验证的HTTP或者HTTPS的服务器。在Foundation框架中,用NSURLProtectionSpace表示。
• 公钥 -(public key)用于加密数据或者验证数字签名。
• 公钥加密系统 -(public key cryptography)一个加密系统。它使用两个分开的密钥。一个是公钥,一个是私钥。私钥用于解密数据和生成数字签名,公钥用于加密数据和验证数字签名。
• 公钥基础设施 -(public key infrastructure)一个管理公钥和私钥的机制。公钥内嵌到证书,TLS使用X.509公钥。
• 根证书 -(root certificate)一个CA提供的自签名证书,用来信任评估该CA派发的证书。
• 安全套接字层 -(SSL)TLS的上版本。
• 自签名证书 -(self-signed certificate)一个X.509数字证书,它的主体和发行人是相同的。根证书是自签名的,但是任何人都可以创建自己的自签名证书。
• 服务器证书 -(server certificate)TLS服务器提供的X.509数字证书。TLS协议确保私钥在服务器上,对应的公钥内嵌在这个证书。这个证书正是TLS服务器信任验证的对象。
• 服务器信任评估 -(server trust evaluation)客户端对服务器进行信任评估。
• 主体 -(subject)对于X.509数字证书,它是证书标识的实体。在TLS中,这个服务器证书主体是服务器的DNS名称。
• TLS -(TLS)传输层安全协议,在SSL之后。
• TLS服务器信任评估 -(TLS server trust evaluation)信任评估包含一组X.509证书信任评估和附加的TLS特定检查。操作的是服务器的证书。
• 信任评估 -(trust evaluation)一个实体决定是否信任另外一个实体,根据这个实体的数字证书。
• 信任锚点 -(trust anchor)系统明确信任的证书。一般是已经存储在系统的CA的根证书。但在一些情况下,你可以标记任何证书为信任锚点。
• 信任的证书认证机构 -(trusted certificate authority)CA的根证书都被存储在系统并成为信任锚点。
• 有效日期范围 -(valid date range)对于X.509证书,代表证书的有效日期范围。
• 验证日期 -(verify date)在X.509证书信任评估,有效日期需要检验。
服务器信任评估错误:
Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “example.com” which could put your confidential information at risk." UserInfo=0x14a730 {NSErrorFailingURLStringKey=https://example.com/, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSErrorFailingURLKey=https://example.com/, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “example.com” which could put your confidential information at risk., NSUnderlyingError=0x14a6c0 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “example.com” which could put your confidential information at risk.", NSURLErrorFailingURLPeerTrustErrorKey=}
在这个错误中code=-1202代表NSURLErrorServerCertificateUnTrusted。表明服务器信任评估失败。
HTTPS是HTTP在TLS上的协议。
• 保证隐私 - 不能修改和替换数据包。
• 客服端验证服务器证明 - 保证不被中间人攻击(能够在客服端和服务器之间修改和替换数据包),中间人可以冒充服务器来获取客服端的私密数据。
TLS也可以服务器验证客服端证明。服务器请求客服端证书来验证是否信任客服端。
当你使用TLS连接服务器,服务器会提供证书给你并且保证服务器的私钥对应证书的公钥。之后检查证书是否是你想要连接的服务器。
TLS服务器信任评估包括下面2个基本步骤:
1. 基本X.509证书信任评估。
2. 附加的TLS检查。
X.509证书有5个重要属性:
• 主体信息。
• 发行人信息。
• 证书信息(例如,有效日期范围)。
• 公钥。
• 数字签名。
如果数字证书是有效的,发行人会担保证书的主体拥有私钥和对应的公钥在证书。
X.509证书信任评估会递归这2步处理:
1. 检查证书的有效性。主要涉及2步检查,检查数字签名的有效性和证书有效日期。
2. 检查发行人的有效性。检查发行人的证书并递归地检查证书有效性。
递归处理必须最终能够终止。信任评估只有一种成功情况:命中信任锚点。信任锚点是系统明确信任的证书和已经存储在系统的著名CA根证书。
信任评估可能会失败由于各种原因:
• 命中无效证书。
• 不能发现证书的发行人。
• 命中自签名的证书不是信任锚点。
如果X.509证书信任评估成功,系统会进行附加的特定TLS检查。涉及检查连接的DNS域名和证书的DNS域名是否相同。这里还有一些其他的事情:
• 通常,你可能希望发现DNS域名在主体通用名参数(Common Name field),也可以在主体可选名扩展中(Subject Alternative Name extension)。如果这个名字出现在主体可选名扩展,优先使用它(相对于Common Name field)。
• 主体可选名扩展(Subject Alternative Name extension)可能包含IP地址。客服端可以商量使用IP地址或者是DNS域名。
• 证书的DNS域名可能包含通配符,例如“*.apple.com”。
• Extended Key Usage扩展希望包含Server Authentication值。
• 没有发行人证书 - 对所以提供的证书(除了信任锚点证书),系统必须能够定位到发行人证书。
• 日期问题 - 验证日期在证书的有效日期范围。
• 自签名证书 - 对于自签名证书,这会导致评估失败(除非它是信任锚点证书)。
• 不是可信任的证书认证机构(CA)- 系统必须随着发行人证书直到信任的CA根证书。
• DNS域名不匹配 - 你尝试连接的服务器的DNS域名必须匹配服务器证书的DNS域名。
钥匙串
访问有大量的证书调试特性:
• 它可以导入和导出证书并且用各种格式标识。
• 如果你双击证书,系统会打开一个GUI界面。
• 证书助手(从钥匙串获取)能够信任评估指定的证书。
• 证书助手可以创建自签名证书。
• 证书助手可以创建CA来派发叶子证书或者中间证书。
你可以创建一个钥匙链来进行钥匙串访问。
• 转存钥匙链为文本形式。
• 详细地进行信任设置。
• 添加和移除钥匙链证书。
如果你使用Safari浏览器访问HTTPS网站,你可以在title栏点击锁头图标来获取证书的相关信息。
如果你使用Safari浏览器访问不被信任的HTTPS网址,Safari会展示一个“不能验证网站身份”的Sheet。
信任评估对象SecTrustRef。你可以直接创建它,但TLS服务器中,系统的API已经帮你做了默认的信任评估处理,你可以在这个基础上自定义它。
1. 获取信任对象。
2. 自己评估,确保检查上述的失败情况。
3. 如果信任评估成功,你可以指定严格的信任评估规则。
4. 如果信任评估失败,使用你自定义信任对象。
5. 再次评估这个对象,确定允许或者取消连接。
OSStatus err;
BOOL allowConnection;
SecTrustResultType trustResult;
allowConnection = NO;
err = SecTrustEvaluate(trust, &trustResult);
if (err == noErr) {
allowConnection = (trustResult == kSecTrustResultProceed) ||
(trustResult == kSecTrustResultUnspecified);
}
如果你收到的服务器证书不是被系统认证的CA派发的,你可以调用SecTrustSetAnchorCertificates方法来设置锚点证书(相当于CA的根证书)。
OSStatus err;
BOOL allowConnection;
SecCertificateRef customAnchor;
SecTrustResultType trustResult;
allowConnection = NO;
customAnchor = ... the CA's root certificate ...;
err = SecTrustSetAnchorCertificates(
trust,
(__bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id) customAnchor]
);
if (err == noErr) {
err = SecTrustEvaluate(trust, &trustResult);
}
if (err == noErr) {
allowConnection = (trustResult == kSecTrustResultProceed) ||
(trustResult == kSecTrustResultUnspecified);
}
code
最后,可能由于一些情况自定义请求不能直接在信任对象评估。例如,如果你希望信任对象被认为是额外的中间证书,你不能直接添加到信任对象。你可能需要重新创建信任对象来评估它。
OSStatus err;
BOOL allowConnection;
CFArrayRef policies;
NSMutableArray * certificates;
CFIndex certCount;
CFIndex certIndex;
SecCertificateRef extraIntermediate;
SecTrustRef newTrust;
SecTrustResultType newTrustResult;
allowConnection = NO;
policies = NULL;
newTrust = NULL;
err = SecTrustCopyPolicies(trust, &policies);
if (err == errSecSuccess) {
certificates = [NSMutableArray array];
certCount = SecTrustGetCertificateCount(trust);
for (certIndex = 0; certIndex < certCount; certIndex++) {
SecCertificateRef thisCertificate;
thisCertificate = SecTrustGetCertificateAtIndex(trust, certIndex);
[certificates addObject:(__bridge id)thisCertificate];
}
extraIntermediate = ... the extra intermediate certificate to use ...;
[certificates addObject:(__bridge id)extraIntermediate];
err = SecTrustCreateWithCertificates(
(__bridge CFArrayRef) certificates,
policies,
&newTrust
);
if (err == noErr) {
err = SecTrustEvaluate(newTrust, &newTrustResult);
}
if (err == noErr) {
allowConnection = (newTrustResult == kSecTrustResultProceed) ||
(newTrustResult == kSecTrustResultUnspecified);
}
}
if (newTrust != NULL) {
CFRelease(newTrust);
}
if (policies != NULL) {
CFRelease(policies);
}
你可以获取NSURLConnect的authentication challenges通过resourceLoadDelegate属性。
不能自定义HTTPS服务器信任评估。
HTTP流媒体直播支持服务器信任评估根据获取的资源类型。
NSURLSession允许你通过实现URLSession:didReceiveChallenge:completionHandler:方法来自定义HTTPS服务器信任评估。为例实现自定义HTTPS服务器信任评估,你需要检查Challenge对象的保护空间(protection space)是否有NSURLAuthenticationMethodServerTrust验证方法。对于其他类型的Challenge,你不用去考虑,只需要调用completion handler block 使用NSURLSessionAuthenticationChallengePerformDefaultHandling的disposition处理和NULL 证书。
当你处理NSURLAuthenticationMethodServerTrust的authentication challenge,你可以获取信任对象从challenge的保护空间(protection space)调用serverTrust方法获取。之后你可以使用这个信任对象来执行自定义HTTPS服务器信任评估,你必须处理challenge使用下面2个方法:
• 如果你想要禁止连接,调用completion handler block并提供NSURLSessionAuthChallengeCancelAuthenticationChallenge的disposition和NULL 证书。
• 如果你想要允许连接,创建证书从你的信任对象(使用+[NSURLCredential credentialForTrust:]方法),之后调用completion handler block并提供NSURLSessionAuthChallengeUserCredential的disposition。
注意:系统会避免你使用创建的证书的信任对象的信任结果;任何有效的信任对象将允许连接成功。
NSURLConnection允许你自定义HTTPS服务器信任评估。类似NSURLSession,这里有2个不同的地方:
• 你实现不同的代理方法(-connect:willSendRequestForAuthenticationChallenge:)。
• 当处理challenge,你必须获取sender从challenge(通过-sender方法),之后调用适当的方法在sender。
• 对于你不用考虑的challenge,调用-performDefaultHandlingForAuthenticationChallenge方法。
• 如果你想要禁止连接,调用-cancelAuthenticationChallenge: 。
• 如果你想要允许连接,调用-useCredential:forAuthenticationChallenge:,提供证书对象使用信任对象创建。
CFHTTPStream允许你自定义HTTPS服务器信任评估。
为了自定义TLS服务器信任评估:
1. 使用kCFStreamSSLValidatesCertificateChain实体的kCFStreamPropertySSLSettings属性来完全失效服务器信任评估。
2. 一旦流被连接,在你发送任何数据和信任任何接收数据之前,从流的kCFStreamPropertySSLPeerTrust的属性获取信任对象。
3. 使用信任对象来实现自定义HTTPS信任评估。
4. 根据信任评估结果来决定继续连接或者关闭。
第2步:什么时候流被连接?当你获取流打开完成事件,信任对象不可用。为了保证信任对象可用,你必须等待有空间可用事件或者有字节可用事件。
安全传输也允许自定义TLS服务器信任评估。处理过程如下:
1. 开始连接之前,调用SSLSetSessionOption并设置kSSLSessionOptionBreakOnServerAuth。
2. 如果有必要,调用SSLSetEnableCertVerify方法来失效默认服务器认证评估。
3. 执行安全传输握手。
4. 当SSLHandshake返回errSSLServerAuthComplete,调用SSLCopyPeerTrust来连接的信任对象。
5. 使用信任对象实现自定义服务器信任评估。
6. 继续执行安全传输的握手或者中断连接。
在处理之前记住2点:
• 目前最简单和最安全的处理服务器信任评估失败的方法是修复服务器。你应该在修复服务器很困难的情况下考虑下面的方法。
• 大多数用户是没有相关网络安全的知识的。呈现用户界面让用户决定是否连接是错误的方式,这可能会造成一些安全隐患。
如果服务器信任评估失败是由于服务器的DNS域名不匹配证书的DNS域名,你可以忽略这种情况。使用SecPolicyCreateSSL方法创建一个新的带有正确的服务器名的政策(SecPolicyRef),之后调用SecTrustSetPolicies方法来让信任对象(SecTrustRef)使用这个政策。
这里有几种方法来解决缺失中间证书:
• 最佳的方法同时也是常用的方法就是修复服务器。让服务器提供可以追踪到信任的CA根证书的服务器证书。
• 在OS X上,你可以添加这个中间证书到钥匙链来完成正确的信任。
• 在IOS上,你可以添加这个中间证书到钥匙链来完成正确的信任。
• 如果上面都失败,你只能自己生产中间证书(bundle或者从网上下载);之后从原来的信任对象获取信任链证书集合并添加中间证书;重新使用证书集合创建新的信任对象;使用这个信任对象进行信任评估。
信任一个特殊证书
在一些情况,把证书当做简单的id token是有用的。例如,在点对点程序中,X.509信任评估是无意义的,因为这里没有CA派发它们。但是,你任然可以使用TLS安全传输。
1. 通过你自己建议的方式获取远程点证书的副本;你可以让远程用户email证书给你并放到U盘上,或者任何其他方式。
2. 从信任对象获取服务器证书。(传0下标给SecTrustGetCertificateAtIndex)
3. 获取证书的数据。(SecCertificateCopyData)
4. 比较这个证书和步骤1获取的证书数据;匹配,进行正确的连接。
如果服务器证书的CA不是系统信任的,你可以包含这个CA的根证书来处理这个问题。
1. 在程序中,包含这个CA根证书副本。
2. 一旦获取信任对象,创建这个证书使用证书数据(SecCertificateCreateWithData)并设置这个证书为信任锚点(SecTrustSetAnchorCertificates)。
3. SecTrustSetAnchorCertificates设置一个flags来阻止信任对象信任其它的锚点;如果你想信任系统的默认锚点,调用SecTrustSetAnchorCertificatesOnly来清楚flag。
4. 评估信任对象。
这里有各种原因使用自签名证书。下面是通常的情况:
在开发过程中使用自签名证书有利于建立TLS基本服务器测试。这是有理由使用自签名证书和关闭TLS服务器信任评估。
当使用自签名证书进行开发,更好的方式是创建自己的CA并派发证书给测试服务器。你可以导入CA的根证书到APP或者每个测试者的系统都安装这个CA的根证书。(使用Safari,Mail和配置描述文件(configuration profiles)在IOS,钥匙串访问在OS X)。这种方式的好处是不用关闭TLS服务器信任评估,也意味着你不用担心在发布环境下忘了打开TLS服务器信任评估。
• 证书助手(Certificate Assistant)- 这个APP已经内嵌在OS X中,它拥有友好的用户界面和管理自己的CA。
• OpenSSL - OpenSSL命令行工具允许你创建和管理自己的CA。
一些组织由于商业原因使用自签名证书在生产环境的基础措施。有可能是这个组织不愿意从CA购买证书或者它有可够获取这样的证书。
如果自签名证书是可选的,那么最好还是使用自己的CA。使用自己的CA比自签名证书有以下好处:
• 服务器改变 - 如果服务器改变会破坏服务器信任评估,你的CA能够派发新的证书来描述这个变化而且客服端会自动的信任它。例如,你改变服务器的DNS域名,新的证书包含新的DNS域名。
• 重新派发 - 如果服务器的证书过期,你的CA能重新派发。
• 撤销 - 你的CA可以通过OCSP或者CRL协议来撤销证书。
你可以避免使用自签名证书,但是如果使用第三方服务器,这个服务器使用自签名证书;最好的办法是让他们使用信任的CA派发的证书。如果这不可能,那么内嵌这个证书在APP中,使用适当的方法来信任这个证书。
如果你构建通用目的应用(能广泛连接各种服务器),在这种情况你需要通用的解决方案。
最好的通用方式是什么都不做。这种默认的服务器信任评估有2个重要的好处:
• 容易实现。
• 安全。
最后一点很重要:如果发生服务器信任评估失败,你提供用户避免安全性的方式,用户将会总是选择它不管有多不安全。从安全性来看,最好是评估失败并且让用户迫使服务器的管理者修复这个问题。
如果你选择避免这个建议,你可以执行一些步骤来减少风险。
1. 尝试连接服务器。
2. 如果服务器信任评估失败(报告用户这个问题)。
3. 如果用户决定连接,记住这个决定并继续这个连接。
4. 之后,如果再次遇到这个服务器信任失败,避免这个问题并继续连接。
在第4步,你怎么确认是同样的错误呢?
1. 用户连接的这个服务器提供一个过期的证书。
2. 你鉴别这个错误并询问用户是否真的确认连接它。
3. 用户同意,记住这个决定并连接它。
4. 之后可能进入一个不安全的网络,再次连接一个冒充的服务器。
5. 你的程序连接了这个冒充的服务器,虽然检测到服务器信任失败,但是用户同意连接它。
这个问题在你的程序是连接一个冒充的服务器,虽然用户只是同意避免过期证书。
你可以使用信任异常来解决这个问题。当用户同意连接这个服务器,在程序中你可以获取信任对象的信任异常。这记录了必要信息去避免当前信任评估失败。在以后,如果你遇到这个的服务器的信任评估失败,你可以设置这个异常给信任对象(SecTrustSetException)。如果解决了服务器信任评估失败,可以安全地进行这个连接。如果不,这必须询问用户是否同意这个额外的信任异常。
自定义服务器信任评估可能不满满足一些问题;你可以使用这种方式来使应用更加安全。如果你进行高级别安全编程,你可能不仅需要默认的服务器信任评估,还需要增加一些自定义检查。
例如,你可能不仅是检查服务器正式是否是信任的CA,而且检查是否是指定的CA派发(这个技术是证书认证机构锁定,ertificate authority pinning)。这很容易使用信任对象完成。这个步骤如下:
1. 包含这个CA根证书副本。
2. 一旦你有信任对象,创建证书使用这个证书的证书数据(SecCertificateCreateWithData)之后设置这个证书为信任锚点(SecTrustSetAnchorCertificates)。
3. 评估信任对象。如果评估成功,服务器证书就是有效的而且是由这个指定的CA派发的。
CA锁定只是一个例子说明怎样强制执行更严格的信任评估。这里有其他方式你可以考虑:
• 你可以实现证书锁定(certificate pinning)。从服务器证书取出公钥(SecTrustCopyPublicKey)。
• 你可以检查证书的某些属性值或者扩展是否存在。在OS X,使用SecCertificateCopyValues。
• SecTrustCopyResult让你决定是否扩展验证作为信任评估的一部分。
• SecPolicyCreateRevocation让你获取安全政策用于检查撤销证书。
这里有2个情形需要SecTrustEvaluate访问网络:
• 需要下载中间证书。
• 需要确定正式是否已经被撤销。
这些网络操作可能会有短暂的超时,但是SecTrustEvaluate任然不适合在主线程调用,特别在IOS。如果你需要在主线程调用SecTrustEvaluate方法,你有3个选项:
• 你可以使用SecTrustEvaluateAsync。
• 你可以使用SecTrustSetNetworkFetchAllowed来禁止信任对象访问网络。
• 你可以使用并发运行SecTrustEvaluate在另外的线程(GCD,NSOperation)。
有时候很难弄清楚为什么信任评估失败。你先检查是否是通用失败,如果你任然不能找到失败原因,你可以尝试下面的方式:
• 打印SecTrustCopyResult的结果。
• 打印SecTrustCopyProperties的结果。
• 检查信任异常数据(SecTrustCopyExceptions)。
警告:信任异常数据是用于调试用的,不能直接打印。
第3步返回的是二进制属性列表文件,你可以保存为.plist文件,之后用xcode打开。
在一些情况下,展示证书给用户可能是有用的。例如,需要手动确认服务器id标识。在OS X上可以调用高级的API:
• SFCertificateView是一个NSView,用于展示证书。
• SFCertificatePanel是一个面板展示一个或者多个证书。
通常,你应该调用高级的API,如果你需要展示自己的图形界面,使用低级的API(SecCertificateCopyValues)。
IOS中是没有高级的API用于展示证书,但是这不重要。因为IOS限制获取证书的细节。
最好不要询问用户安全相关问题,因为他们不是合格的回答者。如果你需要展示失败的信任评估结果,在OS X中有高级别API(SFCertificateTrustPanel)。
IOS没有相同的高级别API展示信任结果。