OpenSSL库中的SMIME_read_PKCS7方法具有输入长度1200的限制

SMIME_read_PKCS7 method in OpenSSL library has input length 1200 restriction?

本文关键字:输入 1200 方法 SMIME read OpenSSL PKCS7      更新时间:2023-10-16

我正在尝试使用OpenSSL提供的高级API进行加密工作。

在这里,它通常工作得很好。

/**
 * Instructions for generating private key file and self signed certificate file.
 *
 * openssl genrsa -des3 -out keys.pem 2048
 * openssl rsa -in keys.pem -out rsa.pem
 * openssl req -new -x509 -key rsa.pem -out rsa-cert.pem -days 9999
 * 
 * Compile
 * g++ -ggdb -o exe.bex src.cpp -lcrypto
 */
//c standard library
#include <stdio.h>
#include <string.h>
//openssl library
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/x509.h>
//c++ standard library
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
    FILE* fd = fopen("rsa.pem", "r");
    X509* x509;
    STACK_OF(X509)* x509_stack = sk_X509_new_null();
    EVP_PKEY* key;
    if (NULL != fd)
    {
        key = PEM_read_PrivateKey(fd, NULL, NULL, NULL);
    }
    fd = fopen("rsa-cert.pem", "r");
    if (NULL != fd)
    {
        while (NULL != (x509 = PEM_read_X509(fd, NULL, NULL, NULL)))
        {
            sk_X509_push(x509_stack, x509);
        }
        fclose(fd);
    }
    OpenSSL_add_all_algorithms();
    while (!std::cin.eof()) {
        std::string msg;
        //get input text
        printf("Message to PKCS7 encrypt: ");
        fflush(stdout);
        std::getline(std::cin, msg);
        if (223 < msg.length())
        {
            // SMIME_read_PKCS7 bug, need to be fixed.
            //
            // bt:
            //#0  asn1_d2i_read_bio (in=0x60bc40, pb=0x7fffffffe238) at a_d2i_fp.c:286
            //#1  0x00007ffff7aeb762 in ASN1_item_d2i_bio (it=0x7ffff7dc2da0, in=0x60bc40, x=0x0) at a_d2i_fp.c:113
            //#2  0x00007ffff7b0201c in b64_read_asn1 (bio=0x60bc40, it=0x7ffff7dc2da0) at asn_mime.c:191
            //#3  0x00007ffff7b02dd0 in SMIME_read_ASN1 (bio=0x60af00, bcont=0x7fffffffe350, it=0x7ffff7dc2da0) at asn_mime.c:527
            //#4  0x00007ffff7b39db2 in SMIME_read_PKCS7 (bio=0x60af00, bcont=0x7fffffffe350) at pk7_mime.c:96
            //#5  0x0000000000401668 in main (argc=1, argv=0x7fffffffe4b8) at ./crypto-pkcs.cpp:99
            //
            std::cout << "string too long " << msg.length() << std::endl;
            continue;
        }
        if (msg.empty())
        {
            std::cout << "string too short" << std::endl;
            continue;
        }

        //save input
        BIO* bio_input_plain_text = BIO_new(BIO_s_mem());
        BIO_write(bio_input_plain_text, msg.c_str(), msg.length());
        BIO_flush(bio_input_plain_text);
        //create PKCS7 object in the way of PKCS7_encrypt.
        PKCS7* pkcs7_encrypt = PKCS7_encrypt(x509_stack, bio_input_plain_text, EVP_aes_256_cbc(), 0);
        if (NULL == pkcs7_encrypt) {
            std::cout << "PKCS7_encrypt returns NULL" <<  std::endl; return -1;}
        //dump encryped info.
        BIO* bio_encrypted_smime = BIO_new(BIO_s_mem());
        if (SMIME_write_PKCS7(bio_encrypted_smime, pkcs7_encrypt, bio_input_plain_text, 0) != 1){ std::cout << "SMIME_write_PKCS7 failed" << std::endl; return -1;}
        BIO_flush(bio_encrypted_smime);
        //get internal data address
        const char* encrypted = NULL;
        BIO_get_mem_data(bio_encrypted_smime, &encrypted);  //encrypted has no new resource, only the reflection of the internal BIO data.
        //char encrypted[8 * 1024] = "";
        //copy BIO to char array
        //BIO_read(bio_encrypted_smime, encrypted, sizeof encrypted - 1); //if we read the data out of BIO, later we need to write it back, BIO_read deletes the internal data inside BIO
        std::cout << "PKCS7_encrypt length:" << strlen(encrypted) << std::endl << encrypted << std::endl;
        //please be careful while taking care of BIO object.
        //if we call BIO_read against BIO to get out data, the operation will cause the data deleted in the BIO at the same time.
        //here we recover BIO data.
        //BIO_write(bio_encrypted_smime, encrypted, strlen(encrypted));
        //BIO_flush(bio_encrypted_smime);
        BIO* bio_pkcs7 = BIO_new(BIO_s_mem());
        //read&load PKCS7 object from SMIME format.
        PKCS7* pkcs7_smime = SMIME_read_PKCS7(bio_encrypted_smime, &bio_pkcs7);
        if (NULL == pkcs7_smime) {std::cout << "SMIME_read_PKCS7 returns NULL" << std::endl;return -1;}
        BIO_flush(bio_pkcs7);
        BIO* bio_pkcs7_decrypt = BIO_new(BIO_s_mem());
        //decrypt in the way of PKCS7_decrypt
        if (0 == PKCS7_decrypt(pkcs7_smime, key, x509, bio_pkcs7_decrypt, 0)){ std::cout << "PKCS7_decrypt failed" << std::endl;return -1;}
        BIO_flush(bio_pkcs7_decrypt);
        //char decrypted[8 * 1024] = "";
        //dump decrypted data.
        //BIO_read(bio_pkcs7_decrypt, decrypted, sizeof decrypted - 1);
        const char* decrypted = NULL;
        //get internal data address
        BIO_get_mem_data(bio_pkcs7_decrypt, &decrypted);
        std::cout << "PKCS7_decrypt length: " << strlen(decrypted) << std::endl << decrypted << std::endl;
        //cleanup, idiot! donot forget to release resource you piece of shit!
        BIO_free(bio_pkcs7_decrypt);
        BIO_free(bio_pkcs7);
        BIO_free(bio_encrypted_smime);
        PKCS7_free(pkcs7_smime);
        PKCS7_free(pkcs7_encrypt);
    }
    X509_free(x509);
    sk_X509_pop_free(x509_stack, X509_free);
    return 0;
}

这段代码在openssl-1.0.1g下运行良好。我尝试了线程和无线程配置选项。

但是如果我们输入一个224长度的纯文本没有if-continue的东西,加密仍然工作,它无法在SMIME_read_PKCS7的方法中解密,检查上面的回溯注释。

如果输入长度为223,则加密后的S/MIME长度为1200。

在224长度输入的情况下,S/MIME格式的加密结果长度超过1200,SMIME_read_PKCS7无法处理该长度,检查上面的内部调用,但我没有找到任何文档或页面进行解释。

smme_read_pkcs7的秘密是什么?

缺陷1:

在@jw的帮助下。应该有

BIO_set_mem_eof_return(bio_encrypted_smime, 0);

之前
PKCS7* pkcs7_smime = SMIME_read_PKCS7(bio_encrypted_smime, &bio_pkcs7);
缺陷2:

BIO_get_mem_data这个应该返回数据的长度,实际上我们需要根据长度来处理char*,不要仅仅依赖于null终止,除非你期望附加垃圾值

smme_read_pkcs7的秘密是什么?

其实没什么秘密。就是有毛病。根据SMIME_read_PKCS7(3)上的文档:

BUGS
The MIME parser used by SMIME_read_PKCS7() is somewhat primitive.
While it will handle most S/MIME messages more complex compound
formats may not work.
The parser assumes that the PKCS7 structure is always base64
encoded and will not handle the case where it is in binary
format or uses quoted printable format.
The use of a memory BIO to hold the signed content limits the
size of message which can be processed due to memory restraints:
a streaming single pass option should be available. 

在OpenSSL用户组中有一些关于SMIME_read_PKCS7的好线程