8.0_Security_pkcs7(CMS)_embedded
carloscn opened this issue · 0 comments
在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中包含:
- 原始签名的内容;
- signserver的证书
- 证书信息
- signer的publick key
- 属性信息
- 签名信息(生成签名的数据是以上信息的总和,并非是原始签名内容的做的sign)
在CMS Blob中有了signserver的公钥信息,因此在验签的时候不需要指定certfile;而blob中也包含原始的签名内容,因此也不需要指定content内容。
多级证书
应用中常常需要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;