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