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 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("LDAP-Password-Auth", "LDAP-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,7): sys.stderr.write("%s invalid arguments\n" % argv[0]) sys.stderr.write("usage: %s username password [host] [port] [tls] [username_format]\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] if len(argv)>=7: kwargs["username_format"] = argv[6] 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 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 _test_hmac_auth(self, mod_name, password, **kwargs): for test_password in (password, "somethingelse"): a = self._init_auth(mod_name, **kwargs) assert a.requires_challenge() assert a.get_password() salt, mac = a.get_challenge( [x for x in get_digests() if x.startswith("hmac")]) assert salt assert mac.startswith("hmac"), "invalid mac: %s" % mac client_salt = strtobytes(uuid.uuid4().hex + uuid.uuid4().hex) salt_digest = a.choose_salt_digest(get_digests()) auth_salt = strtobytes(gendigest(salt_digest, client_salt, salt)) digestmod = get_digest_module(mac) verify = hmac.HMAC(strtobytes(test_password), auth_salt, digestmod=digestmod).hexdigest() passed = a.authenticate(verify, client_salt) assert passed == ( test_password == password ), "expected authentication to %s with %s vs %s" % ([ "fail", "succeed" ][test_password == password], test_password, password) assert not a.authenticate( verify, client_salt ), "should not be able to athenticate again with the same values"
def get_response_salt(self, client_salt=None): server_salt = self.salt #make sure it does not get re-used: self.salt = None if client_salt is None: return server_salt salt = gendigest(self.salt_digest, client_salt, server_salt) if salt in SysAuthenticator.USED_SALT: raise Exception("danger: an attempt was made to re-use the same computed salt") log("combined salt(%s, %s)=%s", hexstr(server_salt), hexstr(client_salt), hexstr(salt)) SysAuthenticator.USED_SALT.append(salt) return salt
def _test_file_auth(self, mod_name, genauthdata): #no file, no go: a = self._init_auth(mod_name) assert a.requires_challenge() p = a.get_password() assert not p, "got a password from %s: %s" % (a, p) #challenge twice is a fail assert a.get_challenge(get_digests()) assert not a.get_challenge(get_digests()) assert not a.get_challenge(get_digests()) for muck in (0, 1): if WIN32: #NamedTemporaryFile doesn't work for reading on win32... import time filename = os.path.join(os.environ.get("TEMP", "/tmp"), "file-auth-test-%s" % time.time()) f = open(filename, 'wb') else: f = tempfile.NamedTemporaryFile(prefix=mod_name) filename = f.name try: with f: a = self._init_auth(mod_name, {"password_file": filename}) password, filedata = genauthdata(a) #print("saving password file data='%s' to '%s'" % (filedata, filename)) f.write(strtobytes(filedata)) f.flush() assert a.requires_challenge() salt, mac = a.get_challenge(get_digests()) assert salt assert mac in get_digests() assert mac != "xor" password = strtobytes(password) client_salt = strtobytes(uuid.uuid4().hex + uuid.uuid4().hex)[:len(salt)] salt_digest = a.choose_salt_digest(get_digests()) assert salt_digest auth_salt = strtobytes( gendigest(salt_digest, client_salt, salt)) if muck == 0: digestmod = get_digest_module(mac) verify = hmac.HMAC(password, auth_salt, digestmod=digestmod).hexdigest() assert a.authenticate(verify, client_salt) assert not a.authenticate(verify, client_salt) assert a.get_password() == password elif muck == 1: for verify in ("whatever", None, "bad"): assert not a.authenticate(verify, client_salt) finally: if WIN32: os.unlink(filename)
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 _test_file_auth(self, mod_name, genauthdata): #no file, no go: a = self._init_auth(mod_name) assert a.requires_challenge() p = a.get_password() assert not p, "got a password from %s: %s" % (a, p) #challenge twice is a fail assert a.get_challenge(get_digests()) assert not a.get_challenge(get_digests()) assert not a.get_challenge(get_digests()) for muck in (0, 1): with TempFileContext(prefix=mod_name) as context: f = context.file filename = context.filename with f: a = self._init_auth(mod_name, {"password_file": filename}) password, filedata = genauthdata(a) #print("saving password file data='%s' to '%s'" % (filedata, filename)) f.write(strtobytes(filedata)) f.flush() assert a.requires_challenge() salt, mac = a.get_challenge(get_digests()) assert salt assert mac in get_digests() assert mac != "xor" password = strtobytes(password) client_salt = strtobytes(uuid.uuid4().hex + uuid.uuid4().hex)[:len(salt)] salt_digest = a.choose_salt_digest(get_digests()) assert salt_digest auth_salt = strtobytes( gendigest(salt_digest, client_salt, salt)) if muck == 0: digestmod = get_digest_module(mac) verify = hmac.HMAC(password, auth_salt, digestmod=digestmod).hexdigest() assert a.authenticate(verify, client_salt) assert not a.authenticate(verify, client_salt) assert a.get_password() == password elif muck == 1: for verify in ("whatever", None, "bad"): assert not a.authenticate(verify, client_salt)
def authenticate_check(self, challenge_response, client_salt=None): if self.salt is None: log.error( "Error: illegal challenge response received - salt cleared or unset" ) return False salt = self.get_response_salt(client_salt) password = gendigest("xor", challenge_response, salt) log("authenticate_check(%s, %s) xor(%s)=%s", repr(challenge_response), repr(client_salt), repr(salt), repr(password)) #warning: enabling logging here would log the actual system password! #log.info("authenticate(%s, %s) password=%s (%s)", hexstr(challenge_response), hexstr(client_salt), password, hexstr(password)) #verify login: try: ret = self.check(password) log("authenticate_check(..)=%s", ret) except Exception as e: log.error("Error: %s authentication check failed:", self) log.error(" %s", e) return False return ret
def _test_hmac_auth(self, auth_class, password, **kwargs): for x in (password, "somethingelse"): a = self._init_auth(auth_class, **kwargs) assert a.requires_challenge() assert a.get_password() salt, mac = a.get_challenge() assert salt assert mac == "hmac", "invalid mac: %s" % mac client_salt = strtobytes(uuid.uuid4().hex + uuid.uuid4().hex)[:len(salt)] salt_digest = a.choose_salt_digest(get_digests()) assert salt_digest auth_salt = strtobytes(gendigest(salt_digest, client_salt, salt)) verify = hmac.HMAC(strtobytes(x), auth_salt, digestmod=hashlib.md5).hexdigest() passed = a.authenticate(verify, client_salt) assert passed == ( x == password ), "expected authentication to %s with %s vs %s" % ( ["fail", "succeed"][x == password], x, password) assert not a.authenticate(verify, client_salt)
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 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)