最近在研究VOIP, 需要用到基于mbedtls的TLS, 发现mbedtls很多API还真没有Openssl丰富, 官方也没有针对openssl的API做wrapper实现接口, 所以很多Openssl的API 功能, mbedtls居然没有, 或者说要自己写代码来实现, 这就有点曲线救国的味道了.
如果没有使用过openssl或是mbedtls认证开发过产品, 对TLS这证书也是一知半解的话, 那初步看上去也是一头雾水, 不知道如何来替代实现X509_Digest功能, 所以要实现之前, 先看阅读源码openssl中关于X509_Digest的源码实现, 这部分源码涉及知识是X509证书的格式和解析,以及SHA1/SHA256算法实现, 其实算法可以不必深究, 我们需要实现的是API替代, SHA算法本身mbedtls就含有API. 证书格式相关部分可以自行查询资料了解, 本文不多做赘述.
接下来我们先初步窥探下openssl源码中的X509_Digest实现原理, 按照官方文档就是DER数据做digest运算, 也就是SHA1/256运算完成计算一组值, 类似MD5这样的运算. 那原理明确了, 在mbedTLS实现就简单了.
根据OpenSSL的描述X509证书DER数据进行sha1运算, 那就分两部走: A 获得DER数据, B SHA运算.
如何获得DER数据, 这个MBEDTLS有API可以载入mbedtls_x509_crt_parse/mbedtls_x509_crt_parse_file/mbedtls_x509_crt_parse_path 三个函数, 载入DER或是PEM数据格式的Cert文件, 完成对cert文件的解析和读取, 并存放在结构体变量:mbedtls_x509_crt 中.
解析完成后的结构体mbedtls_x509_crt的变量A里面就可以找到DER的数据, 有了这个数据就可以结合SHA1/256算法完成Digest的实现.
下面是实现的代码demo:
#include <string.h>
#include <stdio.h>
#include "mbedtls/x509_crt.h"
#include "mbedtls/sha1.h"
#include "mbedtls/sha512.h"
#include "mbedtls/sha256.h"
#define USAGE \
"\n usage: digest param=<>...\n" \
"\n acceptable parameters:\n" \
" ca_file=file default: none\n" \
" sha_method=method default: sha1/256\n" \
"\n"
mbedtls_md_type_t digest_method[] = {
MBEDTLS_MD_NONE,
MBEDTLS_MD_MD2, /**< The MD2 message digest. */
MBEDTLS_MD_MD4, /**< The MD4 message digest. */
MBEDTLS_MD_MD5, /**< The MD5 message digest. */
MBEDTLS_MD_SHA1, /**< The SHA-1 message digest. */
MBEDTLS_MD_SHA224, /**< The SHA-224 message digest. */
MBEDTLS_MD_SHA256, /**< The SHA-256 message digest. */
MBEDTLS_MD_SHA384, /**< The SHA-384 message digest. */
MBEDTLS_MD_SHA512, /**< The SHA-512 message digest. */
MBEDTLS_MD_RIPEMD160, /**< The RIPEMD-160 message digest. */
};
int main(int argc, char** argv)
{
int ret = -1;
int i = 0;
char *q=NULL, *p=NULL, *ca_file = NULL;
unsigned char digest1[20]={0}, digest2[32]={0};
unsigned char *p_result;
size_t len = 0;
int sha_method = 4;
mbedtls_x509_crt cacert;
mbedtls_x509_crt_init(&cacert);
if( argc == 1 )
{
usage:
printf( USAGE );
goto exit;
}
for( i = 1; i < argc; i++ )
{
p = argv[i];
if( ( q = strchr( p, '=' ) ) == NULL )
goto usage;
*q++ = '\0';
if( strcmp( p, "ca_file" ) == 0 )
ca_file = q;
if( strcmp( p, "sha_method" ) == 0 ) {
sha_method = atoi( q );
if (sha_method < 0 || sha_method > 9)
goto usage;
}
}
if(strlen(ca_file))
{
if((ret = mbedtls_x509_crt_parse_file(&cacert, ca_file)) < 0 )
{
printf( " failed\n ! mbedtls_x509_crt_parse_file returned -0x%x\n\n", -ret );
goto exit;
}
}
if (digest_method[sha_method] == MBEDTLS_MD_SHA1) {
mbedtls_sha1_context sha1;
mbedtls_sha1_init(&sha1);
mbedtls_sha1_starts_ret(&sha1);
mbedtls_sha1_update_ret(&sha1, cacert.raw.p, cacert.raw.len);
mbedtls_sha1_finish_ret(&sha1, digest1);
p_result = digest1;
len = sizeof(digest1);
} else if (digest_method[sha_method] == MBEDTLS_MD_SHA256) {
mbedtls_sha256_context sha256;
mbedtls_sha256_init(&sha256);
mbedtls_sha256_starts_ret(&sha256, 0);
mbedtls_sha256_update_ret(&sha256, cacert.raw.p, cacert.raw.len);
mbedtls_sha256_finish_ret(&sha256, digest2);
p_result = digest2;
len = sizeof(digest2);
}
if (!p_result)
goto exit;
printf("Gigest %ld bytes: \n", len);
for (int i = 0; i < len; i++) {
printf("%.2X", p_result[i]);
if (i < (len - 1))
printf(":");
}
printf("\n");
exit:
mbedtls_x509_crt_free(&cacert);
return ret;
}
上面的代码实现了SHA1/SHA256的算法demo, 同时有打印信息输出digest结果, 注意编译需要安装对应mebdtls函数库和头文件.
上述代码用GCC编译出可执行文件digest二进制. 代码中的caert->raw.p 存放的就是证书的原始DER数据
下面是验证结果和对比实验.
用Openssl工具创建一份自我签名的证书: cert.pem, 命令如下:
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
生成私钥匙pem: key.pem, 和证书公钥pem: cert.pem. 这两个文件都可以用cat cert.pem直接查看, 是ASCII字符的文件格式.
创建证书时需要用户输入国家, 邮件, 公司名称等, 用户可以自己填写, 便于完成证书的生成.
下面基于cert.pem 使用openssl生成SHA1和SHA256的对应输出digest结果:
$ openssl x509 -sha1 -in cert.pem --noout -fingerprint
SHA1 Fingerprint=F9:E4:5C:4A:38:29:5F:E3:2D:AA:76:20:4F:53:92:8A:5E:2F:01:E9
$ openssl x509 -sha256 -in cert.pem --noout -fingerprint
SHA256 Fingerprint=48:61:D0:39:62:FA:F8:92:9F:BE:49:EB:29:20:32:16:5B:62:80:05:61:CA:30:60:C7:22:CA:DF:66:5B:48:FC
验证上述demo的代码基于mbedtls实现, 用编译出digest工具计算基于cert.pem的SHA1和SHA256值:
$ digest ca_file=./cert.pem sha_method=4
Gigest 20 bytes:
F9:E4:5C:4A:38:29:5F:E3:2D:AA:76:20:4F:53:92:8A:5E:2F:01:E9
$ digest ca_file=./cert.pem sha_method=6
Gigest 32 bytes:
48:61:D0:39:62:FA:F8:92:9F:BE:49:EB:29:20:32:16:5B:62:80:05:61:CA:30:60:C7:22:CA:DF:66:5B:48:FC
上述指令, sha_method=4对应SHA1, sha_method=6对应SHA256, 可以看出计算结果和openssl工具完全相同.
结论: 验证成功, 参考代码完全等效实现了OpenSSL的x509_digest逻辑.