OpenSSL一直以来各种被诟病,具体挑了哪些刺,本文就不深究。作为OpenSSL有很多替代,我了解到的有cyaSSL(WolfSSL)和PolorSSL。其中PolarSSL已经被ARM收购了,改名为mbedTLS。本文列举了作为一个SSL client端,应该如何使用mbedTLS。
本文可以搭配我上一篇文章OpenSSL一起看;单独看也没问题
本文地址:https://segmentfault.com/a/1190000005998141
Reference
mbed TLS tutorial - Knowledge Base
ARMmbed / mbedtls / programs / ssl / ssl_client1.c
mbedTLS 基本使用
与传统 socket 的对比
传统的socket-based的程序,依照顺序,作为client要做以下的函数调用:
gethostbyname()
socket()
connect()
write()
read()
改成SSL之后,mbedTLS对应上述函数,分别对应为:
gethostbyname() \
socket() -+--> mbedtls_net_connect() + mbedtls_ssl_handshake()
connect() /
write() ----> mbedtls_ssl_write()
read() ----> mbedtls_ssl_read()
当然,实际情况下,会使用更多的其他函数。具体(最简单的)流程见下文
数据结构
mbedtls_net_context
:目前只有文件描述符,可以用于适配异步I/O库mbedtls_ssl_context
:保存SSL基本数据mbedtls_ssl_config
mbedtls_ctr_drbg_context
mbedtls_entropy_context
:保存熵配置mbedtls_x509_crt
:保存认证信息
Init 阶段
下面是init阶段需要调用的各函数。函数的参数,在调用的时候按照上面的函数类型一个一个传入就行了
mbedtls_net_init()
mbedtld_ssl_init()
mbedtld_ssl_config_init()
mbedtls_ctr_drbg_init()
mbedtld_x509_crt_init()
mbedtls_entropy_init()
mebdtls_ctr_drbg_seed()
其中mebdtls_ctr_drbg_seed()
可以指定熵函数。如果回调使用默认的mbedtls_entropy_func
的话,可以传入一个初始的熵seed,也可以NULL
Connect 阶段
mbedtls_net_connect()
:参数是server和端口,均为字符串。server可以使域名或者IP字符串。最后一个参数使用MBEDTLS_NET_PROTO_TCP
即可。端口号不仅仅可以传入数字字符串,也可以类似于get_addrinfo
函数的protocol
参数那样,传入类似于“HTTPS”这样的可读化字符串。
mbedtls_ssl_config_defaults()
:
适配异步I/O库
经实验,mbedTLS是可以顺利适配libev
的,Good!(libuv没有实际试过,不过应该也是OK的)
什么?问我怎么不适配libevent
? 要用libevent的话就用封装了OpenSSL
的bufferevent
去啦
适配的具体思路,可以查看本文的评论中我和 breeze0924 的留言。
此外,还有两位同学也做了相应的适配:
- @gemini 写了一个 libuv demo
- @yorkie 则把 mbedTLS 的客户端实现成功移植到了 ShadowNode 上
各位都是大神啊~~欢迎参考~~
姊妹篇
作为 client 适配 libev 调用过程
以下是伪代码,有点多,从最简单的main()
入手就好:
一些自定义的数据结构
typedef struct _MBEDTLS_SESSION {
mbedtls_net_context mbedNetCtx;
mbedtls_ssl_context mbedSslCtx;
mbedtls_ssl_config mbedSslConf;
mbedtls_ctr_drbg_context mbedDrbgCtx;
mbedtls_entropy_context mbedEtpyCtx;
mbedtls_x509_crt mbedX509Crt;
} MbedTLSSession_st;
typedef struct SSL_SESSION {
struct ev_io *libevWatcher;
MbedTLSSession_st mbedIntf;
} SSLSession_st;
MbedTLS初始化
MbedTlsClientInit(MbedTLSSession_st *session, void *entropy, size_t entropyLen)
{
mbedtls_net_init(&(session->mbedNetCtx));
mbedtls_ssl_init(&(session->mbedSslCtx));
mbedtls_ssl_config_init(&(session->mbedSslConf));
mbedtls_ctr_drbg_init(&(session->mbedDrbgCtx));
mbedtls_x509_crt_init(&(session->mbedX509Crt));
mbedtls_entropy_init(&(session->mbedEtpyCtx));
mbedtls_ctr_drbg_seed(&(session->mbedDrbgCtx),
mbedtls_entropy_func,
&(session->mbedEtpyCtx),
(unsigned char *)entropy,
entropyLen);
}
MbedTLS的 connect 动作
AMCMbedTlsClientConnect(MbedTLSSession_st *session, const char *serverHost, int serverPort)
{
int callStat;
char portStrBuff[16];
snprintf(portStrBuff, sizeof(portStrBuff), "%d", serverPort);
callStat = mbedtls_net_connect(&(session->mbedNetCtx),
serverHost,
portStrBuff,
MBEDTLS_NET_PROTO_TCP);
_RETURN_IF_ERROR(callStat);
callStat = mbedtls_ssl_config_defaults(&(session->mbedSslConf),
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT);
_RETURN_IF_ERROR(callStat);
mbedtls_ssl_conf_authmode(&(session->mbedSslConf), MBEDTLS_SSL_VERIFY_OPTIONAL);
mbedtls_ssl_conf_ca_chain(&(session->mbedSslConf), &(session->mbedX509Crt), NULL);
mbedtls_ssl_conf_rng(&(session->mbedSslConf),
mbedtls_ctr_drbg_random,
&(session->mbedDrbgCtx));
//edtls_ssl_conf_dbg(&(session->mbedSslConf), NULL, NULL);
mbedtls_ssl_set_bio(&(session->mbedSslCtx),
&(session->mbedNetCtx),
mbedtls_net_send,
mbedtls_net_recv,
mbedtls_net_recv_timeout);
callStat = mbedtls_ssl_setup(&(session->mbedSslCtx), &(session->mbedSslConf));
_RETURN_IF_ERROR(callStat);
while((callStat = mbedtls_ssl_handshake(&(session->mbedSslCtx))) != 0)
{
if ((callStat != MBEDTLS_ERR_SSL_WANT_READ)
&& (callStat != MBEDTLS_ERR_SSL_WANT_WRITE))
{
return callStat;
}
}
return 0;
}
主函数:
static SSLSession_st *g_session = NULL;
main()
{
SSLSession_st session;
char *pers = "hello world";
MbedTlsClientInit(&(session.mbedIntf), pers, strlen(pers));
mbedtls_ssl_write(...); // 发送请求。我用的是https://www.bing.com,所以发送的是HTTP请求
/* 下面开始配置 libev */
session.libevWatcher = malloc(sizeof(session.libevWatcher));
set_nonblock(session.mbedNetCtx.fd)
ev_io_init(&(session.libevWatcher), _libev_callback, fd, EV_READ); // 在callback 调用 mbedtls_ssl_read()
ev_io_start(loop, &(session.libevWatcher));
ev_loop(loop, 0);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。感谢作者的启发,这两天终于把 mbedtls 移植到 libuv 上,最麻烦的大概就是抽象 IO 层,也就是
mbedtls_ssl_set_bio
这个函数里的两个回调函数。特别小写了一个 repo 供记录和参考,demo 还未补上。再次感谢,感激不尽。
https://github.com/geminiwen/...
多谢 @Gemini @amc 之前的工作,我也成功把 mbedTLS 的客户端实现成功移植到了 ShadowNode 上,https://github.com/Rokid/shad...
您好,您的文章中提到, mbedTLS可以适配libuv,
请问有什么示例么,我采用了 mbedtls_ssl_set_bio, 将其中的 void p_bio 以实参 uv_tcp_t传入,同时定制了net_recv和net_send函数。 但是,不成功。想请问您,是如何做到的。
@breeze924 重新看了一下我的代码,我的测试是在libev上面做的,libuv没有实际确认……但是libev和libuv很相似啊。我再看看我整个调用过程
@breeze924 我把我的libev调用过程贴上来了,
mbedtls_ssl_set_bio
我在connect
封装里面调用的,你看看和你的封装有什么区别不?&(session->mbedNetCtx) 就是一个文件描述符吧(socket)?mbedtls_net_send, mbedtls_net_recv, 直接对其操作。我不懂libev,它可以获得底层的fd,然后监控它实现异步io的行为么?libuv我不知道如何获得底层的fd。谢谢你的回复啦。这个问题我再研究下。
@breeze924 是的啊,libev就是直接操作fd。libuv不也是直接操作fd么~~所以前面一堆mbedTLS的init和connect完成之后,就从mbedNetCtx中获得这个已经connect OK的fd,交给libev处理就好了。你要适配libuv,也一样,在这里把fd取出来,然后交给libuv处理
@breeze924 你要做的事情就是在libuv的回调里面,把
read
和write
操作换成mbedTLS的read/write就好了@amc 看libuv没有提示如何操纵fd啊。
@breeze924 你看看libuv的接口,
uv_tcp_t
里面第一个域是UV_HANDLE_PRIVATE_FIELDS
,里面有一个union就含有int fd