Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package yubikey-manager for openSUSE:Factory
checked in at 2021-05-19 17:49:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yubikey-manager (Old)
and /work/SRC/openSUSE:Factory/.yubikey-manager.new.2988 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yubikey-manager"
Wed May 19 17:49:15 2021 rev:16 rq:894147 version:4.0.3
Changes:
--------
--- /work/SRC/openSUSE:Factory/yubikey-manager/yubikey-manager.changes
2021-05-10 15:41:27.241068886 +0200
+++
/work/SRC/openSUSE:Factory/.yubikey-manager.new.2988/yubikey-manager.changes
2021-05-19 17:49:27.225516110 +0200
@@ -1,0 +2,14 @@
+Tue May 18 18:39:36 UTC 2021 - Ferdinand Thiessen <[email protected]>
+
+- Update to version 4.0.3
+ * Add support for fido reset over NFC.
+ * Bugfix: The --touch argument to piv change-management-key was
+ ignored.
+ * Bugfix: Don???t prompt for password when importing PIV key/cert
+ if file is invalid.
+ * Bugfix: Fix setting touch-eject/auto-eject for YubiKey 4 and NEO.
+ * Bugfix: Detect PKCS#12 format when outer sequence uses
+ indefinite length.
+ * Dependency: Add support for Click 8.
+
+-------------------------------------------------------------------
Old:
----
yubikey-manager-4.0.2.tar.gz
yubikey-manager-4.0.2.tar.gz.sig
New:
----
yubikey-manager-4.0.3.tar.gz
yubikey-manager-4.0.3.tar.gz.sig
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ yubikey-manager.spec ++++++
--- /var/tmp/diff_new_pack.vYxNQv/_old 2021-05-19 17:49:27.773513812 +0200
+++ /var/tmp/diff_new_pack.vYxNQv/_new 2021-05-19 17:49:27.773513812 +0200
@@ -17,7 +17,7 @@
Name: yubikey-manager
-Version: 4.0.2
+Version: 4.0.3
Release: 0
Summary: Python 3 library and command line tool for configuring a
YubiKey
License: BSD-2-Clause
++++++ yubikey-manager-4.0.2.tar.gz -> yubikey-manager-4.0.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/NEWS
new/yubikey-manager-4.0.3/NEWS
--- old/yubikey-manager-4.0.2/NEWS 2021-04-12 09:23:45.807040200 +0200
+++ new/yubikey-manager-4.0.3/NEWS 2021-05-17 08:33:54.782452800 +0200
@@ -1,3 +1,11 @@
+* Version 4.0.3 (released 2021-05-17)
+ ** Add support for fido reset over NFC.
+ ** Bugfix: The --touch argument to piv change-management-key was ignored.
+ ** Bugfix: Don't prompt for password when importing PIV key/cert if file is
invalid.
+ ** Bugfix: Fix setting touch-eject/auto-eject for YubiKey 4 and NEO.
+ ** Bugfix: Detect PKCS#12 format when outer sequence uses indefinite length.
+ ** Dependency: Add support for Click 8.
+
* Version 4.0.2 (released 2021-04-12)
** Update device names.
** Add read_info output to the --diagnose command, and show exception types.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/PKG-INFO
new/yubikey-manager-4.0.3/PKG-INFO
--- old/yubikey-manager-4.0.2/PKG-INFO 2021-04-12 10:03:27.030768400 +0200
+++ new/yubikey-manager-4.0.3/PKG-INFO 2021-05-17 08:34:07.618134500 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: yubikey-manager
-Version: 4.0.2
+Version: 4.0.3
Summary: Tool for managing your YubiKey configuration.
Home-page: https://github.com/Yubico/yubikey-manager
License: BSD
@@ -18,7 +18,7 @@
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Utilities
-Requires-Dist: click (>=6.0,<8.0)
+Requires-Dist: click (>=6.0,<9.0)
Requires-Dist: cryptography (>=2.1,<4.0)
Requires-Dist: dataclasses (>=0.8,<0.9); python_version < "3.7"
Requires-Dist: fido2 (>=0.9,<1.0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/README.adoc
new/yubikey-manager-4.0.3/README.adoc
--- old/yubikey-manager-4.0.2/README.adoc 2021-04-12 09:23:08.058139000
+0200
+++ new/yubikey-manager-4.0.3/README.adoc 2021-05-17 08:33:03.311514600
+0200
@@ -78,7 +78,9 @@
==== Linux
Packages are available for several Linux distributions by third party package
maintainers.
-Yubico also provides packages for Ubuntu in the yubico/stable PPA:
+Yubico also provides packages for Ubuntu in the yubico/stable PPA (for amd64
+ONLY, other architectures such as arm should use the general `pip` instructions
+above instead):
$ sudo apt-add-repository ppa:yubico/stable
$ sudo apt update
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/man/ykman.1
new/yubikey-manager-4.0.3/man/ykman.1
--- old/yubikey-manager-4.0.2/man/ykman.1 2021-04-12 09:47:51.662626700
+0200
+++ new/yubikey-manager-4.0.3/man/ykman.1 2021-05-17 08:33:54.783446800
+0200
@@ -1,4 +1,4 @@
-.TH YKMAN "1" "April 2021" "ykman 4.0.2" "User Commands"
+.TH YKMAN "1" "May 2021" "ykman 4.0.3" "User Commands"
.SH NAME
ykman \- YubiKey Manager (ykman)
.SH SYNOPSIS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/pyproject.toml
new/yubikey-manager-4.0.3/pyproject.toml
--- old/yubikey-manager-4.0.2/pyproject.toml 2021-04-12 09:24:28.848675000
+0200
+++ new/yubikey-manager-4.0.3/pyproject.toml 2021-05-17 08:33:54.784445800
+0200
@@ -1,6 +1,6 @@
[tool.poetry]
name = "yubikey-manager"
-version = "4.0.2"
+version = "4.0.3"
description = "Tool for managing your YubiKey configuration."
authors = ["Dain Nilsson <[email protected]>"]
license = "BSD"
@@ -33,7 +33,7 @@
pyOpenSSL = {version = ">=0.15.1", optional = true}
pyscard = "^1.9 || ^2.0"
fido2 = ">=0.9, <1.0"
-click = "^6.0 || ^7.0"
+click = "^6.0 || ^7.0 || ^8.0"
pywin32 = {version = ">=223", platform = "win32"}
[tool.poetry.dev-dependencies]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/setup.py
new/yubikey-manager-4.0.3/setup.py
--- old/yubikey-manager-4.0.2/setup.py 2021-04-12 10:03:27.030369000 +0200
+++ new/yubikey-manager-4.0.3/setup.py 2021-05-17 08:34:07.617768500 +0200
@@ -14,7 +14,7 @@
{'': ['*']}
install_requires = \
-['click>=6.0,<8.0',
+['click>=6.0,<9.0',
'cryptography>=2.1,<4.0',
'fido2>=0.9,<1.0',
'pyscard>=1.9,<3.0']
@@ -28,7 +28,7 @@
setup_kwargs = {
'name': 'yubikey-manager',
- 'version': '4.0.2',
+ 'version': '4.0.3',
'description': 'Tool for managing your YubiKey configuration.',
'long_description': None,
'author': 'Dain Nilsson',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/tests/device/test_piv.py
new/yubikey-manager-4.0.3/tests/device/test_piv.py
--- old/yubikey-manager-4.0.2/tests/device/test_piv.py 2021-04-12
09:23:08.102491000 +0200
+++ new/yubikey-manager-4.0.3/tests/device/test_piv.py 2021-05-17
08:33:03.313509700 +0200
@@ -3,14 +3,16 @@
import pytest
from cryptography import x509
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.asymmetric import ec, padding
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
from yubikit.core import AID, NotSupportedError
from yubikit.core.smartcard import ApduError
from yubikit.management import CAPABILITY
from yubikit.piv import (
PivSession,
+ ALGORITHM,
KEY_TYPE,
PIN_POLICY,
TOUCH_POLICY,
@@ -27,6 +29,7 @@
pivman_set_mgm_key,
)
from ykman.util import parse_certificates, parse_private_key
+from ykman.device import is_fips_version
from ..util import open_file
from . import condition
@@ -95,6 +98,61 @@
return key
+def import_key(
+ session,
+ slot=SLOT.AUTHENTICATION,
+ key_type=KEY_TYPE.ECCP256,
+ pin_policy=PIN_POLICY.DEFAULT,
+):
+
+ if key_type.algorithm == ALGORITHM.RSA:
+ private_key = rsa.generate_private_key(
+ 65537, key_type.bit_len, default_backend()
+ )
+ elif key_type == KEY_TYPE.ECCP256:
+ private_key = ec.generate_private_key(ec.SECP256R1(),
default_backend())
+ elif key_type == KEY_TYPE.ECCP384:
+ private_key = ec.generate_private_key(ec.SECP384R1(),
default_backend())
+ session.authenticate(MANAGEMENT_KEY_TYPE.TDES, DEFAULT_MANAGEMENT_KEY)
+ session.put_key(slot, private_key, pin_policy)
+ reset_state(session)
+ return private_key.public_key()
+
+
+def verify_cert_signature(cert, public_key=None):
+ if not public_key:
+ public_key = cert.public_key
+ args = [cert.signature, cert.tbs_certificate_bytes,
cert.signature_hash_algorithm]
+ if KEY_TYPE.from_public_key(public_key).algorithm == ALGORITHM.RSA:
+ args.insert(2, padding.PKCS1v15())
+ else:
+ args[2] = ec.ECDSA(args[2])
+ public_key.verify(*args)
+
+
+class TestCertificateSignatures:
+ @pytest.mark.parametrize("key_type", list(KEY_TYPE))
+ @pytest.mark.parametrize(
+ "hash_algorithm", (hashes.SHA1, hashes.SHA256, hashes.SHA384,
hashes.SHA512)
+ )
+ def test_generate_self_signed_certificate(self, session, key_type,
hash_algorithm):
+ if key_type == KEY_TYPE.ECCP384 and session.version < (4, 0, 0):
+ pytest.skip("ECCP384 requires YubiKey 4 or later")
+ if key_type == KEY_TYPE.RSA1024 and is_fips_version(session.version):
+ pytest.skip("RSA1024 not available on YubiKey FIPS")
+
+ slot = SLOT.SIGNATURE
+ public_key = import_key(session, slot, key_type)
+ session.authenticate(MANAGEMENT_KEY_TYPE.TDES, DEFAULT_MANAGEMENT_KEY)
+ session.verify_pin(DEFAULT_PIN)
+ cert = generate_self_signed_certificate(
+ session, slot, public_key, "CN=alice", NOW, NOW, hash_algorithm
+ )
+
+ assert cert.public_key().public_numbers() ==
public_key.public_numbers()
+ verify_cert_signature(cert, public_key)
+
+
class TestKeyManagement:
def test_delete_certificate_requires_authentication(self, session):
generate_key(session, SLOT.AUTHENTICATION)
@@ -131,7 +189,8 @@
session, SLOT.AUTHENTICATION, public_key, "CN=alice", NOW, NOW
)
- def _test_generate_self_signed_certificate(self, session, slot):
+ @pytest.mark.parametrize("slot", (SLOT.SIGNATURE, SLOT.AUTHENTICATION))
+ def test_generate_self_signed_certificate(self, session, slot):
public_key = generate_key(session, slot)
session.authenticate(MANAGEMENT_KEY_TYPE.TDES, DEFAULT_MANAGEMENT_KEY)
session.verify_pin(DEFAULT_PIN)
@@ -145,12 +204,6 @@
== "alice"
)
- def test_generate_self_signed_certificate_slot_9a_works(self, session):
- self._test_generate_self_signed_certificate(session,
SLOT.AUTHENTICATION)
-
- def test_generate_self_signed_certificate_slot_9c_works(self, session):
- self._test_generate_self_signed_certificate(session, SLOT.SIGNATURE)
-
def test_generate_key_requires_authentication(self, session):
with pytest.raises(ApduError):
session.generate_key(
@@ -465,3 +518,92 @@
with pytest.raises(InvalidPinError) as ctx:
session.change_puk(NON_DEFAULT_PUK, DEFAULT_PUK)
assert ctx.value.attempts_remaining == puk_tries - 1
+
+
+class TestMetadata:
+ @pytest.fixture(autouse=True)
+ @condition.min_version(5, 3)
+ def preconditions(self):
+ pass
+
+ def test_pin_metadata(self, session):
+ data = session.get_pin_metadata()
+ assert data.default_value is True
+ assert data.total_attempts == 3
+ assert data.attempts_remaining == 3
+
+ def test_management_key_metadata(self, session):
+ data = session.get_management_key_metadata()
+ assert data.key_type == MANAGEMENT_KEY_TYPE.TDES
+ assert data.default_value is True
+ assert data.touch_policy is TOUCH_POLICY.NEVER
+
+ session.authenticate(MANAGEMENT_KEY_TYPE.TDES, DEFAULT_MANAGEMENT_KEY)
+ session.set_management_key(
+ MANAGEMENT_KEY_TYPE.AES192, NON_DEFAULT_MANAGEMENT_KEY
+ )
+ data = session.get_management_key_metadata()
+ assert data.key_type == MANAGEMENT_KEY_TYPE.AES192
+ assert data.default_value is False
+ assert data.touch_policy is TOUCH_POLICY.NEVER
+
+ session.set_management_key(MANAGEMENT_KEY_TYPE.TDES,
DEFAULT_MANAGEMENT_KEY)
+ data = session.get_management_key_metadata()
+ assert data.default_value is True
+
+ session.set_management_key(MANAGEMENT_KEY_TYPE.AES192,
DEFAULT_MANAGEMENT_KEY)
+ data = session.get_management_key_metadata()
+ assert data.default_value is False
+
+ @pytest.mark.parametrize("key_type", list(KEY_TYPE))
+ def test_slot_metadata_generate(self, session, key_type):
+ slot = SLOT.SIGNATURE
+ key = generate_key(session, slot, key_type)
+ data = session.get_slot_metadata(slot)
+
+ assert data.key_type == key_type
+ assert data.pin_policy == PIN_POLICY.ALWAYS
+ assert data.touch_policy == TOUCH_POLICY.NEVER
+ assert data.generated is True
+ assert data.public_key.public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
+ ) == key.public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
+ )
+
+ @pytest.mark.parametrize(
+ "key",
+ [
+ rsa.generate_private_key(65537, 1024, default_backend()),
+ rsa.generate_private_key(65537, 2048, default_backend()),
+ ec.generate_private_key(ec.SECP256R1(), default_backend()),
+ ec.generate_private_key(ec.SECP384R1(), default_backend()),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "slot, pin_policy",
+ [
+ (SLOT.AUTHENTICATION, PIN_POLICY.ONCE),
+ (SLOT.SIGNATURE, PIN_POLICY.ALWAYS),
+ (SLOT.KEY_MANAGEMENT, PIN_POLICY.ONCE),
+ (SLOT.CARD_AUTH, PIN_POLICY.NEVER),
+ ],
+ )
+ def test_slot_metadata_put(self, session, key, slot, pin_policy):
+ session.authenticate(MANAGEMENT_KEY_TYPE.TDES, DEFAULT_MANAGEMENT_KEY)
+ session.put_key(slot, key)
+ data = session.get_slot_metadata(slot)
+
+ assert data.key_type == KEY_TYPE.from_public_key(key.public_key())
+ assert data.pin_policy == pin_policy
+ assert data.touch_policy == TOUCH_POLICY.NEVER
+ assert data.generated is False
+ assert data.public_key.public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
+ ) == key.public_key().public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/tests/test_util.py
new/yubikey-manager-4.0.3/tests/test_util.py
--- old/yubikey-manager-4.0.2/tests/test_util.py 2021-04-12
09:23:08.116891900 +0200
+++ new/yubikey-manager-4.0.3/tests/test_util.py 2021-05-17
08:33:03.313509700 +0200
@@ -92,8 +92,6 @@
def test_is_pkcs12(self):
with self.assertRaises(TypeError):
- is_pkcs12("just a string")
- with self.assertRaises(TypeError):
is_pkcs12(None)
with open_file("rsa_2048_key.pem") as rsa_2048_key_pem:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/__init__.py
new/yubikey-manager-4.0.3/ykman/__init__.py
--- old/yubikey-manager-4.0.2/ykman/__init__.py 2021-04-12 09:24:20.479053300
+0200
+++ new/yubikey-manager-4.0.3/ykman/__init__.py 2021-05-17 08:33:54.786439400
+0200
@@ -35,4 +35,4 @@
)
-__version__ = "4.0.2"
+__version__ = "4.0.3"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/cli/__main__.py
new/yubikey-manager-4.0.3/ykman/cli/__main__.py
--- old/yubikey-manager-4.0.2/ykman/cli/__main__.py 2021-04-12
09:23:08.124955400 +0200
+++ new/yubikey-manager-4.0.3/ykman/cli/__main__.py 2021-05-17
08:33:03.314506500 +0200
@@ -41,6 +41,7 @@
list_all_devices,
scan_devices,
connect_to_device,
+ ConnectionNotAvailableException,
)
from ..util import get_windows_version
from ..diagnostics import get_diagnostics
@@ -85,6 +86,9 @@
while True:
try:
return connect_to_device(serial, connections)
+ except ConnectionNotAvailableException as e:
+ logger.error("Failed opening connection", exc_info=e)
+ raise # No need to retry
except Exception as e:
logger.error("Failed opening connection", exc_info=e)
while attempts:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/cli/config.py
new/yubikey-manager-4.0.3/ykman/cli/config.py
--- old/yubikey-manager-4.0.2/ykman/cli/config.py 2021-04-12
09:23:08.128411500 +0200
+++ new/yubikey-manager-4.0.3/ykman/cli/config.py 2021-05-17
08:33:03.315503600 +0200
@@ -205,14 +205,14 @@
"-e",
"--enable",
multiple=True,
- type=EnumChoice(CAPABILITY, hidden=[CAPABILITY.HSMAUTH]),
+ type=EnumChoice(CAPABILITY),
help="Enable applications.",
)
@click.option(
"-d",
"--disable",
multiple=True,
- type=EnumChoice(CAPABILITY, hidden=[CAPABILITY.HSMAUTH]),
+ type=EnumChoice(CAPABILITY),
help="Disable applications.",
)
@click.option(
@@ -377,14 +377,14 @@
"-e",
"--enable",
multiple=True,
- type=EnumChoice(CAPABILITY, hidden=[CAPABILITY.HSMAUTH]),
+ type=EnumChoice(CAPABILITY),
help="Enable applications.",
)
@click.option(
"-d",
"--disable",
multiple=True,
- type=EnumChoice(CAPABILITY, hidden=[CAPABILITY.HSMAUTH]),
+ type=EnumChoice(CAPABILITY),
help="Disable applications.",
)
@click.option("-a", "--enable-all", is_flag=True, help="Enable all
applications.")
@@ -607,9 +607,9 @@
else:
key_type = None
- if autoeject_timeout:
+ if autoeject_timeout: # autoeject implies touch eject
touch_eject = True
- autoeject = autoeject_timeout if touch_eject else 0
+ autoeject = autoeject_timeout if touch_eject else None
if mode.interfaces != USB_INTERFACE.CCID:
if touch_eject:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/cli/fido.py
new/yubikey-manager-4.0.3/ykman/cli/fido.py
--- old/yubikey-manager-4.0.2/ykman/cli/fido.py 2021-04-12 09:23:08.130139400
+0200
+++ new/yubikey-manager-4.0.3/ykman/cli/fido.py 2021-05-17 08:33:03.317499400
+0200
@@ -34,6 +34,7 @@
FPBioEnrollment,
CaptureError,
)
+from fido2.pcsc import CtapPcscDevice
from yubikit.core.fido import FidoConnection
from yubikit.core.smartcard import SW
from time import sleep
@@ -48,6 +49,8 @@
from ..fido import is_in_fips_mode, fips_reset, fips_change_pin,
fips_verify_pin
from ..hid import list_ctap_devices
from ..device import is_fips_version
+from ..pcsc import list_devices as list_ccid
+from smartcard.Exceptions import NoCardException, CardConnectionException
from typing import Optional
import click
@@ -157,11 +160,54 @@
inserted, and requires a touch on the YubiKey.
"""
- n_keys = len(list_ctap_devices())
- if n_keys > 1:
- cli_fail("Only one YubiKey can be connected to perform a reset.")
+ conn = ctx.obj["conn"]
+
+ if isinstance(conn, CtapPcscDevice): # NFC
+ readers = list_ccid(conn._name)
+ if not readers or readers[0].reader.name != conn._name:
+ logger.error(f"Multiple readers matched: {readers}")
+ cli_fail("Unable to isolate NFC reader.")
+ dev = readers[0]
+ logger.debug(f"use: {dev}")
+ is_fips = False
+
+ def prompt_re_insert():
+ click.echo(
+ "Remove and re-place your YubiKey on the NFC reader to perform
the "
+ "reset..."
+ )
- is_fips = is_fips_version(ctx.obj["info"].version)
+ removed = False
+ while True:
+ sleep(0.5)
+ try:
+ with dev.open_connection(FidoConnection):
+ if removed:
+ sleep(1.0) # Wait for the device to settle
+ break
+ except CardConnectionException:
+ pass # Expected, ignore
+ except NoCardException:
+ removed = True
+ return dev.open_connection(FidoConnection)
+
+ else: # USB
+ n_keys = len(list_ctap_devices())
+ if n_keys > 1:
+ cli_fail("Only one YubiKey can be connected to perform a reset.")
+ is_fips = is_fips_version(ctx.obj["info"].version)
+
+ def prompt_re_insert():
+ click.echo("Remove and re-insert your YubiKey to perform the
reset...")
+
+ removed = False
+ while True:
+ sleep(0.5)
+ keys = list_ctap_devices()
+ if not keys:
+ removed = True
+ if removed and len(keys) == 1:
+ return keys[0].open_connection(FidoConnection)
if not force:
if not click.confirm(
@@ -170,31 +216,7 @@
err=True,
):
ctx.abort()
-
- def prompt_re_insert_key():
- click.echo("Remove and re-insert your YubiKey to perform the reset...")
-
- removed = False
- while True:
- sleep(0.5)
- keys = list_ctap_devices()
- if not keys:
- removed = True
- if removed and len(keys) == 1:
- return keys[0]
-
- def try_reset():
- if not force:
- dev = prompt_re_insert_key()
- conn = dev.open_connection(FidoConnection)
- with prompt_timeout():
- if is_fips:
- fips_reset(conn)
- else:
- Ctap2(conn).reset()
-
- if is_fips:
- if not force:
+ if is_fips:
destroy_input = click_prompt(
"WARNING! This is a YubiKey FIPS device. This command will
also "
"overwrite the U2F attestation key; this action cannot be
undone and "
@@ -206,42 +228,39 @@
if destroy_input != "OVERWRITE":
cli_fail("Reset aborted by user.")
- try:
- try_reset()
-
- except ApduError as e:
- logger.error("Reset failed", exc_info=e)
- if e.code == SW.COMMAND_NOT_ALLOWED:
- cli_fail(
- "Reset failed. Reset must be triggered within 5 seconds
after the "
- "YubiKey is inserted."
- )
- else:
- cli_fail("Reset failed.")
+ conn = prompt_re_insert()
- except Exception as e:
- logger.error("Reset failed", exc_info=e)
- cli_fail("Reset failed.")
-
- else:
- try:
- try_reset()
- except CtapError as e:
- logger.error(e)
- if e.code == CtapError.ERR.ACTION_TIMEOUT:
- cli_fail(
- "Reset failed. You need to touch your YubiKey to confirm
the reset."
- )
- elif e.code == CtapError.ERR.NOT_ALLOWED:
- cli_fail(
- "Reset failed. Reset must be triggered within 5 seconds
after the "
- "YubiKey is inserted."
- )
+ try:
+ with prompt_timeout():
+ if is_fips:
+ fips_reset(conn)
else:
- cli_fail(f"Reset failed: {e.code.name}")
- except Exception as e:
- logger.error(e)
+ Ctap2(conn).reset()
+ except CtapError as e:
+ logger.error("Reset failed", exc_info=e)
+ if e.code == CtapError.ERR.ACTION_TIMEOUT:
+ cli_fail(
+ "Reset failed. You need to touch your YubiKey to confirm the
reset."
+ )
+ elif e.code in (CtapError.ERR.NOT_ALLOWED,
CtapError.ERR.PIN_AUTH_BLOCKED):
+ cli_fail(
+ "Reset failed. Reset must be triggered within 5 seconds after
the "
+ "YubiKey is inserted."
+ )
+ else:
+ cli_fail(f"Reset failed: {e.code.name}")
+ except ApduError as e: # From fips_reset
+ logger.error("Reset failed", exc_info=e)
+ if e.code == SW.COMMAND_NOT_ALLOWED:
+ cli_fail(
+ "Reset failed. Reset must be triggered within 5 seconds after
the "
+ "YubiKey is inserted."
+ )
+ else:
cli_fail("Reset failed.")
+ except Exception as e:
+ logger.error(e)
+ cli_fail("Reset failed.")
def _fail_pin_error(ctx, e, other="%s"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/cli/info.py
new/yubikey-manager-4.0.3/ykman/cli/info.py
--- old/yubikey-manager-4.0.2/ykman/cli/info.py 2021-04-12 09:23:08.131291400
+0200
+++ new/yubikey-manager-4.0.3/ykman/cli/info.py 2021-05-17 08:33:03.318670700
+0200
@@ -46,7 +46,7 @@
logger = logging.getLogger(__name__)
-SHOWN_CAPABILITIES = set(CAPABILITY) - {CAPABILITY.HSMAUTH}
+SHOWN_CAPABILITIES = set(CAPABILITY)
def print_app_status_table(supported_apps, enabled_apps):
@@ -142,7 +142,8 @@
@click.option(
"-c",
"--check-fips",
- help="Check if YubiKey is in FIPS Approved mode (YubiKey FIPS only).",
+ help="Check if YubiKey is in FIPS Approved mode (available on YubiKey 4
FIPS "
+ "only).",
is_flag=True,
)
@click.command()
@@ -202,4 +203,4 @@
ctx.obj["conn"].close()
_check_fips_status(pid, info)
else:
- cli_fail("Not a YubiKey FIPS")
+ cli_fail("Unable to check FIPS Approved mode - Not a YubiKey 4
FIPS")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/cli/otp.py
new/yubikey-manager-4.0.3/ykman/cli/otp.py
--- old/yubikey-manager-4.0.2/ykman/cli/otp.py 2021-04-12 09:23:08.134746800
+0200
+++ new/yubikey-manager-4.0.3/ykman/cli/otp.py 2021-05-17 08:33:03.319670700
+0200
@@ -348,8 +348,9 @@
if not public_id:
if serial_public_id:
- serial = session.get_serial()
- if serial is None:
+ try:
+ serial = session.get_serial()
+ except CommandError:
cli_fail("Serial number not set, public ID must be provided")
public_id = modhex_encode(b"\xff\x00" + struct.pack(b">I", serial))
click.echo(f"Using YubiKey serial as public ID: {public_id}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/cli/piv.py
new/yubikey-manager-4.0.3/ykman/cli/piv.py
--- old/yubikey-manager-4.0.2/ykman/cli/piv.py 2021-04-12 09:23:08.136475000
+0200
+++ new/yubikey-manager-4.0.3/ykman/cli/piv.py 2021-05-17 08:33:03.320667300
+0200
@@ -44,6 +44,7 @@
get_leaf_certificates,
parse_private_key,
parse_certificates,
+ InvalidPasswordError,
)
from ..piv import (
get_piv_info,
@@ -70,7 +71,7 @@
prompt_timeout,
EnumChoice,
)
-from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.backends import default_backend
import click
import datetime
@@ -118,6 +119,14 @@
raise ValueError(val)
+@click_callback()
+def click_parse_hash(ctx, param, val):
+ try:
+ return getattr(hashes, val)
+ except AttributeError:
+ raise ValueError(val)
+
+
click_slot_argument = click.argument("slot", callback=click_parse_piv_slot)
click_object_argument = click.argument(
"object_id", callback=click_parse_piv_object, metavar="OBJECT"
@@ -141,6 +150,15 @@
default=TOUCH_POLICY.DEFAULT.name,
help="Touch policy for slot.",
)
+click_hash_option = click.option(
+ "-a",
+ "--hash-algorithm",
+ type=click.Choice(["SHA1", "SHA256", "SHA384", "SHA512"],
case_sensitive=False),
+ default="SHA256",
+ show_default=True,
+ help="Hash algorithm.",
+ callback=click_parse_hash,
+)
@ykman_group(SmartCardConnection)
@@ -575,7 +593,6 @@
PRIVATE-KEY File containing the private key. Use '-' to use stdin.
"""
session = ctx.obj["session"]
- _ensure_authenticated(ctx, pin, management_key)
data = private_key.read()
@@ -584,7 +601,8 @@
password = password.encode()
try:
private_key = parse_private_key(data, password)
- except (ValueError, TypeError):
+ except InvalidPasswordError as e:
+ logger.error("Error parsing key", exc_info=e)
if password is None:
password = click_prompt(
"Enter password to decrypt key",
@@ -599,6 +617,7 @@
continue
break
+ _ensure_authenticated(ctx, pin, management_key)
session.put_key(slot, private_key, pin_policy, touch_policy)
@@ -726,7 +745,6 @@
CERTIFICATE File containing the certificate. Use '-' to use stdin.
"""
session = ctx.obj["session"]
- _ensure_authenticated(ctx, pin, management_key)
data = cert.read()
@@ -735,7 +753,8 @@
password = password.encode()
try:
certs = parse_certificates(data, password)
- except (ValueError, TypeError):
+ except InvalidPasswordError as e:
+ logger.error("Error parsing certificate", exc_info=e)
if password is None:
password = click_prompt(
"Enter password to decrypt certificate",
@@ -758,6 +777,8 @@
else:
cert_to_import = certs[0]
+ _ensure_authenticated(ctx, pin, management_key)
+
if verify:
def do_verify():
@@ -820,8 +841,9 @@
default=365,
show_default=True,
)
+@click_hash_option
def generate_certificate(
- ctx, management_key, pin, slot, public_key, subject, valid_days
+ ctx, management_key, pin, slot, public_key, subject, valid_days,
hash_algorithm
):
"""
Generate a self-signed X.509 certificate.
@@ -849,7 +871,7 @@
try:
with prompt_timeout():
cert = generate_self_signed_certificate(
- session, slot, public_key, subject, now, valid_to
+ session, slot, public_key, subject, now, valid_to,
hash_algorithm
)
session.put_certificate(slot, cert)
session.put_object(OBJECT_ID.CHUID, generate_chuid())
@@ -870,8 +892,9 @@
help="Subject for the requested certificate, as an RFC 4514 string.",
required=True,
)
+@click_hash_option
def generate_certificate_signing_request(
- ctx, pin, slot, public_key, csr_output, subject
+ ctx, pin, slot, public_key, csr_output, subject, hash_algorithm
):
"""
Generate a Certificate Signing Request (CSR).
@@ -896,7 +919,7 @@
try:
with prompt_timeout():
- csr = generate_csr(session, slot, public_key, subject)
+ csr = generate_csr(session, slot, public_key, subject,
hash_algorithm)
except ApduError:
cli_fail("Certificate Signing Request generation failed.")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/cli/util.py
new/yubikey-manager-4.0.3/ykman/cli/util.py
--- old/yubikey-manager-4.0.2/ykman/cli/util.py 2021-04-12 09:23:08.137052000
+0200
+++ new/yubikey-manager-4.0.3/ykman/cli/util.py 2021-05-17 08:33:03.321666500
+0200
@@ -55,7 +55,9 @@
self.choices_enum = choices_enum
def convert(self, value, param, ctx):
- name = super(EnumChoice, self).convert(value, param, ctx).replace("-",
"_")
+ if isinstance(value, self.choices_enum):
+ return value
+ name = super().convert(value, param, ctx).replace("-", "_")
return self.choices_enum[name]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/device.py
new/yubikey-manager-4.0.3/ykman/device.py
--- old/yubikey-manager-4.0.2/ykman/device.py 2021-04-12 09:23:08.138203000
+0200
+++ new/yubikey-manager-4.0.3/ykman/device.py 2021-05-17 08:33:03.322661000
+0200
@@ -50,7 +50,10 @@
)
from yubikit.yubiotp import YubiOtpSession
from .base import PID, YUBIKEY, YkmanDevice
-from .hid import list_otp_devices, list_ctap_devices
+from .hid import (
+ list_otp_devices as _list_otp_devices,
+ list_ctap_devices as _list_ctap_devices,
+)
from .pcsc import list_devices as _list_ccid_devices
from smartcard.pcsc.PCSCExceptions import EstablishContextException
from smartcard.Exceptions import NoCardException
@@ -65,22 +68,48 @@
logger = logging.getLogger(__name__)
-_pcsc_missing = False
+class ConnectionNotAvailableException(ValueError):
+ def __init__(self, connection_types):
+ super().__init__(
+ f"No eligiable connections are available ({connection_types})."
+ )
+ self.connection_types = connection_types
+
+
+def _warn_once(message, e_type=Exception):
+ warned: List[bool] = []
+
+ def outer(f):
+ def inner():
+ try:
+ return f()
+ except e_type:
+ if not warned:
+ print("WARNING:", message, file=sys.stderr)
+ warned.append(True)
+ raise
+ return inner
+ return outer
+
+
+@_warn_once(
+ "PC/SC not available. Smart card protocols will not function.",
+ EstablishContextException,
+)
def list_ccid_devices():
- try:
- return _list_ccid_devices()
- except Exception as e:
- global _pcsc_missing
- if not _pcsc_missing and isinstance(e, EstablishContextException):
- _pcsc_missing = True
- print(
- "WARNING: PCSC not available. Smart card protocols will not
function.",
- file=sys.stderr,
- )
- logger.error("Unable to list CCID devices", exc_info=e)
- return []
+ return _list_ccid_devices()
+
+
+@_warn_once("No CTAP HID backend available. FIDO protocols will not function.")
+def list_ctap_devices():
+ return _list_ctap_devices()
+
+
+@_warn_once("No OTP HID backend available. OTP protocols will not function.")
+def list_otp_devices():
+ return _list_otp_devices()
def is_fips_version(version: Version) -> bool:
@@ -106,7 +135,11 @@
fingerprints = set()
merged: Dict[PID, int] = {}
for list_devs in CONNECTION_LIST_MAPPING.values():
- devs = list_devs()
+ try:
+ devs = list_devs()
+ except Exception as e:
+ logger.error("Unable to list devices for connection", exc_info=e)
+ devs = []
merged.update(Counter(d.pid for d in devs if d.pid is not None))
fingerprints.update({d.fingerprint for d in devs})
if sys.platform == "win32" and not
bool(ctypes.windll.shell32.IsUserAnAdmin()):
@@ -134,7 +167,13 @@
devices = []
for connection_type, list_devs in CONNECTION_LIST_MAPPING.items():
- for dev in list_devs():
+ try:
+ devs = list_devs()
+ except Exception as e:
+ logger.error("Unable to list devices for connection", exc_info=e)
+ devs = []
+
+ for dev in devs:
if dev.pid not in handled_pids and pids.get(dev.pid, True):
try:
with dev.open_connection(connection_type) as conn:
@@ -160,9 +199,19 @@
:return: An open connection to the device, the device reference, and the
device
information read from the device.
"""
+ failed_connections = set()
retry_ccid = []
for connection_type in connection_types:
- for dev in CONNECTION_LIST_MAPPING[connection_type]():
+ try:
+ devs = CONNECTION_LIST_MAPPING[connection_type]()
+ except Exception as e:
+ logger.error(
+ f"Error listing connection of type {connection_type}",
exc_info=e
+ )
+ failed_connections.add(connection_type)
+ continue
+
+ for dev in devs:
try:
conn = dev.open_connection(connection_type)
except NoCardException:
@@ -175,6 +224,9 @@
else:
return conn, dev, info
+ if set(connection_types) == failed_connections:
+ raise ConnectionNotAvailableException(connection_types)
+
# NEO ejects the card when other interfaces are used, and returns it after
~3s.
for _ in range(6):
if not retry_ccid:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/diagnostics.py
new/yubikey-manager-4.0.3/ykman/diagnostics.py
--- old/yubikey-manager-4.0.2/ykman/diagnostics.py 2021-04-12
09:23:08.139355700 +0200
+++ new/yubikey-manager-4.0.3/ykman/diagnostics.py 2021-05-17
08:33:03.323658000 +0200
@@ -2,6 +2,9 @@
from .logging_setup import log_sys_info
from .pcsc import list_readers, list_devices as list_ccid_devices
from .hid import list_otp_devices, list_ctap_devices
+from .device import read_info, get_name
+from .piv import get_piv_info
+from .openpgp import OpenPgpController, get_openpgp_info
from yubikit.core.smartcard import SmartCardConnection
from yubikit.core.fido import FidoConnection
@@ -10,9 +13,6 @@
from yubikit.yubiotp import YubiOtpSession
from yubikit.piv import PivSession
from yubikit.oath import OathSession
-from ykman.device import read_info, get_name
-from ykman.piv import get_piv_info
-from ykman.openpgp import OpenPgpController, get_openpgp_info
from fido2.ctap import CtapError
from fido2.ctap2 import Ctap2, ClientPin
@@ -86,17 +86,23 @@
]
lines.append("Detected YubiKeys over PC/SC:")
- for dev in list_ccid_devices():
- lines.append(f"\t{dev!r}")
- try:
- with dev.open_connection(SmartCardConnection) as conn:
- lines.extend(mgmt_info(dev.pid, conn))
- lines.extend(piv_info(conn))
- lines.extend(oath_info(conn))
- lines.extend(openpgp_info(conn))
- except Exception as e:
- lines.append(f"\tPC/SC connection failure: {e!r}")
- lines.append("")
+ try:
+ for dev in list_ccid_devices():
+ lines.append(f"\t{dev!r}")
+ try:
+ with dev.open_connection(SmartCardConnection) as conn:
+ lines.extend(mgmt_info(dev.pid, conn))
+ lines.extend(piv_info(conn))
+ lines.extend(oath_info(conn))
+ lines.extend(openpgp_info(conn))
+ except Exception as e:
+ lines.append(f"\tPC/SC connection failure: {e!r}")
+ lines.append("")
+ except Exception as e:
+ return [
+ f"PC/SC failure: {e!r}",
+ "",
+ ]
lines.append("")
return lines
@@ -105,20 +111,23 @@
def otp_info():
lines = []
lines.append("Detected YubiKeys over HID OTP:")
- for dev in list_otp_devices():
- lines.append(f"\t{dev!r}")
- try:
- with dev.open_connection(OtpConnection) as conn:
- lines.extend(mgmt_info(dev.pid, conn))
- otp = YubiOtpSession(conn)
- try:
- config = otp.get_config_state()
- lines.append(f"\tOTP: {config!r}")
- except ValueError as e:
- lines.append(f"\tCouldn't read OTP state: {e!r}")
- except Exception as e:
- lines.append(f"\tOTP connection failure: {e!r}")
- lines.append("")
+ try:
+ for dev in list_otp_devices():
+ lines.append(f"\t{dev!r}")
+ try:
+ with dev.open_connection(OtpConnection) as conn:
+ lines.extend(mgmt_info(dev.pid, conn))
+ otp = YubiOtpSession(conn)
+ try:
+ config = otp.get_config_state()
+ lines.append(f"\tOTP: {config!r}")
+ except ValueError as e:
+ lines.append(f"\tCouldn't read OTP state: {e!r}")
+ except Exception as e:
+ lines.append(f"\tOTP connection failure: {e!r}")
+ lines.append("")
+ except Exception as e:
+ lines.append(f"\tHID OTP backend failure: {e!r}")
lines.append("")
return lines
@@ -126,35 +135,39 @@
def fido_info():
lines = []
lines.append("Detected YubiKeys over HID FIDO:")
- for dev in list_ctap_devices():
- lines.append(f"\t{dev!r}")
- try:
- with dev.open_connection(FidoConnection) as conn:
- lines.append("CTAP device version: %d.%d.%d" %
conn.device_version)
- lines.append(f"CTAPHID protocol version: {conn.version}")
- lines.append("Capabilities: %d" % conn.capabilities)
- lines.extend(mgmt_info(dev.pid, conn))
- try:
- ctap2 = Ctap2(conn)
- lines.append(f"\tCtap2Info: {ctap2.info.data!r}")
- if ctap2.info.options.get("clientPin"):
- client_pin = ClientPin(ctap2)
- lines.append(f"PIN retries:
{client_pin.get_pin_retries()}")
- bio_enroll = ctap2.info.options.get("bioEnroll")
- if bio_enroll:
- lines.append(
- f"Fingerprint retries:
{client_pin.get_uv_retries()}"
- )
- elif bio_enroll is False:
- lines.append("Fingerprints: Not configured")
- else:
- lines.append("PIN: Not configured")
-
- except (ValueError, CtapError) as e:
- lines.append(f"\tCouldn't get info: {e!r}")
- except Exception as e:
- lines.append(f"\tFIDO connection failure: {e!r}")
- lines.append("")
+ try:
+ for dev in list_ctap_devices():
+ lines.append(f"\t{dev!r}")
+ try:
+ with dev.open_connection(FidoConnection) as conn:
+ lines.append("CTAP device version: %d.%d.%d" %
conn.device_version)
+ lines.append(f"CTAPHID protocol version: {conn.version}")
+ lines.append("Capabilities: %d" % conn.capabilities)
+ lines.extend(mgmt_info(dev.pid, conn))
+ try:
+ ctap2 = Ctap2(conn)
+ lines.append(f"\tCtap2Info: {ctap2.info.data!r}")
+ if ctap2.info.options.get("clientPin"):
+ client_pin = ClientPin(ctap2)
+ lines.append(f"PIN retries:
{client_pin.get_pin_retries()}")
+ bio_enroll = ctap2.info.options.get("bioEnroll")
+ if bio_enroll:
+ lines.append(
+ "Fingerprint retries: "
+ f"{client_pin.get_uv_retries()}"
+ )
+ elif bio_enroll is False:
+ lines.append("Fingerprints: Not configured")
+ else:
+ lines.append("PIN: Not configured")
+
+ except (ValueError, CtapError) as e:
+ lines.append(f"\tCouldn't get info: {e!r}")
+ except Exception as e:
+ lines.append(f"\tFIDO connection failure: {e!r}")
+ lines.append("")
+ except Exception as e:
+ lines.append(f"\tHID FIDO backend failure: {e!r}")
return lines
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/hid/__init__.py
new/yubikey-manager-4.0.3/ykman/hid/__init__.py
--- old/yubikey-manager-4.0.2/ykman/hid/__init__.py 2021-04-12
09:23:08.142234800 +0200
+++ new/yubikey-manager-4.0.3/ykman/hid/__init__.py 2021-05-17
08:33:03.324655300 +0200
@@ -28,7 +28,6 @@
from ..base import YkmanDevice, PID
from .base import OtpYubiKeyDevice
from yubikit.core import TRANSPORT
-from fido2.hid import list_descriptors, open_connection, CtapHidDevice
from typing import List, Callable
import sys
import logging
@@ -43,36 +42,59 @@
elif sys.platform.startswith("darwin"):
from . import macos as backend
else:
- raise Exception("Unsupported platform")
+ class backend:
+ @staticmethod
+ def list_devices():
+ raise NotImplementedError(
+ "OTP HID support is not implemented on this platform"
+ )
-list_otp_devices: Callable[[], List[OtpYubiKeyDevice]] = backend.list_devices
+list_otp_devices: Callable[[], List[OtpYubiKeyDevice]] = backend.list_devices
-class CtapYubiKeyDevice(YkmanDevice):
- """YubiKey FIDO USB HID device"""
- def __init__(self, descriptor):
- super(CtapYubiKeyDevice, self).__init__(
- TRANSPORT.USB, descriptor.path, PID(descriptor.pid)
- )
- self.descriptor = descriptor
+try:
+ from fido2.hid import list_descriptors, open_connection, CtapHidDevice
- def supports_connection(self, connection_type):
- return issubclass(CtapHidDevice, connection_type)
+ class CtapYubiKeyDevice(YkmanDevice):
+ """YubiKey FIDO USB HID device"""
- def open_connection(self, connection_type):
- if self.supports_connection(connection_type):
- return CtapHidDevice(self.descriptor,
open_connection(self.descriptor))
- return super(CtapYubiKeyDevice, self).open_connection(connection_type)
-
-
-def list_ctap_devices() -> List[CtapYubiKeyDevice]:
- devs = []
- for desc in list_descriptors():
- if desc.vid == 0x1050:
- try:
- devs.append(CtapYubiKeyDevice(desc))
- except ValueError:
- logger.debug(f"Unsupported Yubico device with PID:
{desc.pid:02x}")
- return devs
+ def __init__(self, descriptor):
+ super(CtapYubiKeyDevice, self).__init__(
+ TRANSPORT.USB, descriptor.path, PID(descriptor.pid)
+ )
+ self.descriptor = descriptor
+
+ def supports_connection(self, connection_type):
+ return issubclass(CtapHidDevice, connection_type)
+
+ def open_connection(self, connection_type):
+ if self.supports_connection(connection_type):
+ return CtapHidDevice(self.descriptor,
open_connection(self.descriptor))
+ return super(CtapYubiKeyDevice,
self).open_connection(connection_type)
+
+ def list_ctap_devices() -> List[CtapYubiKeyDevice]:
+ devs = []
+ for desc in list_descriptors():
+ if desc.vid == 0x1050:
+ try:
+ devs.append(CtapYubiKeyDevice(desc))
+ except ValueError:
+ logger.debug(f"Unsupported Yubico device with PID:
{desc.pid:02x}")
+ return devs
+
+
+except Exception:
+ # CTAP not supported on this platform
+
+ class CtapYubiKeyDevice(YkmanDevice): # type: ignore
+ def __init__(self, *args, **kwargs):
+ raise NotImplementedError(
+ "CTAP HID support is not implemented on this platform"
+ )
+
+ def list_ctap_devices() -> List[CtapYubiKeyDevice]:
+ raise NotImplementedError(
+ "CTAP HID support is not implemented on this platform"
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/logging_setup.py
new/yubikey-manager-4.0.3/ykman/logging_setup.py
--- old/yubikey-manager-4.0.2/ykman/logging_setup.py 2021-04-12
09:23:08.147425200 +0200
+++ new/yubikey-manager-4.0.3/ykman/logging_setup.py 2021-05-17
08:33:03.325653000 +0200
@@ -27,6 +27,7 @@
from ykman import __version__ as ykman_version
from ykman.util import get_windows_version
+import platform
import logging
import ctypes
import sys
@@ -46,6 +47,7 @@
def log_sys_info(log):
log(f"Python: {sys.version}")
log(f"Platform: {sys.platform}")
+ log(f"Arch: {platform.machine()}")
if sys.platform == "win32":
log(f"Windows version: {get_windows_version()}")
is_admin = bool(ctypes.windll.shell32.IsUserAnAdmin())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/piv.py
new/yubikey-manager-4.0.3/ykman/piv.py
--- old/yubikey-manager-4.0.2/ykman/piv.py 2021-04-12 09:23:08.152604000
+0200
+++ new/yubikey-manager-4.0.3/ykman/piv.py 2021-05-17 08:33:03.326650400
+0200
@@ -52,7 +52,7 @@
import struct
import os
-from typing import Union, Mapping, Optional, List, cast
+from typing import Union, Mapping, Optional, List, Type, cast
logger = logging.getLogger(__name__)
@@ -273,7 +273,7 @@
raise
# Set the new management key
- session.set_management_key(algorithm, new_key)
+ session.set_management_key(algorithm, new_key, touch)
if pivman.has_derived_key:
# Clear salt for old derived keys.
@@ -529,16 +529,17 @@
slot: SLOT,
key_type: KEY_TYPE,
builder: x509.CertificateBuilder,
+ hash_algorithm: Type[hashes.HashAlgorithm] = hashes.SHA256,
) -> x509.Certificate:
"""Sign a Certificate."""
dummy_key = _dummy_key(key_type)
- cert = builder.sign(dummy_key, hashes.SHA256(), default_backend())
+ cert = builder.sign(dummy_key, hash_algorithm(), default_backend())
sig = session.sign(
slot,
key_type,
cert.tbs_certificate_bytes,
- hashes.SHA256(),
+ hash_algorithm(),
padding.PKCS1v15(), # Only used for RSA
)
@@ -556,11 +557,12 @@
slot: SLOT,
public_key: Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey],
builder: x509.CertificateSigningRequestBuilder,
+ hash_algorithm: Type[hashes.HashAlgorithm] = hashes.SHA256,
) -> x509.CertificateSigningRequest:
"""Sign a CSR."""
key_type = KEY_TYPE.from_public_key(public_key)
dummy_key = _dummy_key(key_type)
- csr = builder.sign(dummy_key, hashes.SHA256(), default_backend())
+ csr = builder.sign(dummy_key, hash_algorithm(), default_backend())
seq = Tlv.parse_list(Tlv.unpack(0x30, csr.public_bytes(Encoding.DER)))
# Replace public key
@@ -577,7 +579,7 @@
slot,
key_type,
seq[0],
- hashes.SHA256(),
+ hash_algorithm(),
padding.PKCS1v15(), # Only used for RSA
)
@@ -596,6 +598,7 @@
subject_str: str,
valid_from: datetime,
valid_to: datetime,
+ hash_algorithm: Type[hashes.HashAlgorithm] = hashes.SHA256,
) -> x509.Certificate:
"""Generate a self-signed certificate using a private key in a slot."""
key_type = KEY_TYPE.from_public_key(public_key)
@@ -612,7 +615,9 @@
)
try:
- return sign_certificate_builder(session, slot, key_type, builder)
+ return sign_certificate_builder(
+ session, slot, key_type, builder, hash_algorithm
+ )
except ApduError as e:
logger.error("Failed to generate certificate for slot %s", slot,
exc_info=e)
raise
@@ -623,6 +628,7 @@
slot: SLOT,
public_key: Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey],
subject_str: str,
+ hash_algorithm: Type[hashes.HashAlgorithm] = hashes.SHA256,
) -> x509.CertificateSigningRequest:
"""Generate a CSR using a private key in a slot."""
builder = x509.CertificateSigningRequestBuilder().subject_name(
@@ -630,7 +636,7 @@
)
try:
- return sign_csr_builder(session, slot, public_key, builder)
+ return sign_csr_builder(session, slot, public_key, builder,
hash_algorithm)
except ApduError as e:
logger.error(
"Failed to generate Certificate Signing Request for slot %s",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/ykman/util.py
new/yubikey-manager-4.0.3/ykman/util.py
--- old/yubikey-manager-4.0.2/ykman/util.py 2021-04-12 09:23:08.162971000
+0200
+++ new/yubikey-manager-4.0.3/ykman/util.py 2021-05-17 08:33:03.326650400
+0200
@@ -42,9 +42,18 @@
PEM_IDENTIFIER = b"-----BEGIN"
+class InvalidPasswordError(Exception):
+ """Raised when parsing key/certificate and the password might be
wrong/missing."""
+
+
def _parse_pkcs12_cryptography(pkcs12, data, password):
- key, cert, cas = pkcs12.load_key_and_certificates(data, password,
default_backend())
- return key, [cert] + cas
+ try:
+ key, cert, cas = pkcs12.load_key_and_certificates(
+ data, password, default_backend()
+ )
+ return key, [cert] + cas
+ except ValueError as e: # cryptography raises ValueError on wrong password
+ raise InvalidPasswordError(e)
def _parse_pkcs12_pyopenssl(crypto, data, password):
@@ -68,7 +77,7 @@
]
return key, certs
except crypto.Error as e:
- raise ValueError(e)
+ raise InvalidPasswordError(e)
def _parse_pkcs12_unsupported(data, password):
@@ -96,14 +105,14 @@
if is_pem(data):
if b"ENCRYPTED" in data:
if password is None:
- raise TypeError("No password provided for encrypted key.")
+ raise InvalidPasswordError("No password provided for encrypted
key.")
try:
return serialization.load_pem_private_key(
data, password, backend=default_backend()
)
- except ValueError:
+ except ValueError as e:
# Cryptography raises ValueError if decryption fails.
- raise
+ raise InvalidPasswordError(e)
except Exception as e:
logger.debug("Failed to parse PEM private key ", exc_info=e)
@@ -142,8 +151,9 @@
except Exception as e:
logger.debug("Failed to parse PEM certificate", exc_info=e)
# Could be valid PEM but not certificates.
- if len(certs) > 0:
- return certs
+ if not certs:
+ raise ValueError("PEM file does not contain any certificate(s)")
+ return certs
# PKCS12
if is_pkcs12(data):
@@ -178,7 +188,7 @@
def is_pem(data):
- return PEM_IDENTIFIER in data if data else False
+ return data and PEM_IDENTIFIER in data
def is_pkcs12(data):
@@ -188,10 +198,11 @@
See: https://tools.ietf.org/html/rfc7292.
"""
try:
- header = Tlv.parse_list(Tlv.unpack(0x30, data))[0]
+ header = Tlv.parse_from(Tlv.unpack(0x30, data))[0]
return header.tag == 0x02 and header.value == b"\x03"
- except ValueError:
- return False
+ except ValueError as e:
+ logger.debug("Unable to parse TLV", exc_info=e)
+ return False
class OSVERSIONINFOW(ctypes.Structure):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/yubikit/core/__init__.py
new/yubikey-manager-4.0.3/yubikit/core/__init__.py
--- old/yubikey-manager-4.0.2/yubikit/core/__init__.py 2021-04-12
09:23:08.165852000 +0200
+++ new/yubikey-manager-4.0.3/yubikit/core/__init__.py 2021-05-17
08:33:03.328645000 +0200
@@ -183,27 +183,36 @@
return int.from_bytes(data, "big")
-def _tlv_parse(data):
+def _tlv_parse(data, offset=0):
try:
- tag, rest = data[0], data[1:]
+ tag = data[offset]
+ offset += 1
if tag & 0x1F == 0x1F: # Long form
- tag, rest = tag << 8 | rest[0], rest[1:]
+ tag = tag << 8 | data[offset]
+ offset += 1
while tag & 0x80 == 0x80: # Additional bytes
- tag, rest = tag << 8 | rest[0], rest[1:]
+ tag = tag << 8 | data[offset]
+ offset += 1
- ln, rest = rest[0], rest[1:]
- if ln == 0x80:
- raise ValueError("Indefinite length not supported")
- if ln > 0x80:
- n_bytes = ln - 0x80
- ln, rest = bytes2int(rest[:n_bytes]), rest[n_bytes:]
+ ln = data[offset]
+ offset += 1
+ if ln == 0x80: # Indefinite length
+ end = offset
+ while data[end] or data[end + 1]: # Run until 0x0000
+ end = _tlv_parse(data, end)[3] # Skip over TLV
+ ln = end - offset
+ end += 2 # End after 0x0000
+ else:
+ if ln > 0x80: # Length spans multiple bytes
+ n_bytes = ln - 0x80
+ ln = bytes2int(data[offset : offset + n_bytes])
+ offset += n_bytes
+ end = offset + ln
- value, rest = rest[:ln], rest[ln:]
+ return tag, offset, ln, end
except IndexError:
raise ValueError("Invalid encoding of tag/length")
- return tag, ln, value, rest
-
T_Tlv = TypeVar("T_Tlv", bound="Tlv")
@@ -215,11 +224,11 @@
@property
def length(self) -> int:
- return len(self) - self._value_offset
+ return self._value_ln
@property
def value(self) -> bytes:
- return self[self._value_offset :]
+ return self[self._value_offset : self._value_offset + self._value_ln]
def __new__(cls, tag_or_data: Union[int, bytes], value: Optional[bytes] =
None):
"""This allows creation by passing either binary data, or tag and
value."""
@@ -248,18 +257,17 @@
return super(Tlv, cls).__new__(cls, data) # type: ignore
def __init__(self, tag_or_data: Union[int, bytes], value: Optional[bytes]
= None):
- self._tag, ln, value, rest = _tlv_parse(self)
- if rest:
+ self._tag, self._value_offset, self._value_ln, end = _tlv_parse(self)
+ if len(self) != end:
raise ValueError("Incorrect TLV length")
- self._value_offset = len(self) - ln
def __repr__(self):
return f"Tlv(tag=0x{self.tag:02x}, value={self.value.hex()})"
@classmethod
def parse_from(cls: Type[T_Tlv], data: bytes) -> Tuple[T_Tlv, bytes]:
- tag, ln, value, rest = _tlv_parse(data)
- return cls(data[: len(data) - len(rest)]), rest
+ tag, offs, ln, end = _tlv_parse(data)
+ return cls(data[:end]), data[end:]
@classmethod
def parse_list(cls: Type[T_Tlv], data: bytes) -> List[T_Tlv]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey-manager-4.0.2/yubikit/management.py
new/yubikey-manager-4.0.3/yubikit/management.py
--- old/yubikey-manager-4.0.2/yubikit/management.py 2021-04-12
09:23:08.170460200 +0200
+++ new/yubikey-manager-4.0.3/yubikit/management.py 2021-05-17
08:33:03.328645000 +0200
@@ -441,7 +441,10 @@
)
def set_mode(
- self, mode: Mode, chalresp_timeout: int = 0, auto_eject_timeout: int = 0
+ self,
+ mode: Mode,
+ chalresp_timeout: int = 0,
+ auto_eject_timeout: Optional[int] = None,
) -> None:
if self.version >= (5, 0, 0):
# Translate into DeviceConfig
@@ -461,6 +464,13 @@
)
)
else:
+ code = mode.code
+ if auto_eject_timeout is not None:
+ if mode.interfaces == USB_INTERFACE.CCID:
+ code |= DEVICE_FLAG.EJECT
+ else:
+ raise ValueError("Touch-eject only applicable for mode:
CCID")
self.backend.set_mode(
- struct.pack(">BBH", mode.code, chalresp_timeout,
auto_eject_timeout)
+ # N.B. This is little endian!
+ struct.pack("<BBH", code, chalresp_timeout, auto_eject_timeout
or 0)
)