• Edited

Hello!

Using godot v4.3, I am trying to use TLS to implement secure authentication in game server, so both client and server are mine. Handling TCP connections provided by the StreamPeerTCP (connect_to_host on client side and TCPServer::take_connection on server side) in StreamPeerTLS leads to an error TLS handshake error: -27648, sometimes only in client, sometimes only on server and sometime on both.

I'm using self-signed certificate (and private key so on) generated by Godot. I read that error -27648 is something about certificate, but server has this error too, so I don't think that's the problem, but I tried to generate certificate using openssl, but the error remains. Also TLSOptions::client_unsafe gave no results.

Below you can find my test code. I tried to implement server which just makes client message to_upper and send it back.

Client side code:

extends Node

var _peer : StreamPeerTLS
var _last_status : StreamPeerTLS.Status

var _status_strings : Dictionary = {
	StreamPeerTLS.STATUS_DISCONNECTED : "STATUS_DISCONNECTED",
	StreamPeerTLS.STATUS_HANDSHAKING : "StreamPeerTLS.STATUS_HANDSHAKING",
	StreamPeerTLS.STATUS_CONNECTED : "StreamPeerTLS.STATUS_CONNECTED",
	StreamPeerTLS.STATUS_ERROR : "StreamPeerTLS.STATUS_ERROR",
	StreamPeerTLS.STATUS_ERROR_HOSTNAME_MISMATCH : "StreamPeerTLS.STATUS_ERROR_HOSTNAME_MISMATCH"
}

func _ready() -> void:
	var tcp_peer : StreamPeerTCP = StreamPeerTCP.new()
	var err : Error = tcp_peer.connect_to_host("localhost", 7070)
	if err != OK:
		printerr("conenct: %s" % error_string(err))
		return get_tree().quit(0)
	
	var tls_options : TLSOptions = TLSOptions.client(load("res://server/game.crt"))
	_peer = StreamPeerTLS.new()
	err = _peer.connect_to_stream(tcp_peer, "localhost", tls_options)
	if err != OK:
		printerr("tls sonnect_to_stream: %s" % error_string(err))
		get_tree().quit.call_deferred(1)
		return
	
	_last_status = _peer.get_status()
	print("Peer created. Status is %s" % _status_strings[_last_status])


func _process(delta: float) -> void:
	_peer.poll()
	
	if _peer.get_status() != _last_status:
		print("Peer status changed to %s" % _status_strings[_last_status])
		_last_status = _peer.get_status()
	
	if _peer.get_status() == StreamPeerTLS.STATUS_CONNECTED:
		var data : PackedByteArray = _peer.get_data(_peer.get_available_bytes())
		var str : String = data.get_string_from_ascii()
		%ServerResponseLabel.append_text(str)


func _on_server_msg_edit_text_submitted(new_text: String) -> void:
	if _peer.get_status() == StreamPeerTLS.STATUS_CONNECTED:
		_peer.put_data(new_text.to_ascii_buffer())
	else:
		printerr("Not connected")
	
	%ServerMsgEdit.clear()

Server code:

extends Node

var _tcp_server : TCPServer
var _tls_optinos : TLSOptions

var _connected_peers : Array[StreamPeerTLS] = []

var _status_strings : Dictionary = {
	StreamPeerTLS.STATUS_DISCONNECTED : "STATUS_DISCONNECTED",
	StreamPeerTLS.STATUS_HANDSHAKING : "StreamPeerTLS.STATUS_HANDSHAKING",
	StreamPeerTLS.STATUS_CONNECTED : "StreamPeerTLS.STATUS_CONNECTED",
	StreamPeerTLS.STATUS_ERROR : "StreamPeerTLS.STATUS_ERROR",
	StreamPeerTLS.STATUS_ERROR_HOSTNAME_MISMATCH : "StreamPeerTLS.STATUS_ERROR_HOSTNAME_MISMATCH"
}

func _ready() -> void:
	_tcp_server = TCPServer.new()
	if _tcp_server.listen(7070) != OK:
		printerr("listen")
		return get_tree().quit(1)
	
	_tls_optinos = TLSOptions.server(
		load("res://server/game-private.key"), load("res://server/game.crt")
	)


func _process(delta: float) -> void:
	if _tcp_server.is_connection_available():
		var connection : StreamPeerTCP = _tcp_server.take_connection()
		var tls_peer : StreamPeerTLS = StreamPeerTLS.new()
		if tls_peer.accept_stream(connection, _tls_optinos) != OK:
			printerr("Error accepting peer %s:%d" % [connection.get_connected_host(), connection.get_connected_port()])
		else:
			var id : int = randi()
			print("Peer %s:%d accepted with id = %d" % [connection.get_connected_host(), connection.get_connected_port(), id])
			print("%d status is: %s" % [id, _status_strings[tls_peer.get_status()]])
			tls_peer.set_meta("id", id)
			_connected_peers.append(tls_peer)
	
	
	for i:StreamPeerTLS in _connected_peers:
		if i.get_status() != StreamPeerTLS.STATUS_CONNECTED and i.get_status() != StreamPeerTLS.STATUS_HANDSHAKING:
			_connected_peers.erase.call_deferred(i)
			continue
		
		i.poll()
		
		if not i.has_meta("last_status") or i.get_status() != i.get_meta("last_status"):
			print("peer %d changed status to %s" % [i.get_meta("id"), _status_strings[i.get_status()]])
			i.set_meta("last_status", i.get_status())
		
		if i.get_status() == StreamPeerTLS.STATUS_CONNECTED:
			var bytes : PackedByteArray = i.get_data(i.get_available_bytes())
			var str : String = bytes.get_string_from_ascii()
			%ClientsMessagesLabel.append_text(str)
			str.to_upper()
			i.put_data(str.to_ascii_buffer())

And generating crypto stuff:

extends Node

func _ready() -> void:
	# don't forget to disable multi instanse before running this scene
	var crypto : Crypto = Crypto.new()
	
	var key : CryptoKey = crypto.generate_rsa(2048)
	var cert : X509Certificate = crypto.generate_self_signed_certificate(key,
		"CN=localhost,O=A Game Company,C=IT"
	)
	
	key.save("res://server/game-private.key")
	cert.save("res://server/game.crt")
	
	print("Generated")

Error:

(same on client and server)

Praying for help. You can find test project in attachments (its pretty simple, so comments isn't necessary). Thanks!

tlstest.zip
2MB

P. S. Also I tried to use different CN's, like localhost, default myserver, localhost:7070. Error remains.

  • Ok, sorry, it seems to be solved.

    The problem was that I was handling TCP connections with StreamPeerTLS instantly, while the former didn't connect yet. Apparently, one needs to wait StreamPeerTCP become connected, and only then call StreamPeerTLS::accept_stream/StreamPeerTLS::conenct_to_stream.

    Working project in attachments (its so messy, don't rely on it)

    tlstest.zip
    2MB

Ok, sorry, it seems to be solved.

The problem was that I was handling TCP connections with StreamPeerTLS instantly, while the former didn't connect yet. Apparently, one needs to wait StreamPeerTCP become connected, and only then call StreamPeerTLS::accept_stream/StreamPeerTLS::conenct_to_stream.

Working project in attachments (its so messy, don't rely on it)

tlstest.zip
2MB