ossl-guide-tls-introduction
名称
ossl-guide-tls-introduction - OpenSSL 指南:OpenSSL 中 SSL/TLS 的简介
简介
此页面将介绍一些基本的 SSL/TLS 概念和背景,以及如何在 OpenSSL 中使用它。假设您已经了解 TCP/IP 和套接字的基本知识。
什么是 TLS?
TLS 代表传输层安全。TLS 允许应用程序通过网络安全地相互通信,从而保护交换信息的机密性(即防止窃听者监听通信)。此外,它还保护交换信息的完整性,以防止攻击者更改信息。最后,它提供身份验证,以便一方或双方可以确保他们正在与他们认为正在交谈的人交谈,而不是冒充者。
有时 TLS 被称为其前身 SSL(安全套接字层)的名称。OpenSSL 诞生于 SSL 名称仍在普遍使用的时期,因此 OpenSSL 使用的许多函数和名称都包含“SSL”缩写。尽管如此,OpenSSL 仍然包含一个功能齐全的 TLS 实现。
TLS 基于客户端/服务器模型。发起通信的应用程序称为客户端。响应远程发起的通信的应用程序称为服务器。术语“端点”是指通信中的客户端或服务器中的任何一个。术语“对等方”是指我们当前正在引用的通信另一端的端点。因此,如果我们目前正在谈论客户端,则对等方将是服务器。
TLS 是一种标准化协议,并且有许多不同的实现。由于标准,OpenSSL 客户端或服务器能够与使用某些不同 TLS 实现的应用程序无缝通信。TLS(及其前身 SSL)已经存在相当长的一段时间,并且该协议多年来经历了各种变化。因此,有不同的协议版本可用。TLS 包括执行版本协商的功能,以便使用客户端和服务器共有的最高协议版本。
TLS 充当某些较低层传输协议的安全层。通常,传输层将是 TCP。
SSL 和 TLS 版本
SSL 最初由 Netscape Communications 开发,其第一个公开发布的版本是 1995 年的 SSLv2。请注意,SSLv1 从未公开发布。SSLv3 很快之后于 1996 年问世。随后,协议的开发转移到 IETF,IETF 于 1999 年发布了第一个 TLS 版本(TLSv1.0)作为 RFC2246。TLSv1.1 于 2006 年作为 RFC4346 发布,TLSv1.2 于 2008 年作为 RFC5246 发布。最新版本的标准是 TLSv1.3,于 2018 年作为 RFC8446 发布。
如今,TLSv1.3 和 TLSv1.2 是最常用的协议版本。IETF 已正式弃用 TLSv1.1 和 TLSv1.0,因此应避免使用低于 TLSv1.2 的任何版本,因为较旧的协议版本容易受到安全问题的攻击。
OpenSSL 不支持 SSLv2(它在 OpenSSL 1.1.0 中被移除)。对 SSLv3 的支持作为编译时选项可用 - 但默认情况下未构建。对 TLSv1.0、TLSv1.1、TLSv1.2 和 TLSv1.3 的支持在 OpenSSL 的标准构建中默认都可用。但是,需要特殊的运行时配置才能使 TLSv1.0 和 TLSv1.1 成功工作。
OpenSSL 将始终尝试协商其已配置为支持的最高协议版本。在大多数情况下,这意味着将选择 TLSv1.3 或 TLSv1.2。
证书
为了使客户端能够与服务器建立连接,它必须验证该服务器的身份,即它需要确认服务器确实是它声称的服务器,而不是某个冒充者。为此,服务器将向客户端发送数字证书(也通常称为 X.509 证书)。证书包含有关服务器的各种信息,包括其完整的 DNS 主机名。此外,证书中还包含服务器的公钥。服务器操作员将拥有一个与公钥关联的私钥,并且不得发布该私钥。
除了证书外,服务器还将向客户端发送证明它知道与证书中的公钥关联的私钥的证据。它使用该私钥对发送到客户端的消息进行数字签名来做到这一点。客户端可以使用证书中的公钥验证签名。如果签名验证成功,则客户端知道服务器拥有正确的私钥。
服务器发送的证书也将由证书颁发机构签名。证书颁发机构(通常称为 CA)是一个第三方组织,负责验证服务器证书中的信息(包括其 DNS 主机名)。CA 仅在能够确认服务器操作员确实控制着与其 DNS 主机名关联的服务器,并且服务器操作员控制着私钥的情况下才会签名证书。
这样,如果客户端信任已签署服务器证书的 CA,并且它可以验证服务器是否拥有正确的私钥,则它可以信任服务器确实代表证书中提供的 DNS 主机名。客户端还必须验证证书中提供的 hostname 是否与它最初发送请求到的 hostname 匹配。
完成所有这些检查后,客户端已成功验证服务器的身份。OpenSSL 可以自动执行所有这些检查,但它必须提供某些信息才能这样做,即客户端信任的 CA 集以及此客户端尝试连接到的服务器的 DNS 主机名。
请注意,证书通常会构建成一个链。例如,服务器的证书可能由中间 CA 拥有的密钥签名。该中间 CA 还拥有一个包含其公钥的证书,该证书又由根 CA 拥有的密钥签名。客户端可能只信任根 CA,但如果服务器发送其自己的证书和中间 CA 的证书,则客户端仍然可以成功验证服务器的身份。在根 CA 和服务器之间存在信任链。
默认情况下,只有客户端使用此方法验证服务器。但是,也可以设置使服务器另外验证客户端。这称为“客户端身份验证”。在这种方法中,客户端仍将以相同的方式验证服务器,但服务器将向客户端请求证书。客户端向服务器发送其证书,服务器以与客户端相同的方式对其进行身份验证。
受信任的证书存储
只有在端点信任的 CA 集和对等方正在使用的证书之间可以建立信任链时,上述系统才能正常工作。因此,端点必须在进行任何通信之前拥有一组它信任的 CA 证书。OpenSSL 本身不提供此类证书集。因此,如果您要验证证书(即如果端点是客户端,则始终验证,并且仅当服务器使用客户端身份验证时才验证),则需要确保您在开始之前拥有它们。
幸运的是,其他组织确实维护了这样的证书集。如果您从操作系统(OS)供应商(例如 Linux 发行版)处获得了 OpenSSL 副本,则通常也会与该副本一起分发 CA 证书集。
您可以通过运行 OpenSSL 命令行应用程序来检查这一点,如下所示
openssl version -d
这将显示 OPENSSLDIR 的值。查看 OPENSSLDIR 的 certs 子目录并检查其内容。例如,如果 OPENSSLDIR 是“/usr/local/ssl”,则检查“/usr/local/ssl/certs”目录的内容。
您希望看到文件列表,通常以“.pem”或“.0”为后缀。如果它们存在,则您已经拥有合适的受信任证书存储。
如果您在 Windows 上运行 OpenSSL 版本,则 OpenSSL(从 3.2 版开始)将使用默认的 Windows 受信任 CA 集。
如果您从源代码构建了 OpenSSL 版本,或从其他位置获取了它,并且它没有一组受信任的 CA 证书,则您需要自己获取它们。其中一个来源是 Curl 项目。请参阅页面 https://curl.se/docs/caextract.html,您可以在其中下载单个文件中的受信任证书。将文件重命名为“cert.pem”并将其直接存储在 OPENSSLDIR 中。例如,如果 OPENSSLDIR 是“/usr/local/ssl”,则将其保存为“/usr/local/ssl/cert.pem”。
您还可以使用环境变量覆盖 OpenSSL 将查找其受信任证书存储的默认位置。设置 SSL_CERT_PATH 环境变量以提供 OpenSSL 应在其处查找证书的目录,或设置 SSL_CERT_FILE 环境变量以提供包含所有证书的单个文件的名称。有关 OpenSSL 环境变量的更多详细信息,请参阅 openssl-env(7)。例如,您可以使用此功能在同一系统上安装 OpenSSL 的多个版本,使用不同的 OPENSSLDIR 值,但都使用相同的受信任证书存储。
您可以通过使用 OpenSSL 命令行来测试您的受信任证书存储是否已正确设置。使用以下命令连接到 TLS 服务器
openssl s_client www.openssl.org:443
连接后,键入字母“Q”,然后按“<enter>”退出会话。这将在屏幕上打印大量有关连接的信息。查找如下所示的文本块
SSL handshake has read 4584 bytes and written 403 bytes
Verification: OK
如果一切正常,则“验证”行将显示“确定”。如果它没有按预期工作,则您可能会看到如下所示的输出
SSL handshake has read 4584 bytes and written 403 bytes
Verification error: unable to get local issuer certificate
“无法获取本地颁发者证书”错误意味着 OpenSSL 无法在其受信任证书存储中找到服务器提供的证书链的受信任 CA。再次检查您的受信任证书存储配置。
请注意,s_client 是一种测试工具,它仍然允许您连接到 TLS 服务器,而不管验证错误如何。大多数应用程序不应这样做,并且应在发生验证错误时中止连接。
OpenSSL TLS 应用程序的重要对象
TLS 连接由 OpenSSL 基于应用程序中的 SSL 对象表示。与远程对等方建立连接后,端点可以“写入”SSL 对象以将数据发送到对等方,或“读取”该对象以接收来自服务器的数据。
一个新的SSL对象是从SSL_CTX对象创建的。可以将SSL_CTX视为创建SSL对象的“工厂”。您可以创建一个SSL_CTX对象,然后从中创建多个连接(即SSL对象)。通常,您可以在SSL_CTX上设置常见的配置选项,以便从它创建的所有SSL对象继承相同的配置选项。
请注意,出于性能原因,OpenSSL 内部在SSL_CTX中缓存了多个SSL对象之间共享的各种项目。因此,建议为多个SSL对象创建一个SSL_CTX供其使用,而不是为每个创建的SSL对象创建一个SSL_CTX。
每个SSL对象也与两个BIO对象相关联。BIO对象用于从底层传输层发送或接收数据。例如,您可以创建一个BIO来表示 TCP 套接字。SSL对象使用一个BIO读取数据,另一个BIO写入数据。在大多数情况下,您会对每个方向使用相同的BIO,但在某些情况下,您可能希望它们不同。
应用程序程序员需要创建所需的BIO对象并将它们提供给SSL对象。有关更多信息,请参见ossl-guide-tls-client-block(7)。
最后,端点可以与其对等方建立“会话”。会话保存有关客户端和服务器之间连接的各种 TLS 参数。然后可以在后续连接尝试中重用会话详细信息以加快连接过程。这称为“恢复”。会话在 OpenSSL 中由SSL_SESSION对象表示。在 TLSv1.2 中,每个连接始终只有一个会话。在 TLSv1.3 中,每个连接可以有任意数量的会话,包括零个。
TLS 连接的阶段
TLS 连接从初始“设置”阶段开始。端点创建SSL_CTX(如果尚未创建)并对其进行配置。
然后客户端创建一个SSL对象来表示新的 TLS 连接。然后应用任何连接特定的配置参数,并创建底层套接字并通过BIO对象将其与SSL关联。
服务器将创建一个套接字以侦听来自客户端的传入连接尝试。一旦连接尝试建立,服务器将以与客户端相同的方式创建一个SSL对象,并将其与新创建的传入套接字的BIO关联。
设置完成后,TLS“握手”阶段开始。TLS 握手包括客户端和服务器交换一系列 TLS 握手消息以建立连接。客户端首先发送“ClientHello”握手消息,服务器以“ServerHello”进行响应。一旦端点发送了其最后一条消息(称为“Finished”消息)并收到了其对等方的 Finished 消息,握手就完成了。请注意,这可能在每个对等方的不同时间发生。例如,在 TLSv1.3 中,服务器始终在客户端之前发送其 Finished 消息。客户端稍后会回复其 Finished 消息。此时,客户端已完成握手,因为它已发送和接收了 Finished 消息。服务器已发送其 Finished 消息,但来自客户端的 Finished 消息可能仍在传输中,因此服务器仍处于握手阶段。即使客户端可能已经开始发送应用程序数据,服务器也可能无法完成握手(如果它认为客户端发送的消息存在某些问题)。在 TLSv1.2 中,这可能会以相反的方式发生,即服务器先完成,然后客户端完成。
握手完成后,应用程序数据传输阶段开始。严格来说,在某些情况下,客户端甚至可以更早地开始发送应用程序数据(使用 TLSv1.3 的“早期数据”功能)——但我们将在本基础介绍中跳过它。
在应用程序数据传输期间,客户端和服务器可以自由地读取和写入连接数据。这方面的详细信息通常留给某些更高级别的应用程序协议(例如 HTTP)。在此阶段交换的信息并非全部都是应用程序数据。仍然可能会交换一些协议级消息——因此,仅仅因为底层套接字“可读”,并不一定意味着应用程序数据就可以读取。
当不再需要连接时,应将其关闭。客户端或服务器可以通过称为“close_notify”警报的消息启动关闭。接收 close_notify 的客户端或服务器可能会回复一个,然后连接完全关闭,并且应用程序数据将无法再发送或接收。
关闭完成后,TLS 应用程序必须通过释放 SSL 对象来进行清理。
进一步阅读
请参阅ossl-guide-tls-client-block(7),以了解如何应用这些概念来编写基于阻塞套接字的简单 TLS 客户端。请参阅ossl-guide-quic-introduction(7),以了解 OpenSSL 中 QUIC 的简介。
另请参阅
ossl-guide-introduction(7),ossl-guide-libraries-introduction(7),ossl-guide-libssl-introduction(7),ossl-guide-tls-client-block(7),ossl-guide-quic-introduction(7)
版权
版权所有 2023 OpenSSL 项目作者。保留所有权利。
根据 Apache 许可证 2.0(“许可证”)许可。除非符合许可证,否则您不得使用此文件。您可以在源代码分发中的 LICENSE 文件或https://www.openssl.org/source/license.html中获取副本。