Skip to content

Commit

Permalink
Merge branch 'release/5.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Jun 26, 2024
2 parents ea723b6 + 43abb8e commit af39ea4
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 32 deletions.
15 changes: 15 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
* Version 5.5.0 (released 2024-06-26)
* Add Secure Channel support to smartcard sessions.
* Support extended APDUs in the "apdu" command (this is now the default).
* HSMAuth: Treat management key as a PIN/password instead of a key, adding new CLI
commands.
* PIV: Deprecate explicit passing of management key type when authenticating.
* CLI: Add "config nfc --restrict" command to set "NFC restricted mode".
* CLI: Display more information about PIN complexity and FIPS status for compatible
YubiKeys.
* CLI: Improved error messages for illegal values of PIV PIN and PUK.
* CLI: Drop error messages for old 3.x commands.
* CLI: Removal of --upload for YubiCloud credentials. Export to CSV and upload via web
instead.
* CLI: Add more detailed information to the CLI output for several commands.

* Version 5.4.0 (released 2024-03-27)
* Support for YubiKey Bio Multi-protocol Edition.
* CLI: Improve error messages for several failures.
Expand Down
4 changes: 2 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ image:https://github.com/Yubico/yubikey-manager/actions/workflows/windows.yml/ba
image:https://github.com/Yubico/yubikey-manager/actions/workflows/macOS.yml/badge.svg["MacOS build", link="https://github.com/Yubico/yubikey-manager/actions/workflows/macOS.yml"]
image:https://github.com/Yubico/yubikey-manager/actions/workflows/ubuntu.yml/badge.svg["Ubuntu build", link="https://github.com/Yubico/yubikey-manager/actions/workflows/ubuntu.yml"]

Python 3.7 (or later) library and command line tool for configuring a YubiKey.
Python 3.8 (or later) library and command line tool for configuring a YubiKey.
If you're looking for the graphical application, it's https://developers.yubico.com/yubikey-manager-qt/[here].

=== Usage
Expand Down Expand Up @@ -95,7 +95,7 @@ Additionally, packages are available from Homebrew and MacPorts.
When running one of the `ykman otp` commands you may run into an error such as:
`Failed to open device for communication: -536870174`. This indicates a problem
with the permission to access the OTP (keyboard) USB interface.

To access a YubiKey over this interface the application needs the `Input
Monitoring` permission. If you are not automatically prompted to grant this
permission, you may have to do so manually. Note that it is the _terminal_ you
Expand Down
4 changes: 4 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,7 @@ def get_version():
"python": ("https://docs.python.org/", None),
"cryptography": ("https://cryptography.io/en/latest/", None),
}


# Custom config
autodoc_member_order = "bysource"
14 changes: 13 additions & 1 deletion man/ykman.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.TH YKMAN "1" "March 2024" "ykman 5.4.0" "User Commands"
.TH YKMAN "1" "June 2024" "ykman 5.0.0" "User Commands"
.SH NAME
ykman \- YubiKey Manager (ykman)
.SH SYNOPSIS
Expand All @@ -14,6 +14,18 @@ specify which YubiKey to interact with by serial number
.TP
\fB\-r\fR, \fB\-\-reader\fR NAME
specify a YubiKey by smart card reader name (can't be used with \-\-device or list)
.TP
\fB\-t\fR, \fB\-\-scp\-ca\fR FILENAME
specify the CA to use to verify the SCP11 card key (CA\-KLCC)
.TP
\fB\-s\fR, \fB\-\-scp\fR CRED
specify private key and certificate chain for secure messaging, can be used multiple times to provide key and certificates in multiple files (private key, certificates in leaf\-last order), OR SCP03 keys in hex separated by colon (:) K\-ENC:K\-MAC[:K\-DEK]
.TP
\fB\-p\fR, \fB\-\-scp\-password\fR PASSWORD
specify a password required to access the
.TP
\fB\-\-scp\fR \fBfile\fR, \fBif\fR \fBneeded\fR

.TP
\fB\-l\fR, \fB\-\-log\-level\fR [ERROR|WARNING|INFO|DEBUG|TRAFFIC]
enable logging at given verbosity level
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "yubikey-manager"
version = "5.4.1-dev.0"
version = "5.5.1-dev.0"
description = "Tool for managing your YubiKey configuration."
authors = ["Dain Nilsson <dain@yubico.com>"]
license = "BSD"
Expand Down
2 changes: 1 addition & 1 deletion ykman/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

__version__ = "5.4.1-dev.0"
__version__ = "5.5.1-dev.0"
15 changes: 14 additions & 1 deletion ykman/_cli/otp.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,24 @@ def otp(ctx, access_code):

def _get_session(ctx, types=[OtpConnection, SmartCardConnection]):
dev = ctx.obj["device"]

resolve_scp = ctx.obj.get("scp")
if resolve_scp:
if SmartCardConnection in types:
types = [SmartCardConnection]
else:
raise CliFail("SCP can only be used with SmartCardConnection")

for conn_type in types:
if dev.supports_connection(conn_type):
conn = dev.open_connection(conn_type)
ctx.call_on_close(conn.close)
return YubiOtpSession(conn)
if resolve_scp:
scp_params = resolve_scp(conn)
else:
scp_params = None
return YubiOtpSession(conn, scp_params)

raise CliFail(
"The connection type required for this command is not supported/enabled on the "
"YubiKey."
Expand Down
11 changes: 6 additions & 5 deletions ykman/_cli/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,10 @@ def find_scp11_params(
else:
raise ValueError(f"No SCP key found matching KID=0x{kid:x}")
try:
chain = scp.get_certificate_bundle(KeyRef(kid, kvn))
ref = KeyRef(kid, kvn)
chain = scp.get_certificate_bundle(ref)
if not chain:
raise ValueError(f"No certificate chain stored for {ref}")
if ca:
logger.debug("Validating KLCC CA using supplied file")
parent = parse_certificates(ca, None)[0]
Expand All @@ -380,11 +383,9 @@ def find_scp11_params(
logger.info("No CA supplied, skipping KLCC CA validation")

pub_key = chain[-1].public_key()
return Scp11KeyParams(KeyRef(kid, kvn), pub_key)
return Scp11KeyParams(ref, pub_key)
except ApduError:
raise ValueError(
f"Unable to get SCP key paramaters (KID=0x{kid:x}, KVN=ox{kvn:x})"
)
raise ValueError(f"Unable to get SCP key paramaters ({ref})")


def get_scp_params(
Expand Down
16 changes: 16 additions & 0 deletions yubikit/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from typing import Optional, Union, Mapping
import abc
import struct
import warnings
import logging

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -560,10 +561,19 @@ def __init__(
)

def close(self) -> None:
"""Close the underlying connection.
:deprecated: call .close() on the underlying connection instead.
"""
warnings.warn(
"Deprecated: call .close() on the underlying connection instead.",
DeprecationWarning,
)
self.backend.close()

@property
def version(self) -> Version:
"""The firmware version of the YubiKey"""
return self.backend.version

def read_device_info(self) -> DeviceInfo:
Expand Down Expand Up @@ -676,6 +686,12 @@ def set_mode(
logger.info("Mode configuration written")

def device_reset(self) -> None:
"""Global factory reset.
This is only available for YubiKey Bio, which has a PIN that is shared between
applications. This will factory reset the global PIN as well as the associated
applications.
"""
if not isinstance(self.backend, _ManagementSmartCardBackend):
raise NotSupportedError("Device reset can only be performed over CCID")
logger.debug("Performing device reset")
Expand Down
27 changes: 17 additions & 10 deletions yubikit/oath.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,22 +290,25 @@ def __init__(

@property
def version(self) -> Version:
"""The OATH application version."""
"""The version of the OATH application."""
return self._version

@property
def device_id(self) -> str:
"""The device ID."""
"""The device ID.
A random static identifier that is re-generated on reset.
"""
return self._device_id

@property
def has_key(self) -> bool:
"""If True, the YubiKey has an access key."""
"""If True, the YubiKey has an access key set."""
return self._has_key

@property
def locked(self) -> bool:
"""If True, the OATH application is password protected."""
"""If True, the OATH application is currently locked via an access key."""
return self._challenge is not None

def reset(self) -> None:
Expand All @@ -319,7 +322,7 @@ def reset(self) -> None:
self._device_id = _get_device_id(self._salt)

def derive_key(self, password: str) -> bytes:
"""Derive a key from password.
"""Derive an access key from a password.
:param password: The derivation password.
"""
Expand All @@ -328,6 +331,8 @@ def derive_key(self, password: str) -> bytes:
def validate(self, key: bytes) -> None:
"""Validate authentication with access key.
This unlocks the session for use.
:param key: The access key.
"""
logger.debug("Unlocking session")
Expand All @@ -344,7 +349,7 @@ def validate(self, key: bytes) -> None:
self._neo_unlock_workaround = False

def set_key(self, key: bytes) -> None:
"""Set access key for authentication.
"""Set an access key for authentication.
:param key: The access key.
"""
Expand All @@ -369,9 +374,9 @@ def set_key(self, key: bytes) -> None:
self.validate(key)

def unset_key(self) -> None:
"""Remove access code.
"""Remove the access key.
WARNING: This removes authentication.
This removes the need to authentication a session before using it.
"""
self.protocol.send_apdu(0, INS_SET_CODE, 0, 0, Tlv(TAG_KEY))
logger.info("Access code removed")
Expand All @@ -380,7 +385,7 @@ def unset_key(self) -> None:
def put_credential(
self, credential_data: CredentialData, touch_required: bool = False
) -> Credential:
"""Add a OATH credential.
"""Add an OATH credential.
:param credential_data: The credential data.
:param touch_required: The touch policy.
Expand Down Expand Up @@ -486,7 +491,9 @@ def calculate_all(
) -> Mapping[Credential, Optional[Code]]:
"""Calculate codes for all OATH credentials on the YubiKey.
:param timestamp: A timestamp.
This excludes credentials which require touch as well as HOTP credentials.
:param timestamp: A timestamp used for the TOTP challenge.
"""
timestamp = int(timestamp or time())
challenge = _get_challenge(timestamp, DEFAULT_PERIOD)
Expand Down
33 changes: 31 additions & 2 deletions yubikit/piv.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def check_key_support(
This method will return None if the key (with PIN and touch policies) is supported,
or it will raise a NotSupportedError if it is not.
THIS FUNCTION IS DEPRECATED! Use PivSession.check_key_support() instead.
:deprecated: Use PivSession.check_key_support() instead.
"""
warnings.warn(
"Deprecated: use PivSession.check_key_support() instead.",
Expand Down Expand Up @@ -524,13 +524,21 @@ def __init__(

@property
def version(self) -> Version:
"""The version of the PIV application,
typically the same as the YubiKey firmware."""
return self._version

@property
def management_key_type(self) -> MANAGEMENT_KEY_TYPE:
"""The algorithm of the management key currently in use."""
return self._management_key_type

def reset(self) -> None:
"""Factory reset the PIV application data.
This deletes all user-data from the PIV application, and resets the default
values for PIN, PUK, and management key.
"""
logger.debug("Preparing PIV reset")

try:
Expand Down Expand Up @@ -677,7 +685,7 @@ def set_management_key(
logger.info("Management key set")

def verify_pin(self, pin: str) -> None:
"""Verify the PIN.
"""Verify the user by PIN.
:param pin: The PIN.
"""
Expand All @@ -695,6 +703,17 @@ def verify_pin(self, pin: str) -> None:
def verify_uv(
self, temporary_pin: bool = False, check_only: bool = False
) -> Optional[bytes]:
"""Verify the user by fingerprint (YubiKey Bio only).
Fingerprint verification will allow usage of private keys which have a PIN
policy allowing MATCH. For those using MATCH_ALWAYS, the fingerprint must be
verified just prior to using the key, or by first requesting a temporary PIN
and then later verifying the PIN just prior to key use.
:param temporary_pin: Request a temporary PIN for later use within the session.
:param check_only: Do not verify the user, instead immediately throw an
InvalidPinException containing the number of remaining attempts.
"""
logger.debug("Verifying UV")
if temporary_pin and check_only:
raise ValueError(
Expand Down Expand Up @@ -724,6 +743,10 @@ def verify_uv(
return response if temporary_pin else None

def verify_temporary_pin(self, pin: bytes) -> None:
"""Verify the user via temporary PIN.
:param pin: A temporary PIN previously requested via verify_uv.
"""
logger.debug("Verifying temporary PIN")
if len(pin) != TEMPORARY_PIN_LEN:
raise ValueError(f"Temporary PIN must be exactly {TEMPORARY_PIN_LEN} bytes")
Expand Down Expand Up @@ -856,6 +879,12 @@ def get_slot_metadata(self, slot: SLOT) -> SlotMetadata:
)

def get_bio_metadata(self) -> BioMetadata:
"""Get YubiKey Bio metadata.
This tells you if fingerprints are enrolled or not, how many fingerprint
verification attempts remain, and whether or not a temporary PIN is currently
active.
"""
logger.debug("Getting bio metadata")
try:
data = Tlv.parse_dict(
Expand Down
Loading

0 comments on commit af39ea4

Please sign in to comment.