使用 OpenSSL API 进行安全编程,第 2 部分: 安全握手

不久以前,安全握手是双方的业务得以实现的一个标记。毕竟,握手是一次面对面的机会,能够对潜在的合做者进行评价。安全且可信的握手意味着事务的双方都相信它们正在作的事情对双方都是有益的。不安全的握手标记着只有一方会对事务有着正确的理解。html

握手的工做方式与在线事务相同。linux

developerWorks 上的前一篇文章“使用 OpenSSL API 进行安全编程,第 1 部分:API 概述” 向您介绍了如何使用 OpenSSL 建立基本、简单的安全链接。然而,这篇文章只是展现了基本的默认设置;它并无介绍如何对内容进行定制。不过,我仍然建议你们阅读这篇文章,这样可使您对本文的理解更加完整,由于前一篇文章介绍了数字证书的概念,而且介绍了如何判断一个证书是否成功经过了 OpenSSL 的内部验证。git

本文将深刻介绍 OpenSSL,向您介绍如何加强握手的安全性,防止所谓的 中间人 (MITM)攻击。算法

关于数字证书数据库

在本文后面,咱们将介绍如何获取数字证书并对数字证书进行验证,所以下面咱们将快速讨论一下什么是数字证书,以及它是如何工做的。若是您熟悉数据加密和 SSL 的知识,就能够跳过本节。要了解更多有关加密和 SSL 问题的信息,请参阅在本文后面 参考资料中所列出的文章和教程。编程

数字证书的最简单形式就是 不对称加密密钥(asymmetric cryptography key)。目前关于数字证书的标准中都有一些标识信息,在密钥中也都包含了这些信息。一个典型的数字证书包含全部者的名字(若是这个证书是在一个 Web 服务器上使用的,那么名字就是完整的域名)以及联系信息,还有一个有效日期范围,以及一个安全性签名,用来验证这个证书没有被篡改。浏览器

数字证书可使用 OpenSSL 命令行工具或其余用于此目的的工具简单地建立。可是任何人建立的数字证书都有一个信任的问题。数字证书不只仅是一个加密密钥,它仍是一个在线凭证。证书会向那些试图与您进行通讯的人证实您的身份。为了显示信任关系,数字证书能够由认证权威(CA)机构进行签名。安全

认证权威在数字安全性领域充当一个可信的第三方。因为在在线领域中证实某个实体的身份很是困难,认证权威就接管了这个挑战。它们为那些购买证书或对证书进行签名的用户的身份提供证实。所以,要信任一个证书,用户只须要信任证书权威便可。用户经过拥有并使用 CA 的信任证书来代表本身对认证权威的信任。Verisign 和 Thawte 是很是知名的认证权威。服务器

若是一个证书的安全性曾经受到过威胁,那么这个证书就会被丢弃 —— 也就是说,将其声明为无效。当一个证书被声明为无效时,CA 不可能将其通知全部拥有该证书拷贝的人。相反,CA 会发布一个 证书撤销列表(Certificate Revocation List)(CRL)。浏览器和其余使用数字证书的程序均可以验证这个证书已经被其属主或 CA 撤销了。网络

证书的撤销也可使用 OCSP 协议进行检查。OCSP 表明 Online Certificate Status Protocol(在线证书状态协议),它是在 RFC 2560 中定义的。OpenSSL 既有 OCSP 的功能,又有 CRL 的功能,可是对这些功能的介绍已经超出了本文的范围。目前数字证书所采用的标准是 X.509,这是在 RFC 3280 中定义的。

回页首

开展业务以前的握手

因为本文重点要介绍在握手过程当中服务器数字证书的处理,所以让咱们来深刻介绍一下握手是如何工做的。若是您熟悉 SSL 的过程,能够跳过本节。

在一个链接上开始握手一般是从客户机向服务器说“Hello”开始的。helllo 消息(在规范中就是这么说的)包含了客户机的安全性参数:

  • SSL 版本号
  • 随机生成数据
  • 密码设置
  • 通讯所须要的其余内容

服务器会使用本身的 hello 消息进行响应,其中包含了服务器的安全性参数,这些信息与客户机所提供的信息的类型相同。此时服务器还会发送本身的数字证书。若是客户机还要为这个链接进行认证,那么服务器还会发送一个请求,索取客户机的证书。

当客户机接收到服务器端的 hello 消息以后,数字证书就要进行验证了。这包括检查证书的各类参数,从而确保该证书从未受到过侵害,同时是在它的有效期内使用的。

此处还要执行的另一个步骤是根据链接所使用的主机名对证书的名字进行检查。虽然这不是 SSL 标准的一部分,可是这个步骤倒是高度建议的,它能够防止中间人攻击。这个步骤会验证证书就是来自您认为它所来自的那个实体。若是这两个实体在此处不能匹配,那么就只能怀疑这个证书是无效的。

在客户机和服务器之间共享的随机数据用来建立 premaster secret,这是一个只有服务器和客户机才会知道的共享秘密值,而且只用于这个会话。这个秘密值会对服务器的数字证书进行加密,并发送给服务器用于验证客户机的身份。

若是服务器请求客户机认证,那么客户机就会对这个在握手过程当中随机生成的数据(只有服务器和客户机知道它)建立一个单向的 hash 值。客户机使用本身的私钥对这个 hash 值进行签名,并将签名后的数据和数字证书发送给服务器。服务器使用这些信息对客户机进行认证。

若是认证成功,那么服务器和客户机就会经过一个算法使用这个共享的随机数据来建立 master secret。从 master secret 中,客户机和服务器能够建立 session keys(会话密钥),这是选择用来对会话数据进行加密所使用的对称密码中的对称密钥。

客户机经过向服务器发送一个代表本身已经完成握手的消息,以及一组加密的单向 hash 值让服务器进行验证,从而结束握手的过程。服务器也会向客户机发送一个相似的消息。客户机和服务器在结束握手并开始通讯以前,都要对这些数据进行验证。

回页首

中间人

虽然这是孩子们玩的一个游戏,但却也是在公钥基础设施(PKI)上可能发生的一种很严重的攻击。当咱们在讨论有关数字证书的问题时,就必须考虑中间人攻击的问题,由于无论 SSL 链接以后的安全参数如何,中间人攻击均可以让这些防范措施形同虚设。

假设 Casey 和 Samantha 但愿使用 SSL 进行通讯。Isabel 是一个第三方,她截获了这个链接请求,并在他们两个以前充当代理。当她注意到正在创建一个 SSL 链接时,就向 Samantha 假装成 Casey,向 Casey 假装成 Samantha。因为她位于中间,能够截获会话双方的内容。若是这个会话中包含账号和我的信息,那么她就可使用这些信息窃取他人的身份了。

在这种状况中,信任链和证书中的通用名能够防止中间人攻击的发生。在握手过程当中,会对证书进行交换。在分析证书的有效性时,同时还会检查可信签名。若是服务器证书中的通用名是与证书的其他部分一块儿验证的,那么这种攻击就不攻自破了,对么?其实不彻底对。

让咱们假设 Isabel 有一个证书,其中有 Samantha 的名字;而且这个证书由 Casey 信任模型中的一个 CA 进行了签名。此时只经过检查通用名并不能避免 MITM 攻击的发生。证书及其信任关系都是有效的,名字也检查出来了。咱们如今有了一个大麻烦。

然而,若是考虑一下认证权威,这个问题就并不重要了。大部分认证权威都会在发行带有我的名的数字证书以前就试图验证他的身份就是本人。因为这个缘由,Isabel 就很难从一个知名的认证权威那里得到一个带有 Samantha 名字的数字证书。

若是在 CA 中工做,则能够消弱这种安全设施的做用。例如,若是 CA 和 Isabel 都在一个公司工做(换而言之,是一个“内部工做”)。那么用来签名的密钥就有可能会被 CA 内部工做的人员窃取,他们随后就可使用任何人的名字来建立任意的证书。尽管在建立签名时要使用证书的私有部分,可是采用一些工程的方法或相似的技术也能够窃取密码。

MITM 攻击在使用 代理服务器 的状况中尤为重要。由于安全链接必须由代理服务器提供一个隧道才能到达目的地,所以恶意的代理服务器就能够很容易地窃取任何会话。恶意的代理能够在并不存在隧道时却假装成仿佛隧道真正存在同样。在使用 Internet 上的“匿名代理”时,记得这一点尤为重要:您正在经过它们的系统来发送用户名和密码,怎样才能相信它们呢?

然而咱们要认识到,这种攻击并不只仅存在于计算机和数字安全领域。有一个女人曾经使用相似于 MITM 攻击的技术抢劫了不少家的不少钱(请参阅 参考资料)。

回页首

OpenSSL 和数字证书

OpenSSL 有一个专门用于数字证书的库。假设您如今已经有了 OpenSSL 的源代码,那么这个库的源代码就位于 crypto/x509 和 crypto/x509v3 目录中。源代码为数字证书的处理定义了几个结构。表 1 中列出了这些结构。

表 1. 与 X.509 证书有关的 OpenSSL 结构

结构 功能
X509 包含全部有关数字证书的数据
X509_ALGOR 提供该证书设计所针对的算法
X509_VAL 该证书有效的时间跨度
X509_PUBKEY 证书的公钥算法,一般是 RSA 或 DSA
X509_SIG 证书的 hash 签名
X509_NAME_ENTRY 证书所包含的数据的各个项
X509_NAME 包含名字项的堆栈

这些只是其中涉及的几个结构。在 OpenSSL 中使用的大部分 X.509 结构您本身在应用程序中几乎都不会用到。在上面列出的这些结构中,本文中只使用了两个:X509 和 X509_NAME。

在这些结构之上,有一些用来处理数字证书的函数。这些函数得名于它们所适用的结构。例如,一个名字以 X509_NAME 开始的函数,一般会应用于一个 X509_NAME 结构。后面咱们会根据须要介绍一些函数。

回页首

提供本身的信任证书

在数字证书进行信任验证以前,必须为在为安全链接设置时建立的 OpenSSL SSL_CTX 对象提供一个默认的信任证书,这可使用几种方法来提供,可是最简单的方法是将这个证书保存为一个 PEM 文件,并使用 SSL_CTX_load_verify_locations(ctx, file, path); 将其加载到 OpenSSL 中。file 是包含一个或多个 PEM 格式的证书的文件的路径。path 是到一个或多个 PEM 格式文件的路径,不过文件名必须使用特定的格式。将信任证书保存在一个 PEM 文件中比较简单,这样可使 path 参数为空,以下所示:SSL_CTX_load_verify_locations(ctx, "/path/to/trusted.pem", NULL);

尽管当信任证书在一个目录中有多个单独的文件时更容易添加或更新,可是您不太可能会如此频繁地更新信任证书,所以没必要担忧这个问题。

回页首

验证证书

在通讯继续链接或接收证书以前,请使用 SSL_get_verify_result() 来肯定 OpenSSL 内部对证书的验证结果。若是SSL_get_verify_result() 返回的代码不是 X509_V_OK,那么这就意味着这个证书无效吗?这要取决于返回代码。

一般,若是返回代码不是 X509_V_OK,那么这个证书就有问题,或者这个证书存在安全性隐患。时刻要记住一件事情:OpenSSL 在对证书进行验证时,有一些安全性检查并无执行,包括证书的失效检查和对证书中通用名的有效性验证。

SSL_get_verify_result() 所返回的代码在 OpenSSL 文档的 verify 部分中都进行了介绍,这是在 apps 之下列出的。有些代码的说明是还没有使用,意味着它们永远不会返回。有些代码很是重要,而有些则不过重要。例如,若是因为没有加载所保存的信任证书,而不能对信任证书进行验证,那么是否继续进行通讯,就彻底取决于开发者了。

无论验证结果如何,是否继续使用一些可能不安全的参数也彻底取决于开发者。因为证书多是不安全的,所以会返回错误代码。

回页首

检索证书

若是您但愿向用户显示证书的内容,或者要根据主机名或证书权威对证书进行验证,那么就须要检索证书的内容。要在验证测试结果以后再检索证书,请调用 SSL_get_peer_certificate()。它返回一个指向该证书的 X509 指针,若是证书不存在,就返回 NULL(参见清单 1)。


清单 1. 检索证书
X509 * peerCertificate;
if(SSL_get_verify_result(ssl) == X509_V_OK)
    peerCertificate = SSL_get_peer_certificate(ssl);
else
{
    /* Handle verification error here */
}

回页首

验证证书

在握手时所提供的服务器的证书应该有一个名字与该服务器的主机名匹配。若是没有,那么这个证书就应该标记为值得怀疑的。内部验证过程已经对证书进行信任和有效期的验证;若是这个证书已经超期,或者包含一个不可信的签名,那么这个证书就会被标记为无效的。因为这不是 SSL 标准的一部分,所以 OpenSSL 并不须要根据主机名对该证书的名字进行检查。

证书的“名字”其实是证书中的 Common Name 字段。这个字段应该从证书中进行检索,并根据主机名进行验证。若是两者不能匹配,就只有怀疑这个证书无效了。有些公司(例如 Yahoo)就在不一样的主机上使用相同的证书,即便证书中的 Common Name 只是用于一个主机的。为了确保这个证书是来自于相同的公司,能够进行更深刻的检查,可是这彻底取决于项目的安全性须要。

从证书中检索通用名须要两个步骤:

  • 从证书结构检索 X509_NAME 对象。
  • 而后从 X509_NAME 对象检索名字。

使用 X509_get_subject_name() 从证书中检索 X509_NAME 结构。这会返回一个指向 X509_NAME 的对象。从如今开始,请使用X509_NAME_get_text_by_NID() 来检索通用名,并保存到一个字符串中(如清单 2 所示)。


清单 2. 检索并验证 Common Name
char commonName [512];
X509_NAME * name = X509_get_subject_name(peerCertificate);
X509_NAME_get_text_by_NID(name, NID_commonName, commonName, 512);
/* More in-depth checks of the common name can be used if necessary */
if(stricmp(commonName, hostname) != 0)
{
    /* Handle a suspect certificate here */
}

使用标准的 C 字符串函数或您习惯使用的字符串库对通用名和主机名进行比较。对不匹配的处理,彻底取决于项目的须要或用户的决策。若是要更深刻地进行检查,我建议使用一个单独的字符串库来下降复杂性。

回页首

得到信任

在本文中,咱们已经介绍了如何加强握手的安全性,从而防止中间人攻击(攻击一方假装成另一个可信源),咱们还介绍了数字证书的概念,以及 OpenSSL API 如何处理数字证书。

记住,在 SSL 会话过程当中加强会话的安全性很是重要,这是由于该链接中的全部安全性都是在握手过程当中创建的。遵循本文中概要介绍的每一个步骤到底有多重要取决于项目的要求以及开发者的决定。


参考资料

关于做者

Kenneth Ballard 拥有 Peru State College(位于 Peru, Nebraska)的计算机科学专业学士学位,在这里,他是校报 The Peru State Times 的专职做者。他还拥有 Southwestern Community College(位于 Creston, Iowa)的计算机编程专业副学士学位,在这里,他是一名半工半读的 PC 技术员。他的研究领域包括 Java、C++、COBOL、 Visual Basic、网络、数据库管理和 Internet 编程。您能够经过 kenneth.ballard@ptk.org 与 Kenneth 联系。

相关文章
相关标签/搜索