簡體   English   中英

TLS Session 使用 OpenSSL 服務器和 SChannel 客戶端恢復

[英]TLS Session Resumption with OpenSSL server and SChannel client

我必須使用 RFC5077 TLS session 恢復。 我的客戶端使用 Windows SChannel,服務器通常使用 OpenSSL。 在我的測試中,結果如下。

  • OpenSSL 1.1.0(或更高版本)和 SChannel:始終重用 session,SChannel 發送以前的 Session Ticket。
  • OpenSSL 1.0.2(任何修訂版)和 Schannel:始終是新的 session,SChannel 不發送 Session 票證。
  • OpenSSL 和 OpenSSL:始終重復使用 session。

所以我想知道

  • 為什么 Schannel 僅對 OpenSSL 1.0.2 不使用 TLS session 恢復?
  • 1.0.2 和 1.1.0 的區別。
  • 如何在 OpenSSL 1.0.2 和 SChannel 中使用 TLS session 恢復?

服務器代碼:簡單 TLS 服務器

客戶端代碼: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 session 以確保安全。

Windows 更新 2019/10中,啟用了 RFC7627 Extended Master Secret 當 RFC5077 TLS Session 恢復時,SChannel 需要 RFC7627 EMS 支持。

OpenSSL 支持來自1.1.0的 RFC7627 擴展主密鑰。 所以 SChannel 不能在 OpenSSL 1.0.2 中重用 TLS session。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM