如果没有安全的服务器应用程序,那么也就不需要安全的客户机应用程序。使用 OpenSSL,我们可以创建安全的服务器应用程序,尽管文档让这一切看起来非常复杂,但实际上并非如此。本文中我们将学习怎么样使用在这个 3 部分系列文章 的 第 1 部分 中学习到的概念来构建安全的服务器应用程序。
本系列文章的前两部分讨论了使用 OpenSSL 来创建客户机端应用程序的内容。第 1 部分 讨论了使用 OpenSSL 创建基本安全客户机的问题,而 第 2 部分 则深入讨论了有关数字证书的问题。在阅读本文的读者给我发回很多 e-mail 和正面反馈之后,我非常清楚,接下来的一期理论介绍应该是有关服务器的。
服务器为网络和 Internet 提供了访问诸如文件和设备之类的资源的访问能力。有时我们必须要通过一个安全通道来提供这些服务。OpenSSL 让我们可以使用安全通道和开放通道来编写服务。
使用 OpenSSL 来创建基本的服务器应用程序从本质上来说几乎 等同于创建一个基本的客户机应用程序。二者之间的区别不多。显然,区别之一就是服务器将被设置为接收到达的连接,而不是建立外发的连接。并且,正如我们可以从本系列的第 2 部分有关数字证书的讨论中看到的一样,服务器还必须要在握手过程中提供安全证书。
等待
服务器基本上就是呆在那里等待到达的连接。毕竟,这就是服务器存在的原因。Web 服务器要等待浏览器请求页面,FTP 服务器要等待客户机请求文件,聊天服务器要等待聊天客户机所发出的连接。因此服务器要做的事情就是等待。
在客户机和服务器通信之间差别不大,惟一的差别就是对于握手来说,服务器就像是硬币的反面。其他东西都是相同的。
这让我们可以使用 OpenSSL 来编写安全的服务器应用程序,再次假设您已经了解怎么样使用 OpenSSL 来编写客户机应用程序。(如果您还不了解相关知识,请参阅本系列第 1 部分 “API 概述” 来学习怎么样设置 OpenSSL 库。)
两种形式的标识
 |
SSL 上下文
要设置本文中使用的 SSL 上下文,请使用下面的代码。这个函数在出错时会返回 NULL:
SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
| |
也可以说是标识的两部分。
服务器要负责提供在握手过程中使用的安全证书。完整的服务器证书包括两个部分:公钥和私钥。公钥是发送给客户机的,而私钥则是保密的。
就像是信任证书必须要提供给客户机应用程序使用的库一样,服务器密钥也必须要提供给服务器应用程序使用的库。有几个函数都提供了这种功能:
清单 1. 加载服务器证书的函数
SSL_CTX_use_certificate(SSL_CTX *, X509 *)
SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, int len, unsigned char *d);
SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
|
这个函数的 ASN1 变种可以将指定内存位置处的使用 ASN1 编码的数字证书加载到 SSL 环境中。这个函数会加载给定内存结构中所提供的一个 X.509 证书;而最后一个函数,即带有 _file 的那个,会从文件中加载一个使用 PEM 编码的数字证书。这个函数的 type 参数让我们可以加载使用 DER 编码的证书。
要加载私钥,请使用下面函数之一:
清单 2. 加载私钥使用的函数
SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey);
SSL_CTX_use_PrivateKey_ASN1(int pk, SSL_CTX *ctx, unsigned char *d, long len);
SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
SSL_CTX_use_RSAPrivateKey(SSL_CTX *ctx, RSA *rsa);
SSL_CTX_use_RSAPrivateKey_ASN1(SSL_CTX *ctx, unsigned char *d, long len);
SSL_CTX_use_RSAPrivateKey_file(SSL_CTX *ctx, const char *file, int type);
|
需要的交互
私钥最适合用来加密存储。不过,问题是加载证书的函数并没有请求使用加密证书的密码。相反,OpenSSL 为获得密码提供了一种回调机制。
回调格式如下:
清单 3. 回调格式
int password_callback(char *buf, int size, int rwflag, void *userdata);
|
就本文的目的来说,最后一个参数 userdata 是不需要的。缓冲区在调用这个函数之前被调用,因此对于这个缓冲区的大小我们无法控制。
|