def get_challenge(self): if self.salt is not None: log.error("challenge already sent!") return None self.salt = get_hex_uuid()+get_hex_uuid() #this authenticator can use the safer "hmac" digest: return self.salt, "hmac"
def get_challenge(self): if self.salt is not None: log.error("challenge already sent!") return None self.salt = get_hex_uuid()+get_hex_uuid() #we need the raw password, so tell the client to use "xor": return self.salt, "xor"
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.password_file and not os.environ.get('XPRA_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_hex_uuid()+get_hex_uuid() #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 def s(v): try: return v.encode() except: return str(v) password = s(password) salt = s(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 challenge_response = 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 new_cipher_caps(proto, cipher, encryption_key): iv = get_hex_uuid()[:16] key_salt = get_hex_uuid()+get_hex_uuid() iterations = 1000 proto.set_cipher_in(cipher, iv, encryption_key, key_salt, iterations) return { "cipher" : cipher, "cipher.iv" : iv, "cipher.key_salt" : key_salt, "cipher.key_stretch_iterations" : iterations }
def _process_challenge(self, packet): log("processing challenge: %s", packet[1:]) if not self.password_file and not os.environ.get('XPRA_PASSWORD'): self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "server requires authentication, please provide a password") return password = self.load_password() if not password: self.warn_and_quit(EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file) return salt = packet[1] if self.encryption: assert len(packet)>=3, "challenge does not contain encryption details to use for the response" server_cipher = packet[2] key = self.get_encryption_key() if key is None: self.warn_and_quit(EXIT_ENCRYPTION, "encryption key is missing") return if not self.set_server_encryption(server_cipher, key): return digest = "hmac" client_can_salt = len(packet)>=4 client_salt = None if client_can_salt: #server supports client salt, and tells us which digest to use: digest = packet[3] client_salt = get_hex_uuid()+get_hex_uuid() #TODO: use some key stretching algorigthm? (meh) salt = xor(salt, client_salt) if digest=="hmac": import hmac challenge_response = hmac.HMAC(password, salt).hexdigest() elif digest=="xor": #don't send XORed password unencrypted: if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS: self.warn_and_quit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest) return challenge_response = xor(password, salt) else: self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest) return if digest: log("%s(%s, %s)=%s", digest, password, salt, challenge_response) self.password_sent = True for d in (self._packet_handlers, self._ui_packet_handlers): try: del d["challenge"] except: pass self.send_hello(challenge_response, client_salt)
def make_hello_base(self): capabilities = get_network_caps() capabilities.update({ "version" : local_version, "encoding.generic" : True, "namespace" : True, "file-transfer" : self.file_transfer, "file-size-limit" : self.file_size_limit, "printing" : self.printing, "hostname" : socket.gethostname(), "uuid" : self.uuid, "username" : self.username, "name" : get_name(), "client_type" : self.client_type(), "python.version" : sys.version_info[:3], "compression_level" : self.compression_level, }) if self.display: capabilities["display"] = self.display def up(prefix, d): updict(capabilities, prefix, d) up("platform", get_platform_info()) up("build", get_version_info()) mid = get_machine_id() if mid: capabilities["machine_id"] = mid if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS iv = get_hex_uuid()[:16] key_salt = get_hex_uuid()+get_hex_uuid() iterations = 1000 capabilities.update({ "cipher" : self.encryption, "cipher.iv" : iv, "cipher.key_salt" : key_salt, "cipher.key_stretch_iterations": iterations, }) 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) log("encryption capabilities: %s", [(k,v) for k,v in capabilities.items() if k.startswith("cipher")]) return capabilities
def test_file(self): from xpra.os_util import get_hex_uuid password = get_hex_uuid() f = self._temp_file(password) self._test_auth("file", "", EXIT_PASSWORD_REQUIRED) self._test_auth("file:filename=%s" % f.name, "", EXIT_PASSWORD_REQUIRED) self._test_auth("file:filename=%s" % f.name, "", EXIT_OK, password) self._test_auth("file:filename=%s" % f.name, "", EXIT_FAILURE, password+"A") f.close()
def make_hello(self, challenge_response=None): capabilities = {} add_version_info(capabilities) capabilities["python.version"] = sys.version_info[:3] if challenge_response: assert self.password capabilities["challenge_response"] = challenge_response if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS capabilities["cipher"] = self.encryption iv = get_hex_uuid()[:16] capabilities["cipher.iv"] = iv key_salt = get_hex_uuid() capabilities["cipher.key_salt"] = key_salt iterations = 1000 capabilities["cipher.key_stretch_iterations"] = iterations self._protocol.set_cipher_in(self.encryption, iv, self.get_password(), key_salt, iterations) log("encryption capabilities: %s", [(k,v) for k,v in capabilities.items() if k.startswith("cipher")]) capabilities["platform"] = sys.platform capabilities["platform.release"] = python_platform.release() capabilities["platform.machine"] = python_platform.machine() capabilities["platform.processor"] = python_platform.processor() capabilities["client_type"] = self.client_type() capabilities["namespace"] = True capabilities["raw_packets"] = True capabilities["chunked_compression"] = True capabilities["bencode"] = True capabilities["rencode"] = has_rencode if has_rencode: capabilities["rencode.version"] = rencode_version capabilities["hostname"] = socket.gethostname() capabilities["uuid"] = self.uuid try: from xpra.platform.info import get_username, get_name capabilities["username"] = get_username() capabilities["name"] = get_name() except: log.error("failed to get username/name", exc_info=True) capabilities["randr_notify"] = False #only client.py cares about this capabilities["windows"] = False #only client.py cares about this if self._reverse_aliases: capabilities["aliases"] = self._reverse_aliases return capabilities
def test_multifile(self): from xpra.platform.info import get_username username = get_username() from xpra.os_util import get_hex_uuid password = get_hex_uuid() displays = "" data = "%s|%s|%i|%i|%s||" % (username, password, os.getuid(), os.getgid(), displays) f = self._temp_file(data) self._test_auth("multifile", "", EXIT_PASSWORD_REQUIRED) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_PASSWORD_REQUIRED) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_OK, password) self._test_auth("multifile:filename=%s" % f.name, "", EXIT_FAILURE, password+"A") f.close()
def copy_and_verify(self, display1, display2, synced=True, wait=1, selection="clipboard"): log("copy_and_verify%s", (display1, display2, synced, wait, selection)) value = get_hex_uuid() self.set_clipboard_value(display1, value) #wait for synchronization to occur: time.sleep(wait) new_value = self.get_clipboard_value(display2) if synced: assert new_value==value, "clipboard contents do not match, expected '%s' but got '%s'" % (value, new_value) else: assert new_value!=value, "clipboard contents match but synchronization was not expected: value='%s'" % value if SANITY_CHECKS and display2!=display1: #verify that the value has not changed on the original display: new_value = self.get_clipboard_value(display1) return value
def get_challenge(self): return get_hex_uuid(), "hmac"
"HOME" : os.environ.get("HOME", os.getcwd()), "DISPLAY" : display_name} for var,value in subs.items(): xvfb_str = xvfb_str.replace("$%s" % var, value) xvfb_str = xvfb_str.replace("${%s}" % var, value) xvfb_cmd = xvfb_str.split()+[display_name] xvfb_executable = xvfb_cmd[0] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def setsid(): #run in a new session if os.name=="posix": os.setsid() xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=setsid) from xpra.os_util import get_hex_uuid xauth_cmd = ["xauth", "add", display_name, "MIT-MAGIC-COOKIE-1", get_hex_uuid()] try: code = subprocess.call(xauth_cmd) if code != 0: raise OSError("non-zero exit code: %s" % code) except OSError, e: #trying to continue anyway! sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e)) return xvfb def check_xvfb_process(xvfb=None): if xvfb is None: #we don't have a process to check return True if xvfb.poll() is None: #process is running
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): log.warn("Warning: authentication failed: %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: try: proto.authenticator = proto.auth_class(username) except Exception as e: log.warn("error instantiating %s: %s", proto.auth_class, 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") auth_caps = {} if cipher and cipher_iv: if cipher not in ENCRYPTION_CIPHERS: log.warn("unsupported cipher: %s", cipher) auth_failed("unsupported cipher") return False encryption_key = self.get_encryption_key(proto.authenticator) if encryption_key is None: auth_failed("encryption key is missing") return False proto.set_cipher_out(cipher, cipher_iv, encryption_key, key_salt, iterations) # use the same cipher as used by the client: auth_caps = new_cipher_caps(proto, cipher, encryption_key) log("server cipher=%s", auth_caps) else: 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") log( "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 salt, digest = challenge log.info( "Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest, ) if digest not in self.digest_modes: auth_failed("cannot proceed without %s digest support" % digest) return False else: log.warn("Warning: client expects a challenge but this connection is unauthenticated") # fake challenge so the client will send the real hello: from xpra.os_util import get_hex_uuid salt = get_hex_uuid() + get_hex_uuid() 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 log("authentication challenge passed") else: # did the client expect a challenge? if c.boolget("challenge"): log.warn("this server does not require authentication (client supplied a challenge)") return auth_caps
return False if proto.authenticator: challenge = proto.authenticator.get_challenge() if challenge is None: auth_failed("invalid state: unexpected challenge response") return False salt, digest = challenge log.info("Authentication required, %s sending challenge for '%s' using digest %s", proto.authenticator, username, digest) if digest not in self.digest_modes: auth_failed("cannot proceed without %s digest support" % digest) return False else: log.warn("Warning: client expects a challenge but this connection is unauthenticated") #fake challenge so the client will send the real hello: from xpra.os_util import get_hex_uuid salt = get_hex_uuid()+get_hex_uuid() 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 log("authentication challenge passed") else: #did the client expect a challenge? if c.boolget("challenge"): log.warn("this server does not require authentication (client supplied a challenge)") return auth_caps
def start_Xvfb(xvfb_str, display_name): # We need to set up a new server environment xauthority = os.environ.get("XAUTHORITY", os.path.expanduser("~/.Xauthority")) if not os.path.exists(xauthority): try: open(xauthority, 'wa').close() except Exception as e: #trying to continue anyway! sys.stderr.write("Error trying to create XAUTHORITY file %s: %s\n" % (xauthority, e)) if not xvfb_str: raise InitException("the 'xvfb' command is not defined") #identify logfile argument if it exists and if we may have to rename it: xvfb_cmd = xvfb_str.split() if '-logfile' in xvfb_cmd and display_name[0]=='S': xvfb_cmd = xvfb_str.split() logfile_argindex = xvfb_cmd.index('-logfile') + 1 assert logfile_argindex<len(xvfb_cmd), "invalid xvfb command string: -logfile should not be last" xorg_log_file = xvfb_cmd[logfile_argindex] else: xorg_log_file = None #apply string substitutions: subs = {"XAUTHORITY" : xauthority, "USER" : os.environ.get("USER", "unknown-user"), "HOME" : os.environ.get("HOME", os.getcwd()), "DISPLAY" : display_name} xvfb_str = shellsub(xvfb_str, subs) def setsid(): #run in a new session if os.name=="posix": os.setsid() xvfb_cmd = xvfb_str.split() if not xvfb_cmd: raise Exception("cannot start Xvfb, command definition is missing!") xvfb_executable = xvfb_cmd[0] if display_name[0]=='S': # 'S' means that we allocate the display automatically r_pipe, w_pipe = os.pipe() xvfb_cmd += ["-displayfd", str(w_pipe)] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def preexec(): setsid() #duplicate python's _close_fds() function #(taking care to exclude the pipe) try: MAXFD = os.sysconf("SC_OPEN_MAX") except: MAXFD = 256 for i in range(3, MAXFD): if i not in (r_pipe, w_pipe): try: os.close(i) except: pass xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=False, stdin=subprocess.PIPE, preexec_fn=preexec) # Read the display number from the pipe we gave to Xvfb # waiting up to 10 seconds for it to show up limit = time.time()+10 buf = "" while time.time()<limit and len(buf)<8: r, _, _ = select.select([r_pipe], [], [], max(0, limit-time.time())) if r_pipe in r: buf += os.read(r_pipe, 8) if buf[-1] == '\n': break os.close(r_pipe) os.close(w_pipe) if len(buf) == 0: raise OSError("%s did not provide a display number using -displayfd" % xvfb_executable) if buf[-1] != '\n': raise OSError("%s output not terminated by newline: %s" % (xvfb_executable, buf)) try: n = int(buf[:-1]) except: raise OSError("%s display number is not a valid number: %s" % (xvfb_executable, buf[:-1])) if n<0 or n>=2**16: raise OSError("%s provided an invalid display number: %s" % (xvfb_executable, n)) new_display_name = ":%s" % n sys.stdout.write("Using display number provided by %s: %s\n" % (xvfb_executable, new_display_name)) if xorg_log_file != None: #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.S14700.log f0 = shellsub(xorg_log_file, subs) subs["DISPLAY"] = new_display_name #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.:1.log f1 = shellsub(xorg_log_file, subs) if f0 != f1: try: os.rename(f0, f1) except Exception as e: sys.stderr.write("failed to rename Xorg log file from '%s' to '%s'\n" % (f0, f1)) sys.stderr.write(" %s\n" % e) display_name = new_display_name else: # use display specified xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) xvfb_cmd.append(display_name) xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=setsid) from xpra.os_util import get_hex_uuid xauth_cmd = ["xauth", "add", display_name, "MIT-MAGIC-COOKIE-1", get_hex_uuid()] try: code = subprocess.call(xauth_cmd) if code != 0: raise OSError("non-zero exit code: %s" % code) except OSError as e: #trying to continue anyway! sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e)) return xvfb, display_name