OpenSSL 简史
Netscape 在 1995 年发布的一种加密协议。该协议层可以位于 HTTP 之上,从而为 HTTPS 提供了 S:安全。SSL 协议提供了各种安全服务,其中包括两项在 HTTPS 中至关重要的服务:
- 对等身份验证(也称为相互质询):连接的每一边都对另一边的身份进行身份验证。如果 Alice 和 Bob 要通过 SSL 交换消息,则每个人首先验证彼此的身份。
- 机密性:发送者在通过通道发送消息之前先对其进行加密。然后,接收者解密每个接收到的消息。此过程可保护网络对话。即使窃听者 Eve 截获了从 Alice 到 Bob 的加密消息(即中间人攻击),Eve 会发现他无法在计算上解密此消息。
反过来,这两个关键 SSL 服务与其他不太受关注的服务相关联。例如,SSL 支持消息完整性,从而确保接收到的消息与发送的消息相同。此功能是通过哈希函数实现的,哈希函数也随 OpenSSL 工具箱一起提供。
SSL 有多个版本(例如 SSLv2 和 SSLv3),并且在 1999 年出现了一个基于 SSLv3 的类似协议传输层安全性(TLS)。TLSv1 和 SSLv3 相似,但不足以相互配合工作。不过,通常将 SSL/TLS 称为同一协议。例如,即使正在使用的是 TLS(而非 SSL),OpenSSL 函数也经常在名称中包含 SSL。此外,调用 OpenSSL 命令行实用程序以 openssl
开始。
除了 man 页面之外,OpenSSL 的文档是零零散散的,鉴于 OpenSSL 工具包很大,这些页面很难以查找使用。命令行和代码示例可以将主要主题集中起来。让我们从一个熟悉的示例开始(使用 HTTPS 访问网站),然后使用该示例来选出我们感兴趣的加密部分进行讲述。
一个 HTTPS 客户端
此处显示的 client
程序通过 HTTPS 连接到 Google:
-
/* compilation: gcc -o client client.c -lssl -lcrypto */
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <openssl/bio.h> /* BasicInput/Output streams */
-
#include <openssl/err.h> /* errors */
-
#include <openssl/ssl.h> /* core library */
-
#define BuffSize 1024
-
void report_and_exit(const char* msg) {
-
perror(msg);
-
ERR_print_errors_fp(stderr);
-
exit(-1);
-
}
-
void init_ssl() {
-
SSL_load_error_strings();
-
SSL_library_init();
-
}
-
void cleanup(SSL_CTX* ctx, BIO* bio) {
-
SSL_CTX_free(ctx);
-
BIO_free_all(bio);
-
}
-
void secure_connect(const char* hostname) {
-
char name[BuffSize];
-
char request[BuffSize];
-
char response[BuffSize];
-
const SSL_METHOD* method = TLSv1_2_client_method();
-
if (NULL == method) report_and_exit("TLSv1_2_client_method...");
-
SSL_CTX* ctx = SSL_CTX_new(method);
-
if (NULL == ctx) report_and_exit("SSL_CTX_new...");
-
BIO* bio = BIO_new_ssl_connect(ctx);
-
if (NULL == bio) report_and_exit("BIO_new_ssl_connect...");
-
SSL* ssl = NULL;
-
/* 链路 bio 通道,SSL 会话和服务器端点 */
-
sprintf(name, "%s:%s", hostname, "https");
-
BIO_get_ssl(bio, &ssl); /* 会话 */
-
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* 鲁棒性 */
-
BIO_set_conn_hostname(bio, name); /* 准备连接 */
-
/* 尝试连接 */
-
if (BIO_do_connect(bio) <= 0) {
-
cleanup(ctx, bio);
-
report_and_exit("BIO_do_connect...");
-
}
-
/* 验证信任库,检查证书 */
-
if (!SSL_CTX_load_verify_locations(ctx,
-
"/etc/ssl/certs/ca-certificates.crt", /* 信任库 */
-
"/etc/ssl/certs/")) /* 其它信任库 */
-
report_and_exit("SSL_CTX_load_verify_locations...");
-
long verify_flag = SSL_get_verify_result(ssl);
-
if (verify_flag != X509_V_OK)
-
fprintf(stderr,
-
"##### Certificate verification error (%i) but continuing...\n",
-
(int) verify_flag);
-
/* 获取主页作为示例数据 */
-
sprintf(request,
-
"GET / HTTP/1.1\x0D\x0AHost: %s\x0D\x0A\x43onnection: Close\x0D\x0A\x0D\x0A",
-
hostname);
-
BIO_puts(bio, request);
-
/* 从服务器读取 HTTP 响应并打印到输出 */
-
while (1) {
-
memset(response, '\0', sizeof(response));
-
int n = BIO_read(bio, response, BuffSize);
-
if (n <= 0) break; /* 0 代表流结束,< 0 代表有错误 */
-
puts(response);
-
}
-
cleanup(ctx, bio);
-
}
-
int main() {
-
init_ssl();
-
const char* hostname = "www.google.com:443";
-
fprintf(stderr, "Trying an HTTPS connection to %s...\n", hostname);
-
secure_connect(hostname);
-
return 0;
-
}
可以从命令行编译和执行该程序(请注意 -lssl
和 -lcrypto
中的小写字母 L
):
-
gcc -o client client.c -lssl -lcrypto
该程序尝试打开与网站 的安全连接。在与 Google Web 服务器的 TLS 握手过程中,client
程序会收到一个或多个数字证书,该程序会尝试对其进行验证(但在我的系统上失败了)。尽管如此,client
程序仍继续通过安全通道获取 Google 主页。该程序取决于前面提到的安全工件,尽管在上述代码中只着重突出了数字证书。但其它工件仍在幕后发挥作用,稍后将对它们进行详细说明。
通常,打开 HTTP(非安全)通道的 C 或 C++ 的客户端程序将使用诸如文件描述符或网络套接字之类的结构,它们是两个进程(例如,这个 client
程序和 Google Web 服务器)之间连接的端点。另一方面,文件描述符是一个非负整数值,用于在程序中标识该程序打开的任何文件类的结构。这样的程序还将使用一种结构来指定有关 Web 服务器地址的详细信息。
这些相对较低级别的结构不会出现在客户端程序中,因为 OpenSSL 库会将套接字基础设施和地址规范等封装在更高层面的安全结构中。其结果是一个简单的 API。下面首先看一下 client
程序示例中的安全性详细信息。