Skip to content

Commit

Permalink
#1796: choose the most suitable handler based on digest type, update …
Browse files Browse the repository at this point in the history
…man page, simplify u2f code

git-svn-id: https://xpra.org/svn/Xpra/trunk@22715 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed May 15, 2019
1 parent 26d3d95 commit 5764e11
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 28 deletions.
25 changes: 14 additions & 11 deletions src/man/xpra.1
Original file line number Diff line number Diff line change
Expand Up @@ -776,27 +776,30 @@ Authentication module to use for the \fBbind-rfb\fP sockets.
Just like the \fBauth\fP switch, except this one only applies
to VSOCK sockets (sockets defined using the \fBbind-vsock\fP switch).
.TP
\fB--challenge-handlers\fP=\fIMODULES\fP
\fB--challenge-handlers\fP=\fIMODULE:options\fP
Configures which challenge handlers are used by the client and in
which order.
which order. This option may be repeated to specify multiple handlers,
which can be useful if the server sends more than one authentication
challenge.
The default value is: \fIall\fP which corresponds to:
\fIuri,file,env,kerberos,gss,u2f,prompt\fP.
Note: some of these modules will fall through to others if they are
unable to supply the challenge requested (\fIuri\fP, \fIfile\fP,
\fIkerberos\fP and \fIgss\fP), others do not (\fIenv\fP and \fIprompt\fP).
.RS
.IP \fBuri\fP
Use the password specified on the connection string, if any.
.IP \fBfile\fP
Loads the password from the file specified with the
\fPpassword-file switch.
The filename used to store the password can be specified using the
\fIfilename\fP option.
If this option is not specified, it will fallback to using the
password filename specified with the \fIpassword-file\fP switch
or if that is also unset, \fIpassword.txt\fP will be used.
.IP \fBenv\fP
Use the password specified using the \fIXPRA_PASSWORD\fP
environment variable.
Use the password specified using the environment variable
specified using the \fIname\fP option, which defaults to
\fIXPRA_PASSWORD\fP if unspecified.
.IP \fBkerberos\fP
Request a kerberos token for the service specified.
Requests a kerberos token for the service specified.
.IP \fBgss\fP
Request a gss token for the service specified.
Requests a gss token for the service specified.
.IP \fBu2f\fP
Requests a token from a U2F device.
.IP \fBprompt\fP
Expand Down
34 changes: 19 additions & 15 deletions src/xpra/client/auth/u2f_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,32 @@ def handle(self, packet):
log("%s is not a u2f challenge", digest)
return False
try:
import pyu2f #@UnresolvedImport @UnusedImport
from pyu2f import model #@UnresolvedImport
from pyu2f.u2f import GetLocalU2FInterface #@UnresolvedImport
except ImportError as e:
log.warn("Warning: cannot use u2f authentication handler")
log.warn(" %s", e)
return False
if not is_debug_enabled("auth"):
logging.getLogger("pyu2f.hardware").setLevel(logging.INFO)
logging.getLogger("pyu2f.hidtransport").setLevel(logging.INFO)
from pyu2f import model #@UnresolvedImport
from pyu2f.u2f import GetLocalU2FInterface #@UnresolvedImport
dev = GetLocalU2FInterface()
APP_ID = os.environ.get("XPRA_U2F_APP_ID", "Xpra")
key_handle = self.get_key_handle()
if not key_handle:
return False
key = model.RegisteredKey(key_handle)
#use server salt as challenge directly
challenge = packet[1]
log.info("activate your U2F device for authentication")
response = dev.Authenticate(APP_ID, challenge, [key])
sig = response.signature_data
client_data = response.client_data
log("process_challenge_u2f client data=%s, signature=%s", client_data, binascii.hexlify(sig))
self.client.do_send_challenge_reply(bytes(sig), client_data.origin)
return True

def get_key_handle(self):
key_handle_str = os.environ.get("XPRA_U2F_KEY_HANDLE")
log("process_challenge_u2f XPRA_U2F_KEY_HANDLE=%s", key_handle_str)
if not key_handle_str:
Expand All @@ -61,16 +75,6 @@ def handle(self, packet):
break
if not key_handle_str:
log.warn("Warning: no U2F key handle found")
return False
return None
log("process_challenge_u2f key_handle=%s", key_handle_str)
key_handle = binascii.unhexlify(key_handle_str)
key = model.RegisteredKey(key_handle)
#use server salt as challenge directly
challenge = packet[1]
log.info("activate your U2F device for authentication")
response = dev.Authenticate(APP_ID, challenge, [key])
sig = response.signature_data
client_data = response.client_data
log("process_challenge_u2f client data=%s, signature=%s", client_data, binascii.hexlify(sig))
self.client.do_send_challenge_reply(bytes(sig), client_data.origin)
return True
return binascii.unhexlify(key_handle_str)
17 changes: 15 additions & 2 deletions src/xpra/client/client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,14 +596,15 @@ def _process_challenge(self, packet):
if not self.validate_challenge_packet(packet):
return
authlog("challenge handlers: %s", self.challenge_handlers)
digest = bytestostr(packet[3])
while self.challenge_handlers:
handler = self.challenge_handlers.pop(0)
handler = self.pop_challenge_handler(digest)
try:
authlog("calling challenge handler %s", handler)
r = handler.handle(packet)
authlog("%s(%s)=%s", handler.handle, packet, r)
if r:
#the challenge handler claims to have done it
#the challenge handler claims to have handled authentication
return
except Exception as e:
authlog("%s(%s)", handler.handle, packet, exc_info=True)
Expand All @@ -613,6 +614,18 @@ def _process_challenge(self, packet):
authlog.warn("Warning: failed to connect, authentication required")
self.quit(EXIT_PASSWORD_REQUIRED)

def pop_challenge_handler(self, digest):
#find the challenge handler most suitable for this digest type,
#otherwise take the first one
digest_type = digest.split(":")[0] #ie: "kerberos:value" -> "kerberos"
index = 0
for i, handler in enumerate(self.challenge_handlers):
if handler.get_digest()==digest_type:
index = i
break
return self.challenge_handlers.pop(index)


#utility method used by some authentication handlers,
#and overriden in UI client to provide a GUI dialog
def do_process_challenge_prompt(self, packet, prompt="password"):
Expand Down

0 comments on commit 5764e11

Please sign in to comment.