Open carloscn opened 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二进制数据格式,还有一个比较简单的用例:
如果做认证的属性(例如:时间戳)也包含进入CMS signed file,那么signature最后的hash值中也把这个属性信息包含进去了。
参考:https://www.jensign.com/sigview/index.html
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已全部生成完毕。
server.key
server.crt
我们可以通过openssl的verify功能验证server.crt是否是ca.key签出来的:
$ openssl verify -verbose -CAfile ca.crt server.crt server.crt: OK
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中包含:
在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
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证书的证书链。在验证多级证书签名的数据要使用下面的方法:
tmpca.cer
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语言接口对数据进行sign和verify,我这里已经包装成了工具 https://github.com/carloscn/cryptography/tree/master/tools/cms_sign_verify_tool
https://github.com/carloscn/cryptography/blob/master/tools/cms_sign_verify_tool/cms_tool.c#L532
传入安全相关的参数:
make sign可以一键测试sign的功能
make 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
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;
在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认证机构出来:
生成Sign Server的证书:
用上面那个虚构出来的CA机构来认证:
使用CA对CSR进行签名,得到server.crt
至此,私钥
server.key
和证书server.crt
已全部生成完毕。我们可以通过openssl的verify功能验证server.crt是否是ca.key签出来的:
CMS签名和认证
签名
验签
在CMS签名文件Blob中包含:
在CMS Blob中有了signserver的公钥信息,因此在验签的时候不需要指定certfile;而blob中也包含原始的签名内容,因此也不需要指定content内容。
参考: https://blog.csdn.net/as3luyuan123/article/details/13612917?ydreferer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8%3D
多级证书
应用中常常需要CA签发多级证书,例如下图的应用场景。在PKI验签中,此时指定的证书需要,把证书链存入一个cert store中,然后有了store之后一级一级的对证书进行验证。
制作一个验证证书的脚本,对证书链进行验证:
调用
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证书的证书链。在验证多级证书签名的数据要使用下面的方法:参考: 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的功能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输入的安全相关参数: