mbed TLS 简明教程(二)
原文地址
译者:远方的*
转载请注明出处: http://blog.csdn.net/z2066411585
上一篇文章mbed TLS 简明教程(一) 简要描述了mbed TLS. 下面主要通过示例程序来说明mbed 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 contexts and structures.对于连接到服务器,读取和写入数据的网络功能,这些修改是较小的.
设置
安装mbed TLS需要一个好的随机数生成器和它自己的SSL context 和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()调用.mbed TLS通常在网络层(net.c)内抽象出来,因此下面代码被简化了.
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开始实际的连接如下:
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 );
这个库需要知道使用哪个随机引擎以及将哪个调试功能作为会回调.
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 context来使用它.
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 context需要知道它需要用来发送网络流量的输入和输出功能.
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
的形式提供或参见ssl_client1.c on github.