Skip to content

Instantly share code, notes, and snippets.

@zollman
Created January 15, 2016 21:59
Show Gist options
  • Save zollman/248864acfb8248d0ea29 to your computer and use it in GitHub Desktop.
Save zollman/248864acfb8248d0ea29 to your computer and use it in GitHub Desktop.
yubitouch.py
#!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