2016-06-28 107 views
2

我嘗試寫一個郵件聊天器來用S/MIME簽名郵件。到目前爲止,我已經完成了代碼簽署郵件。我在openssl中使用了demos/smime代碼示例來完成這項工作。不幸的是,這些例子演示瞭如何將輸入消息寫入輸出文件,但我需要將結果作爲字符串。如何將PKCS7_sign結果轉換爲char *或std :: string

這是我的SMIME-方法:

void Smime::sign() { 
    if (!isLoaded()) 
     return; 

    // Null-mailer or unknown 
    if (mailFrom.empty()) 
     return; 

    auto *client = util::mlfipriv(ctx); 
    bool signedOrEncrypted = false; 
    std::vector<std::string> contentType; 

    contentType.push_back("multipart/signed"); 
    contentType.push_back("multipart/encrypted"); 
    contentType.push_back("application/pkcs7-mime"); 

    if (client->sessionData.count("Content-Type") == 1) { 
     std::string value {client->sessionData["Content-Type"]}; 
     std::size_t found; 

     for (int i=0; i<contentType.size(); i++) { 
      found = value.find(contentType.at(i)); 
      if (found != std::string::npos) { 
       signedOrEncrypted = true; 
       break; 
      } 
     } 
    } 

    if (signedOrEncrypted) { 
     const char logmsg[] = "Message already signed or encrypted"; 
     syslog(LOG_NOTICE, "%s", logmsg); 
     return; 
    } 

    /* 
    * TODO: 
    * Catch more cases, where an email already could have been encrypted 
    * or signed elsewhere. 
    */ 

    mapfile::Map email {mailFrom}; 

    auto cert = fs::path(email.getSmimeFilename<mapfile::Smime::CERT>()); 
    auto key = fs::path(email.getSmimeFilename<mapfile::Smime::KEY>()); 

    if (!fs::exists(cert) && !fs::is_regular(cert)) 
     return; 
    if (!fs::exists(key) && !fs::is_regular(key)) 
     return; 

    // Signing starts here 

    BIO *in = nullptr, *out = nullptr, *tbio = nullptr; 
    X509 *scert = nullptr; 
    EVP_PKEY *skey = nullptr; 
    PKCS7 *p7 = nullptr; 

    int flags = PKCS7_DETACHED | PKCS7_STREAM; 

    OpenSSL_add_all_algorithms(); 
    ERR_load_crypto_strings(); 

    // S/MIME certificate 
    tbio = BIO_new_file(cert.string().c_str(), "r"); 

    if (!tbio) { 
     std::cerr << "Error: BIO_new_file(Cert) failed" << std::endl; 
     return; 
    } 

    scert = PEM_read_bio_X509(tbio, nullptr, 0, nullptr); 

    // S/MIME key 
    tbio = BIO_new_file(key.string().c_str(), "r"); 

    if (!tbio) { 
     std::cerr << "Error: BIO_new_file(Key) failed" << std::endl; 
     return; 
    } 

    skey = PEM_read_bio_PrivateKey(tbio, nullptr, 0, nullptr); 

    if (!scert || !skey) { 
     std::cerr << "Error: Neither cert or key was loaded" << std::endl; 
     return; 
    } 

    // Loading mail content from temp file 
    in = BIO_new_file(client->getTempFile().c_str(), "r"); 

    if (!in) { 
     std::cerr << "Error: Unable to load content from temp file" 
        << std::endl; 
     return; 
    } 

    // Signing 
    p7 = PKCS7_sign(scert, skey, nullptr, in, flags); 

    if (!p7) { 
     std::cerr << "Error: Message could not be signed" << std::endl; 
     return; 
    } 

    // Cleanup 
    PKCS7_free(p7); 
    X509_free(scert); 
    EVP_PKEY_free(skey); 
    BIO_free(in); 
    BIO_free(out); 
    BIO_free(tbio); 

    smimeSigned = true; 
} 

由於有超過1600人頁OpenSSL的,我不知道到哪裏尋找信息。我很樂意使用「p7」並將它寫入一個簡單的std :: string(或char *,如果需要的話)。我編寫的milter應用程序將拾取此字符串並執行更改主體(尚未寫入,但這是我的想法)。

有人可以指點我的例程/手冊頁,或有代碼示例可以幫助我嗎?

在此先感謝

+0

,我不相信你可以把它放在一個'字符*'因爲有可能是嵌入式'NULL',這將截斷結果。 – jww

+0

示例代碼調出= BIO_new_file(「smout.txt」,「w」); ... SMIME_write_PKCS7(out,p7,in,flags) 結果是base64編碼。我很確定,只有一兩個職能可以完成這項工作。但是哪個? :-) –

回答

2

我喜歡用「P7」,並將其寫入到一個簡單的std :: string(或字符*,如果需要的話)。我編寫的milter應用程序將拾取此字符串並執行更改主體(尚未寫入,但這是我的想法)。

我不相信你可以把它放在char*,因爲可能會嵌入NULL,這會截斷結果。

使用std::string和(1)用於ASN.1/DER或(2)PEM_write_bio_PKCS7用於PEM。想法是像往常一樣使用庫,將輸出寫入MEM_BIO,然後使用BUF_MEM獲取生物的內容。 BUF_MEM包含一個指向數據及其長度的指針。喜歡的東西...您使用PEM_write_bio_PKCS7

using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>; 
using BIO_MEM_BUF_ptr = std::unique_ptr<BUF_MEM, decltype(&::BIO_free)>; 

BIO_MEM_ptr bio(BIO_new(BIO_s_mem()), ::BIO_free); 
int ret = i2d_PKCS7_bio(bio, p7); 
ASSERT(ret == 1); 

BIO_MEM_BUF_ptr buff; 
BIO_get_mem_ptr(bio.get(), &buff.get()); 

const BUF_MEM& t = *buff.get(); 
std::string result((t.data ? t.data : ""), (t.data ? t.length : 0)); 

如果char*,則PEM編碼將缺乏終止NULL。一定要解釋它,因爲它不是一個C字符串。另請參閱Non-printable character after generating random n-byte Base64 string,其中討論瞭如何在不編碼的情況下編寫NULL。


由於有超過1600人頁OpenSSL的,我不知道到哪裏尋找信息...

結帳的子命令的源代碼。它向您展示了圖書館如何處理API。例如,當您使用openssl pkcs7時,它使用pkcs7應用程序。

$ cd <openssl src dir> 
$ cd apps 
$ ls *.c 
app_rand.c dsaparam.c openssl.c rehash.c speed.c 
apps.c  ec.c  opt.c  req.c  spkac.c 
asn1pars.c ecparam.c passwd.c rsa.c  srp.c 
ca.c  enc.c  pkcs12.c rsautl.c ts.c 
ciphers.c engine.c pkcs7.c  s_cb.c  verify.c 
cms.c  errstr.c pkcs8.c  s_client.c version.c 
crl.c  gendsa.c pkey.c  s_server.c vms_decc_init.c 
crl2p7.c genpkey.c pkeyparam.c s_socket.c x509.c 
dgst.c  genrsa.c pkeyutl.c s_time.c 
dhparam.c nseq.c  prime.c  sess_id.c 
dsa.c  ocsp.c  rand.c  smime.c 

使用unique_ptr與析構函數功能確保對象會被自動清除,並有助於保持代碼乾淨。每當OpenSSL用C++跨越路徑時,我都嘗試使用它(另一個示例,請參閱How to generate RSA private key using openssl)。

下面是我的C++項目的一個東西,它使用的OpenSSL:

using EC_KEY_ptr = std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)>; 
using EC_GROUP_ptr = std::unique_ptr<EC_GROUP, decltype(&::EC_GROUP_free)>; 
using EC_POINT_ptr = std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)>; 

using DH_ptr = std::unique_ptr<DH, decltype(&::DH_free)>; 

using RSA_ptr = std::unique_ptr<RSA, decltype(&::RSA_free)>; 

using DSA_ptr = std::unique_ptr<DSA, decltype(&::DSA_free)>; 

using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>; 

using BN_ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>; 

using FILE_ptr = std::unique_ptr<FILE, decltype(&::fclose)>; 

using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>; 
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>; 

using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>; 

using X509_ptr = std::unique_ptr<X509, decltype(&::X509_free)>; 
using ASN1_INTEGER_ptr = std::unique_ptr<ASN1_INTEGER, decltype(&::ASN1_INTEGER_free)>; 
using ASN1_TIME_ptr = std::unique_ptr<ASN1_TIME, decltype(&::ASN1_TIME_free)>; 
using X509_EXTENSION_ptr = std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)>; 

using X509_NAME_ptr = std::unique_ptr<X509_NAME, decltype(&::X509_NAME_free)>; 
using X509_NAME_ENTRY_ptr = std::unique_ptr<X509_NAME_ENTRY, decltype(&::X509_NAME_ENTRY_free)>; 

using X509_STORE_ptr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>; 
using X509_LOOKUP_ptr = std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>; 
using X509_STORE_CTX_ptr = std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>; 
+0

非常感謝你。這太棒了。特別是使用智能指針。通過閱讀你的例子,我學到了很多美妙的東西。明天我會聽取你的建議 –

相關問題