def get_challenge(self, mac="xor"): if self.salt is not None: log.error("challenge already sent!") return None self.salt = get_salt() #we need the raw password, so tell the client to use "xor": return self.salt, mac
def main(argv): from xpra.util import xor from xpra.net.crypto import get_salt, get_digests, gendigest from xpra.platform import program_context with program_context("LDAP3-Password-Auth", "LDAP3-Password-Authentication"): for x in list(argv): if x=="-v" or x=="--verbose": enable_debug_for("auth") argv.remove(x) if len(argv) not in (3,4,5,6): sys.stderr.write("%s invalid arguments\n" % argv[0]) sys.stderr.write("usage: %s username password [host] [port] [tls]\n" % argv[0]) return 1 username = argv[1] password = argv[2] kwargs = {} if len(argv)>=4: kwargs["host"] = argv[3] if len(argv)>=5: kwargs["port"] = argv[4] if len(argv)>=6: kwargs["tls"] = argv[5] a = Authenticator(username, **kwargs) server_salt, digest = a.get_challenge(["xor"]) salt_digest = a.choose_salt_digest(get_digests()) assert digest=="xor" client_salt = get_salt(len(server_salt)) combined_salt = gendigest(salt_digest, client_salt, server_salt) response = xor(password, combined_salt) r = a.authenticate(response, client_salt) print("success: %s" % r) return int(not r)
def make_hello_base(self): capabilities = flatten_dict(get_network_caps()) import struct bits = struct.calcsize("P") * 8 capabilities.update( { "version": local_version, "encoding.generic": True, "namespace": True, "hostname": socket.gethostname(), "uuid": self.uuid, "username": self.username, "name": get_name(), "client_type": self.client_type(), "python.version": sys.version_info[:3], "python.bits": bits, "compression_level": self.compression_level, "argv": sys.argv, } ) capabilities.update(self.get_file_transfer_features()) if self.display: capabilities["display"] = self.display def up(prefix, d): updict(capabilities, prefix, d) up("build", self.get_version_info()) mid = get_machine_id() if mid: capabilities["machine_id"] = mid if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS iv = get_iv() key_salt = get_salt() iterations = get_iterations() padding = choose_padding(self.server_padding_options) up( "cipher", { "": self.encryption, "iv": iv, "key_salt": key_salt, "key_stretch_iterations": iterations, "padding": padding, "padding.options": PADDING_OPTIONS, }, ) key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations, padding) netlog( "encryption capabilities: %s", dict((k, v) for k, v in capabilities.items() if k.startswith("cipher")) ) capabilities.update(self.hello_extra) return capabilities
def main(argv): from xpra.platform import program_context with program_context("Kerberos-Password-Auth", "Kerberos-Password-Authentication"): if len(argv) not in (3, 4, 5): sys.stderr.write("%s invalid arguments\n" % argv[0]) sys.stderr.write( "usage: %s username password [service [realm]]\n" % argv[0]) return 1 username = argv[1] password = argv[2] kwargs = {} if len(argv) >= 4: kwargs["service"] = argv[3] if len(argv) == 5: kwargs["realm"] = argv[4] a = Authenticator(username, **kwargs) server_salt, digest = a.get_challenge(["xor"]) salt_digest = a.choose_salt_digest(get_digests()) assert digest == "xor" client_salt = get_salt(len(server_salt)) combined_salt = gendigest(salt_digest, client_salt, server_salt) response = xor(password, combined_salt) a.authenticate(response, client_salt) return 0
def send_hello(self, challenge_response=None, client_salt=None): try: hello = self.make_hello_base() if self.has_password() and not challenge_response: #avoid sending the full hello: tell the server we want #a packet challenge first hello["challenge"] = True else: hello.update(self.make_hello()) except InitExit as e: log.error("error preparing connection:") log.error(" %s", e) self.quit(EXIT_INTERNAL_ERROR) return except Exception as e: log.error("error preparing connection: %s", e, exc_info=True) self.quit(EXIT_INTERNAL_ERROR) return if challenge_response: hello["challenge_response"] = challenge_response #make it harder for a passive attacker to guess the password length #by observing packet sizes (only relevant for wss and ssl) hello["challenge_padding"] = get_salt( max(32, 512 - len(challenge_response))) if client_salt: hello["challenge_client_salt"] = client_salt log("send_hello(%s) packet=%s", hexstr(challenge_response or ""), hello) self.send("hello", hello)
def send_challenge_reply(self, packet, password): if not password: if self.password_file: self.auth_error( EXIT_PASSWORD_FILE_ERROR, "failed to load password from file%s %s" % (engs(self.password_file), csv(self.password_file)), "no password available") else: self.auth_error( EXIT_PASSWORD_REQUIRED, "this server requires authentication and no password is available" ) return server_salt = bytestostr(packet[1]) if self.encryption: assert len( packet ) >= 3, "challenge does not contain encryption details to use for the response" server_cipher = typedict(packet[2]) key = self.get_encryption_key() if key is None: self.auth_error(EXIT_ENCRYPTION, "the server does not use any encryption", "client requires encryption") return if not self.set_server_encryption(server_cipher, key): return #all server versions support a client salt, #they also tell us which digest to use: digest = bytestostr(packet[3]) actual_digest = digest.split(":", 1)[0] l = len(server_salt) salt_digest = "xor" if len(packet) >= 5: salt_digest = bytestostr(packet[4]) if salt_digest == "xor": #with xor, we have to match the size assert l >= 16, "server salt is too short: only %i bytes, minimum is 16" % l assert l <= 256, "server salt is too long: %i bytes, maximum is 256" % l else: #other digest, 32 random bytes is enough: l = 32 client_salt = get_salt(l) salt = gendigest(salt_digest, client_salt, server_salt) authlog("combined %s salt(%s, %s)=%s", salt_digest, hexstr(server_salt), hexstr(client_salt), hexstr(salt)) challenge_response = gendigest(actual_digest, password, salt) if not challenge_response: log("invalid digest module '%s': %s", actual_digest) self.auth_error( EXIT_UNSUPPORTED, "server requested '%s' digest but it is not supported" % actual_digest, "invalid digest") return authlog("%s(%s, %s)=%s", actual_digest, repr(password), repr(salt), repr(challenge_response)) self.do_send_challenge_reply(challenge_response, client_salt)
def get_challenge(self, digests): if self.salt is not None: log.error("Error: authentication challenge already sent!") return None self.salt = get_salt() self.digest = choose_digest(digests) self.challenge_sent = True return self.salt, self.digest
def get_challenge(self, digests): if self.salt is not None: log.error("Error: authentication challenge already sent!") return None self.salt = get_salt() self.digest = choose_digest(digests) #we need the raw password, so tell the client to use "xor": return self.salt, self.digest
def get_challenge(self, digests): assert not self.challenge_sent if "gss" not in digests: log.error("Error: client does not support gss authentication") return None self.salt = get_salt() self.challenge_sent = True return self.salt, "gss:%s" % self.service
def make_hello_base(self): capabilities = flatten_dict(get_network_caps()) import struct bits = struct.calcsize("P") * 8 capabilities.update({ "version": XPRA_VERSION, "encoding.generic": True, "namespace": True, "hostname": socket.gethostname(), "uuid": self.uuid, "username": self.username, "name": get_name(), "client_type": self.client_type(), "python.version": sys.version_info[:3], "python.bits": bits, "compression_level": self.compression_level, "argv": sys.argv, }) capabilities.update(self.get_file_transfer_features()) if self.display: capabilities["display"] = self.display def up(prefix, d): updict(capabilities, prefix, d) up("build", self.get_version_info()) mid = get_machine_id() if mid: capabilities["machine_id"] = mid if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS iv = get_iv() key_salt = get_salt() iterations = get_iterations() padding = choose_padding(self.server_padding_options) up( "cipher", { "": self.encryption, "iv": iv, "key_salt": key_salt, "key_stretch_iterations": iterations, "padding": padding, "padding.options": PADDING_OPTIONS, }) key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations, padding) netlog( "encryption capabilities: %s", dict((k, v) for k, v in capabilities.items() if k.startswith("cipher"))) capabilities.update(self.hello_extra) return capabilities
def get_challenge(self): if self.salt is not None: log.error("challenge already sent!") if self.salt is not False: self.salt = False return None self.salt = get_salt() #this authenticator can use the safer "hmac" digest: return self.salt, "hmac"
def get_challenge(self, digests): if self.salt is not None: log.error("challenge already sent!") if self.salt is not False: self.salt = False return None self.salt = get_salt() self.digest = choose_digest(digests) if not self.digest: return None return self.salt, self.digest
def main(argv): from xpra.platform import program_context with program_context("GSS-Auth", "GSS-Authentication"): if len(argv) != 3: sys.stderr.write("%s invalid arguments\n" % argv[0]) sys.stderr.write("usage: %s username token\n" % argv[0]) return 1 username = argv[1] token = argv[2] kwargs = {} a = Authenticator(username, **kwargs) server_salt, digest = a.get_challenge(["xor"]) salt_digest = a.choose_salt_digest(get_digests()) assert digest == "xor" client_salt = get_salt(len(server_salt)) combined_salt = gendigest(salt_digest, client_salt, server_salt) response = xor(token, combined_salt) a.authenticate(response, client_salt) return 0
def _process_challenge(self, packet): authlog("processing challenge: %s", packet[1:]) def warn_server_and_exit(code, message, server_message="authentication failed"): authlog.error("Error: authentication failed:") authlog.error(" %s", message) self.disconnect_and_quit(code, server_message) if not self.has_password(): warn_server_and_exit( EXIT_PASSWORD_REQUIRED, "this server requires authentication, please provide a password", "no password available", ) return password = self.load_password() if not password: warn_server_and_exit( EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file, "no password available", ) return salt = packet[1] if self.encryption: assert len(packet) >= 3, "challenge does not contain encryption details to use for the response" server_cipher = typedict(packet[2]) key = self.get_encryption_key() if key is None: warn_server_and_exit( EXIT_ENCRYPTION, "the server does not use any encryption", "client requires encryption" ) return if not self.set_server_encryption(server_cipher, key): return # all server versions support a client salt, # they also tell us which digest to use: digest = packet[3] client_salt = get_salt(len(salt)) # TODO: use some key stretching algorigthm? (meh) salt = xor(salt, client_salt) if digest == b"hmac": import hmac, hashlib password = strtobytes(password) salt = strtobytes(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() elif digest == b"xor": # don't send XORed password unencrypted: encrypted = self._protocol.cipher_out or self._protocol.get_info().get("type") == "ssl" local = self.display_desc.get("local", False) authlog("xor challenge, encrypted=%s, local=%s", encrypted, local) if local and ALLOW_LOCALHOST_PASSWORDS: pass elif not encrypted and not ALLOW_UNENCRYPTED_PASSWORDS: warn_server_and_exit( EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest, "invalid digest", ) return salt = salt[: len(password)] challenge_response = strtobytes(xor(password, salt)) else: warn_server_and_exit( EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest, "invalid digest" ) return if digest: authlog("%s(%s, %s)=%s", digest, binascii.hexlify(password), binascii.hexlify(salt), challenge_response) self.password_sent = True self.remove_packet_handlers("challenge") self.send_hello(challenge_response, client_salt)
def get_challenge(self): return get_salt(), "hmac"
def _process_challenge(self, packet): authlog("processing challenge: %s", packet[1:]) def warn_server_and_exit(code, message, server_message="authentication failed"): authlog.error("Error: authentication failed:") authlog.error(" %s", message) self.disconnect_and_quit(code, server_message) if not self.has_password(): warn_server_and_exit( EXIT_PASSWORD_REQUIRED, "this server requires authentication, please provide a password", "no password available") return password = self.load_password() if not password: warn_server_and_exit( EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file, "no password available") return salt = packet[1] if self.encryption: assert len( packet ) >= 3, "challenge does not contain encryption details to use for the response" server_cipher = typedict(packet[2]) key = self.get_encryption_key() if key is None: warn_server_and_exit(EXIT_ENCRYPTION, "the server does not use any encryption", "client requires encryption") return if not self.set_server_encryption(server_cipher, key): return #all server versions support a client salt, #they also tell us which digest to use: digest = packet[3] client_salt = get_salt(len(salt)) #TODO: use some key stretching algorigthm? (meh) try: from xpra.codecs.xor.cyxor import xor_str #@UnresolvedImport salt = xor_str(salt, client_salt) except: salt = xor(salt, client_salt) if digest == b"hmac": import hmac, hashlib password = strtobytes(password) salt = strtobytes(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() elif digest == b"xor": #don't send XORed password unencrypted: if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS: warn_server_and_exit( EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest, "invalid digest") return salt = salt[:len(password)] challenge_response = strtobytes(xor(password, salt)) else: warn_server_and_exit( EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest, "invalid digest") return if digest: authlog("%s(%s, %s)=%s", digest, binascii.hexlify(password), binascii.hexlify(salt), challenge_response) self.password_sent = True self.remove_packet_handlers("challenge") self.send_hello(challenge_response, client_salt)
def get_challenge(self, digests): return get_salt(), choose_digest(digests)
def get_challenge(self, digests): self.challenge_sent = True return get_salt(), choose_digest(digests)
def process_server_packet(self, proto, packet): packet_type = packet[0] log("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="disconnect": log("got disconnect from server: %s", packet[1]) if self.exit: self.server_protocol.close() else: self.stop("disconnect from server: %s" % packet[1]) elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: from xpra.net.crypto import crypto_backend_init, new_cipher_caps, DEFAULT_PADDING crypto_backend_init() padding_options = self.caps.strlistget("cipher.padding.options", [DEFAULT_PADDING]) auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key, padding_options) caps.update(auth_caps) #may need to bump packet size: proto.max_packet_size = maxw*maxh*4*4 file_transfer = self.caps.boolget("file-transfer") and c.boolget("file-transfer") file_size_limit = max(self.caps.intget("file-size-limit"), c.intget("file-size-limit")) file_max_packet_size = int(file_transfer) * (1024 + file_size_limit*1024*1024) self.client_protocol.max_packet_size = max(self.client_protocol.max_packet_size, file_max_packet_size) self.server_protocol.max_packet_size = max(self.server_protocol.max_packet_size, file_max_packet_size) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(self.get_proxy_info(proto)) elif packet_type=="lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type=="draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return #we do want to reformat cursor packets... #as they will have been uncompressed by the network layer already: elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", "png", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet)>=8: #hard to distinguish png cursors from normal cursors... try: int(packet[1]) self._packet_recompress(packet, 8, "cursor") except: self._packet_recompress(packet, 9, "cursor") elif packet_type=="window-icon": self._packet_recompress(packet, 5, "icon") elif packet_type=="send-file": if packet[6]: packet[6] = Compressed("file-data", packet[6]) elif packet_type=="send-file-chunk": if packet[3]: packet[3] = Compressed("file-chunk-data", packet[3]) elif packet_type=="challenge": from xpra.net.crypto import get_salt #client may have already responded to the challenge, #so we have to handle authentication from this end salt = packet[1] digest = packet[3] client_salt = get_salt(len(salt)) salt = xor_str(salt, client_salt) if digest!=b"hmac": self.stop("digest mode '%s' not supported", std(digest)) return password = self.disp_desc.get("password", self.session_options.get("password")) log("password from %s / %s = %s", self.disp_desc, self.session_options, password) if not password: self.stop("authentication requested by the server, but no password available for this session") return import hmac, hashlib password = strtobytes(password) salt = strtobytes(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() log.info("sending %s challenge response", digest) self.send_hello(challenge_response, client_salt) return self.queue_client_packet(packet)
def make_hello_base(self): capabilities = flatten_dict(get_network_caps()) import struct bits = struct.calcsize("P") * 8 capabilities.update({ "version" : XPRA_VERSION, "encoding.generic" : True, "namespace" : True, "hostname" : socket.gethostname(), "uuid" : self.uuid, "username" : self.username, "name" : get_name(), "client_type" : self.client_type(), "python.version" : sys.version_info[:3], "python.bits" : bits, "compression_level" : self.compression_level, "argv" : sys.argv, }) capabilities.update(self.get_file_transfer_features()) if self.display: capabilities["display"] = self.display def up(prefix, d): updict(capabilities, prefix, d) up("build", self.get_version_info()) mid = get_machine_id() if mid: capabilities["machine_id"] = mid #get socket speed if we have it: pinfo = self._protocol.get_info() netlog("protocol info=%s", pinfo) socket_speed = pinfo.get("socket", {}).get("speed") if socket_speed: capabilities["connection-data"] = {"speed" : socket_speed} bandwidth_limit = self.bandwidth_limit log("bandwidth-limit=%s, socket-speed=%s", self.bandwidth_limit, socket_speed) if bandwidth_limit is None: if socket_speed: #auto: use 80% of socket speed if we have it: bandwidth_limit = socket_speed*AUTO_BANDWIDTH_PCT//100 or 0 else: bandwidth_limit = 0 if bandwidth_limit>0: capabilities["bandwidth-limit"] = bandwidth_limit if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS iv = get_iv() key_salt = get_salt() iterations = get_iterations() padding = choose_padding(self.server_padding_options) up("cipher", { "" : self.encryption, "iv" : iv, "key_salt" : key_salt, "key_stretch_iterations": iterations, "padding" : padding, "padding.options" : PADDING_OPTIONS, }) key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return self._protocol.set_cipher_in(self.encryption, iv, key, key_salt, iterations, padding) netlog("encryption capabilities: %s", dict((k,v) for k,v in capabilities.items() if k.startswith("cipher"))) capabilities.update(self.hello_extra) return capabilities
def verify_hello(self, proto, c): remote_version = c.strget("version") verr = version_compat_check(remote_version) if verr is not None: self.disconnect_client(proto, VERSION_ERROR, "incompatible version: %s" % verr) proto.close() return False def auth_failed(msg): authlog.error("Error: authentication failed") authlog.error(" %s", msg) self.timeout_add(1000, self.disconnect_client, proto, msg) #authenticator: username = c.strget("username") if proto.authenticator is None and proto.auth_class: authlog("creating authenticator %s", proto.auth_class) try: auth, aclass, options = proto.auth_class ainstance = aclass(username, **options) proto.authenticator = ainstance authlog("%s=%s", auth, ainstance) except Exception as e: authlog.error("Error instantiating %s:", proto.auth_class) authlog.error(" %s", e) auth_failed("authentication failed") return False self.digest_modes = c.get("digest", ("hmac", )) #client may have requested encryption: cipher = c.strget("cipher") cipher_iv = c.strget("cipher.iv") key_salt = c.strget("cipher.key_salt") iterations = c.intget("cipher.key_stretch_iterations") padding = c.strget("cipher.padding", DEFAULT_PADDING) padding_options = c.strlistget("cipher.padding.options", [DEFAULT_PADDING]) auth_caps = {} if cipher and cipher_iv: if cipher not in ENCRYPTION_CIPHERS: authlog.warn("unsupported cipher: %s", cipher) auth_failed("unsupported cipher") return False encryption_key = self.get_encryption_key(proto.authenticator, proto.keyfile) if encryption_key is None: auth_failed("encryption key is missing") return False if padding not in ALL_PADDING_OPTIONS: auth_failed("unsupported padding: %s" % padding) return False authlog("set output cipher using encryption key '%s'", repr_ellipsized(encryption_key)) proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations, padding) #use the same cipher as used by the client: auth_caps = new_cipher_caps(proto, cipher, encryption_key, padding_options) authlog("server cipher=%s", auth_caps) else: if proto.encryption: authlog("client does not provide encryption tokens") auth_failed("missing encryption tokens") return False auth_caps = None #verify authentication if required: if (proto.authenticator and proto.authenticator.requires_challenge() ) or c.get("challenge") is not None: challenge_response = c.strget("challenge_response") client_salt = c.strget("challenge_client_salt") authlog( "processing authentication with %s, response=%s, client_salt=%s, challenge_sent=%s", proto.authenticator, challenge_response, binascii.hexlify(client_salt or ""), proto.challenge_sent) #send challenge if this is not a response: if not challenge_response: if proto.challenge_sent: auth_failed( "invalid state, challenge already sent - no response!") return False if proto.authenticator: challenge = proto.authenticator.get_challenge() if challenge is None: auth_failed( "invalid state, unexpected challenge response") return False authlog("challenge: %s", challenge) salt, digest = challenge authlog.info( "Authentication required by %s authenticator module", proto.authenticator) authlog.info(" sending challenge for '%s' using %s digest", username, digest) if digest not in self.digest_modes: auth_failed( "cannot proceed without %s digest support" % digest) return False else: authlog.warn( "Warning: client expects a challenge but this connection is unauthenticated" ) #fake challenge so the client will send the real hello: salt = get_salt() digest = "hmac" proto.challenge_sent = True proto.send_now(("challenge", salt, auth_caps or "", digest)) return False if not proto.authenticator.authenticate(challenge_response, client_salt): auth_failed("invalid challenge response") return False authlog("authentication challenge passed") else: #did the client expect a challenge? if c.boolget("challenge"): authlog.warn( "this server does not require authentication (client supplied a challenge)" ) return auth_caps
def process_server_packet(self, proto, packet): packet_type = packet[0] log("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="disconnect": log("got disconnect from server: %s", packet[1]) if self.exit: self.server_protocol.close() else: self.stop("disconnect from server: %s" % packet[1]) elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: from xpra.net.crypto import crypto_backend_init, new_cipher_caps, DEFAULT_PADDING crypto_backend_init() padding_options = self.caps.strlistget("cipher.padding.options", [DEFAULT_PADDING]) auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key, padding_options) caps.update(auth_caps) #may need to bump packet size: proto.max_packet_size = maxw*maxh*4*4 file_transfer = self.caps.boolget("file-transfer") and c.boolget("file-transfer") file_size_limit = max(self.caps.intget("file-size-limit"), c.intget("file-size-limit")) file_max_packet_size = int(file_transfer) * (1024 + file_size_limit*1024*1024) self.client_protocol.max_packet_size = max(self.client_protocol.max_packet_size, file_max_packet_size) self.server_protocol.max_packet_size = max(self.server_protocol.max_packet_size, file_max_packet_size) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(self.get_proxy_info(proto)) elif packet_type=="lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type=="draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return #we do want to reformat cursor packets... #as they will have been uncompressed by the network layer already: elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", "png", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet)>=8: #hard to distinguish png cursors from normal cursors... try: int(packet[1]) self._packet_recompress(packet, 8, "cursor") except: self._packet_recompress(packet, 9, "cursor") elif packet_type=="window-icon": self._packet_recompress(packet, 5, "icon") elif packet_type=="send-file": if packet[6]: packet[6] = Compressed("file-data", packet[6]) elif packet_type=="send-file-chunk": if packet[3]: packet[3] = Compressed("file-chunk-data", packet[3]) elif packet_type=="challenge": from xpra.net.crypto import get_salt #client may have already responded to the challenge, #so we have to handle authentication from this end salt = packet[1] digest = packet[3] client_salt = get_salt(len(salt)) salt = xor_str(salt, client_salt) if digest!=b"hmac": self.stop("digest mode '%s' not supported", std(digest)) return password = self.session_options.get("password") if not password: self.stop("authentication requested by the server, but no password available for this session") return import hmac, hashlib password = strtobytes(password) salt = strtobytes(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() log.info("sending %s challenge response", digest) self.send_hello(challenge_response, client_salt) return self.queue_client_packet(packet)
def process_server_packet(self, proto, packet): packet_type = packet[0] log("process_server_packet: %s", packet_type) if packet_type == Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type == "disconnect": log("got disconnect from server: %s", packet[1]) if self.exit: self.server_protocol.close() else: self.stop("disconnect from server: %s" % packet[1]) elif packet_type == "hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: from xpra.net.crypto import crypto_backend_init, new_cipher_caps, DEFAULT_PADDING crypto_backend_init() padding_options = self.caps.strlistget( "cipher.padding.options", [DEFAULT_PADDING]) auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key, padding_options) caps.update(auth_caps) #may need to bump packet size: proto.max_packet_size = maxw * maxh * 4 * 4 file_transfer = self.caps.boolget("file-transfer") and c.boolget( "file-transfer") file_size_limit = max(self.caps.intget("file-size-limit"), c.intget("file-size-limit")) file_max_packet_size = int(file_transfer) * ( 1024 + file_size_limit * 1024 * 1024) self.client_protocol.max_packet_size = max( self.client_protocol.max_packet_size, file_max_packet_size) self.server_protocol.max_packet_size = max( self.server_protocol.max_packet_size, file_max_packet_size) packet = ("hello", caps) elif packet_type == "info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(self.get_proxy_info(proto)) elif packet_type == "lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type == "draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return #we do want to reformat cursor packets... #as they will have been uncompressed by the network layer already: elif packet_type == "cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", "png", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet) >= 8: #hard to distinguish png cursors from normal cursors... try: int(packet[1]) self._packet_recompress(packet, 8, "cursor") except: self._packet_recompress(packet, 9, "cursor") elif packet_type == "window-icon": self._packet_recompress(packet, 5, "icon") elif packet_type == "send-file": if packet[6]: packet[6] = Compressed("file-data", packet[6]) elif packet_type == "send-file-chunk": if packet[3]: packet[3] = Compressed("file-chunk-data", packet[3]) elif packet_type == "challenge": password = self.session_options.get("password") if not password: self.stop( "authentication requested by the server, but no password available for this session" ) return from xpra.net.crypto import get_salt, gendigest #client may have already responded to the challenge, #so we have to handle authentication from this end server_salt = bytestostr(packet[1]) l = len(server_salt) digest = bytestostr(packet[3]) salt_digest = "xor" if len(packet) >= 5: salt_digest = bytestostr(packet[4]) if salt_digest in ("xor", "des"): if not LEGACY_SALT_DIGEST: self.stop("server uses legacy salt digest '%s'" % salt_digest) return log.warn( "Warning: server using legacy support for '%s' salt digest", salt_digest) if salt_digest == "xor": #with xor, we have to match the size assert l >= 16, "server salt is too short: only %i bytes, minimum is 16" % l assert l <= 256, "server salt is too long: %i bytes, maximum is 256" % l else: #other digest, 32 random bytes is enough: l = 32 client_salt = get_salt(l) salt = gendigest(salt_digest, client_salt, server_salt) challenge_response = gendigest(digest, password, salt) if not challenge_response: log("invalid digest module '%s': %s", digest) self.stop( "server requested '%s' digest but it is not supported" % digest) return log.info("sending %s challenge response", digest) self.send_hello(challenge_response, client_salt) return self.queue_client_packet(packet)
def _process_challenge(self, packet): authlog("processing challenge: %s", packet[1:]) def warn_server_and_exit(code, message, server_message="authentication failed"): authlog.error("Error: authentication failed:") authlog.error(" %s", message) self.disconnect_and_quit(code, server_message) if not self.has_password(): warn_server_and_exit( EXIT_PASSWORD_REQUIRED, "this server requires authentication, please provide a password", "no password available") return password = self.load_password() if not password: warn_server_and_exit( EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file, "no password available") return server_salt = packet[1] if self.encryption: assert len( packet ) >= 3, "challenge does not contain encryption details to use for the response" server_cipher = typedict(packet[2]) key = self.get_encryption_key() if key is None: warn_server_and_exit(EXIT_ENCRYPTION, "the server does not use any encryption", "client requires encryption") return if not self.set_server_encryption(server_cipher, key): return #all server versions support a client salt, #they also tell us which digest to use: digest = bytestostr(packet[3]) l = len(server_salt) salt_digest = "xor" if len(packet) >= 5: salt_digest = bytestostr(packet[4]) if salt_digest == "xor": #with xor, we have to match the size assert l >= 16, "server salt is too short: only %i bytes, minimum is 16" % l assert l <= 256, "server salt is too long: %i bytes, maximum is 256" % l else: #other digest, 32 random bytes is enough: l = 32 client_salt = get_salt(l) if salt_digest in ("xor", "des"): if not LEGACY_SALT_DIGEST: warn_server_and_exit( EXIT_INCOMPATIBLE_VERSION, "server uses legacy salt digest '%s'" % salt_digest, "unsupported digest %s" % salt_digest) return log.warn( "Warning: server using legacy support for '%s' salt digest", salt_digest) salt = gendigest(salt_digest, client_salt, server_salt) authlog("combined %s salt(%s, %s)=%s", salt_digest, binascii.hexlify(server_salt), binascii.hexlify(client_salt), binascii.hexlify(salt)) if digest.startswith(b"hmac"): import hmac digestmod = get_digest_module(digest) if not digestmod: log("invalid digest module '%s': %s", digest) warn_server_and_exit( EXIT_UNSUPPORTED, "server requested digest '%s' but it is not supported" % digest, "invalid digest") return password = strtobytes(password) salt = memoryview_to_bytes(salt) challenge_response = hmac.HMAC(password, salt, digestmod=digestmod).hexdigest() authlog("hmac.HMAC(%s, %s)=%s", binascii.hexlify(password), binascii.hexlify(salt), challenge_response) elif digest == b"xor": #don't send XORed password unencrypted: encrypted = self._protocol.cipher_out or self._protocol.get_info( ).get("type") == "ssl" local = self.display_desc.get("local", False) authlog("xor challenge, encrypted=%s, local=%s", encrypted, local) if local and ALLOW_LOCALHOST_PASSWORDS: pass elif not encrypted and not ALLOW_UNENCRYPTED_PASSWORDS: warn_server_and_exit( EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest, "invalid digest") return salt = salt[:len(password)] challenge_response = memoryview_to_bytes(xor(password, salt)) else: warn_server_and_exit( EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest, "invalid digest") return if digest: authlog("%s(%s, %s)=%s", digest, binascii.hexlify(password), binascii.hexlify(salt), binascii.hexlify(challenge_response)) self.password_sent = True self.remove_packet_handlers("challenge") self.send_hello(challenge_response, client_salt)
def verify_hello(self, proto, c): remote_version = c.strget("version") verr = version_compat_check(remote_version) if verr is not None: self.disconnect_client(proto, VERSION_ERROR, "incompatible version: %s" % verr) proto.close() return False def auth_failed(msg): authlog.error("Error: authentication failed") authlog.error(" %s", msg) self.timeout_add(1000, self.disconnect_client, proto, msg) #authenticator: username = c.strget("username") if proto.authenticator is None and proto.auth_class: authlog("creating authenticator %s", proto.auth_class) try: auth, aclass, options = proto.auth_class ainstance = aclass(username, **options) proto.authenticator = ainstance authlog("%s=%s", auth, ainstance) except Exception as e: authlog.error("Error instantiating %s:", proto.auth_class) authlog.error(" %s", e) auth_failed("authentication failed") return False digest_modes = c.get("digest", ("hmac", )) #client may have requested encryption: cipher = c.strget("cipher") cipher_iv = c.strget("cipher.iv") key_salt = c.strget("cipher.key_salt") iterations = c.intget("cipher.key_stretch_iterations") padding = c.strget("cipher.padding", DEFAULT_PADDING) padding_options = c.strlistget("cipher.padding.options", [DEFAULT_PADDING]) auth_caps = {} if cipher and cipher_iv: if cipher not in ENCRYPTION_CIPHERS: authlog.warn("unsupported cipher: %s", cipher) auth_failed("unsupported cipher") return False encryption_key = self.get_encryption_key(proto.authenticator, proto.keyfile) if encryption_key is None: auth_failed("encryption key is missing") return False if padding not in ALL_PADDING_OPTIONS: auth_failed("unsupported padding: %s" % padding) return False authlog("set output cipher using encryption key '%s'", repr_ellipsized(encryption_key)) proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations, padding) #use the same cipher as used by the client: auth_caps = new_cipher_caps(proto, cipher, encryption_key, padding_options) authlog("server cipher=%s", auth_caps) else: if proto.encryption: authlog("client does not provide encryption tokens") auth_failed("missing encryption tokens") return False auth_caps = None #verify authentication if required: if (proto.authenticator and proto.authenticator.requires_challenge()) or c.get("challenge") is not None: challenge_response = c.strget("challenge_response") client_salt = c.strget("challenge_client_salt") authlog("processing authentication with %s, response=%s, client_salt=%s, challenge_sent=%s", proto.authenticator, challenge_response, binascii.hexlify(client_salt or ""), proto.challenge_sent) #send challenge if this is not a response: if not challenge_response: if proto.challenge_sent: auth_failed("invalid state, challenge already sent - no response!") return False if proto.authenticator: challenge = proto.authenticator.get_challenge() if challenge is None: auth_failed("invalid state, unexpected challenge response") return False authlog("challenge: %s", challenge) salt, digest = challenge authlog.info("Authentication required by %s authenticator module", proto.authenticator) authlog.info(" sending challenge for '%s' using %s digest", username, digest) if digest not in digest_modes: auth_failed("cannot proceed without %s digest support" % digest) return False else: authlog.warn("Warning: client expects a challenge but this connection is unauthenticated") #fake challenge so the client will send the real hello: salt = get_salt() digest = "hmac" proto.challenge_sent = True proto.send_now(("challenge", salt, auth_caps or "", digest)) return False if not proto.authenticator.authenticate(challenge_response, client_salt): auth_failed("invalid challenge response") return False authlog("authentication challenge passed") else: #did the client expect a challenge? if c.boolget("challenge"): authlog.warn("this server does not require authentication (client supplied a challenge)") return auth_caps