Merge pull request #98 from tycho/add-hmac-sha256-gui.

This commit is contained in:
Dain Nilsson 2016-09-23 16:51:46 +02:00
commit db19508e77
4 changed files with 63 additions and 10 deletions

View File

@ -30,7 +30,7 @@ from __future__ import print_function
from .. import __version__
from ..core.ccid import open_scard
from ..core.standard import TYPE_HOTP, TYPE_TOTP
from ..core.standard import ALG_SHA1, ALG_SHA256, TYPE_HOTP, TYPE_TOTP
from ..core.utils import parse_uri
from ..core.exc import NoSpaceError
from .keystore import get_keystore
@ -136,23 +136,44 @@ def show(ctx, query, slot1, slot2, timestamp):
@click.option('-S', '--destination', type=click.IntRange(0, 2), default=0)
@click.option('-N', '--name', required=False, help='Credential name.')
@click.option('-A', '--oath-type', type=click.Choice(['totp', 'hotp']),
default='totp', help='OATH algorithm.')
default='totp', help='Specify whether this is a time or counter-based OATH credential.')
@click.option('-D', '--digits', type=click.Choice(['6', '8']), default='6',
callback=lambda c, p, v: int(v), help='Number of digits.')
@click.option('-H', '--hmac-algorithm', type=click.Choice(['SHA1', 'SHA256']), default='SHA1',
help='HMAC algorithm for OTP generation.')
@click.option('-I', '--imf', type=int, default=0, help='Initial moving factor.')
@click.option('-T', '--touch', is_flag=True, help='Require touch.')
@click.pass_context
def put(ctx, key, destination, name, oath_type, digits, imf, touch):
def put(ctx, key, destination, name, oath_type, hmac_algorithm, digits, imf, touch):
"""
Stores a new OATH credential in the YubiKey.
"""
if key.startswith('otpauth://'):
parsed = parse_uri(key)
key = parsed['secret']
name = name or parsed.get('name')
name = parsed.get('name')
oath_type = parsed.get('type')
digits = digits or int(parsed.get('digits', '6'))
imf = imf or int(parsed.get('counter', '0'))
hmac_algorithm = parsed.get('algorithm', 'SHA1').upper()
digits = int(parsed.get('digits', '6'))
imf = int(parsed.get('counter', '0'))
if oath_type not in ['totp', 'hotp']:
ctx.fail('Invalid OATH credential type')
if hmac_algorithm == 'SHA1':
algo = ALG_SHA1
elif hmac_algorithm == 'SHA256':
algo = ALG_SHA256
else:
ctx.fail('Invalid HMAC algorithm')
if digits == 5 and name.startswith('Steam:'):
# Steam is a special case where we allow the otpauth URI to contain a 'digits'
# value of '5'.
digits = 6
if digits not in [6, 8]:
ctx.fail('Invalid number of digits for OTP')
digits = digits or 6
unpadded = key.upper()
@ -165,7 +186,7 @@ def put(ctx, key, destination, name, oath_type, digits, imf, touch):
oath_type = TYPE_TOTP if oath_type == 'totp' else TYPE_HOTP
try:
controller.add_cred(dev, name, key, oath_type, digits=digits,
imf=imf, require_touch=touch)
imf=imf, algo=algo, require_touch=touch)
except NoSpaceError:
ctx.fail('There is not enough space to add another credential on your device.'
'To create free space to add a new credential, delete those you no longer need.')

View File

@ -221,6 +221,7 @@ class YubiOathApplication(qt.Application):
self._controller.add_cred(
dialog.name, dialog.key, oath_type=dialog.oath_type,
digits=dialog.n_digits,
algo=dialog.algorithm,
require_touch=dialog.require_touch)
except NoSpaceError:
QtGui.QMessageBox.critical(self.window, m.no_space, m.no_space_desc)

View File

@ -73,6 +73,7 @@ cred_key = "Secret key (base32)"
cred_type = "Credential type"
cred_totp = "Time based (TOTP)"
cred_hotp = "Counter based (HOTP)"
algorithm = "Algorithm"
invalid_name = "Invalid name"
invalid_name_desc = "Name must be at least 3 characters"
invalid_key = "Invalid key"
@ -111,6 +112,13 @@ qr_not_found_desc = "No usable QR code detected. Make sure the QR code is " \
qr_not_supported = "Credential not supported"
qr_not_supported_desc = "This credential type is not supported for slot " \
"based usage."
qr_invalid_type = "Invalid OTP type"
qr_invalid_type_desc = "Only TOTP and HOTP types are supported."
qr_invalid_digits = "Invalid number of digits"
qr_invalid_digits_desc = "An OTP may only contain 6 or 8 digits."
qr_invalid_algo = "Unsupported algorithm"
qr_invalid_algo_desc = "SHA1 and SHA256 are the only supported OTP algorithms " \
"at this time."
tt_slot_enabled_1 = "Check to calculate TOTP codes using the YubiKey " \
"standard slot %d credential."
tt_num_digits = "The number of digits to show for the credential."

View File

@ -25,7 +25,7 @@
# for the parts of OpenSSL used as well as that of the covered work.
from yubioath.yubicommon import qt
from ...core.standard import TYPE_TOTP, TYPE_HOTP
from ...core.standard import ALG_SHA1, ALG_SHA256, TYPE_TOTP, TYPE_HOTP
from ...core.utils import parse_uri
from .. import messages as m
from ..qrparse import parse_qr_codes
@ -99,6 +99,10 @@ class AddCredDialog(qt.Dialog):
self._n_digits.addItems(['6', '8'])
layout.addRow(m.n_digits, self._n_digits)
self._algorithm = QtGui.QComboBox()
self._algorithm.addItems(['SHA-1', 'SHA-256'])
layout.addRow(m.algorithm, self._algorithm)
self._require_touch = QtGui.QCheckBox(m.require_touch)
# Touch-required support not available before 4.2.6
if self._version >= (4, 2, 6):
@ -128,11 +132,26 @@ class AddCredDialog(qt.Dialog):
def _handle_qr(self, parsed):
if parsed:
otp_type = parsed['type'].lower()
n_digits = parsed.get('digits', '6')
algo = parsed.get('algorithm', 'SHA1').upper()
if otp_type not in ['totp', 'hotp']:
QtGui.QMessageBox.warning(self, m.qr_invalid_type, m.qr_invalid_type_desc)
return
if n_digits not in ['6', '8']:
QtGui.QMessageBox.warning(self, m.qr_invalid_digits, m.qr_invalid_digits_desc)
return
if algo not in ['SHA1', 'SHA256']:
# RFC6238 says SHA512 is also supported, but it's not implemented here yet.
QtGui.QMessageBox.warning(self, m.qr_invalid_algo, m.qr_invalid_algo_desc)
return
self._cred_name.setText(parsed['name'])
self._cred_key.setText(parsed['secret'])
n_digits = parsed.get('digits', '6')
self._n_digits.setCurrentIndex(0 if n_digits == '6' else 1)
if parsed['type'] == 'totp':
self._algorithm.setCurrentIndex(0 if algo == 'SHA1' else 1)
if otp_type == 'totp':
self._cred_totp.setChecked(True)
else:
self._cred_hotp.setChecked(True)
@ -177,6 +196,10 @@ class AddCredDialog(qt.Dialog):
def n_digits(self):
return int(self._n_digits.currentText())
@property
def algorithm(self):
return ALG_SHA1 if self._algorithm.currentIndex() == 0 else ALG_SHA256
@property
def require_touch(self):
return self._require_touch.isChecked()