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



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

  • OpenSSL 1.1.0(或更高版本(和SChannel:始终重用会话,SChannel发送以前的会话票证
  • OpenSSL 1.0.2(任何修订版(和Schannel:始终是新会话,Schannel不发送会话票证
  • OpenSSL和OpenSSL:始终重用会话

所以我想知道

  • 为什么Schannel不只在OpenSSL 1.0.2中使用TLS会话恢复
  • 1.0.2和1.1.0之间的差异
  • 如何在OpenSSL 1.0.2和SChannel上使用TLS会话恢复

服务器代码:简单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会话以确保安全。

在Windows Update 2019/10中,RFC7627扩展主密钥已启用。当RFC5077 TLS会话恢复时,SChannel需要RFC7627 EMS支持。

OpenSSL支持RFC7627从1.1.0扩展了主密钥。因此SChannel无法在OpenSSL 1.0.2中重用TLS会话。

最新更新