写在前面
在做php的pkcs7签名校验的时候,遇到一个校验问题,我的情况是,生成的签名不包含证书信息,而在校验时一定要指定一个文件(对应参数$outfilename),用于存放从签名提取的证书。于是就很迷惑,签名都已经不包含证书了,怎么在没有证书的情况下提取证书,然后就看到一个网站,看起来好像是php的bug问题?
php的openssl_pkcs7_verify源码地址:4077行
/* {{{ proto bool openssl_pkcs7_verify(string filename, long flags [, string signerscerts [, array cainfo [, string extracerts [, string content]]]])
Verifys that the data block is intact, the signer is who they say they are, and returns the CERTs of the signers */
PHP_FUNCTION(openssl_pkcs7_verify)
{
X509_STORE * store = NULL;
zval * cainfo = NULL;
STACK_OF(X509) *signers= NULL;
STACK_OF(X509) *others = NULL;
PKCS7 * p7 = NULL;
BIO * in = NULL, * datain = NULL, * dataout = NULL;
zend_long flags = 0;
char * filename;
size_t filename_len;
char * extracerts = NULL;
size_t extracerts_len = 0;
char * signersfilename = NULL;
size_t signersfilename_len = 0;
char * datafilename = NULL;
size_t datafilename_len = 0;
RETVAL_LONG(-1);
if (zend_parse_parameters(ZEND_NUM_ARGS(), "pl|papp", &filename, &filename_len,
&flags, &signersfilename, &signersfilename_len, &cainfo,
&extracerts, &extracerts_len, &datafilename, &datafilename_len) == FAILURE) {
return;
}
if (extracerts) {
others = load_all_certs_from_file(extracerts);
if (others == NULL) {
goto clean_exit;
}
}
flags = flags & ~PKCS7_DETACHED;
store = setup_verify(cainfo);
if (!store) {
goto clean_exit;
}
if (php_openssl_open_base_dir_chk(filename)) {
goto clean_exit;
}
in = BIO_new_file(filename, (flags & PKCS7_BINARY) ? "rb" : "r");
if (in == NULL) {
goto clean_exit;
}
p7 = SMIME_read_PKCS7(in, &datain);
if (p7 == NULL) {
#if DEBUG_SMIME
zend_printf("SMIME_read_PKCS7 failed
");
#endif
goto clean_exit;
}
if (datafilename) {
if (php_openssl_open_base_dir_chk(datafilename)) {
goto clean_exit;
}
dataout = BIO_new_file(datafilename, "w");
if (dataout == NULL) {
goto clean_exit;
}
}
#if DEBUG_SMIME
zend_printf("Calling PKCS7 verify
");
#endif
if (PKCS7_verify(p7, others, store, datain, dataout, (int)flags)) {
RETVAL_TRUE;
if (signersfilename) {
BIO *certout;
if (php_openssl_open_base_dir_chk(signersfilename)) {
goto clean_exit;
}
certout = BIO_new_file(signersfilename, "w");
if (certout) {
int i;
signers = PKCS7_get0_signers(p7, NULL, (int)flags);
for(i = 0; i < sk_X509_num(signers); i++) {
PEM_write_bio_X509(certout, sk_X509_value(signers, i));
}
BIO_free(certout);
sk_X509_free(signers);
} else {
php_error_docref(NULL, E_WARNING, "signature OK, but cannot open %s for writing", signersfilename);
RETVAL_LONG(-1);
}
}
goto clean_exit;
} else {
RETVAL_FALSE;
}
clean_exit:
X509_STORE_free(store);
BIO_free(datain);
BIO_free(in);
BIO_free(dataout);
PKCS7_free(p7);
sk_X509_free(others);
}
/* }}} */
简单看了下,前面部分基本是读取签名和证书信息,而从(PKCS7_verify(p7, others, store, datain, dataout, (int)flags)这句开始,这部分应该是校验p7签名,在校验通过之后,会判断是否指定了存放证书的文件$outfilename(该证书从签名里提取)。
在正常签名包含证书的情况下,因为提取的证书存放文件($outfilename)没有默认值,是个必填参数,所以只要指定了就不会出错。
但是在签名没有包含证书的情况下,应该是无法提取证书的,可是指定证书文件是个必填参数。如果随便指定个文件,会报无法提取证书的错误;如果指定$outfilename为null的话,会有错误信息提示无法写入啥的,报错会有警告,其中一段就包含了signature OK, but cannot open xxxxxxxxxx。
所以简单总结的话,似乎是php的判断不够严谨导致的问题。
不够本人对这些了解的不多,如果因为自己错漏导致的理解错误,希望能有人及时指出,非常感谢!