1757 lines
55 KiB
C
1757 lines
55 KiB
C
#include <vschannel.h>
|
|
#include <sspi.h>
|
|
|
|
// ALPN (RFC 7301) compatibility shim. Older toolchain headers (notably the
|
|
// ones bundled with tcc) predate the SChannel ALPN additions, so the structs,
|
|
// enums and constants below are missing there. Define them ourselves when the
|
|
// SDK headers did not. SECPKG_ATTR_APPLICATION_PROTOCOL guards the schannel.h
|
|
// types; SECBUFFER_APPLICATION_PROTOCOLS guards the sspi.h buffer constant.
|
|
#ifndef ANYSIZE_ARRAY
|
|
#define ANYSIZE_ARRAY 1
|
|
#endif
|
|
|
|
#ifndef SECPKG_ATTR_APPLICATION_PROTOCOL
|
|
#define SECPKG_ATTR_APPLICATION_PROTOCOL 35
|
|
|
|
typedef enum _SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT {
|
|
SecApplicationProtocolNegotiationExt_None,
|
|
SecApplicationProtocolNegotiationExt_NPN,
|
|
SecApplicationProtocolNegotiationExt_ALPN
|
|
} SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT, *PSEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT;
|
|
|
|
typedef struct _SEC_APPLICATION_PROTOCOL_LIST {
|
|
SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT ProtoNegoExt;
|
|
unsigned short ProtocolListSize;
|
|
unsigned char ProtocolList[ANYSIZE_ARRAY];
|
|
} SEC_APPLICATION_PROTOCOL_LIST, *PSEC_APPLICATION_PROTOCOL_LIST;
|
|
|
|
typedef struct _SEC_APPLICATION_PROTOCOLS {
|
|
unsigned long ProtocolListsSize;
|
|
SEC_APPLICATION_PROTOCOL_LIST ProtocolLists[ANYSIZE_ARRAY];
|
|
} SEC_APPLICATION_PROTOCOLS, *PSEC_APPLICATION_PROTOCOLS;
|
|
|
|
typedef enum _SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS {
|
|
SecApplicationProtocolNegotiationStatus_None,
|
|
SecApplicationProtocolNegotiationStatus_Success,
|
|
SecApplicationProtocolNegotiationStatus_SelectedClientOnly
|
|
} SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS, *PSEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS;
|
|
|
|
#define MAX_PROTOCOL_ID_SIZE 0xff
|
|
|
|
typedef struct _SecPkgContext_ApplicationProtocol {
|
|
SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS ProtoNegoStatus;
|
|
SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT ProtoNegoExt;
|
|
unsigned char ProtocolIdSize;
|
|
unsigned char ProtocolId[MAX_PROTOCOL_ID_SIZE];
|
|
} SecPkgContext_ApplicationProtocol, *PSecPkgContext_ApplicationProtocol;
|
|
#endif // SECPKG_ATTR_APPLICATION_PROTOCOL
|
|
|
|
#ifndef SECBUFFER_APPLICATION_PROTOCOLS
|
|
#define SECBUFFER_APPLICATION_PROTOCOLS 18
|
|
#endif
|
|
|
|
// Proxy
|
|
WCHAR * psz_proxy_server = L"proxy";
|
|
INT i_proxy_port = 80;
|
|
|
|
// Options
|
|
INT port_number = 443;
|
|
BOOL use_proxy = FALSE;
|
|
DWORD protocol = 0;
|
|
ALG_ID aid_key_exch = 0;
|
|
|
|
// TODO: joe-c
|
|
// socket / tls ctx
|
|
struct TlsContext {
|
|
// SSPI
|
|
PSecurityFunctionTable sspi;
|
|
// Cred store
|
|
HCERTSTORE cert_store;
|
|
SCHANNEL_CRED schannel_cred;
|
|
// Socket
|
|
SOCKET socket;
|
|
CredHandle h_client_creds;
|
|
CtxtHandle h_context;
|
|
PCCERT_CONTEXT p_pemote_cert_context;
|
|
INT last_error_code;
|
|
BOOL validate_server_certificate;
|
|
BOOL creds_initialized;
|
|
BOOL context_initialized;
|
|
// ALPN protocol list to advertise, in the standard ALPN wire format (each
|
|
// name 1-byte length-prefixed), e.g. "\x02h2\x08http/1.1". alpn_wire_len == 0
|
|
// means "do not advertise ALPN".
|
|
unsigned char alpn_wire[256];
|
|
unsigned long alpn_wire_len;
|
|
// Negotiated application protocol name (e.g. "h2"); negotiated_alpn_len == 0
|
|
// when the server selected none.
|
|
char negotiated_alpn[256];
|
|
unsigned long negotiated_alpn_len;
|
|
// Streaming transport state, used by the keep-the-connection-open path
|
|
// (vschannel_h2_connect / vschannel_write / vschannel_read) that backs the
|
|
// HTTP/2 driver. The one-shot request() path does not touch these.
|
|
SecPkgContext_StreamSizes stream_sizes; // cached TLS record sizes
|
|
BOOL stream_sizes_valid;
|
|
// Ciphertext staging buffer: bytes recv()'d from the socket that have not yet
|
|
// been decrypted into a full record (SEC_E_INCOMPLETE_MESSAGE) plus any
|
|
// trailing SECBUFFER_EXTRA from the last DecryptMessage.
|
|
unsigned char *recv_buf;
|
|
unsigned long recv_buf_cap;
|
|
unsigned long recv_buf_len;
|
|
// Decrypted plaintext carryover: a single DecryptMessage can yield more
|
|
// application bytes than the caller's read buffer can hold, so the remainder
|
|
// is stashed here and drained on the next vschannel_read().
|
|
unsigned char *plain_buf;
|
|
unsigned long plain_buf_cap;
|
|
unsigned long plain_buf_len; // valid decrypted bytes
|
|
unsigned long plain_buf_off; // bytes already returned to caller
|
|
// Reusable encryption buffer for vschannel_write(): one full record
|
|
// (header + max message + trailer). Cached so the HTTP/2 driver's many small
|
|
// writes do not LocalAlloc/LocalFree on every call.
|
|
unsigned char *send_buf;
|
|
unsigned long send_buf_cap;
|
|
BOOL stream_eof; // close_notify / context expired seen
|
|
};
|
|
|
|
TlsContext new_tls_context() {
|
|
return (struct TlsContext) {
|
|
.cert_store = NULL,
|
|
.last_error_code = 0,
|
|
.socket = INVALID_SOCKET,
|
|
.validate_server_certificate = TRUE,
|
|
.creds_initialized = FALSE,
|
|
.context_initialized = FALSE,
|
|
.p_pemote_cert_context = NULL,
|
|
.alpn_wire_len = 0,
|
|
.negotiated_alpn_len = 0,
|
|
.stream_sizes_valid = FALSE,
|
|
.recv_buf = NULL,
|
|
.recv_buf_cap = 0,
|
|
.recv_buf_len = 0,
|
|
.plain_buf = NULL,
|
|
.plain_buf_cap = 0,
|
|
.plain_buf_len = 0,
|
|
.plain_buf_off = 0,
|
|
.send_buf = NULL,
|
|
.send_buf_cap = 0,
|
|
.stream_eof = FALSE
|
|
};
|
|
};
|
|
|
|
// vschannel_alpn_supported reports whether this Windows version's SChannel
|
|
// supports client-side ALPN, which was introduced in Windows 8.1 / Server
|
|
// 2012 R2 (version 6.3). On older versions, passing a
|
|
// SECBUFFER_APPLICATION_PROTOCOLS input buffer into the handshake can fail it
|
|
// outright, so callers should skip ALPN (and HTTP/2) entirely there. Uses
|
|
// RtlGetVersion because GetVersionEx lies on manifest-less binaries from
|
|
// Windows 8.1 onwards.
|
|
INT vschannel_alpn_supported() {
|
|
static INT cached = -1;
|
|
if (cached < 0) {
|
|
typedef LONG (WINAPI *RtlGetVersionFn)(OSVERSIONINFOW *);
|
|
OSVERSIONINFOW vi;
|
|
RtlGetVersionFn get_version = (RtlGetVersionFn)GetProcAddress(
|
|
GetModuleHandleW(L"ntdll.dll"), "RtlGetVersion");
|
|
INT supported = 0;
|
|
if (get_version != NULL) {
|
|
ZeroMemory(&vi, sizeof(vi));
|
|
vi.dwOSVersionInfoSize = sizeof(vi);
|
|
if (get_version(&vi) == 0
|
|
&& (vi.dwMajorVersion > 6
|
|
|| (vi.dwMajorVersion == 6 && vi.dwMinorVersion >= 3))) {
|
|
supported = 1;
|
|
}
|
|
}
|
|
cached = supported;
|
|
}
|
|
return cached;
|
|
}
|
|
|
|
// vschannel_set_alpn configures the ALPN protocol list to advertise during the
|
|
// next handshake. `wire` is the standard ALPN wire format (each protocol name
|
|
// preceded by a 1-byte length), e.g. "\x02h2\x08http/1.1". Passing len == 0
|
|
// disables ALPN advertisement.
|
|
void vschannel_set_alpn(TlsContext *tls_ctx, const char *wire, INT len) {
|
|
if (len < 0) {
|
|
len = 0;
|
|
}
|
|
if (len > (INT)sizeof(tls_ctx->alpn_wire)) {
|
|
len = (INT)sizeof(tls_ctx->alpn_wire);
|
|
}
|
|
if (len > 0) {
|
|
memcpy(tls_ctx->alpn_wire, wire, (size_t)len);
|
|
}
|
|
tls_ctx->alpn_wire_len = (unsigned long)len;
|
|
}
|
|
|
|
// vschannel_get_alpn copies the protocol the server selected via ALPN (e.g.
|
|
// "h2") into `out` and returns its length, or 0 if none was negotiated.
|
|
INT vschannel_get_alpn(TlsContext *tls_ctx, char *out, INT out_cap) {
|
|
unsigned long n = tls_ctx->negotiated_alpn_len;
|
|
if (out_cap < 0) {
|
|
out_cap = 0;
|
|
}
|
|
if (n > (unsigned long)out_cap) {
|
|
n = (unsigned long)out_cap;
|
|
}
|
|
if (n > 0) {
|
|
memcpy(out, tls_ctx->negotiated_alpn, (size_t)n);
|
|
}
|
|
return (INT)n;
|
|
}
|
|
|
|
// vschannel_capture_alpn queries the negotiated ALPN protocol from a completed
|
|
// handshake and stores it on the context for vschannel_get_alpn().
|
|
static void vschannel_capture_alpn(TlsContext *tls_ctx) {
|
|
SecPkgContext_ApplicationProtocol appproto;
|
|
SECURITY_STATUS st;
|
|
|
|
tls_ctx->negotiated_alpn_len = 0;
|
|
st = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context,
|
|
SECPKG_ATTR_APPLICATION_PROTOCOL, &appproto);
|
|
if (st == SEC_E_OK
|
|
&& appproto.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success
|
|
&& appproto.ProtocolIdSize > 0
|
|
&& appproto.ProtocolIdSize <= sizeof(tls_ctx->negotiated_alpn)) {
|
|
memcpy(tls_ctx->negotiated_alpn, appproto.ProtocolId, appproto.ProtocolIdSize);
|
|
tls_ctx->negotiated_alpn_len = appproto.ProtocolIdSize;
|
|
}
|
|
}
|
|
|
|
static void vschannel_clear_last_error(TlsContext *tls_ctx) {
|
|
tls_ctx->last_error_code = 0;
|
|
}
|
|
|
|
static void vschannel_set_last_error(TlsContext *tls_ctx, INT err_code) {
|
|
tls_ctx->last_error_code = err_code;
|
|
}
|
|
|
|
static INT vschannel_last_error(TlsContext *tls_ctx) {
|
|
return tls_ctx->last_error_code;
|
|
}
|
|
|
|
void vschannel_cleanup(TlsContext *tls_ctx) {
|
|
// Free the server certificate context.
|
|
if(tls_ctx->p_pemote_cert_context) {
|
|
CertFreeCertificateContext(tls_ctx->p_pemote_cert_context);
|
|
tls_ctx->p_pemote_cert_context = NULL;
|
|
}
|
|
|
|
// Free SSPI context handle.
|
|
if(tls_ctx->context_initialized) {
|
|
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
|
|
tls_ctx->context_initialized = FALSE;
|
|
}
|
|
|
|
// Free SSPI credentials handle.
|
|
if(tls_ctx->creds_initialized) {
|
|
tls_ctx->sspi->FreeCredentialsHandle(&tls_ctx->h_client_creds);
|
|
tls_ctx->creds_initialized = FALSE;
|
|
}
|
|
|
|
// Close socket.
|
|
if(tls_ctx->socket != INVALID_SOCKET) {
|
|
closesocket(tls_ctx->socket);
|
|
tls_ctx->socket = INVALID_SOCKET;
|
|
}
|
|
|
|
// Close "MY" certificate store.
|
|
if(tls_ctx->cert_store) {
|
|
CertCloseStore(tls_ctx->cert_store, 0);
|
|
tls_ctx->cert_store = NULL;
|
|
}
|
|
|
|
// Free streaming-transport buffers.
|
|
if(tls_ctx->recv_buf) {
|
|
LocalFree(tls_ctx->recv_buf);
|
|
tls_ctx->recv_buf = NULL;
|
|
}
|
|
tls_ctx->recv_buf_cap = 0;
|
|
tls_ctx->recv_buf_len = 0;
|
|
if(tls_ctx->plain_buf) {
|
|
LocalFree(tls_ctx->plain_buf);
|
|
tls_ctx->plain_buf = NULL;
|
|
}
|
|
tls_ctx->plain_buf_cap = 0;
|
|
tls_ctx->plain_buf_len = 0;
|
|
tls_ctx->plain_buf_off = 0;
|
|
if(tls_ctx->send_buf) {
|
|
LocalFree(tls_ctx->send_buf);
|
|
tls_ctx->send_buf = NULL;
|
|
}
|
|
tls_ctx->send_buf_cap = 0;
|
|
tls_ctx->stream_sizes_valid = FALSE;
|
|
tls_ctx->stream_eof = FALSE;
|
|
}
|
|
|
|
void vschannel_init(TlsContext *tls_ctx, BOOL validate_server_certificate) {
|
|
tls_ctx->sspi = InitSecurityInterface();
|
|
tls_ctx->validate_server_certificate = validate_server_certificate;
|
|
|
|
if(tls_ctx->sspi == NULL) {
|
|
wprintf(L"Error 0x%x reading security interface.\n",
|
|
GetLastError());
|
|
vschannel_cleanup(tls_ctx);
|
|
}
|
|
|
|
// Create credentials.
|
|
if(create_credentials(tls_ctx)) {
|
|
wprintf(L"Error creating credentials\n");
|
|
vschannel_cleanup(tls_ctx);
|
|
}
|
|
tls_ctx->creds_initialized = TRUE;
|
|
}
|
|
|
|
// vschannel_open_and_handshake performs the connection setup shared by
|
|
// request(), vschannel_alpn_probe() and vschannel_h2_connect(): connect to
|
|
// host:iport, run the TLS handshake (advertising any configured ALPN), record
|
|
// the negotiated protocol, and — when verify_cert is set — validate the server
|
|
// certificate. On success the connection is left open (context_initialized) and,
|
|
// when pExtraData is non-NULL, it receives any application bytes the handshake
|
|
// bundled with its final flight (the caller then owns pExtraData->pvBuffer and
|
|
// must LocalFree it). On failure it records the error, frees any bundled extra,
|
|
// tears the connection down, and returns a non-zero SECURITY_STATUS. A connect
|
|
// failure already set last_error via connect_to_server.
|
|
static SECURITY_STATUS vschannel_open_and_handshake(TlsContext *tls_ctx, INT iport, LPWSTR host, BOOL verify_cert, SecBuffer *pExtraData) {
|
|
SecBuffer local_extra;
|
|
SecBuffer *extra = pExtraData ? pExtraData : &local_extra;
|
|
SECURITY_STATUS Status;
|
|
|
|
extra->pvBuffer = NULL;
|
|
extra->cbBuffer = 0;
|
|
|
|
protocol = SP_PROT_TLS1_2_CLIENT;
|
|
port_number = iport;
|
|
vschannel_clear_last_error(tls_ctx);
|
|
|
|
if(connect_to_server(tls_ctx, host, port_number)) {
|
|
vschannel_cleanup(tls_ctx);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
Status = perform_client_handshake(tls_ctx, host, extra);
|
|
if(Status != SEC_E_OK) {
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
goto fail;
|
|
}
|
|
tls_ctx->context_initialized = TRUE;
|
|
|
|
// Record the ALPN protocol the server selected (if any).
|
|
vschannel_capture_alpn(tls_ctx);
|
|
|
|
if(verify_cert) {
|
|
// Get and validate the server's certificate.
|
|
Status = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context,
|
|
SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&tls_ctx->p_pemote_cert_context);
|
|
if(Status != SEC_E_OK) {
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
goto fail;
|
|
}
|
|
Status = verify_server_certificate(tls_ctx->p_pemote_cert_context, host, 0);
|
|
if(Status != SEC_E_OK) {
|
|
// Could not authenticate the server (possible MITM): abort.
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
goto fail;
|
|
}
|
|
CertFreeCertificateContext(tls_ctx->p_pemote_cert_context);
|
|
tls_ctx->p_pemote_cert_context = NULL;
|
|
}
|
|
|
|
// If the caller does not want the bundled application data, drop it.
|
|
if(pExtraData == NULL && local_extra.pvBuffer != NULL) {
|
|
LocalFree(local_extra.pvBuffer);
|
|
}
|
|
return SEC_E_OK;
|
|
|
|
fail:
|
|
if(extra->pvBuffer != NULL) {
|
|
LocalFree(extra->pvBuffer);
|
|
extra->pvBuffer = NULL;
|
|
}
|
|
vschannel_cleanup(tls_ctx);
|
|
return Status;
|
|
}
|
|
|
|
INT request(TlsContext *tls_ctx, INT iport, LPWSTR host, CHAR *req, DWORD req_len, CHAR **out, vschannel_allocator afn)
|
|
{
|
|
SECURITY_STATUS Status;
|
|
INT resp_length = 0;
|
|
|
|
// Connect + handshake (+ cert validation when enabled). request() does not
|
|
// consume handshake-bundled application data (HTTP/1.1 servers do not send
|
|
// before the request), so pass NULL to have it dropped.
|
|
if(vschannel_open_and_handshake(tls_ctx, iport, host,
|
|
tls_ctx->validate_server_certificate, NULL) != SEC_E_OK) {
|
|
return resp_length;
|
|
}
|
|
|
|
// Request from server
|
|
Status = https_make_request(tls_ctx, req, req_len, out, &resp_length, afn);
|
|
if(Status) {
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
vschannel_cleanup(tls_ctx);
|
|
return resp_length;
|
|
}
|
|
|
|
// Send a close_notify alert to the server and
|
|
// close down the connection.
|
|
Status = disconnect_from_server(tls_ctx);
|
|
if(Status) {
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
wprintf(L"Error disconnecting from server\n");
|
|
vschannel_cleanup(tls_ctx);
|
|
return resp_length;
|
|
}
|
|
tls_ctx->context_initialized = FALSE;
|
|
tls_ctx->socket = INVALID_SOCKET;
|
|
|
|
return resp_length;
|
|
}
|
|
|
|
// vschannel_request_on_open runs a one-shot HTTP/1.1 request over a connection
|
|
// that vschannel_h2_connect() already opened and handshaked, then closes it.
|
|
// It is the HTTP/1.1 fallback used when a server, asked for ALPN `h2`, does not
|
|
// select it: rather than reconnect, we reuse the open connection. Returns the
|
|
// response length (see request()).
|
|
INT vschannel_request_on_open(TlsContext *tls_ctx, CHAR *req, DWORD req_len, CHAR **out, vschannel_allocator afn) {
|
|
SECURITY_STATUS Status;
|
|
INT resp_length = 0;
|
|
|
|
Status = https_make_request(tls_ctx, req, req_len, out, &resp_length, afn);
|
|
if(Status) {
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
vschannel_cleanup(tls_ctx);
|
|
return resp_length;
|
|
}
|
|
|
|
Status = disconnect_from_server(tls_ctx);
|
|
if(Status) {
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
vschannel_cleanup(tls_ctx);
|
|
return resp_length;
|
|
}
|
|
tls_ctx->context_initialized = FALSE;
|
|
tls_ctx->socket = INVALID_SOCKET;
|
|
|
|
return resp_length;
|
|
}
|
|
|
|
// vschannel_alpn_probe connects to host:iport, performs the TLS handshake while
|
|
// advertising whatever ALPN list was configured via vschannel_set_alpn(),
|
|
// captures the protocol the server selected into `out` (up to out_cap bytes),
|
|
// and disconnects without sending an application request. Returns the
|
|
// negotiated protocol length (0 = handshake succeeded but no protocol selected),
|
|
// or -1 on connect/handshake failure (see vschannel_last_error). Intended for
|
|
// tests and capability checks, since request() only speaks HTTP/1.1.
|
|
INT vschannel_alpn_probe(TlsContext *tls_ctx, INT iport, LPWSTR host, char *out, INT out_cap) {
|
|
// Probe only: handshake (no cert validation, no application data) then close.
|
|
if(vschannel_open_and_handshake(tls_ctx, iport, host, FALSE, NULL) != SEC_E_OK) {
|
|
return -1;
|
|
}
|
|
|
|
disconnect_from_server(tls_ctx);
|
|
tls_ctx->context_initialized = FALSE;
|
|
tls_ctx->socket = INVALID_SOCKET;
|
|
|
|
return vschannel_get_alpn(tls_ctx, out, out_cap);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Streaming transport (keep the TLS connection open and exchange raw bytes).
|
|
//
|
|
// request() above is a one-shot: connect, handshake, send the whole HTTP/1.1
|
|
// request, read the whole response, disconnect. An HTTP/2 driver instead needs
|
|
// a long-lived, byte-oriented transport. The functions below expose exactly
|
|
// that: vschannel_h2_connect() opens the connection (handshake + cert check)
|
|
// and leaves it open, vschannel_write()/vschannel_read() move application bytes
|
|
// across it, and vschannel_h2_close() shuts it down. They reuse the same SSPI
|
|
// primitives as request()/https_make_request(), but keep the encrypt/decrypt
|
|
// state on the TlsContext so reads can span calls.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// vschannel_ensure_stream_state caches the negotiated TLS record sizes and
|
|
// allocates the ciphertext/plaintext working buffers, once per connection.
|
|
static SECURITY_STATUS vschannel_ensure_stream_state(TlsContext *tls_ctx) {
|
|
if(tls_ctx->stream_sizes_valid) {
|
|
return SEC_E_OK;
|
|
}
|
|
SECURITY_STATUS scRet = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context,
|
|
SECPKG_ATTR_STREAM_SIZES, &tls_ctx->stream_sizes);
|
|
if(scRet != SEC_E_OK) {
|
|
return scRet;
|
|
}
|
|
// One full wire record: header + max plaintext + trailer. recv() never needs
|
|
// more than this buffered to complete a single record; trailing bytes of the
|
|
// next record are carried as SECBUFFER_EXTRA.
|
|
tls_ctx->recv_buf_cap = tls_ctx->stream_sizes.cbHeader
|
|
+ tls_ctx->stream_sizes.cbMaximumMessage + tls_ctx->stream_sizes.cbTrailer;
|
|
tls_ctx->recv_buf = (unsigned char *)LocalAlloc(LPTR, tls_ctx->recv_buf_cap);
|
|
// One record decrypts to at most cbMaximumMessage plaintext bytes.
|
|
tls_ctx->plain_buf_cap = tls_ctx->stream_sizes.cbMaximumMessage;
|
|
tls_ctx->plain_buf = (unsigned char *)LocalAlloc(LPTR, tls_ctx->plain_buf_cap);
|
|
// Reusable send buffer: one full outgoing record.
|
|
tls_ctx->send_buf_cap = tls_ctx->recv_buf_cap;
|
|
tls_ctx->send_buf = (unsigned char *)LocalAlloc(LPTR, tls_ctx->send_buf_cap);
|
|
if(tls_ctx->recv_buf == NULL || tls_ctx->plain_buf == NULL || tls_ctx->send_buf == NULL) {
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
tls_ctx->recv_buf_len = 0;
|
|
tls_ctx->plain_buf_len = 0;
|
|
tls_ctx->plain_buf_off = 0;
|
|
tls_ctx->stream_sizes_valid = TRUE;
|
|
return SEC_E_OK;
|
|
}
|
|
|
|
// vschannel_h2_connect connects to host:iport, performs the TLS handshake while
|
|
// advertising whatever ALPN list was set via vschannel_set_alpn(), validates the
|
|
// server certificate (when enabled), and leaves the connection open for
|
|
// vschannel_write()/vschannel_read(). Returns 0 on success, non-zero on failure
|
|
// (see vschannel_last_error). The negotiated protocol is available afterwards
|
|
// via vschannel_get_alpn().
|
|
INT vschannel_h2_connect(TlsContext *tls_ctx, INT iport, LPWSTR host) {
|
|
SecBuffer ExtraData;
|
|
SECURITY_STATUS Status;
|
|
|
|
// Connect + handshake + cert validation (when enabled). Unlike the one-shot
|
|
// paths, keep the handshake-bundled application data: for HTTP/2 it is the
|
|
// server's first record (SETTINGS), needed below.
|
|
if(vschannel_open_and_handshake(tls_ctx, iport, host,
|
|
tls_ctx->validate_server_certificate, &ExtraData) != SEC_E_OK) {
|
|
return -1;
|
|
}
|
|
|
|
Status = vschannel_ensure_stream_state(tls_ctx);
|
|
if(Status != SEC_E_OK) {
|
|
vschannel_set_last_error(tls_ctx, Status);
|
|
if(ExtraData.pvBuffer != NULL) {
|
|
LocalFree(ExtraData.pvBuffer);
|
|
}
|
|
vschannel_cleanup(tls_ctx);
|
|
return -1;
|
|
}
|
|
|
|
// The final handshake flight often arrives in the same TCP segment as the
|
|
// server's first application record (for HTTP/2, the SETTINGS frame), which
|
|
// the handshake hands back as SECBUFFER_EXTRA. Those bytes are already off
|
|
// the socket, so they must be carried into the read buffer; otherwise the
|
|
// first vschannel_read() would skip them and H2Conn would desync. Grow the
|
|
// staging buffer if the bundled data exceeds one record.
|
|
if(ExtraData.pvBuffer != NULL) {
|
|
if(ExtraData.cbBuffer > 0) {
|
|
if(ExtraData.cbBuffer > tls_ctx->recv_buf_cap) {
|
|
unsigned char *grown = (unsigned char *)LocalAlloc(LPTR, ExtraData.cbBuffer);
|
|
if(grown != NULL) {
|
|
LocalFree(tls_ctx->recv_buf);
|
|
tls_ctx->recv_buf = grown;
|
|
tls_ctx->recv_buf_cap = ExtraData.cbBuffer;
|
|
}
|
|
}
|
|
if(ExtraData.cbBuffer <= tls_ctx->recv_buf_cap) {
|
|
MoveMemory(tls_ctx->recv_buf, ExtraData.pvBuffer, ExtraData.cbBuffer);
|
|
tls_ctx->recv_buf_len = ExtraData.cbBuffer;
|
|
}
|
|
}
|
|
LocalFree(ExtraData.pvBuffer);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// vschannel_write encrypts and sends `len` application bytes over the open
|
|
// connection, chunked to the negotiated maximum record size. Returns the number
|
|
// of bytes consumed (== len) on success, or -1 on error.
|
|
INT vschannel_write(TlsContext *tls_ctx, const char *buf, INT len) {
|
|
SecBufferDesc Message;
|
|
SecBuffer Buffers[4];
|
|
SECURITY_STATUS scRet;
|
|
PBYTE io;
|
|
DWORD off;
|
|
INT cbData;
|
|
|
|
if(len <= 0) {
|
|
return 0;
|
|
}
|
|
if(vschannel_ensure_stream_state(tls_ctx) != SEC_E_OK) {
|
|
return -1;
|
|
}
|
|
|
|
// Reuse the per-context send buffer (one full record) rather than allocating
|
|
// on every write; the HTTP/2 driver issues many small writes per request.
|
|
io = (PBYTE)tls_ctx->send_buf;
|
|
|
|
off = 0;
|
|
while(off < (DWORD)len) {
|
|
DWORD chunk = (DWORD)len - off;
|
|
if(chunk > tls_ctx->stream_sizes.cbMaximumMessage) {
|
|
chunk = tls_ctx->stream_sizes.cbMaximumMessage;
|
|
}
|
|
memcpy(io + tls_ctx->stream_sizes.cbHeader, buf + off, chunk);
|
|
|
|
Buffers[0].pvBuffer = io;
|
|
Buffers[0].cbBuffer = tls_ctx->stream_sizes.cbHeader;
|
|
Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
|
|
Buffers[1].pvBuffer = io + tls_ctx->stream_sizes.cbHeader;
|
|
Buffers[1].cbBuffer = chunk;
|
|
Buffers[1].BufferType = SECBUFFER_DATA;
|
|
Buffers[2].pvBuffer = io + tls_ctx->stream_sizes.cbHeader + chunk;
|
|
Buffers[2].cbBuffer = tls_ctx->stream_sizes.cbTrailer;
|
|
Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
|
Buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
|
|
Message.ulVersion = SECBUFFER_VERSION;
|
|
Message.cBuffers = 4;
|
|
Message.pBuffers = Buffers;
|
|
|
|
scRet = tls_ctx->sspi->EncryptMessage(&tls_ctx->h_context, 0, &Message, 0);
|
|
if(FAILED(scRet)) {
|
|
vschannel_set_last_error(tls_ctx, scRet);
|
|
return -1;
|
|
}
|
|
|
|
DWORD to_send = Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer;
|
|
DWORD sent = 0;
|
|
while(sent < to_send) {
|
|
cbData = send(tls_ctx->socket, (char*)io + sent, (int)(to_send - sent), 0);
|
|
if(cbData == SOCKET_ERROR || cbData == 0) {
|
|
vschannel_set_last_error(tls_ctx, WSAGetLastError());
|
|
return -1;
|
|
}
|
|
sent += (DWORD)cbData;
|
|
}
|
|
off += chunk;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
// vschannel_read returns up to `cap` decrypted application bytes from the open
|
|
// connection. It returns the number of bytes written into `buf` (> 0), 0 at
|
|
// end of stream (close_notify / context expired / peer closed the socket), or
|
|
// -1 on error. Leftover decrypted plaintext that did not fit in `buf`, and
|
|
// ciphertext that did not yet form a complete record, are carried on the
|
|
// TlsContext across calls.
|
|
INT vschannel_read(TlsContext *tls_ctx, char *buf, INT cap) {
|
|
SecBufferDesc Message;
|
|
SecBuffer Buffers[4];
|
|
SecBuffer ExtraBuffer;
|
|
SecBuffer *pDataBuffer;
|
|
SecBuffer *pExtraBuffer;
|
|
SECURITY_STATUS scRet;
|
|
INT cbData;
|
|
int i;
|
|
|
|
if(cap <= 0) {
|
|
return 0;
|
|
}
|
|
if(vschannel_ensure_stream_state(tls_ctx) != SEC_E_OK) {
|
|
return -1;
|
|
}
|
|
|
|
// 1. Serve leftover decrypted plaintext from a previous record first.
|
|
if(tls_ctx->plain_buf_off < tls_ctx->plain_buf_len) {
|
|
DWORD avail = tls_ctx->plain_buf_len - tls_ctx->plain_buf_off;
|
|
DWORD n = avail < (DWORD)cap ? avail : (DWORD)cap;
|
|
memcpy(buf, tls_ctx->plain_buf + tls_ctx->plain_buf_off, n);
|
|
tls_ctx->plain_buf_off += n;
|
|
return (INT)n;
|
|
}
|
|
|
|
if(tls_ctx->stream_eof) {
|
|
return 0;
|
|
}
|
|
|
|
for(;;) {
|
|
// Try to decrypt whatever ciphertext we have buffered.
|
|
if(tls_ctx->recv_buf_len > 0) {
|
|
Buffers[0].pvBuffer = tls_ctx->recv_buf;
|
|
Buffers[0].cbBuffer = tls_ctx->recv_buf_len;
|
|
Buffers[0].BufferType = SECBUFFER_DATA;
|
|
Buffers[1].BufferType = SECBUFFER_EMPTY;
|
|
Buffers[2].BufferType = SECBUFFER_EMPTY;
|
|
Buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
|
|
Message.ulVersion = SECBUFFER_VERSION;
|
|
Message.cBuffers = 4;
|
|
Message.pBuffers = Buffers;
|
|
|
|
scRet = tls_ctx->sspi->DecryptMessage(&tls_ctx->h_context, &Message, 0, NULL);
|
|
|
|
if(scRet == SEC_E_OK || scRet == SEC_I_RENEGOTIATE || scRet == SEC_I_CONTEXT_EXPIRED) {
|
|
pDataBuffer = NULL;
|
|
pExtraBuffer = NULL;
|
|
for(i = 1; i < 4; i++) {
|
|
if(pDataBuffer == NULL && Buffers[i].BufferType == SECBUFFER_DATA) {
|
|
pDataBuffer = &Buffers[i];
|
|
}
|
|
if(pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA) {
|
|
pExtraBuffer = &Buffers[i];
|
|
}
|
|
}
|
|
|
|
// Copy out decrypted application data: as much as fits in the
|
|
// caller's buffer, the rest into the plaintext carryover. Both
|
|
// reads happen before the SECBUFFER_EXTRA move below, since the
|
|
// data buffer points inside recv_buf.
|
|
INT produced = 0;
|
|
if(pDataBuffer && pDataBuffer->cbBuffer > 0) {
|
|
DWORD data_len = pDataBuffer->cbBuffer;
|
|
DWORD n = data_len < (DWORD)cap ? data_len : (DWORD)cap;
|
|
memcpy(buf, pDataBuffer->pvBuffer, n);
|
|
produced = (INT)n;
|
|
DWORD rest = data_len - n;
|
|
if(rest > 0) {
|
|
memcpy(tls_ctx->plain_buf, (unsigned char*)pDataBuffer->pvBuffer + n, rest);
|
|
tls_ctx->plain_buf_len = rest;
|
|
tls_ctx->plain_buf_off = 0;
|
|
}
|
|
}
|
|
|
|
// Carry trailing ciphertext (start of the next record) to the
|
|
// front of recv_buf for the next decrypt.
|
|
if(pExtraBuffer) {
|
|
MoveMemory(tls_ctx->recv_buf, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
|
|
tls_ctx->recv_buf_len = pExtraBuffer->cbBuffer;
|
|
} else {
|
|
tls_ctx->recv_buf_len = 0;
|
|
}
|
|
|
|
if(scRet == SEC_I_RENEGOTIATE) {
|
|
// The server requested a new handshake. Run it, then carry any
|
|
// extra data it left behind and keep reading.
|
|
SECURITY_STATUS rh = client_handshake_loop(tls_ctx, FALSE, &ExtraBuffer);
|
|
if(rh != SEC_E_OK) {
|
|
vschannel_set_last_error(tls_ctx, rh);
|
|
return -1;
|
|
}
|
|
if(ExtraBuffer.pvBuffer) {
|
|
if(ExtraBuffer.cbBuffer <= tls_ctx->recv_buf_cap) {
|
|
MoveMemory(tls_ctx->recv_buf, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer);
|
|
tls_ctx->recv_buf_len = ExtraBuffer.cbBuffer;
|
|
}
|
|
LocalFree(ExtraBuffer.pvBuffer);
|
|
}
|
|
} else if(scRet == SEC_I_CONTEXT_EXPIRED) {
|
|
// Graceful close_notify from the server.
|
|
tls_ctx->stream_eof = TRUE;
|
|
if(produced > 0) {
|
|
return produced;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if(produced > 0) {
|
|
return produced;
|
|
}
|
|
// A record with no application data (e.g. a session ticket or a
|
|
// renegotiation). Keep going.
|
|
continue;
|
|
} else if(scRet == SEC_E_INCOMPLETE_MESSAGE) {
|
|
// Need more bytes to complete the current record: fall through to
|
|
// recv() more ciphertext.
|
|
} else {
|
|
vschannel_set_last_error(tls_ctx, scRet);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Receive more ciphertext.
|
|
if(tls_ctx->recv_buf_len >= tls_ctx->recv_buf_cap) {
|
|
// Should not happen: a single record fits in recv_buf_cap.
|
|
vschannel_set_last_error(tls_ctx, SEC_E_INTERNAL_ERROR);
|
|
return -1;
|
|
}
|
|
cbData = recv(tls_ctx->socket, (char*)tls_ctx->recv_buf + tls_ctx->recv_buf_len,
|
|
(int)(tls_ctx->recv_buf_cap - tls_ctx->recv_buf_len), 0);
|
|
if(cbData == SOCKET_ERROR) {
|
|
vschannel_set_last_error(tls_ctx, WSAGetLastError());
|
|
return -1;
|
|
}
|
|
if(cbData == 0) {
|
|
// Peer closed the socket. Any buffered bytes are an incomplete record.
|
|
tls_ctx->stream_eof = TRUE;
|
|
return 0;
|
|
}
|
|
tls_ctx->recv_buf_len += (DWORD)cbData;
|
|
}
|
|
}
|
|
|
|
// vschannel_h2_close sends a close_notify alert and tears down the connection.
|
|
void vschannel_h2_close(TlsContext *tls_ctx) {
|
|
if(tls_ctx->context_initialized) {
|
|
disconnect_from_server(tls_ctx);
|
|
tls_ctx->context_initialized = FALSE;
|
|
tls_ctx->socket = INVALID_SOCKET;
|
|
}
|
|
vschannel_cleanup(tls_ctx);
|
|
}
|
|
|
|
|
|
static SECURITY_STATUS create_credentials(TlsContext *tls_ctx) {
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS Status;
|
|
|
|
DWORD cSupportedAlgs = 0;
|
|
ALG_ID rgbSupportedAlgs[16];
|
|
|
|
PCCERT_CONTEXT pCertContext = NULL;
|
|
|
|
// Open the "MY" certificate store, which is where Internet Explorer
|
|
// stores its client certificates.
|
|
if(tls_ctx->cert_store == NULL) {
|
|
tls_ctx->cert_store = CertOpenSystemStore(0, L"MY");
|
|
|
|
if(!tls_ctx->cert_store) {
|
|
wprintf(L"Error 0x%x returned by CertOpenSystemStore\n",
|
|
GetLastError());
|
|
return SEC_E_NO_CREDENTIALS;
|
|
}
|
|
}
|
|
|
|
// Build Schannel credential structure. Currently, this sample only
|
|
// specifies the protocol to be used (and optionally the certificate,
|
|
// of course). Real applications may wish to specify other parameters
|
|
// as well.
|
|
|
|
ZeroMemory(&tls_ctx->schannel_cred, sizeof(tls_ctx->schannel_cred));
|
|
|
|
tls_ctx->schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
if(pCertContext)
|
|
{
|
|
tls_ctx->schannel_cred.cCreds = 1;
|
|
tls_ctx->schannel_cred.paCred = &pCertContext;
|
|
}
|
|
|
|
tls_ctx->schannel_cred.grbitEnabledProtocols = protocol;
|
|
|
|
if(aid_key_exch)
|
|
{
|
|
rgbSupportedAlgs[cSupportedAlgs++] = aid_key_exch;
|
|
}
|
|
|
|
if(cSupportedAlgs)
|
|
{
|
|
tls_ctx->schannel_cred.cSupportedAlgs = cSupportedAlgs;
|
|
tls_ctx->schannel_cred.palgSupportedAlgs = rgbSupportedAlgs;
|
|
}
|
|
|
|
tls_ctx->schannel_cred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
|
|
tls_ctx->schannel_cred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
|
|
|
|
// Keep certificate validation under the caller's control. The validated
|
|
// path runs explicit hostname/chain validation after the handshake.
|
|
|
|
// Create an SSPI credential.
|
|
|
|
Status = tls_ctx->sspi->AcquireCredentialsHandle(
|
|
NULL, // Name of principal
|
|
UNISP_NAME_W, // Name of package
|
|
SECPKG_CRED_OUTBOUND, // Flags indicating use
|
|
NULL, // Pointer to logon ID
|
|
&tls_ctx->schannel_cred, // Package specific data
|
|
NULL, // Pointer to GetKey() func
|
|
NULL, // Value to pass to GetKey()
|
|
&tls_ctx->h_client_creds, // (out) Cred Handle
|
|
&tsExpiry); // (out) Lifetime (optional)
|
|
if(Status != SEC_E_OK) {
|
|
wprintf(L"Error 0x%x returned by AcquireCredentialsHandle\n", Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
// Free the certificate context. Schannel has already made its own copy.
|
|
|
|
if(pCertContext) {
|
|
CertFreeCertificateContext(pCertContext);
|
|
}
|
|
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
static INT connect_to_server(TlsContext *tls_ctx, LPWSTR host, INT port_number) {
|
|
SOCKET Socket;
|
|
|
|
SOCKADDR_STORAGE local_address = { 0 };
|
|
SOCKADDR_STORAGE remote_address = { 0 };
|
|
|
|
DWORD local_address_length = sizeof(local_address);
|
|
DWORD remote_address_length = sizeof(remote_address);
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = 60;
|
|
tv.tv_usec = 0;
|
|
|
|
Socket = socket(PF_INET, SOCK_STREAM, 0);
|
|
if(Socket == INVALID_SOCKET) {
|
|
INT err_code = WSAGetLastError();
|
|
vschannel_set_last_error(tls_ctx, err_code);
|
|
return err_code;
|
|
}
|
|
|
|
LPWSTR connect_name = use_proxy ? psz_proxy_server : host;
|
|
|
|
WCHAR service_name[10];
|
|
int res = wsprintf(service_name, L"%d", port_number);
|
|
|
|
if(WSAConnectByNameW(Socket,connect_name, service_name, &local_address_length,
|
|
&local_address, &remote_address_length, &remote_address, &tv, NULL) == FALSE) {
|
|
INT err_code = WSAGetLastError();
|
|
vschannel_set_last_error(tls_ctx, err_code);
|
|
closesocket(Socket);
|
|
return err_code;
|
|
}
|
|
|
|
if(use_proxy) {
|
|
BYTE pbMessage[200];
|
|
DWORD cbMessage;
|
|
|
|
// Build message for proxy server
|
|
strcpy(pbMessage, "CONNECT ");
|
|
strcat(pbMessage, host);
|
|
strcat(pbMessage, ":");
|
|
_itoa(port_number, pbMessage + strlen(pbMessage), 10);
|
|
strcat(pbMessage, " HTTP/1.0\r\nUser-Agent: webclient\r\n\r\n");
|
|
cbMessage = (DWORD)strlen(pbMessage);
|
|
|
|
// Send message to proxy server
|
|
if(send(Socket, pbMessage, cbMessage, 0) == SOCKET_ERROR) {
|
|
INT err_code = WSAGetLastError();
|
|
vschannel_set_last_error(tls_ctx, err_code);
|
|
return err_code;
|
|
}
|
|
|
|
// Receive message from proxy server
|
|
cbMessage = recv(Socket, pbMessage, 200, 0);
|
|
if(cbMessage == SOCKET_ERROR) {
|
|
INT err_code = WSAGetLastError();
|
|
vschannel_set_last_error(tls_ctx, err_code);
|
|
return err_code;
|
|
}
|
|
|
|
// this sample is limited but in normal use it
|
|
// should continue to receive until CR LF CR LF is received
|
|
}
|
|
|
|
tls_ctx->socket = Socket;
|
|
|
|
return SEC_E_OK;
|
|
}
|
|
|
|
|
|
static LONG disconnect_from_server(TlsContext *tls_ctx) {
|
|
DWORD dwType;
|
|
PBYTE pbMessage;
|
|
DWORD cbMessage;
|
|
DWORD cbData;
|
|
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer OutBuffers[1];
|
|
DWORD dwSSPIFlags;
|
|
DWORD dwSSPIOutFlags;
|
|
TimeStamp tsExpiry;
|
|
DWORD Status;
|
|
|
|
// Notify schannel that we are about to close the connection.
|
|
|
|
dwType = SCHANNEL_SHUTDOWN;
|
|
|
|
OutBuffers[0].pvBuffer = &dwType;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = sizeof(dwType);
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = tls_ctx->sspi->ApplyControlToken(&tls_ctx->h_context, &OutBuffer);
|
|
|
|
if(FAILED(Status)) {
|
|
wprintf(L"Error 0x%x returned by ApplyControlToken\n", Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
// Build an SSL close notify message.
|
|
|
|
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_RET_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM;
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = tls_ctx->sspi->InitializeSecurityContext(
|
|
&tls_ctx->h_client_creds, &tls_ctx->h_context, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP,
|
|
NULL, 0, &tls_ctx->h_context, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
|
|
|
|
if(FAILED(Status)) {
|
|
wprintf(L"Error 0x%x returned by InitializeSecurityContext\n", Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
pbMessage = OutBuffers[0].pvBuffer;
|
|
cbMessage = OutBuffers[0].cbBuffer;
|
|
|
|
// Send the close notify message to the server.
|
|
|
|
if(pbMessage != NULL && cbMessage != 0) {
|
|
cbData = send(tls_ctx->socket, pbMessage, cbMessage, 0);
|
|
if(cbData == SOCKET_ERROR || cbData == 0) {
|
|
Status = WSAGetLastError();
|
|
wprintf(L"Error %d sending close notify\n", Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
// Free output buffer.
|
|
tls_ctx->sspi->FreeContextBuffer(pbMessage);
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
// Free the security context.
|
|
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
|
|
|
|
// Close the socket.
|
|
closesocket(tls_ctx->socket);
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
static SECURITY_STATUS perform_client_handshake(TlsContext *tls_ctx, WCHAR *host, SecBuffer *pExtraData) {
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer OutBuffers[1];
|
|
DWORD dwSSPIFlags;
|
|
DWORD dwSSPIOutFlags;
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS scRet;
|
|
DWORD cbData;
|
|
|
|
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_RET_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM;
|
|
|
|
//
|
|
// Optionally advertise ALPN protocols in the ClientHello. SChannel takes
|
|
// this as a SECBUFFER_APPLICATION_PROTOCOLS input buffer holding a
|
|
// SEC_APPLICATION_PROTOCOLS record. The backing store is a 4-byte-aligned
|
|
// unsigned long array so the struct cast is well aligned on every compiler.
|
|
//
|
|
SecBuffer InBuffers[1];
|
|
SecBufferDesc InBuffer;
|
|
SecBufferDesc *pInput = NULL;
|
|
unsigned long alpn_store[80]; // 320 bytes; alpn_wire is at most 256
|
|
if (tls_ctx->alpn_wire_len > 0) {
|
|
SEC_APPLICATION_PROTOCOLS *protos = (SEC_APPLICATION_PROTOCOLS *)alpn_store;
|
|
SEC_APPLICATION_PROTOCOL_LIST *list = &protos->ProtocolLists[0];
|
|
unsigned long wlen = tls_ctx->alpn_wire_len;
|
|
|
|
list->ProtoNegoExt = SecApplicationProtocolNegotiationExt_ALPN;
|
|
list->ProtocolListSize = (unsigned short)wlen;
|
|
memcpy(list->ProtocolList, tls_ctx->alpn_wire, (size_t)wlen);
|
|
protos->ProtocolListsSize =
|
|
(unsigned long)(FIELD_OFFSET(SEC_APPLICATION_PROTOCOL_LIST, ProtocolList) + wlen);
|
|
|
|
InBuffers[0].pvBuffer = protos;
|
|
InBuffers[0].cbBuffer =
|
|
(unsigned long)(FIELD_OFFSET(SEC_APPLICATION_PROTOCOLS, ProtocolLists) + protos->ProtocolListsSize);
|
|
InBuffers[0].BufferType = SECBUFFER_APPLICATION_PROTOCOLS;
|
|
|
|
InBuffer.cBuffers = 1;
|
|
InBuffer.pBuffers = InBuffers;
|
|
InBuffer.ulVersion = SECBUFFER_VERSION;
|
|
pInput = &InBuffer;
|
|
}
|
|
|
|
//
|
|
// Initiate a ClientHello message and generate a token.
|
|
//
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
scRet = tls_ctx->sspi->InitializeSecurityContext(
|
|
&tls_ctx->h_client_creds,
|
|
NULL,
|
|
host,
|
|
dwSSPIFlags,
|
|
0,
|
|
SECURITY_NATIVE_DREP,
|
|
pInput,
|
|
0,
|
|
&tls_ctx->h_context,
|
|
&OutBuffer,
|
|
&dwSSPIOutFlags,
|
|
&tsExpiry);
|
|
|
|
if(scRet != SEC_I_CONTINUE_NEEDED)
|
|
{
|
|
wprintf(L"Error %d returned by InitializeSecurityContext (1)\n", scRet);
|
|
return scRet;
|
|
}
|
|
|
|
// Send response to server if there is one.
|
|
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
|
|
{
|
|
cbData = send(tls_ctx->socket, OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
|
|
if(cbData == SOCKET_ERROR || cbData == 0) {
|
|
wprintf(L"Error %d sending data to server (1)\n", WSAGetLastError());
|
|
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
// Free output buffer.
|
|
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
}
|
|
|
|
return client_handshake_loop(tls_ctx, TRUE, pExtraData);
|
|
}
|
|
|
|
|
|
static SECURITY_STATUS client_handshake_loop(TlsContext *tls_ctx, BOOL fDoInitialRead, SecBuffer *pExtraData) {
|
|
SecBufferDesc InBuffer;
|
|
SecBuffer InBuffers[2];
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer OutBuffers[1];
|
|
DWORD dwSSPIFlags;
|
|
DWORD dwSSPIOutFlags;
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS scRet;
|
|
DWORD cbData;
|
|
|
|
PUCHAR IoBuffer;
|
|
DWORD cbIoBuffer;
|
|
BOOL fDoRead;
|
|
|
|
|
|
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_RET_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM;
|
|
|
|
//
|
|
// Allocate data buffer.
|
|
//
|
|
|
|
IoBuffer = LocalAlloc(LPTR, IO_BUFFER_SIZE);
|
|
if(IoBuffer == NULL)
|
|
{
|
|
wprintf(L"Out of memory (1)\n");
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
cbIoBuffer = 0;
|
|
|
|
fDoRead = fDoInitialRead;
|
|
|
|
|
|
//
|
|
// Loop until the handshake is finished or an error occurs.
|
|
//
|
|
|
|
scRet = SEC_I_CONTINUE_NEEDED;
|
|
|
|
while(scRet == SEC_I_CONTINUE_NEEDED ||
|
|
scRet == SEC_E_INCOMPLETE_MESSAGE ||
|
|
scRet == SEC_I_INCOMPLETE_CREDENTIALS) {
|
|
|
|
// Read data from server.
|
|
if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) {
|
|
if(fDoRead) {
|
|
cbData = recv(tls_ctx->socket,
|
|
IoBuffer + cbIoBuffer,
|
|
IO_BUFFER_SIZE - cbIoBuffer,
|
|
0);
|
|
if(cbData == SOCKET_ERROR) {
|
|
wprintf(L"Error %d reading data from server\n", WSAGetLastError());
|
|
scRet = SEC_E_INTERNAL_ERROR;
|
|
break;
|
|
}
|
|
else if(cbData == 0) {
|
|
wprintf(L"Server unexpectedly disconnected\n");
|
|
scRet = SEC_E_INTERNAL_ERROR;
|
|
break;
|
|
}
|
|
|
|
cbIoBuffer += cbData;
|
|
}
|
|
else {
|
|
fDoRead = TRUE;
|
|
}
|
|
}
|
|
|
|
// Set up the input buffers. Buffer 0 is used to pass in data
|
|
// received from the server. Schannel will consume some or all
|
|
// of this. Leftover data (if any) will be placed in buffer 1 and
|
|
// given a buffer type of SECBUFFER_EXTRA.
|
|
|
|
InBuffers[0].pvBuffer = IoBuffer;
|
|
InBuffers[0].cbBuffer = cbIoBuffer;
|
|
InBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
|
|
InBuffers[1].pvBuffer = NULL;
|
|
InBuffers[1].cbBuffer = 0;
|
|
InBuffers[1].BufferType = SECBUFFER_EMPTY;
|
|
|
|
InBuffer.cBuffers = 2;
|
|
InBuffer.pBuffers = InBuffers;
|
|
InBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
// Set up the output buffers. These are initialized to NULL
|
|
// so as to make it less likely we'll attempt to free random
|
|
// garbage later.
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].BufferType= SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
// Call InitializeSecurityContext.
|
|
|
|
scRet = tls_ctx->sspi->InitializeSecurityContext(
|
|
&tls_ctx->h_client_creds, &tls_ctx->h_context, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP,
|
|
&InBuffer, 0, NULL, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
|
|
|
|
// If InitializeSecurityContext was successful (or if the error was
|
|
// one of the special extended ones), send the contends of the output
|
|
// buffer to the server.
|
|
|
|
if(scRet == SEC_E_OK ||
|
|
scRet == SEC_I_CONTINUE_NEEDED ||
|
|
FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)) {
|
|
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) {
|
|
cbData = send(tls_ctx->socket,
|
|
OutBuffers[0].pvBuffer,
|
|
OutBuffers[0].cbBuffer,
|
|
0);
|
|
if(cbData == SOCKET_ERROR || cbData == 0) {
|
|
wprintf(L"Error %d sending data to server (2)\n",
|
|
WSAGetLastError());
|
|
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
// Free output buffer.
|
|
tls_ctx->sspi->FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
// If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
|
|
// then we need to read more data from the server and try again.
|
|
if(scRet == SEC_E_INCOMPLETE_MESSAGE) {
|
|
continue;
|
|
}
|
|
|
|
// If InitializeSecurityContext returned SEC_E_OK, then the
|
|
// handshake completed successfully.
|
|
|
|
if(scRet == SEC_E_OK) {
|
|
// If the "extra" buffer contains data, this is encrypted application
|
|
// protocol layer stuff. It needs to be saved. The application layer
|
|
// will later decrypt it with DecryptMessage.
|
|
|
|
if(InBuffers[1].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
pExtraData->pvBuffer = LocalAlloc(LPTR, InBuffers[1].cbBuffer);
|
|
if(pExtraData->pvBuffer == NULL) {
|
|
wprintf(L"Out of memory (2)\n");
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
MoveMemory(pExtraData->pvBuffer,
|
|
IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
|
|
InBuffers[1].cbBuffer);
|
|
|
|
pExtraData->cbBuffer = InBuffers[1].cbBuffer;
|
|
pExtraData->BufferType = SECBUFFER_TOKEN;
|
|
|
|
// wprintf(L"%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer);
|
|
}
|
|
else {
|
|
pExtraData->pvBuffer = NULL;
|
|
pExtraData->cbBuffer = 0;
|
|
pExtraData->BufferType = SECBUFFER_EMPTY;
|
|
}
|
|
|
|
// Bail out to quit
|
|
break;
|
|
}
|
|
|
|
// Check for fatal error.
|
|
if(FAILED(scRet)) {
|
|
wprintf(L"Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
|
|
break;
|
|
}
|
|
|
|
// If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
|
|
// then the server just requested client authentication.
|
|
if(scRet == SEC_I_INCOMPLETE_CREDENTIALS) {
|
|
// Busted. The server has requested client authentication and
|
|
// the credential we supplied didn't contain a client certificate.
|
|
|
|
// This function will read the list of trusted certificate
|
|
// authorities ("issuers") that was received from the server
|
|
// and attempt to find a suitable client certificate that
|
|
// was issued by one of these. If this function is successful,
|
|
// then we will connect using the new certificate. Otherwise,
|
|
// we will attempt to connect anonymously (using our current
|
|
// credentials).
|
|
|
|
get_new_client_credentials(tls_ctx);
|
|
|
|
// Go around again.
|
|
fDoRead = FALSE;
|
|
scRet = SEC_I_CONTINUE_NEEDED;
|
|
continue;
|
|
}
|
|
|
|
// Copy any leftover data from the "extra" buffer, and go around
|
|
// again.
|
|
|
|
if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) {
|
|
MoveMemory(IoBuffer,
|
|
IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
|
|
InBuffers[1].cbBuffer);
|
|
|
|
cbIoBuffer = InBuffers[1].cbBuffer;
|
|
}
|
|
else {
|
|
cbIoBuffer = 0;
|
|
}
|
|
}
|
|
|
|
// Delete the security context in the case of a fatal error.
|
|
if(FAILED(scRet)) {
|
|
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
|
|
}
|
|
|
|
LocalFree(IoBuffer);
|
|
|
|
return scRet;
|
|
}
|
|
|
|
|
|
static SECURITY_STATUS https_make_request(TlsContext *tls_ctx, CHAR *req, DWORD req_len, CHAR **out, int *length, vschannel_allocator afn) {
|
|
SecPkgContext_StreamSizes Sizes;
|
|
SECURITY_STATUS scRet;
|
|
SecBufferDesc Message;
|
|
SecBuffer Buffers[4];
|
|
SecBuffer *pDataBuffer;
|
|
SecBuffer *pExtraBuffer;
|
|
SecBuffer ExtraBuffer;
|
|
|
|
PBYTE pbIoBuffer;
|
|
DWORD cbIoBuffer;
|
|
DWORD cbIoBufferLength;
|
|
PBYTE pbMessage;
|
|
DWORD cbMessage;
|
|
|
|
INT cbData;
|
|
INT i;
|
|
DWORD req_offset;
|
|
DWORD chunk_len;
|
|
DWORD to_send;
|
|
DWORD sent;
|
|
|
|
|
|
// Read stream encryption properties.
|
|
scRet = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context, SECPKG_ATTR_STREAM_SIZES, &Sizes);
|
|
if(scRet != SEC_E_OK) {
|
|
wprintf(L"Error 0x%x reading SECPKG_ATTR_STREAM_SIZES\n", scRet);
|
|
return scRet;
|
|
}
|
|
|
|
// Allocate a working buffer. The plaintext sent to EncryptMessage
|
|
// should never be more than 'Sizes.cbMaximumMessage', so a buffer
|
|
// size of this plus the header and trailer sizes should be safe enough.
|
|
cbIoBufferLength = Sizes.cbHeader + Sizes.cbMaximumMessage + Sizes.cbTrailer;
|
|
|
|
pbIoBuffer = LocalAlloc(LPTR, cbIoBufferLength);
|
|
if(pbIoBuffer == NULL) {
|
|
wprintf(L"Out of memory (2)\n");
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
// Build and send HTTP request in chunks no larger than cbMaximumMessage.
|
|
// EncryptMessage expects plaintext <= cbMaximumMessage.
|
|
pbMessage = pbIoBuffer + Sizes.cbHeader;
|
|
req_offset = 0;
|
|
while(req_offset < req_len) {
|
|
chunk_len = req_len - req_offset;
|
|
if(chunk_len > Sizes.cbMaximumMessage) {
|
|
chunk_len = Sizes.cbMaximumMessage;
|
|
}
|
|
|
|
memcpy(pbMessage, req + req_offset, chunk_len);
|
|
cbMessage = chunk_len;
|
|
|
|
Buffers[0].pvBuffer = pbIoBuffer;
|
|
Buffers[0].cbBuffer = Sizes.cbHeader;
|
|
Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
|
|
|
|
Buffers[1].pvBuffer = pbMessage;
|
|
Buffers[1].cbBuffer = cbMessage;
|
|
Buffers[1].BufferType = SECBUFFER_DATA;
|
|
|
|
Buffers[2].pvBuffer = pbMessage + cbMessage;
|
|
Buffers[2].cbBuffer = Sizes.cbTrailer;
|
|
Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
|
|
|
Buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
|
|
Message.ulVersion = SECBUFFER_VERSION;
|
|
Message.cBuffers = 4;
|
|
Message.pBuffers = Buffers;
|
|
|
|
scRet = tls_ctx->sspi->EncryptMessage(&tls_ctx->h_context, 0, &Message, 0);
|
|
if(FAILED(scRet)) {
|
|
wprintf(L"Error 0x%x returned by EncryptMessage\n", scRet);
|
|
return scRet;
|
|
}
|
|
|
|
// Send all encrypted bytes for this chunk.
|
|
to_send = Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer;
|
|
sent = 0;
|
|
while(sent < to_send) {
|
|
cbData = send(tls_ctx->socket, (char*)pbIoBuffer + sent, (int)(to_send - sent), 0);
|
|
if(cbData == SOCKET_ERROR || cbData == 0) {
|
|
wprintf(L"Error %d sending data to server (3)\n", WSAGetLastError());
|
|
tls_ctx->sspi->DeleteSecurityContext(&tls_ctx->h_context);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
sent += (DWORD)cbData;
|
|
}
|
|
|
|
req_offset += chunk_len;
|
|
}
|
|
|
|
// Read data from server until done.
|
|
INT buff_size = vsc_init_resp_buff_size;
|
|
cbIoBuffer = 0;
|
|
while(TRUE){
|
|
// Read some data.
|
|
if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) {
|
|
cbData = recv(tls_ctx->socket, pbIoBuffer + cbIoBuffer, cbIoBufferLength - cbIoBuffer, 0);
|
|
if(cbData == SOCKET_ERROR) {
|
|
wprintf(L"Error %d reading data from server\n", WSAGetLastError());
|
|
scRet = SEC_E_INTERNAL_ERROR;
|
|
break;
|
|
}
|
|
else if(cbData == 0) {
|
|
// Server disconnected.
|
|
if(cbIoBuffer) {
|
|
wprintf(L"Server unexpectedly disconnected\n");
|
|
scRet = SEC_E_INTERNAL_ERROR;
|
|
return scRet;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
cbIoBuffer += cbData;
|
|
}
|
|
}
|
|
|
|
// Attempt to decrypt the received data.
|
|
Buffers[0].pvBuffer = pbIoBuffer;
|
|
Buffers[0].cbBuffer = cbIoBuffer;
|
|
Buffers[0].BufferType = SECBUFFER_DATA;
|
|
|
|
Buffers[1].BufferType = SECBUFFER_EMPTY;
|
|
Buffers[2].BufferType = SECBUFFER_EMPTY;
|
|
Buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
|
|
Message.ulVersion = SECBUFFER_VERSION;
|
|
Message.cBuffers = 4;
|
|
Message.pBuffers = Buffers;
|
|
|
|
scRet = tls_ctx->sspi->DecryptMessage(&tls_ctx->h_context, &Message, 0, NULL);
|
|
|
|
if(scRet == SEC_E_INCOMPLETE_MESSAGE) {
|
|
// The input buffer contains only a fragment of an
|
|
// encrypted record. Loop around and read some more
|
|
// data.
|
|
continue;
|
|
}
|
|
|
|
// Server signalled end of session
|
|
if(scRet == SEC_I_CONTEXT_EXPIRED) {
|
|
break;
|
|
}
|
|
|
|
if( scRet != SEC_E_OK &&
|
|
scRet != SEC_I_RENEGOTIATE &&
|
|
scRet != SEC_I_CONTEXT_EXPIRED)
|
|
{
|
|
wprintf(L"Error 0x%x returned by DecryptMessage\n", scRet);
|
|
return scRet;
|
|
}
|
|
|
|
// Locate data and (optional) extra buffers.
|
|
pDataBuffer = NULL;
|
|
pExtraBuffer = NULL;
|
|
for(i = 1; i < 4; i++) {
|
|
if(pDataBuffer == NULL && Buffers[i].BufferType == SECBUFFER_DATA)
|
|
{
|
|
pDataBuffer = &Buffers[i];
|
|
// wprintf(L"Buffers[%d].BufferType = SECBUFFER_DATA\n",i);
|
|
}
|
|
if(pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
pExtraBuffer = &Buffers[i];
|
|
}
|
|
}
|
|
|
|
// increase buffer size if we need
|
|
int required_length = *length+(int)pDataBuffer->cbBuffer;
|
|
if( required_length > buff_size ) {
|
|
CHAR *a = afn(*out, required_length);
|
|
if( a == NULL ) {
|
|
scRet = SEC_E_INTERNAL_ERROR;
|
|
return scRet;
|
|
}
|
|
*out = a;
|
|
buff_size = required_length;
|
|
}
|
|
// Copy the decrypted data to our output buffer
|
|
memcpy(*out+*length, pDataBuffer->pvBuffer, (int)pDataBuffer->cbBuffer);
|
|
*length += (int)pDataBuffer->cbBuffer;
|
|
|
|
// Move any "extra" data to the input buffer.
|
|
if(pExtraBuffer) {
|
|
MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
|
|
cbIoBuffer = pExtraBuffer->cbBuffer;
|
|
}
|
|
else {
|
|
cbIoBuffer = 0;
|
|
}
|
|
|
|
if(scRet == SEC_I_RENEGOTIATE)
|
|
{
|
|
// The server wants to perform another handshake sequence.
|
|
scRet = client_handshake_loop(tls_ctx, FALSE, &ExtraBuffer);
|
|
if(scRet != SEC_E_OK) {
|
|
return scRet;
|
|
}
|
|
|
|
// Move any "extra" data to the input buffer.
|
|
if(ExtraBuffer.pvBuffer)
|
|
{
|
|
MoveMemory(pbIoBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer);
|
|
cbIoBuffer = ExtraBuffer.cbBuffer;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SEC_E_OK;
|
|
}
|
|
|
|
|
|
static DWORD verify_server_certificate( PCCERT_CONTEXT pServerCert, LPWSTR host, DWORD dwCertFlags) {
|
|
HTTPSPolicyCallbackData polHttps;
|
|
CERT_CHAIN_POLICY_PARA PolicyPara;
|
|
CERT_CHAIN_POLICY_STATUS PolicyStatus;
|
|
CERT_CHAIN_PARA ChainPara;
|
|
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
|
|
|
|
CHAR *rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH,
|
|
szOID_SERVER_GATED_CRYPTO,
|
|
szOID_SGC_NETSCAPE };
|
|
DWORD cUsages = sizeof(rgszUsages) / sizeof(CHAR*);
|
|
|
|
PWSTR pwszServerName = NULL;
|
|
DWORD cchServerName;
|
|
DWORD Status;
|
|
|
|
if(pServerCert == NULL)
|
|
{
|
|
Status = SEC_E_WRONG_PRINCIPAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(host == NULL || wcslen(host) == 0) {
|
|
Status = SEC_E_WRONG_PRINCIPAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Build certificate chain.
|
|
|
|
ZeroMemory(&ChainPara, sizeof(ChainPara));
|
|
ChainPara.cbSize = sizeof(ChainPara);
|
|
ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
|
|
ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages;
|
|
ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;
|
|
|
|
// Best-effort TLS revocation check: detect a positively revoked leaf
|
|
// certificate, but let policy evaluation ignore unknown/offline status.
|
|
if(!CertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara,
|
|
CERT_CHAIN_CACHE_END_CERT |
|
|
CERT_CHAIN_REVOCATION_CHECK_END_CERT |
|
|
CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
|
|
NULL, &pChainContext)) {
|
|
Status = GetLastError();
|
|
wprintf(L"Error 0x%x returned by CertGetCertificateChain!\n", Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
// Validate certificate chain.
|
|
ZeroMemory(&polHttps, sizeof(HTTPSPolicyCallbackData));
|
|
polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData);
|
|
polHttps.dwAuthType = AUTHTYPE_SERVER;
|
|
polHttps.fdwChecks = dwCertFlags;
|
|
polHttps.pwszServerName = host;
|
|
|
|
memset(&PolicyPara, 0, sizeof(PolicyPara));
|
|
PolicyPara.cbSize = sizeof(PolicyPara);
|
|
PolicyPara.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
|
|
PolicyPara.pvExtraPolicyPara = &polHttps;
|
|
|
|
memset(&PolicyStatus, 0, sizeof(PolicyStatus));
|
|
PolicyStatus.cbSize = sizeof(PolicyStatus);
|
|
|
|
if(!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus)){
|
|
Status = GetLastError();
|
|
wprintf(L"Error 0x%x returned by CertVerifyCertificateChainPolicy!\n", Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
if(PolicyStatus.dwError) {
|
|
Status = PolicyStatus.dwError;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
Status = SEC_E_OK;
|
|
|
|
cleanup:
|
|
|
|
if(pChainContext)
|
|
{
|
|
CertFreeCertificateChain(pChainContext);
|
|
}
|
|
|
|
if(pwszServerName)
|
|
{
|
|
LocalFree(pwszServerName);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
static void get_new_client_credentials(TlsContext *tls_ctx) {
|
|
CredHandle hCreds;
|
|
SecPkgContext_IssuerListInfoEx IssuerListInfo;
|
|
PCCERT_CHAIN_CONTEXT pChainContext;
|
|
CERT_CHAIN_FIND_BY_ISSUER_PARA FindByIssuerPara;
|
|
PCCERT_CONTEXT pCertContext;
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS Status;
|
|
|
|
// Read list of trusted issuers from schannel.
|
|
Status = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context, SECPKG_ATTR_ISSUER_LIST_EX, (PVOID)&IssuerListInfo);
|
|
if(Status != SEC_E_OK) {
|
|
wprintf(L"Error 0x%x querying issuer list info\n", Status);
|
|
return;
|
|
}
|
|
|
|
// Enumerate the client certificates.
|
|
|
|
ZeroMemory(&FindByIssuerPara, sizeof(FindByIssuerPara));
|
|
|
|
FindByIssuerPara.cbSize = sizeof(FindByIssuerPara);
|
|
FindByIssuerPara.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
|
|
FindByIssuerPara.dwKeySpec = 0;
|
|
FindByIssuerPara.cIssuer = IssuerListInfo.cIssuers;
|
|
FindByIssuerPara.rgIssuer = IssuerListInfo.aIssuers;
|
|
|
|
pChainContext = NULL;
|
|
|
|
while(TRUE) {
|
|
// Find a certificate chain.
|
|
pChainContext = CertFindChainInStore(tls_ctx->cert_store,
|
|
X509_ASN_ENCODING,
|
|
0,
|
|
CERT_CHAIN_FIND_BY_ISSUER,
|
|
&FindByIssuerPara,
|
|
pChainContext);
|
|
if(pChainContext == NULL) {
|
|
wprintf(L"Error 0x%x finding cert chain\n", GetLastError());
|
|
break;
|
|
}
|
|
|
|
// Get pointer to leaf certificate context.
|
|
pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;
|
|
|
|
// Create schannel credential.
|
|
tls_ctx->schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
tls_ctx->schannel_cred.cCreds = 1;
|
|
tls_ctx->schannel_cred.paCred = &pCertContext;
|
|
|
|
Status = tls_ctx->sspi->AcquireCredentialsHandle(
|
|
NULL, // Name of principal
|
|
UNISP_NAME_W, // Name of package
|
|
SECPKG_CRED_OUTBOUND, // Flags indicating use
|
|
NULL, // Pointer to logon ID
|
|
&tls_ctx->schannel_cred, // Package specific data
|
|
NULL, // Pointer to GetKey() func
|
|
NULL, // Value to pass to GetKey()
|
|
&hCreds, // (out) Cred Handle
|
|
&tsExpiry); // (out) Lifetime (optional)
|
|
if(Status != SEC_E_OK) {
|
|
wprintf(L"Error 0x%x returned by AcquireCredentialsHandle\n", Status);
|
|
continue;
|
|
}
|
|
|
|
// Destroy the old credentials.
|
|
tls_ctx->sspi->FreeCredentialsHandle(&tls_ctx->h_client_creds);
|
|
|
|
tls_ctx->h_client_creds = hCreds;
|
|
|
|
//
|
|
// As you can see, this sample code maintains a single credential
|
|
// handle, replacing it as necessary. This is a little unusual.
|
|
//
|
|
// Many applications maintain a global credential handle that's
|
|
// anonymous (that is, it doesn't contain a client certificate),
|
|
// which is used to connect to all servers. If a particular server
|
|
// should require client authentication, then a new credential
|
|
// is created for use when connecting to that server. The global
|
|
// anonymous credential is retained for future connections to
|
|
// other servers.
|
|
//
|
|
// Maintaining a single anonymous credential that's used whenever
|
|
// possible is most efficient, since creating new credentials all
|
|
// the time is rather expensive.
|
|
//
|
|
|
|
break;
|
|
}
|
|
}
|