Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oath: add import command #327

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@

install_requires = [
'six', 'pyscard', 'pyusb', 'click',
'cryptography', 'pyopenssl', 'fido2 >= 0.7'
'cryptography', 'pyopenssl', 'fido2 >= 0.7',
'python-pskc'
]
if sys.version_info < (3, 4):
install_requires.append('enum34')
Expand Down
25 changes: 25 additions & 0 deletions ykman/cli/oath.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from __future__ import absolute_import
import click
import logging
from pskc import PSKC
from threading import Timer
from binascii import b2a_hex, a2b_hex
from .util import (
Expand Down Expand Up @@ -249,6 +250,30 @@ def uri(ctx, uri, touch, force):
_add_cred(ctx, data, force=force)


@oath.command('import')
@click_force_option
@click.pass_context
@click.option(
'-p', '--password', help='A password may be needed to decrypt the file.')
@click.argument('import_file', type=click.File('rb'), metavar='FILE')
def import_from_file(ctx, import_file, password, force):
"""
Import credentials from a file.

Import credentials from a PSKC file.
"""
ensure_validated(ctx)
pskc = PSKC(import_file)
if pskc.encryption.is_encrypted:
if not password:
ctx.fail('File is encrypted, password required.')
else:
pskc.encryption.derive_key(password)
for key in pskc.keys:
cred_data = CredentialData.from_pskc(key)
_add_cred(ctx, cred_data, force=force)


def _add_cred(ctx, data, force):
controller = ctx.obj['controller']

Expand Down
33 changes: 33 additions & 0 deletions ykman/oath.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,39 @@ def from_uri(cls, uri):
counter=int(params.get('counter', 0))
)

@classmethod
def from_pskc(cls, pskc_key):

def _get_oath_type(pskc_algo):
if 'hotp' in pskc_algo.lower():
return OATH_TYPE.HOTP
elif 'totp' in pskc_algo.lower():
return OATH_TYPE.TOTP
else:
raise ValueError('Unsupported PSKC algorithm!')

def _get_algo(pskc_algo_suite):
if 'sha1' in pskc_algo_suite.lower():
return ALGO.SHA1
elif 'sha256' in pskc_algo_suite.lower():
return ALGO.SHA256
elif 'sha512' in pskc_algo_suite.lower():
return ALGO.SHA512

return cls(
secret=pskc_key.secret,
issuer=pskc_key.issuer,
# TODO: Look over what we want as the name here.
name=pskc_key.friendly_name or pskc_key.userid or pskc_key.issuer,
oath_type=_get_oath_type(pskc_key.algorithm),
algorithm=_get_algo(
pskc_key.algorithm_suite) \
if pskc_key.algorithm_suite else ALGO.SHA1,
digits=pskc_key.response_length or 6,
period=pskc_key.time_interval or 30,
counter=pskc_key.counter or 0
)

def make_key(self):
key = self.name
if self.issuer:
Expand Down