计算机网络 - mbed TLS
计算机网络 - mbed TLS
背景
SSL/TLS 是什么?
传输层安全性协议(英语:Transport Layer Security,缩写作TLS),及其前身安全套接层(Secure Sockets Layer,缩写作SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。
- 安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性。
- 该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。
TLS协议主要解决如下三个网络安全问题:
- 保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;
- 完整性(message integrity),通过MAC校验机制,一旦被篡改,通信双方会立刻发现;
- 认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;
TLS 协议是可选的,必须配置客户端和服务器才能使用。主要有两种方式实现这一目标:一个是使用统一的 TLS 协议通信端口(例如:用于 HTTPS 的端口 443);另一个是客户端请求服务器连接到 TLS 时使用特定的协议机制(例如:邮件、新闻协议和 STARTTLS)。一旦客户端和服务器都同意使用 TLS 协议,他们通过使用一个握手过程协商出一个有状态的连接以传输数据。通过握手,客户端和服务器协商各种参数用于创建安全连接:
- 当客户端连接到支持 TLS 协议的服务器要求创建安全连接并列出了受支持的密码组合(加密密码算法和加密哈希函数),握手开始。
- 服务器从该列表中决定加密和散列函数,并通知客户端。
- 服务器发回其数字证书,此证书通常包含服务器的名称、受信任的证书颁发机构(CA)和服务器的公钥。
- 客户端确认其颁发的证书的有效性。
- 为了生成会话密钥用于安全连接,客户端使用服务器的公钥加密随机生成的密钥,并将其发送到服务器,只有服务器才能使用自己的私钥解密。
- 利用随机数,双方生成用于加密和解密的对称密钥。这就是 TLS 协议的握手,握手完毕后的连接是安全的,直到连接(被)关闭。如果上述任何一个步骤失败,TLS 握手过程就会失败,并且断开所有的连接。
为什么用 mbed TLS?
在SSL/TLS出现之前,很多应用层协议(http、ftp、smtp等)都存在着网络安全问题,例如大家所熟知的 http 协议,在传输过程中使用的是明文信息,传输报文一旦被截获便会泄露传输内容;传输过程中报文如果被篡改,无法轻易发现;无法保证消息交换的对端身份的可靠性。为了解决此类问题,人们在应用层和传输层之间加入了 SSL/TLS 协议。
其他 SSL 库可能很难使用,OpenSSL一直以来各种被诟病,mbed TLS/SSL 库是为了你的方便而设计的,是替代 OpenSSL 的一个很好的选择。
为了执行 SSL 或 TLS 协议,SSL 库需要提供一些附加功能。
- SSL 库需要执行对称加密操作,例如 AES,来加密连接上的数据。
- SSL 库使用非对称加密操作,如 RSA,来识别和验证链接的各方。
- SSL 库使用报文摘要操作,例如 SHA-256 哈希算法来保护通过线路发送的信息完整性。
- 另外,SSL 库需要能够解析 理解和使用 X.509 证书。
- 最后,SSL 库必须执行网络操作来发送和接收协议包。
所有这些对大多数用户来说都是不可见的,并且被封装在一个 SSL 库,如 mbed TLS中,开发人员可以使用它来在其应用程序中实现 SSL 或 TLS。
概述
ARM® mbed™ 使开发人员可以非常轻松地在其(嵌入式)产品中加入加密和 SSL/TLS 功能,并通过最大限度地减少代码占用空间来推动这一功能。它提供了具有直观的 API 和可读源代码的 SSL 库,并包含精心设计的测试套件。该工具即开即用,您可以在大部分系统上直接构建它,也可以手动选择和配置各项功能。
mbed TLS 库的设计是为了方便地与现有(嵌入式)应用集成,并提供安全通信,加密和密钥管理功能。 本教程将帮助您了解要执行此操作时要执行的步骤。
mbed TLS 库被设计为尽可能降低耦合,允许您只集成所需的部件,而不会产生其余部分的开销。 这也导致 mbed TLS 库的内存占用非常低并且构建足迹。通过消除系统中不需要的功能部分,您可以将构建尺寸从低至 45 kB 降低到更典型的 300 kB,从而实现更全面的设置。
mbed TLS 采用易于移植的 C 语言设计,以嵌入式环境为主要目标,可在ARM,AVR等嵌入式平台上运行,适用于PC,iPad,iPhone甚至XBox。 请告诉我们您在其他平台上的体验!
mbed TLS 库提供了一组可单独使用和编译的加密组件,您还可以使用单个配置头文件加入或排除这些组件。mbed TLS 还提供了构建于加密组件上的中央 SSL/TLS 模块,以及为 SSL 和 TLS 提供完整协议实施的抽象层和支持组件。
从功能角度来看,该库分为三个主要部分:
- SSL/TLS 协议实施。
- 一个加密库。
- 一个 X.509 证书处理库。
SSL/TLS 客户端和服务器
mbed TLS 为当前所有的 SSL 和 TLS 标准提供客户端和服务器端支持,这些标准包括:SSL 3 版以及 TLS 1.0 版、1.1 版和 1.2 版。当然还支持大多数标准化协议扩展,如服务器名称指示 (SNI)、会话票证和安全重审。
mbed TLS 实现支持主要的密钥交换方式和 100 多种不同的标准化密码组。
加密库
mbed TLS 的加密部分拥有针对公钥加密、散列算法(消息摘要)和对称加密算法的抽象层。另外,它还包含多个基于标准的随机数发生器和一个熵池。
所有的加密算法都作为松散耦合的模块执行。您可以直接根据需要选取相应的头文件和源代码文件并将其放入项目中。
对称加密算法
密码抽象层提供了对称加密和解密功能,以实现保密性。它针对不同算法支持不同的块加密模式,从电子密码本 (ECB)、密码块链接 (CBC) 到计数器模式 (CTR)、密码反馈模式 (CFB) 和伽罗瓦计数器模式 (GCM)。
mbed TLS 不仅提供 AES、Blowfish 和 Camellia 等最常用的算法,还提供 DES 和 RC4 等老旧或已弃用的算法。
散列算法
mbed TLS 针对散列算法和消息摘要提供了消息摘要抽象层,可提供单向散列和散列消息认证码 (HMAC)。
mbed TLS 不仅为 SHA-256、SHA-512 和 RIPEMD-160 等最常用的算法提供支持,还支持 MD2、MD4、MD5 和 SHA-1 等老旧或已弃用的算法。
公钥
非对称算法可以搭配传统的 RSA 或椭圆曲线,您可以在此基础上使用公钥抽象层,以确保机密性、完整性、身份验证和不可抵赖性。
密钥交换支持可用于:
- Diffie-Hellman-Merkle 密钥交换协议。
- 椭圆曲线 Diffie-Hellman-Merkle (ECDH) 密钥交换协议。
- 椭圆曲线数字签名算法 (ECDSA)。
随机数发生
针对随机数发生,mbed TLS 提供了熵池和适用于 CTR-DRBG 与 HMAC-DRBG 的特定实现(NIST 标准化随机数发生器)。熵池系统可从标准源和应用程序提供的源收集熵。
X.509 证书处理
SSL/TLS 身份验证和一些其他协议需要支持 X.509 证书处理。X.509 证书可以将身份传达给其他方,但在使用前必须由其他方进行有效性检查。
mbed TLS 包括以下支持:
- X.509 证书 (CRT) 解析。
- X.509 证书吊销列表 (CRL) 解析。
- X.509 (RSA/ECDSA) 私钥解析。
- X.509 证书验证:检查证书的签名链是否由受信任的证书颁发机构签发,以及该证书(或其签名链中的其中一个中间 CA 证书)是否位于其签发 CA 的证书吊销列表中。
另外,还可以执行某些证书颁发机构操作以从头创建证书,如:
- X.509 证书 (CRT) 编写。
- X.509 (RSA/ECDSA) 私钥编写。
- X.509 证书请求 (CSR) 解析。
- X.509 证书请求 (CSR) 编写。
测试
mbed TLS 采用持续集成系统以确保我们尽可能维持最高的代码质量。我们的系统借助一套不断增长的操作系统和芯片组来检查所有提交的代码,检查范围包括:
- 回归测试。
- 测试向量。
- 多个静态分析器。
- 互操作性测试。
- 行为测试。
- 通过模糊测试进行的安全性测试。
- 针对已知值的函数和单元测试。
- 代码覆盖率测试。
- 验证测试。
mbed TLS
堆栈解释(Stack explanation)
本教程的目的是向您展示如何使用 mbed TLS 保护(加密)客户端和服务器之间的通信。 首先展示所涉及的主要组件。
从下往上:
-
硬件(Hardware)
硬件平台提供物理处理器,存储器,内存和网络接口 -
操作系统(Operating System)
操作系统提供以太网驱动程序和标准服务.这取决于操作系统,包括调度,线程安全,和一个完整的网络堆栈. -
网络堆栈(Network Stack)
取决于操作系统,网络堆栈要么完全集成,要么是一个单独的模块,从网络接口提供抽象层.最常用的是 lwIP TCP/IP stack 和 uIP TCP/IP stack. -
mbed TLS SSL/TLS Library
建立在网络接口之上, mbed tls 为安全通信提供了一个抽象层. -
客户端应用(Client Application)
客户端应用程序使用mbed tls 本身抽象安全通信.
将mbed tls 集成到应用程序的确切步骤非常依赖与上面特定组件。在基本教程中,我们将假设一个集成了BSD-like TCP/IP 堆栈的操作系统。
SSL/TLS 说明
mbed tls 的 ssl/tls 部分提供了使用 ssl/tls 通过安全通信通道建立和通信方法.
- 其基本规定是:
- 初始化一个SSL/TLS context
- 执行一个SSL/TLS握手(handshake)
- 发送/接收数据
- 通知对方一个连接正在关闭
- 一个通道很多方面都是通过参数和回调函数来设置的.
- 端点角色(endpoint role),客户端和服务器
- 身份验证模式: 是否应该进行证书验证
- 主机到主机通信通道: 发送和接收功能
- 随机数生成器(RNG)功能
- 用于加密/解密的密码
- 证书验证功能
- 会话控制: 会话获取和设置功能
- 证书处理和密钥交换的X.509参数
mbed TLS可用于通过提供设置一个框架用来创建SSL/TLS服务器和客户端,并且通过SSL/TLS的通信信道进行通信。 SSL/TLS部分直接依赖于库的证书解析,对称和非对称加密以及散列模块。
示例客户端
因此,假设我们有一个简单的网络客户端,它尝试打开与 HTTP 服务器的连接并读取默认页面。 该应用程序可能看起来像这样:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <netdb.h>
#define SERVER_PORT 80
#define SERVER_NAME "localhost"
#define GET_REQUEST "GET / HTTP/1.0\r\n\r\n"
int main( void )
{
int ret, len, server_fd;
unsigned char buf[1024];
struct sockaddr_in server_addr;
struct hostent *server_host;
/*
* Start the connection
*/
printf( "\n . Connecting to tcp/%s/%4d...", SERVER_NAME,
SERVER_PORT );
fflush( stdout );
if( ( server_host = gethostbyname( SERVER_NAME ) ) == NULL )
{
printf( " failed\n ! gethostbyname failed\n\n");
goto exit;
}
if( ( server_fd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP) ) < 0 )
{
printf( " failed\n ! socket returned %d\n\n", server_fd );
goto exit;
}
memcpy( (void *) &server_addr.sin_addr,
(void *) server_host->h_addr,
server_host->h_length );
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( SERVER_PORT );
if( ( ret = connect( server_fd, (struct sockaddr *) &server_addr,
sizeof( server_addr ) ) ) < 0 )
{
printf( " failed\n ! connect returned %d\n\n", ret );
goto exit;
}
printf( " ok\n" );
/*
* Write the GET request
*/
printf( " > Write to server:" );
fflush( stdout );
len = sprintf( (char *) buf, GET_REQUEST );
while( ( ret = write( server_fd, buf, len ) ) <= 0 )
{
if( ret != 0 )
{
printf( " failed\n ! write returned %d\n\n", ret );
goto exit;
}
}
len = ret;
printf( " %d bytes written\n\n%s", len, (char *) buf );
/*
* Read the HTTP response
*/
printf( " < Read from server:" );
fflush( stdout );
do
{
len = sizeof( buf ) - 1;
memset( buf, 0, sizeof( buf ) );
ret = read( server_fd, buf, len );
if( ret <= 0 )
{
printf( "failed\n ! ssl_read returned %d\n\n", ret );
break;
}
len = ret;
printf( " %d bytes read\n\n%s", len, (char *) buf );
}
while( 1 );
exit:
close( server_fd );
#ifdef WIN32
printf( " + Press Enter to exit this program.\n" );
fflush( stdout ); getchar();
#endif
return( ret );
}
一个简单的客户端应用程序:
- 在端口80上打开到服务器的连接
- 为主页面写一个标准的HTTP GET请求
- 读取结果,直到不再发送任何内容
添加安全通信
将 SSL/TLS 添加到应用程序需要进行许多修改。主要修改是 SSL 上下文和结构的设置,配置和拆除。 较小的更改是用于连接服务器,读取和写入数据的网络功能。
设置
由于设置 mbed TLS 需要一个好的随机数生成器及其自己的 SSL 上下文和SSL会话存储。对于随机数生成,mbed TLS 包含 CTR_DRBG 随机数生成器,也在此处使用。
- mbed TLS 所需的头文件:
#include "mbedtls/net.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/debug.h"
- mbed TLS结构的创建和初始化如下所示:
mbedtls_net_context server_fd;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_net_init( &server_fd );
mbedtls_ssl_init( &ssl );
mbedtls_ssl_config_init( &conf );
mbedtls_x509_crt_init( &cacert );
mbedtls_ctr_drbg_init( &ctr_drbg );
mbedtls_entropy_init( &entropy );
if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char *) pers,
strlen( pers ) ) ) != 0 )
{
printf( " failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret );
goto exit;
}
SSL 连接
通常在 TCP/IP 客户端应用程序中,应用程序通过调用 socket()
和 connect()
进行连接。 代码如下
if( ( server_host = gethostbyname( SERVER_NAME ) ) == NULL )
goto exit;
if( ( server_fd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP) ) < 0 )
goto exit;
memcpy( (void *) &server_addr.sin_addr, (void *) server_host->h_addr,
server_host->h_length );
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( SERVER_PORT );
if( ( ret = connect( server_fd, (struct sockaddr *) &server_addr,
sizeof( server_addr ) ) ) < 0 )
goto exit;
mbed TLS 在其网络层(net.c)中抽象出来。 因此,上面代码得到简化。通过 mbed TLS 进行连接的代码如下:
if( ( ret = mbedtls_net_connect( &server_fd, SERVER_NAME,
SERVER_PORT, MBEDTLS_NET_PROTO_TCP ) ) != 0 )
{
printf( " failed\n ! mbedtls_net_connect returned %d\n\n", ret );
goto exit;
}
SSL/TLS 配置
现在,低级套接字连接已经启动并运行,我们应该配置 SSL/TLS 层。
首先通过设置端点和传输类型,并为安全参数加载合理的默认值来准备SSL配置。端点决定 SSL/TLS 层将作为服务器(MBEDTLS_SSL_IS_SERVER)还是客户端 (MBEDTLS_SSL_IS_CLIENT)。传输类型决定我们是使用 (MBEDTLS_SSL_TRANSPORT_STREAM)还是(MBEDTLS_SSL_TRANSPORT_DATAGRAM).
if( ( ret = mbedtls_ssl_config_defaults( &conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ssl_config_defaults returned %d\n\n", ret );
goto exit;
}
认证方式决定检查所颁发的证书的严格程度。对于本教程,我们不检查任何内容。 **警告:**这不是您在完整应用程序中所需的内容。
mbedtls_ssl_conf_authmode( &conf, MBEDTLS_SSL_VERIFY_NONE );
mbed TLS 库需要知道使用哪个随机引擎以及将哪个调试功能作为会回调。
mbedtls_ssl_conf_rng( &conf, mbedtls_ctr_drbg_random, &ctr_drbg );
mbedtls_ssl_conf_dbg( &conf, my_debug, stdout );
为了使调试功能正常,我们需要在 main() 函数添加一个名为 my_debug 的调试回调函数。
static void my_debug( void *ctx, int level,
const char *file, int line, const char *str )
{
((void) level);
fprintf( (FILE *) ctx, "%s:%04d: %s", file, line, str );
fflush( (FILE *) ctx );
}
现在配置已准备就绪,我们可以设置 SSL 上下文来使用它。
if( ( ret = mbedtls_ssl_set_hostname( &ssl, "mbed TLS Server 1" ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret );
goto exit;
}
最后,SSL 上下文需要知道它用于发送网络数据的输入和输出函数。
mbedtls_ssl_set_bio( &ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL );
读写数据
配置好 SSL/TLS 层之后,我们应该实际写入并读取它。
写入到网络层:
while( ( ret = write( server_fd, buf, len ) ) <= 0 )
变成:
while( ( ret = mbedtls_ssl_write( &ssl, buf, len ) ) <= 0 )
从网络层读取:
ret = read( server_fd, buf, len );
变成:
ret = mbedtls_ssl_read( &ssl, buf, len );
**注意:**如果 mbedtls_ssl_read()
和 mbedtls_ssl_write()
返回一个错误,连接必须被关闭.
拆除(Teardown)
在应用程序的出口处,我们应该彻底的关闭 SSL/TLS 连接,并且还应该销毁任何与 SSL/TLS 相关的信息,最后,我们释放分配的资源。
所以
close( server_fd );
变成:
mbedtls_net_free( &server_fd );
mbedtls_ssl_free( &ssl );
mbedtls_ssl_config_free( &conf );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
服务器认证
真正的应用程序应该正确地验证服务器。为此,您需要一组受信任的 CA。如何获取或选择取决于您的用例:要连接到 Web 服务器,您可以使用受信任的浏览器供应商提供的列表; 如果您的客户端是仅连接到您控制的一组服务器的设备,您可能希望成为自己的 CA等。
代码方面,您需要执行以下操作才能验证服务器证书:
mbedtls_x509_crt cacert;
const char *cafile = "/path/to/trusted-ca-list.pem";
mbedtls_x509_crt_init( &cacert );
if( ( ret = mbedtls_x509_crt_parse_file( &cacert, cafile ) ) != 0 )
{
mbedtls_printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret );
goto exit;
}
mbedtls_ssl_conf_ca_chain( &conf, &cacert, NULL );
// remove the following line
// mbedtls_ssl_conf_authmode( &conf, MBEDTLS_SSL_VERIFY_NONE );
结论
将 SERVER_PORT 更改为 443 后,编译此应用程序并将其链接到 mbed TLS 库,我们现在有一个可以通过基本的 HTTPS 与 Web 服务器通信的应用程序。最终代码在库的源代码中以 ssl_client1.c 或在 github 上以 ssl_client1.c 的形式提供。