SSL_use_certificate似乎导致双重免费

时间:2015-04-06 04:46:23

标签: c++ ssl openssl boost-asio

某些情境
我正在使用openSSL在C ++中编写一个透明/拦截,支持HTTPS的代理。我使用WinDivert通过我的代理重定向流量。对于我的SSL初始化,我的HTTPSAcceptor为整个服务器上下文生成一个临时EC_KEY,用于握手操作。我保留了一个内存"商店" (不是实际的X509_STORE对象)我使用主机/ SAN DNS条目作为查找键来欺骗和存储证书。作为旁注,这是我第一次使用openSSL,所以请纠正并赦免我的方法中的任何无知。 :)同时原谅过度滥用cout因为调试/错误,这些将在以后被包装到记录器中。

肉类
无论如何,当我得到一个传入的HTTPS连接时,我要么检索或欺骗,然后检索真正的上游证书。当我生成这些证书时,我使用的是EC密钥。 Les代码:

EC_KEY *ecdh = NULL;

if ((ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)) == NULL || EC_KEY_generate_key(ecdh) != 1)
{
    std::cout << "In CertStore::GenerateEcKey() - Failed to generate EC_KEY" << std::endl;
}
else
{
    EC_KEY_set_asn1_flag(ecdh, OPENSSL_EC_NAMED_CURVE);

    EVP_PKEY* pkey = NULL;

    pkey = EVP_PKEY_new();

    if (pkey == nullptr)
    {
        std::cout << "In CertStore::GenerateEcKey() - Failed to generate EVP_PKEY" << std::endl;
    }
    else
    {
        if (1 != EVP_PKEY_set1_EC_KEY(pkey, ecdh))
        {
            std::cout << "In CertStore::GenerateEcKey() - Failed EVP_PKEY_set1_EC_KEY" << std::endl;
            EVP_PKEY_free(pkey);
            return nullptr;
        }else{

            EC_KEY_up_ref(ecdh);
            return pkey;
        }
    }
}

成功获取欺骗证书&amp;相关的密钥,然后我告诉我的SSL *对象使用这些来进行握手,显然。

if (SSL_use_PrivateKey(m_secureDownstreamSocket->native_handle(), upKey) <= 0)
{
    std::cout << "set private key failed" << std::endl;
    Kill();
    return;
}

if (SSL_use_certificate(m_secureDownstreamSocket->native_handle(), upCert) <= 0)
{
    std::cout << "set use cert failed" << std::endl;
    Kill();
    return;
}

m_secureDownstreamSocket->async_handshake(SslSocket::server, m_strand.wrap(boost::bind(&HttpsBridge::OnDownstreamHandshake, shared_from_this(), boost::asio::placeholders::error)));

然而,这个原因似乎是我申请死于可怕死亡的原因。我曾经在每个HTTPS连接(客户端和服务器)上生成一个新的CTX,但是在对文档和一些SO帖子进行一些阅读之后,我被认为正确的方法是使用全局上下文来创建SSL对象。无论如何,提到这一点的原因是,我很快就会出现的错误,在我愚蠢和创造大量CTX时很少发生。由于更改为两个全局CTX(客户端,服务器),此错误现在发生得非常快,但仍然是随机点。

错误是,不知何故,来自键的EC_GROUP被双重释放。问题是,我甚至不知道为什么他们首先被释放。我在任何SSL_ *或SSL_CTX *方法的文档中都没有提及我使用任何东西。下面是App Verifier的事件跟踪,因为Eclipse在调试时无用,而Visual Studio调试器在我拦截和处理本地网络流量时只是以某种方式拒绝工作。请互相帮助。 :(

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<avrf:logfile xmlns:avrf="Application Verifier">
    <avrf:logSession TimeStarted="2015-04-05 : 23:51:30" PID="812" Version="2">
        <avrf:logEntry Time="2015-04-05 : 23:51:57" LayerName="Heaps" StopCode="0x7" Severity="Error">
            <avrf:message>Heap block already freed.</avrf:message>
            <avrf:parameter1>8411000 - Heap handle for the heap owning the block.</avrf:parameter1>
            <avrf:parameter2>aac49270 - Heap block being freed again.</avrf:parameter2>
            <avrf:parameter3>20 - Size of the heap block.</avrf:parameter3>
            <avrf:parameter4>0 - Not used</avrf:parameter4>
            <avrf:stackTrace>
                <avrf:trace>vrfcore!VerifierDisableVerifier+948 ( @ 0)</avrf:trace>
                <avrf:trace>verifier!VerifierStopMessage+a0 ( @ 0)</avrf:trace>
                <avrf:trace>verifier!VerifierDisableFaultInjectionExclusionRange+318b ( @ 0)</avrf:trace>
                <avrf:trace>verifier!VerifierDisableFaultInjectionExclusionRange+8a6 ( @ 0)</avrf:trace>
                <avrf:trace>verifier!VerifierDisableFaultInjectionExclusionRange+94b ( @ 0)</avrf:trace>
                <avrf:trace>verifier!VerifierCheckPageHeapAllocation+40 ( @ 0)</avrf:trace>
                <avrf:trace>vfbasics!+7ff99e7f3773 ( @ 0)</avrf:trace>
                <avrf:trace>msvcrt!setjmp+123 ( @ 0)</avrf:trace>
                <avrf:trace>vfbasics!+7ff99e7f4606 ( @ 0)</avrf:trace>
                <avrf:trace>LIBEAY32!CRYPTO_free+2b ( @ 0)</avrf:trace>
                <avrf:trace>LIBEAY32!BN_free+29 ( @ 0)</avrf:trace>
                <avrf:trace>LIBEAY32!EC_GROUP_cmp+307 ( @ 0)</avrf:trace>
                <avrf:trace>LIBEAY32!EC_GROUP_free+2c ( @ 0)</avrf:trace>
                <avrf:trace>LIBEAY32!EC_KEY_set_group+2b ( @ 0)</avrf:trace>
                <avrf:trace>LIBEAY32!EC_GF2m_simple_method+180f ( @ 0)</avrf:trace>
                <avrf:trace>SSLEAY32!SSL_use_PrivateKey_ASN1+1a5 ( @ 0)</avrf:trace>
                <avrf:trace>SSLEAY32!SSL_use_certificate+9a ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+407ef6 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+4a2dbf ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+4a2e0a ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+45b6b1 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+45c50e ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+488870 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+461241 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+451908 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+47d3a0 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+451938 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+472739 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+45e9c4 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+474001 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+4a4098 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+465a7d ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+488af1 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+47774c ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+461001 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+451488 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+47ce40 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+4514b8 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+478de7 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+470f8b ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+45e2c7 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+47d3f4 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+451e18 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+464f44 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+451e48 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+4819b1 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+47cc68 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+47f2d2 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+47ecb8 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+45db6c ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+4a2c75 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+45b32c ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+45ce36 ( @ 0)</avrf:trace>
                <avrf:trace>SSITM!+48ce4e ( @ 0)</avrf:trace>
                <avrf:trace>libboost_thread!ZN5boost6detail23get_current_thread_dataEv+729 ( @ 0)</avrf:trace>
                <avrf:trace>msvcrt!strupr+c3 ( @ 0)</avrf:trace>
                <avrf:trace>msvcrt!endthreadex+9d ( @ 0)</avrf:trace>
                <avrf:trace>vfbasics!+7ff99e7fc729 ( @ 0)</avrf:trace>
                <avrf:trace>KERNEL32!BaseThreadInitThunk+22 ( @ 0)</avrf:trace>
                <avrf:trace>ntdll!RtlUserThreadStart+34 ( @ 0)</avrf:trace>
            </avrf:stackTrace>
        </avrf:logEntry>
    </avrf:logSession>
</avrf:logfile>

P.S。 - 对我来说奇怪的一件事是堆栈跟踪显示对SSL_use_PrivateKey_ASN1的调用。不知道为什么,因为我只调用每个SSL_use_cert和SSL_use_prvkey。可能是use_cert试图从证书中提取私钥吗?只是有了这个想法,我将调查更新 - 没有。由于安全性方面的原因,无法将私钥添加到X509结构中。是一个绝望的,没有思想的想法。

2 个答案:

答案 0 :(得分:2)

我被这个困扰了,我感谢你的后续回答。

我的解决方案,而不是删除创建它的析构函数中的ssl :: context实例,我将删除发布到主io_service:

类似的东西:

[
    DMFTargetTransformationAttribute(true),
    DMFTargetTransformationDescAttribute('function that determines LineAmount for export'),
    DMFTargetTransformationSequenceAttribute(11),
    DMFTargetTransFieldListAttribute([fieldStr(DMFVendPackingSlipTransEntity, LineAmount)])
]
public container GenerateLineAmount(boolean _stagingToTarget = true)
{
    container                  res;
    PurchLine                  purchLine;
    VendPackingSlipTrans       vendPackingSlipTrans;

    if (_stagingToTarget)
    {
        // this will be executed during import
        res = [0.0];
    }
    else
    {
        // this will be executed during export
        // the target variable contains the VendPackingSlipTrans that is exported
        vendPackingSlipTrans = target;
        purchLine = vendPackingSlipTrans.purchLine();
        if (purchLine)
        {
            res = [purchLine.LineAmount];
        }
    }
    return res;
}
似乎治愈它对我来说很好。 (我使用new / delete因为我只根据需要创建它。)

我认为必须有一种更确定的方式来做这件事,也许是shared_ptr,但我还没有解决这个问题。

答案 1 :(得分:0)

所以,Michael Foukarakis帮助我质疑我在评论中的一些假设,这些假设最终导致我解决了这个问题,因此给予了充分的信任。这是我在我的方法中出错的地方以及我如何解决它。

正如问题所述,我最初为每个代理连接创建一个新的SSL_CTXboost::asio::ssl::context)对象:一个用于下游,一个用于下游。作为客户端的代理上游在初始化期间对其上下文有boost::asio::ssl::context::load_verify_file"ca-bundle.crt"),这导致应用程序的内存消耗大量膨胀。

原始问题的第二部分是我为每个下游连接创建了一个新的SSL_CTX,与我们的客户端的连接,我们正在提供欺骗性证书和伪装成原始服务器的握手。每个新连接“桥”都被提交给构造上的“CertStore”对象,该对象旨在欺骗,存储和检索由主机名索引的证书及其密钥对。

所以我们发生的事情是持有证书和密钥的中心位置,但是每个单独的SSL_CTX对象都被分配了这些证书和密钥,然后在连接关闭时销毁,最终“随机”将导致因为openSSL使用内部引用计数(大多数?)对象,所以在某处可以使用双重自由。创建SSL_CTX后会增加和减少这些引用,并在其上调用SSL_CTX_use_certificateSSL_CTX_use_PrivateKey,然后销毁。最终,两个SSL_CTX的死亡将持有对相同证书或密钥对的引用,引用计数为1,当两个上下文最终都死亡时,导致内存双倍释放。

解决方案是使用单个共享上下文,在上游(客户端)连接的应用程序关闭之前,您可以保持安全和活动状态。在该单个上下文上调用boost::asio::ssl::context::load_verify_file"ca-bundle.crt")一次,然后从中生成所有客户端SSL对象。对于服务器上下文,创建一个您正在充当的每个主机的服务器上下文,设置该上下文的证书和私钥,然后在代表该主机的所有下游SSL *套接字之间共享该上下文。