使用 OpenSSL 服务器和 SChannel 客户端的 TLS 会话恢复

TLS Session Resumption with OpenSSL server and SChannel client

我必须使用RFC5077 TLS 会话恢复。我的客户端使用 Windows SChannel,服务器通常使用 OpenSSL。在我的测试中,结果如下。

所以我想知道

服务器代码:Simple TLS Server

客户端代码:Windows C++

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define SECURITY_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h>
#include <sspi.h>
#include <schannel.h>
#include <stdio.h>
#include <vector>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Secur32.lib")

struct WSA {
    WSA() {
        WSADATA wsaData;
        if (auto result = WSAStartup(WINSOCK_VERSION, &wsaData))
            throw result;
    }
    ~WSA() {
        WSACleanup();
    }
};

struct Credential : CredHandle {
    Credential() {
        SCHANNEL_CRED cred = { .dwVersion = SCHANNEL_CRED_VERSION, .dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION };
        if (auto ss = AcquireCredentialsHandleW(nullptr, UNISP_NAME_W, SECPKG_CRED_OUTBOUND, nullptr, &cred, nullptr, nullptr, this, nullptr); ss != SEC_E_OK)
            throw ss;
    }
    ~Credential() {
        FreeCredentialsHandle(this);
    }
};

struct Socket {
    SOCKET s;
    Socket(const char* target, int port) {
        SOCKADDR_IN addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(target);
        s = socket(AF_INET, SOCK_STREAM, 0);
        if (s == INVALID_SOCKET)
            throw WSAGetLastError();
        if (connect(s, reinterpret_cast<const SOCKADDR*>(&addr), sizeof addr))
            throw WSAGetLastError();
        u_long val = 1;
        ioctlsocket(s, FIONBIO, &val);
    }
    ~Socket() {
        closesocket(s);
    }
    auto Read() {
        for (std::vector<unsigned char> result;;) {
            char buffer[2048];
            if (auto read = recv(s, buffer, sizeof buffer, 0); read == 0)
                return result;
            else if (read == SOCKET_ERROR) {
                if (auto lastError = WSAGetLastError(); lastError != WSAEWOULDBLOCK)
                    throw lastError;
                if (!empty(result))
                    return result;
                Sleep(0);
            } else
                result.insert(end(result), buffer, buffer + read);
        }
    }
    void Write(void* data, int length) {
        for (auto p = reinterpret_cast<const char*>(data); 0 < length;) {
            auto sent = send(s, p, length, 0);
            if (sent == 0)
                throw 0;
            else if (sent == SOCKET_ERROR)
                throw WSAGetLastError();
            p += sent;
            length -= sent;
        }
    }
};

int main() {
    WSA wsa;
    Credential credential;

    for (int i = 0; i < 5; i++) {
        Socket socket{ "127.0.0.1", 4433 };
        std::vector<unsigned char> read;
        auto first = true;
        CtxtHandle context;
        for (SECURITY_STATUS ss = SEC_I_CONTINUE_NEEDED; ss == SEC_I_CONTINUE_NEEDED;) {
            SecBuffer inbuf[] = {
                { .BufferType = SECBUFFER_EMPTY },
                { .BufferType = SECBUFFER_EMPTY },
            };
            if (!first) {
                auto data = socket.Read();
                read.insert(end(read), begin(data), end(data));
                inbuf[0] = { static_cast<unsigned long>(read.size()), SECBUFFER_TOKEN, read.data() };
            }
            SecBufferDesc indesc = { SECBUFFER_VERSION, 2, inbuf };
            SecBuffer outbuf = { .BufferType = SECBUFFER_TOKEN };
            SecBufferDesc outdesc = { SECBUFFER_VERSION, 1, &outbuf };
            unsigned long attr = 0;
            ss = InitializeSecurityContextW(&credential, first ? nullptr : &context, L"localhost", ISC_REQ_ALLOCATE_MEMORY, 0, SECURITY_NETWORK_DREP, &indesc, 0, &context, &outdesc, &attr, nullptr);
            if (FAILED(ss))
                throw ss;
            first = false;
            read.erase(begin(read), end(read) - (inbuf[1].BufferType == SECBUFFER_EXTRA ? inbuf[1].cbBuffer : 0));
            if (outbuf.cbBuffer != 0) {
                socket.Write(outbuf.pvBuffer, outbuf.cbBuffer);
                FreeContextBuffer(outbuf.pvBuffer);
            }
        }
        for (;;) {
            SecBuffer buffer[] = {
                { static_cast<unsigned long>(read.size()), SECBUFFER_DATA, read.data() },
                { .BufferType = SECBUFFER_EMPTY },
                { .BufferType = SECBUFFER_EMPTY },
                { .BufferType = SECBUFFER_EMPTY },
            };
            SecBufferDesc desc{ SECBUFFER_VERSION, 4, buffer };
            if (auto ss = DecryptMessage(&context, &desc, 0, nullptr); ss == SEC_I_CONTEXT_EXPIRED)
                break;
            else if (ss == SEC_E_OK) {
                if (buffer[1].BufferType == SECBUFFER_DATA && 0 < buffer[1].cbBuffer && buffer[1].pvBuffer)
                    printf("%.*s", buffer[1].cbBuffer, reinterpret_cast<const char*>(buffer[1].pvBuffer));
                read.erase(begin(read), end(read) - (buffer[3].BufferType == SECBUFFER_EXTRA ? buffer[3].cbBuffer : 0));
            } else if (ss != SEC_E_INCOMPLETE_MESSAGE)
                throw ss;
            if (auto data = socket.Read(); empty(data))
                break;
            else
                read.insert(end(read), begin(data), end(data));
        }
        if (auto ss = DeleteSecurityContext(&context); ss != SEC_E_OK)
            throw ss;
    }
}

我是 ftp 客户端的维护者。某些 ftps 服务器要求 DATA 连接必须重用 CONTROL 连接的 TLS 会话以确保安全。

Windows Update 2019/10, RFC7627 Extended Master Secret 已启用。当 RFC5077 TLS 会话恢复时,SChannel 需要 RFC7627 EMS 支持。

OpenSSL 支持来自 1.1.0 的 RFC7627 扩展主密钥。所以 SChannel 不能重用 OpenSSL 1.0.2 的 TLS 会话。