You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Visual C++中无需OpenSSL获取SMTP服务器(587端口,STARTTLS)证书

不用OpenSSL,用WinHTTP实现SMTP STARTTLS获取证书

没问题,完全可以不依赖OpenSSL,用WinHTTP原生API就能搞定SMTP 587端口的STARTTLS流程并获取服务器证书。和你之前处理隐式SSL(465端口)的流程不同,STARTTLS需要先建立明文连接,再手动触发TLS升级,具体步骤和代码如下:

核心实现思路

  1. 建立明文连接:先以非SSL模式连接到SMTP 587端口,不设置WINHTTP_FLAG_SECURE标记
  2. 协商STARTTLS支持:发送EHLO命令,确认服务器支持STARTTLS扩展
  3. 触发TLS升级:发送STARTTLS命令,收到服务器确认后,调用WinHTTP的升级接口切换到SSL/TLS连接
  4. 获取证书:升级完成后,再用WinHttpQueryOption获取服务器证书上下文

完整代码示例

#include <windows.h>
#include <winhttp.h>
#include <wincrypt.h>
#include <iostream>
#include <string>
#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "crypt32.lib")

int main() {
    const char* szServer = "smtp.gmail.com";
    const int PortNum = 587;

    // Convert server name to wide string
    USES_CONVERSION;
    LPWSTR lpServerName = A2W(szServer);

    BOOL bResults = FALSE;
    HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;

    // 1. 创建WinHTTP会话
    hSession = WinHttpOpen(L"SMTP STARTTLS Client", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hSession) {
        std::cerr << "WinHttpOpen failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 2. 建立明文连接(不使用SSL标记)
    hConnect = WinHttpConnect(hSession, lpServerName, PortNum, 0);
    if (!hConnect) {
        std::cerr << "WinHttpConnect failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 3. 创建明文请求
    hRequest = WinHttpOpenRequest(hConnect, L"POST", NULL, NULL, WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES, 0); // 注意这里不要加WINHTTP_FLAG_SECURE
    if (!hRequest) {
        std::cerr << "WinHttpOpenRequest failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 4. 发送EHLO命令,协商STARTTLS支持
    std::string ehloCmd = "EHLO localhost\r\n";
    bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
        (LPVOID)ehloCmd.c_str(), ehloCmd.length(), ehloCmd.length(), 0);
    if (!bResults) {
        std::cerr << "WinHttpSendRequest (EHLO) failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 接收EHLO响应,确认服务器支持STARTTLS
    bResults = WinHttpReceiveResponse(hRequest, NULL);
    if (!bResults) {
        std::cerr << "WinHttpReceiveResponse (EHLO) failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 读取响应内容(简单检查是否包含STARTTLS)
    char buffer[1024];
    DWORD bytesRead = 0;
    std::string ehloResponse;
    while (WinHttpReadData(hRequest, buffer, sizeof(buffer)-1, &bytesRead) && bytesRead > 0) {
        buffer[bytesRead] = '\0';
        ehloResponse += buffer;
    }
    if (ehloResponse.find("STARTTLS") == std::string::npos) {
        std::cerr << "Server does not support STARTTLS" << std::endl;
        goto Cleanup;
    }

    // 5. 发送STARTTLS命令
    std::string starttlsCmd = "STARTTLS\r\n";
    bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
        (LPVOID)starttlsCmd.c_str(), starttlsCmd.length(), starttlsCmd.length(), 0);
    if (!bResults) {
        std::cerr << "WinHttpSendRequest (STARTTLS) failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 接收STARTTLS响应(服务器应返回220 Ready to start TLS)
    bResults = WinHttpReceiveResponse(hRequest, NULL);
    if (!bResults) {
        std::cerr << "WinHttpReceiveResponse (STARTTLS) failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 6. 升级连接到SSL/TLS
    DWORD dwOptionValue = TRUE;
    bResults = WinHttpSetOption(hRequest, WINHTTP_OPTION_UPGRADE_TO_SECURE,
        &dwOptionValue, sizeof(dwOptionValue));
    if (!bResults) {
        std::cerr << "WinHttpSetOption (UPGRADE_TO_SECURE) failed: " << GetLastError() << std::endl;
        goto Cleanup;
    }

    // 7. 获取服务器证书上下文
    PCCERT_CONTEXT pCert = NULL;
    DWORD dwSize = sizeof(pCert);
    BOOL res = WinHttpQueryOption(hRequest, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &pCert, &dwSize);
    if (res && pCert) {
        std::cout << "Successfully retrieved server certificate!" << std::endl;
        // 这里可以对证书做处理,比如打印证书信息
        // 记得用完后释放证书上下文
        CertFreeCertificateContext(pCert);
    } else {
        std::cerr << "WinHttpQueryOption (CERT_CONTEXT) failed: " << GetLastError() << std::endl;
    }

Cleanup:
    // 释放WinHTTP句柄
    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);

    return 0;
}

关键注意事项

  • 明文连接初始化:创建请求时不要加WINHTTP_FLAG_SECURE,否则会直接尝试SSL握手,不符合STARTTLS流程
  • SMTP命令格式:所有SMTP命令必须以\r\n结尾,这是SMTP协议的强制要求
  • 证书释放:获取到PCCERT_CONTEXT后,一定要调用CertFreeCertificateContext释放资源,避免内存泄漏
  • 错误处理:每一步WinHTTP调用都要检查返回值,根据GetLastError()排查问题

内容的提问来源于stack exchange,提问作者steve_79

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:42:56