Hi,

This patch implements secure passwords authentication (I believe - can
someone please check!)

If password is enabled (via the command line switch), the server sends a
challenge packet (with the salt in it) when it receives the "hello" command.
The client must re-send the hello command with the password hashed with
the salt.

The only part that I am not too sure about is the sending of the
challenge packet: at this point, self._protocol is not set, so I used:
proto._sock.send(data)
Which may not be the right thing to do as it does not deal with errors
or short writes... But it felt wrong to add a whole lot of code just to
send ~50 bytes!

Comments welcome.

Antoine

Index: trunk/dev/xpra/client.py
===================================================================
--- trunk/dev/xpra/client.py	(revision 74)
+++ trunk/dev/xpra/client.py	(working copy)
@@ -9,6 +9,7 @@
 import os
 import os.path
 import sys
+import hashlib
 
 from wimpiggy.util import (one_arg_signal,
                            gtk_main_quit_really,
@@ -268,18 +269,17 @@
 gobject.type_register(ClientWindow)
 
 class XpraClient(gobject.GObject):
-    def __init__(self, sock, compression_level, title_suffix):
+    def __init__(self, sock, compression_level, title_suffix, password):
         gobject.GObject.__init__(self)
         self._window_to_id = {}
         self._id_to_window = {}
         self.title_suffix = title_suffix
+        self.password = password
+        self.compression_level = compression_level
 
         self._protocol = Protocol(sock, self.process_packet)
         ClientSource(self._protocol)
-        capabilities_request = dict(default_capabilities)
-        if compression_level:
-            capabilities_request["deflate"] = compression_level
-        self.send(["hello", capabilities_request])
+        self.send_hello()
 
         self._keymap = gtk.gdk.keymap_get_default()
         self._keymap.connect("keys-changed", self._keys_changed)
@@ -320,6 +320,25 @@
     def send_mouse_position(self, packet):
         self._protocol.source.queue_mouse_position_packet(packet)
 
+    def send_hello(self, hash=None):
+        capabilities_request = dict(default_capabilities)
+        if hash:
+            capabilities_request["challenge_response"] = hash
+        if self.compression_level:
+			capabilities_request["deflate"] = self.compression_level
+        self.send(["hello", capabilities_request])
+
+    def _process_challenge(self, packet):
+        if not self.password:
+			log.error("password is required by the server")
+			gtk.main_quit()
+			return
+        (_, salt) = packet
+        hash = hashlib.sha1()
+        hash.update(salt)
+        hash.update(self.password)
+        self.send_hello(hash.hexdigest())
+
     def _process_hello(self, packet):
         (_, capabilities) = packet
         if "deflate" in capabilities:
@@ -372,6 +391,7 @@
         gtk_main_quit_really()
 
     _packet_handlers = {
+        "challenge": _process_challenge,
         "hello": _process_hello,
         "new-window": _process_new_window,
         "new-override-redirect": _process_new_override_redirect,
Index: trunk/dev/xpra/scripts/server.py
===================================================================
--- trunk/dev/xpra/scripts/server.py	(revision 74)
+++ trunk/dev/xpra/scripts/server.py	(working copy)
@@ -284,7 +284,7 @@
 
     # This import is delayed because the module depends on gtk:
     import xpra.server
-    app = xpra.server.XpraServer(upgrading, sockets)
+    app = xpra.server.XpraServer(upgrading, sockets, opts.password)
     def cleanup_socket(self):
         print "removing socket"
         try:
Index: trunk/dev/xpra/server.py
===================================================================
--- trunk/dev/xpra/server.py	(revision 74)
+++ trunk/dev/xpra/server.py	(working copy)
@@ -15,6 +15,8 @@
 import os
 import os.path
 import subprocess
+import hashlib
+import uuid
 
 from wimpiggy.wm import Wm
 from wimpiggy.util import (LameStruct,
@@ -207,7 +209,7 @@
         "wimpiggy-child-map-event": one_arg_signal,
         }
 
-    def __init__(self, clobber, sockets):
+    def __init__(self, clobber, sockets, password):
         gobject.GObject.__init__(self)
         
         # Do this before creating the Wm object, to avoid clobbering its
@@ -289,6 +291,9 @@
         self._has_focus = 0
         self._upgrading = False
 
+        self.password = password
+        self.salt = None
+
         ### All right, we're ready to accept customers:
         self._protocol = None
         self._potential_protocols = []
@@ -483,7 +488,7 @@
 
     def _calculate_capabilities(self, client_capabilities):
         capabilities = {}
-        for cap in ("deflate", "__prerelease_version"):
+        for cap in ("deflate", "__prerelease_version", "challenge_response"):
             if cap in client_capabilities:
                 capabilities[cap] = client_capabilities[cap]
         return capabilities
@@ -497,6 +502,30 @@
                       + "of exactly the same version (v%s)", xpra.__version__)
             proto.close()
             return
+        if self.password:
+            log.debug("password auth required")
+            client_hash = capabilities.get("challenge_response")
+            if not client_hash or not self.salt:
+                 self.salt = "%s" % uuid.uuid4()
+                 capabilities["challenge"] = self.salt
+                 log.info("Password required, sending challenge: %s" % str(capabilities))
+                 packet = ("challenge", self.salt)
+                 from xpra.bencode import bencode
+                 data = bencode(packet)
+                 proto._sock.send(data)
+                 return
+
+            hash = hashlib.sha1()
+            hash.update(self.salt)
+            hash.update(self.password)
+            if client_hash != hash.hexdigest():
+                log.error("Password supplied does not match! dropping the connection.")
+                proto.close()
+                return
+            else:
+                log.info("Password matches!")
+                del capabilities["challenge_response"]
+                self.salt = None		#prevent replay attacks
         # Okay, things are okay, so let's boot out any existing connection and
         # set this as our new one:
         if self._protocol is not None:
Index: trunk/dev/xpra/scripts/main.py
===================================================================
--- trunk/dev/xpra/scripts/main.py	(revision 74)
+++ trunk/dev/xpra/scripts/main.py	(working copy)
@@ -53,6 +53,9 @@
                       dest="bind_tcp", default=None,
                       metavar="[HOST]:PORT",
                       help="Listen for connections over TCP (insecure)")
+    parser.add_option("--password", action="store",
+                      dest="password", default=None,
+                      help="Password required to connect (useful to secure TCP mode)")
     parser.add_option("--title-suffix", action="store",
                       dest="title_suffix", default=" (via xpra)",
                       help="Text which is appended to the window's title")
@@ -164,7 +167,7 @@
     sock, local = client_sock(parser, opts, pick_display(parser, extra_args))
     if opts.compression_level < 0 or opts.compression_level > 9:
         parser.error("Compression level must be between 0 and 9 inclusive.")
-    app = XpraClient(sock, opts.compression_level, opts.title_suffix)
+    app = XpraClient(sock, opts.compression_level, opts.title_suffix, opts.password)
     sys.stdout.write("Attached\n")
     app.run()
 

Attachment: signature.asc
Description: OpenPGP digital signature

_______________________________________________
Parti-discuss mailing list
[email protected]
http://lists.partiwm.org/cgi-bin/mailman/listinfo/parti-discuss

Reply via email to