-
Notifications
You must be signed in to change notification settings - Fork 16
U2F login for SSH #1
Changes from all commits
f21515a
4d4fc71
cb84483
f088d20
eae6c04
df572d3
3cfa8da
eb1e743
bce754b
7618c6c
e25b7f4
333b89e
0e9961a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/usr/bin/env python | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, this is the wrapper for the SSH client to handle the U2F stuff.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I put myself in a situation where I was motivated to solve the problem because I'd already bought U2F tokens for my team :)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you buy the blue u2f yubikeys or did you buy daplug?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually went cheap and got HyperFIDO which are U2F only |
||
| # | ||
| # -*- coding: utf-8 -*- | ||
| # | ||
| # 2016-03-03 Brandon Smith <freedom@reardencode.com> | ||
| # Initial Creation | ||
| # | ||
| # (c) Brandon Smith | ||
| # Info: http://www.privacyidea.org | ||
| # | ||
| # This code is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE | ||
| # License as published by the Free Software Foundation; either | ||
| # version 3 of the License, or any later version. | ||
| # | ||
| # This code is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU AFFERO GENERAL PUBLIC LICENSE for more details. | ||
| # | ||
| # You should have received a copy of the GNU Affero General Public | ||
| # License along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| # | ||
| from __future__ import print_function | ||
|
|
||
| import getpass,os,re,signal,subprocess,sys | ||
| import pexpect | ||
|
|
||
| __doc__ = """This is an ssh (and ssh-like) wrapper that uses pexpect to | ||
| interact with privacyIDEA's pam_python module for u2f challenge/response. | ||
|
|
||
| Usage: | ||
| Make executable | ||
| Symlink ssh-u2f, scp-u2f, sftp-u2f, mosh-u2f, etc. into your PATH | ||
| Call just like ssh, eg. "ssh-u2f name@example.com" | ||
| """ | ||
|
|
||
| ssh = None | ||
|
|
||
| def handler(signum, frame): | ||
| global ssh | ||
| if ssh: | ||
| ssh.kill(signum) | ||
| sys.exit(signum) | ||
|
|
||
| signal.signal(signal.SIGQUIT, handler) | ||
| signal.signal(signal.SIGTERM, handler) | ||
| signal.signal(signal.SIGINT, handler) | ||
|
|
||
| def winch_handler(signum, frame): | ||
| global ssh | ||
| if ssh: | ||
| rows, cols = os.popen('stty size', 'r').read().split() | ||
| ssh.setwinsize(int(rows), int(cols)) | ||
|
|
||
| signal.signal(signal.SIGWINCH, winch_handler) | ||
|
|
||
| try: | ||
| command = os.path.splitext(os.path.basename(__file__))[0].split("-")[0] | ||
| except: | ||
| command = None | ||
|
|
||
| ssh = pexpect.spawn(command or "ssh", sys.argv[1:]) | ||
| winch_handler(None, None) | ||
|
|
||
| def passthrough(): | ||
| print() | ||
| sys.stdout.write(ssh.match.group()) | ||
| try: | ||
| ssh.interact() | ||
| except UnboundLocalError: | ||
| # Work around bug in pexpect 3.1 | ||
| pass | ||
| sys.exit(0) | ||
|
|
||
| while True: | ||
| index = ssh.expect(["Authenticated with partial success.", | ||
| "([Pp]assword[^:\r\n]*|Enter additional factors): ?", | ||
| "----- BEGIN U2F CHALLENGE -----\r\n", | ||
| "[^ \r\n]+", | ||
| pexpect.EOF]) | ||
|
|
||
| if index == 0: | ||
| print(ssh.match.group()) | ||
|
|
||
| if index == 1: | ||
| try: | ||
| pin = getpass.getpass(ssh.match.group()) | ||
| except EOFError: | ||
| pin = "" | ||
| ssh.sendline(pin.strip()) | ||
|
|
||
| elif index == 2: | ||
| u2f_origin = ssh.readline().strip() | ||
| u2f_challenge = ssh.readline().strip() | ||
| ssh.expect("(.*)----- END U2F CHALLENGE -----") | ||
| message = ssh.match.group(1).strip() | ||
| print(message or "Interact with your U2F token.") | ||
| p = subprocess.Popen(["u2f-host", "-aauthenticate", "-o", u2f_origin], | ||
| stdin=subprocess.PIPE, stdout=subprocess.PIPE) | ||
| out, err = p.communicate(u2f_challenge) | ||
| p.wait() | ||
| ssh.sendline(out.strip()) | ||
|
|
||
| elif index == 3: | ||
| passthrough() | ||
|
|
||
| elif index == 4: | ||
| sys.exit(0) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to run it but I break at this point. The ssh-u2f.py client seems to return a wrong response object from the conversation.
response.resp == "u2f".
Mar 3 18:27:43 puckel sshd: Mar 3 18:27:43 puckel sshd: u2f Mar 3 18:27:43 puckel sshd: Traceback (most recent call last): #012 File "/home/cornelius/src/pam_python_u2f/privacyidea_pam.py", line 236, in pam_sm_authenticate #012 rval = Auth.authenticate(pamh.authtok) #012 File "/home/cornelius/src/pam_python_u2f/privacyidea_pam.py", line 157, in authenticate #012 challenge) #012 File "/home/cornelius/src/pam_python_u2f/privacyidea_pam.py", line 181, in challenge_response #012 chal_response = json.loads(response.resp) #012 File "/usr/lib/python2.7/json/__init__.py", line 338, in loads #012 return _default_decoder.decode(s) #012 File "/usr/lib/python2.7/json/decoder.py", line 366, in decode #012 obj, end = self.raw_decode(s, idx=_w(s, 0).end()) #012 File "/usr/lib/python2.7/json/decoder.py", line 384, in raw_decode #012 raise ValueError("No JSON object could be decoded") #012ValueError: No JSON object could be decodedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, what version of libu2f-host are you running?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1.1.0-1
ppa1trusty1Am Donnerstag, den 03.03.2016, 10:29 -0800 schrieb Brandon Smith:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1.1.0-1
ppa1trusty1There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weird -- my team have it working on Mac with libu2f-host from homebrew, on linux mint with libu2f-host from git and on ubuntu with u2f-host from the PPA.
If you send a challenge manually to your u2f-host command, does it output some text prior to the response?