建立基本的安全链接和非安全链接php
简介: 学习如何使用 OpenSSL —— 用于安全通讯的最著名的开放库 —— 的 API 有些强人所难,由于其文档并不彻底。您能够经过本文中的提示补充这方面的知识,并驾驭该 API。在创建基本的链接以后,就能够查看如何使用 OpenSSL 的 BIO 库来创建安全链接和非安全链接。与此同时,您还会学到一些关于错误检测的知识。html
OpenSSL API 的文档有些含糊不清。由于尚未多少关于 OpenSSL 使用的教程,因此对初学者来讲,在应用程序中使用它可能会有一些困难。那么怎样才能使用 OpenSSL 实现一个基本的安全链接呢?本教程将帮助您解决这个问题。java
学习如何实现 OpenSSL 的困难部分在于其文档的不彻底。不彻底的 API 文档一般会妨碍开发人员使用该 API,而这一般意味着它注定要失败。但 OpenSSL 仍然很活跃,并且正逐渐变得强大。这是为何?linux
OpenSSL 是用于安全通讯的最著名的开放库。在 google 中搜索“SSL library”获得的返回结果中,列表最上方就是 OpenSSL。它诞生于 1998 年,源自 Eric Young 和 Tim Hudson 开发的 SSLeay 库。其余 SSL 工具包包括遵循 GNU General Public License 发行的 GNU TLS,以及 Mozilla Network Security Services(NSS)(请参阅本文后面的 参考资料 ,以得到其余信息)。程序员
那么,是什么使得 OpenSSL 比 GNU TLS、Mozilla NSS 或其余全部的库都优越呢?许但是一方面因素(请参阅 参考资料)。此外,GNS TLS(迄今为止)只支持 TLS v1.0 和 SSL v3.0 协议,仅此而已。算法
Mozilla NSS 的发行既遵循 Mozilla Public License 又遵循 GNU GPL,它容许开发人员进行选择。不过,Mozilla NSS 比 OpenSSL 大,而且须要其余外部库来对库进行编译,而 OpenSSL 是彻底自包含的。与 OpenSSL 相同,大部分 NSS API 也没有文档资料。Mozilla NSS 得到了 PKCS #11 支持,该支持能够用于诸如智能卡这样的加密标志。OpenSSL 就不具有这一支持。编程
要充分理解并利用本文,您应该:安全
并不绝对要求您熟悉 SSL ,由于稍后将给出对 SLL 的简短说明;不过,若是您但愿获得详细论述 SSL 的文章的连接,请参阅 参考资料部分。拥有密码学方面的知识当然好,但这并非必需的。服务器
SSL 是一个缩写,表明的是 Secure Sockets Layer。它是支持在 Internet 上进行安全通讯的标准,而且将数据密码术集成到了协议之中。数据在离开您的计算机以前就已经被加密,而后只有到达它预约的目标后才被解密。证书和密码学算法支持了这一切的运转,使用 OpenSSL,您将有机会切身体会它们。
理论上,若是加密的数据在到达目标以前被截取或窃听,那些数据是不可能被破解的。不过,因为计算机的变化一年比一年快,并且密码翻译方法有了新的发展,所以,SSL 中使用的加密协议被破解的可能性也在增大。
能够将 SSL 和安全链接用于 Internet 上任何类型的协议,无论是 HTTP、POP3,仍是 FTP。还能够用 SSL 来保护 Telnet 会话。虽然能够用 SSL 保护任何链接,可是没必要对每一类链接都使用 SSL。若是链接传输敏感信息,则应使用 SSL。
OpenSSL 不只仅是 SSL。它能够实现消息摘要、文件的加密和解密、数字证书、数字签名和随机数字。关于 OpenSSL 库的内容很是多,远不是一篇文章能够容纳的。
OpenSSL 不仅是 API,它仍是一个命令行工具。命令行工具能够完成与 API 一样的工做,并且更进一步,能够测试 SSL 服务器和客户机。它还让开发人员对 OpenSSL 的能力有一个认识。要得到关于如何使用 OpenSSL 命令行工具的资料,请参阅 参考资料部分。
首先须要的是最新版本的 OpenSSL。查阅参考资料部分,以肯定从哪里能够得到最新的能够本身编译的源代码,或者最新版本的二进制文件(若是您不但愿花费时间来编译的话)。不过,为了安全起见,我建议您下载最新的源代码并本身编译它。二进制版本一般是由第三方而不是由 OpenSSL 的开发人员来编译和发行的。
一些 Linux 的发行版本附带了 OpenSSL 的二进制版本,对于学习如何使用 OpenSSL 库来讲,这足够了;不过,若是您打算去作一些实际的事情,那么必定要获得最新的版本,并保持该版本一直是最新的。
对于以 RPM 形式安装的 Linux 发行版本(Red Hat、Mandrake 等),建议您经过从发行版本制造商那里得到 RPM 程序包来更新您的 OpenSSL 发行版本。出于安全方面的缘由,建议您使用最新版本的发行版本。若是您的发行版本不能使用最新版本的 OpenSSL,那么建议您只覆盖库文件,不要覆盖可执行文件。OpenSSL 附带的 FAQ 文档中包含了有关这方面的细节。
还要注意的是,OpenSSL 并无在全部的平台上都得到官方支持。虽然制造商已经尽力使其可以跨平台兼容,但仍然存在 OpenSSL 不能用于您的计算机 和/或 操做系统的可能。请参阅 OpenSSL 的 Web 站点( 参考资料 中的连接),以得到关于哪些平台能够获得支持的信息。
若是想使用 OpenSSL 来生成证书请求和数字证书,那么必须建立一个配置文件。在 OpenSSL 程序包的 apps
文件夹中,有一个名为 openssl.cnf
的可用模板文件。我不会对该文件进行讨论,由于这不在本文要求范围以内。不过,该模板文件有一些很是好的注释,并且若是在 Internet 上搜索,您能够找到不少讨论修改该文件的教程。
本教程所使用的头文件只有三个:ssl.h、bio.h 和 err.h。它们都位于 openssl 子目录中,并且都是开发您的项目所必需的。要初始化 OpenSSL 库,只须要三个代码行便可。清单 1 中列出了全部内容。其余的头文件 和/或 初始化函数多是其余一些功能所必需的。
/* OpenSSL headers */ #include "openssl/bio.h" #include "openssl/ssl.h" #include "openssl/err.h" /* Initializing OpenSSL */ SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); |
无论链接是安全的仍是不安全的,OpenSSL 都使用了一个名为 BIO 的抽象库来处理包括文件和套接字在内的各类类型的通讯。您还能够将 OpenSSL 设置成为一个过滤器,好比用于 UU 或 Base64 编码的过滤器。
在这里对 BIO 库进行全面说明有点麻烦,因此我将根据须要一点一点地介绍它。首先,我将向您展现如何创建一个标准的套接字链接。相对于使用 BSD 套接字库,该操做须要的代码行更少一些。
在创建链接(不管安全与否)以前,要建立一个指向 BIO 对象的指针。这相似于在标准 C 中为文件流建立 FILE 指针。
BIO * bio; |
建立新的链接须要调用 BIO_new_connect
。您能够在同一个调用中同时指定主机名和端口号。也能够将其拆分为两个单独的调用:一个是建立链接并设置主机名的 BIO_new_connect
调用,另外一个是设置端口号的 BIO_set_conn_port
(或者 BIO_set_conn_int_port
)调用。
无论怎样,一旦 BIO 的主机名和端口号都已指定,该指针会尝试打开链接。没有什么能够影响它。若是建立 BIO 对象时遇到问题,指针将会是 NULL。为了确保链接成功,必须执行 BIO_do_connect
调用。
bio = BIO_new_connect("hostname:port"); if(bio == NULL) { /* Handle the failure */ } if(BIO_do_connect(bio) <= 0) { /* Handle failed connection */ } |
在这里,第一行代码使用指定的主机名和端口建立了一个新的 BIO 对象,并以所示风格对该对象进行 格式化。例如,若是您要链接到 www.ibm.com 的 80 端口,那么该字符串将是 www.ibm.com:80
。调用 BIO_do_connect
检查链接是否成功。若是出错,则返回 0 或 -1。
无论 BIO 对象是套接字仍是文件,对其进行的读和写操做都是经过如下两个函数来完成的: BIO_read
和 BIO_write
。很简单,对吧?精彩之处就在于它始终如此。
BIO_read
将尝试从服务器读取必定数目的字节。它返回读取的字节数、 0 或者 -1。在受阻塞的链接中,该函数返回 0,表示链接已经关闭,而 -1 则表示链接出现错误。在非阻塞链接的状况下,返回 0 表示没有能够得到的数据,返回 -1 表示链接出错。能够调用 BIO_should_retry
来肯定是否可能重复出现该错误。
int x = BIO_read(bio, buf, len); if(x == 0) { /* Handle closed connection */ } else if(x < 0) { if(! BIO_should_retry(bio)) { /* Handle failed read here */ } /* Do something to handle the retry */ } |
BIO_write
会试着将字节写入套接字。它将返回实际写入的字节数、0 或者 -1。同 BIO_read
,0 或 -1 不必定表示错误。 BIO_should_retry
是找出问题的途径。若是须要重试写操做,它必须使用和前一次彻底相同的参数。
if(BIO_write(bio, buf, len) <= 0) { if(! BIO_should_retry(bio)) { /* Handle failed write here */ } /* Do something to handle the retry */ } |
关闭链接也很简单。您可使用如下两种方式之一来关闭链接: BIO_reset
或 BIO_free_all
。若是您还须要从新使用对象,那么请使用第一种方式。若是您再也不从新使用它,则可使用第二种方式。
BIO_reset
关闭链接并从新设置 BIO 对象的内部状态,以即可以从新使用链接。若是要在整个应用程序中使用同一对象,好比使用一台安全的聊天客户机,那么这样作是有益的。该函数没有返回值。
BIO_free_all
所作正如其所言:它释放内部结构体,并释放全部相关联的内存,其中包括关闭相关联的套接字。若是将 BIO 嵌入于一个类中,那么应该在类的析构函数中使用这个调用。
/* To reuse the connection, use this line */ BIO_reset(bio); /* To free it from memory, use this line */ BIO_free_all(bio); |
如今须要给出创建安全链接须要作哪些事情。唯一要改变的地方就是创建并进行链接。其余全部内容都是相同的。
安全链接要求在链接创建后进行握手。在握手过程当中,服务器向客户机发送一个证书,而后,客户机根据一组可信任证书来核实该证书。它还将检查证书,以确保它没有过时。要检验证书是可信任的,须要在链接创建以前提早加载一个可信任证书库。
只有在服务器发出请求时,客户机才会向服务器发送一个证书。该过程叫作客户机认证。使用证书,在客户机和服务器之间传递密码参数,以创建安全链接。尽管握手是在创建链接以后才进行的,可是客户机或服务器能够在任什么时候刻请求进行一次新的握手。
参考资料 部分中列出的 Netscasp 文章和 RFC 2246 ,对握手以及创建安全链接的其余方面的知识进行了更详尽的论述。
为安全链接进行设置要多几行代码。同时须要有另外一个类型为 SSL_CTX 的指针。该结构保存了一些 SSL 信息。您也能够利用它经过 BIO 库创建 SSL 链接。能够经过使用 SSL 方法函数调用 SSL_CTX_new 来建立这个结构,该方法函数一般是 SSLv23_client_method
。
还须要另外一个 SSL 类型的指针来保持 SSL 链接结构(这是短期就能完成的一些链接所必需的)。之后还能够用该 SSL 指针来检查链接信息或设置其余 SSL 参数。
SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method()); SSL * ssl; |
在建立上下文结构以后,必须加载一个可信任证书库。这是成功验证每一个证书所必需的。若是不能确认证书是可信任的,那么 OpenSSL 会将证书标记为无效(但链接仍能够继续)。
OpenSSL 附带了一组可信任证书。它们位于源文件树的 certs
目录中。不过,每一个证书都是一个独立的文件 —— 也就是说,须要单独加载每个证书。在 certs
目录下,还有一个存放过时证书的子目录。试图加载这些证书将会出错。
若是您愿意,能够分别加载每个文件,但为了简便起见,最新的 OpenSSL 发行版本的可信任证书一般存放在源代码档案文件中,这些档案文件位于名为“TrustStore.pem”的单个文件中。若是已经有了一个可信任证书库,并打算将它用于特定的项目中,那么只需使用您的文件替换清单 8 中的“TrustStore.pem”(或者使用单独的函数调用将它们所有加载)便可。
能够调用 SSL_CTX_load_verify_locations
来加载可信任证书库文件。这里要用到三个参数:上下文指针、可信任库文件的路径和文件名,以及证书所在目录的路径。必须指定可信任库文件或证书的目录。若是指定成功,则返回 1,若是遇到问题,则返回 0。
if(! SSL_CTX_load_verify_locations(ctx, "/path/to/TrustStore.pem", NULL)) { /* Handle failed load here */ } |
若是打算使用目录存储可信任库,那么必需要以特定的方式命名文件。OpenSSL 文档清楚地说明了应该如何去作,不过,OpenSSL 附带了一个名为 c_rehash
的工具,它能够将文件夹配置为可用于 SSL_CTX_load_verify_locations
的路径参数。
/* Use this at the command line */ c_rehash /path/to/certfolder /* then call this from within the application */ if(! SSL_CTX_load_verify_locations(ctx, NULL, "/path/to/certfolder")) { /* Handle error here */ } |
为了指定全部须要的验证证书,您能够根据须要命名任意数量的单独文件或文件夹。您还能够同时指定文件和文件夹。
将指向 SSL 上下文的指针做为唯一参数,使用 BIO_new_ssl_connect
建立 BIO 对象。还须要得到指向 SSL 结构的指针。在本文中,只将该指针用于 SSL_set_mode
函数。而这个函数是用来设置 SSL_MODE_AUTO_RETRY 标记的。使用这个选项进行设置,若是服务器忽然但愿进行一次新的握手,那么 OpenSSL 能够在后台处理它。若是没有这个选项,当服务器但愿进行一次新的握手时,进行读或写操做都将返回一个错误,同时还会在该过程当中设置 retry 标记。
bio = BIO_new_ssl_connect(ctx); BIO_get_ssl(bio, & ssl); SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); |
设置 SSL 上下文结构以后,就能够建立链接了。主机名是使用 BIO_set_conn_hostname
函数设置的。主机名和端口的指定格式与前面的相同。该函数还能够打开到主机的链接。为了确认已经成功打开链接,必须执行对 BIO_do_connect
的调用。该调用还将执行握手来创建安全链接。
/* Attempt to connect */ BIO_set_conn_hostname(bio, "hostname:port"); /* Verify the connection opened and perform the handshake */ if(BIO_do_connect(bio) <= 0) { /* Handle failed connection */ } |
链接创建后,必须检查证书,以肯定它是否有效。实际上,OpenSSL 为咱们完成了这项任务。若是证书有致命的问题(例如,哈希值无效),那么将没法创建链接。可是,若是证书的问题并非致命的(当它已通过期或者尚不合法时),那么仍能够继续使用链接。
能够将 SSL 结构做为唯一参数,调用 SSL_get_verify_result
来查明证书是否经过了 OpenSSL 的检验。若是证书经过了包括信任检查在内的 OpenSSL 的内部检查,则返回 X509_V_OK。若是有地方出了问题,则返回一个错误代码,该代码被记录在命令行工具的 verify
选项下。
应该注意的是,验证失败并不意味着链接不能使用。是否应该使用链接取决于验证结果和安全方面的考虑。例如,失败的信任验证可能只是意味着没有可信任的证书。链接仍然可用,只是须要从思想上提升安全意识。
if(SSL_get_verify_result(ssl) != X509_V_OK) { /* Handle the failed verification */ } |
这就是所须要的所有操做。一般,与服务器进行通讯都要使用 BIO_read
和 BIO_write
。而且只需调用 BIO_free_all
或 BIO_reset
,就能够关闭链接,具体调用哪个方法取决因而否重用 BIO。
必须在结束应用程序以前的某个时刻释放 SSL 上下文结构。能够调用 SSL_CTX_free
来释放该结构。
SSL_CTX_free(ctx); |
显然 OpenSSL 抛出了某种类型的错误。这意味着什么?首先,您须要获得错误代码自己; ERR_get_error
能够完成这项任务;而后,须要将错误代码转换为错误字符串,它是一个指向由 SSL_load_error_strings
或 ERR_load_BIO_strings
加载到内存中的永久字符串的指针。能够在一个嵌套调用中完成这项操做。
表 1 略述了从错误栈检索错误的方法。清单 24 展现了如何打印文本字符串中的最后一个错误信息。
ERR_reason_error_string |
返回一个静态字符串的指针,而后能够将字符串显示在屏幕上、写入文件,或者以任何您但愿的方式进行处理 |
ERR_lib_error_string |
指出错误发生在哪一个库中 |
ERR_func_error_string |
返回致使错误的 OpenSSL 函数 |
printf("Error: %s\n", ERR_reason_error_string(ERR_get_error())); |
您还可让库给出预先格式化了的错误字符串。能够调用 ERR_error_string
来获得该字符串。该函数将错误代码和一个预分配的缓冲区做为参数。而这个缓冲区必须是 256 字节长。若是参数为 NULL,则 OpenSSL 会将字符串写入到一个长度为 256 字节的静态缓冲区中,并返回指向该缓冲区的指针。不然,它将返回您给出的指针。若是您选择的是静态缓冲区选项,那么在下一次调用 ERR_error_string
时,该缓冲区会被覆盖。
printf("%s\n", ERR_error_string(ERR_get_error(), NULL)); |
您还能够将整个错误队列转储到文件或 BIO 中。能够经过 ERR_print_errors
或 ERR_print_errors_fp
来实现这项操做。队列是以可读格式被转储的。第一个函数将队列发送到 BIO
,第二个函数将队列发送到 FILE
。字符串格式以下(引自 OpenSSL 文档):
[pid]:error:[error code]:[library name]:[function name]:[reason string]:[file name]:[line]:[optional text message]
其中, [pid]
是进程 ID, [error code]
是一个 8 位十六进制代码, [file name]
是 OpenSSL 库中的源代码文件, [line]
是源文件中的行号。
ERR_print_errors_fp(FILE *); ERR_print_errors(BIO *); |
使用 OpenSSL 建立基本的链接并不困难,可是,当试着肯定该如何去作时,文档多是一个小障碍。本文向您介绍了一些基本概念,但 OpenSSL 还有不少灵活之处有待发掘,并且您还可能须要一些高级设置,以便项目可以充分利用 SSL 的功能。
本文中有两个样例。一个样例展现了到 http://www.verisign.com/ 的非安全链接,另外一个则展现了到 http://www.verisign.com/ 的安全 SSL 链接。二者都是链接到服务器并下载其主页。它们没有进行任何安全检查,并且库中的全部设置都是默认值 —— 做为本文的一部分,应该只将这些用于教学目的。
在任何支持的平台上,源代码的编译都应该是很是容易的,不过我建议您使用最新版本的 OpenSSL。在撰写本文时,OpenSSL 的最新版本是 0.9.7d。