2
votes

I must use the RFC5077 TLS session resumption. My Client use Windows SChannel and server usually uses OpenSSL. In my test, following result.

  • OpenSSL 1.1.0 (or later) and SChannel: Always session reused, SChannel send previous Session Ticket.
  • OpenSSL 1.0.2 (any revision) and Schannel: Always new session, SChannel does not send Session Ticket.
  • OpenSSL and OpenSSL: Always session reused.

So I want to know that

  • Why Schannel don't use TLS session resumption only for OpenSSL 1.0.2?
  • The difference between 1.0.2 and 1.1.0.
  • How to use TLS session resumption at OpenSSL 1.0.2 and SChannel?

Server code: Simple TLS Server

Client code: 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;
    }
}

I'm a maintainer of ftp client. Some ftps server requires that DATA connection must reuse the TLS session of CONTROL connection for security.

1

1 Answers

2
votes

At Windows Update 2019/10, RFC7627 Extended Master Secret was enabled. SChannel requires RFC7627 EMS support when RFC5077 TLS Session Resumption.

OpenSSL suport RFC7627 extended master secret from 1.1.0. So SChannel cannot reuse TLS session with OpenSSL 1.0.2.