Hello community,
here is the log from the commit of package yubikey-manager for openSUSE:Factory checked in at 2019-06-30 10:22:09
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yubikey-manager (Old)
and /work/SRC/openSUSE:Factory/.yubikey-manager.new.4615 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yubikey-manager"
Sun Jun 30 10:22:09 2019 rev:11 rq:712543 version:3.0.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/yubikey-manager/yubikey-manager.changes 2019-06-18 14:56:18.253404922 +0200
+++ /work/SRC/openSUSE:Factory/.yubikey-manager.new.4615/yubikey-manager.changes 2019-06-30 10:22:26.987708605 +0200
@@ -1,0 +2,10 @@
+Sat Jun 29 20:32:49 UTC 2019 - Karol Babioch
+
+- Version 3.0.0 (released 2019-06-24)
+ * Add support for new YubiKey Preview and lightning form factor
+ * FIDO: Support for credential management
+ * OpenPGP: Support for OpenPGP attestation, cardholder certificates and
+ cached touch policies
+ * OTP: Add flag for using numeric keypad when sending digits
+
+-------------------------------------------------------------------
Old:
----
yubikey-manager-2.1.1.tar.gz
yubikey-manager-2.1.1.tar.gz.sig
New:
----
yubikey-manager-3.0.0.tar.gz
yubikey-manager-3.0.0.tar.gz.sig
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ yubikey-manager.spec ++++++
--- /var/tmp/diff_new_pack.t9CyQJ/_old 2019-06-30 10:22:27.447709320 +0200
+++ /var/tmp/diff_new_pack.t9CyQJ/_new 2019-06-30 10:22:27.451709326 +0200
@@ -17,7 +17,7 @@
Name: yubikey-manager
-Version: 2.1.1
+Version: 3.0.0
Release: 0
Summary: Python 3 library and command line tool for configuring a YubiKey
License: BSD-2-Clause
++++++ yubikey-manager-2.1.1.tar.gz -> yubikey-manager-3.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/NEWS new/yubikey-manager-3.0.0/NEWS
--- old/yubikey-manager-2.1.1/NEWS 2019-05-28 09:04:43.000000000 +0200
+++ new/yubikey-manager-3.0.0/NEWS 2019-06-24 09:07:27.000000000 +0200
@@ -1,3 +1,9 @@
+* Version 3.0.0 (released 2019-06-24)
+ ** Add support for new YubiKey Preview and lightning form factor
+ ** FIDO: Support for credential management
+ ** OpenPGP: Support for OpenPGP attestation, cardholder certificates and cached touch policies
+ ** OTP: Add flag for using numeric keypad when sending digits
+
* Version 2.1.1 (released 2019-05-28)
** OTP: Add initial support for uploading Yubico OTP credentials to YubiCloud
** Don't automatically select the U2F applet on YubiKey NEO, it might be blocked by the OS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/PKG-INFO new/yubikey-manager-3.0.0/PKG-INFO
--- old/yubikey-manager-2.1.1/PKG-INFO 2019-05-28 10:05:25.000000000 +0200
+++ new/yubikey-manager-3.0.0/PKG-INFO 2019-06-24 09:12:28.000000000 +0200
@@ -1,13 +1,12 @@
-Metadata-Version: 1.2
+Metadata-Version: 1.1
Name: yubikey-manager
-Version: 2.1.1
+Version: 3.0.0
Summary: Tool for managing your YubiKey configuration.
Home-page: https://github.com/Yubico/yubikey-manager
-Author: Dain Nilsson
-Author-email: dain@yubico.com
-Maintainer: Yubico Open Source Maintainers
-Maintainer-email: ossmaint@yubico.com
+Author: Yubico Open Source Maintainers
+Author-email: ossmaint@yubico.com
License: BSD 2 clause
+Description-Content-Type: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: BSD License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/man/ykman.1 new/yubikey-manager-3.0.0/man/ykman.1
--- old/yubikey-manager-2.1.1/man/ykman.1 2019-05-28 08:53:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/man/ykman.1 2019-06-24 09:07:27.000000000 +0200
@@ -1,4 +1,4 @@
-.TH YKMAN "1" "May 2019" "ykman 2.1.1" "User Commands"
+.TH YKMAN "1" "June 2019" "ykman 3.0.0" "User Commands"
.SH NAME
ykman \- YubiKey Manager (ykman)
.SH SYNOPSIS
Binary files old/yubikey-manager-2.1.1/test/__pycache__/__init__.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/__init__.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/__pycache__/test_device.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/test_device.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/__pycache__/test_external_libs.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/test_external_libs.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/__pycache__/test_oath.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/test_oath.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/__pycache__/test_piv.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/test_piv.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/__pycache__/test_scancodes.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/test_scancodes.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/__pycache__/test_util.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/test_util.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/__pycache__/util.cpython-36.pyc and new/yubikey-manager-3.0.0/test/__pycache__/util.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/__init__.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/__init__.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_cli_config.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_cli_config.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_cli_misc.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_cli_misc.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_cli_oath.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_cli_oath.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_cli_openpgp.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_cli_openpgp.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_cli_otp.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_cli_otp.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_fips_u2f_commands.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_fips_u2f_commands.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_interfaces.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_interfaces.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/test_piv.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/test_piv.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/__pycache__/util.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/__pycache__/util.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/__init__.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/__init__.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/test_fips.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/test_fips.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/test_generate_cert_and_csr.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/test_generate_cert_and_csr.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/test_key_management.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/test_key_management.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/test_management_key.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/test_management_key.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/test_misc.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/test_misc.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/test_pin_puk.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/test_pin_puk.cpython-36.pyc differ
Binary files old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/__pycache__/util.cpython-36.pyc and new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/__pycache__/util.cpython-36.pyc differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/test_key_management.py new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/test_key_management.py
--- old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/test_key_management.py 2019-04-09 09:39:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/test_key_management.py 2019-06-24 09:07:27.000000000 +0200
@@ -107,7 +107,7 @@
csr = x509.load_pem_x509_csr(output.encode(), default_backend())
self.assertTrue(csr.is_signature_valid)
- def test_import_correct_cert_succeeds_with_pin(self):
+ def test_import_verify_correct_cert_succeeds_with_pin(self):
# Set up a key in the slot and create a certificate for it
public_key_pem = ykman_cli(
'piv', 'generate-key', '9a', '-a', 'ECCP256', '-m',
@@ -122,17 +122,20 @@
with self.assertRaises(SystemExit):
ykman_cli(
- 'piv', 'import-certificate', '9a', '/tmp/test-pub-key.pem',
+ 'piv', 'import-certificate', '--verify',
+ '9a', '/tmp/test-pub-key.pem',
'-m', DEFAULT_MANAGEMENT_KEY)
ykman_cli(
- 'piv', 'import-certificate', '9a', '/tmp/test-pub-key.pem',
+ 'piv', 'import-certificate', '--verify',
+ '9a', '/tmp/test-pub-key.pem',
'-m', DEFAULT_MANAGEMENT_KEY, '-P', DEFAULT_PIN)
ykman_cli(
- 'piv', 'import-certificate', '9a', '/tmp/test-pub-key.pem',
+ 'piv', 'import-certificate', '--verify',
+ '9a', '/tmp/test-pub-key.pem',
'-m', DEFAULT_MANAGEMENT_KEY, input=DEFAULT_PIN)
- def test_import_wrong_cert_fails(self):
+ def test_import_verify_wrong_cert_fails(self):
# Set up a key in the slot and create a certificate for it
public_key_pem = ykman_cli(
'piv', 'generate-key', '9a', '-a', 'ECCP256', '-m',
@@ -153,10 +156,10 @@
with self.assertRaises(SystemExit):
ykman_cli(
- 'piv', 'import-certificate', '9a', '-',
+ 'piv', 'import-certificate', '--verify', '9a', '-',
'-m', DEFAULT_MANAGEMENT_KEY, '-P', DEFAULT_PIN, input=cert_pem)
- def test_import_wrong_cert_can_be_forced(self):
+ def test_import_no_verify_wrong_cert_succeeds(self):
# Set up a key in the slot and create a certificate for it
public_key_pem = ykman_cli(
'piv', 'generate-key', '9a', '-a', 'ECCP256', '-m',
@@ -177,7 +180,7 @@
with self.assertRaises(SystemExit):
ykman_cli(
- 'piv', 'import-certificate', '9a', '-',
+ 'piv', 'import-certificate', '--verify', '9a', '-',
'-m', DEFAULT_MANAGEMENT_KEY, '-P', DEFAULT_PIN, input=cert_pem)
ykman_cli(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/test_misc.py new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/test_misc.py
--- old/yubikey-manager-2.1.1/test/on_yubikey/cli_piv/test_misc.py 2019-04-09 09:39:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/test/on_yubikey/cli_piv/test_misc.py 2019-06-24 09:07:27.000000000 +0200
@@ -1,6 +1,7 @@
from ..util import ykman_cli
from .util import PivTestCase, DEFAULT_MANAGEMENT_KEY
+
class Misc(PivTestCase):
def test_info(self):
@@ -14,7 +15,7 @@
def test_write_read_object(self):
ykman_cli(
'piv', 'write-object',
- '-m', DEFAULT_MANAGEMENT_KEY,'0x5f0001',
+ '-m', DEFAULT_MANAGEMENT_KEY, '0x5f0001',
'-', input='test data')
output = ykman_cli('piv', 'read-object', '0x5f0001')
self.assertEquals('test data\n', output)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/test/on_yubikey/test_cli_misc.py new/yubikey-manager-3.0.0/test/on_yubikey/test_cli_misc.py
--- old/yubikey-manager-2.1.1/test/on_yubikey/test_cli_misc.py 2019-04-09 09:39:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/test/on_yubikey/test_cli_misc.py 2019-06-24 09:07:27.000000000 +0200
@@ -13,7 +13,7 @@
@unittest.skipIf(is_fips(), 'Not applicable to YubiKey FIPS.')
def test_ykman_info_does_not_report_fips_for_non_fips_device(self):
- info = ykman_cli('info --check-fips')
+ info = ykman_cli('info', '--check-fips')
self.assertNotIn('FIPS', info)
@unittest.skipIf(not is_fips(), 'YubiKey FIPS required.')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/test/test_util.py new/yubikey-manager-3.0.0/test/test_util.py
--- old/yubikey-manager-2.1.1/test/test_util.py 2019-04-09 09:39:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/test/test_util.py 2019-06-24 09:07:27.000000000 +0200
@@ -33,7 +33,7 @@
for l in range(0, 38):
self.assertRegex(
generate_static_pw(l),
- '^[cbdefghijklnrtuvCBDEFGHIJKLNRTUV]{' + '{:d}'.format(l) + '}$')
+ '^[cbdefghijklnrtuvCBDEFGHIJKLNRTUV]{%d}$' % l)
def test_hmac_shorten_key(self):
self.assertEqual(b'short', hmac_shorten_key(b'short', 'sha1'))
@@ -146,15 +146,14 @@
with open_file('rsa_2048_key_cert.pfx') as rsa_2048_key_cert_pfx:
self.assertFalse(is_pem(rsa_2048_key_cert_pfx.read()))
- with open_file('rsa_2048_cert_metadata.pem') as rsa_2048_cert_metadata_pem:
- self.assertTrue(is_pem(rsa_2048_cert_metadata_pem.read()))
+ with open_file('rsa_2048_cert_metadata.pem') as f:
+ self.assertTrue(is_pem(f.read()))
with open_file(
'rsa_2048_key_cert_encrypted.pfx') as \
rsa_2048_key_cert_encrypted_pfx:
self.assertFalse(is_pem(rsa_2048_key_cert_encrypted_pfx.read()))
-
def test_form_factor_from_code(self):
self.assertEqual(FORM_FACTOR.UNKNOWN, FORM_FACTOR.from_code(None))
with self.assertRaises(ValueError):
@@ -166,4 +165,6 @@
self.assertEqual(
FORM_FACTOR.USB_C_KEYCHAIN, FORM_FACTOR.from_code(0x03))
self.assertEqual(FORM_FACTOR.USB_C_NANO, FORM_FACTOR.from_code(0x04))
- self.assertEqual(FORM_FACTOR.UNKNOWN, FORM_FACTOR.from_code(0x05))
+ self.assertEqual(FORM_FACTOR.USB_C_LIGHTNING,
+ FORM_FACTOR.from_code(0x05))
+ self.assertEqual(FORM_FACTOR.UNKNOWN, FORM_FACTOR.from_code(0x06))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/VERSION new/yubikey-manager-3.0.0/ykman/VERSION
--- old/yubikey-manager-2.1.1/ykman/VERSION 2019-05-27 12:35:48.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/VERSION 2019-06-24 09:07:27.000000000 +0200
@@ -1 +1 @@
-2.1.1
+3.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/cli/fido.py new/yubikey-manager-3.0.0/ykman/cli/fido.py
--- old/yubikey-manager-2.1.1/ykman/cli/fido.py 2019-05-08 09:19:37.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/cli/fido.py 2019-06-24 09:07:27.000000000 +0200
@@ -102,6 +102,74 @@
click.echo('PIN is not set.')
+@fido.command('list')
+@click.pass_context
+@click.option('-P', '--pin', help='PIN code.')
+def list_creds(ctx, pin):
+ """
+ List resident credentials.
+ """
+ controller = ctx.obj['controller']
+
+ if not controller.has_pin:
+ ctx.fail('No PIN set.')
+
+ if controller.has_pin and pin is None:
+ pin = _prompt_current_pin(prompt='Enter your PIN')
+
+ try:
+ for cred in controller.get_resident_credentials(pin):
+ click.echo('{} ({})'.format(cred.user_name, cred.rp_id))
+ except CtapError as e:
+ if e.code == CtapError.ERR.PIN_INVALID:
+ ctx.fail('Wrong PIN.')
+ except Exception as e:
+ logger.debug('Failed to list resident credentials', exc_info=e)
+ ctx.fail('Failed to list resident credentials.')
+
+
+@fido.command()
+@click.pass_context
+@click.argument('query')
+@click.option('-P', '--pin', help='PIN code.')
+@click.option('-f', '--force', is_flag=True,
+ help='Confirm deletion without prompting')
+def delete(ctx, query, pin, force):
+ """
+ Delete a resident credential.
+ """
+ controller = ctx.obj['controller']
+
+ if not controller.has_pin:
+ ctx.fail('No PIN set.')
+
+ if controller.has_pin and pin is None:
+ pin = _prompt_current_pin(prompt='Enter your PIN')
+
+ try:
+ hits = [
+ cred for cred in controller.get_resident_credentials(pin)
+ if query.lower() in cred.user_name or query.lower() in cred.rp_id
+ ]
+ if len(hits) == 0:
+ ctx.fail('No matches, nothing to be done.')
+ elif len(hits) == 1:
+ cred = hits[0]
+ if force or click.confirm(
+ 'Delete credential {} ({})?'.format(
+ cred.user_name, cred.rp_id)):
+ controller.delete_resident_credential(
+ cred.credential_id, pin)
+ else:
+ ctx.fail('Multiple matches, make the query more specific.')
+ except CtapError as e:
+ if e.code == CtapError.ERR.PIN_INVALID:
+ ctx.fail('Wrong PIN.')
+ except Exception as e:
+ logger.debug('Failed to delete resident credential', exc_info=e)
+ ctx.fail('Failed to delete resident credential.')
+
+
@fido.command('set-pin')
@click.pass_context
@click.option('-P', '--pin', help='Current PIN code.')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/cli/opgp.py new/yubikey-manager-3.0.0/ykman/cli/opgp.py
--- old/yubikey-manager-2.1.1/ykman/cli/opgp.py 2019-04-09 09:39:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/cli/opgp.py 2019-06-24 09:07:27.000000000 +0200
@@ -29,28 +29,17 @@
import logging
import click
-from ..util import TRANSPORT
+from ..util import TRANSPORT, parse_certificates, parse_private_key
from ..opgp import OpgpController, KEY_SLOT, TOUCH_MODE
from ..driver_ccid import APDUError, SW
-from .util import click_force_option, click_postpone_execution
+from .util import (
+ click_force_option, click_format_option, click_postpone_execution,
+ UpperCaseChoice)
logger = logging.getLogger(__name__)
-KEY_NAMES = dict(
- sig=KEY_SLOT.SIGNATURE,
- enc=KEY_SLOT.ENCRYPTION,
- aut=KEY_SLOT.AUTHENTICATION
-)
-
-MODE_NAMES = dict(
- off=TOUCH_MODE.OFF,
- on=TOUCH_MODE.ON,
- fixed=TOUCH_MODE.FIXED
-)
-
-
def one_of(data):
def inner(ctx, param, key):
if key is not None:
@@ -92,7 +81,7 @@
\b
Require touch to use the authentication key:
- $ ykman openpgp touch aut on
+ $ ykman openpgp set-touch aut on
"""
try:
ctx.obj['controller'] = OpgpController(ctx.obj['dev'].driver)
@@ -116,17 +105,23 @@
click.echo('PIN tries remaining: {}'.format(retries.pin))
click.echo('Reset code tries remaining: {}'.format(retries.reset))
click.echo('Admin PIN tries remaining: {}'.format(retries.admin))
- click.echo()
- click.echo('Touch policies')
- click.echo(
- 'Signature key {.name}'.format(
- controller.get_touch(KEY_SLOT.SIGNATURE)))
- click.echo(
- 'Encryption key {.name}'.format(
- controller.get_touch(KEY_SLOT.ENCRYPTION)))
- click.echo(
- 'Authentication key {.name}'.format(
- controller.get_touch(KEY_SLOT.AUTHENTICATION)))
+ # Touch only available on YK4 and later
+ if controller.version >= (4, 2, 6):
+ click.echo()
+ click.echo('Touch policies')
+ click.echo(
+ 'Signature key {!s}'.format(
+ controller.get_touch(KEY_SLOT.SIGNATURE)))
+ click.echo(
+ 'Encryption key {!s}'.format(
+ controller.get_touch(KEY_SLOT.ENCRYPTION)))
+ click.echo(
+ 'Authentication key {!s}'.format(
+ controller.get_touch(KEY_SLOT.AUTHENTICATION)))
+ if controller.supports_attestation:
+ click.echo(
+ 'Attestation key {!s}'.format(
+ controller.get_touch(KEY_SLOT.ATTESTATION)))
@openpgp.command()
@@ -153,63 +148,242 @@
click.echo('Admin PIN: 12345678')
-@openpgp.command()
-@click.argument('key', metavar='KEY', type=click.Choice(sorted(KEY_NAMES)),
- callback=lambda c, p, k: KEY_NAMES.get(k))
-@click.argument('policy', metavar='POLICY', type=click.Choice(sorted(MODE_NAMES)),
- callback=lambda c, p, k: MODE_NAMES.get(k))
-@click.option('--admin-pin', required=False, metavar='PIN',
- help='Admin PIN for OpenPGP.')
+@openpgp.command('set-touch')
+@click.argument(
+ 'key', metavar='KEY', type=UpperCaseChoice(['AUT', 'ENC', 'SIG', 'ATT']),
+ callback=lambda c, p, v: KEY_SLOT(v))
+@click.argument(
+ 'policy', metavar='POLICY',
+ type=UpperCaseChoice(['ON', 'OFF', 'FIXED', 'CACHED', 'CACHED-FIXED']),
+ callback=lambda c, p, v: TOUCH_MODE[v.replace('-', '_')])
+@click.option('-a', '--admin-pin', help='Admin PIN for OpenPGP.')
@click_force_option
@click.pass_context
-def touch(ctx, key, policy, admin_pin, force):
+def set_touch(ctx, key, policy, admin_pin, force):
"""
- Manage touch policy for OpenPGP keys.
+ Set touch policy for OpenPGP keys.
\b
- KEY Key slot to set (sig, enc or aut).
- POLICY Touch policy to set (on, off or fixed).
+ KEY Key slot to set (sig, enc, aut or att).
+ POLICY Touch policy to set (on, off, fixed, cached or cached-fixed).
"""
controller = ctx.obj['controller']
- old_policy = controller.get_touch(key)
- if old_policy == TOUCH_MODE.FIXED:
- ctx.fail('A FIXED policy cannot be changed!')
-
- force or click.confirm('Set touch policy of {.name} key to {.name}?'.format(
- key, policy), abort=True, err=True)
if admin_pin is None:
admin_pin = click.prompt('Enter admin PIN', hide_input=True, err=True)
- controller.set_touch(key, policy, admin_pin.encode('utf8'))
+
+ policy_name = policy.name.lower().replace('_', '-')
+
+ if policy not in controller.supported_touch_policies:
+ ctx.fail('Touch policy {} not supported.'.format(policy_name))
+
+ if force or click.confirm(
+ 'Set touch policy of {} key to {}?'.format(
+ key.name.lower(),
+ policy_name),
+ abort=True, err=True):
+ try:
+ controller.set_touch(key, policy, admin_pin)
+ except APDUError as e:
+ if e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED:
+ ctx.fail('Touch policy not allowed.')
+ logger.debug('Failed to set touch policy', exc_info=e)
+ ctx.fail('Failed to set touch policy.')
@openpgp.command('set-pin-retries')
-@click.argument('pw-attempts', nargs=3, type=click.IntRange(1, 99))
-@click.password_option('--admin-pin', metavar='PIN', prompt='Enter admin PIN',
- confirmation_prompt=False)
+@click.argument(
+ 'pin-retries', type=click.IntRange(1, 99), metavar='PIN-RETRIES')
+@click.argument(
+ 'reset-code-retries',
+ type=click.IntRange(1, 99), metavar='RESET-CODE-RETRIES')
+@click.argument(
+ 'admin-pin-retries',
+ type=click.IntRange(1, 99), metavar='ADMIN-PIN-RETRIES')
+@click.option('-a', '--admin-pin', help='Admin PIN for OpenPGP.')
@click_force_option
@click.pass_context
-def set_pin_retries(ctx, pw_attempts, admin_pin, force):
+def set_pin_retries(
+ ctx, admin_pin, pin_retries,
+ reset_code_retries, admin_pin_retries, force):
"""
- Manage pin-retries.
-
- Sets the number of attempts available before locking for each PIN.
-
- PW_ATTEMPTS should be three integer values corresponding to the number of
- attempts for the PIN, Reset Code, and Admin PIN, respectively.
+ Set PIN, Reset Code and Admin PIN retries.
"""
controller = ctx.obj['controller']
+
+ if admin_pin is None:
+ admin_pin = click.prompt('Enter admin PIN', hide_input=True, err=True)
+
resets_pins = controller.version < (4, 0, 0)
if resets_pins:
click.echo('WARNING: Setting PIN retries will reset the values for all '
'3 PINs!')
- force or click.confirm('Set PIN retry counters to: {} {} {}?'.format(
- *pw_attempts), abort=True, err=True)
- controller.set_pin_retries(*(pw_attempts + (admin_pin.encode('utf8'),)))
- click.echo('PIN retries successfully set.')
- if resets_pins:
- click.echo('Default PINs are set.')
- echo_default_pins()
+ if force or click.confirm(
+ 'Set PIN retry counters to: {} {} {}?'.format(
+ pin_retries, reset_code_retries,
+ admin_pin_retries), abort=True, err=True):
+
+ controller.set_pin_retries(
+ pin_retries, reset_code_retries, admin_pin_retries, admin_pin)
+
+ if resets_pins:
+ click.echo('Default PINs are set.')
+ echo_default_pins()
+
+
+@openpgp.command()
+@click.pass_context
+@click.option('-P', '--pin', help='PIN code.')
+@click_format_option
+@click.argument(
+ 'key', metavar='KEY', type=UpperCaseChoice(['AUT', 'ENC', 'SIG']),
+ callback=lambda c, p, v: KEY_SLOT(v))
+@click.argument('certificate', type=click.File('wb'), metavar='CERTIFICATE')
+def attest(ctx, key, certificate, pin, format):
+ """
+ Generate a attestation certificate for a key.
+
+ Attestation is used to show that an asymmetric key was generated on the
+ YubiKey and therefore doesn't exist outside the device.
+
+ \b
+ KEY Key slot to attest (sig, enc, aut).
+ CERTIFICATE File to write attestation certificate to. Use '-' to use stdout.
+ """
+
+ controller = ctx.obj['controller']
+
+ if not pin:
+ pin = click.prompt(
+ 'Enter PIN', default='', hide_input=True,
+ show_default=False, err=True)
+
+ try:
+ cert = controller.read_certificate(key)
+ except ValueError:
+ cert = None
+
+ if not cert or click.confirm(
+ 'There is already data stored in the certificate slot for {}, '
+ 'do you want to overwrite it?'.format(key.name)):
+ touch_policy = controller.get_touch(KEY_SLOT.ATTESTATION)
+ if touch_policy in [TOUCH_MODE.ON, TOUCH_MODE.FIXED]:
+ click.echo('Touch your YubiKey...')
+ try:
+ cert = controller.attest(key, pin)
+ certificate.write(cert.public_bytes(encoding=format))
+ except Exception as e:
+ logger.debug('Failed to attest', exc_info=e)
+ ctx.fail('Attestation failed')
+
+
+@openpgp.command('export-certificate')
+@click.pass_context
+@click.argument(
+ 'key', metavar='KEY', type=UpperCaseChoice(['AUT', 'ENC', 'SIG', 'ATT']),
+ callback=lambda c, p, v: KEY_SLOT(v))
+@click_format_option
+@click.argument('certificate', type=click.File('wb'), metavar='CERTIFICATE')
+def export_certificate(ctx, key, format, certificate):
+ """
+ Export an OpenPGP Cardholder certificate.
+
+ \b
+ KEY Key slot to read from (sig, enc, aut, or att).
+ CERTIFICATE File to write certificate to. Use '-' to use stdout.
+ """
+ controller = ctx.obj['controller']
+ try:
+ cert = controller.read_certificate(key)
+ except ValueError:
+ ctx.fail('Failed to read certificate from {}'.format(key.name))
+ certificate.write(cert.public_bytes(encoding=format))
+
+
+@openpgp.command('delete-certificate')
+@click.option('-a', '--admin-pin', help='Admin PIN for OpenPGP.')
+@click.pass_context
+@click.argument(
+ 'key', metavar='KEY', type=UpperCaseChoice(['AUT', 'ENC', 'SIG', 'ATT']),
+ callback=lambda c, p, v: KEY_SLOT(v))
+def delete_certificate(ctx, key, admin_pin):
+ """
+ Delete an OpenPGP Cardholder certificate.
+
+ \b
+ KEY Key slot to delete certificate from (sig, enc, aut, or att).
+ """
+ controller = ctx.obj['controller']
+ if admin_pin is None:
+ admin_pin = click.prompt('Enter admin PIN', hide_input=True, err=True)
+ try:
+ controller.delete_certificate(key, admin_pin)
+ except Exception as e:
+ logger.debug('Failed to delete ', exc_info=e)
+ ctx.fail('Failed to delete certificate.')
+
+
+@openpgp.command('import-certificate')
+@click.option('-a', '--admin-pin', help='Admin PIN for OpenPGP.')
+@click.pass_context
+@click.argument(
+ 'key', metavar='KEY', type=UpperCaseChoice(['AUT', 'ENC', 'SIG', 'ATT']),
+ callback=lambda c, p, v: KEY_SLOT(v))
+@click.argument('cert', type=click.File('rb'), metavar='CERTIFICATE')
+def import_certificate(ctx, key, cert, admin_pin):
+ """
+ Import an OpenPGP Cardholder certificate.
+
+ \b
+ KEY Key slot to import certificate to (sig, enc, aut, or att).
+ CERTIFICATE File containing the certificate. Use '-' to use stdin.
+ """
+ controller = ctx.obj['controller']
+
+ if admin_pin is None:
+ admin_pin = click.prompt('Enter admin PIN', hide_input=True, err=True)
+
+ try:
+ certs = parse_certificates(cert.read(), password=None)
+ except Exception as e:
+ logger.debug('Failed to parse', exc_info=e)
+ ctx.fail('Failed to parse certificate.')
+ if len(certs) != 1:
+ ctx.fail('Can only import one certificate.')
+ try:
+ controller.import_certificate(key, certs[0], admin_pin)
+ except Exception as e:
+ logger.debug('Failed to import', exc_info=e)
+ ctx.fail('Failed to import certificate')
+
+
+@openpgp.command('import-attestation-key')
+@click.option('-a', '--admin-pin', help='Admin PIN for OpenPGP.')
+@click.pass_context
+@click.argument('private-key', type=click.File('rb'), metavar='PRIVATE-KEY')
+def import_attestation_key(ctx, private_key, admin_pin):
+ """
+ Import a private attestation key.
+
+ Import a private key for OpenPGP attestation.
+
+ \b
+ PRIVATE-KEY File containing the private key. Use '-' to use stdin.
+ """
+ controller = ctx.obj['controller']
+
+ if admin_pin is None:
+ admin_pin = click.prompt('Enter admin PIN', hide_input=True, err=True)
+ try:
+ private_key = parse_private_key(private_key.read(), password=None)
+ except Exception as e:
+ logger.debug('Failed to parse', exc_info=e)
+ ctx.fail('Failed to parse private key.')
+ try:
+ controller.import_attestation_key(private_key, admin_pin)
+ except Exception as e:
+ logger.debug('Failed to import', exc_info=e)
+ ctx.fail('Failed to import attestation key.')
openpgp.transports = TRANSPORT.CCID
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/cli/otp.py new/yubikey-manager-3.0.0/ykman/cli/otp.py
--- old/yubikey-manager-2.1.1/ykman/cli/otp.py 2019-05-27 12:04:25.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/cli/otp.py 2019-06-24 09:07:27.000000000 +0200
@@ -36,7 +36,7 @@
from binascii import a2b_hex, b2a_hex
from .. import __version__
from ..driver_otp import YkpersError
-from ..otp import OtpController, PrepareUploadFailed
+from ..otp import OtpController, PrepareUploadFailed, SlotConfig
from ..scancodes import KEYBOARD_LAYOUT
import logging
import os
@@ -339,7 +339,9 @@
abort=True, err=True)
try:
- controller.program_otp(slot, key, public_id, private_id, not no_enter)
+ controller.program_otp(slot, key, public_id, private_id, SlotConfig(
+ append_cr=not no_enter
+ ))
except YkpersError as e:
_failed_to_write_msg(ctx, e)
@@ -394,8 +396,9 @@
if not force:
_confirm_slot_overwrite(controller, slot)
try:
- controller.program_static(
- slot, password, not no_enter, keyboard_layout=keyboard_layout)
+ controller.program_static(slot, password, keyboard_layout, SlotConfig(
+ append_cr=not no_enter
+ ))
except YkpersError as e:
_failed_to_write_msg(ctx, e)
@@ -546,7 +549,9 @@
err=True)
try:
controller.program_hotp(
- slot, key, counter, int(digits) == 8, not no_enter)
+ slot, key, counter, int(digits) == 8, SlotConfig(
+ append_cr=not no_enter
+ ))
except YkpersError as e:
_failed_to_write_msg(ctx, e)
@@ -569,8 +574,11 @@
'-p', '--pacing', type=click.Choice(['0', '20', '40', '60']),
default='0', show_default=True, help='Throttle output speed by '
'adding a delay (in ms) between characters emitted.')
+@click.option('--use-numeric-keypad', is_flag=True, show_default=True,
+ help='Use scancodes for numeric keypad when sending digits.'
+ ' Helps with some keyboard layouts. ')
def settings(ctx, slot, new_access_code, delete_access_code, enter, pacing,
- force):
+ use_numeric_keypad, force):
"""
Update the settings for a slot.
@@ -605,7 +613,11 @@
pacing = int(pacing)
try:
- controller.update_settings(slot, enter=enter, pacing=pacing)
+ controller.update_settings(slot, SlotConfig(
+ append_cr=enter,
+ pacing=pacing,
+ numeric_keypad=use_numeric_keypad
+ ))
except YkpersError as e:
_failed_to_write_msg(ctx, e)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/cli/piv.py new/yubikey-manager-3.0.0/ykman/cli/piv.py
--- old/yubikey-manager-2.1.1/ykman/cli/piv.py 2019-04-30 12:51:29.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/cli/piv.py 2019-06-24 09:07:27.000000000 +0200
@@ -165,9 +165,11 @@
except AttributeError:
print_dn = False
logger.debug('Failed to read DN, falling back to only CNs')
- subject_cn = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
+ subject_cn = cert.subject.get_attributes_for_oid(
+ x509.NameOID.COMMON_NAME)
subject_cn = subject_cn[0].value if subject_cn else 'None'
- issuer_cn = cert.issuer.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
+ issuer_cn = cert.issuer.get_attributes_for_oid(
+ x509.NameOID.COMMON_NAME)
issuer_cn = issuer_cn[0].value if issuer_cn else 'None'
except ValueError as e:
# Malformed certificates may throw ValueError
@@ -823,6 +825,7 @@
show_default=False, hide_input=True, err=True)
controller.unblock_pin(puk, new_pin)
+
@piv.command('read-object')
@click_pin_option
@click.pass_context
@@ -875,8 +878,8 @@
the range 5f0000 - 5fffff.
\b
- OBJECT-ID Id of PIV object in HEX.
- DATA File containing the data to be written. Use '-' to use stdin.
+ OBJECT-ID Id of PIV object in HEX.
+ DATA File containing the data to be written. Use '-' to use stdin.
"""
controller = ctx.obj['controller']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/device.py new/yubikey-manager-3.0.0/ykman/device.py
--- old/yubikey-manager-2.1.1/ykman/device.py 2019-05-27 12:04:25.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/device.py 2019-06-24 09:07:27.000000000 +0200
@@ -270,6 +270,7 @@
FORM_FACTOR.USB_A_NANO,
FORM_FACTOR.USB_C_KEYCHAIN,
FORM_FACTOR.USB_C_NANO,
+ FORM_FACTOR.USB_C_LIGHTNING
):
config._set(TAG.NFC_SUPPORTED, 0)
config._set(TAG.NFC_ENABLED, 0)
@@ -284,7 +285,8 @@
if config.nfc_supported:
self.device_name = 'Security Key NFC'
elif self._key_type == YUBIKEY.YK4:
- if (5, 1, 0) > self.version >= (5, 0, 0):
+ if (5, 0, 0) <= self.version < (5, 1, 0) or \
+ self.version in [(5, 2, 0), (5, 2, 1), (5, 2, 2)]:
self.device_name = 'YubiKey Preview'
elif self.version >= (5, 1, 0):
logger.debug('Identified YubiKey 5')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/fido.py new/yubikey-manager-3.0.0/ykman/fido.py
--- old/yubikey-manager-2.1.1/ykman/fido.py 2019-04-29 14:32:48.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/fido.py 2019-06-24 09:07:27.000000000 +0200
@@ -29,13 +29,35 @@
import six
import time
+import logging
from fido2.ctap1 import CTAP1, ApduError
-from fido2.ctap2 import CTAP2, PinProtocolV1
+from fido2.ctap2 import CTAP2, PinProtocolV1, CredentialManagement
from threading import Timer
from .driver_ccid import SW
from .driver_fido import FIPS_U2F_CMD
+logger = logging.getLogger(__name__)
+
+
+class ResidentCredential(object):
+ def __init__(self, raw_credential, raw_rp):
+ self._raw_credential = raw_credential
+ self._raw_rp = raw_rp
+
+ @property
+ def credential_id(self):
+ return self._raw_credential[CredentialManagement.RESULT.CREDENTIAL_ID]
+
+ @property
+ def rp_id(self):
+ return self._raw_rp[CredentialManagement.RESULT.RP]['id']
+
+ @property
+ def user_name(self):
+ return self._raw_credential[CredentialManagement.RESULT.USER]['name']
+
+
class Fido2Controller(object):
def __init__(self, driver):
@@ -48,6 +70,27 @@
def has_pin(self):
return self._pin
+ def get_resident_credentials(self, pin):
+ _credman = CredentialManagement(
+ self.ctap,
+ self.pin.VERSION,
+ self.pin.get_pin_token(pin))
+
+ for rp in _credman.enumerate_rps():
+ for cred in _credman.enumerate_creds(
+ rp[CredentialManagement.RESULT.RP_ID_HASH]):
+ yield ResidentCredential(cred, rp)
+
+ def delete_resident_credential(self, credential_id, pin):
+ _credman = CredentialManagement(
+ self.ctap,
+ self.pin.VERSION,
+ self.pin.get_pin_token(pin))
+
+ for cred in self.get_resident_credentials(pin):
+ if credential_id == cred.credential_id:
+ _credman.delete_cred(credential_id)
+
def get_pin_retries(self):
return self.pin.get_pin_retries()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/opgp.py new/yubikey-manager-3.0.0/ykman/opgp.py
--- old/yubikey-manager-2.1.1/ykman/opgp.py 2019-04-09 09:39:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/opgp.py 2019-06-24 09:07:27.000000000 +0200
@@ -28,18 +28,62 @@
from __future__ import absolute_import
import six
+import struct
+import logging
from .util import AID
from .driver_ccid import (APDUError, SW, GP_INS_SELECT)
-from enum import IntEnum, unique
-from binascii import b2a_hex
+from enum import Enum, IntEnum, unique
+from binascii import b2a_hex, a2b_hex
from collections import namedtuple
+from cryptography import x509
+from cryptography.utils import int_to_bytes
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.serialization import Encoding
+from cryptography.hazmat.primitives.asymmetric import rsa, ec
+
+
+logger = logging.getLogger(__name__)
@unique
-class KEY_SLOT(IntEnum): # noqa: N801
- SIGNATURE = 0xd6
- ENCRYPTION = 0xd7
- AUTHENTICATION = 0xd8
+class KEY_SLOT(Enum): # noqa: N801
+ SIGNATURE = 'SIG'
+ ENCRYPTION = 'ENC'
+ AUTHENTICATION = 'AUT'
+ ATTESTATION = 'ATT'
+
+ @property
+ def key_position(self):
+ if self == KEY_SLOT.SIGNATURE:
+ return 0x01
+ if self == KEY_SLOT.ENCRYPTION:
+ return 0x02
+ if self == KEY_SLOT.AUTHENTICATION:
+ return 0x03
+ if self == KEY_SLOT.ATTESTATION:
+ return 0x04
+
+ @property
+ def touch_position(self):
+ if self == KEY_SLOT.SIGNATURE:
+ return 0xd6
+ if self == KEY_SLOT.ENCRYPTION:
+ return 0xd7
+ if self == KEY_SLOT.AUTHENTICATION:
+ return 0xd8
+ if self == KEY_SLOT.ATTESTATION:
+ return 0xd9
+
+ @property
+ def cert_position(self):
+ if self == KEY_SLOT.SIGNATURE:
+ return 0x02
+ if self == KEY_SLOT.ENCRYPTION:
+ return 0x01
+ if self == KEY_SLOT.AUTHENTICATION:
+ return 0x00
+ if self == KEY_SLOT.ATTESTATION:
+ return 0x03
@unique
@@ -47,6 +91,25 @@
OFF = 0x00
ON = 0x01
FIXED = 0x02
+ CACHED = 0x03
+ CACHED_FIXED = 0x04
+
+ def __str__(self):
+ if self == TOUCH_MODE.OFF:
+ return 'Off'
+ elif self == TOUCH_MODE.ON:
+ return 'On'
+ elif self == TOUCH_MODE.FIXED:
+ return 'On (fixed)'
+ elif self == TOUCH_MODE.CACHED:
+ return 'Cached'
+ elif self == TOUCH_MODE.CACHED_FIXED:
+ return 'Cached (fixed)'
+
+
+@unique
+class TAG(IntEnum): # noqa: N801
+ CARDHOLDER_CERTIFICATE = 0x7f
@unique
@@ -58,6 +121,9 @@
TERMINATE = 0xe6
ACTIVATE = 0x44
PUT_DATA = 0xda
+ GET_ATTESTATION = 0xfb
+ SEND_REMAINING = 0xc0
+ SELECT_DATA = 0xa5
PinRetries = namedtuple('PinRetries', ['pin', 'reset', 'admin'])
@@ -92,6 +158,23 @@
return self._driver.send_apdu(cl, ins, p1, p2, data, check)
raise
+ def send_cmd(self, cl, ins, p1=0, p2=0, data=b'', check=SW.OK):
+ while len(data) > 0xff:
+ self._driver.send_apdu(0x10, ins, p1, p2, data[:0xff])
+ data = data[0xff:]
+ resp, sw = self._driver.send_apdu(0, ins, p1, p2, data, check=None)
+
+ while (sw >> 8) == SW.MORE_DATA:
+ more, sw = self._driver.send_apdu(
+ 0, INS.SEND_REMAINING, 0, 0, b'', check=None)
+ resp += more
+
+ if check is None:
+ return resp, sw
+ elif sw != check:
+ raise APDUError(resp, sw)
+ return resp
+
def _read_version(self):
bcd_hex = b2a_hex(self.send_apdu(0, INS.GET_VERSION, 0, 0))
return tuple(int(bcd_hex[i:i+2]) for i in range(0, 6, 2))
@@ -118,32 +201,163 @@
def _verify(self, pw, pin):
try:
+ pin = pin.encode('utf-8')
self.send_apdu(0, INS.VERIFY, 0, pw, pin)
except APDUError:
pw_remaining = self.get_remaining_pin_tries()[pw-PW1]
raise ValueError('Invalid PIN, {} tries remaining.'.format(
pw_remaining))
- def get_touch(self, key_slot):
+ @property
+ def supported_touch_policies(self):
if self.version < (4, 2, 0):
+ return []
+ if self.version < (5, 2, 1):
+ return [TOUCH_MODE.ON, TOUCH_MODE.OFF, TOUCH_MODE.FIXED]
+ if self.version >= (5, 2, 1):
+ return [
+ TOUCH_MODE.ON, TOUCH_MODE.OFF, TOUCH_MODE.FIXED,
+ TOUCH_MODE.CACHED, TOUCH_MODE.CACHED_FIXED]
+
+ @property
+ def supports_attestation(self):
+ return self.version >= (5, 2, 1)
+
+ def get_touch(self, key_slot):
+ if not self.supported_touch_policies:
raise ValueError('Touch policy is available on YubiKey 4 or later.')
- data = self.send_apdu(0, INS.GET_DATA, 0, key_slot)
+ if key_slot == KEY_SLOT.ATTESTATION and not self.supports_attestation:
+ raise ValueError('Attestation key not available on this device.')
+ data = self.send_apdu(0, INS.GET_DATA, 0, key_slot.touch_position)
return TOUCH_MODE(six.indexbytes(data, 0))
- def set_touch(self, key_slot, mode, pin):
- if self.version < (4, 2, 0):
+ def set_touch(self, key_slot, mode, admin_pin):
+ if not self.supported_touch_policies:
raise ValueError('Touch policy is available on YubiKey 4 or later.')
- self._verify(PW3, pin)
- self.send_apdu(0, INS.PUT_DATA, 0, key_slot,
+ if mode not in self.supported_touch_policies:
+ raise ValueError('Touch policy not available on this device.')
+ self._verify(PW3, admin_pin)
+ self.send_apdu(0, INS.PUT_DATA, 0, key_slot.touch_position,
bytes(bytearray([mode, TOUCH_METHOD_BUTTON])))
- def set_pin_retries(self, pw1_tries, pw2_tries, pw3_tries, pin):
+ def set_pin_retries(self, pw1_tries, pw2_tries, pw3_tries, admin_pin):
if self.version < (1, 0, 7): # For YubiKey NEO
raise ValueError('Setting PIN retry counters requires version '
'1.0.7 or later.')
if (4, 0, 0) <= self.version < (4, 3, 1): # For YubiKey 4
raise ValueError('Setting PIN retry counters requires version '
'4.3.1 or later.')
- self._verify(PW3, pin)
+ self._verify(PW3, admin_pin)
self.send_apdu(0, INS.SET_PIN_RETRIES, 0, 0,
bytes(bytearray([pw1_tries, pw2_tries, pw3_tries])))
+
+ def read_certificate(self, key_slot):
+ self.send_cmd(
+ 0, INS.SELECT_DATA, key_slot.cert_position,
+ 0x04, data=a2b_hex('0660045C027F21'))
+ data = self.send_cmd(
+ 0, INS.GET_DATA, TAG.CARDHOLDER_CERTIFICATE, 0x21)
+ if not data:
+ raise ValueError('No certificate found!')
+ return x509.load_der_x509_certificate(data, default_backend())
+
+ def import_certificate(self, key_slot, certificate, admin_pin):
+ self._verify(PW3, admin_pin)
+ cert_data = certificate.public_bytes(Encoding.DER)
+ self.send_cmd(
+ 0, INS.SELECT_DATA, key_slot.cert_position,
+ 0x04, data=a2b_hex('0660045C027F21'))
+ self.send_cmd(
+ 0, INS.PUT_DATA, TAG.CARDHOLDER_CERTIFICATE, 0x21, data=cert_data)
+
+ def _get_key_attributes(self, key):
+ if isinstance(key, rsa.RSAPrivateKey):
+ return struct.pack('>BHHB', 0x01, key.key_size, 32, 0)
+ if isinstance(key, ec.EllipticCurvePrivateKey):
+ return int_to_bytes(
+ self._get_opgp_algo_id_from_ec(
+ key)) + a2b_hex(self._get_oid_from_ec(key))
+ raise ValueError('Not a valid private key!')
+
+ def _get_oid_from_ec(self, key):
+ curve = key.curve.name
+ if curve == 'secp384r1':
+ return '2B81040022'
+ if curve == 'secp256r1':
+ return '2A8648CE3D030107'
+ if curve == 'secp521r1':
+ return '2B81040023'
+ if curve == 'x25519':
+ return '2B060104019755010501'
+ raise ValueError('No OID for curve: ' + curve)
+
+ def _get_opgp_algo_id_from_ec(self, key):
+ curve = key.curve.name
+ if curve in ['secp384r1', 'secp256r1', 'secp521r1']:
+ return 0x13
+ if curve == 'x25519':
+ return 0x16
+ raise ValueError('No Algo ID for curve: ' + curve)
+
+ def _get_key_data(self, key):
+
+ def _der_len(data):
+ ln = len(data)
+ if ln <= 128:
+ res = [ln]
+ elif ln <= 255:
+ res = [0x81, ln]
+ else:
+ res = [0x82, (ln >> 8) & 0xff, ln & 0xff]
+ return bytearray(res)
+
+ private_numbers = key.private_numbers()
+ data = a2b_hex('B603840181')
+
+ if isinstance(key, rsa.RSAPrivateKey):
+ ln = key.key_size // 8 // 2
+ data += b'\x7f\x48\x08\x91\x03\x92\x81\x80\x93\x81\x80\x5f\x48\x82\x01\x03\x01\x00\x01' # noqa: E501
+ data += int_to_bytes(private_numbers.p, ln)
+ data += int_to_bytes(private_numbers.q, ln)
+ return b'\x4d' + _der_len(data) + data
+ elif isinstance(key, ec.EllipticCurvePrivateKey):
+ ln = key.key_size // 8
+ privkey = int_to_bytes(private_numbers.private_value, ln)
+ data += b'\x7f\x48\x02\x92' + _der_len(privkey)
+ data += b'\x5f\x48' + _der_len(privkey) + privkey
+ return b'\x4d' + _der_len(data) + data
+
+ def import_attestation_key(self, key, admin_pin):
+ self._verify(PW3, admin_pin)
+ data = self._get_key_attributes(key)
+ self.send_cmd(0, INS.PUT_DATA, 0, 0xda, data=data)
+ data = self._get_key_data(key)
+ self.send_cmd(0, 0xdb, 0x3f, 0xff, data=data)
+
+ def delete_attestation_key(self, admin_pin):
+ self._verify(PW3, admin_pin)
+ # Delete attestation key by changing the key attributes twice.
+ self.send_cmd(
+ 0, INS.PUT_DATA, 0, 0xda,
+ data=struct.pack('>BHHB', 0x01, 2048, 32, 0))
+ self.send_cmd(
+ 0, INS.PUT_DATA, 0, 0xda,
+ data=struct.pack('>BHHB', 0x01, 4096, 32, 0))
+
+ def delete_certificate(self, key_slot, admin_pin):
+ self._verify(PW3, admin_pin)
+ self.send_cmd(
+ 0, INS.SELECT_DATA, key_slot.cert_position(),
+ 0x04, data=a2b_hex('0660045C027F21'))
+ self.send_apdu(
+ 0, INS.PUT_DATA, TAG.CARDHOLDER_CERTIFICATE, 0x21, data=b'')
+
+ def attest(self, key_slot, pin):
+ self._verify(PW1, pin)
+ self.send_apdu(0x80, INS.GET_ATTESTATION, key_slot.key_position, 0)
+ self.send_cmd(
+ 0, INS.SELECT_DATA, key_slot.cert_position(),
+ 0x04, data=a2b_hex('0660045C027F21'))
+ data = self.send_cmd(
+ 0, INS.GET_DATA, TAG.CARDHOLDER_CERTIFICATE, 0x21)
+ return x509.load_der_x509_certificate(data, default_backend())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/otp.py new/yubikey-manager-3.0.0/ykman/otp.py
--- old/yubikey-manager-2.1.1/ykman/otp.py 2019-05-16 09:16:05.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/otp.py 2019-06-24 09:07:27.000000000 +0200
@@ -111,6 +111,68 @@
return [e.message() for e in self.errors]
+class SlotConfig(object):
+ def __init__(
+ self,
+ serial_api_visible=True,
+ allow_update=True,
+ append_cr=True,
+ pacing=None,
+ numeric_keypad=False,
+ ):
+ self.serial_api_visible = serial_api_visible
+ self.allow_update = allow_update
+ self.append_cr = append_cr
+ self.pacing = pacing
+ self.numeric_keypad = numeric_keypad
+
+
+class _SlotConfigContext(object):
+
+ def __init__(self, dev, cmd, conf):
+ st = ykpers.ykds_alloc()
+ self.cfg = ykpers.ykp_alloc()
+ try:
+ check(ykpers.yk_get_status(dev, st))
+ ykpers.ykp_configure_version(self.cfg, st)
+ ykpers.ykp_configure_command(self.cfg, cmd)
+ except YkpersError:
+ ykpers.ykp_free_config(self.cfg)
+ raise
+ finally:
+ ykpers.ykds_free(st)
+
+ if conf is None:
+ conf = SlotConfig()
+ self._apply(conf)
+
+ def _apply(self, config):
+ if config.serial_api_visible:
+ check(ykpers.ykp_set_extflag(self.cfg, 'SERIAL_API_VISIBLE'))
+ if config.allow_update:
+ check(ykpers.ykp_set_extflag(self.cfg, 'ALLOW_UPDATE'))
+ if config.append_cr:
+ check(ykpers.ykp_set_tktflag(self.cfg, 'APPEND_CR'))
+
+ # Output speed throttling
+ if config.pacing == 20:
+ check(ykpers.ykp_set_cfgflag(self.cfg, 'PACING_10MS'))
+ elif config.pacing == 40:
+ check(ykpers.ykp_set_cfgflag(self.cfg, 'PACING_20MS'))
+ elif config.pacing == 60:
+ check(ykpers.ykp_set_cfgflag(self.cfg, 'PACING_10MS'))
+ check(ykpers.ykp_set_cfgflag(self.cfg, 'PACING_20MS'))
+
+ if config.numeric_keypad:
+ check(ykpers.ykp_set_extflag(self.cfg, 'USE_NUMERIC_KEYPAD'))
+
+ def __enter__(self):
+ return self.cfg
+
+ def __exit__(self, type, value, traceback):
+ ykpers.ykp_free_config(self.cfg)
+
+
class OtpController(object):
def __init__(self, driver):
@@ -126,30 +188,19 @@
def access_code(self, value):
self._access_code = value
- def _create_cfg(self, cmd):
- st = ykpers.ykds_alloc()
- cfg = ykpers.ykp_alloc()
- try:
- check(ykpers.yk_get_status(self._dev, st))
- ykpers.ykp_configure_version(cfg, st)
- ykpers.ykp_configure_command(cfg, cmd)
- check(ykpers.ykp_set_extflag(cfg, 'SERIAL_API_VISIBLE'))
- check(ykpers.ykp_set_extflag(cfg, 'ALLOW_UPDATE'))
- if self.access_code is not None:
- check(ykpers.ykp_set_access_code(
- cfg, self.access_code, _ACCESS_CODE_LENGTH))
- return cfg
- except YkpersError:
- ykpers.ykp_free_config(cfg)
- raise
- finally:
- ykpers.ykds_free(st)
+ def _create_cfg(self, cmd, conf=None):
+ context = _SlotConfigContext(self._dev, cmd, conf)
+ if self.access_code is not None:
+ check(ykpers.ykp_set_access_code(
+ context.cfg, self.access_code, _ACCESS_CODE_LENGTH))
+
+ return context
@property
def slot_status(self):
return self._driver.slot_status
- def program_otp(self, slot, key, fixed, uid, append_cr=True):
+ def program_otp(self, slot, key, fixed, uid, config=None):
if len(key) != 16:
raise ValueError('key must be 16 bytes')
if len(uid) != 6:
@@ -158,19 +209,16 @@
raise ValueError('public ID must be <= 16 bytes')
cmd = slot_to_cmd(slot)
- cfg = self._create_cfg(cmd)
- try:
+ with self._create_cfg(cmd, config) as cfg:
check(ykpers.ykp_set_fixed(cfg, fixed, len(fixed)))
check(ykpers.ykp_set_uid(cfg, uid, 6))
ykpers.ykp_AES_key_from_raw(cfg, key)
- if append_cr:
- check(ykpers.ykp_set_tktflag(cfg, 'APPEND_CR'))
- check(ykpers.yk_write_command(self._dev,
- ykpers.ykp_core_config(cfg),
- cmd, self.access_code))
- finally:
- ykpers.ykp_free_config(cfg)
+ check(ykpers.yk_write_command(
+ self._dev,
+ ykpers.ykp_core_config(cfg),
+ cmd, self.access_code
+ ))
def prepare_upload_key(self, key, public_id, private_id, serial=None,
user_agent='python-yubikey-manager/' + __version__):
@@ -218,8 +266,13 @@
errors = []
raise PrepareUploadFailed(resp.status, resp_body, errors)
- def program_static(self, slot, password, append_cr=True,
- keyboard_layout=KEYBOARD_LAYOUT.MODHEX):
+ def program_static(
+ self,
+ slot,
+ password,
+ keyboard_layout=KEYBOARD_LAYOUT.MODHEX,
+ config=None
+ ):
pw_len = len(password)
if self._driver.version < (2, 0, 0):
raise ValueError('static password requires YubiKey 2.0.0 or later')
@@ -231,14 +284,9 @@
'maximum of %d characters' % 38)
cmd = slot_to_cmd(slot)
- cfg = self._create_cfg(cmd)
- try:
+ with self._create_cfg(cmd, config) as cfg:
check(ykpers.ykp_set_cfgflag(cfg, 'SHORT_TICKET'))
-
- if append_cr:
- check(ykpers.ykp_set_tktflag(cfg, 'APPEND_CR'))
-
pw_bytes = encode(password, keyboard_layout=keyboard_layout)
if pw_len <= 16: # All in fixed
check(ykpers.ykp_set_fixed(cfg, pw_bytes, pw_len))
@@ -252,10 +300,8 @@
check(ykpers.yk_write_command(
self._dev, ykpers.ykp_core_config(cfg), cmd, self.access_code))
- finally:
- ykpers.ykp_free_config(cfg)
- def program_chalresp(self, slot, key, touch=False):
+ def program_chalresp(self, slot, key, touch=False, config=None):
if self._driver.version < (2, 2, 0):
raise ValueError('challenge-response requires YubiKey 2.2.0 or '
'later')
@@ -263,9 +309,9 @@
if len(key) > 20:
raise ValueError('key lengths >20 bytes not supported')
cmd = slot_to_cmd(slot)
- cfg = self._create_cfg(cmd)
key = key.ljust(20, b'\0') # Pad key to 20 bytes
- try:
+
+ with self._create_cfg(cmd, config) as cfg:
check(ykpers.ykp_set_tktflag(cfg, 'CHAL_RESP'))
check(ykpers.ykp_set_cfgflag(cfg, 'CHAL_HMAC'))
check(ykpers.ykp_set_cfgflag(cfg, 'HMAC_LT64'))
@@ -274,8 +320,6 @@
ykpers.ykp_HMAC_key_from_raw(cfg, key)
check(ykpers.yk_write_command(
self._dev, ykpers.ykp_core_config(cfg), cmd, self.access_code))
- finally:
- ykpers.ykp_free_config(cfg)
def calculate(
self, slot, challenge=None, totp=False,
@@ -323,7 +367,7 @@
else:
return b2a_hex(resp.raw[:20])
- def program_hotp(self, slot, key, imf=0, hotp8=False, append_cr=True):
+ def program_hotp(self, slot, key, imf=0, hotp8=False, config=None):
if self._driver.version < (2, 1, 0):
raise ValueError('HOTP requires YubiKey 2.1.0 or later')
key = hmac_shorten_key(key, 'SHA1')
@@ -333,20 +377,15 @@
if imf % 16 != 0:
raise ValueError('imf must be a multiple of 16')
cmd = slot_to_cmd(slot)
- cfg = self._create_cfg(cmd)
- try:
+ with self._create_cfg(cmd, config) as cfg:
check(ykpers.ykp_set_tktflag(cfg, 'OATH_HOTP'))
check(ykpers.ykp_set_oath_imf(cfg, imf))
if hotp8:
check(ykpers.ykp_set_cfgflag(cfg, 'OATH_HOTP8'))
- if append_cr:
- check(ykpers.ykp_set_tktflag(cfg, 'APPEND_CR'))
ykpers.ykp_HMAC_key_from_raw(cfg, key)
check(ykpers.yk_write_command(
self._dev, ykpers.ykp_core_config(cfg), cmd, self.access_code))
- finally:
- ykpers.ykp_free_config(cfg)
def zap_slot(self, slot):
check(ykpers.yk_write_command(self._dev, None, slot_to_cmd(slot),
@@ -355,12 +394,10 @@
def swap_slots(self):
if self._driver.version < (2, 3, 0):
raise ValueError('swapping slots requires YubiKey 2.3.0 or later')
- cfg = self._create_cfg(SLOT.SWAP)
- try:
+
+ with self._create_cfg(SLOT.SWAP) as cfg:
ycfg = ykpers.ykp_core_config(cfg)
check(ykpers.yk_write_command(self._dev, ycfg, SLOT.SWAP, None))
- finally:
- ykpers.ykp_free_config(cfg)
def configure_ndef_slot(self, slot, prefix='https://my.yubico.com/yk/#'):
ndef = ykpers.ykp_alloc_ndef()
@@ -389,8 +426,7 @@
'code when initially programming the slot instead.')
cmd = slot_to_cmd(slot, update)
- cfg = self._create_cfg(cmd)
- try:
+ with self._create_cfg(cmd) as cfg:
check(ykpers.ykp_set_access_code(
cfg, new_code or _RESET_ACCESS_CODE, _ACCESS_CODE_LENGTH))
ycfg = ykpers.ykp_core_config(cfg)
@@ -399,9 +435,6 @@
self.access_code = new_code
- finally:
- ykpers.ykp_free_config(cfg)
-
def delete_access_code(self, slot):
if self._has_update_access_code_bug:
raise ValueError(
@@ -411,26 +444,11 @@
self.set_access_code(slot, None)
- def update_settings(self, slot, enter=True, pacing=None):
+ def update_settings(self, slot, config=None):
cmd = slot_to_cmd(slot, update=True)
- cfg = self._create_cfg(cmd)
- if enter:
- check(ykpers.ykp_set_tktflag(cfg, 'APPEND_CR'))
-
- # Output speed throttling
- if pacing == 20:
- check(ykpers.ykp_set_cfgflag(cfg, 'PACING_10MS'))
- elif pacing == 40:
- check(ykpers.ykp_set_cfgflag(cfg, 'PACING_20MS'))
- elif pacing == 60:
- check(ykpers.ykp_set_cfgflag(cfg, 'PACING_10MS'))
- check(ykpers.ykp_set_cfgflag(cfg, 'PACING_20MS'))
-
- try:
+ with self._create_cfg(cmd, config) as cfg:
check(ykpers.yk_write_command(
self._dev, ykpers.ykp_core_config(cfg), cmd, self.access_code))
- finally:
- ykpers.ykp_free_config(cfg)
@property
def is_in_fips_mode(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/piv.py new/yubikey-manager-3.0.0/ykman/piv.py
--- old/yubikey-manager-2.1.1/ykman/piv.py 2019-04-09 09:39:55.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/piv.py 2019-06-24 09:07:27.000000000 +0200
@@ -846,10 +846,18 @@
).public_key(default_backend())
elif algorithm in [ALGO.ECCP256, ALGO.ECCP384]:
curve = ec.SECP256R1 if algorithm == ALGO.ECCP256 else ec.SECP384R1
- return ec.EllipticCurvePublicNumbers.from_encoded_point(
- curve(),
- resp[5:]
- ).public_key(default_backend())
+
+ try:
+ # Added in cryptography 2.5
+ return ec.EllipticCurvePublicKey.from_encoded_point(
+ curve(),
+ resp[5:]
+ )
+ except AttributeError:
+ return ec.EllipticCurvePublicNumbers.from_encoded_point(
+ curve(),
+ resp[5:]
+ ).public_key(default_backend())
raise UnsupportedAlgorithm(
'Invalid algorithm: {}'.format(algorithm),
@@ -949,7 +957,7 @@
raise KeypairMismatch(slot, certificate)
raise
- except InvalidSignature as e:
+ except InvalidSignature:
raise KeypairMismatch(slot, certificate)
self.put_data(OBJ.from_slot(slot), Tlv(TAG.CERTIFICATE, cert_data) +
@@ -1010,10 +1018,10 @@
def update_chuid(self):
# Non-Federal Issuer FASC-N
# [9999-9999-999999-0-1-0000000000300001]
- FASC_N=b'\xd4\xe7\x39\xda\x73\x9c\xed\x39\xce\x73\x9d\x83\x68' + \
- b'\x58\x21\x08\x42\x10\x84\x21\xc8\x42\x10\xc3\xeb'
+ FASC_N = b'\xd4\xe7\x39\xda\x73\x9c\xed\x39\xce\x73\x9d\x83\x68' + \
+ b'\x58\x21\x08\x42\x10\x84\x21\xc8\x42\x10\xc3\xeb'
# Expires on: 2030-01-01
- EXPIRY=b'\x32\x30\x33\x30\x30\x31\x30\x31'
+ EXPIRY = b'\x32\x30\x33\x30\x30\x31\x30\x31'
self.put_data(
OBJ.CHUID,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/ykman/util.py new/yubikey-manager-3.0.0/ykman/util.py
--- old/yubikey-manager-2.1.1/ykman/util.py 2019-05-27 10:21:11.000000000 +0200
+++ new/yubikey-manager-3.0.0/ykman/util.py 2019-06-24 09:07:27.000000000 +0200
@@ -111,6 +111,7 @@
USB_A_NANO = 0x02
USB_C_KEYCHAIN = 0x03
USB_C_NANO = 0x04
+ USB_C_LIGHTNING = 0x05
def __str__(self):
if self == FORM_FACTOR.USB_A_KEYCHAIN:
@@ -121,6 +122,8 @@
return 'Keychain (USB-C)'
elif self == FORM_FACTOR.USB_C_NANO:
return 'Nano (USB-C)'
+ elif self == FORM_FACTOR.USB_C_LIGHTNING:
+ return 'Keychain (USB-C, Lightning)'
elif self == FORM_FACTOR.UNKNOWN:
return 'Unknown.'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/yubikey_manager.egg-info/PKG-INFO new/yubikey-manager-3.0.0/yubikey_manager.egg-info/PKG-INFO
--- old/yubikey-manager-2.1.1/yubikey_manager.egg-info/PKG-INFO 2019-05-28 10:05:25.000000000 +0200
+++ new/yubikey-manager-3.0.0/yubikey_manager.egg-info/PKG-INFO 2019-06-24 09:12:28.000000000 +0200
@@ -1,13 +1,12 @@
-Metadata-Version: 1.2
+Metadata-Version: 1.1
Name: yubikey-manager
-Version: 2.1.1
+Version: 3.0.0
Summary: Tool for managing your YubiKey configuration.
Home-page: https://github.com/Yubico/yubikey-manager
-Author: Dain Nilsson
-Author-email: dain@yubico.com
-Maintainer: Yubico Open Source Maintainers
-Maintainer-email: ossmaint@yubico.com
+Author: Yubico Open Source Maintainers
+Author-email: ossmaint@yubico.com
License: BSD 2 clause
+Description-Content-Type: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: BSD License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yubikey-manager-2.1.1/yubikey_manager.egg-info/SOURCES.txt new/yubikey-manager-3.0.0/yubikey_manager.egg-info/SOURCES.txt
--- old/yubikey-manager-2.1.1/yubikey_manager.egg-info/SOURCES.txt 2019-05-28 10:05:25.000000000 +0200
+++ new/yubikey-manager-3.0.0/yubikey_manager.egg-info/SOURCES.txt 2019-06-24 09:12:28.000000000 +0200
@@ -14,6 +14,14 @@
test/test_scancodes.py
test/test_util.py
test/util.py
+test/__pycache__/__init__.cpython-36.pyc
+test/__pycache__/test_device.cpython-36.pyc
+test/__pycache__/test_external_libs.cpython-36.pyc
+test/__pycache__/test_oath.cpython-36.pyc
+test/__pycache__/test_piv.cpython-36.pyc
+test/__pycache__/test_scancodes.cpython-36.pyc
+test/__pycache__/test_util.cpython-36.pyc
+test/__pycache__/util.cpython-36.pyc
test/files/rsa_1024_key.pem
test/files/rsa_2048_cert.der
test/files/rsa_2048_cert.pem
@@ -32,6 +40,16 @@
test/on_yubikey/test_interfaces.py
test/on_yubikey/test_piv.py
test/on_yubikey/util.py
+test/on_yubikey/__pycache__/__init__.cpython-36.pyc
+test/on_yubikey/__pycache__/test_cli_config.cpython-36.pyc
+test/on_yubikey/__pycache__/test_cli_misc.cpython-36.pyc
+test/on_yubikey/__pycache__/test_cli_oath.cpython-36.pyc
+test/on_yubikey/__pycache__/test_cli_openpgp.cpython-36.pyc
+test/on_yubikey/__pycache__/test_cli_otp.cpython-36.pyc
+test/on_yubikey/__pycache__/test_fips_u2f_commands.cpython-36.pyc
+test/on_yubikey/__pycache__/test_interfaces.cpython-36.pyc
+test/on_yubikey/__pycache__/test_piv.cpython-36.pyc
+test/on_yubikey/__pycache__/util.cpython-36.pyc
test/on_yubikey/cli_piv/__init__.py
test/on_yubikey/cli_piv/test_fips.py
test/on_yubikey/cli_piv/test_generate_cert_and_csr.py
@@ -40,6 +58,14 @@
test/on_yubikey/cli_piv/test_misc.py
test/on_yubikey/cli_piv/test_pin_puk.py
test/on_yubikey/cli_piv/util.py
+test/on_yubikey/cli_piv/__pycache__/__init__.cpython-36.pyc
+test/on_yubikey/cli_piv/__pycache__/test_fips.cpython-36.pyc
+test/on_yubikey/cli_piv/__pycache__/test_generate_cert_and_csr.cpython-36.pyc
+test/on_yubikey/cli_piv/__pycache__/test_key_management.cpython-36.pyc
+test/on_yubikey/cli_piv/__pycache__/test_management_key.cpython-36.pyc
+test/on_yubikey/cli_piv/__pycache__/test_misc.cpython-36.pyc
+test/on_yubikey/cli_piv/__pycache__/test_pin_puk.cpython-36.pyc
+test/on_yubikey/cli_piv/__pycache__/util.cpython-36.pyc
ykman/VERSION
ykman/__init__.py
ykman/descriptor.py