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 2024-04-04 22:26:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yubikey-manager (Old)
and /work/SRC/openSUSE:Factory/.yubikey-manager.new.1905 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yubikey-manager"
Thu Apr 4 22:26:37 2024 rev:24 rq:1164540 version:5.4.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/yubikey-manager/yubikey-manager.changes
2024-03-17 22:18:35.730108338 +0100
+++
/work/SRC/openSUSE:Factory/.yubikey-manager.new.1905/yubikey-manager.changes
2024-04-04 22:28:09.079491886 +0200
@@ -1,0 +2,13 @@
+Wed Apr 3 12:02:24 UTC 2024 - [email protected]
+
+- version update to 5.4.0
+ * Support for YubiKey Bio Multi-protocol Edition.
+ * CLI: Improve error messages for several failures.
+ * Attempt to send SIGHUP to yubikey-agent if it is blocking the connection.
+ * Bugfix: Allow "fido config" to work when no PIN is set on the YubiKey.
+ * Bugfix: MacOS - Fix race condition resulting in unneeded delay in fido
commands over
+ USB.
+ * Bugfix: Linux - Fix error when listing OTP devices when no YubiKeys are
attached.
+ * Bugfix: OpenPGP - Fix RSA key generation on YubiKey NEO.
+
+-------------------------------------------------------------------
Old:
----
yubikey_manager-5.3.0.tar.gz
yubikey_manager-5.3.0.tar.gz.sig
New:
----
yubikey_manager-5.4.0.tar.gz
yubikey_manager-5.4.0.tar.gz.sig
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ yubikey-manager.spec ++++++
--- /var/tmp/diff_new_pack.gbDKOX/_old 2024-04-04 22:28:09.703514860 +0200
+++ /var/tmp/diff_new_pack.gbDKOX/_new 2024-04-04 22:28:09.703514860 +0200
@@ -17,7 +17,7 @@
Name: yubikey-manager
-Version: 5.3.0
+Version: 5.4.0
Release: 0
Summary: Python 3 library and command line tool for configuring a
YubiKey
License: BSD-2-Clause
++++++ yubikey_manager-5.3.0.tar.gz -> yubikey_manager-5.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/NEWS
new/yubikey_manager-5.4.0/NEWS
--- old/yubikey_manager-5.3.0/NEWS 2024-01-30 10:57:19.552522400 +0100
+++ new/yubikey_manager-5.4.0/NEWS 2024-03-26 14:52:50.940620000 +0100
@@ -1,13 +1,21 @@
-* Version 5.3.0 (released 2023-01-30)
+* Version 5.4.0 (released)
+ * Support for YubiKey Bio Multi-protocol Edition.
+ * CLI: Improve error messages for several failures.
+ * Attempt to send SIGHUP to yubikey-agent if it is blocking the connection.
+ * Bugfix: Allow "fido config" to work when no PIN is set on the YubiKey.
+ * Bugfix: MacOS - Fix race condition resulting in unneeded delay in fido
commands over
+ USB.
+ * Bugfix: Linux - Fix error when listing OTP devices when no YubiKeys are
attached.
+ * Bugfix: OpenPGP - Fix RSA key generation on YubiKey NEO.
+
+* Version 5.3.0 (released 2024-01-31)
** FIDO: Add new CLI commands for PIN management and authenticator config
(force-change, set-min-length, toggle-always-uv, enable-ep-attestation).
- ** PIV: Support new key types on supported devices (RSA 3072/4096,
Curve25519).
- ** PIV: Support for moving and deleting keys on supported devices.
** PIV: Improve handling of legacy "PUK blocked" flag.
** PIV: Improve handling of malformed certificates.
** PIV: Display key information in "piv info" output on supported devices.
** OTP: Fix some commands incorrectly showing errors when used over NFC/CCID.
- ** Add tab-completion YubiKey serial numbers and NRC readers.
+ ** Add tab-completion for YubiKey serial numbers and NFC readers.
* Version 5.2.1 (released 2023-10-10)
** Add support for Python 3.12.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/PKG-INFO
new/yubikey_manager-5.4.0/PKG-INFO
--- old/yubikey_manager-5.3.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
+++ new/yubikey_manager-5.4.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: yubikey-manager
-Version: 5.3.0
+Version: 5.4.0
Summary: Tool for managing your YubiKey configuration.
Home-page: https://github.com/Yubico/yubikey-manager
License: BSD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/man/ykman.1
new/yubikey_manager-5.4.0/man/ykman.1
--- old/yubikey_manager-5.3.0/man/ykman.1 2024-01-30 10:57:19.552522400
+0100
+++ new/yubikey_manager-5.4.0/man/ykman.1 2024-03-26 14:52:50.940620000
+0100
@@ -1,4 +1,4 @@
-.TH YKMAN "1" "January 2024" "ykman 5.3.0" "User Commands"
+.TH YKMAN "1" "March 2024" "ykman 5.4.0" "User Commands"
.SH NAME
ykman \- YubiKey Manager (ykman)
.SH SYNOPSIS
@@ -44,7 +44,7 @@
run a python script
.TP
config
-enable or disable applications
+configure the YubiKey, enable or disable applications
.TP
fido
manage the FIDO applications
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/pyproject.toml
new/yubikey_manager-5.4.0/pyproject.toml
--- old/yubikey_manager-5.3.0/pyproject.toml 2024-01-30 10:57:19.552522400
+0100
+++ new/yubikey_manager-5.4.0/pyproject.toml 2024-03-26 14:52:50.940620000
+0100
@@ -1,6 +1,6 @@
[tool.poetry]
name = "yubikey-manager"
-version = "5.3.0"
+version = "5.4.0"
description = "Tool for managing your YubiKey configuration."
authors = ["Dain Nilsson <[email protected]>"]
license = "BSD"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/yubikey_manager-5.3.0/tests/device/cli/test_config.py
new/yubikey_manager-5.4.0/tests/device/cli/test_config.py
--- old/yubikey_manager-5.3.0/tests/device/cli/test_config.py 2023-09-17
13:05:24.076793000 +0200
+++ new/yubikey_manager-5.4.0/tests/device/cli/test_config.py 2024-03-26
14:52:50.940620000 +0100
@@ -14,6 +14,8 @@
def not_sky(device, info):
+ if info.is_sky:
+ return False
if device.transport == TRANSPORT.NFC:
return not (
info.serial is None
@@ -109,6 +111,7 @@
"OTP",
)
+ @condition.capability(CAPABILITY.U2F, TRANSPORT.USB)
def test_mode_command(self, ykman_cli, await_reboot):
ykman_cli("config", "mode", "ccid", "-f")
await_reboot()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/tests/device/test_piv.py
new/yubikey_manager-5.4.0/tests/device/test_piv.py
--- old/yubikey_manager-5.3.0/tests/device/test_piv.py 2024-01-29
09:10:25.565461600 +0100
+++ new/yubikey_manager-5.4.0/tests/device/test_piv.py 2024-03-26
14:52:50.944620000 +0100
@@ -746,3 +746,20 @@
session.delete_key(SLOT.AUTHENTICATION)
with pytest.raises(ApduError):
session.get_slot_metadata(SLOT.AUTHENTICATION)
+
+
+class TestPinComplexity:
+ @pytest.fixture(autouse=True)
+ def preconditions(self, info):
+ if not info.pin_complexity:
+ pytest.skip("Requires YubiKey with PIN complexity enabled")
+
+ @pytest.mark.parametrize("pin", ("111111", "22222222", "333333",
"4444444"))
+ def test_repeated_pins(self, session, keys, pin):
+ with pytest.raises(ApduError):
+ session.change_pin(keys.pin, pin)
+
+ @pytest.mark.parametrize("pin", ("abc123", "password", "123123"))
+ def test_invalid_pins(self, session, keys, pin):
+ with pytest.raises(ApduError):
+ session.change_pin(keys.pin, pin)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/__init__.py
new/yubikey_manager-5.4.0/ykman/__init__.py
--- old/yubikey_manager-5.3.0/ykman/__init__.py 2024-01-30 10:57:19.552522400
+0100
+++ new/yubikey_manager-5.4.0/ykman/__init__.py 2024-03-26 14:52:50.944620000
+0100
@@ -25,4 +25,4 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-__version__ = "5.3.0"
+__version__ = "5.4.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/config.py
new/yubikey_manager-5.4.0/ykman/_cli/config.py
--- old/yubikey_manager-5.3.0/ykman/_cli/config.py 2024-01-29
09:10:25.565461600 +0100
+++ new/yubikey_manager-5.4.0/ykman/_cli/config.py 2024-03-26
14:52:50.944620000 +0100
@@ -48,7 +48,6 @@
)
import os
import re
-import sys
import click
import logging
@@ -68,7 +67,7 @@
@click_postpone_execution
def config(ctx):
"""
- Enable or disable applications.
+ Configure the YubiKey, enable or disable applications.
The applications may be enabled and disabled independently
over different transports (USB and NFC). The configuration may
@@ -112,13 +111,15 @@
)
[email protected](hidden="--full-help" not in sys.argv)
[email protected]()
@click.pass_context
@click_force_option
def reset(ctx, force):
"""
Reset all YubiKey data.
+ This command is used with the YubiKey Bio Multi-protocol Edition.
+
This action will wipe all data and restore factory settings for
all applications on the YubiKey.
"""
@@ -127,7 +128,10 @@
is_bio = info.form_factor in (FORM_FACTOR.USB_A_BIO, FORM_FACTOR.USB_C_BIO)
has_piv = CAPABILITY.PIV in info.supported_capabilities.get(transport)
if not (is_bio and has_piv):
- raise CliFail("Full device reset is not supported on this YubiKey.")
+ raise CliFail(
+ "Full device reset is not supported on this YubiKey, "
+ "refer to reset commands for specific applications instead."
+ )
force or click.confirm(
"WARNING! This will delete all stored data and restore factory "
@@ -248,7 +252,9 @@
f"{unsupported.display_name} not supported over {transport} on
this "
"YubiKey."
)
- new_enabled = (enabled | enable) & ~disable
+
+ # N.B. NOT (~) of IntFlag doesn't work as expected
+ new_enabled = (enabled | enable) & ~int(disable)
if transport == TRANSPORT.USB:
if sum(CAPABILITY) & new_enabled == 0:
@@ -268,11 +274,9 @@
is_locked = info.is_locked
if force and is_locked and not lock_code:
- raise CliFail("Configuration is locked - please supply the --lock-code
option.")
+ raise CliFail("Configuration is locked - supply the --lock-code
option.")
if lock_code and not is_locked:
- raise CliFail(
- "Configuration is not locked - please remove the --lock-code
option."
- )
+ raise CliFail("Configuration is not locked - remove the --lock-code
option.")
click.echo(f"{transport} configuration changes:")
for change in changes:
@@ -635,6 +639,8 @@
f"Mode {mode} is not supported on this YubiKey!\n"
+ "Use --force to attempt to set it anyway."
)
+ elif info.is_sky and USB_INTERFACE.FIDO not in mode.interfaces:
+ raise CliFail("Security Key requires FIDO to be enabled.")
force or click.confirm(f"Set mode of YubiKey to {mode}?", abort=True,
err=True)
try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/fido.py
new/yubikey_manager-5.4.0/ykman/_cli/fido.py
--- old/yubikey_manager-5.3.0/ykman/_cli/fido.py 2024-01-23
12:00:35.030305100 +0100
+++ new/yubikey_manager-5.4.0/ykman/_cli/fido.py 2024-03-26
14:52:50.944620000 +0100
@@ -36,6 +36,7 @@
Config,
)
from fido2.pcsc import CtapPcscDevice
+from yubikit.management import CAPABILITY
from yubikit.core.fido import FidoConnection
from yubikit.core.smartcard import SW
from time import sleep
@@ -63,10 +64,6 @@
logger = logging.getLogger(__name__)
-FIPS_PIN_MIN_LENGTH = 6
-PIN_MIN_LENGTH = 4
-
-
@click_group(connections=[FidoConnection])
@click.pass_context
@click_postpone_execution
@@ -172,8 +169,14 @@
inserted, and requires a touch on the YubiKey.
"""
- conn = ctx.obj["conn"]
+ info = ctx.obj["info"]
+ if CAPABILITY.FIDO2 in info.reset_blocked:
+ raise CliFail(
+ "Cannot perform FIDO reset when PIV is configured, "
+ "use 'ykman config reset' for full factory 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:
@@ -233,10 +236,10 @@
)
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 "
- "this YubiKey will no longer be a FIPS compliant device.\n"
- 'To proceed, please enter the text "OVERWRITE"',
+ "WARNING! This is a YubiKey FIPS (4 Series) device. This
command will "
+ "also overwrite the U2F attestation key; this action cannot be
undone "
+ "and this YubiKey will no longer be a FIPS compliant device.\n"
+ 'To proceed, enter the text "OVERWRITE"',
default="",
show_default=False,
)
@@ -305,16 +308,17 @@
"-u",
"--u2f",
is_flag=True,
- help="set FIDO U2F PIN instead of FIDO2 PIN (YubiKey 4 FIPS only)",
+ help="set FIDO U2F PIN instead of FIDO2 PIN (YubiKey FIPS only)",
)
def change_pin(ctx, pin, new_pin, u2f):
"""
Set or change the PIN code.
- The FIDO2 PIN must be at least 4 characters long, and supports any type
- of alphanumeric characters.
+ The FIDO2 PIN must be at least 4 characters long, and supports any type of
+ alphanumeric characters. Some YubiKeys can be configured to require a
longer
+ PIN.
- On YubiKey FIPS, a PIN can be set for FIDO U2F. That PIN must be at least
+ On YubiKey FIPS (4 Series), a PIN can be set for FIDO U2F. That PIN must
be at least
6 characters long.
"""
@@ -322,22 +326,29 @@
if is_fips and not u2f:
raise CliFail(
- "This is a YubiKey FIPS. To set the U2F PIN, pass the --u2f
option."
+ "This is a YubiKey FIPS (4 Series). "
+ "To set the U2F PIN, pass the --u2f option."
)
if u2f and not is_fips:
raise CliFail(
- "This is not a YubiKey 4 FIPS, and therefore does not support a
U2F PIN. "
- "To set the FIDO2 PIN, remove the --u2f option."
+ "This is not a YubiKey FIPS (4 Series), and therefore does not
support a "
+ "U2F PIN. To set the FIDO2 PIN, remove the --u2f option."
)
if is_fips:
conn = ctx.obj["conn"]
+ min_len = 6
else:
ctap2 = ctx.obj.get("ctap2")
if not ctap2:
raise CliFail("PIN is not supported on this YubiKey.")
client_pin = ClientPin(ctap2)
+ min_len = ctap2.info.min_pin_length
+
+ def _fail_if_not_valid_pin(pin=None, name="PIN"):
+ if not pin or len(pin) < min_len:
+ raise CliFail(f"{name} must be at least {min_len} characters long")
def prompt_new_pin():
return click_prompt(
@@ -347,8 +358,6 @@
)
def change_pin(pin, new_pin):
- if pin is not None:
- _fail_if_not_valid_pin(ctx, pin, is_fips)
try:
if is_fips:
try:
@@ -357,7 +366,7 @@
except ApduError as e:
if e.code == SW.WRONG_LENGTH:
pin = _prompt_current_pin()
- _fail_if_not_valid_pin(ctx, pin, is_fips)
+ _fail_if_not_valid_pin(pin)
fips_change_pin(conn, pin, new_pin)
else:
raise
@@ -367,7 +376,7 @@
except CtapError as e:
if e.code == CtapError.ERR.PIN_POLICY_VIOLATION:
- raise CliFail("New PIN doesn't meet policy requirements.")
+ raise CliFail("New PIN doesn't meet complexity requirements.")
else:
_fail_pin_error(ctx, e, "Failed to change PIN: %s")
@@ -380,12 +389,12 @@
raise CliFail(f"Failed to change PIN: SW={e.code:04x}")
def set_pin(new_pin):
- _fail_if_not_valid_pin(ctx, new_pin, is_fips)
+ _fail_if_not_valid_pin(new_pin)
try:
client_pin.set_pin(new_pin)
except CtapError as e:
if e.code == CtapError.ERR.PIN_POLICY_VIOLATION:
- raise CliFail("New PIN doesn't meet policy requirements.")
+ raise CliFail("New PIN doesn't meet complexity requirements.")
else:
raise CliFail(f"Failed to set PIN: {e.code}")
@@ -399,14 +408,11 @@
if not new_pin:
new_pin = prompt_new_pin()
+ _fail_if_not_valid_pin(new_pin, "New PIN")
if is_fips:
- _fail_if_not_valid_pin(ctx, new_pin, is_fips)
change_pin(pin, new_pin)
else:
- min_len = ctap2.info.min_pin_length
- if len(new_pin) < min_len:
- raise CliFail(f"New PIN is too short. Minimum length: {min_len}")
if ctap2.info.options.get("clientPin"):
change_pin(pin, new_pin)
else:
@@ -435,7 +441,7 @@
Verify the FIDO PIN against a YubiKey.
For YubiKeys supporting FIDO2 this will reset the "retries" counter of the
PIN.
- For YubiKey FIPS this will unlock the session, allowing U2F registration.
+ For YubiKey FIPS (4 Series) this will unlock the session, allowing U2F
registration.
"""
ctap2 = ctx.obj.get("ctap2")
@@ -450,7 +456,6 @@
except CtapError as e:
raise CliFail(f"PIN verification failed: {e}")
elif is_yk4_fips(ctx.obj["info"]):
- _fail_if_not_valid_pin(ctx, pin, True)
try:
fips_verify_pin(ctx.obj["conn"], pin)
except ApduError as e:
@@ -472,14 +477,20 @@
if not Config.is_supported(ctap2.info):
raise CliFail("Authenticator Configuration is not supported on this
YubiKey.")
- pin = _require_pin(ctx, pin, "Authenticator Configuration")
- client_pin = ClientPin(ctap2)
- try:
- token = client_pin.get_pin_token(pin,
ClientPin.PERMISSION.AUTHENTICATOR_CFG)
- except CtapError as e:
- _fail_pin_error(ctx, e, "PIN error: %s")
+ protocol = None
+ token = None
+ if ctap2.info.options.get("clientPin"):
+ pin = _require_pin(ctx, pin, "Authenticator Configuration")
+ client_pin = ClientPin(ctap2)
+ try:
+ protocol = client_pin.protocol
+ token = client_pin.get_pin_token(
+ pin, ClientPin.PERMISSION.AUTHENTICATOR_CFG
+ )
+ except CtapError as e:
+ _fail_pin_error(ctx, e, "PIN error: %s")
- return Config(ctap2, client_pin.protocol, token)
+ return Config(ctap2, protocol, token)
@access.command("force-change")
@@ -492,6 +503,8 @@
options = ctx.obj.get("ctap2").info.options
if not options.get("setMinPINLength"):
raise CliFail("Force change PIN is not supported on this YubiKey.")
+ if not options.get("clientPin"):
+ raise CliFail("No PIN is set.")
config = _init_config(ctx, pin)
config.set_min_pin_length(force_change_pin=True)
@@ -509,9 +522,17 @@
Optionally use the --rp option to specify which RPs are allowed to request
this
information.
"""
- options = ctx.obj.get("ctap2").info.options
- if not options.get("setMinPINLength"):
+ info = ctx.obj["ctap2"].info
+ if not info.options.get("setMinPINLength"):
raise CliFail("Set minimum PIN length is not supported on this
YubiKey.")
+ if info.options.get("alwaysUv") and not info.options.get("clientPin"):
+ raise CliFail(
+ "Setting min PIN length requires a PIN to be set when alwaysUv is
enabled."
+ )
+
+ min_len = info.min_pin_length
+ if length < min_len:
+ raise CliFail(f"Cannot set a minimum length that is shorter than
{min_len}.")
config = _init_config(ctx, pin)
if rp_id:
@@ -521,6 +542,7 @@
raise CliFail(
f"Authenticator supports up to {cap} RP IDs ({len(rp_id)}
given)."
)
+
config.set_min_pin_length(min_pin_length=length, rp_ids=rp_id)
@@ -528,12 +550,6 @@
return click_prompt(prompt, hide_input=True)
-def _fail_if_not_valid_pin(ctx, pin=None, is_fips=False):
- min_length = FIPS_PIN_MIN_LENGTH if is_fips else PIN_MIN_LENGTH
- if not pin or len(pin) < min_length:
- ctx.fail(f"PIN must be over {min_length} characters long")
-
-
def _gen_creds(credman):
data = credman.get_metadata()
if data.get(CredentialManagement.RESULT.EXISTING_CRED_COUNT) == 0:
@@ -889,6 +905,11 @@
options = ctx.obj.get("ctap2").info.options
if "ep" not in options:
raise CliFail("Enterprise Attestation is not supported on this
YubiKey.")
+ if options.get("alwaysUv") and not options.get("clientPin"):
+ raise CliFail(
+ "Enabling Enterprise Attestation requires a PIN to be set when
alwaysUv is "
+ "enabled."
+ )
config = _init_config(ctx, pin)
config.enable_enterprise_attestation()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/hsmauth.py
new/yubikey_manager-5.4.0/ykman/_cli/hsmauth.py
--- old/yubikey_manager-5.3.0/ykman/_cli/hsmauth.py 2023-10-30
11:10:16.448203300 +0100
+++ new/yubikey_manager-5.4.0/ykman/_cli/hsmauth.py 2024-03-26
14:52:50.944620000 +0100
@@ -77,6 +77,8 @@
raise CliFail("Credential with the provided label was not found.")
elif e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED:
raise CliFail("The device was not touched.")
+ elif e.sw == SW.CONDITIONS_NOT_SATISFIED:
+ raise CliFail("Credential password does not meet complexity
requirement.")
raise CliFail(default_exception_msg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/info.py
new/yubikey_manager-5.4.0/ykman/_cli/info.py
--- old/yubikey_manager-5.3.0/ykman/_cli/info.py 2023-09-17
13:05:24.080793000 +0200
+++ new/yubikey_manager-5.4.0/ykman/_cli/info.py 2024-03-26
14:52:50.944620000 +0100
@@ -127,7 +127,6 @@
def _check_fips_status(device, info):
fips_status = get_overall_fips_status(device, info)
- click.echo()
click.echo(f"FIPS Approved Mode: {'Yes' if all(fips_status.values()) else
'No'}")
@@ -140,7 +139,7 @@
@click.option(
"-c",
"--check-fips",
- help="check if YubiKey is in FIPS Approved mode (YubiKey 4 FIPS only)",
+ help="check if YubiKey is in FIPS Approved mode (4 Series only)",
is_flag=True,
)
@click_command(connections=[SmartCardConnection, OtpConnection,
FidoConnection])
@@ -186,18 +185,23 @@
if info.config.enabled_capabilities.get(TRANSPORT.NFC)
else "disabled"
)
- click.echo(f"NFC transport is {f_nfc}.")
+ click.echo(f"NFC transport is {f_nfc}")
+ if info.pin_complexity:
+ click.echo("PIN complexity is enforced")
if info.is_locked:
- click.echo("Configured capabilities are protected by a lock code.")
- click.echo()
+ click.echo("Configured capabilities are protected by a lock code")
+ click.echo()
print_app_status_table(
info.supported_capabilities, info.config.enabled_capabilities
)
if check_fips:
+ click.echo()
if is_yk4_fips(info):
device = ctx.obj["device"]
_check_fips_status(device, info)
else:
- raise CliFail("Unable to check FIPS Approved mode - Not a YubiKey
4 FIPS")
+ raise CliFail(
+ "Unable to check FIPS Approved mode - Not a YubiKey FIPS (4
Series)"
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/oath.py
new/yubikey_manager-5.4.0/ykman/_cli/oath.py
--- old/yubikey_manager-5.3.0/ykman/_cli/oath.py 2023-10-30
11:10:16.448203300 +0100
+++ new/yubikey_manager-5.4.0/ykman/_cli/oath.py 2024-03-26
14:52:50.944620000 +0100
@@ -76,7 +76,7 @@
\b
Set a password for the OATH application:
- $ ykman oath access change-password
+ $ ykman oath access change
"""
dev = ctx.obj["device"]
@@ -356,9 +356,7 @@
def _error_multiple_hits(ctx, hits):
- click.echo(
- "Error: Multiple matches, please make the query more specific.",
err=True
- )
+ click.echo("Error: Multiple matches, make the query more specific.",
err=True)
click.echo("", err=True)
for cred in hits:
click.echo(_string_id(cred), err=True)
@@ -618,7 +616,7 @@
Generate codes from OATH accounts stored on the YubiKey.
Provide a query string to match one or more specific accounts.
- Accounts of type HOTP, or those that require touch, requre a single match
to be
+ Accounts of type HOTP, or those that require touch, require a single match
to be
triggered.
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/openpgp.py
new/yubikey_manager-5.4.0/ykman/_cli/openpgp.py
--- old/yubikey_manager-5.3.0/ykman/_cli/openpgp.py 2024-01-23
12:00:35.030305100 +0100
+++ new/yubikey_manager-5.4.0/ykman/_cli/openpgp.py 2024-03-26
14:52:50.944620000 +0100
@@ -195,7 +195,12 @@
confirmation_prompt=True,
)
- session.change_pin(pin, new_pin)
+ try:
+ session.change_pin(pin, new_pin)
+ except ApduError as e:
+ if e.sw == SW.CONDITIONS_NOT_SATISFIED:
+ raise CliFail("PIN does not meet complexity requirement.")
+ raise
@access.command("change-reset-code")
@@ -223,7 +228,12 @@
)
session.verify_admin(admin_pin)
- session.set_reset_code(reset_code)
+ try:
+ session.set_reset_code(reset_code)
+ except ApduError as e:
+ if e.sw == SW.CONDITIONS_NOT_SATISFIED:
+ raise CliFail("Reset Code does not meet complexity requirement.")
+ raise
@access.command("change-admin-pin")
@@ -250,7 +260,12 @@
confirmation_prompt=True,
)
- session.change_admin(admin_pin, new_admin_pin)
+ try:
+ session.change_admin(admin_pin, new_admin_pin)
+ except ApduError as e:
+ if e.sw == SW.CONDITIONS_NOT_SATISFIED:
+ raise CliFail("Admin PIN does not meet complexity requirement.")
+ raise
@access.command("unblock-pin")
@@ -294,7 +309,13 @@
if admin_pin:
session.verify_admin(admin_pin)
- session.reset_pin(new_pin, reset_code)
+
+ try:
+ session.reset_pin(new_pin, reset_code)
+ except ApduError as e:
+ if e.sw == SW.CONDITIONS_NOT_SATISFIED:
+ raise CliFail("New PIN does not meet complexity requirement.")
+ raise
@access.command("set-signature-policy")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/otp.py
new/yubikey_manager-5.4.0/ykman/_cli/otp.py
--- old/yubikey_manager-5.3.0/ykman/_cli/otp.py 2023-09-17 13:05:24.080793000
+0200
+++ new/yubikey_manager-5.4.0/ykman/_cli/otp.py 2024-03-26 14:52:50.944620000
+0100
@@ -422,7 +422,7 @@
click.echo(f"Using YubiKey serial as public ID: {public_id}")
elif force:
ctx.fail(
- "Public ID not given. Please remove the --force flag, or "
+ "Public ID not given. Remove the --force flag, or "
"add the --serial-public-id flag or --public-id option."
)
else:
@@ -441,7 +441,7 @@
click.echo(f"Using a randomly generated private ID:
{private_id.hex()}")
elif force:
ctx.fail(
- "Private ID not given. Please remove the --force flag, or "
+ "Private ID not given. Remove the --force flag, or "
"add the --generate-private-id flag or --private-id option."
)
else:
@@ -454,7 +454,7 @@
click.echo(f"Using a randomly generated secret key: {key.hex()}")
elif force:
ctx.fail(
- "Secret key not given. Please remove the --force flag, or "
+ "Secret key not given. Remove the --force flag, or "
"add the --generate-key flag or --key option."
)
else:
@@ -619,7 +619,7 @@
else:
if force and not generate:
ctx.fail(
- "No secret key given. Please remove the --force flag, "
+ "No secret key given. Remove the --force flag, "
"set the KEY argument or set the --generate flag."
)
elif generate:
@@ -906,6 +906,8 @@
new_access_code = parse_access_code_hex(new_access_code)
except Exception as e:
ctx.fail("Failed to parse access code: " + str(e))
+ if ctx.obj["info"].pin_complexity and len(set(new_access_code)) < 2:
+ raise CliFail("Access code does not meet complexity requirement.")
force or click.confirm(
f"Update the settings for slot {slot}? "
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/_cli/piv.py
new/yubikey_manager-5.4.0/ykman/_cli/piv.py
--- old/yubikey_manager-5.3.0/ykman/_cli/piv.py 2024-01-29 09:10:25.565461600
+0100
+++ new/yubikey_manager-5.4.0/ykman/_cli/piv.py 2024-03-26 14:52:50.944620000
+0100
@@ -27,6 +27,7 @@
from yubikit.core import NotSupportedError
from yubikit.core.smartcard import SmartCardConnection
+from yubikit.management import CAPABILITY
from yubikit.piv import (
PivSession,
InvalidPinError,
@@ -220,6 +221,13 @@
This action will wipe all data and restore factory settings for
the PIV application on the YubiKey.
"""
+ info = ctx.obj["info"]
+ if CAPABILITY.PIV in info.reset_blocked:
+ raise CliFail(
+ "Cannot perform PIV reset when biometrics are configured, "
+ "use 'ykman config reset' for full factory reset."
+ )
+
force or click.confirm(
"WARNING! This will delete all stored PIV data and restore factory "
"settings. Proceed?",
@@ -275,6 +283,31 @@
raise CliFail("Setting pin retries failed.")
+def _do_change_pin_puk(pin_complexity, name, current, new, fn):
+ def validate_pin_length(pin, prefix):
+ unit = "characters" if pin_complexity else "bytes"
+ pin_len = len(pin) if pin_complexity else len(pin.encode())
+ if not 6 <= pin_len <= 8:
+ raise CliFail(f"{prefix} {name} must be between 6 and 8 {unit}
long.")
+
+ validate_pin_length(current, "Current")
+ validate_pin_length(new, "New")
+
+ try:
+ fn()
+ click.echo(f"New {name} set.")
+ except InvalidPinError as e:
+ attempts = e.attempts_remaining
+ if attempts:
+ raise CliFail(f"{name} change failed - %d tries left." % attempts)
+ else:
+ raise CliFail(f"{name} is blocked.")
+ except ApduError as e:
+ if e.sw == SW.CONDITIONS_NOT_SATISFIED:
+ raise CliFail(f"{name} does not meet complexity requirement.")
+ raise
+
+
@access.command("change-pin")
@click.pass_context
@click.option("-P", "--pin", help="current PIN code")
@@ -283,11 +316,11 @@
"""
Change the PIN code.
- The PIN must be between 6 and 8 characters long, and supports any type of
+ The PIN must be between 6 and 8 bytes long, and supports any type of
alphanumeric characters. For cross-platform compatibility, numeric PINs are
recommended.
"""
-
+ info = ctx.obj["info"]
session = ctx.obj["session"]
if not pin:
@@ -301,21 +334,13 @@
confirmation_prompt=True,
)
- if not _valid_pin_length(pin):
- ctx.fail("Current PIN must be between 6 and 8 characters long.")
-
- if not _valid_pin_length(new_pin):
- ctx.fail("New PIN must be between 6 and 8 characters long.")
-
- try:
- pivman_change_pin(session, pin, new_pin)
- click.echo("New PIN set.")
- except InvalidPinError as e:
- attempts = e.attempts_remaining
- if attempts:
- raise CliFail("PIN change failed - %d tries left." % attempts)
- else:
- raise CliFail("PIN is blocked.")
+ _do_change_pin_puk(
+ info.pin_complexity,
+ "PIN",
+ pin,
+ new_pin,
+ lambda: pivman_change_pin(session, pin, new_pin),
+ )
@access.command("change-puk")
@@ -327,10 +352,12 @@
Change the PUK code.
If the PIN is lost or blocked it can be reset using a PUK.
- The PUK must be between 6 and 8 characters long, and supports any type of
+ The PUK must be between 6 and 8 bytes long, and supports any type of
alphanumeric characters.
"""
+ info = ctx.obj["info"]
session = ctx.obj["session"]
+
if not puk:
puk = _prompt_pin("Enter the current PUK")
if not new_puk:
@@ -342,21 +369,13 @@
confirmation_prompt=True,
)
- if not _valid_pin_length(puk):
- ctx.fail("Current PUK must be between 6 and 8 characters long.")
-
- if not _valid_pin_length(new_puk):
- ctx.fail("New PUK must be between 6 and 8 characters long.")
-
- try:
- session.change_puk(puk, new_puk)
- click.echo("New PUK set.")
- except InvalidPinError as e:
- attempts = e.attempts_remaining
- if attempts:
- raise CliFail("PUK change failed - %d tries left." % attempts)
- else:
- raise CliFail("PUK is blocked.")
+ _do_change_pin_puk(
+ info.pin_complexity,
+ "PUK",
+ puk,
+ new_puk,
+ lambda: session.change_puk(puk, new_puk),
+ )
@access.command("change-management-key")
@@ -461,7 +480,7 @@
click.echo(f"Generated management key:
{new_management_key.hex()}")
elif force:
ctx.fail(
- "New management key not given. Please remove the --force "
+ "New management key not given. Remove the --force "
"flag, or set the --generate flag or the "
"--new-management-key option."
)
@@ -504,7 +523,11 @@
puk = click_prompt("Enter PUK", default="", show_default=False,
hide_input=True)
if not new_pin:
new_pin = click_prompt(
- "Enter a new PIN", default="", show_default=False, hide_input=True
+ "Enter a new PIN",
+ default="",
+ show_default=False,
+ hide_input=True,
+ confirmation_prompt=True,
)
try:
session.unblock_pin(puk, new_pin)
@@ -515,6 +538,10 @@
raise CliFail("PIN unblock failed - %d tries left." % attempts)
else:
raise CliFail("PUK is blocked.")
+ except ApduError as e:
+ if e.sw == SW.CONDITIONS_NOT_SATISFIED:
+ raise CliFail("PIN does not meet complexity requirement.")
+ raise
@piv.group()
@@ -686,7 +713,7 @@
except ApduError as e:
if e.sw == SW.REFERENCE_DATA_NOT_FOUND:
raise CliFail(f"No key stored in slot {slot}.")
- raise e
+ raise
@keys.command()
@@ -804,7 +831,12 @@
"""
session = ctx.obj["session"]
_ensure_authenticated(ctx, pin, management_key)
- session.delete_key(slot)
+ try:
+ session.delete_key(slot)
+ except ApduError as e:
+ if e.sw == SW.REFERENCE_DATA_NOT_FOUND:
+ raise CliFail(f"No key stored in slot {slot}.")
+ raise
@piv.group("certificates")
@@ -892,8 +924,8 @@
timeout = None
except ApduError as e:
if e.sw == SW.REFERENCE_DATA_NOT_FOUND:
- raise CliFail("No private key in slot {slot}")
- raise e
+ raise CliFail(f"No private key in slot {slot}")
+ raise
except NotSupportedError:
timeout = 1.0
@@ -982,7 +1014,8 @@
timeout = None
except ApduError as e:
if e.sw == SW.REFERENCE_DATA_NOT_FOUND:
- raise CliFail("No private key in slot {slot}")
+ raise CliFail(f"No private key in slot {slot}.")
+ raise
except NotSupportedError:
timeout = 1.0
@@ -1054,7 +1087,8 @@
timeout = None
except ApduError as e:
if e.sw == SW.REFERENCE_DATA_NOT_FOUND:
- raise CliFail("No private key in slot {slot}")
+ raise CliFail(f"No private key in slot {slot}.")
+ raise
except NotSupportedError:
timeout = 1.0
@@ -1172,7 +1206,7 @@
except ApduError as e:
if e.sw == SW.INCORRECT_PARAMETERS:
raise CliFail("Something went wrong, is the object id valid?")
- raise CliFail("Error writing object")
+ raise CliFail("Error writing object.")
@objects.command("generate")
@@ -1219,10 +1253,6 @@
return click_prompt(prompt, default="", hide_input=True,
show_default=False)
-def _valid_pin_length(pin):
- return 6 <= len(pin) <= 8
-
-
def _ensure_authenticated(
ctx,
pin=None,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/diagnostics.py
new/yubikey_manager-5.4.0/ykman/diagnostics.py
--- old/yubikey_manager-5.3.0/ykman/diagnostics.py 2024-01-15
10:53:20.944693800 +0100
+++ new/yubikey_manager-5.4.0/ykman/diagnostics.py 2024-03-26
14:52:50.944620000 +0100
@@ -6,6 +6,7 @@
from .openpgp import get_openpgp_info
from .hsmauth import get_hsmauth_info
+from yubikit.core import Tlv
from yubikit.core.smartcard import SmartCardConnection
from yubikit.core.fido import FidoConnection
from yubikit.core.otp import OtpConnection
@@ -51,9 +52,13 @@
def mgmt_info(pid, conn):
data: List[Any] = []
try:
+ m = ManagementSession(conn)
+ raw_info = m.backend.read_config()
+ if Tlv.parse_dict(raw_info[1:]).get(0x10) == b"\1":
+ raw_info += m.backend.read_config(1)
data.append(
{
- "Raw Info": ManagementSession(conn).backend.read_config(),
+ "Raw Info": raw_info,
}
)
except Exception as e:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/fido.py
new/yubikey_manager-5.4.0/ykman/fido.py
--- old/yubikey_manager-5.3.0/ykman/fido.py 2023-09-17 13:05:24.080793000
+0200
+++ new/yubikey_manager-5.4.0/ykman/fido.py 2024-03-26 14:52:50.944620000
+0100
@@ -44,7 +44,7 @@
def is_in_fips_mode(fido_connection: FidoConnection) -> bool:
- """Check if a YubiKey FIPS is in FIPS approved mode.
+ """Check if a YubiKey 4 FIPS is in FIPS approved mode.
:param fido_connection: A FIDO connection.
"""
@@ -62,7 +62,7 @@
def fips_change_pin(
fido_connection: FidoConnection, old_pin: Optional[str], new_pin: str
):
- """Change the PIN on a YubiKey FIPS.
+ """Change the PIN on a YubiKey 4 FIPS.
If no PIN is set, pass None or an empty string as old_pin.
@@ -82,7 +82,7 @@
def fips_verify_pin(fido_connection: FidoConnection, pin: str):
- """Unlock the YubiKey FIPS U2F module for credential creation.
+ """Unlock the YubiKey 4 FIPS U2F module for credential creation.
:param fido_connection: A FIDO connection.
:param pin: The FIDO PIN.
@@ -92,11 +92,11 @@
def fips_reset(fido_connection: FidoConnection):
- """Reset the FIDO module of a YubiKey FIPS.
+ """Reset the FIDO module of a YubiKey 4 FIPS.
- Note: This action is only permitted immediately after YubiKey FIPS
power-up. It
- also requires the user to touch the flashing button on the YubiKey, and
will halt
- until that happens, or the command times out.
+ Note: This action is only permitted immediately after YubiKey power-up. It
also
+ requires the user to touch the flashing button on the YubiKey, and will
halt until
+ that happens, or the command times out.
:param fido_connection: A FIDO connection.
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/hid/linux.py
new/yubikey_manager-5.4.0/ykman/hid/linux.py
--- old/yubikey_manager-5.3.0/ykman/hid/linux.py 2023-09-17
13:05:24.080793000 +0200
+++ new/yubikey_manager-5.4.0/ykman/hid/linux.py 2024-03-26
14:52:50.944620000 +0100
@@ -109,15 +109,15 @@
def list_devices():
- stale = set(_failed_cache)
devices = []
for hidraw in glob.glob("/dev/hidraw*"):
- stale.discard(hidraw)
try:
with open(hidraw, "rb") as f:
bustype, vid, pid = get_info(f)
if vid == YUBICO_VID and get_usage(f) == USAGE_OTP:
devices.append(OtpYubiKeyDevice(hidraw, pid,
HidrawConnection))
+ if hidraw in _failed_cache:
+ _failed_cache.remove(hidraw)
except Exception:
if hidraw not in _failed_cache:
logger.debug(
@@ -126,7 +126,4 @@
_failed_cache.add(hidraw)
continue
- # Remove entries from the cache that were not seen
- _failed_cache.difference_update(hidraw)
-
return devices
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/otp.py
new/yubikey_manager-5.4.0/ykman/otp.py
--- old/yubikey_manager-5.3.0/ykman/otp.py 2023-09-17 13:05:24.084793000
+0200
+++ new/yubikey_manager-5.4.0/ykman/otp.py 2024-03-26 14:52:50.944620000
+0100
@@ -52,7 +52,7 @@
CONNECTION_FAILED = "Failed to open HTTPS connection."
NOT_FOUND = "Upload request not recognized by server."
SERVICE_UNAVAILABLE = (
- "Service temporarily unavailable, please try again later." # noqa:
E501
+ "Service temporarily unavailable, try again later." # noqa: E501
)
# Defined in upload project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/ykman/pcsc/__init__.py
new/yubikey_manager-5.4.0/ykman/pcsc/__init__.py
--- old/yubikey_manager-5.3.0/ykman/pcsc/__init__.py 2023-09-17
13:05:24.084793000 +0200
+++ new/yubikey_manager-5.4.0/ykman/pcsc/__init__.py 2024-03-26
14:52:50.944620000 +0100
@@ -95,7 +95,7 @@
try:
return ScardSmartCardConnection(self.reader.createConnection())
except CardConnectionException as e:
- if kill_scdaemon():
+ if kill_scdaemon() or kill_yubikey_agent():
return ScardSmartCardConnection(self.reader.createConnection())
raise e
@@ -152,6 +152,17 @@
return killed
+def kill_yubikey_agent():
+ killed = False
+ return_code = subprocess.call(["pkill", "-HUP", "yubikey-agent"]) # nosec
+ if return_code == 0:
+ killed = True
+ if killed:
+ sleep(0.1)
+
+ return killed
+
+
def list_readers():
try:
return System.readers()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/yubikit/management.py
new/yubikey_manager-5.4.0/yubikit/management.py
--- old/yubikey_manager-5.3.0/yubikit/management.py 2024-01-29
09:10:25.565461600 +0100
+++ new/yubikey_manager-5.4.0/yubikit/management.py 2024-03-26
14:52:50.948619800 +0100
@@ -49,7 +49,7 @@
from fido2.hid import CAPABILITY as CTAP_CAPABILITY
from enum import IntEnum, IntFlag, unique
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from typing import Optional, Union, Mapping
import abc
import struct
@@ -97,7 +97,12 @@
if self & (CAPABILITY.U2F | CAPABILITY.FIDO2):
ifaces |= USB_INTERFACE.FIDO
if self & (
- CAPABILITY.OATH | CAPABILITY.PIV | CAPABILITY.OPENPGP |
CAPABILITY.HSMAUTH
+ 0x4 # General CCID bit
+ | 0x400 # Management over CCID bit
+ | CAPABILITY.OATH
+ | CAPABILITY.PIV
+ | CAPABILITY.OPENPGP
+ | CAPABILITY.HSMAUTH
):
ifaces |= USB_INTERFACE.CCID
return ifaces
@@ -164,16 +169,25 @@
TAG_REBOOT = 0x0C
TAG_NFC_SUPPORTED = 0x0D
TAG_NFC_ENABLED = 0x0E
+TAG_IAP_DETECTION = 0x0F
+TAG_MORE_DATA = 0x10
+TAG_FREE_FORM = 0x11
+TAG_HID_INIT_DELAY = 0x12
+TAG_PART_NUMBER = 0x13
+TAG_PIN_COMPLEXITY = 0x16
+TAG_NFC_RESTRICTED = 0x17
+TAG_RESET_BLOCKED = 0x18
@dataclass
class DeviceConfig:
"""Management settings for YubiKey which can be configured by the user."""
- enabled_capabilities: Mapping[TRANSPORT, CAPABILITY]
- auto_eject_timeout: Optional[int]
- challenge_response_timeout: Optional[int]
- device_flags: Optional[DEVICE_FLAG]
+ enabled_capabilities: Mapping[TRANSPORT, CAPABILITY] =
field(default_factory=dict)
+ auto_eject_timeout: Optional[int] = None
+ challenge_response_timeout: Optional[int] = None
+ device_flags: Optional[DEVICE_FLAG] = None
+ nfc_restricted: Optional[bool] = None
def get_bytes(
self,
@@ -200,6 +214,8 @@
buf += Tlv(TAG_DEVICE_FLAGS, int2bytes(self.device_flags))
if new_lock_code:
buf += Tlv(TAG_CONFIG_LOCK, new_lock_code)
+ if self.nfc_restricted is not None:
+ buf += Tlv(TAG_NFC_RESTRICTED, b"\1" if self.nfc_restricted else
b"\0")
if len(buf) > 0xFF:
raise NotSupportedError("DeviceConfiguration too large")
return int2bytes(len(buf)) + buf
@@ -217,6 +233,8 @@
is_locked: bool
is_fips: bool = False
is_sky: bool = False
+ pin_complexity: bool = False
+ reset_blocked: CAPABILITY = CAPABILITY(0)
def has_transport(self, transport: TRANSPORT) -> bool:
return transport in self.supported_capabilities
@@ -225,7 +243,12 @@
def parse(cls, encoded: bytes, default_version: Version) -> "DeviceInfo":
if len(encoded) - 1 != encoded[0]:
raise BadResponseError("Invalid length")
- data = Tlv.parse_dict(encoded[1:])
+ return cls.parse_tlvs(Tlv.parse_dict(encoded[1:]), default_version)
+
+ @classmethod
+ def parse_tlvs(
+ cls, data: Mapping[int, bytes], default_version: Version
+ ) -> "DeviceInfo":
locked = data.get(TAG_CONFIG_LOCK) == b"\1"
serial = bytes2int(data.get(TAG_SERIAL, b"\0")) or None
ff_value = bytes2int(data.get(TAG_FORM_FACTOR, b"\0"))
@@ -253,9 +276,12 @@
if TAG_NFC_SUPPORTED in data: # YK with NFC
supported[TRANSPORT.NFC] =
CAPABILITY(bytes2int(data[TAG_NFC_SUPPORTED]))
enabled[TRANSPORT.NFC] =
CAPABILITY(bytes2int(data[TAG_NFC_ENABLED]))
+ nfc_restricted = data.get(TAG_NFC_RESTRICTED, b"\0") == b"\1"
+ pin_complexity = data.get(TAG_PIN_COMPLEXITY, b"\0") == b"\1"
+ reset_blocked = CAPABILITY(bytes2int(data.get(TAG_RESET_BLOCKED,
b"\0")))
return cls(
- DeviceConfig(enabled, auto_eject_to, chal_resp_to, flags),
+ DeviceConfig(enabled, auto_eject_to, chal_resp_to, flags,
nfc_restricted),
serial,
version,
form_factor,
@@ -263,6 +289,8 @@
locked,
fips,
sky,
+ pin_complexity,
+ reset_blocked,
)
@@ -320,7 +348,7 @@
...
@abc.abstractmethod
- def read_config(self) -> bytes:
+ def read_config(self, page: int = 0) -> bytes:
...
@abc.abstractmethod
@@ -347,8 +375,10 @@
return # ProgSeq isn't updated by set mode when empty
raise
- def read_config(self):
- response = self.protocol.send_and_receive(SLOT_YK4_CAPABILITIES)
+ def read_config(self, page: int = 0):
+ response = self.protocol.send_and_receive(
+ SLOT_YK4_CAPABILITIES, int2bytes(page)
+ )
r_len = response[0]
if check_crc(response[: r_len + 1 + 2]):
return response[: r_len + 1]
@@ -397,8 +427,8 @@
else:
self.protocol.send_apdu(0, INS_SET_MODE, P1_DEVICE_CONFIG, 0, data)
- def read_config(self):
- return self.protocol.send_apdu(0, INS_READ_CONFIG, 0, 0)
+ def read_config(self, page: int = 0):
+ return self.protocol.send_apdu(0, INS_READ_CONFIG, page, 0)
def write_config(self, config):
self.protocol.send_apdu(0, INS_WRITE_CONFIG, 0, 0, config)
@@ -430,8 +460,8 @@
def set_mode(self, data):
self.ctap.call(CTAP_YUBIKEY_DEVICE_CONFIG, data)
- def read_config(self):
- return self.ctap.call(CTAP_READ_CONFIG)
+ def read_config(self, page: int = 0):
+ return self.ctap.call(CTAP_READ_CONFIG, int2bytes(page))
def write_config(self, config):
self.ctap.call(CTAP_WRITE_CONFIG, config)
@@ -464,7 +494,20 @@
def read_device_info(self) -> DeviceInfo:
"""Get detailed information about the YubiKey."""
require_version(self.version, (4, 1, 0))
- return DeviceInfo.parse(self.backend.read_config(), self.version)
+ more_data = True
+ tlvs = {}
+ page = 0
+ while more_data:
+ logger.debug(f"Reading DeviceInfo page: {page}")
+ encoded = self.backend.read_config(page)
+ if len(encoded) - 1 != encoded[0]:
+ raise BadResponseError("Invalid length")
+ data = Tlv.parse_dict(encoded[1:])
+ more_data = data.pop(TAG_MORE_DATA, 0) == b"\1"
+ tlvs.update(data)
+ page += 1
+
+ return DeviceInfo.parse_tlvs(tlvs, self.version)
def write_device_config(
self,
@@ -485,7 +528,7 @@
raise ValueError("Lock code must be 16 bytes")
if new_lock_code is not None and len(new_lock_code) != 16:
raise ValueError("Lock code must be 16 bytes")
- config = config or DeviceConfig({}, None, None, None)
+ config = config or DeviceConfig()
logger.debug(
f"Writing device config: {config}, reboot: {reboot}, "
f"current lock code: {cur_lock_code is not None}, "
@@ -520,9 +563,21 @@
if USB_INTERFACE.OTP in mode.interfaces:
usb_enabled |= CAPABILITY.OTP
if USB_INTERFACE.CCID in mode.interfaces:
- usb_enabled |= CAPABILITY.OATH | CAPABILITY.PIV |
CAPABILITY.OPENPGP
+ usb_enabled |= (
+ CAPABILITY.OATH
+ | CAPABILITY.PIV
+ | CAPABILITY.OPENPGP
+ | CAPABILITY.HSMAUTH
+ | 0x400 # Management over CCID bit
+ )
if USB_INTERFACE.FIDO in mode.interfaces:
usb_enabled |= CAPABILITY.U2F | CAPABILITY.FIDO2
+
+ # Overlay with supported capabilities
+ supported = self.read_device_info().supported_capabilities.get(
+ TRANSPORT.USB, 0
+ )
+ usb_enabled = usb_enabled & supported
logger.debug(f"Delegating to DeviceConfig with usb_enabled:
{usb_enabled}")
# N.B: reboot=False, since we're using the older set_mode command
self.write_device_config(
@@ -530,7 +585,6 @@
{TRANSPORT.USB: usb_enabled},
auto_eject_timeout,
chalresp_timeout,
- None,
)
)
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/yubikit/openpgp.py
new/yubikey_manager-5.4.0/yubikit/openpgp.py
--- old/yubikey_manager-5.3.0/yubikit/openpgp.py 2024-01-29
09:10:25.569461800 +0100
+++ new/yubikey_manager-5.4.0/yubikit/openpgp.py 2024-03-26
14:52:50.948619800 +0100
@@ -895,9 +895,11 @@
raise ValueError("RSA keys with e != 65537 are not supported!")
return RsaAttributes.create(
RSA_SIZE(private_key.key_size),
- RSA_IMPORT_FORMAT.CRT_W_MOD
- if 0 < version[0] < 4
- else RSA_IMPORT_FORMAT.STANDARD,
+ (
+ RSA_IMPORT_FORMAT.CRT_W_MOD
+ if 0 < version[0] < 4
+ else RSA_IMPORT_FORMAT.STANDARD
+ ),
)
return EcAttributes.create(key_ref, OID._from_key(private_key))
@@ -1521,7 +1523,12 @@
EXTENDED_CAPABILITY_FLAGS.ALGORITHM_ATTRIBUTES_CHANGEABLE
in self.extended_capabilities.flags
):
- attributes = RsaAttributes.create(key_size)
+ import_format = (
+ RSA_IMPORT_FORMAT.CRT_W_MOD
+ if 0 < self.version[0] < 4 # Use CRT for NEO
+ else RSA_IMPORT_FORMAT.STANDARD
+ )
+ attributes = RsaAttributes.create(key_size, import_format)
self.set_algorithm_attributes(key_ref, attributes)
elif key_size != RSA_SIZE.RSA2048:
raise NotSupportedError("Algorithm attributes not supported")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/yubikit/piv.py
new/yubikey_manager-5.4.0/yubikit/piv.py
--- old/yubikey_manager-5.3.0/yubikit/piv.py 2024-01-29 09:10:25.569461800
+0100
+++ new/yubikey_manager-5.4.0/yubikit/piv.py 2024-03-26 14:52:50.948619800
+0100
@@ -443,9 +443,11 @@
# FIPS
if (4, 4, 0) <= version < (4, 5, 0):
if key_type == KEY_TYPE.RSA1024:
- raise NotSupportedError("RSA 1024 not supported on YubiKey FIPS")
+ raise NotSupportedError("RSA 1024 not supported on YubiKey FIPS (4
Series)")
if pin_policy == PIN_POLICY.NEVER:
- raise NotSupportedError("PIN_POLICY.NEVER not allowed on YubiKey
FIPS")
+ raise NotSupportedError(
+ "PIN_POLICY.NEVER not allowed on YubiKey FIPS (4 Series)"
+ )
# New key types
if version < (5, 7, 0) and key_type in (
@@ -1153,9 +1155,11 @@
TAG_DYN_AUTH,
Tlv(TAG_AUTH_RESPONSE)
+ Tlv(
- TAG_AUTH_EXPONENTIATION
- if exponentiation
- else TAG_AUTH_CHALLENGE,
+ (
+ TAG_AUTH_EXPONENTIATION
+ if exponentiation
+ else TAG_AUTH_CHALLENGE
+ ),
message,
),
),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/yubikey_manager-5.3.0/yubikit/support.py
new/yubikey_manager-5.4.0/yubikit/support.py
--- old/yubikey_manager-5.3.0/yubikit/support.py 2023-09-17
13:05:24.084793000 +0200
+++ new/yubikey_manager-5.4.0/yubikit/support.py 2024-03-26
14:52:50.948619800 +0100
@@ -403,7 +403,7 @@
return "YubiKey"
elif major_version == 4:
if info.is_fips:
- device_name = "YubiKey FIPS"
+ device_name = "YubiKey FIPS (4 Series)"
elif usb_supported == CAPABILITY.OTP | CAPABILITY.U2F:
device_name = "YubiKey Edge"
else: