什么是 OpenSSL BIO?
OpenSSL BIO 是一个提供输入/输出相关功能的 API。首字母缩略词 BIO 代表Basic Input/Output
它的总体思路是什么?它与 stdio API 或 sockets API 有何不同?
第一个想法是它不是用于某些特定类型 IO(例如,用于文件或网络)的 API。它是能够进行输入/输出操作的各种类型实体的通用 API。它类似于具有纯虚函数的 C++ 抽象类。您只使用一个接口,但行为会根据使用的特定 BIO 对象而有所不同。例如,它可以是套接字对象或文件对象。如果您将BIO_write 函数与套接字对象一起使用,则数据将通过网络发送。如果您将BIO_write 函数与文件对象一起使用,则数据将写入文件。
OpenSSL BIO API 背后的第二个想法是 BIO 对象可以堆叠在一起形成一个单一的线性链。它允许在将数据发送到最终输出(接收器)或从初始输入(源)读取数据之后通过不同的过滤器处理数据。过滤器也是 BIO 对象。
什么是过滤器 BIO?什么是源 BIO?什么是水槽 BIO?
OpenSSL 过滤器 BIO 是一种获取数据、处理数据并将其传递给另一个 BIO 的 BIO。
OpenSSL 源 BIO 是不从另一个 BIO 获取数据但从其他地方(从文件、网络等)获取数据的 BIO。
OpenSSL sink BIO 是一种 BIO,它不会将数据传递给另一个 BIO,而是将其传输到其他地方(到文件、网络等)。
关于source和sink BIO,没有专门的source BIO,也没有专门的sink BIO,只有“source-sink”BIO。作为源 BIO 的 BIO 也是接收器 BIO。例如,一个套接字 BIO 同时是一个源 BIO 和一个接收器 BIO。当数据写入套接字 BIO 时,BIO 充当接收器。当从套接字 BIO 读取数据时,BIO 工作于一个源。 Source-sink BIO 始终是 BIO 链的终止部分。这与通常的数据处理管道不同,其中源是管道的起点,而接收器是管道的终点。
如何通过过滤器 BIO 运行数据?我是否必须使用 BIO_write 函数将数据提供给过滤器 BIO 并使用 BIO_read 函数获取处理后的数据?
如果您使用 BIO_write 函数将数据放入过滤器,则无法通过简单地调用 BIO 上的 BIO_read 函数来获取处理后的数据。过滤器 BIO 以不同的方式工作。过滤器 BIO 可以避免将处理后的数据存储在缓冲区中。它可能只获取输入数据,对其进行处理并立即使用与将数据放入 BIO 相同的 BIO_write 函数将其传递给链中的下一个 BIO。下一个 BIO 继而可以在处理之后将数据写入链中的下一个 BIO。如果某个 BIO 将数据存储在其内部缓冲区中(如果它没有足够的数据为下一个 BIO 生成输出)或数据到达接收器,则该过程停止。
如果您只需要通过过滤器 BIO 运行数据而不通过网络发送数据或将其写入文件,您可以将过滤器 BIO 附加到 OpenSSL 内存 BIO(即创建以下链:filter bio <-> memory bio)。内存 BIO 是 source-sink BIO,但它不会将数据发送到任何地方,它只是将数据存储在内存缓冲区中。将数据写入过滤器 BIO 后,数据将写入内存 BIO,内存 BIO 将其存储在内存缓冲区中。内存 BIO 具有直接从缓冲区获取数据的特殊接口(尽管您可以使用 BIO_read 来获取写入内存 BIO 的数据,见下文)。
从过滤器 BIO 中读取以相反的方式工作。如果您请求从过滤器 BIO 读取数据,则过滤器 BIO 可能反过来请求从链中的下一个 BIO 读取数据。如果某个 BIO 有足够的缓冲数据可以返回,或者该进程到达源 BIO,则该进程停止。对过滤器 BIO 上的 BIO_read 函数的一次调用可能会导致对过滤器 BIO 内的 BIO_read 函数的多次调用,以从下一个 BIO 获取数据。过滤器 BIO 将继续调用BIO_read,直到它获得足够的数据来生成处理结果。
如果链的 source-sink BIO 工作在非阻塞模式,情况会更加复杂。例如,使用非阻塞套接字或使用内存 BIO(内存 BIO 本质上是非阻塞的)。
还请注意,与写入该 BIO 时所做的处理相比,从过滤器 BIO 中读取数据会进行相反的数据处理。例如,如果您使用密码 BIO,则写入 BIO 将加密写入的数据,但从该 BIO 读取将解密输入数据。这允许制作这样的链:your code <-> cipher BIO <-> socket BIO。您将未加密的数据写入密码 BIO,该密码 BIO 对其进行加密并将其发送到套接字。当您从密码 BIO 中读取时,它首先从套接字获取加密数据,然后对其进行解密并将未加密的数据返回给您。这允许您通过网络设置加密通道。您只需使用BIO_write 和BIO_read,所有加密/解密均由 BIO 链自动完成。
一般来说,BIO 链如下图所示:
/------\ /--------\ /---------\ /-------------\
| your | -- BIO_write -> | filter | -- BIO_write -> | another | -- BIO_write -> | source/sink |
| | | | | filter | | |
| code | <- BIO_read -- | BIO | <- BIO_read -- | BIO | <- BIO_read -- | BIO |
\------/ \--------/ \---------/ \-------------/
为什么在 OpenSSL 中需要 BIO?使用 OpenSSL 编程时如何使用它们?有什么例子吗?
OpenSSL 在运行 SSL/TLS 协议时使用 BIO 与远程端进行通信。 SSL_set_bio 函数用于设置 BIO,以便在 SSL/TLS 链接的具体实例中进行通信。例如,您可以使用套接字 BIO 通过网络连接运行 SSL/TLS 协议。但您也可以开发自己的 BIO(是的,有可能)或使用内存 BIO 通过您自己的链接类型运行 SSL/TLS 协议。
您还可以将 SSL/TLS 链接的实例包装为 BIO 本身 (BIO_f_ssl)。在 SSL BIO 上调用 BIO_write 将导致调用 SSL_write。调用BIO_read 将导致调用SSL_read。
虽然 SSL BIO 是一个过滤器 BIO,但它与其他过滤器 BIO 有点不同。在 SSL BIO 上调用 BIO_write 可能会导致在链中的下一个 BIO 上同时调用 BIO_read 和 BIO_write。因为SSL_write(在 SSL BIO 的BIO_write 内部使用)不仅发送数据,还提供运行 SSL/TLS 协议,这可能需要双方之间的多个数据交换步骤来执行一些协商。 SSL BIO 的BIO_read 也是如此。这就是 SSL BIO 与普通过滤器 BIO 的不同之处。
另外请注意,您不需要使用 SSL BIO。您仍然可以直接使用SSL_read 和SSL_write。
OpenSSL 提供哪些 BIO?您能否提供 BIO 的示例并说明它们之间的区别?
以下是 OpenSSL 提供的 source-sink BIO 示例:
- 文件 BIO (
BIO_s_file)。它是 stdio 的 FILE* 对象的包装器。它用于写入和读取文件。
- 文件描述符 BIO (
BIO_s_fd)。它类似于文件 BIO,但适用于 POSIX 文件描述符而不是 stdio 文件。
- 一个套接字 BIO (
BIO_s_socket)。它是 POSIX 套接字的包装器。它用于通过网络进行通信。
- 空 BIO (
BIO_s_null)。它类似于 POSIX 系统中的/dev/null 设备。写入此 BIO 只会丢弃数据,从中读取会导致 EOF(文件结尾)。
- 内存 BIO (
BIO_s_mem )。它本质上是一个loopback BIO。从这种类型的 BIO 中读取会返回之前写入 BIO 的数据。但也可以通过调用特定于此类 BIO 的函数从内部缓冲区中提取(或放置到)数据(每种类型的 BIO 都具有仅针对此类 BIO 的函数)。
- “生物”BIO (
BIO_s_bio)。它是一个类似管道的 BIO。可以创建一对这样的 BIO。写入该对中的一个 BIO 的数据将被放置以读取该对中的第二个 BIO。反之亦然。它类似于内存 BIO,但内存 BIO 将数据放置到自身,管道 BIO 将数据放置到与之配对的 BIO。
有关BIO_s_mem 和BIO_s_bio 之间相似性的一些信息可以在这里找到:OpenSSL “BIO_s_mem” VS “BIO_s_bio”。
这里是过滤器 BIO 的示例:
- base64 BIO (
BIO_f_base64)。 BIO_write 通过这个 BIO 将数据编码为 base64 格式。 BIO_read 通过这个 BIO 解码 base64 格式的数据。
- 密码 BIO (
BIO_f_cipher)。它加密/解密通过它的数据。可以使用不同的加密算法。
- 摘要计算 BIO (
BIO_f_md)。它不会修改通过它传递的数据。它只计算流经它的数据的摘要,而数据本身保持不变。可以使用不同的摘要计算算法。可以使用特殊函数检索计算得出的摘要。
- 缓冲 BIO (
BIO_f_buffer)。它也不会更改通过它的数据。写入此 BIO 的数据会被缓冲,因此并非此 BIO 的每个写入操作都会导致将数据写入下一个 BIO。至于阅读,也是类似的情况。这可以减少位于缓冲 IO 后面的 BIO 上的 IO 操作数量。
- 一个 SSL BIO (
BIO_f_ssl)。上面描述了这种类型的 BIO。它将 SSL 链接包装在里面。