Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls incompatible with winhttp #18

Closed
acgreek opened this issue Jun 3, 2021 · 7 comments
Closed

tls incompatible with winhttp #18

acgreek opened this issue Jun 3, 2021 · 7 comments

Comments

@acgreek
Copy link
Collaborator

acgreek commented Jun 3, 2021

I used visual studio 2019 and ran this against websocket_tls server . If I simply change the port to 28000 and replace WINHTTP_FLAG_SECURE with 0, this code is able to connect to the non-tls websocket example

// winhttpExample.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#pragma comment(lib, "winhttp")

#include <Windows.h>
#include <WinHttp.h>
#include <stdio.h>

int __cdecl wmain()
{
	DWORD dwError = ERROR_SUCCESS;
	BOOL fStatus = FALSE;
	HINTERNET hSessionHandle = NULL;
	HINTERNET hConnectionHandle = NULL;
	HINTERNET hRequestHandle = NULL;
	HINTERNET hWebSocketHandle = NULL;
	BYTE rgbCloseReasonBuffer[123];
	BYTE rgbBuffer[1024];
	BYTE* pbCurrentBufferPointer = rgbBuffer;
	DWORD dwBufferLength = ARRAYSIZE(rgbBuffer);
	DWORD dwBytesTransferred = 0;
	DWORD dwCloseReasonLength = 0;
	USHORT usStatus = 0;
	WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType;
	INTERNET_PORT Port = 8888;
	const WCHAR* pcwszServerName = L"localhost";
	const WCHAR* pcwszPath = L"/ws";
	const WCHAR* pcwszMessage = L"Hello world";
	const DWORD cdwMessageLength = ARRAYSIZE(L"Hello world") * sizeof(WCHAR);

	wprintf(L"program started\n");
	//
	// Create session, connection and request handles.
	//
	hSessionHandle = WinHttpOpen(L"WebSocket sample",
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		NULL,
		NULL,
		0);
	if (hSessionHandle == NULL)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"session handle constructed\n");

	hConnectionHandle = WinHttpConnect(hSessionHandle,
		pcwszServerName,
		Port,
		0);
	if (hConnectionHandle == NULL)
	{
		dwError = GetLastError();
		wprintf(L"connection failed %d\n", dwError);
		goto quit;
	}
	wprintf(L"connected\n");

	hRequestHandle = WinHttpOpenRequest(hConnectionHandle,
		L"GET",
		pcwszPath,
		NULL,
		NULL,
		NULL,
		WINHTTP_FLAG_SECURE);
		// 0);
	if (hRequestHandle == NULL)
	{
		dwError = GetLastError();
		goto quit;
	}

	wprintf(L"open request successs\n");
	//
	// Request protocol upgrade from http to websocket.
	//
#pragma prefast(suppress:6387, "WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET does not take any arguments.")
	fStatus = WinHttpSetOption(hRequestHandle,
		WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,
		NULL,
		0);
	if (!fStatus)
	{
		dwError = GetLastError();
		goto quit;
	}

	//
	// Perform websocket handshake by sending a request and receiving server's response.
	// Application may specify additional headers if needed.
	//

	fStatus = WinHttpSendRequest(hRequestHandle,
		WINHTTP_NO_ADDITIONAL_HEADERS,
		0,
		NULL,
		0,
		0,
		0);
	if (!fStatus)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"upgraded \n");

	fStatus = WinHttpReceiveResponse(hRequestHandle, 0);
	if (!fStatus)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"receive response\n");

	//
	// Application should check what is the HTTP status code returned by the server and behave accordingly.
	// WinHttpWebSocketCompleteUpgrade will fail if the HTTP status code is different than 101.
	//

	hWebSocketHandle = WinHttpWebSocketCompleteUpgrade(hRequestHandle, NULL);
	if (hWebSocketHandle == NULL)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"upgrade complete\n");

	//
	// The request handle is not needed anymore. From now on we will use the websocket handle.
	//

	WinHttpCloseHandle(hRequestHandle);
	hRequestHandle = NULL;

	wprintf(L"Succesfully upgraded to websocket protocol\n");

	//
	// Send and receive data on the websocket protocol.
	//

	dwError = WinHttpWebSocketSend(hWebSocketHandle,
		WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,
		(PVOID)pcwszMessage,
		cdwMessageLength);
	if (dwError != ERROR_SUCCESS)
	{
		goto quit;
	}

	wprintf(L"Sent message to the server: '%s'\n", pcwszMessage);

	do
	{
		if (dwBufferLength == 0)
		{
			dwError = ERROR_NOT_ENOUGH_MEMORY;
			goto quit;
		}

		dwError = WinHttpWebSocketReceive(hWebSocketHandle,
			pbCurrentBufferPointer,
			dwBufferLength,
			&dwBytesTransferred,
			&eBufferType);
		if (dwError != ERROR_SUCCESS)
		{
			goto quit;
		}

		//
		// If we receive just part of the message restart the receive operation.
		//

		pbCurrentBufferPointer += dwBytesTransferred;
		dwBufferLength -= dwBytesTransferred;
	} while (eBufferType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE);

	//
	// We expected server just to echo single binary message.
	//

	if (eBufferType != WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE)
	{
		wprintf(L"Unexpected buffer type\n");
		dwError = ERROR_INVALID_PARAMETER;
		goto quit;
	}

	wprintf(L"Received message from the server: '%.*s'\n", dwBufferLength, (WCHAR*)rgbBuffer);

	//
	// Gracefully close the connection.
	//

	dwError = WinHttpWebSocketClose(hWebSocketHandle,
		WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS,
		NULL,
		0);
	if (dwError != ERROR_SUCCESS)
	{
		goto quit;
	}

	//
	// Check close status returned by the server.
	//

	dwError = WinHttpWebSocketQueryCloseStatus(hWebSocketHandle,
		&usStatus,
		rgbCloseReasonBuffer,
		ARRAYSIZE(rgbCloseReasonBuffer),
		&dwCloseReasonLength);
	if (dwError != ERROR_SUCCESS)
	{
		goto quit;
	}

	wprintf(L"The server closed the connection with status code: '%d' and reason: '%.*S'\n",
		(int)usStatus,
		dwCloseReasonLength,
		rgbCloseReasonBuffer);

quit:

	if (hRequestHandle != NULL)
	{
		WinHttpCloseHandle(hRequestHandle);
		hRequestHandle = NULL;
	}

	if (hWebSocketHandle != NULL)
	{
		WinHttpCloseHandle(hWebSocketHandle);
		hWebSocketHandle = NULL;
	}

	if (hConnectionHandle != NULL)
	{
		WinHttpCloseHandle(hConnectionHandle);
		hConnectionHandle = NULL;
	}

	if (hSessionHandle != NULL)
	{
		WinHttpCloseHandle(hSessionHandle);
		hSessionHandle = NULL;
	}

	if (dwError != ERROR_SUCCESS)
	{
		wprintf(L"Application failed with error: %u\n", dwError);
		return -1;
	}

	return 0;
}

@acgreek acgreek changed the title tls impatible with winhttp tls incompatible with winhttp Jun 3, 2021
@acgreek
Copy link
Collaborator Author

acgreek commented Jun 3, 2021

in tls/conn.go, after reading the handshake around line 1356, c.readRecord returns no data available.

With the other clients, this method returns data

@acgreek
Copy link
Collaborator Author

acgreek commented Jun 3, 2021

found the problem I think but I don't have a fix yet and I have to sign-off. If you look at the pcap, the client cipher comes in with the encrypted handshake data in the same packet. I believe your code isn't able to handle when the handle shake comes in with the encrypted data. With other clients the encrypted handshake data comes in, in a separate packet.

@lesismal
Copy link
Owner

lesismal commented Jun 3, 2021

I have built an exe and reproduced it, will debug it.

@lesismal
Copy link
Owner

lesismal commented Jun 4, 2021

also failed with gorilla server on win10:

set GOHOSTARCH=amd64
set GOHOSTOS=windows

go version go1.16 windows/amd64
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{}

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		mt, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}
		err = c.WriteMessage(mt, message)
		if err != nil {
			log.Println("write:", err)
			break
		}
		fmt.Println("onEcho:", string(message))
	}
}

func main() {
	addr := "localhost:8888"
	mux := &http.ServeMux{}
	mux.HandleFunc("/ws", echo)
	server := http.Server{
		Addr:    addr,
		Handler: mux,
	}
	log.Println("running on:", addr)
	log.Fatalln(server.ListenAndServeTLS("server.crt", "server.key"))
}

when I debug, the tls handshake succeeded for golang, but got the dwError = 12175(ERROR_WINHTTP_SECURE_FAILURE) for c++.

what's your windows version?
did you get the same error 12175(ERROR_WINHTTP_SECURE_FAILURE)?

@acgreek
Copy link
Collaborator Author

acgreek commented Jun 4, 2021

I was testing with a letsencrypt server cert so the windows client accepted it. I figured out how to disable the server cert verification in the c++ client app

to disabled the server cert verification add

        DWORD dwSSLFlag;
	dwSSLFlag = SECURITY_FLAG_IGNORE_UNKNOWN_CA;
	dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
	dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
	dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;

	WinHttpSetOption(hRequestHandle, WINHTTP_OPTION_SECURITY_FLAGS,
		&dwSSLFlag, sizeof(dwSSLFlag));		

after hRequestHandle is initialized.

here is the complete code

// winhttpExample.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#pragma comment(lib, "winhttp")

#include <Windows.h>
#include <WinHttp.h>
#include <stdio.h>

int __cdecl wmain()
{
	DWORD dwError = ERROR_SUCCESS;
	BOOL fStatus = FALSE;
	HINTERNET hSessionHandle = NULL;
	HINTERNET hConnectionHandle = NULL;
	HINTERNET hRequestHandle = NULL;
	HINTERNET hWebSocketHandle = NULL;
	BYTE rgbCloseReasonBuffer[123];
	BYTE rgbBuffer[1024];
	BYTE* pbCurrentBufferPointer = rgbBuffer;
	DWORD dwBufferLength = ARRAYSIZE(rgbBuffer);
	DWORD dwBytesTransferred = 0;
	DWORD dwCloseReasonLength = 0;
	USHORT usStatus = 0;
	WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType;
	INTERNET_PORT Port = 8888;
	const WCHAR* pcwszServerName = L"localhost";
	const WCHAR* pcwszPath = L"/ws";
	const WCHAR* pcwszMessage = L"Hello world";
	const DWORD cdwMessageLength = ARRAYSIZE(L"Hello world") * sizeof(WCHAR);

	wprintf(L"program started\n");
	//
	// Create session, connection and request handles.
	//
	hSessionHandle = WinHttpOpen(L"WebSocket sample",
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		NULL,
		NULL,
		0);
	if (hSessionHandle == NULL)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"session handle constructed\n");

	WinHttpSetOption(hSessionHandle, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, NULL, 0);
	hConnectionHandle = WinHttpConnect(hSessionHandle,
		pcwszServerName,
		Port,
		0);
	if (hConnectionHandle == NULL)
	{
		dwError = GetLastError();
		wprintf(L"connection failed %d\n", dwError);
		goto quit;
	}
	wprintf(L"connected\n");

	hRequestHandle = WinHttpOpenRequest(hConnectionHandle,
		L"GET",
		pcwszPath,
		NULL,
		NULL,
		NULL,
		WINHTTP_FLAG_SECURE);
		// 0);
	if (hRequestHandle == NULL)
	{
		dwError = GetLastError();
		goto quit;
	}
	DWORD dwSSLFlag;
	dwSSLFlag = SECURITY_FLAG_IGNORE_UNKNOWN_CA;
	dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
	dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
	dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;

	WinHttpSetOption(hRequestHandle, WINHTTP_OPTION_SECURITY_FLAGS,
		&dwSSLFlag, sizeof(dwSSLFlag));

	wprintf(L"open request successs\n");
	//
	// Request protocol upgrade from http to websocket.
	//
#pragma prefast(suppress:6387, "WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET does not take any arguments.")
	fStatus = WinHttpSetOption(hRequestHandle,
		WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,
		NULL,
		0);
	if (!fStatus)
	{
		dwError = GetLastError();
		goto quit;
	}

	//
	// Perform websocket handshake by sending a request and receiving server's response.
	// Application may specify additional headers if needed.
	//

	fStatus = WinHttpSendRequest(hRequestHandle,
		WINHTTP_NO_ADDITIONAL_HEADERS,
		0,
		NULL,
		0,
		0,
		0);
	if (!fStatus)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"upgraded \n");

	fStatus = WinHttpReceiveResponse(hRequestHandle, 0);
	if (!fStatus)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"receive response\n");

	//
	// Application should check what is the HTTP status code returned by the server and behave accordingly.
	// WinHttpWebSocketCompleteUpgrade will fail if the HTTP status code is different than 101.
	//

	hWebSocketHandle = WinHttpWebSocketCompleteUpgrade(hRequestHandle, NULL);
	if (hWebSocketHandle == NULL)
	{
		dwError = GetLastError();
		goto quit;
	}
	wprintf(L"upgrade complete\n");

	//
	// The request handle is not needed anymore. From now on we will use the websocket handle.
	//

	WinHttpCloseHandle(hRequestHandle);
	hRequestHandle = NULL;

	wprintf(L"Succesfully upgraded to websocket protocol\n");

	//
	// Send and receive data on the websocket protocol.
	//

	dwError = WinHttpWebSocketSend(hWebSocketHandle,
		WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,
		(PVOID)pcwszMessage,
		cdwMessageLength);
	if (dwError != ERROR_SUCCESS)
	{
		goto quit;
	}

	wprintf(L"Sent message to the server: '%s'\n", pcwszMessage);

	do
	{
		if (dwBufferLength == 0)
		{
			dwError = ERROR_NOT_ENOUGH_MEMORY;
			goto quit;
		}

		dwError = WinHttpWebSocketReceive(hWebSocketHandle,
			pbCurrentBufferPointer,
			dwBufferLength,
			&dwBytesTransferred,
			&eBufferType);
		if (dwError != ERROR_SUCCESS)
		{
			goto quit;
		}

		//
		// If we receive just part of the message restart the receive operation.
		//

		pbCurrentBufferPointer += dwBytesTransferred;
		dwBufferLength -= dwBytesTransferred;
	} while (eBufferType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE);

	//
	// We expected server just to echo single binary message.
	//

	if (eBufferType != WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE)
	{
		wprintf(L"Unexpected buffer type\n");
		dwError = ERROR_INVALID_PARAMETER;
		goto quit;
	}

	wprintf(L"Received message from the server: '%.*s'\n", dwBufferLength, (WCHAR*)rgbBuffer);

	//
	// Gracefully close the connection.
	//

	dwError = WinHttpWebSocketClose(hWebSocketHandle,
		WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS,
		NULL,
		0);
	if (dwError != ERROR_SUCCESS)
	{
		goto quit;
	}

	//
	// Check close status returned by the server.
	//

	dwError = WinHttpWebSocketQueryCloseStatus(hWebSocketHandle,
		&usStatus,
		rgbCloseReasonBuffer,
		ARRAYSIZE(rgbCloseReasonBuffer),
		&dwCloseReasonLength);
	if (dwError != ERROR_SUCCESS)
	{
		goto quit;
	}

	wprintf(L"The server closed the connection with status code: '%d' and reason: '%.*S'\n",
		(int)usStatus,
		dwCloseReasonLength,
		rgbCloseReasonBuffer);

quit:

	if (hRequestHandle != NULL)
	{
		WinHttpCloseHandle(hRequestHandle);
		hRequestHandle = NULL;
	}

	if (hWebSocketHandle != NULL)
	{
		WinHttpCloseHandle(hWebSocketHandle);
		hWebSocketHandle = NULL;
	}

	if (hConnectionHandle != NULL)
	{
		WinHttpCloseHandle(hConnectionHandle);
		hConnectionHandle = NULL;
	}

	if (hSessionHandle != NULL)
	{
		WinHttpCloseHandle(hSessionHandle);
		hSessionHandle = NULL;
	}

	if (dwError != ERROR_SUCCESS)
	{
		wprintf(L"Application failed with error: %u\n", dwError);
		return -1;
	}

	return 0;
}

this client works with your gorrilla websocket example above with out error for me

@lesismal
Copy link
Owner

lesismal commented Jun 4, 2021

fixed: lesismal/llib@d120a1b

@acgreek
Copy link
Collaborator Author

acgreek commented Jun 5, 2021

thank you

@acgreek acgreek closed this as completed Jun 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants