Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8.0_Security_pkcs7(CMS)_embedded #186

Open
carloscn opened this issue May 17, 2023 · 0 comments
Open

8.0_Security_pkcs7(CMS)_embedded #186

carloscn opened this issue May 17, 2023 · 0 comments
Assignees
Labels

Comments

@carloscn
Copy link
Owner

carloscn commented May 17, 2023

在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信息包含:

  • 签名证书的Issuer的SubjectName
  • Signer证书的serial number
  • (可选)真实的被签的数据
  • (可选)真实的签名证书
  • 额外的authenticated attributes.

一个典型的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

传入安全相关的参数:

  • signer的cert文件
  • signer的private key文件

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输入的安全相关参数:

  • signer的cert文件
  • CA证书链(参考多级证书)
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;

@carloscn carloscn self-assigned this May 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant