Created
January 15, 2016 21:59
-
-
Save zollman/248864acfb8248d0ea29 to your computer and use it in GitHub Desktop.
yubitouch.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!python | |
''' | |
Python port of yubitouch.sh for controlling touch-to-sign (or | |
touch-to-authenticate) | |
''' | |
import argparse | |
import getpass | |
import os | |
import platform | |
import re | |
import subprocess | |
import sys | |
from distutils.spawn import find_executable | |
def valid_keytype(arg): | |
'''Convert text keytype into hex code''' | |
if arg == 'sig': | |
return 'D6' | |
elif arg == 'dec': | |
return 'D7' | |
elif arg == 'aut': | |
return 'D8' | |
else: | |
raise argparse.ArgumentTypeError('Invalid value ' | |
'{} (must be sig, aut, dec).'.format(arg)) | |
def valid_status(arg): | |
'''Convert text status into hex code''' | |
if arg == 'off': | |
return '00' | |
elif arg == 'on': | |
return '01' | |
elif arg == 'fix': | |
return '02' | |
else: | |
raise argparse.ArgumentTypeError('Invalid value ' | |
'{} (must be on, off, fix).'.format(arg)) | |
GPG_AGENT_CONF = '~/.gnupg/gpg-agent.conf' | |
def find_pinentry(): | |
'''Look for the pinentry program in a number of possible places. | |
It's likely gpg is installed, so gpg-agent.conf probably has it. | |
If not, we'll look for pinentry-mac or pinentry. | |
''' | |
try: | |
with open(os.path.expanduser(GPG_AGENT_CONF)) as gpg_agent_conf: | |
pinentry_line = next((l for l in gpg_agent_conf | |
if l.startswith('pinentry-program ')), None) | |
if pinentry_line: | |
return pinentry_line.strip().split()[1] | |
except IOError: | |
pass # OK, try another path | |
if platform.system() == 'Darwin' and find_executable('pinentry-mac'): | |
return find_executable('pinentry-mac') | |
if find_executable('pinentry'): | |
return find_executable('pinentry') | |
PINENTRY_PROMPT = '''SETPROMPT Admin PIN | |
SETDESC Admin PIN | |
GETPIN | |
BYE | |
''' | |
def getpin(): | |
''' | |
Get a PIN from the user using the most secure available | |
mechanism. | |
''' | |
pinentry_program = find_pinentry() | |
if pinentry_program: | |
sproc = subprocess.Popen(pinentry_program, stdin=subprocess.PIPE, | |
stdout=subprocess.PIPE) | |
(out, _) = sproc.communicate(PINENTRY_PROMPT) | |
pin_match = re.search('^D (.+)', out, flags=re.MULTILINE) | |
if pin_match: | |
return pin_match.group(1) | |
else: | |
print >>sys.stderr, 'Cannot find getpass, trying terminal entry' | |
return getpass.getpass('Admin PIN: ') | |
def pin_as_hex(pin): | |
'''Convert PIN to hex codes for gpg-connect-agent''' | |
hexcodes = ['{:02x}'.format(ord(c)) for c in pin] | |
return ' '.join(hexcodes) | |
def gca_command(gca, command): | |
'''Send a command through gpg-connect-agent, and return the results.''' | |
return subprocess.check_output([gca, '--hex', command, '/bye']) | |
def main(): | |
'''Parse arguments and send commands''' | |
parser = argparse.ArgumentParser( | |
description='Change YubiKey touch parameters') | |
parser.add_argument('keytype', type=valid_keytype, | |
help='{sig|aut|dec}') | |
parser.add_argument('status', type=valid_status, | |
help='{off|on|fix}') | |
parser.add_argument('admin_pin', nargs='?') | |
args = parser.parse_args() | |
gpg_connect_agent = find_executable('gpg-connect-agent') | |
if not gpg_connect_agent: | |
print >>sys.stderr, 'You must have gpg-connect-agent installed.' | |
parser.print_help() | |
sys.exit(1) | |
if args.admin_pin: | |
pin = args.admin_pin | |
else: | |
pin = getpin() | |
if not pin: | |
print >>sys.stderr, 'No PIN entered.' | |
parser.print_help() | |
sys.exit(1) | |
gca_command(gpg_connect_agent, 'scd reset') | |
verify_pin_command = 'scd apdu 00 20 00 83 {:02x} {}'.format( | |
len(pin), pin_as_hex(pin)) | |
verify_output = gca_command(gpg_connect_agent, verify_pin_command) | |
if '90 00' not in verify_output: | |
print >>sys.stderr,'Verification failed. Wrong pin?' | |
sys.exit(1) | |
set_status_command = 'scd apdu 00 da 00 {} 02 {} 20'.format( | |
args.keytype, args.status) | |
set_status_output = gca_command(gpg_connect_agent, set_status_command) | |
if '90 00' not in set_status_output: | |
print >>sys.stderr,'Unable to change mode. Set to fix?' | |
sys.exit(1) | |
print 'Status set!' | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment