第 3 章 - Azure RTOS NetX Secure 功能说明

执行概述

本章包含 Azure RTOS NetX Secure TLS 的功能说明。 NetX Secure TLS 应用程序中主要包含两种程序执行:初始化和应用程序接口调用。

NetX Secure 假定存在 ThreadX 和 NetX/NetXDuo。 在 ThreadX 中,NetX Secure 需要线程执行、挂起、定期计时器和互斥设施。 在 NetX/NetXDuo 中,NetX Secure 需要 TCP/IP 网络设施和驱动程序。

传输层安全性 (TLS)和安全套接字层 (SSL)

NetX Secure 的安全网络协议组件是传输层安全性 (TLS) 协议一种实现,如 RFC 2246(1.0 版)、4346(1.1 版)、5246(1.2 版)和 8446(1.3 版)中所述。 还包括用于基本 X.509 的支持例程 (RFC 5280)。

NetX Secure TLS 支持 TLS 1.2 和1.3 版。 为现已弃用的 TLS 1.0 和 TLS 1.1 提供了实现,但必须对其进行显式初始化,而且不建议在新产品中使用。

安全套接字层 (SSL) 是 TLS 成为 RFC 2246 标准文件之前的原始名称,而“SSL”通常用作 TLS 协议的通用名称。 最新 SSL 版本为 3.0,而 TLS 1.0 有时被称为 SSL 3.1。 所有“SSL”协议官方版本都被视为过时且不安全,且当前 NetX Secure 不提供 SSL 实现。

TLS 指定生成会话密钥的协议,系统将在 TLS 客户端与服务器之间进行 TLS 握手时创建这些密钥,系统可借此在 TLS 会话期间为应用程序发送的数据进行加密。

TLS 数据可被划分为记录,其与 TCP 数据包概念的意义相同。 每个 TLS 记录都有标头,TLS 加密记录还会有页脚(校验和哈希)。 TLS 握手记录在较大的 TLS 记录中封装了其他标头。 TLS 记录由传输层网络协议封装,具体方式与 IP 数据包封装 TCP 数据包的方式相同。

TLS 1.3

2018 年 8 月,TLS 1.3 规范定稿。 我们对新版协议进行了重大更新,其中更改了 TLS 底层安全和性能的一些基本因素。 但是,这些更改基本上对典型 TLS 用户不可见,因为这些更改主要用于 TLS 握手状态机和会话密钥生成。 我们还在其中添加了许多可选功能和扩展。 更改及其对 TLS 功能影响的汇总如下。

  • 已通过删除服务器的整个消息交换对握手状态机进行优化。
  • 已将密钥生成更新为使用名为“HKDF”(基于 HMAC 的密钥派生函数)的标准化例程,并将会话密钥与所有握手消息(而不是少数选定参数)绑定。
  • 所有 TLS 1.2 和更早版本的密码套件均已弃用,并且与 TLS 1.3 不兼容。 同样,所有 TLS 1.3 密码套件也无法在之前的版本中使用。
  • 所有 TLS 1.3 密码套件都支持使用临时密钥进行完全向前保密 (PFS)6
  • TLS 1.3 从每条记录中删除“消息验证码”(MAC),以支持使用 AEAD7 密码
  • 新增了一些其他可选功能,包括允许在握手期间发送应用程序数据的 0-RTT(零往返时间)。 0-RTT 纯粹是可选功能,目前在 Azure RTOS TLS 中不受支持。

TLS 1.3 不会显著影响用户应用程序。 不同版本之间的 API 也完全相同,并会标记密码套件,因此可以使用单个密码套件表。

要使用 TLS 1.3,必须从全局角度出发,定义宏 NX_SECURE_TLS_ENABLE_TLS_1_3。 默认情况下,Azure RTOS TLS 中会禁用 TLS 1.3。

  1. “临时”密钥是指非对称密钥对,将于 TLS 握手期间生成并仅可用于该会话的密钥交换。 这些密钥用后即弃,这样一来,样即使将来任何时候证书私钥遭到泄露,也可防止攻击者访问已记录 TLS 会话中的加密数据,从而实现“完全向前保密”。

  2. 关联数据的认证加密 – AES 之类的密码模式,可在一项操作中兼具加密性和完整性检查,无需单独的数据哈希来执行完整性检查。

TLS 记录标头

任何有效的 TLS 记录必须具有 TLS 标头,如“错误”中所示! 未找到引用源。

Diagram of a TLS record header.

图 1 - TLS 记录标头

TLS 记录标头的字段定义如下:

TLS 标头字段 目的
8 位消息类型 此字段包含发送的 TLS 记录类型。 有效文件类型如下:
- ChangeCipherSpec8:0x14
- Alert:0x15
- Handshake:0x16
- Application Data:0x17
16 位协议版本 此字段中包含 TLS 协议版本。 以下是有效值:
- SSL 3.0:0x0300
- TLS 1.0:0x0301
- TLS 1.1:0x0302
- TLS 1.2:0x0303
- TLS 1.390x0303
16 位长度 此字段包含 TLS 记录中封装的数据的长度。
  1. TLS 1.3 中将不再使用 ChangeCipherSpec 消息,但仍可能出于兼容性原因发送此消息,在这种情况下,系统将忽略该消息。

  2. 如果继续使用此方案,则 TLS 1.3 从技术上来说应具有 0x0304 的值,但该协议经过更改,扩展中具有实际协议版本,因此所有 TLS 1.3 记录在协议版本字段中都使用 0x0303 来实现向后兼容性。

TLS 握手记录标头

任何有效的 TLS 握手记录必须具有 TLS 握手标头,如图 2 所示。

Diagram of a TLS Handshake record header.

图 2 - TLS 握手记录标头

TLS 握手记录标头的字段定义如下:

TLS 标头字段 目的
8 位消息类型 此字段包含发送的 TLS 记录类型。 有效文件类型如下:
- ChangeCipherSpec10:0x14
- Alert:0x15
- Handshake:0x16
- Application Data:0x17
16 位协议版本 此字段中包含 TLS 协议版本。 以下是有效值:
- SSL 3.0:0x0300
- TLS 1.0:0x0301
- TLS 1.1:0x0302
- TLS 1.2:0x0303
- TLS 1.3110x0303
16 位长度 此字段包含 TLS 记录中封装的数据的长度。
8 位握手类型 此字段中包含握手消息类型。 以下是有效值(*粗体消息已添加到 TLS 1.3 中):
- HelloRequest:0x00
- ClientHello:0x01
- ServerHello:0x02
- HelloVerifyRequest0x03
- NewSessionTicket0x04
- EndOfEarlyData0x05
- EncryptedExtensions0x08
- Certificate:0x0B
- ServerKeyExchange:0x0C
- CertificateRequest:0x0D
- ServerHelloDone:0x0E
- CertificateVerify:0x0F
- ClientKeyExchange:0x10
- Finished:0x14
- KeyUpdate0x18
- MessageHash0xFE
24 位长度 此字段中含握手消息数据的长度。
  1. TLS 1.3 中将不再使用 ChangeCipherSpec 消息,但仍可能出于兼容性原因发送此消息,在这种情况下,系统将忽略该消息。

  2. 如果继续使用此方案,则 TLS 1.3 从技术上来说应具有 0x0304 的值,但该协议经过更改,扩展中具有实际协议版本,因此所有 TLS 1.3 记录在协议版本字段中都使用 0x0303 来实现向后兼容性。

TLS 握手和 TLS 会话

典型的 TLS 握手(版本 1.0-1.2),如图 3 所示。 TLS 客户端向 TLS 服务器发送 ClientHello 消息时开始 TLS 握手,表示需要启动 TLS 会话。 此消息中包含客户端要用于会话的加密相关信息,以及用于稍后在握手中生成会话密钥的信息。 在生成会话密钥之前,TLS 握手中的所有消息都不会加密。 TLS 1.3 对握手稍有更改 - 详情请见下一节。

TLS 服务器使用 ServerHello 消息响应 ClientHello,表示从客户端提供的加密选项中选择。 ServerHello 之后是 Certificate 消息,其中包含服务器提供的数字证书,用于向客户端验证身份。 最后,服务器会发送 ServerHelloDone 消息,表示不会再发送其他消息。 服务器也可选择在 ServerHello 之后发送其他消息,某些情况下可能不会发送 Certificate 消息,因此需要 ServerHelloDone 消息。

当客户端接收所有服务器的消息后,就有足够的信息来生成会话密钥。 TLS 通过创建名为“预主密钥”的随机数据共享位来实现此功能,该密钥大小固定,一旦启用加密,它将被用作种子来生成所有需要的密钥。 使用 Hello 消息中指定的公钥算法(例如,RSA)(请参阅下文了解公钥算法信息)以及服务器在其证书中提供的公钥,即可加密预主密钥。 名为“预共享密钥”(PSK) 的可选 TLS 功能会启用不使用证书,而使用主机之间共享密钥值的密码套件(通常通过物理传输或其他安全方法)。 使用共享秘钥生成预主密钥,而不是通过加密消息发送预主密钥。 请参阅以下有关预共享密钥的部分。

通过 ClientKeyExchange 消息将加密的预主密钥机密发送到服务器。 服务器收到 ClientKeyExchange 消息后会使用私钥解密预主密钥,并继续与 TLS 客户端并行生成会话密钥。

生成会话密钥后,即可使用 Hello 消息中选定的私钥算法(例如,AES)加密之后的所有消息。 客户端和服务器均会发送名为 ChangeCipherSpec 的最终非加密消息,表示将对所有后续消息进行加密。

客户端和服务器发送的第一条加密消息也是最终 TLS 握手消息,称为“Finished”。 此消息包含所有已接收和已发送握手消息的哈希。 此哈希值用于验证握手中的消息无篡改或损坏(表示可能存在安全漏洞)。

收到 Finished 消息并验证握手哈希后,TLS 会话开始,而且应用程序开始发送和接收数据。 在 TLS 会话期间,任何一端发送的所有数据都将首先使用 Hello 消息中所选的哈希算法进行哈希处理(保证消息完整性),然后使用具有已生成会话密钥的所选密钥算法进行加密。

最后,仅当客户端或服务器选择执行此操作时,TLS 会话才能成功结束。 截断会话被视为安全漏洞(由于攻击者可能试图阻止接收所有已发送数据),因此任何一方想要结束会话时都会发送名为 CloseNotify 警报的特殊通知。 客户端和服务器都必须发送和处理 CloseNotify 警报,才能成功关闭会话。

Diagram of a Typical TLS handshake.

图 3 - 典型 TLS 握手

TLS 1.3 握手

TLS 1.3 是对 TLS 协议的重要改进。 大部分改进在于握手部分,以便提高安全性和性能。 典型 TLS 1.3 握手如图 4 所示。 可以通过服务器和客户端之间的交换次数中看出主要区别。

在 TLS 1.2 和更早版本中,服务器将发送两组12 消息 – 首先依次发送 ServerHello 和 ChangeCipherSpec 消息,然后再发送已加密的 Finished 消息结束握手。 在 TLS 1.3 中,服务器会在第一组消息传送中发送所有内容 – ServerHello、Extensions、Certificate 和 Finished。 已清除 ChangeCipherSpec 消息,服务器会生成会话密钥,并在 ServerHello 之后立即开始加密握手消息。

这种新排列表示,TLS 握手更多通过加密来保护,这就限制了攻击者可访问的明文数据量。 此外,删除第二组服务器消息传送(是指 ChangeCipherSpec 后接 Finished 消息),则意味着 TLS 客户端无需再等待开始传输应用程序数据 – 客户端发送自己的 Finished 消息后就会立即启动会话。

  1. 一组消息传送 (flight) 就是指组内同时发送的 TLS 消息的集合。

Diagram of a TLS 1.3 Handshake.

图 4 - TLS 1.3 握手

注意

TLS 1.3 还引入了“早期数据”和 0-RTT(零往返时间)的概念,这意味着可以在第一组消息传送中发送一些应用程序数据。 添加此可选功能的主要目的是为了优化 Web 浏览器响应能力(例如,发送早期 HTTP 标头以开始呈现页面)。 从 Azure RTOS 6.0 版本开始不支持此功能。

初始化

必须对 NetX 或 NetXDuo TCP/IP 堆栈进行初始化后才能使用 NetX Secure TLS。 请参阅 NetX 或 NetXDuo 用户指南,获取有关如何正确初始化 TCP/IP 堆栈的信息。

初始化 NetX TCP/IP 堆栈后,即可启用 TLS。 在内部,所有 TLS 网络流量和处理都由 NetX/NetXDuo 堆栈处理,无需用户介入。 但是,TLS 有一些特定要求,因此必须与基础网络堆栈分开处理。 使用 nx_secure_tls_session_create 服务将这些参数分配给名为“NX_SECURE_TLS_SESSION”的控制块。

TLS 具有服务器和客户端两种模式,这两种模式都可以在应用程序中启用(但每个 NetX 套接字只能使用一种模式),而且每种模式都有自己的特定要求,具体如下所述。

在任一模式下,NetX Secure TLS 都需要创建 TCP 套接字 (NX_TCP_SOCKET) 并设置与远程主机的 TCP 通信。 通过 nx_secure_tls_session_start 服务将 TCP 套接字分配给 TLS 会话实例,详细信息如下。

初始化 – TLS 服务器

除 TCP 套接字外,NetX Secure TLS 服务器模式还需要一个数字证书(用于识别连接 TLS客户端的 TLS 服务器的文档)以及与私钥(通常用于 RSA 加密算法)对应的证书。 国际电信联盟 X.509 标准指定 TLS 使用的证书格式,还有许多用于创建 X.509 数字证书的实用程序。

对于 NetX Secure TLS,X.509 证书必须采用 ASN.1 的可辨别编码规则 (DER) 格式进行二进制编码。 DER 是证书的标准 TLS(通过网络传播)二进制格式。

与已提供证书关联的私钥必须采用 DER 编码的 PKCS#1 格式。 私钥仅在设备上使用,且绝对不能通过网络传播。 因为密钥可保障 TLS 通信安全,因此要保证密钥安全!

要初始化 TLS 服务器证书,应用程序必须使用 nx_secure_x509_certificate_intialize 服务提供指向缓冲区的指针,该缓冲区包含 DER 编码的 X.509 证书和可选 DER 编码的 PKCS#1 RSA 私钥数据,该服务使用合适的证书数据填充 NX_SECURE_X509_CERT 结构以供 TLS 使用。

初始化服务器证书后,必须使用 nx_secure_tls_local_certificate_add 服务将其添加到 TLS 控制块。

将服务器证书添加到 TLS 控制块后,即可使用套接字建立安全的 TLS 服务器连接。

初始化 – TLS 客户端

NetX Secure TLS 客户端模式需要受信任的证书存储,这是受信任证书颁发机构 (CA) 颁发的 X.509 编码数字证书的集合。 TLS 协议会将这些证书视为“受信任”,并将其用作对 TLS 服务器实体向 NetX Secure TLS 客户端提供的证书进行身份验证的基础。

受信任的 CA 证书可以自签名,也可以由其他 CA 签名,在这种情况下,该证书称为“中间 CA”(ICA)。 在典型的 TLS 应用程序中,服务器会提供 ICA 证书及其服务器证书,但是成功进行身份验证的唯一要求是可以从服务器证书中追溯颁发者链(用于签署其他证书的证书),并将其追溯到受信任的证书存储中的受信任 CA 证书。 此链称为“信任链”或“证书链”。

若要初始化受信任的 CA 或 ICA 证书,应用程序必须使用 nx_secure_x509_certificate_intialize 服务(使用合适的证书数据填充 NX_SECURE_X509_CERT 结构,以供 TLS 使用)提供指向缓冲区的指针,该缓冲区包含 DER 编码的 X.509 证书。

然后,使用 nx_secure_tls_trusted_certificate_add 服务将已初始化的受信任证书添加到 TLS 控制块中。 未能添加证书将导致 TLS 客户端会话失败,因为 TLS 协议无法对远程 TLS 服务器主机进行身份验证。

TLS 客户端还需要空间来分配传入服务器证书(假设未使用预共享密钥模式)。 从 NetX Secure TLS 5.12 开始,应用程序无需再为远程证书分配空间。 然而,为服务器证书分配空间的旧选项仍然可用,而且在内部证书缓冲区优化13 之前,仍会使用用户分配的证书。有关详细信息,请参阅 nx_secure_tls_remote_certificate_allocate 服务。

创建受信任的证书存储并为服务器证书分配空间后,即可使用套接字建立安全的 TLS 客户端连接。

  1. 优化过程使用 nx_secure_tls_session_packet_buffer_set 来利用用户应用程序向 TLS 会话提供的“数据包缓冲区”,以便分配 X.509 分析结构,而不是使用 NetX Secure TLS 早期版本中采用的用户提供的结构。 可能(机率极小)遇到证书链超出数据包缓冲区大小的情况,在这种情况下,可能会增加数据包缓冲区大小或使用 nx_secure_tls _remote_certificate_allocate 为证书链分配更多空间。

应用程序接口调用

NetX Secure TLS 应用程序通常会在 ThreadX RTOS 下运行的应用程序线程内执行函数调用。 可以从 tx_application_define 中调用某些初始化,尤其是基础网络通信协议(例如,TCP 和 IP)初始化。有关初始化网络通信的详细信息,请参阅 NetX/NetXDuo 用户指南。

TLS 大量使用加密例程,这些例程操作通常会占用大量处理器时间。 通常,这些操作将在调用线程的上下文中执行。

TLS 会话启动

TLS 需要基础传输层网络协议才能工作。 通常使用 TCP 协议。 要建立 NetX Secure TLS 会话,必须使用 NetX/NetXDuo TCP API 建立 TCP 连接。 必须使用 nx_tcp_server_socket_listen 和 nx_tcp_server_socket_accept 服务(对于 TLS 服务器),或 nx_tcp_client_socket_connect 服务(对于 TLS 客户端)创建 NX_TCP_SOCKET 并建立连接。

建立 TCP 连接后,会将 TCP 套接字传递到 nx_secure_tls_session_start 服务。

TLS 数据包分配

NetX Secure TLS 使用与 NetX/NetXDuo TCP (NX_PACKET) 相同的数据包结构,不同之处在于必须调用 nx_secure_tls_packet_allocate 服务,而不是 nx_packet_allocate 服务,以便为 TLS 标头分配适当空间。

TLS 会话发送

TLS 会话启动后,应用程序可使用 nx_secure_tls_session_send 服务发送数据。 发送服务的用法与 nx_tcp_socket_send 服务相同,采用包含要发送的数据的 NX_PACKET 数据结构,只不过需要在发送前使用 NX Secure TLS 堆栈对数据加密,而且必须使用 nx_secure_tls_packet_allocate 来分配数据包。

TLS 会话接收

TLS 会话启动后,应用程序可使用 nx_secure_tls_session_receive 服务开始接收数据。 与 TLS 会话发送一样,此服务的用法与 nx_tcp_socket_receive 相同,不同之处在于,传入数据需要经过 TLS 堆栈解密和验证后才会在数据包结构中返回。

TLS 会话关闭

TLS 会话完成后,TLS 客户端和服务器必须向对方发送 CloseNotify 警报以关闭会话。 双方都必须接收并处理警报,确保成功关闭会话。

如果远程主机发送 CloseNotify 警报,则对 nx_secure_tls_session_receive 服务的任何调用都将处理警报,将相应的警报发送回远程主机,并返回 NX_SECURE_TLS_SESSION_CLOSED 值。 会话关闭后,通过该 TLS 会话发送或接收数据的任何尝试都会失败。

如果应用程序希望关闭 TLS 会话,则必须调用 nx_secure_tls_session_end 服务。 该服务将发送 CloseNotify 警报并处理响应 CloseNotify。 如果未收到响应,将返回错误值 NX_SECURE_TLS_SESSION_CLOSE_FAIL,指示 TLS 会话未完全关闭,可能存在安全漏洞。

TLS 警报

TLS 旨在提供最高安全性,因此协议中的任何错误行为都被视为潜在安全漏洞。 因此,消息处理或加密/解密中的任何错误都被视为严重错误,会立即终止握手或会话。

在本地应用程序中处理错误相对简单,而远程主机要知道发生错误后才能正确处理这种情况,并防止其他可能的安全漏洞。 因此,TLS 会在出现任何错误时向远程主机发送警报消息。

警报的处理方式与任何其他 TLS 消息相同,并在会话期间进行了加密,以防止攻击者从提供的警报类型中收集信息。 握手期间发送的警报范围有限,以限制潜在攻击者可获得的信息量。

用于关闭 TLS 会话的 CloseNotify 警报是唯一的非严重警报。 尽管将其视为警报并作为警报消息发送,但 CloseNotify 与其他警报不同,因为并不表示发生错误。

TLS RFC 中定义了警报值和“级别”(级别分为“警告”和“严重”– 大多数 TLS 警报属于“严重”级别),表示发生的错误类型。 除 CloseNotify 以外的大多数 TLS 警报都可视为表示潜在安全问题,并将导致 TLS 会话或握手中止。 如果任何 TLS API 调用返回 NX_SECURE_TLS_ALERT_RECEIVED (0x114),则可以使用 API 服务 nx_secure_tls_session_alert_value_get(NetX SECURE TLS 5.12 版中的新增服务)检索 TLS 警报值和级别,以便应用程序用于对任何有关安全问题的响应决策。 在大多数情况下,从远程主机收到除 CloseNotify 以外的任何警报都应视为严重错误,但也有例外情况 - 有关详细信息,请参阅 TLS RFC。

TLS 会话重新协商

TLS 支持“重新协商”概念,即,在现有 TLS 会话上下文内重新协商 TLS 会话参数。 实际上,这意味着使用现有会话对新的握手消息进行加密和身份验证。 如果 TLS 主机需要生成新的会话参数(例如,生成新的 TLS 会话密钥)但无需完成现有会话时,则可使用重新协商。 例如,当应用程序的安全策略要求会话密钥仅在有限时间内使用,但 TLS 会话在该时间以外仍处于活动状态时,可能需要重新协商。

会话重新协商存在一个问题,导致 TLS 容易受到特定中间人攻击,攻击者可以说服服务器使用新参数发起重新协商,从而允许其劫持 TLS 会话。 为了缓解此问题,引入了安全重新协商标识扩展(请参阅“错误!未找到参考源”部分)。

NetX Secure TLS 完全支持会话重新协商和安全重新协商标识扩展。

从远程主机接收数据时,无需应用程序交互即可自动处理重新协商(及扩展)。 如果需要有关会话重新协商的通知,则可以使用 nx_secure_tls_session_renegotiate_callback_set 服务提供重新协商回叫。 只要远程主机请求重新协商,就会调用该回叫,这样应用程序就可以根据需要采取措施。

要从活动 TLS 会话启动重新协商,只需在所需 TLS 会话上调用 nx_secure_tls_session_renegotiate 服务。

TLS 会话恢复

尽管有一些相似之处,但不应将 TLS 会话恢复与会话重协商混淆。 如果会话重新协商涉及在现有 TLS 会话中启动新握手,则会话恢复纯粹是可选功能,其中包括重启已关闭的 TLS 会话而不执行完整的 TLS 握手。 为实现此目的,TLS 实现可以缓存会话参数和密钥,并将其与会话 ID 关联,这是在原始握手中提供的唯一标识符。 通过将会话 ID 提供给 TLS 服务器,客户端会指示主机之间曾经存在一个 TLS 会话,且已在过去某个时间内完成,而且该客户端仍然具有通过减少握手以重新建立会话的状态。 由于会话密钥理论上仍然是机密的,只有两个通信主机知道,因此服务器可以启动新 TLS 会话并绕过大多数正常握手。

会话恢复可有效避免可能需要巨大开销的公钥操作,这些操作用于共享密钥生成主密钥和验证证书签名,但它还要求在内存中为所有可能的会话维护会话参数、密钥和密码状态(至少在配置时间范围内)。

当前 NetX Secure TLS 版本不支持会话恢复,会话 ID 会被 TLS 服务器忽略,而且 TLS 客户端始终提供 NULL 会话 ID,以提示服务器执行完整握手。 缺乏会话恢复应该不会引起互操作性问题,因为这完全是一个可选功能,并且在会话 ID 为 NULL 或无法识别时,所有 TLS 实现都必须默认为完全握手。

协议分层

TLS 协议适用于传输层(例如 TCP)和应用程序层之间的网络堆栈。 TLS 有时会被视为传输层协议(因此称为传输层安全),因为它在基础网络协议(如 TCP)中充当应用程序,有时会划为应用程序层。

TLS 需要一个支持按顺序无损传递(如 TCP)的传输层协议。 由于这种要求,TLS 无法在 UDP 顶层运行,因为 UDP 无法保证传送数据报。 名为“DTLS”的单独协议是 TLS 的修改版本,用于需要通过数据报协议(例如 UDP)实现 TLS 安全性的应用程序。 NetX Secure 支持 DTLS,但 DTLS 的文档不同于本文档。

Diagram of a TCP/IP and TLS protocol layers.

图 5 - TCP/IP 和 TLS 协议层

网络通信安全

保护跨公共网络和 Internet 的通信是一项重要议题,也是大量书籍、文章和解决方案探讨的的主题。 本话题复杂程度难以想象,但可以简化为一个简单的理念:通过网络发送信息,以便只有预期目标可以访问或更改该信息。 可划分为三个重要概念:保密性、完整性和身份验证。 TLS 协议为这三个方面提供了解决方案。

保密性

通过网络发送数据时,保证恶意实体无法获取数据通常至关重要。 如果通过 TCP/IP 连接发送数据,有权访问网络的任何人都可以使用易于获取的网络工具来读取数据。 为防止他人获取数据,必须对数据编码,从而除了预期目标之外的任何人都无法读取,这就是“保密性”。在 TLS 中,RSA 和 AES 等加密算法可提供保密性。

完整性

有时,保密性不足以保护网络上传输的数据。 在某些情况下,恶意实体无需知道数据包内容就可能改变 TCP 数据包的内容。 可以更改加密数据,使解密无效或更改消息参数,从而导致攻击者可能希望实现的任何结果。 在网络中,我们无法阻止攻击者更改传输中的数据,但可以提供一种机制来了解数据是否已更改。 如果在传输过程中更改数据,可识别这种行为并可拒绝数据。 这个概念就是“完整性”。 在 TLS 中,由一类称为“哈希函数”的加密例程提供完整性。 MD5 和 SHA-1 属于哈希函数示例。

身份验证

网络通信安全的第三个重要概念就是,只应将数据传递给预期目标。 攻击者可能会试图伪装成合法实体,接收预期发送给另一台主机的数据。 即使已通过保密性和完整性机制来发送数据,攻击者仍然可以通过这种欺骗获得所需结果(安全通信泄露)。 要防止出现这种情况,就需要一种机制来证明远程主机标识,然后再发送敏感数据。 这种证明远程主机身份的过程就称为“身份验证”。在 TLS 中使用数字证书、哈希函数和利用公钥加密属性的“数字签名”机制(如下所述)进行身份验证。 还可以通过预共享密钥 (PSK) 提供有限但有用的身份验证形式。

TLS 加密

TLS 协议是一个通信框架,通过加密保证 Internet 网络通信安全。 根据一般定义,加密是指以某种方式对数据进行编码,如果没有密钥,将很难获取原始数据(或有关该数据的信息)。 在计算机系统中,加密基于诸如有限域之类的复杂数学,可以分为两类:私钥(或对称加密)和公钥(或非对称加密)。 私钥加密示例包括 AES(高级加密标准)和 RC4 (Rivest Cipher 4)。 公钥加密示例包括 RSA(Rivest、Shamir、Adleson)和 Diffie-Hellman 密码。

TLS 协议同时利用私钥和公钥加密例程来加强平衡性能、安全性和灵活性。

私钥加密

私钥加密已经使用数千年。 基本替换密码(其中,用字母或字替换为另一个不相关的字母或字)是已知的最早加密示例,但随着信息时代的到来,私钥加密技术也显著提高。

私钥密码使用的“密钥”其实是一个值(一般情况下可能是字、短语或数字),用于对某些数据进行某种编码,以便只有对该密钥具有访问权限的实体才能通过有效方式解码数据。 数据加密和解密都使用该密钥,因此也称为“对称加密”。

即使所涉及的数学过于复杂,通常也能快速简单地实施私钥密码。 出于这个原因,TLS 使用在大量安全通信中使用私钥密码。

但是,当我们在一般计算机网络通信中使用私钥加密时存在一个问题:那就是必须在尝试通信的两台计算机之间共享该密钥。 通常情况下,在 Internet 上的两台计算机之间安全地传递私钥不切实际(通常也不可能),因为可以假定,任意数量的实体可以在通过 Internet 路由数据时经过的各种跃点中获取流量。 如果该密钥由恶意实体获得,则使用该密钥加密的所有数据都将泄露。 由于 Internet 上的大多数计算机只有网络连接,没有其他安全通信通道,因此通过网络发送密钥等同于发送未加密数据,毫无安全性可言。

出于这个原因,私钥加密不足以实现一般用途的网络通信安全协议。 这时就需要公钥加密帮助。

NetX Secure TLS 支持 AES 私钥加密。

公钥加密

不同于私钥加密,公钥加密是在 1970 年代出现的一种新概念。 使用数学中的“陷阱门函数”概念,发现有一种方法可以在网络上共享密钥,同时不会影响加密数据的安全性。

公钥加密的工作方式是将密钥(上文所述私钥加密意义上的密钥)分为私钥和公钥两部分,这也是公钥加密的名字来源。 基本概念是将其中一个密钥(通常是公钥)用于加密,而另一个密钥用于解密。 正是因为这种密钥的不对称特性,公钥加密还有另一个名称 -“非对称加密”。

公钥加密的数学相当复杂,但理念是公钥只能用于加密,因此,获取该密钥则不允许获取加密数据。 相反,私钥也是对使用公钥加密的数据解密的唯一方法。 因此,通过对私钥保密,想要与私钥所有者安全通信的任何人都只能使用相应的公来加密数据,而且知道只有拥有该私钥的人员才能获得安全数据。

NetX Secure TLS 支持 RSA 公钥加密。

重要

如果使用软件 RSA 实现,则 RSA 会占用大量处理器时间。 如果密钥大小较大,则需要按平方系数增加处理能力 – 密钥大小增加 2 倍,处理速度就会降低 4 倍。

公钥身份验证

公钥加密概念还有一个有趣的副作用,可以通过反向执行操作来提供身份验证和加密,即使用私钥加密,使用公钥解密。 执行此操作的实际机制取决于所使用的公钥算法,但概念相同。

要使用公钥身份验证进行身份验证,私钥所有者需要使用该私钥加密某些数据片段(通常是待验证数据的加密哈希)。 然后,要对来自私钥所有者的数据进行身份验证的用户可使用关联公钥来解密数据,如果解密成功,并且假定用户信任该公钥的有效性,则该用户可以确定数据来自私钥所有者。

在 TLS 中,公钥身份验证用于使用受信任的证书存储中的公钥来验证 TLS 服务器(或 TLS 客户端)提供的数字证书的有效性。 对照证书存储中的公钥检查证书,然后使用证书中的数据检查服务器身份。

NetX Secure TLS 支持 RSA 身份验证。

加密哈希

加密不是 TLS 中使用的唯一加密操作。 为了在 TLS 会话过程中保证消息完整性,还需要校验和确保消息内容未被篡改。 但是,简单校验和(如 TCP 中使用的校验和)不足以保证达到可接受的完整性级别,因为可以被经验丰富的攻击者轻松颠覆。 TLS 用于保证消息完整性的机制称为“加密哈希”。

加密是一种 1:1 编码,即,可以从加密数据中获取整个原始数据。 但是,哈希将任意数量的数据映射到固定大小的值,正如校验和。 与简单的校验和不同,哈希的开发初衷在于减少冲突,而不同的输入数据会出现相同输出。 在简单的校验和中,如果某个位从 1 翻转到 0,另一个位从 0 翻转到 1,但校验和保持不变。 使用加密哈希时,输出内容明显不同,从而让攻击者难以更改哈希数据,而且对已更改数据进行哈希运算仍会产生相同值(从而虚假验证该数据的完整性)。

TLS 使用多种不同的哈希算法实现消息(应用程序消息和 TLS 控制消息)完整性。 其中包括 MD5、SHA-1 和 SHA-256。

NetX Secure TLS 支持 MD5、SHA-1 和 SHA-256 哈希。

TLS 扩展

TLS 提供了许多扩展,可为某些应用程序提供附加功能。 这些扩展通常作为 ClientHello 或 ServerHello 消息的一部分发送,向远程主机表明使用扩展的愿望,或者提供用于建立安全 TLS 会话的其他详细信息。

通常,扩展会在握手开始时向 TLS 提供可选参数,指导接下来的操作。 某些扩展需要应用程序输入或决策,而其他扩展则会自动处理。

下表描述了 NetX Secure TLS 当前支持的 TLS 扩展:

Extension Name 说明
安全重新协商指示 此扩展可缓解重新协商握手期间发生的中间人攻击漏洞。
服务器名称指示 此扩展允许 TLS 客户端向 TLS 服务器提供特定的 DNS 名称,使服务器能够选择正确凭据(假定服务器具有多个身份证书和网络入口点)。
签名算法 此扩展支持 TLS 客户端向 TLS 服务器提供可接受的签名和哈希算法列表。

支持的 TLS 扩展概述

安全重新协商指示

TLS 支持在现有 TLS 会话中执行握手的概念,从而使用已建立的会话来加密握手消息。 此过程允许重新建立加密会话密钥,而无需结束 TLS 会话(请参阅“TLS 会话重新协商”部分)。

遗憾的是,在 TLS 使用一段时间的重协商后,发现存在利用重新协商功能的中间人攻击漏洞。 为了消除此漏洞,引入了安全重新协商指示扩展。 实质上,安全重新协商扩展使用已建立连接中的 Finished 消息哈希来验证原始主机是否参与重新协商握手,实际上,在假设攻击者无法伪造哈希(可能需要访问会话密钥)的情况下,哈希将用作验证令牌。

NetX Secure TLS 会自动处理重新协商,并在默认情况下使用安全重新协商扩展。 无需应用程序交互。

服务器名称指示

TLS 握手期间,TLS 客户端期望远程服务器提供身份证书,以便客户端对服务器进行身份验证。 但是,在某些情况下,服务器将为多种不同的服务提供不同的“虚拟”服务器,且每个服务都具有唯一标识。 如果单个服务器具有多个标识,TLS 客户端可以提供特定 DNS 名称以供服务器选择正确凭据,而提供名称的机制就是服务器名称指示 (SNI) 扩展。

对于使用 SNI 扩展的应用程序,需要进行特定交互。 对于 TLS 客户端,应用程序必须提供要发送到远程服务器的 DNS 名称。 对于 TLS 服务器,应用程序必须从扩展中读取 DNS 名称,并选择要发送回客户端的相应证书。

以下部分详细说明了如何在 NetX Secure TLS 中使用 SNI 扩展。

SNI 扩展 – TLS 客户端

希望使用 SNI 扩展名的 NetX Secure TLS 客户端必须为握手期间提供的 TLS 提供 DNS 名称。 必须在启动 TLS 会话之前初始化并提供该名称,因为该扩展通过启动握手过程的 ClientHello 消息发送。

以下代码段演示如何使用扩展。 首先,使用所需的服务器名称对 NX_SECURE_X509_DNS_NAME 对象进行初始化。 然后,在启动 TLS 会话之前,需要使用 SNI 扩展 API 向 TLS 提供该名称。 设置名称后,无需执行其他操作。 请参阅第 4 章中的 API 参考

有关各个函数的详细信息,请参阅 NetX Secure 服务说明。

/* The dns_name variable will contain our desired server name. */
UINT status;
NX_SECURE_X509_DNS_NAME dns_name;

/* Initialize the server DNS name. */
status = nx_secure_x509_dns_name_initialize(&dns_name, "www.example.com", 
                                            strlen("www.example.com"));


/* Initialize SNI extension in previously-initialized TLS Session. */
status = nx_secure_tls_session_sni_extension_set(&client_tls_session, &dns_name);

/* Now start the TLS session, starting with establishing the TCP connection – if 
   TLS is started before initializing the SNI extension, the extension will not be 
   sent in the ClientHello message! */
status = nx_tcp_client_socket_connect(&client_socket, IP_ADDRESS(1, 2, 3, 4), 443, 
                                      5 * NX_IP_PERIODIC_RATE);

status = nx_secure_tls_session_start(&client_tls_session, &client_socket, 
                                     NX_WAIT_FOREVER);

SNI 扩展 – TLS 服务器

在 TLS 服务器端,应用程序可以处理 SNI 扩展,以便选择正确的凭据(例如,证书)并在握手期间提供给远程客户端。 为此,应用程序必须提供一个会话回叫,收到 ClientHello 消息后将会调用该回叫。

nx_secure_tls_session_server_callback_set API 的示例代码(请参阅第 122 页)说明了如何使用服务器回调分析传入的 SNI 扩展。 实质上,TLS 服务器接收 ClientHello 并调用回叫。 然后,应用程序使用 nx_secure_tls_session_sni_extension_parse API 来分析提供给回叫的扩展数据,以查找 SNI 扩展并返回提供的 DNS 名称(请注意,该扩展仅支持单个 DNS 名称)。 获取名称后,应用程序将利用该名称查找并发送相应的服务器身份证书(适用情况下还有颁发者链)。

签名算法扩展

这是 TLS 1.2 的特定算法,允许 TLS 客户端提供可接受的签名和哈希算法对列表,这些签名和哈希算法对可用于生成和验证数字签名。 此列表由 NetX Secure TLS 使用提供给 nx_secure_tls_session_create 的密码表自动生成,适用于 TLS 客户端。 无需应用程序交互。

身份验证方法

TLS 提供的框架便于两个设备通过不安全的网络建立安全连接,但问题是需要了解该连接的另一端上的设备标识。 如果没有对远程主机标识进行身份验证的机制,则攻击者可以轻松将其伪装成受信任的设备。

最初,使用 IP 地址、硬件 MAC 地址或 DNS 可能会为标识网络上的主机提供较高置信度,但是考虑到 TCP/IP 技术的本质以及地址被欺骗和 DNS 条目受损的难易程度(例如,通过 DNS 缓存中毒),TLS 显然还需要附加一层保护来防止欺诈性标识。

可以通过多种机制为 TLS 提供这一额外的身份验证层,但是最常见的机制是数字证书。其他机制包括预共享密钥 (PSK) 和密码方案。

数字证书

数字证书是在 TLS 中对远程主机进行身份验证的最常见方法。 数字证书本质上是一种具有特定格式的文档,可为计算机网络上的设备提供标识信息。

TLS 通常使用一种名为 X.509 的格式,这是一种由国际电信联盟制定的标准,但如果 TLS 主机可以就所使用的格式达成一致,则可以使用其他格式的证书。 X.509 定义了证书的特定格式,以及可用于生成数字文档的各种编码。 可使用另一种电信标准 ASN.1 的变体对与 TLS 一起使用的大多数 X.509 证书进行编码。 ASN.1 中具有各种数字编码,但最常见的 TLS 证书编码是可辨别编码规则 (DER) 标准。 “DER”是 ASN.1 基本编码规则 (BER) 的简化子集,旨在厘清歧义,从而简化分析。 无线传播时,TLS 证书通常采用二进制 DER 编码,这也是 NetX Secure 希望用于 X.509 证书的格式。

尽管实际 TLS 协议中使用了 DER 格式的二进制证书,但这些证书可能会以多种不同的编码生成并存储,采用 .pem、.crt 和 .p12 等文件扩展名。 不同制造商的不同应用程序使用不同的变体,但通常可以使用广泛可用的工具将所有变体转换成 DER。

PEM 是最常见的替代证书编码。 PEM(隐私增强邮件)格式是 DER 编码的 base-64 编码版本,由于采用这种编码方式生成的可打印文本便于通过电子邮件或基于 Web 的协议发送,因此经常使用。

本手册通常并未涵盖为 NetX Secure 应用程序生成证书的内容,但 OpenSSL 命令行工具 (www.openssl.org) 广泛可用,可以在大多数格式之间进行转换。

你可以生成自己的证书,可以由制造商或政府组织提供证书,也可以从商业证书颁发机构购买证书,具体取决于应用程序。

要在 NetX Secure 应用程序中使用数字证书,必须首先将证书转换为二进制 DER 格式,并根据需要将关联的私钥(例如,RSA 的“私有幂”)转换为二进制格式,通常为 PKCS#1 格式的 DER 编码的 RSA 密钥或 DER 编码的 ECC 密钥。 转换完成后,你可以选择证书和私钥加载到设备上。 可能的选项包括使用基于闪存的文件系统,或从数据中生成 C 数组(使用 Linux 的“xxd”等工具),并将证书和密钥作为常量数据编译到应用程序中。

将证书加载到设备后,可以使用 TLS API 将证书与 TLS 会话关联。

有关如何在 NetX Secure TLS 中使用 X.509 证书的详细信息和示例,请参阅“将 X.509 证书导入 NetX Secure”部分。

有关详细信息,请参阅 API 参考中的以下 TLS 服务:

  • nx_secure_x509_certificate_initialize
  • nx_secure_tls_local_certificate_add
  • nx_secure_tls_local_certificate_remove
  • nx_secure_tls_remote_certificate_allocate
  • nx_secure_tls_trusted_certificate_add
  • nx_secure_trusted_certificate_remove

TLS 客户端证书详细信息

TLS 客户端实现通常不需要将“本地”证书14 加载到设备上。 已启用客户端证书身份验证时例外,但这种情况并不常见。

TLS 客户端至少需要加载一个“受信任”证书15(如果需要,可能需要加载更多证书),并为“远程”证书16 分配空间。

有关添加受信任证书和为远程证书分配空间的详细信息,请参阅以下服务的 TLS API 参考:nx_secure_tls_remote_certificate_allocate、nx_secure_tls_trusted_certificate_add。

  1. “本地”证书是指标识本地设备的证书,即,可为其中加载 TLS 应用程序的设备提供标识信息。

  2. “受信任”证书是指直接或通过公钥基础结构 (PKI) 为远程设备提供信任和身份验证基础的证书。 信任链的根通常称为“证书颁发机构”或 CA 证书。

  3. “远程”证书是指远程主机在 TLS 握手期间发送的证书。 可为该远程主机提供标识,通过将其与本地设备上的“受信任”证书对比来进行身份验证。

TLS 服务器证书详细信息

TLS 服务器实现通常无需在设备上加载“受信任”证书或分配远程证书。 启用客户端证书身份验证时例外(这种情况不太常见)。

TLS 服务器要求加载“本地”证书,以便服务器能够在 TLS 握手期间向远程客户端提供该证书,从而向客户端验证服务器的身份。

有关加载本地证书以供 NetX TLS 服务器应用程序使用的详细信息,请参阅以下服务的 API 参考:

  • nx_secure_tls_local_certificate_add、
  • nx_secure_tls_local_certificate_remov。

预共享密钥 (PSK)

预共享密钥 (PSK) 概念是用于在 TLS 中提供身份验证的一种替代机制。 使用 PSK 密码套件则无需执行占用大量处理器时间的的公钥加密操作,非常适合资源受限的嵌入式设备。 PSK 替换 TLS 握手中的证书,并用于替代 TLS 会话密钥生成的加密预主密钥。

由于必须在两个设备上都具有共享秘钥时才能建立 TLS 会话,因此,PSK 密码套件会受限。 这意味着必须使用某种安全方法(不是 TLS PSK 连接)加载使用该密钥的设备,可通过 TLS PSK 连接更新 PSK,但必须使用通过其他机制加载的 PSK 启动设备。 例如,传感器设备及其网关设备可以在出厂前在工厂中加载 PSK,或使用标准 TLS 连接(带有证书)加载 PSK。

PSK 密码套件分为两种形式,如 RFC 4279 中所述。 第一种形式使用 RSA 或 Diffie-Hellman 密钥,这些密钥的使用方式与标准 TLS 握手的证书中传输的公钥相同。 第二种形式(在资源受限的环境中更有用)使用 PSK 直接生成会话密钥(例如,供 AES 使用),从而避免使用昂贵的 RSA 或 Diffie-Hellman 操作。

NetX Secure 支持第二种形式的 PSK 密码套件,允许应用程序删除所有公钥加密代码和内存使用情况。 PSK 本身不是 AES 密钥,但可以视为更像是从中生成实际密钥的密码。 虽然 PSK 值越长,安全性就越高(与密码同理),但对 PSK 值却几乎没有限制。

若要在 NetX Secure 应用程序中使用 PSK,必须首先定义全局宏 NX_SECURE_ENABLE_PSK_CIPHERSUITES。 通常通过编译器设置完成此操作,但也可以将定义放置在 nx_secure_tls 的标头文件中。 定义宏后,可将 PSK 密码套件支持编译到 NetX Secure TLS 应用程序中。

启用 PSK 支持后,然后就可使用 TLS API 为应用程序设置 PSK。 每个 PSK 都需要 PSK 值(实际“密钥”– 确保值安全)、用于标识特定 PSK 的“标识”值以及 TLS 服务器用来选择特定 PSK 值的“标识提示”。

PSK 本身可以是任何二进制值,因为它从不通过网络连接发送。 PSK 可以是长度不超过 64 字节的任何值。

标识和提示必须是使用 UTF-8 格式化的可打印字符串。 标识值和提示值可以是不超过 128 字节的任何长度。

“标识”和“PSK”形成唯一的对,然后加载到网络中需要彼此通信的每个设备上。

“提示”主要用于定义特定的应用程序配置文件,从而按功能或服务对 PSK 分组。 必须提前对这些值进行协商,并且具体取决于应用程序。 例如,OpenSSL 命令行服务器应用程序(已启用 PSK)使用默认字符串“Client_identity”,该字符串必须由 TLS 客户端提供才能继续进行 TLS 握手。

有关 PSK 的详细信息,请参阅以下服务的 NetX Secure API 参考:nx_secure_tls_client_psk_set、nx_secure_tls_psk_add。

将 X.509 证书导入 NetX Secure

Internet 上的大多数 TLS 连接都需要数字证书。 证书提供了一种方法,通过使用受信任的中介机构(通常称为证书颁发机构或 CA)对 Internet 上以前未知的主机进行身份验证。 若要将 NetX Secure 设备与商用云服务(例如 Amazon Web Services)连接,则需要将证书加载到设备中,从而将其导入应用程序。

除证书外,有时还需要与证书关联的私钥。 在某些应用程序中(例如,未使用客户端证书身份验证的 TLS 客户端),仅证书便已足够,但如果要使用证书标识设备,则需要私钥。 私钥通常在创建证书时生成,存储在单独文件中,并通常使用密码加密。

证书类型

数字证书通常用于标识网络上的实体,但根据应用程序而属性略有不同。

本地证书

在本文档中,“本地证书”是指那些为本地设备提供标识的证书(也可能称为“设备证书”)。 当远程主机需要对本地设备进行身份验证时,将向远程主机提供这些证书。

远程证书

在本文档中,“远程证书”是指 TLS 握手期间由远程主机提供的证书(如果适用)。 必须为这些证书分配空间,否则 NetX Secure 将无法分析并完成 TLS 握手。

签名证书

“签名证书”用于对其他证书或数据进行数字签名,以便进行身份验证。 这些证书可以是公钥基础结构 (PKI) 的中的中间证书或根证书,通常不用于标识单个设备或主机。

根 CA 证书

“根 CA 证书”是一种提供 PKI 基础的自签名证书,不是由其他签名证书进行签名。 TLS 客户端通常需要至少一个根 CA 证书来验证远程服务器。

证书格式

数字证书只是包含使用 ASN.1 语法编码的结构化数据的文件。 但是,可以通过多种格式存储证书,而且只有正确格式的证书才能加载到 NetX Secure 应用程序中。

最常见的证书格式为 DER 和 PEM。 DER(可辨别编码规则,ASN.1 格式)是 TLS 在执行初始握手时使用的二进制格式。 PEM(隐私增强邮件)是 base-64 编码的 DER 版本格式,适用于发送电子邮件或在 Web 上通过 HTTP 发送。 不同的供应商对证书使用不同的文件扩展名,例如,PEM 证书使用“.pem”或“.crt”,而 DER 证书则使用“.der”。 如果具备证书但不清楚使用的格式,则在文本编辑器中打开文件后即可确定类型,因为 DER 文件是编码二进制文件,而 PEM 文件是以标头“-----BEGIN CERTIFICATE-----”开头的常规 ASCII 文本。

NetX Secure 要求证书采用二进制 DER 格式,因此在导入之前需要将证书转换为 DER 格式。 可以使用 OpenSSL 之类的现有工具来完成此操作。

如果应用程序需要私钥,将使用特定格式的 PEM 或 DER(RSA 为 PKCS#1,ECC 为 RFC 5915)对密钥文件进行编码。 私钥文件将需要在导入前转换为 DER。

以下 OpenSSL 命令示例中,将证书和 RSA 密钥文件转换为 NetX Secure 所需的 DER 格式(ECC 操作类似 - 请参阅 OpenSSL 文档)。

openssl x509 -inform PEM -in <certificate> -outform DER -out cert.der
openssl x509 -inform PEM -in <root CA cert> -outform DER -out CA_cert.der
openssl rsa -inform PEM -in <private key> -outform DER -out private.key

私钥和证书

对于标识设备的证书,必须与证书一起加载关联私钥。 TLS 服务器使用私钥(可能使用 RSA、Diffie-hellman 或 Elliptic-Curve 等其中一种公钥算法)解密来自 TLS 客户端的传入密钥材料(“预主密钥”),从而向客户端验证身份。 对于 TLS 客户端,如果已提供身份证书(具有关联私钥的证书)且服务器请求客户端证书,则会利用该私钥对客户端进行身份验证。对于 RSA 客户端,客户端会使用私钥加密令牌,然后服务器会使用客户端证书中提供的客户端公钥解密此私钥(Diffie-Hellman 和 ECC 身份验证的工作方式类似,但细节略有不同)。

在 NetX Secure 中,使用 nx_secure_x509_certificate_initialize 服务初始化 X.509 证书(请参阅“将证书加载到设备上”了解详细信息),还可将私钥与该证书关联。

如果提供了私钥,则会将证书标记为标识设备的“标识”证书。 密钥以连续的二进制 blob 的形式和长度传递,采用关联的密钥类型。 密钥类型取决于密钥的类型(例如 RSA、ECC 等 )和格式(例如 PKCS#1 DER)。 如果未提供任何密钥,则可以传递值 NX_SECURE_X509_KEY_TYPE_NONE(值 0x0),表示未提供任何密钥(长度为 0,且数据参数的 NX_NULL 指针也具有相同效果)。

下表显示了 NetX Secure 已知的密钥类型,以及要传递到 nx_secure_x509_certificate_initialize 的关联类型标识符。 随着 NetX Secure 中添加更多加密算法,还会添加其他密钥类型。

标识符 算法 格式 编码 “值”
NX_SECURE_X509_KEY_TYPE_NONE None 不适用 空值 0x0
NX_SECURE_X509_KEY_TYPE_RSA_PKCS1_DER RSA PKCS#1 DER 0x1
NX_SECURE_X509_KEY_TYPE_EC_DER ECDSA RFC 5915 DER 0x2

用户定义的私钥类型

nx_secure_x509_certificate_initialize 服务的密钥类型标识符的值可管理提供私钥时执行的操作。 已知类型的值范围介于 0x0000 0000 – 0x0000 FFFF 之间(32 位无符号整数的低 16 位)。 对于采用自定义密钥类型17 的平台(例如某些基于硬件的加密引擎),可以将范围介于 0x0000 1000-0xFFFF FFFF(高 16 位非零整数)的用户定义密钥类型作为密钥类型传递。 如果设置为密钥类型中的任何高 16 位,则会将私钥数据直接传递到 TLS 密码套件表中的相应加密例程(例如,RSA)。 用户定义的密钥类型在传递到加密例程之前不会进行分析或处理。 此外,还会将用户定义的密钥类型传递给加密例程,以便可以在该级别进行任何适当的处理。

请注意,用户定义的密钥类型通常用于利用自定义(可能加密)密钥数据的特定硬件平台。 通常,这意味着将使用该硬件供应商特定的机制(在 PKCS#11 等标准情况下,则使用特定标准)生成或编码密钥数据。 有关详细信息,请查阅硬件平台文档。

  1. 用户定义的密钥类型需要相应的自定义加密例程来处理自定义密钥格式。 加密例程必须具备匹配的算法(例如 RSA),而且要传递到密码套件表的 TLS 中。

将证书加载到设备上

任何将文件加载到设备上的方法都可以导入证书。

加载证书的最简单方法是将二进制 DER 编码数据转换为 C 数组,并将其作为常量编译到应用程序中。 这可以通过 Linux 中的“xxd”等工具(具有“-i”选项)轻松完成。

或者,也可将证书加载到闪存文件系统或其他存储选项,前提是可以将指向证书数据的指针传递到 NetX Secure API。

NetX Secure 所需的证书文件

需要导入的证书文件取决于应用程序。 通常,TLS 服务器需要证书来识别设备,TLS 客户端需要一个或多个受信任证书来对远程服务器进行身份验证。 下表说明了某些不同 TLS 应用程序所需的证书。

TLS 功能/选项 所需证书/密钥(最低要求)
TLS 客户端 根 CA 证书
TLS 服务器 本地证书,该证书的私钥
具有客户端证书身份验证的 TLS 服务器 本地证书、私钥、根 CA
具有客户端证书身份验证的 TLS 客户端 本地证书、私钥、根 CA
仅具有预共享密钥的 TLS 客户端或服务器 无(使用 PSK 而不是证书)

用于加载证书的相关服务如下所示:

API Name(API 名称) 用途
nx_secure_x509_certificate_initialize 必须为所有证书调用,才能用证书数据和私钥填充 NX_SECURE_X509_CERT 结构。
nx_secure_tls_local_certificate_add 将本地证书添加到 TLS 会话以标识设备。
nx_secure_tls_local_certificate_remove 从 TLS 会话中删除本地证书。
nx_secure_tls_remote_certificate_allocate 为远程证书分配空间(通过未初始化的 NX_SECURE_X509_CERT 调用)。
nx_secure_tls_trusted_certificate_add 将证书作为受信任证书添加到 TLS 会话,以便对远程主机进行身份验证。
nx_secure_tls_trusted_certificate_remove 从 TLS 会话中删除受信任证书。

使用 AWS IoT 证书

在 Amazon Web Services IoT 界面中,从侧栏菜单中选择“安全”,然后选择“证书”。 创建新证书,并按照说明下载新设备证书。

下载证书后,需要使用 OpenSSL 或类似的实用工具将其转换为 DER 格式。

注意:AWS 还会提供公钥文件。 公钥包含在本地设备证书中,因此无需将其导入应用程序。

例如,可通过以下命令将本地设备证书及其私钥转换为 DER 格式,以供 NetX Secure使用:

openssl x509 -inform PEM -in <certificate> -outform DER -out cert.der
openssl x509 -inform PEM -in <root CA cert> -outform DER -out CA_cert.der
openssl rsa -inform PEM -in <private key> -outform DER -out private.key

按照以上说明操作,可以将转换后的文件导入应用程序。

NetX Secure 中的 X.509 证书验证

使用带有 X.509 证书的 TLS 进行主机标识和验证时,必须理解这些证书的实际验证方式。 尽管 TLS 规范未提供有关如何验证证书的详细说明,但确实参考了 X.509 规范 (RFC 5280)。 通常情况下,TLS 应该至少会对传入证书执行基本验证(这些证书由远程主机在 TLS 握手期间提供),NetX Secure TLS 同样如此。

基本 X.509 验证

对于任何传入证书,NetX Secure TLS 会执行基本 X.509 路径验证。 此过程涉及对照颁发者证书检查每个证书的数字签名,颁发者证书可能由远程主机提供,也可能位于受信任的证书存储中(有关详细信息,请参阅“将 X.509 证书导入 NetX 安全”部分)。 对颁发者证书递归地重复验证过程,直到达到可信证书或证书链结束(带有自签名证书或缺少颁发者证书)。 如果已达到受信任证书,则验证证书,否则将拒绝证书。 此外,在验证过程的每个阶段,将对照应用程序时间戳函数提供的时间检查每个证书的到期日期(有关详细信息,请查看服务“nx_secure_tls_session_time_function_set”)。

X.509 规范还提供了一项支持“策略”的算法,该算法是在路径验证过程中可以检查的 X.509 扩展中存在的标识符。 NetX Secure 当前将 X.509 证书视为已定义“anyPolicy”选项,即,所有策略均可接受,并且不执行可选策略检查。 在将来的版本中,可以使用此功能增强 NetX Secure X.509 实现。 目前,可以使用 nx_secure_x509_extension_find API 从证书中获取策略扩展。

基本路径验证完成后,TLS 将使用 nx_secure_tls_session_certificate_callback_set API 调用应用程序提供的证书验证回叫。 如果未提供回叫,则会在成功进行路径验证后将证书视为受信任。 如果提供了回叫,则回叫将对应用程序所需的证书执行任何其他验证。 利用回叫的返回值,可确定是否继续 TLS 握手或由于验证失败而中止握手。

使用指向相关 TLS 会话的指针和指向待验证证书的 NX_SECURE_X509_CERT 指针调用该回叫。 在 TLS 会话和证书之间,应用程序具有要从 TLS 中获取的所有数据,以执行其他验证检查。

为了帮助进行其他验证,NetX Secure 为某些常见验证操作提供 X.509 例程,包括 DNS 验证和证书吊销列表检查。 所有这些例程都适用于在证书验证回叫中使用,但也可用于对 X.509 证书执行脱机检查。

下表汇总了可供 X.509 证书处理的 helper 函数。 有关操作的更详细说明,请参阅第 4 章的以下部分和 API 参考

NetX Secure 服务说明提供有关特定例程的更多详细信息。

API Name(API 名称) 说明
nx_secure_x509_common_name_dns_check 对照预期 DNS 名称检查 X.509 使用者公用名和 SubjectAltName
nx_secure_x509_crl_revocation_check 检查 X.509 证书吊销列表 (CRL) 中是否包含已吊销证书
nx_secure_x509_extended_key_usage_extension_parse 在证书中分析和查找特定的扩展密钥用法 OID
nx_secure_x509_key_usage_extension_parse 分析并返回证书中的密钥使用位域
nx_secure_x509_extension_find 查找并返回特定扩展的原始 DER 编码的 ASN.1 数据。

用于证书验证回叫的 X.509 helper 函数

X.509 扩展

X.509 规范介绍了多个可用于提供其他信息的“扩展”,这些信息可供证书验证使用。 大多数情况下,这些扩展是可选的,不是针对受信任的根证书进行数字证书安全验证的必备条件。 但是,NetX Secure 确实支持某些基本扩展。 将来的版本中可能会添加对其他扩展的支持。

下表列出了当前支持的扩展:

扩展名称 说明 相关 API
密钥用法 对位域中的证书公钥提供可接受的用法 nx_secure_x509_key_usage_extension_parse
扩展密钥用法 对使用 OID 的证书公钥提供其他可接受的用法 nx_secure_x509_extended_key_usage_extension_parse
使用者可选名称 提供同样由证书表示的替代 DNS 名称 nx_secure_x509_common_name_dns_check

不支持的 X.509 扩展

NetX Secure 的 X.509 实现还提供一种服务,可提取不受支持的扩展:nx_secure_x509_extension_find。 此 API 适用于高级用户,因为需要了解 DER 编码的 ASN.1 才能分析返回的数据。 在内部用于提取支持的扩展,但也便于开发 X.509 扩展的自定义支持。

要使用 nx_secure_x509_extension_find,需要传入 NX_SECURE_X509_EXTENSION 以及证书和扩展 ID,这是已知扩展类型的可变长度 OID 字符串的整数表示形式。 请参阅第 178 页,了解有关 nx_secure_x509_extension_find API 参考中提供的 X.509 扩展支持的 OID 完整列表。

NX_SECURE_X509_EXTENSION 结构定义如下:

typedef struct NX_SECURE_X509_EXTENSION_STRUCT
{
    /* Identifier (maps to OID) for this extension. */
    USHORT nx_secure_x509_extension_id;

    /* Critical flag - boolean value. */
    USHORT nx_secure_x509_extension_critical;

    /* Pointer to DER-encoded extension data. */
    const UCHAR *nx_secure_x509_extension_data;
    ULONG        nx_secure_x509_extension_data_length;
} NX_SECURE_X509_EXTENSION;

当成功返回服务时,将使用证书中的相关数据来填充结构。 “nx_secure_x509_extension_id”字段通常用于内部用途,但会使用相关 OID 整数表示形式填充。 nx_secure_x509_extension_critical 字段公开了 X.509 关键扩展标记值(布尔)。 “nx_secure_x509_extension_data”和“nx_secure_x509_extension_data_length”字段分别包含指向扩展的 DER 编码 ASN.1 数据的指针和该数据的长度。

对扩展 ASN.1 数据的分析不属于本文档范围。但如果有权访问 NetX Secure TLS 源,就可以看到在为支持的扩展调用 nx_secure_x509_extension_find 时如何进行分析。

X.509 DNS 验证

TLS 中的常见证书验证操作包括,对照主机在 TLS 握手期间提供的 X.509 证书检查远程主机的顶级域 (TLD) 名。 假设可以信任 DNS 查找,此操作有助于确保证书确实与提供证书的主机服务器匹配。 在 NetX Secure TLS 中,此功能由 nx_secure_x509_common_name_dns_check 服务提供,该服务利用证书和包含所用 URL 的 TLD 部分的字符串来访问主机。 TLD 将与证书的“公用名”字段进行比较,如果匹配,则返回 NX_SUCCESS。 如果公用名不匹配,该例程还会检查是否存在 x.509 证书扩展 subjectAltName。 如果存在 subjectAltName,则还会对照提供的 TLD 检查扩展中的任何 DNSName 条目。 同样,如果有任何匹配项,则返回 NX_SUCCESS。 如果未找到匹配项,则返回适合从证书验证回叫返回的错误。

X.509 密钥用法和扩展密钥用法扩展

X.509 密钥用法和扩展密钥用法扩展提供了有关在对证书进行身份验证时如何使用证书公钥的信息。 密钥用法由证书颁发者在签名并颁发证书时提供。 TLS 主机可以使用密钥用法来检查证书是否经过授权,可用于对远程 TLS 主机进行身份验证和执行其他操作。

密钥用法扩展包含一个简单位域,其中每个位代表一种特定密钥用法。 请参阅第 183 页,了解有关 nx_secure_x509_key_usage_extension_parse API 参考中提供的这些值的完整列表。 有关密钥用法位及其含义的更完整说明,请参阅 RFC 5280 的 4.2.1.3 部分。

与密钥用法扩展一样,扩展密钥用法扩展也可提供可接受的密钥使用信息。 但是,为了支持任意用法,扩展密钥用法扩展使用 OID 而不是位域。 分析 NetX Secure X.509 中的扩展密钥用法扩展时,应用程序提供了一个表示 OID 的整数 - 无论是否存在该 OID,随后都会返回 nx_secure_x509_extended_key_usage_extension_parse 服务。 请参阅第 175 页,了解有关 nx_secure_x509_extended_key_usage_extension_parse API 参考中提供的扩展密钥用法支持的 OID 完整列表。 有关 OID 及其含义的更完整说明,请参阅 RFC 5280 第 4.2.1.12 部分。

X.509 CRL 吊销状态检查

X.509 提供一种名为“证书吊销列表” (CRL) 的机制,该机制允许数字证书签名颁发机构吊销已签名证书的有效性。 任何需要验证签名颁发机构证书的应用程序都可以获取 CRL,并将该颁发机构(颁发者)签名的任何证书与 CRL 进行比较,查看其状态是否出于某种原因(例如私钥泄露)而被吊销。 通过这种方式,应用程序可以避免使用通过其他证书验证检查的潜在危险证书。

应用程序通过从预定义服务器下载 DER 编码列表或通过其他方式来获取 CRL。 实际设置可能根据颁发者而异,因此 NetX Secure 不提供获取 CRL 的机制,但提供了可根据 CRL 检查证书的例程 nx_secure_x509_crl_revocation_check。

该 API 需要 DER 编码的 CRL,要参照检查的证书存储(例如 TLS 会话中的证书存储)以及要检查的证书。 例程首先根据受信任的存储(应用程序提供的证书存储的一部分)验证 CRL 本身。 此操作可有效防止使用欺诈性 CRL 进行拒绝服务攻击,并确定 CRL 确实来自正确的颁发者。 进行 CRL 验证后,将检查颁发者 – 如果 CRL 颁发者与证书颁发者不匹配,则 CRL 对该证书无效,并返回错误。 此时,应用程序可以确定 TLS 握手是否可以继续。 如果颁发者匹配,则会搜索 CRL,查找当前验证证书的序列号。 如果列表中存在此序列号,则返回一个错误,指示证书已被吊销。 如果未发现匹配项,则返回 NX_SUCCESS。

NetX Secure TLS 中的客户端证书身份验证

使用 X.509 证书身份验证时,TLS 协议要求 TLS 服务器实例为标识提供证书,但在默认情况下,TLS 客户端实例无需提供用于身份验证的证书,而是使用另一种形式的身份验证(例如用户名/密码组合)。 这符合 Internet 上最常见的 TLS 网站用途。 例如,在线零售网站必须向使用 Web 浏览器的潜在客户证明服务器的合法性,但用户将使用登录名/密码来访问特定帐户。

但是,默认用法无法保证满意,因此 TLS 允许 TLS 服务器实例向远程客户端请求证书。 启用此功能后,TLS 服务器会在握手期间向 TLS 客户端发送一条 CertificateRequest 消息。 客户端必须使用自己的证书和 CertificateVerify 消息进行响应,该消息中包含一个加密令牌,证明该客户端拥有与证书关联的匹配私钥。 如果验证失败,或者证书未连接到服务器上的受信任证书,则 TLS 握手失败。

TLS 中的客户端证书身份验证会出现两种不同的情况 - 以下部分将分别说明。

TLS 客户端的客户端证书身份验证

TLS 客户端可能尝试连接到请求证书进行客户端身份验证的服务器。 在这种情况下,客户端必须向服务器提供证书,并验证它是否拥有匹配的私钥,否则服务器将终止 TLS 握手。

NetX Secure TLS 中没有支持此功能的特殊配置,但应用程序必须使用 nx_secure_tls_local_certificate_add 服务为 TLS 客户端实例提供本地标识证书。 如果应用程序未提供证书,但远程服务器使用客户端证书身份验证并请求证书,则 TLS 握手将失败。 使用 nx_secure_tls_local_certificate_add 为 TLS 会话提供的证书必须由远程服务器识别,才能完成 TLS 握手。

TLS 服务器的客户端证书身份验证

由于是可选功能,因此客户端证书身份验证的 TLS 服务器案例比 TLS 客户端案例稍微复杂一些。 在这种情况下,TLS 服务器需要专门从远程 TLS 客户端请求证书,然后处理 CertificateVerify 消息以验证远程客户端拥有匹配的私钥,之后服务器必须检查客户端提供的证书是否可以追溯到本地受信任的证书存储中的证书。

在 NetX Secure TLS 服务器实例中,客户端证书身份验证由以下服务控制
nxsecuretlssessionclientverifyenable
nxsecuretlssessionclientverifydisable 服务。

要启用客户端证书身份验证,应用程序必须
通过 TLS 服务器会话实例调用 nxsecuretlssessionclientverifyenable,然后再调用 nx_secure_tls_session_start。 请注意,在用于 TLS 客户端连接的 TLS 会话上调用此服务无效。

当启用客户端证书身份验证时,TLS 服务器将在 TLS 握手期间从远程 TLS 客户端请求证书。 .在 NetX Secure TLS 服务器中,将对照使用 nxsecure_tlstrustedcertificateadd(根据 X.509 颁发者链)创建的受信任的证书存储检查客户端证书。 远程客户端必须提供一个将身份证书连接到受信任存储中的证书的链,否则 TLS 握手将会失败。 此外,如果 CertificateVerify 消息处理失败,TLS 握手也会失败。

CertificateVerify 方法采用的签名方法在 TLS 1.0 和 1.1 版中是固定的,在 TLS 1.2 版中可由 TLS 服务器指定。 对于 TLS 1.2,支持的签名方法一般遵循加密方法表中提供的相关方法,但 SHA-256 通常采用 RSA 签名(请参阅“NetX Secure TLS 中的加密”部分,了解有关使用加密方法初始化 TLS 的详细信息)。

NetX Secure TLS 中的加密

TLS 定义了一种协议,在该协议中,可以使用加密来保护网络通信的安全。 因此,TLS 可方便用户广泛使用实际加密。 该规范仅要求实现单个密码套件:在 TLS 1.2 中,该密码套件为 TLS_RSA_WITH_AES_128_CBC_SHA,表示针对公钥操作使用 RSA,针对会话加密使用 CBC 模式下的 AES(带有 128 位密钥),针对消息身份验证哈希使用 SHA-1。

由于符合 TLS 1.2 标准,NetX Secure 会默认启用强制 TLS_RSA_WITH_AES_128_CBC_SHA 密码套件,但考虑到硬件能力和其他因素,每种加密方法可能实现的数量,NetX Secure 提供了一个通用加密 API,允许用户指定在 TLS 中使用哪些加密方法。

注意:通用加密 API 机制还允许用户实现自己的密码套件,但推荐熟悉 TLS 密码套件和扩展的高级用户使用。 如果对支持自己的密码套件感兴趣,请联系 Express Logic 代表。

加密方法

NetX Secure TLS 在软件中实现 DES、3DES、AES、MD5、HMAC-MD5、SHA-1、HMAC-SHA1、SHA-256、HMAC-SHA256、RSA 和 ECC(所选曲线),并为特定硬件平台提供硬件驱动程序。 应用程序可以使用 NetX Secure 中提供的加密例程,也可使用最终用户或第三方提供的自定义例程。

NX_CRYPTO_METHOD 是为应用程序设计的控制块,用于描述要在 NetX Secure TLS 中使用的加密算法的特定实现。 凭借 NX_CRYPTO_METHOD,应用程序可以与将自身的加密实现轻松集成到 NetX Secure 中。 NX_CRYPTO_METHOD 结构声明如下:

typedef struct NX_CRYPTO_METHOD_STRUCT
{
    /* Symbolic name of the algorithm. */
    USHORT nx_crypto_algorithm;

    /* Size of the key, in bits. */
    USHORT nx_crypto_key_size_in_bits;

    /* Size of the IV block, in bits, used for encryption. */
    USHORT nx_crypto_IV_size_in_bits;

    /* Size of the ICV block, in bits, used for authentication. */
    USHORT nx_crypto_ICV_size_in_bits;

    /* Size of the crypto block, in bytes. */
    ULONG nx_crypto_block_size_in_bytes;

    /* Size of the metadata area. */
    ULONG nx_crypto_metadata_size;

    /* nx_crypto_init function initializes the crypto method with the
        "secret key" or other state  information. The initialization 
        routine should return a handle to the caller.  This handle is 
        used in subsequent crypto operations to identify the session.  
        */

    UINT (*nx_crypto_init) (NX_CRYPTO_METHOD     *method,
                            UCHAR               *key, 
                            NX_CRYPTO_KEY_SIZE   key_size_in_bits,
                            VOID               **handler,
                            VOID                *crypto_metadata,
                            VOID                 crypto_metadata_size);

    /* NetX Secure calls the nx_crypto_cleanup routine when a TLS
       session is to be deleted (or updated).  Resources allocated 
       during the crypto operation should be released in this routine.  
       */
    UINT (*nx_crypto_cleanup) (VOID *handler);

    /* nx_crypto_operation is the actual crypto or hash operation. Note 
       that both input and output buffers are prepared by the caller. 
       For encryption or decryption operations, the crypto operation 
       routine uses the output buffer for encrypted or decrypted data. 
       For authentication operations, the authentication routine shall 
       use the output buffer for the digest. */
    UINT (*nx_crypto_operation)(UINT  op, 
                  VOID              *handler, 
                  NX_CRYPTO_METHOD  *method,
                  UCHAR             *key,
                  NX_CRYPTO_KEY_SIZE key_size_in_bits,
                  UCHAR             *input,
                  ULONG              input_length_in_byte,
                  UCHAR             *iv_ptr,
                  UCHAR             *output,
                  ULONG              output_length_in_byte,
                  VOID              *crypto_metadata,
                  VOID               crypto_metadata_size,
                  NX_PACKET*         packet_ptr,
                  VOID (*nx_crypto_hw_process_callback(NX_PACKET 
                                                       *packet_ptr, 
                                                        UINT status);
} NX_CRYPTO_METHOD;

下面介绍 NX_CRYPTO_METHOD 结构中的各个元素:

  • nx_crypto_algorithm:此字段标识变量方法中所述的算法。NETX Secure TLS 的部分有效值如下(有关特定值,请参考 nx_crypto_const.h):

    • NX_CRYPTO_NONE
    • NX_CRYPTO_ENCRYPTION_NULL
    • NX_CRYPTO_ENCRYPTION_AES_CBC
    • NX_CRYPTO_AUTHENTICATION_NONE
    • TLS_HASH_SHA_1
    • TLS_HASH_SHA_256
    • TLS_HASH_MD5
    • TLS_CIPHER_RSA
    • TLS_CIPHER_NULL
  • nx_crypto_key_size_in_bits:此字段指定方法使用的密钥大小。

  • nx_crypto_IV_size_in_bits:此字段指定初始化向量 (IV) 大小。 请注意,在大多数情况下,IV 块仅用于加密/解密算法。 身份验证和验证算法很少使用此字段。

  • nx_crypto_ICV_size_in_bits:此字段指定完整性检查值 (ICV) 块的大小。 注意:此块用于 IPsec,不会在 TLS 中使用。 有关详细信息,请参阅 NetX Duo IPsec。

  • nx_crypto_block_size_in_bytes:此字段为基于块的密码指定加密算法块大小(以字节为单位)。 在大多数情况下由加密例程使用,身份验证例程很少使用。

  • nx_crypto_metadata_area_size:此字段指定此方法所需的元数据区域大小。 每个实现都可能需要一些内存来存储其状态信息,或存储中间数据(例如密钥转换材料),或用作草稿区。 此字段中指定实现所需的空间量。 创建 TLS 会话时,应用程序会提供内存空间。 加密函数负责管理此元数据区域。

  • nx_crypto_init:这是加密算法的初始化函数。 对于不需要初始化例程的实现,此字段可设置为 NX_NULL。 初始化函数的典型用法是初始化算法的内部数据结构。 NetX Secure TLS 通过在内部调用此函数来处理加密例程初始化。

初始化函数的原型是:

UINT crypto_init_function(NX_CRYPTO_METHOD *method, 
                          UCHAR *key, 
                          UINT  key_size_in_bits, 
                          VOID  **handle, 
                          VOID  *crypto_metadata_area, 
                          ULONG crypto_metadata_area_size);
  • method 是指向加密方法控制块的指针。

  • key 是用于处理数据包的密钥字符串。

  • key_size_in_bits 定义密钥大小(以位为单位)。

  • handle 是一个实现定义的项,可标识特定的加密会话。 该值由初始化例程生成,并被传递回调用方。 后续的加密操作或清理例程使用此句柄来标识会话。

  • crypto_metadata 是指向实现此算法所需的元数据区域的指针。 对于不需要元数据区域的算法,此字段设置为“NX_NULL”,并且初始化例程不得访问元数据区域。

  • crypto_metadata_size 指定元数据区域大小。 对于在没有元数据区域的情况下创建的 SA,此字段设置为零,并且初始化例程不得访问元数据区域。

  • 如果初始化过程成功,则此例程应返回 NX_SUCCESS。 调用方将任何其他返回值视为失败。

  • nx_crypto_cleanup:这是为加密算法实现定义的清理例程。 在删除或重启 TLS 会话时调用。

清理函数的原型为:

UINT crypto_cleanup_function(VOID *handle);
  • 调用方将句柄传递给清理函数。 句柄由加密初始化例程初始化并用于标识加密算法状态。

  • 如果清理过程成功,则此例程应返回 NX_SUCCESS。 调用方将任何其他返回值视为失败。

  • nx_crypto_operation:这是执行实际加密、解密和身份验证服务的例程。 操作例程的函数原型为:

UINT crypto_operation_function(UINT   op,
          VOID  *handle,  
          NX_CRYPTO_METHOD* method,
          UCHAR *key,
          UCHAR  key_size_in_bits,
          UCHAR* input,
          ULONG  input_length_in_byte,
          UCHAR* iv_ptr,
          UCHAR* output,
          ULONG  output_length_in_byte,
          VOID *crypto_metadata,
          ULONG crypto_metadata_size,
          NX_PACKET *packet_ptr,
          VOID (*nx_crypto_hw_process_callback)(NX_PACKET 
                          *packet_ptr, UINT status));
  • op 标识此例程应执行的操作类型。有效值为:

    • NX_CRYPTO_ENCRYPT
    • NX_CRYPTO_DECRYPT
    • NX_CRYPTO_AUTHENTICATE
    • NX_CRYPTO_VERIFY
  • 句柄由调用方传递给操作函数。 由加密初始化例程生成。

  • method 是指加密方法控制块

  • key 是指用于此操作的密钥

  • key_size_in_bits 是指密钥大小(以位为单位)

  • input 是指向要操作的消息开头的指针。

  • input_length_in_byte 由调用方传递,标识要操作的消息大小。

  • iv_ptr 由调用方设置,表示 IV 块的开头。 请注意,IV 块的内存由调用方提供。 加密时,操作函数应将 IV 信息写入此内存块;解密时,操作函数应从此内存块中检索 IV 信息。 身份验证和验证操作算法通常都不会使用初始化向量。

  • output 由调用方设置,表示输出缓冲区。 请注意,输出缓冲区的内存由调用方提供。 加密时,操作函数应将密码文本写入到输出缓冲区;解密时,操作函数将解密文本(明文)写入输出缓冲区;进行身份验证时,应将哈希值写入输出缓冲区。 验证时,输出缓冲区用于存储哈希信息。

  • output_length_in_byte 表示输出缓冲区大小

  • crypto_metadata 表示此加密操作要使用的元数据区域。 加密元数据区域通常由 crypto_init_function 初始化。

  • crypto_metadata_size 表示元数据区域大小。

  • 如果操作过程成功,则此例程应返回 NX_SUCCESS。 调用方将任何其他返回值视为失败。

  • packet_ptr:包含正在处理的数据的数据包。 注意:TLS 不使用此参数,应将参数设置为 NX_NULL。

  • nx_crypto_hw_process_callback:加密方法提供的回叫函数。 如果加密函数由硬件提供且需要回叫例程,则使用此方法。

NetX Secure TLS 提供以下加密方法:

  • AES
  • RSA
  • NULL

NetX Secure TLS 提供以下身份验证方法:

  • HMAC-MD5
  • HMAC-SHA1
  • HMAC-SHA256

以下示例演示如何将 NX_CRYPTO_METHOD 结构配置为使用 NetX Duo IPsec 提供的加密和身份验证方法。

AES:

/* AES-CBC 128. */
NX_CRYPTO_METHOD crypto_method_aes_cbc_128 = 
{
    /* AES crypto algorithm                             */
    NX_CRYPTO_ENCRYPTION_AES_CBC,                       

    /* Key size in bits. For AES-128 this value is 128  */
    NX_CRYPTO_AES_128_KEY_LEN_IN_BITS,              
   
    /* IV size in bits.  For AES-128 this value is 128  */
    NX_CRYPTO_AES_IV_LEN_IN_BITS,

    /* ICV size in bits, not used.                      */
    0,                                              

    /* Block size in bytes.  For AES this value is 16   */
    (NX_CRYPTO_AES_BLOCK_SIZE_IN_BITS >> 3),        

    /* Metadata size in bytes, for AES this value is 262*/
    sizeof(NX_CRYPTO_AES),              

    /* AES-CBC initialization routine.                  */
    _nx_secure_crypto_method_aes_init,               

    /* AES-CBC cleanup routine, not used.               */
    NX_NULL,                                        

    /* AES-CBC operation                                */
    _nx_secure_crypto_method_aes_operation           
};

/* RSA. */
NX_CRYPTO_METHOD crypto_method_rsa = 
{
    /* RSA crypto algorithm                             */
    TLS_CIPHER_RSA,                       

    /* Key size. RSA key sizes vary, so set to 0.         */
    0,              
   
    /* IV size in bits.  RSA does not use an IV.         */
    0,

    /* ICV size in bits, not used.                      */
    0,                                              

    /* Block size in bytes.  RSA does not have a block size. */
    0,        

    /* Metadata size in bytes, for RSA use the control block. */
    sizeof(NX_CRYPTO_RSA),              

    /* RSA initialization routine.                  */
    _nx_secure_crypto_method_rsa_init,               

    /* Cleanup routine, not used.                    */
    NX_NULL,                                        

    /* RSA operation                                */
    _nx_secure_crypto_method_rsa_operation           

};

NULL

/* NULL encryption method. */
NX_CRYPTO_METHOD crypto_method_null = 
{
    NX_CRYPTO_ENCRYPTION_NULL,/* Name of the crypto algorithm  */
    0,                        /* Key size in bits, not used    */
    0,                        /* IV size in bits, not used     */
    0,                        /* ICV size in bits, not used    */
    4,                        /* Block size in bytes           */
    0,                        /* Metadata size in bytes        */
    NX_NULL,                  /* Initialization routine,unused */
    NX_NULL,                  /* Cleanup routine, not used     */
    _nx_secure_crypto_method_null_operation  /* NULL operation  
*/
}; 

HMAC-SHA1

NX_CRYPTO_METHOD crypto_method_hmac_sha1 = 
{
    /* HMAC SHA1 algorithm                               */
    TLS_HASH_SHA1,            


    /* Key size in bits. For HMAC-SHA1 this value is 160 */ 
    NX_CRYPTO_HMAC_SHA1_KEY_LEN_IN_BITS,              

    /* IV size in bits, not used                         */
    0,                                            

    /* Transmitted ICV size in bits. Unused.             */
    0, 

    /* Block size in bytes, not used                     */
    0,                                            

    /* Metadata size in bytes                            */
    sizeof(NX_SHA1_HMAC),                                            

    /* Initialization routine, not used                  */
    NX_NULL,                                      

    /* Cleanup routine, not used                         */
    NX_NULL,                                          

    /* HMAC SHA1 operation                               */
    _nx_secure_crypto_method_hmac_sha1_operation   
};

NONE

特殊方法 NX_CRYPTO_NONE 用于向 IPsec 模块发出信号,指示不需要加密或身份验证服务。 配置如下:

/* NX_CRYPTO_NONE means encryption or authentication
   method is not needed.  */
NX_CRYPTO_METHOD crypto_method_none = 
{
    NX_CRYPTO_NONE,       /* Name of the crypto algorithm */
    0,                    /* Key size in bits, not used   */
    0,                    /* IV size in bits, not used    */
    0,                    /* ICV size in bits, not used   */
    0,                    /* Block size in bytes          */
    0,                    /* Metadata size in bytes       */
    NX_NULL,              /* Initialization routine, not used */
    NX_NULL,              /* Cleanup routine, not used    */
    NX_NULL               /* NULL operation               */
};                                               

用加密方法初始化 TLS

创建符合上一部分所述的加密方法签名的加密例程后,就需要在初始化 NX_SECURE_TLS_SESSION 控制块时将其传递到 TLS。 在 TLS 服务 nx_secure_tls_session_create 中完成此操作:

UINT  nx_secure_tls_session_create(
              NX_SECURE_TLS_SESSION*     session_ptr,
              const NX_SECURE_TLS_CRYPTO*    tls_cipher_table,
              VOID*                encryption_metadata_area,
              ULONG                 encryption_metadata_size
);
  • session_pointer 是指向 NX_SECURE_TLS_SESSION 控制块的指针。
  • tls_cipher_table 是指向 NX_SECURE_TLS_CRYPTO 控制块的指针,如下所述。
  • encryption_metadata_area 指向 TLS 中加密例程所使用的空间。
  • encryption_metadata_size 是指元数据区域大小(以字节为单位)。

NetX Secure TLS 中的椭圆曲线加密 (ECC)

椭圆曲线加密 (ECC) 提供可取代 RSA 的公钥加密方案。 相比 RSA,ECC 的速度通常更快,使用的密钥更少,因此对于嵌入 TLS 非常有用。 在 Azure RTOS 6.0 之前的 X-Ware 版本中,ECC 作为附加产品提供,需在项目中安装 ECC 源代码。 Azure RTOS 6.0 将 ECC 集成到主线代码库中,因此无需再安装 ECC 文件。 但是,ECC 仍需要初始化,与以前版本相同。

支持的 ECC 曲线

NetX Secure 根据 http://www.secg.org/sec2-v2.pdf 实现部分曲线。 支持以下曲线18

  • secp256r1
  • secp384r1
  • secp521r1

如果使用其他 ECC 曲线,则 nx_secure_tls_session_start() 例程将返回错误,NX_SECURE_TLS_NO_SUPPORTED_CIPHERS 指示使用了不支持的曲线。

请注意,TLS 证书链也可以通过 ECC 算法进行加密。 即使 TLS 客户端提供的曲线受支持,也可能不支持证书链中使用的 ECC 曲线。 在这种情况下,nx_secure_tls_session_start 例程返回 NX_SECURE_TLS_UNSUPPORTED_PUBLIC_CIPHER。

nx_crypto_generic_ciphersuites.c 中提供了 ECC 的默认密码套件表示例。 有关密码套件表的详细信息,请参阅“TLS 加密密码表”部分。

  1. 请注意,还为旧应用程序提供了曲线 secp192r1 和 secp224r1are 的实现。 不过,这些曲线现在被视为弱曲线,不得用于新应用程序开发。

ECC 加密方法

椭圆曲线组的加密方法:

  • NX_CRYPTO_METHOD crypto_method_ec_secp19215
  • NX_CRYPTO_METHOD crypto_method_ec_secp22415
  • NX_CRYPTO_METHOD crypto_method_ec_secp256;
  • NX_CRYPTO_METHOD crypto_method_ec_secp384;
  • NX_CRYPTO_METHOD crypto_method_ec_secp521;

nx_crypto_generic_ciphersuites 中定义了 ECC 曲线的加密方法。

ECDHE 加密方法:

  • NX_CRYPTO_METHOD crypto_method_ecdhe;

ECDSA 加密方法:

  • NX_CRYPTO_METHOD crypto_method_ecdsa;

nx_crypto_generic_ciphersuites 中定义了 ECDSA 和 ECDHE 加密方法。

与其他加密方法(如 RSA、SHA、AES)结合使用时,ECDSA 和 ECDHE 可用作密码套件查找表的构建基块。

为 TLS 启用 ECC 支持

默认情况下已为 TLS 启用 ECC。 要禁用 ECC 支持,必须定义 NX_SECURE_DISABLE_ECC_CIPHERSUITE 符号。

要使更改生效,需要重新生成 NetX Secure 库和使用该库的所有应用程序。

在应用程序代码中,必须在创建 TLS 会话后调用 API nx_secure_tls_ecc_initialize()。 此 API 向 TLS 会话通知要用于 TLS 密钥交换操作和证书验证的曲线类型。 在 TLS 握手阶段,如果选择了 ECC 算法,客户端和服务器将交换与 ECC 曲线相关的参数,从而决定要使用的曲线。

以下代码段演示了如何使用 API: 请注意,参数(nx_crypto_ecc_supported_groups、nx_crypto_ecc_supported_groups_size 和 nx_crypto_ecc_curves)均在 nx_crypto_generic_ciphersuites.c 中定义。 因此,这些符号可以直接使用。

status = nx_secure_tls_ecc_initialize(&tls_session,     
                    nx_crypto_ecc_supported_groups,      
                    nx_crypto_ecc_supported_groups_size,     
                    nx_crypto_ecc_curves);

nx_crypto_generic_ciphersuites.c 中的示例配置包含启用 ECC 时使用的 ECC 密码套件查找表。 要使用 ECC,请在使用 nx_secure_tls_session_create 创建 TLS 会话时将 nx_crypto_tls_ciphers_ecc 作为密码套件表参数传递。 示例表同时包含 ECC 和非 ECC 密码套件。

TLS 加密密码表

NX_SECURE_TLS_CRYPTO 结构定义如下:

typedef struct NX_SECURE_METHODS_STRUCT
{
    /* Table that maps ciphersuites to crypto methods. */
    NX_SECURE_TLS_CIPHERSUITE_INFO* nx_secure_tls_ciphersuite_lookup_table;
    USHORT nx_secure_tls_ciphersuite_lookup_table_size;

    /* Table that maps X.509 cipher identifiers to crypto methods. */
    NX_SECURE_X509_CRYPTO *nx_secure_tls_x509_cipher_table;
    USHORT nx_secure_tls_x509_cipher_table_size;

    /* Specific routines needed for specific TLS versions. */
#if (NX_SECURE_TLS_TLS_1_0_ENABLED || NX_SECURE_TLS_TLS_1_1_ENABLED)
    NX_CRYPTO_METHOD *nx_secure_tls_handshake_hash_md5_method;
    NX_CRYPTO_METHOD *nx_secure_tls_handshake_hash_sha1_method;
    NX_CRYPTO_METHOD *nx_secure_tls_prf_1_method;
#endif

#if (NX_SECURE_TLS_TLS_1_2_ENABLED)
    NX_CRYPTO_METHOD *nx_secure_tls_handshake_hash_sha256_method;
    NX_CRYPTO_METHOD *nx_secure_tls_prf_sha256_method;
#endif

#if (NX_SECURE_TLS_TLS_1_3_ENABLED)
    const NX_CRYPTO_METHOD *nx_secure_tls_hkdf_method;
    const NX_CRYPTO_METHOD *nx_secure_tls_hmac_method;
    const NX_CRYPTO_METHOD *nx_secure_tls_ecdhe_method;
#endif

} NX_SECURE_TLS_CRYPTO;

通过在 NetX Secure TLS 项目的静态常量中填充此结构的条目来创建表,通常位于加密例程和模块中。

例如,使用 NetX Secure 提供的仅软件(“通用”)加密库包含以下表定义(用于非 ECC 密码套件支持19):

/* Define the cipher table object we can pass into TLS. */
const NX_SECURE_TLS_CRYPTO nx_crypto_tls_ciphers =
{
    /* TLS Ciphersuite lookup table and size. */
    _nx_crypto_ciphersuite_lookup_table,
    sizeof(_nx_crypto_ciphersuite_lookup_table) / 
    sizeof(NX_SECURE_TLS_CIPHERSUITE_INFO),

    /* X.509 certificate cipher table and size. */
    _nx_crypto_x509_cipher_lookup_table,
    sizeof(_nx_crypto_x509_cipher_lookup_table) / sizeof(NX_SECURE_X509_CRYPTO),

    /* TLS version-specific methods. */
#if (NX_SECURE_TLS_TLS_1_0_ENABLED || NX_SECURE_TLS_TLS_1_1_ENABLED)
    &crypto_method_md5,
    &crypto_method_sha1,
    &crypto_method_tls_prf_1,
#endif

#if (NX_SECURE_TLS_TLS_1_2_ENABLED)
    &crypto_method_sha256,
    &crypto_method_tls_prf_sha_256
#endif

#if (NX_SECURE_TLS_TLS_1_3_ENABLED)
    &crypto_method_hkdf,
    &crypto_method_hmac,
    &crypto_method_ecdhe,
#endif
};

在该结构中,第一个条目是 TLS 密码套件表。 NX_SECURE_TLS_CIPHERSUITE_INFO 结构将加密例程(以 NX_CRYPTO_METHOD 指针的形式)映射到特定密码套件,如 TLS 规范定义。 第二个值是第一个字段指向表中的条目数。

下一个字段指向 X.509 在处理数字证书时使用的例程表,NX_SECURE_X509_CRYPTO 结构的形式类似 NX_SECURE_TLS_CIPHERSUITE_INFO。 以下字段是指表中的条目数。

查找表后面是特定 TLS 版本所需的几个例程。 例如,在 TLS 1.2 版之前,密钥生成和握手哈希例程都固定使用 SHA-1 和 MD5 的组合 - 由于这些例程未与特定密码套件绑定,因此会在密码结构中专门调用这些例程的方法。 在 TLS 1.2 版中,密钥生成和哈希例程由密码套件选择,但对于没有指定使用例程的密码套件,则使用 SHA-256 哈希方法,密码结构会特别调用该例程。

TLS 1.3 需要一些额外的特定密码才能进行各种操作。

  1. 请注意,TLS 1.3 支持需要 ECC – 如果已启用 TLS 1.3,则使用 nx_crypto_tls_ciphers_ecc。

TLS 密码套件查找表

要填写 TLS 的密码表,还需要创建一个密码套件查找表,将加密例程映射到特定密码套件标识符。 标识符是通用的 IANA 注册值。 有关详细信息,请参阅 TLS RFC。 例程表示每个密码套件中使用的 5 种单独方法(一些密码套件可能不会使用全部 5 种方法):公共密码、公钥身份验证、会话密码、会话哈希例程和 TLS 伪随机函数 (PRF)。 下表分别介绍这 5 种方法:

例程类别 说明 示例算法
公共密码 用于在 TLS 握手期间交换密钥 RSA、Diffie-hellman、ECC
公钥身份验证 用于在 TLS 握手期间对数据进行身份验证或签名 RSA、DSS
会话密码 用于在 TLS 会话期间对应用程序数据进行加密的对称密钥算法 AES, RC4
会话哈希 用于在 TLS 会话期间保留消息的完整性(确保数据未更改) SHA-1、SHA-256
TLS PRF 用于在 TLS 握手中生成密钥材料和握手哈希 PRF 基于哈希例程 – SHA-1 + MD5、SHA-256、SHA-512

NX_SECURE_TLS_CIPHERSUITE_INFO 结构定义如下:

typedef struct NX_SECURE_TLS_CIPHERSUITE_INFO_struct
{
    /* The IANA value of the ciphersuite as defined by the TLS spec.*/
    USHORT nx_secure_tls_ciphersuite;

    /* The Public Key operation in this suite - RSA or DH. */
    NX_CRYPTO_METHOD *nx_secure_tls_public_cipher;

    /* The Public Authentication method used for signing data. */
    NX_CRYPTO_METHOD *nx_secure_tls_public_auth;

    /* The session cipher being used - AES, RC4, etc. */
    NX_CRYPTO_METHOD *nx_secure_tls_session_cipher;

    /* The size of the initialization vectors for the session cipher (bytes).*/
    USHORT nx_secure_tls_iv_size;

    /* The key size for the session cipher (bytes). */
    UCHAR nx_secure_tls_session_key_size;

    /* The hash being used - MD5, SHA-1, SHA-256, etc. */
    NX_CRYPTO_METHOD *nx_secure_tls_hash;

    /* The size of the hash being used. SHA-1 is 20 bytes, MD5 is 16 bytes.*/
    USHORT nx_secure_tls_hash_size;

    /* The TLS PRF being used – this is only for TLSv1.2. */
    NX_CRYPTO_METHOD *nx_secure_tls_prf;

} NX_SECURE_TLS_CIPHERSUITE_INFO;

nx_secure_tls_ciphersuite 字段包含 IANA 密码套件值,NX_CRYPTO_METHOD 指针表示该密码套件使用的 5 种方法。 标量值(nx_secure_tls_iv_size、nx_secure_tls_key_size 和 nx_secure_tls_hash_size)可参考,提供了 NX_CRYPTO_METHOD 条目中可能没有的信息。

例如,我们将查看 TLS 的默认密码套件 TLS_RSA_WITH_AES_128_CBC_SHA,其中指定使用 RSA、使用 128 位密钥的 AES CBC 和 SHA-1 进行会话哈希处理。 未对此密码套件指定 TLS PRF,因此在 TLSv1.2 模式下将默认使用 SHA-256 PRF。 请注意,无论表中指定哪种 PRF,TLS 1.0 和 1.1 中的所有密码套件均使用 SHA-1+MD5 PRF。

通用密码库中,NX_SECURE_TLS_CIPHERSUITE_INFO 表的条目定义如下:

{ 
  TLS_RSA_WITH_AES_128_CBC_SHA,     /* Ciphersuite identifier */
  &crypto_method_rsa,               /* Public-key cipher (NX_CRYPTO_METHOD)*/
  &crypto_method_rsa,               /* Authentication method(NX_CRYPTO_METHOD)*/
  &crypto_method_aes_cbc_128,       /* Session cipher method(NX_CRYPTO_METHOD)*/
  16,                               /* Session cipher IV size in bytes */
  16,                               /* Session cipher key size in bytes */
  &crypto_method_hmac_sha1,         /* Session hash routine(NX_CRYPTO_METHOD) */
  20,                               /* Session hash output size in bytes */
  &crypto_method_tls_prf_sha_256    /* TLSv1.2 PRF */
},

请注意,对于会话密码,密钥大小由密码套件确定,但对于公钥方法,密钥大小在 TLS 握手之前都未知,因为公钥包含在握手期间交换的数字证书中。

X.509 密码查找表

与 NX_SECURE_TLS_CIPHERSUITE_INFO 表一样,NX_SECURE_X509_CRYPTO 结构会将加密例程映射到已知值。 对于 X.509,标识符实际上是由 X.509 定义的 OID,并已向 ISO 和 ITU 标准组织注册。 OID 是可变长度的多字节值,旨在唯一标识各种电信标准中的各种信息,包括数字证书中使用的加密例程。 由于 OID 的长度可变,NetX Secure TLS 会将正式 OID 值映射到内部使用的固定长度常量(请参阅 nx_secure_x509.h)。 这些常量在 NX_SECURE_X509_CRYPTO 结构中使用,定义如下:

/* Structure to hold X.509 cryptographic routine information. */
typedef struct NX_SECURE_X509_CRYPTO_struct
{
    /* Internal NetX Secure identifier for certificate "ciphersuite" which consists
       of a hash and a public key operation. These can be mapped to OIDs in X.509.
        */
    USHORT nx_secure_x509_crypto_identifier;

    /* Public-Key Cryptographic method used by certificates. */
    NX_CRYPTO_METHOD *nx_secure_x509_public_cipher_method;

    /* Hash method used by certificates. */
    NX_CRYPTO_METHOD *nx_secure_x509_hash_method;
} NX_SECURE_X509_CRYPTO;

第一个字段 nx_secure_x509_crypto_identifier 是 NetX Secure 使用的内部 OID 表现形式。

第二个和第三个字段表示 NX_CRYPTO_METHOD 对象,代表由 OID(与哈希例程配对的公钥操作)标识的加密方法。 请注意,每个数字证书可能具有多个用于加密例程的 OID。

X.509 方法表的构造方式与密码套件查找表相同。 以 RSA_SHA1 的 OID 为例。 RSA_SHA1 的实际 OID 如下所示:

{iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) sha1-with-rsa-
signature(5)}

OID 用 ASN.1 语法表示,其数字值为 1.2.840.113549.1.1.5。 然后,以二进制格式编码该值,并创建以下字节:

UCHAR RSA_SHA1_OID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 };

从 ASN.1 到二进制格式的实际转换不属于本文档范围。 有关详细信息,请搜索 ASN.1 OID 编码。 可在 nx_secure_x509 文件中找到 NetX Secure 支持的 OID 的二进制表示形式。

将实际 OID 映射到内部识别的常量后,可以在 NX_SECURE_X509_CRYPTO 表中为 RSA_SHA1 创建条目:

{ 
    NX_SECURE_TLS_X509_TYPE_RSA_SHA_1,    /* Internal OID constant. */
    &crypto_method_rsa,                   /* RSA method (NX_CRYPTO_METHOD). */ 
    &crypto_method_sha1                   /* SHA-1 method (NX_CRYPTO_METHOD). */
}, 

默认 TLS 例程

如上所述,TLS 在握手期间需要一些默认例程,用于密钥生成和消息验证。 主例程是 TLS 伪随机函数 (PRF)。 PRF 基于哈希例程,可用来生成任意数量的伪随机数据20,以用作密钥生成或其他目的。

除了 PRF 以外,每个 TLS 版本都会用到默认哈希例程,同样也需要提供。 在 TLS 1.0 和 1.1 版中,这些哈希例程为 MD5 和 SHA-1。 TLS 1.2 版只需要 SHA-256。

NX_SECURE_TLS_CRYPTO 结构具有用于 MD5、SHA-1、SHA-256、TLS 1.0/1.1 版和默认 TLS 1.2 PRF 的 NX_CRYPTO_METHOD 指针。

TLS 1.3 支持为 HKDF(密钥生成)、HMAC(用于在握手期间使用的特定哈希运算)和 ECDHE(TLS 1.3 功能所需)添加字段。

一般软件加密库中提供 TLS PRF 的软件版本。 在 TLS 1.0/1.1 中,此函数称为 nx_crypto_tls_prf_1。 在 TLS 1.2 中,此函数称为 nx_secure_tls_prf_sha256。 后缀“1”表示旧版 TLS 1.0 PRF,“sha256”后缀表示 TLS 1.2 默认 PRF 基于 SHA-256。 如果需要支持其他 PRF 例程,则这些例程的后缀将体现所用的哈希方法。 由于 PRF 例程基于哈希方法,因此可能会在不同的目标平台上独立地对基础哈希例程进行硬件加速。

除了 TLS 密码套件和 X.509 查找表,还可以填充 NX_SECURE_TLS_CRYPTO 结构中的填入的默认 PRF 和哈希例程,并将其用于初始化 TLS 会话。

  1. “伪随机”是指 PRF 具有确定性,表示在给定相同输入的情况下,PRF 始终生成相同的输出内容,但当输出不可预测时就会出现随机结果。 TLS 利用 PRF 的这一特性从各种公共数据生成会话密钥,还有在握手期间使用公钥密码(如 RSA)交换的主密钥。

加密元数据

用 NX_SECURE_TLS_CRYPTO 表初始化 TLS 会话之前,我们需要为加密例程元数据分配缓冲区空间。 元数据用于存储与特定例程关联的所有状态,状态由其控制块表示。 必须将每个 NX_CRYPTO_METHOD 的 nx_crypto_metadata_area_size 字段设置为与该例程关联的控制结构的大小,否则 TLS 初始化将无法正确考虑所需空间,可能导致缓冲区溢出问题。

创建 TLS 会话之前,必须分配元数据缓冲区。 缓冲区由 nx_secure_tls_session_create 自动划分,并为加密方法表中提供的每个例程保留空间。 请注意,由于 TLS 会话中每次只有一个密码套件处于活动状态,因此支持的密码套件数量并不影响所需的元数据空间 – 按密码套件查找表中为该类别的指定的最大控制块大小为 5 个密码套件例程分别保留空间。

为了方便计算元数据缓冲区大小,nx_secure_metadata_size_calculate 服务执行与 nx_secure_tls_session_create 相同的计算,但只返回所需的元数据缓冲区总大小(以字节为单位)。

初始化 TLS 会话

创建 NX_CRYPTO_METHOD 和 NX_SECURE_TLS_CRYPTO 对象并保留了元数据区域,就可以按以下示例中所示(值来初始化 TLS 会话):

/* Pointer to the platform-specific cipher table. */
extern nx_crypto_tls_ciphers;

/* Cryptographic routine metadata buffer. Size is determined by calling 
nx_secure_tls_metadata_size_calculate with the nx_crypto_tls_ciphers table referenced 
above. */
UCHAR crypto_metadata[4500];

/* Initialize our TLS session using our cipher table and metadata area. Note that we can 
use sizeof for the metadata array because the size parameter expects the size in bytes.*/

nx_secure_tls_session_create(
    &tls_session,            /* Pointer to TLS session.      */
    &nx_crypto_tls_ciphers,  /* Pointer to cipher table.     */
    crypto_metadata,         /* Cryptography metadata buffer.*/
    sizeof(crypto_metadata), /* Size of metadata buffer.     */
);