예제 #1
0
 def authenticate_hmac(self, challenge_response, client_salt):
     if not self.salt:
         log.error(
             "Error: illegal challenge response received - salt cleared or unset"
         )
         return None
     salt = self.get_response_salt(client_salt)
     password = self.get_password()
     if not password:
         log.error("Error: %s authentication failed", self)
         log.error(" no password defined for '%s'", self.username)
         return False
     digestmod = get_digest_module(self.digest)
     if not digestmod:
         log.error("Error: %s authentication failed", self)
         log.error(" digest module '%s' is invalid", self.digest)
         return False
     verify = hmac.HMAC(strtobytes(password),
                        strtobytes(salt),
                        digestmod=digestmod).hexdigest()
     log("%s auth: authenticate(%s) password=%s, hex(salt)=%s, hash=%s",
         self, challenge_response, password, hexstr(salt), verify)
     if not hmac.compare_digest(verify, challenge_response):
         log("expected '%s' but got '%s'", verify, challenge_response)
         log.error("Error: hmac password challenge for '%s' does not match",
                   self.username)
         return False
     return True
예제 #2
0
 def authenticate_hmac(self, challenge_response, client_salt):
     log("authenticate_hmac(%s, %s)", challenge_response, client_salt)
     self.sessions = None
     if not self.salt:
         log.error(
             "Error: illegal challenge response received - salt cleared or unset"
         )
         return None
     #ensure this salt does not get re-used:
     salt = self.get_response_salt(client_salt)
     entry = self.get_auth_info()
     if entry is None:
         log.error("Error: authentication failed")
         log.error(" no password for '%s' in '%s'", self.username,
                   self.password_filename)
         return None
     log("authenticate: auth-info(%s)=%s", self.username, entry)
     fpassword, uid, gid, displays, env_options, session_options = entry
     digestmod = get_digest_module(self.digest)
     verify = hmac.HMAC(strtobytes(fpassword),
                        strtobytes(salt),
                        digestmod=digestmod).hexdigest()
     log(
         "multifile authenticate_hmac(%s) password='******', hex(salt)=%s, hash=%s",
         challenge_response, fpassword, binascii.hexlify(strtobytes(salt)),
         verify)
     if not hmac.compare_digest(verify, challenge_response):
         log("expected '%s' but got '%s'", verify, challenge_response)
         log.error("Error: hmac password challenge for '%s' does not match",
                   self.username)
         return False
     self.sessions = uid, gid, displays, env_options, session_options
     return True
예제 #3
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"
예제 #4
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):
         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)
예제 #5
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)
예제 #6
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
        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)
예제 #7
0
 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, get_digest_module
         #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)
         digestmod = get_digest_module(digest)
         if not digestmod:
             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
         password = strtobytes(password)
         salt = memoryview_to_bytes(salt)
         challenge_response = hmac.HMAC(password, salt, digestmod=digestmod).hexdigest()
         log.info("sending %s challenge response", digest)
         self.send_hello(challenge_response, client_salt)
         return
     self.queue_client_packet(packet)