carloscn / blog

My blog
Apache License 2.0
134 stars 38 forks source link

8.0_Security_pkcs7(CMS)_embedded #186

Open carloscn opened 1 year ago

carloscn commented 1 year ago

在NXP High Assurance Booting (HAB)中,整体的RSA认证体系全部使用的PKCS7中CMS签名Cryptographic Message Syntax (CMS),借此机会,对CMS签名认证相关的专业知识进行整理。PKCS#7是一个复杂的格式,也叫做CMS。

Cryptographic Message Syntax (CMS)是IETF密码学标准用于保护消息。用于数字签名、digest、认证或者加密。CMS基于PKCS#7的原语,基于Privacy-Enhanced Mail standard,最新的CMS版本是2009。CMS架构的建立是基于certificate-based证书的key的管理,其中也使用S/MIME、PKCS#12、还有RFC3161时间戳协议。OpenSSL对于CMS进行了完整的实现。

签名格式

一个CMS的签名的信息主要包含两部分,其一是数字签名本身(加密了的hash值),其二就是signer的信息。数字签名本身没有什么好介绍的,和一般的RSA签名一致,而signer信息就很丰富了。

Signer信息包含:

一个典型的CMS签过的二进制数据如下面图所示:

未突出显示的部分(黑色)是ASN.1说明符和其他属性、算法等

这个图中的signed message包含原始签名的content和signer的证书,但是没有一些认证的属性。签名的数据块被RSA的private key联合signer的证书加密。

除了典型的CMS二进制数据格式,还有一个比较简单的用例:

未突出显示的部分(黑色)是ASN.1说明符和其他属性、算法等

如果做认证的属性(例如:时间戳)也包含进入CMS signed file,那么signature最后的hash值中也把这个属性信息包含进去了。

参考:https://www.jensign.com/sigview/index.html

PKI机制

自签Key和cert

CMS使用PKI机制,因此需要一个CA机制,可以按照:

生成过程:

虚构一个CA认证机构出来:

# 生成CA认证机构的证书密钥key
openssl genrsa -out ca.key 2048 # 生成CA的私钥

# 用私钥ca.key生成CA认证机构的证书ca.crt
# 其实就是相当于用私钥生成公钥,再把公钥包装成证书
openssl req -new -x509 -key ca.key -out ca.crt -days 365

生成Sign Server的证书:

用上面那个虚构出来的CA机构来认证:

# 生成自己的密钥server.key
openssl genrsa -out server.key 2048
# 生成CSR
openssl req -new -key server.key -out server.csr

使用CA对CSR进行签名,得到server.crt

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -days 36

至此,私钥server.key和证书server.crt已全部生成完毕。

我们可以通过openssl的verify功能验证server.crt是否是ca.key签出来的:

$ openssl verify -verbose -CAfile ca.crt  server.crt 
server.crt: OK

CMS签名和认证

签名

openssl cms -sign \
            -md sha256 \
            -signer server.crt \
            -inkey server.key \
            -outform der \
            -nodetach \
            -out signed_cmd_data_0.bin \
            -in hello.txt \
            -noattr

验签

openssl cms -verify \
            -CAfile ca.crt \
            -inform der \
            -in signed_cmd_data_0.bin \
            -certfile server.crt \  # optional
            -content hello.txt   # optional

在CMS签名文件Blob中包含:

  1. 原始签名的内容;
  2. signserver的证书
    1. 证书信息
    2. signer的publick key
  3. 属性信息
  4. 签名信息(生成签名的数据是以上信息的总和,并非是原始签名内容的做的sign)

在CMS Blob中有了signserver的公钥信息,因此在验签的时候不需要指定certfile;而blob中也包含原始的签名内容,因此也不需要指定content内容。

参考: https://blog.csdn.net/as3luyuan123/article/details/13612917?ydreferer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8%3D

多级证书

应用中常常需要CA签发多级证书,例如下图的应用场景。在PKI验签中,此时指定的证书需要,把证书链存入一个cert store中,然后有了store之后一级一级的对证书进行验证。

制作一个验证证书的脚本,对证书链进行验证:

#!/bin/sh

if [[ "$1" = "" || "$2" = ""  ]]; then
        echo "certSignVerify.sh  CAfiles certfile "
        exit 0;
fi

touch tmpca.cer
count=$#
tmp=1
for i in "$@"; do
        if [  "$tmp" -eq  "$count" ] ; then

                break;
        fi
        cat $i >> tmpca.cer
        tmp=$[$tmp +1]
done

openssl verify -CAfile tmpca.cer -verbose $(eval echo "\$$#")

调用bash ./verify_chains.sh CA1_sha256_2048_65537_v3_ca_crt.pem SRK1_sha256_2048_65537_v3_ca_crt.pem IMG1_1_sha256_2048_65537_v3_usr_crt.pem

这样就验证了最底层IMG1_1_sha256_2048_65537_v3_usr_crt的合法性。

这个脚本中生成tmpca.cer是一个包含CA证书的证书链。在验证多级证书签名的数据要使用下面的方法:

openssl cms -sign \
            -md sha256 \
            -signer ./keys/IMG1_1_sha256_2048_65537_v3_usr_crt.pem \
            -inkey ./keys/IMG1_1_sha256_2048_65537_v3_usr_key.pem \
            -outform der \
            -nodetach \
            -out signed_cmd_data_0.bin \
            -in to_be_signed_0.bin \
            -noattr

echo "sign finish!"

openssl cms -verify \
            -CAfile ./keys/tmpca.cer \   # 需要证书链
            -inform der \
            -in signed_cmd_data_0.bin

参考: https://blog.csdn.net/xiangguiwang/article/details/80336755

openssl c语言 示例

下面我们使用openssl的C语言接口对数据进行sign和verify,我这里已经包装成了工具 https://github.com/carloscn/cryptography/tree/master/tools/cms_sign_verify_tool

sign过程

https://github.com/carloscn/cryptography/blob/master/tools/cms_sign_verify_tool/cms_tool.c#L532

传入安全相关的参数:

make sign可以一键测试sign的功能

int32_t
gen_sig_data_cms(const char *in_file,
                 const char *cert_file,
                 const char *key_file,
                 const char *sig_out_name,
                 hash_alg_t hash_alg,
                 uint8_t *sig_buf,
                 size_t *sig_buf_bytes)
{
    BIO             *bio_in = NULL;   /**< BIO for in_file data */
    X509            *cert = NULL;     /**< Ptr to X509 certificate read data */
    EVP_PKEY        *key = NULL;      /**< Ptr to key read data */
    CMS_ContentInfo *cms = NULL;      /**< Ptr used with openssl API */
    const EVP_MD    *sign_md = NULL;  /**< Ptr to digest name */
    int32_t err_value = CAL_SUCCESS;  /**< Used for return value */
    /** Array to hold error string */
    char err_str[MAX_ERR_STR_BYTES];
    /* flags set to match Openssl command line options for generating
     *  signatures
     */
    int32_t         flags = CMS_DETACHED | CMS_NOCERTS |
                            CMS_NOSMIMECAP | CMS_BINARY;

    if (NULL == in_file ||
        NULL == cert_file ||
        NULL == key_file ||
        NULL == sig_out_name ||
        NULL == sig_buf ||
        NULL == sig_buf_bytes ||
        hash_alg >= INVALID_DIGEST) {
        display_error("Input param error!\n");
        return CAL_INVALID_ARGUMENT;
    }

    /* Set signature message digest alg */
    sign_md = EVP_get_digestbyname(get_digest_name(hash_alg));
    if (sign_md == NULL) {
        display_error("Invalid hash digest algorithm");
        return CAL_INVALID_ARGUMENT;
    }

    do
    {
        cert = read_certificate(cert_file);
        if (!cert) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Cannot open certificate file %s", cert_file);
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        /* Read key */
        key = read_private_key(key_file,
                           (pem_password_cb *)get_passcode_to_key_file,
                           key_file);
        if (!key) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Cannot open key file %s", key_file);
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        /* Read Data to be signed */
        if (!(bio_in = BIO_new_file(in_file, "rb"))) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Cannot open data file %s", in_file);
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }
        /* Generate CMS Signature - can only use CMS_sign if default
         * MD is used which is SHA1 */
        flags |= CMS_PARTIAL;

        cms = CMS_sign(NULL, NULL, NULL, bio_in, flags);
        if (!cms) {
            display_error("Failed to initialize CMS signature");
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        if (!CMS_add1_signer(cms, cert, key, sign_md, flags)) {
            display_error("Failed to generate CMS signature");
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        /* Finalize the signature */
        if (!CMS_final(cms, bio_in, NULL, flags)) {
            display_error("Failed to finalize CMS signature");
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        /* Write CMS signature to output buffer - DER format */
        err_value = cms_to_buf(cms, bio_in, sig_buf, sig_buf_bytes, flags);
    } while(0);

    do {
        BIO *yy = BIO_new_file(sig_out_name, "wb");
        BIO_write(yy, sig_buf, *sig_buf_bytes);
        BIO_free(yy);
    } while (0);

    /* Print any Openssl errors */
    if (err_value != CAL_SUCCESS) {
        ERR_print_errors_fp(stderr);
    }

    /* Close everything down */
    if (cms)      CMS_ContentInfo_free(cms);
    if (cert)     X509_free(cert);
    if (key)      EVP_PKEY_free(key);
    if (bio_in)   BIO_free(bio_in);

    return err_value;
}

sign之后的二进制信息可以用openssl来查看:

openssl pkcs7 -inform der -in sign.out -print

verify 过程

https://github.com/carloscn/cryptography/blob/master/tools/cms_sign_verify_tool/cms_tool.c#L302

verify输入的安全相关参数:

int32_t verify_sig_data_cms(const char *in_file,
                            const char *cert_ca,
                            const char *cert_signer,
                            const char *sig_file,
                            hash_alg_t hash_alg)
{
    BIO             *bio_in = NULL;   /**< BIO for in_file data */
    BIO             *bio_sigfile = NULL;   /**< BIO for sigfile data */
    X509_STORE      *store = NULL;     /**< Ptr to X509 certificate read data */
    X509            *signer_cert = NULL;
    CMS_ContentInfo *cms = NULL;      /**< Ptr used with openssl API */
    const EVP_MD    *sign_md = NULL;  /**< Ptr to digest name */
    int32_t err_value = CAL_SUCCESS;  /**< Used for return value */
    int32_t rc = 0;
    /** Array to hold error string */
    char err_str[MAX_ERR_STR_BYTES];
    /* flags set to match Openssl command line options for generating
     *  signatures
     */
    int32_t         flags = CMS_DETACHED | CMS_NOCERTS |
                            CMS_NOSMIMECAP | CMS_BINARY;

    if (NULL == in_file ||
        NULL == cert_ca ||
        NULL == cert_signer ||
        NULL == sig_file ||
        hash_alg >= INVALID_DIGEST) {
        display_error("Input param error!\n");
        return CAL_INVALID_ARGUMENT;
    }

    /* Set signature message digest alg */
    sign_md = EVP_get_digestbyname(get_digest_name(hash_alg));
    if (sign_md == NULL) {
        display_error("Invalid hash digest algorithm");
        return CAL_INVALID_ARGUMENT;
    }

    do
    {
        store = load_cert_chain(cert_ca);
        if (store == NULL) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Cannot open certificates file %s", cert_ca);
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        signer_cert = read_certificate(cert_signer);
        if (!signer_cert) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Cannot open certificate file %s", cert_signer);
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        /* Read signature Data */
        if (!(bio_sigfile = BIO_new_file(sig_file, "rb"))) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Cannot open signature file %s", sig_file);
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        flags |= CMS_NO_SIGNER_CERT_VERIFY;

        /* Parse the DER-encoded CMS message */
        cms = d2i_CMS_bio(bio_sigfile, NULL);
        if (!cms) {
            display_error("Cannot be parsed as DER-encoded CMS signature blob.\n");
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        if (!CMS_add1_cert(cms, signer_cert)) {
            display_error("Cannot be inserted signer_cert into cms.\n");
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        /* Open the content file (data which was signed) */
        if (!(bio_in = BIO_new_file(in_file, "rb"))) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Cannot open data which was signed  %s", in_file);
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        rc = CMS_verify(cms, NULL, store, bio_in, NULL, flags);
        if (!rc) {
            display_error("Failed to verify the file!\n");
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        if (check_verified_signer(cms, store)) {
            snprintf(err_str, MAX_ERR_STR_BYTES-1,
                     "Authentication of all signatures failed!\n");
            display_error(err_str);
            err_value = CAL_CRYPTO_API_ERROR;
            break;
        }

        LOG_DEBUG("Verified OK!\n");

    } while(0);

    /* Print any Openssl errors */
    if (err_value != CAL_SUCCESS) {
        ERR_print_errors_fp(stderr);
    }

    /* Close everything down */
    if (cms) CMS_ContentInfo_free(cms);
    if (store) X509_STORE_free(store);
    if (bio_in) BIO_free(bio_in);
    if (bio_sigfile)   BIO_free(bio_sigfile);

    return err_value;