在系统软件中有系统升级功能,方便软件的版本迭代,而在系统升级前需要校验升级的Image是否是可信的,以及信息是否被篡改过。
升级包由以下部分组成 数字证书 + 升级文件的文件摘要 + 使用密钥加密升级文件摘要得到的数字签名 + 升级文件
验证过程大体如下图
下面简述基于开源库Openssl的验证过程,在系统中会有一个预先存好的根证书,当新的软件包接入的时候,首先会使用根证书来校验软件包中的证书是否是正确的,包含下面这些细节;
1.校验软件包中的证书的签发者和用途是否合规;
2.校验软件包中的证书有效期时间是否在有效期内(所以对系统时间有依赖);
3.使用根证书中的密钥解密软件包证书中的数字签名,并与软件包中证书的摘要进行对比;
4.其他验证步骤;
经过以上几上步骤的验证可以得到软件包中的证书是可信的,那么接下来就可以使用软件包中的证书里携带的密钥了。Openssl中提供用于证书验证接口。
下面代码可以将证书从文件中读取,并转换为X509的结构体指针。
BIO* cabio = BIO_new(BIO_s_mem());
BIO_write(cabio, rootCaBuf, strlen(rootCaBuf));
m_RootCertificate = PEM_read_bio_X509(cabio, NULL, 0, NULL);
下面过程可以将根证书和软件包中的证书添加到验证的上下文,并通过X509_verify_cert验证证书
OpenSSL_add_all_algorithms();
X509_STORE* ca_store = X509_STORE_new();
X509_STORE_CTX* ctx = X509_STORE_CTX_new();
X509_STORE_set_verify_cb(ca_store, verify_cb);
int ret = X509_STORE_add_cert(ca_store, caX509);
if (1 != ret)
{
LOGFORMAT(m_context, "System", LOG_LEVEL_0, "[ssl_verify] X509_STORE_add_cert ret failed");
return false;
}
STACK_OF(X509) *ca_stack = NULL;
ret = X509_STORE_CTX_init(ctx, ca_store, usrX509, ca_stack);
if (ret != 1)
{
LOGFORMAT(m_context, "System", LOG_LEVEL_0, "[ssl_verify] X509_STORE_CTX_init ret failed");
return false;
}
ret = X509_verify_cert(ctx);
其中X509_STORE_set_verify_cb可以设置校验过程中的回调函数,如当证书为自签名证书的时候,Openssl会回调用户设置的CallBack,在CallBack中通过X509_STORE_CTX_get_error可以得到错误的原因,或者忽略这个错误,如果没有设置CallBackd的话,Openssl会使用一个默认的处理,停止下面的验证步骤。
用户证书验证通过以后通过下面接口获得用户证书中的公钥Key。
EVP_PKEY* k = X509_get_pubkey(Certificate);
rsa_pub_key = EVP_PKEY_get1_RSA(k);
并通过这个公钥来解密升级文件的数字签名,将文件的数字签名和密钥传给Openssl进行sha256数字签名的校验。
RSA_verify(NID_sha256, FileMd, MD_LENGTH, Sign, SIGN_LENGTH, rsa_pub_key);
以上校验通过以后可以得到升级文件文件摘要是正确的。
const EVP_MD *md = EVP_sha256();
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
unsigned int len = 32;
if (!EVP_DigestInit(&ctx,md))
{
return false;
}
FILE* fp = fopen(path, "r");
if(NULL == fp)
{
return false;
}
const int bufsize = 1024 * 1024;
while(!feof(fp))
{
char buf[bufsize] = {0x00};
int size = fread(buf, 1, bufsize, fp);
EVP_DigestUpdate(&ctx, buf, size);
}
EVP_DigestFinal(&ctx, (unsigned char*)sha, &len);
上面过程用来计算文件的摘要,如果计算得到的文件摘要和升级包中的文件摘要相同,则可认为升级包是正确的。