在系统软件中有系统升级功能,方便软件的版本迭代,而在系统升级前需要校验升级的Image是否是可信的,以及信息是否被篡改过。

      升级包由以下部分组成 数字证书 + 升级文件的文件摘要 + 使用密钥加密升级文件摘要得到的数字签名 + 升级文件

     验证过程大体如下图

python openssl 双向认证 openssl校验证书_linux

       下面简述基于开源库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);

上面过程用来计算文件的摘要,如果计算得到的文件摘要和升级包中的文件摘要相同,则可认为升级包是正确的。