From 7d3f028768ffd86819843ba1504752bbabf2dcb9 Mon Sep 17 00:00:00 2001 From: AleaJactaEst Date: Tue, 19 Dec 2023 01:18:26 +0100 Subject: [PATCH] update xmpp sasl --- client/Stream/Stream.gd | 589 ++++++++++++++++++++++++++-------------- 1 file changed, 379 insertions(+), 210 deletions(-) diff --git a/client/Stream/Stream.gd b/client/Stream/Stream.gd index e1cfc17..081b68b 100644 --- a/client/Stream/Stream.gd +++ b/client/Stream/Stream.gd @@ -5,6 +5,7 @@ extends Node # # https://xmpp.org/extensions/xep-0178.html # https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml +# https://datatracker.ietf.org/doc/html/rfc6120#section-6.5 # Boucle @@ -40,11 +41,12 @@ enum XMPPState { MISSING_TLS, # Missing feature TLS -> wait 30min and retry STARTED_TLS, # TLS connection is done SELECT_MECHANISM_AUTHENTICATE, - AUTHENTICATE_STEP_PLAIN, # Select mechanism authenticate PLAIN - AUTHENTICATE_STEP_1, # Launch authenticate - AUTHENTICATE_STEP_2, # Launch authenticate - AUTHENTICATE_STEP_3, # Launch authenticate - AUTHENTICATE_CHECK_RESULT, # Last time check result + AUTHENTICATE_STEP_PLAIN, # Select mechanism authenticate PLAIN + AUTHENTICATE_STEP_DIGEST_MD5, +# AUTHENTICATE_STEP_1, # Launch authenticate +# AUTHENTICATE_STEP_2, # Launch authenticate +# AUTHENTICATE_STEP_3, # Launch authenticate +# AUTHENTICATE_CHECK_RESULT, # Last time check result AUTHENTICATE_FAILURE, AUTHENTICATE_SUCCESS, AUTHENTICATED, # We finished authenticate @@ -141,22 +143,26 @@ var status:StreamState = StreamState.END var xmpp_state = XMPPState.NONE var authentication_methods = [] var selected_mechanism_authenticate:String = "" -var order_preference_mechanism_authenticate: Array = ['PLAIN'] +#var order_preference_mechanism_authenticate: Array = ['PLAIN'] +var order_preference_mechanism_authenticate: Array = ['DIGEST-MD5'] var banned_mechanism_authenticate:Array = [] +#DIGEST-MD5, PLAIN, SCRAM-SHA-512-PLUS, SCRAM-SHA-512, SCRAM-SHA-256-PLUS, SCRAM-SHA-256, SCRAM-SHA-1-PLUS, SCRAM-SHA-1, X-OAUTH2 +var auhtentification_step:int = 0 +var auhtentification_challenge:Array = [] -func reinit_stream(): - if ssl_peer != null: - ssl_peer.disconnect_from_stream() - else: - ssl_peer = StreamPeerTLS.new() - if tcp_peer != null: - tcp_peer.disconnect_from_host() - else: - tcp_peer = StreamPeerTCP.new() - tgt_peer = tcp_peer - start_stream() - stream_status = self.StreamState.START +#func reinit_stream(): +# if ssl_peer != null: +# ssl_peer.disconnect_from_stream() +# else: +# ssl_peer = StreamPeerTLS.new() +# if tcp_peer != null: +# tcp_peer.disconnect_from_host() +# else: +# tcp_peer = StreamPeerTCP.new() +# tgt_peer = tcp_peer +## start_stream() +# stream_status = self.StreamState.START func _init() -> void: @@ -167,7 +173,7 @@ func _init() -> void: func _process(delta) -> void: - print(server_xmpp_name, ":", port_number, " / get_status:" , tcp_peer.get_status(), " / count_connecting_time:", count_connecting_time, " / stream_status:", stream_status, " / xmpp_state:", XMPPState.keys()[xmpp_state]) + #print(server_xmpp_name, ":", port_number, " / get_status:" , tcp_peer.get_status(), " / count_connecting_time:", count_connecting_time, " / stream_status:", stream_status, " / xmpp_state:", XMPPState.keys()[xmpp_state]) if xmppclient == false: return # Wait MAX_WAIT_MISSING_TLS (s) if server can't negociate with TLS @@ -327,7 +333,13 @@ func _process(delta) -> void: return elif selected_mechanism_authenticate == "PLAIN": xmpp_state = XMPPState.AUTHENTICATE_STEP_PLAIN + auhtentification_step = 0 + elif selected_mechanism_authenticate == "DIGEST-MD5": + xmpp_state = XMPPState.AUTHENTICATE_STEP_DIGEST_MD5 + auhtentification_step = 0 + auhtentification_challenge = [] if xmpp_state == XMPPState.AUTHENTICATE_STEP_PLAIN: + # https://datatracker.ietf.org/doc/html/rfc4616 if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED: xmpp_state = XMPPState.NONE count_connecting_time = 0 @@ -365,10 +377,112 @@ func _process(delta) -> void: count_connecting_time = 0 xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE banned_mechanism_authenticate.append(selected_mechanism_authenticate) + if xmpp_state == XMPPState.AUTHENTICATE_STEP_DIGEST_MD5: + # https://datatracker.ietf.org/doc/html/rfc2831 + if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED: + xmpp_state = XMPPState.NONE + count_connecting_time = 0 + return + if count_connecting_time >= MAX_WAIT_CONNECTING: + count_connecting_time = 0 + negotiate_ssl_authenticate_digest_md5() + else: + count_connecting_time += delta + if (ssl_peer.has_method("poll")): + ssl_peer.poll() + if ssl_peer.get_available_bytes()>0: + var response = ssl_peer.get_string(ssl_peer.get_available_bytes()) + send_msg_debug.emit("Stream: response: " + response) + response = remove_stream_header(response) + if response.begins_with(" String: + var node_name:String = "" + var data:String = "" + var parser:XMLParser = XMLParser.new() + var cur:Array = [] + parser.open_buffer(response.to_utf8_buffer()) + while (parser.read() == OK): + if (parser.get_node_type()== XMLParser.NODE_ELEMENT): + node_name = parser.get_node_name() + cur.append(node_name) + print("node_name:", node_name) + var attributes_dict = {} + for idx in range(parser.get_attribute_count()): + attributes_dict[parser.get_attribute_name(idx)] = parser.get_attribute_value(idx) + print("The ", node_name, " element has the following attributes: ", attributes_dict) + elif (parser.get_node_type()== XMLParser.NODE_TEXT): + var correctpath:bool = true + print(nodes.size() , " != ", cur.size()) + if nodes.size() != cur.size(): + continue + for i in range(0, nodes.size()): + print(cur[i] , " != ", nodes[i]) + if cur[i] != nodes[i]: + correctpath = false + break + if correctpath: + data = parser.get_node_data() + print("data:", data) + return data + elif (parser.get_node_type()== XMLParser.NODE_ELEMENT_END): + cur.pop_back() + return "" + + +func get_xml_attribute(response:String, nodes:Array, attribute:String) -> String: + var node_name:String = "" + var data:String = "" + var parser:XMLParser = XMLParser.new() + var cur:Array = [] + parser.open_buffer(response.to_utf8_buffer()) + while (parser.read() == OK): + if (parser.get_node_type()== XMLParser.NODE_ELEMENT): + node_name = parser.get_node_name() + cur.append(node_name) + print("node_name:", node_name) + var attributes_dict = {} + for idx in range(parser.get_attribute_count()): + if parser.get_attribute_name(idx) == attribute: + return parser.get_attribute_value(idx) + elif (parser.get_node_type()== XMLParser.NODE_ELEMENT_END): + cur.pop_back() + return "" func get_result_authenticate(response:String) -> SASLReturn: - response = remove_stream_header(response) if response.begins_with(" void: func negotiate_ssl_authenticate_plain() -> void: - send_msg_debug.emit("Stream: sending request for plain") + send_msg_debug.emit("Stream: sending request for PLAIN") var msg:PackedByteArray = PackedByteArray() + # [authzid] + #msg += server_xmpp_name.to_ascii_buffer() + # UTF8NUL msg.push_back(0) + # authcid var t = account_name.split("@")[0] msg += t.to_ascii_buffer() - #msg += conv_string_to_PackedByteArray(account_name.split("@")[0]) + # UTF8NUL msg.push_back(0) + # passwd msg += password.to_ascii_buffer() var auth_account:String = Marshalls.raw_to_base64(msg) var request_sasl:String = " void: send_ssl_string(request_sasl) +func negotiate_ssl_authenticate_digest_md5() -> bool: + send_msg_debug.emit("Stream: sending request for DIGEST-MD5 (step:%d)" % auhtentification_step) + if auhtentification_step == 0: + var request_sasl:String = "" + "" + send_ssl_string(request_sasl) + auhtentification_step += 1 + return true + elif auhtentification_step == 1: + var tmp:String = Marshalls.base64_to_utf8(auhtentification_challenge[0]) + var tab:Array = tmp.split(",") + var realm:String = "" + var nonce:String = "" + var qop:String = "" + var charset:String = "" + var algorithm:String = "" + print(str(tab)) + for item in tab: + var tab2:Array = item.split("=") + var key:String = tab2[0] + var value:String = tab2[1] + if key == "realm": + realm = value.trim_prefix('"').trim_suffix('"') + if key == "nonce": + nonce = value.trim_prefix('"').trim_suffix('"') + elif key == "qop": + qop = value.trim_prefix('"').trim_suffix('"') + elif key == "charset": + charset = value + elif key == "algorithm": + algorithm = value + print(key," = ", value) + print(tmp, " ", qop, " ", charset, " ",nonce) + if qop != "auth": + send_msg_error.emit("Stream: Authenticate digest-md5, qop unknonw (%s)" % qop) + return false + if charset != "utf-8": + send_msg_error.emit("Stream: Authenticate digest-md5, charset unknonw (%s)" % charset) + return false + if algorithm != "md5-sess": + send_msg_error.emit("Stream: Authenticate digest-md5, algorithm unknonw (%s)" % algorithm) + return false + print(tmp) + auhtentification_step += 1 + return true + return false + + func negotiate_ssl_sasl(authentication_methods : Array) -> bool: if ( authentication_methods.has("PLAIN")): send_msg_debug.emit("Stream: sending request for plain") @@ -535,53 +704,53 @@ func negotiate_ssl_sasl(authentication_methods : Array) -> bool: # # -func connect_to_server_xmpp() -> void: - var res = tcp_peer.connect_to_host(server_xmpp_name, port_number) - if res == OK: - count_connecting_time = 0 - stream_status = self.StreamState.END - set_process(true) - tgt_peer = tcp_peer +#func connect_to_server_xmpp() -> void: +# var res = tcp_peer.connect_to_host(server_xmpp_name, port_number) +# if res == OK: +# count_connecting_time = 0 +# stream_status = self.StreamState.END +# set_process(true) +# tgt_peer = tcp_peer -func _process_old(delta) -> void: - print(server_xmpp_name, ":", port_number, " / get_status:" , tcp_peer.get_status(), " / count_connecting_time:", count_connecting_time, " / stream_status:", stream_status) - if tgt_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED: - print("STATUS_CONNECTED:", delta) - if (tgt_peer.has_method("poll")): - tgt_peer.poll() - if tgt_peer.get_available_bytes()>0: - var response = tgt_peer.get_string(tgt_peer.get_available_bytes()) - send_msg_debug.emit("Stream: response: " + response) - if stream_status == self.StreamState.STANZA: - #collect_stanza(remove_stream_header(response)) - print("collect_stanza") - else: - print("stream_process") - stream_process(remove_stream_header(response)) -# print(remove_stream_header(response)) - elif stream_status == self.StreamState.END: - reinit_stream() - #start_stream() - #stream_status = self.StreamState.START - count_connecting_time = 0 - if tgt_peer.get_status() == StreamPeerTCP.STATUS_CONNECTING: - print("STATUS_CONNECTING:", delta) - count_connecting_time += delta - if (tgt_peer.has_method("poll")): - tgt_peer.poll() - if count_connecting_time > 60: # (1 -> 1s) if it took more than 1s to connect, error - print("**** Stream: Stuck connecting, will now disconnect") - Global.msg_error("Stream: Stuck connecting, will now disconnect", []) - send_msg_debug.emit("Stream: Stuck connecting, will now disconnect") - tgt_peer.disconnect_from_host() #interrupts connection to nothing - set_process(false) # stop listening for packets - try_connect = 20 - stream_status = self.StreamState.END - reinit_stream() - if tgt_peer.get_status() == StreamPeerTCP.STATUS_NONE and xmppclient: - print("connect_to_server:", xmppclient) - connect_to_server() - xmppclient = false +#func _process_old(delta) -> void: +# print(server_xmpp_name, ":", port_number, " / get_status:" , tcp_peer.get_status(), " / count_connecting_time:", count_connecting_time, " / stream_status:", stream_status) +# if tgt_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED: +# print("STATUS_CONNECTED:", delta) +# if (tgt_peer.has_method("poll")): +# tgt_peer.poll() +# if tgt_peer.get_available_bytes()>0: +# var response = tgt_peer.get_string(tgt_peer.get_available_bytes()) +# send_msg_debug.emit("Stream: response: " + response) +# if stream_status == self.StreamState.STANZA: +# #collect_stanza(remove_stream_header(response)) +# print("collect_stanza") +# else: +# print("stream_process") +# stream_process(remove_stream_header(response)) +## print(remove_stream_header(response)) +# elif stream_status == self.StreamState.END: +# reinit_stream() +# #start_stream() +# #stream_status = self.StreamState.START +# count_connecting_time = 0 +# if tgt_peer.get_status() == StreamPeerTCP.STATUS_CONNECTING: +# print("STATUS_CONNECTING:", delta) +# count_connecting_time += delta +# if (tgt_peer.has_method("poll")): +# tgt_peer.poll() +# if count_connecting_time > 60: # (1 -> 1s) if it took more than 1s to connect, error +# print("**** Stream: Stuck connecting, will now disconnect") +# Global.msg_error("Stream: Stuck connecting, will now disconnect", []) +# send_msg_debug.emit("Stream: Stuck connecting, will now disconnect") +# tgt_peer.disconnect_from_host() #interrupts connection to nothing +# set_process(false) # stop listening for packets +# try_connect = 20 +# stream_status = self.StreamState.END +# reinit_stream() +# if tgt_peer.get_status() == StreamPeerTCP.STATUS_NONE and xmppclient: +# print("connect_to_server:", xmppclient) +# connect_to_server() +# xmppclient = false func connect_to_server(): @@ -630,24 +799,24 @@ func conv_string_to_PackedByteArray(message:String) -> PackedByteArray: # tgt_peer.put_data(message) -func start_stream() -> void: - """ Send the stream header. - Needs to be done several times over stream negotiation. - """ - var server_name = account_name.split("@")[-1] - var message:String = "" + \ - " " - # " from=\"" + account_name + "\" " + \ - # + locale + "'" + - print(message) - send_string(message) +#func start_stream() -> void: +# """ Send the stream header. +# Needs to be done several times over stream negotiation. +# """ +# var server_name = account_name.split("@")[-1] +# var message:String = "" + \ +# " " +# # " from=\"" + account_name + "\" " + \ +# # + locale + "'" + +# print(message) +# send_string(message) func remove_stream_header(text :String) -> String: var index = 0 @@ -666,130 +835,130 @@ func remove_stream_header(text :String) -> String: return text -func stream_process(response :String = "") -> void: - """ Try to authenticate using SASL. We can currently only do plain. - For SCRAM based methods, we need HMAC at the very least. - """ - - if (!response.length() == 0 and stream_status != self.StreamState.STANZA): - if response.begins_with(" 0): - var tmp:String = "" - var sep:String = "" - for item in authentication_methods: - tmp += sep + item - sep = ", " - send_msg_debug.emit("Stream: authentication methods: " + tmp) - negotiate_sasl(authentication_methods) - stream_status = self.StreamState.AUTHENTICATE - - else: - if stream_status == StreamState.TLS: - if response.begins_with(" void: +# """ Try to authenticate using SASL. We can currently only do plain. +# For SCRAM based methods, we need HMAC at the very least. +# """ +# +# if (!response.length() == 0 and stream_status != self.StreamState.STANZA): +# if response.begins_with(" 0): +# var tmp:String = "" +# var sep:String = "" +# for item in authentication_methods: +# tmp += sep + item +# sep = ", " +# send_msg_debug.emit("Stream: authentication methods: " + tmp) +# negotiate_sasl(authentication_methods) +# stream_status = self.StreamState.AUTHENTICATE +# +# else: +# if stream_status == StreamState.TLS: +# if response.begins_with(" bool: - #var ssl_peer = StreamPeerTLS.new() - # I am unsure how to validate the ssl certificate for an unknown host? - #var ssl_succes = FAILED - var options:TLSOptions = TLSOptions.client_unsafe() - print("accept_stream") - #var ssl_succes = ssl_peer.accept_stream(tcp_peer, options) - var ssl_succes = ssl_peer.connect_to_stream(tcp_peer, "localhost", options) - print("resultat:", ssl_succes) - if ssl_succes == OK: - send_msg_debug.emit("Stream: switched to SSL!") - print("get_status:", ssl_peer.get_status()) - # Wait connection done - while ssl_peer.get_status() == StreamPeerTLS.STATUS_HANDSHAKING: - ssl_peer.poll() - print("get_status:", ssl_peer.get_status()) - tgt_peer = ssl_peer - start_stream() - print("get_status:", ssl_peer.get_status()) - print("-----") - #stream_status == StreamState.START - return true - else: - send_msg_error.emit("SSL failed, error %".format([ssl_succes], "%")) - return false +#func negotiate_tls() -> bool: +# #var ssl_peer = StreamPeerTLS.new() +# # I am unsure how to validate the ssl certificate for an unknown host? +# #var ssl_succes = FAILED +# var options:TLSOptions = TLSOptions.client_unsafe() +# print("accept_stream") +# #var ssl_succes = ssl_peer.accept_stream(tcp_peer, options) +# var ssl_succes = ssl_peer.connect_to_stream(tcp_peer, "localhost", options) +# print("resultat:", ssl_succes) +# if ssl_succes == OK: +# send_msg_debug.emit("Stream: switched to SSL!") +# print("get_status:", ssl_peer.get_status()) +# # Wait connection done +# while ssl_peer.get_status() == StreamPeerTLS.STATUS_HANDSHAKING: +# ssl_peer.poll() +# print("get_status:", ssl_peer.get_status()) +# tgt_peer = ssl_peer +# start_stream() +# print("get_status:", ssl_peer.get_status()) +# print("-----") +# #stream_status == StreamState.START +# return true +# else: +# send_msg_error.emit("SSL failed, error %".format([ssl_succes], "%")) +# return false +# +# +#func negotiate_sasl(authentication_methods : Array) -> void: +# if ( authentication_methods.has("PLAIN")): +# send_msg_debug.emit("Stream: sending request for plain") +# var msg:PackedByteArray = PackedByteArray() +# msg.push_back(0) +# var t = account_name.split("@")[0] +# msg += t.to_ascii_buffer() +# #msg += conv_string_to_PackedByteArray(account_name.split("@")[0]) +# msg.push_back(0) +# msg += password.to_ascii_buffer() +# var auth_account:String = Marshalls.raw_to_base64(msg) +# var request_sasl:String = "" + auth_account + "" +# send_string(request_sasl) +# else: +# send_msg_error.emit("Impossible to authenticate (unknown protocol)") +# end_stream() - -func negotiate_sasl(authentication_methods : Array) -> void: - if ( authentication_methods.has("PLAIN")): - send_msg_debug.emit("Stream: sending request for plain") - var msg:PackedByteArray = PackedByteArray() - msg.push_back(0) - var t = account_name.split("@")[0] - msg += t.to_ascii_buffer() - #msg += conv_string_to_PackedByteArray(account_name.split("@")[0]) - msg.push_back(0) - msg += password.to_ascii_buffer() - var auth_account:String = Marshalls.raw_to_base64(msg) - var request_sasl:String = "" + auth_account + "" - send_string(request_sasl) - else: - send_msg_error.emit("Impossible to authenticate (unknown protocol)") - end_stream() - - -func end_stream() -> void: - """ End the stream """ - send_msg_debug.emit("Stream: Ending stream") - send_string("") - if tcp_peer.has_method("disconnect_from_stream"): - tcp_peer.disconnect_from_stream() - else: - tcp_peer.disconnect_from_host() - set_process(false) - stream_status = StreamState.END +# +#func end_stream() -> void: +# """ End the stream """ +# send_msg_debug.emit("Stream: Ending stream") +# send_string("") +# if tcp_peer.has_method("disconnect_from_stream"): +# tcp_peer.disconnect_from_stream() +# else: +# tcp_peer.disconnect_from_host() +# set_process(false) +# stream_status = StreamState.END