OPENSSL源码阅读
前言
我最近有点烦,想找点副业不知道干啥,化悲愤为思考,写点文章吧。一般人阅读OPENSSL的源码是先说有几个文件,每个文件干啥。我打算从S_SERVER文件说起,因为做实验的时候一般都是自己搭建TLS 服务器。这次不会细致到每个点都扣,不值得也没有必要,读书不求甚解,至于精微之处则慢慢品味。
从S_SERVER说起
openssl自带一个s_server服务器,可以完成NST颁发,计算PSK,握手的每个计算。如果想从代码层面研究TLS1.3的握手流程,那么openssl就很适合。当然openssl的代码写的非常直接,我司几个人看的时候都把openssl骂了一顿。学习学习倒是极好的。
前置知识
s_server的使用方法大致如下,openssl s_server -accept “port” -key “key_file” -cert “cert_file” -ciphersuite “xxx” -tls1_2,所有双引号的内容都是自己制定的。然后带”-“的都是选项。
源码阅读
打开s_server.c文件,主函数是s_server_main.c。s_server_main函数刚打开实际上是一大堆状态,CTX等用于环境的基本设置,就不多介绍了。唯一需要注意的是
const SSL_METHOD *meth = TLS_server_method();
...
int min_version = 0, max_version = 0, prot_opt = 0, no_prot_opt = 0;
meth就是server端服务的主程序,而min_version和max_version本质上对应于服务端支持的版本范围。此外还有几个全局变量要注意:stateless全局变量,用于标识服务端完全不存储任何客户端信息,这里针对的是办法TICKET IDENTITY
case OPT_STATELESS:
stateless = 1;
break;
s_server_main首先分配SSL设置环境(SSL configuration context),接着验证参数的有效性,之后再给SSL设置环境打上服务端和命令行模式标签。标签打上以后就开始根据参数设置各种变量。下面的代码就设置了服务端支持的版本范围,比方说加了标签”TLS1_2”,那么就设置最大版本和最小版本都是TLS1.2。
cctx = SSL_CONF_CTX_new();
vpm = X509_VERIFY_PARAM_new();
if (cctx == NULL || vpm == NULL)
goto end;
SSL_CONF_CTX_set_flags(cctx,
SSL_CONF_FLAG_SERVER | SSL_CONF_FLAG_CMDLINE);
prog = opt_init(argc, argv, s_server_options);
while ((o = opt_next()) != OPT_EOF) {
if (IS_PROT_FLAG(o) && ++prot_opt > 1) {
BIO_printf(bio_err, "Cannot supply multiple protocol flags\n");
goto end;
}
...
case OPT_SSL3:
min_version = SSL3_VERSION;
max_version = SSL3_VERSION;
break;
case OPT_TLS1_3:
min_version = TLS1_3_VERSION;
max_version = TLS1_3_VERSION;
break;
case OPT_TLS1_2:
min_version = TLS1_2_VERSION;
max_version = TLS1_2_VERSION;
break;
case OPT_TLS1_1:
min_version = TLS1_1_VERSION;
max_version = TLS1_1_VERSION;
break;
case OPT_TLS1:
min_version = TLS1_VERSION;
max_version = TLS1_VERSION;
上面的初始化代码初始化了支持的版本,是否支持max_early_data,最大接受early data的字节之后。函数首先载入证书和私钥.如果有证书链文件再载入证书链文件。
if (nocert == 0) {
s_key = load_key(s_key_file, s_key_format, 0, pass, engine,
"server certificate private key file");
if (s_key == NULL)
goto end;
s_cert = load_cert(s_cert_file, s_cert_format,
"server certificate file");
if (s_cert == NULL)
goto end;
if (s_chain_file != NULL) {
if (!load_certs(s_chain_file, &s_chain, FORMAT_PEM, NULL,
"server certificate chain"))
goto end;
}
之后载入crl文件,也就是判断证书是否有效的文件。
if (crl_file != NULL) {
X509_CRL *crl;
crl = load_crl(crl_file, crl_format, "CRL");
if (crl == NULL)
goto end;
crls = sk_X509_CRL_new_null();
if (crls == NULL || !sk_X509_CRL_push(crls, crl)) {
BIO_puts(bio_err, "Error adding CRL\n");
ERR_print_errors(bio_err);
X509_CRL_free(crl);
goto end;
}
}
基本的配置都载入完成了,就可以初始化CTX了。这里需要注意的是:ctx和ctx2都是静态全局变量,下面的代码初始化CTX并使用CCTX来初始化CTX,同时设置CTX的各种属性,比方说最大支持版本,最小支持版本,设置是否异步(async),设置支持early data,支持接受的最大的early data的长度。
ctx = SSL_CTX_new(meth);
if (ctx == NULL) {
ERR_print_errors(bio_err);
goto end;
}
SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
if (sdebug)
ssl_ctx_security_debug(ctx, sdebug);
if (!config_ctx(cctx, ssl_args, ctx))
goto end;
if (ssl_config) {
if (SSL_CTX_config(ctx, ssl_config) == 0) {
BIO_printf(bio_err, "Error using configuration \"%s\"\n",
ssl_config);
ERR_print_errors(bio_err);
goto end;
}
}
#ifndef OPENSSL_NO_SCTP
if (protocol == IPPROTO_SCTP && sctp_label_bug == 1)
SSL_CTX_set_mode(ctx, SSL_MODE_DTLS_SCTP_LABEL_LENGTH_BUG);
#endif
if (min_version != 0
&& SSL_CTX_set_min_proto_version(ctx, min_version) == 0)
goto end;
if (max_version != 0
&& SSL_CTX_set_max_proto_version(ctx, max_version) == 0)
goto end;
最后进入了服务器搭建的关键部分了。根据协议,设置服务器的主函数,这实际上是个回调函数。do_server本质就是绑定服务器到一个地址。注意,这里实现了写代码的时候一个很关键的地方,分层的想法。面向四层的操作交给一个四层的函数去做,面向四层一下的操作给四层以下的去做,这种想法很有用。因为我们只关注TLS层,所以就不对四层以下的多做解释了。有兴趣可以自己看。解析来函数跳转到sv_body,sv_body本身就是提供TLS服务的关键,里面包含的是一个自动机。下一节我们再细说。
if (rev)
server_cb = rev_body;
else if (www)
server_cb = www_body;
else
server_cb = sv_body;
#ifdef AF_UNIX
if (socket_family == AF_UNIX
&& unlink_unix_path)
unlink(host);
#endif
do_server(&accept_socket, host, port, socket_family, socket_type, protocol,
server_cb, context, naccept, bio_s_out);
print_stats(bio_s_out, ctx);
ret = 0;
结束
我个人很反感这段代码,配置这块我觉得最好单独拆一个函数出来,然后里面不同的部分分开。现在这样子看的太费劲了。
结尾的闲言碎语
写到这里差不多就可以结束了,实际上Session Identity还经常需要添加ALPN信息,就不多说了。TLS这块还有啥不明白的直接告诉我就成了