mirror of
https://github.com/polhenarejos/pico-openpgp.git
synced 2026-08-23 13:07:11 +01:00
Add pytest tests for PIV
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
@@ -31,7 +31,7 @@ RUN apt install -y libccid \
|
||||
tpm2-tools \
|
||||
swtpm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip3 install pytest pycvc cryptography pyscard --break-system-packages
|
||||
RUN pip3 install pytest pycvc cryptography pyscard 'yubikey-manager>=5.6,<6' --break-system-packages
|
||||
RUN git clone https://github.com/Yubico/yubico-piv-tool
|
||||
WORKDIR /yubico-piv-tool
|
||||
RUN git checkout tags/yubico-piv-tool-2.5.1
|
||||
|
||||
@@ -30,7 +30,7 @@ RUN apt install -y libccid \
|
||||
libtss2-dev \
|
||||
tpm2-tools \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip3 install pytest pycvc cryptography pyscard
|
||||
RUN pip3 install pytest pycvc cryptography pyscard 'yubikey-manager>=5.6,<6'
|
||||
RUN git clone https://github.com/Yubico/yubico-piv-tool
|
||||
WORKDIR /yubico-piv-tool
|
||||
RUN git checkout tags/yubico-piv-tool-2.5.1
|
||||
|
||||
@@ -26,7 +26,7 @@ RUN apt install -y libccid \
|
||||
check \
|
||||
gengetopt \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip3 install pytest pycvc cryptography pyscard
|
||||
RUN pip3 install pytest pycvc cryptography pyscard 'yubikey-manager>=5.6,<6'
|
||||
RUN git clone https://github.com/Yubico/yubico-piv-tool
|
||||
WORKDIR /yubico-piv-tool
|
||||
RUN git checkout tags/yubico-piv-tool-2.5.1
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
|
||||
from card_reader import get_ccid_device
|
||||
|
||||
yubikit_core = pytest.importorskip("yubikit.core")
|
||||
yubikit_piv = pytest.importorskip("yubikit.piv")
|
||||
from yubikit.core import TRANSPORT
|
||||
from yubikit.core.smartcard import ApduError, SW, SmartCardConnection
|
||||
from yubikit.piv import PivSession
|
||||
|
||||
from piv_helpers import DEFAULT_MANAGEMENT_KEY
|
||||
|
||||
|
||||
class PyscardConnection(SmartCardConnection):
|
||||
@property
|
||||
def transport(self):
|
||||
return TRANSPORT.USB
|
||||
|
||||
def __init__(self, reader):
|
||||
self.reader = reader
|
||||
|
||||
def send_and_receive(self, apdu):
|
||||
response = self.reader.send_cmd(apdu)
|
||||
return response[:-2], int.from_bytes(response[-2:], "big")
|
||||
|
||||
def close(self):
|
||||
self.reader.ccid_power_off()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def piv_connection():
|
||||
try:
|
||||
reader = get_ccid_device()
|
||||
except Exception as error:
|
||||
pytest.skip(f"PC/SC service or card unavailable: {error}")
|
||||
connection = PyscardConnection(reader)
|
||||
yield connection
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def piv(piv_connection):
|
||||
try:
|
||||
return PivSession(piv_connection)
|
||||
except ApduError as error:
|
||||
if error.sw in (SW.APPLET_SELECT_FAILED, SW.FILE_NOT_FOUND):
|
||||
pytest.skip("connected card does not expose the PIV application")
|
||||
raise
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def managed_piv(piv):
|
||||
piv.authenticate(DEFAULT_MANAGEMENT_KEY)
|
||||
return piv
|
||||
@@ -0,0 +1,23 @@
|
||||
import pytest
|
||||
|
||||
from yubikit.core.smartcard import ApduError, SW
|
||||
from yubikit.piv import DEFAULT_MANAGEMENT_KEY
|
||||
|
||||
|
||||
DEFAULT_PIN = "123456"
|
||||
DEFAULT_PUK = "12345678"
|
||||
|
||||
|
||||
def assert_apdu_error(call, status):
|
||||
with pytest.raises(ApduError) as raised:
|
||||
call()
|
||||
assert raised.value.sw == status
|
||||
|
||||
|
||||
def delete_key(piv, slot):
|
||||
try:
|
||||
piv.authenticate(DEFAULT_MANAGEMENT_KEY)
|
||||
piv.delete_key(slot)
|
||||
except ApduError as error:
|
||||
if error.sw not in (SW.FILE_NOT_FOUND, SW.REFERENCE_DATA_NOT_FOUND):
|
||||
raise
|
||||
@@ -0,0 +1,18 @@
|
||||
from yubikit.core.smartcard import SW
|
||||
from yubikit.core import Tlv
|
||||
|
||||
from piv_helpers import assert_apdu_error
|
||||
|
||||
|
||||
def test_select_and_version(piv):
|
||||
assert tuple(piv.version) == (5, 7, 0)
|
||||
|
||||
|
||||
def test_discovery_object(piv):
|
||||
# Pico wraps the discovery TLV in 0x53 and appends two implementation bytes.
|
||||
response = piv.protocol.send_apdu(0, 0xCB, 0x3F, 0xFF, Tlv(0x5C, b"\x7e"))
|
||||
assert Tlv.unpack(0x53, response).startswith(bytes.fromhex("4f0ba0000003080000100001005f2f024010"))
|
||||
|
||||
|
||||
def test_unsupported_instruction_has_expected_status(piv):
|
||||
assert_apdu_error(lambda: piv.protocol.send_apdu(0, 0x00, 0, 0), SW.INVALID_INSTRUCTION)
|
||||
@@ -0,0 +1,46 @@
|
||||
import pytest
|
||||
|
||||
from piv_helpers import DEFAULT_PIN, DEFAULT_PUK
|
||||
from yubikit.core import InvalidPinError
|
||||
|
||||
|
||||
def test_pin_metadata_and_authentication(piv):
|
||||
metadata = piv.get_pin_metadata()
|
||||
assert metadata.default_value
|
||||
assert metadata.total_attempts == 3
|
||||
assert metadata.attempts_remaining == 3
|
||||
|
||||
with pytest.raises(InvalidPinError) as raised:
|
||||
piv.verify_pin("000000")
|
||||
assert raised.value.attempts_remaining == 2
|
||||
piv.verify_pin(DEFAULT_PIN)
|
||||
|
||||
|
||||
def test_pin_change_round_trip(piv):
|
||||
new_pin = "654321"
|
||||
changed = False
|
||||
try:
|
||||
piv.change_pin(DEFAULT_PIN, new_pin)
|
||||
changed = True
|
||||
piv.verify_pin(new_pin)
|
||||
with pytest.raises(InvalidPinError):
|
||||
piv.verify_pin(DEFAULT_PIN)
|
||||
piv.verify_pin(new_pin)
|
||||
finally:
|
||||
if changed:
|
||||
piv.change_pin(new_pin, DEFAULT_PIN)
|
||||
|
||||
|
||||
def test_puk_change_and_unblock_round_trip(piv):
|
||||
new_puk = "87654321"
|
||||
new_pin = "135790"
|
||||
changed = False
|
||||
try:
|
||||
piv.change_puk(DEFAULT_PUK, new_puk)
|
||||
changed = True
|
||||
piv.unblock_pin(new_puk, new_pin)
|
||||
piv.verify_pin(new_pin)
|
||||
piv.change_pin(new_pin, DEFAULT_PIN)
|
||||
finally:
|
||||
if changed:
|
||||
piv.change_puk(new_puk, DEFAULT_PUK)
|
||||
@@ -0,0 +1,34 @@
|
||||
import pytest
|
||||
|
||||
from piv_helpers import DEFAULT_MANAGEMENT_KEY, assert_apdu_error
|
||||
from yubikit.core import Tlv
|
||||
from yubikit.core.smartcard import ApduError, SW
|
||||
from yubikit.piv import KEY_TYPE, MANAGEMENT_KEY_TYPE, SLOT
|
||||
|
||||
|
||||
def test_default_management_key_authenticates(piv):
|
||||
piv.authenticate(DEFAULT_MANAGEMENT_KEY)
|
||||
|
||||
|
||||
def test_invalid_management_key_has_expected_status(piv):
|
||||
with pytest.raises(ApduError) as raised:
|
||||
piv.authenticate(b"\x00" * len(DEFAULT_MANAGEMENT_KEY))
|
||||
assert raised.value.sw == SW.DATA_INVALID
|
||||
|
||||
|
||||
def test_management_key_change_round_trip(piv):
|
||||
new_key = b"Pico PIV temporary key".ljust(24, b"!")
|
||||
changed = False
|
||||
try:
|
||||
piv.authenticate(DEFAULT_MANAGEMENT_KEY)
|
||||
piv.set_management_key(MANAGEMENT_KEY_TYPE.AES192, new_key)
|
||||
changed = True
|
||||
piv.authenticate(new_key)
|
||||
finally:
|
||||
if changed:
|
||||
piv.set_management_key(MANAGEMENT_KEY_TYPE.AES192, DEFAULT_MANAGEMENT_KEY)
|
||||
|
||||
|
||||
def test_key_generation_requires_management_authentication(piv):
|
||||
request = Tlv(0xAC, Tlv(0x80, bytes([KEY_TYPE.ECCP256])))
|
||||
assert_apdu_error(lambda: piv.protocol.send_apdu(0, 0x47, 0, SLOT.RETIRED1, request), SW.SECURITY_CONDITION_NOT_SATISFIED)
|
||||
@@ -0,0 +1,97 @@
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
|
||||
from itertools import product
|
||||
|
||||
from piv_helpers import DEFAULT_PIN, assert_apdu_error, delete_key
|
||||
from yubikit.core.smartcard import SW
|
||||
from yubikit.piv import KEY_TYPE, PIN_POLICY, SLOT, TOUCH_POLICY
|
||||
|
||||
|
||||
SUPPORTED_KEY_TYPES = (KEY_TYPE.RSA1024, KEY_TYPE.ECCP256, KEY_TYPE.ECCP384)
|
||||
COMMON_SLOTS = (SLOT.AUTHENTICATION, SLOT.SIGNATURE, SLOT.KEY_MANAGEMENT, SLOT.CARD_AUTH)
|
||||
TEST_SLOTS = COMMON_SLOTS + tuple(SLOT(value) for value in range(0x82, 0x96))
|
||||
KEY_CASES = tuple(product(SUPPORTED_KEY_TYPES, TEST_SLOTS))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("key_type", "slot"), KEY_CASES)
|
||||
def test_generate_and_delete_supported_key_types(managed_piv, key_type, slot):
|
||||
try:
|
||||
public_key = managed_piv.generate_key(slot, key_type, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
assert managed_piv.get_slot_metadata(slot).public_key.public_numbers() == public_key.public_numbers()
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
assert_apdu_error(lambda: managed_piv.get_slot_metadata(slot), SW.REFERENCE_DATA_NOT_FOUND)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("key_type", "slot"), KEY_CASES)
|
||||
def test_sign_and_verify_supported_key_types(managed_piv, key_type, slot):
|
||||
message = b"Pico PIV functional test"
|
||||
try:
|
||||
public_key = managed_piv.generate_key(slot, key_type, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
managed_piv.verify_pin(DEFAULT_PIN)
|
||||
signature = managed_piv.sign(slot, key_type, message, hashes.SHA256(), padding.PKCS1v15() if key_type.algorithm.value == "rsa" else None)
|
||||
if isinstance(public_key, rsa.RSAPublicKey):
|
||||
public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA256())
|
||||
else:
|
||||
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("slot", COMMON_SLOTS)
|
||||
def test_common_slots_can_sign(managed_piv, slot):
|
||||
key_type = KEY_TYPE.ECCP256
|
||||
message = b"Pico PIV common slot test"
|
||||
try:
|
||||
public_key = managed_piv.generate_key(slot, key_type, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
managed_piv.verify_pin(DEFAULT_PIN)
|
||||
signature = managed_piv.sign(slot, key_type, message, hashes.SHA256(), None)
|
||||
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
|
||||
|
||||
def test_sign_requires_pin(managed_piv):
|
||||
slot = SLOT.RETIRED1
|
||||
try:
|
||||
managed_piv.generate_key(slot, KEY_TYPE.ECCP256, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
assert_apdu_error(lambda: managed_piv.sign(slot, KEY_TYPE.ECCP256, b"no PIN", hashes.SHA256(), None), SW.SECURITY_CONDITION_NOT_SATISFIED)
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
|
||||
|
||||
def test_rsa_decipher(managed_piv):
|
||||
slot = SLOT.KEY_MANAGEMENT
|
||||
plaintext = b"Pico PIV RSA decipher"
|
||||
try:
|
||||
public_key = managed_piv.generate_key(slot, KEY_TYPE.RSA1024, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
oaep = padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
|
||||
ciphertext = public_key.encrypt(plaintext, oaep)
|
||||
managed_piv.verify_pin(DEFAULT_PIN)
|
||||
assert managed_piv.decrypt(slot, ciphertext, oaep) == plaintext
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key_type", "private_key"),
|
||||
[
|
||||
(KEY_TYPE.RSA1024, rsa.generate_private_key(public_exponent=65537, key_size=1024)),
|
||||
(KEY_TYPE.ECCP256, ec.generate_private_key(ec.SECP256R1())),
|
||||
],
|
||||
)
|
||||
def test_import_key_and_use_it(managed_piv, key_type, private_key):
|
||||
slot = SLOT.RETIRED2
|
||||
try:
|
||||
assert managed_piv.put_key(slot, private_key, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER) == key_type
|
||||
managed_piv.verify_pin(DEFAULT_PIN)
|
||||
message = b"imported PIV key"
|
||||
signature = managed_piv.sign(slot, key_type, message, hashes.SHA256(), padding.PKCS1v15() if key_type.algorithm.value == "rsa" else None)
|
||||
public_key = private_key.public_key()
|
||||
if isinstance(public_key, rsa.RSAPublicKey):
|
||||
public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA256())
|
||||
else:
|
||||
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
@@ -0,0 +1,88 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from itertools import product
|
||||
|
||||
import pytest
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
from piv_helpers import DEFAULT_MANAGEMENT_KEY, DEFAULT_PIN, assert_apdu_error, delete_key
|
||||
from yubikit.core import Tlv
|
||||
from yubikit.core.smartcard import ApduError, SW
|
||||
from yubikit.piv import KEY_TYPE, OBJECT_ID, PIN_POLICY, SLOT, TOUCH_POLICY
|
||||
from ykman.piv import generate_csr, generate_self_signed_certificate
|
||||
|
||||
from test_030_keys import SUPPORTED_KEY_TYPES, TEST_SLOTS
|
||||
|
||||
|
||||
def test_certificate_round_trip(managed_piv):
|
||||
slot = SLOT.RETIRED1
|
||||
ca_key = ec.generate_private_key(ec.SECP256R1())
|
||||
try:
|
||||
public_key = managed_piv.generate_key(slot, KEY_TYPE.ECCP256, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Pico PIV test")])
|
||||
certificate = x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(public_key).serial_number(x509.random_serial_number()).not_valid_before(datetime.now(timezone.utc) - timedelta(minutes=1)).not_valid_after(datetime.now(timezone.utc) + timedelta(days=1)).sign(ca_key, hashes.SHA256())
|
||||
managed_piv.put_certificate(slot, certificate)
|
||||
assert managed_piv.get_certificate(slot).public_bytes(serialization.Encoding.DER) == certificate.public_bytes(serialization.Encoding.DER)
|
||||
managed_piv.delete_certificate(slot)
|
||||
assert_apdu_error(lambda: managed_piv.get_certificate(slot), 0x6A82)
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("key_type", "slot"), tuple(product(SUPPORTED_KEY_TYPES, TEST_SLOTS)))
|
||||
def test_signature_and_certificate_round_trip_for_all_script_cases(managed_piv, key_type, slot):
|
||||
message = b"Pico PIV shell compatibility test"
|
||||
ca_key = ec.generate_private_key(ec.SECP256R1())
|
||||
name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Pico PIV shell compatibility")])
|
||||
ca_certificate = x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(ca_key.public_key()).serial_number(x509.random_serial_number()).not_valid_before(datetime.now(timezone.utc) - timedelta(minutes=1)).not_valid_after(datetime.now(timezone.utc) + timedelta(days=1)).sign(ca_key, hashes.SHA256())
|
||||
try:
|
||||
public_key = managed_piv.generate_key(slot, key_type, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
managed_piv.verify_pin(DEFAULT_PIN)
|
||||
signature = managed_piv.sign(slot, key_type, message, hashes.SHA256(), padding.PKCS1v15() if isinstance(public_key, rsa.RSAPublicKey) else None)
|
||||
if isinstance(public_key, rsa.RSAPublicKey):
|
||||
public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA256())
|
||||
else:
|
||||
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
|
||||
|
||||
csr = generate_csr(managed_piv, slot, public_key, "CN=Pico PIV slot")
|
||||
assert csr.is_signature_valid
|
||||
assert csr.public_key().public_numbers() == public_key.public_numbers()
|
||||
certificate = generate_self_signed_certificate(managed_piv, slot, public_key, "CN=Pico PIV slot", datetime.now(timezone.utc) - timedelta(minutes=1), datetime.now(timezone.utc) + timedelta(days=1))
|
||||
assert certificate.subject == certificate.issuer
|
||||
if isinstance(public_key, rsa.RSAPublicKey):
|
||||
public_key.verify(certificate.signature, certificate.tbs_certificate_bytes, padding.PKCS1v15(), hashes.SHA256())
|
||||
else:
|
||||
public_key.verify(certificate.signature, certificate.tbs_certificate_bytes, ec.ECDSA(hashes.SHA256()))
|
||||
|
||||
certificate = x509.CertificateBuilder().subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Pico PIV slot")])).issuer_name(name).public_key(public_key).serial_number(x509.random_serial_number()).not_valid_before(datetime.now(timezone.utc) - timedelta(minutes=1)).not_valid_after(datetime.now(timezone.utc) + timedelta(days=1)).sign(ca_key, hashes.SHA256())
|
||||
managed_piv.put_certificate(slot, certificate)
|
||||
stored = managed_piv.get_certificate(slot)
|
||||
assert stored.public_bytes(serialization.Encoding.DER) == certificate.public_bytes(serialization.Encoding.DER)
|
||||
ca_key.public_key().verify(stored.signature, stored.tbs_certificate_bytes, ec.ECDSA(stored.signature_hash_algorithm))
|
||||
ca_key.public_key().verify(ca_certificate.signature, ca_certificate.tbs_certificate_bytes, ec.ECDSA(ca_certificate.signature_hash_algorithm))
|
||||
managed_piv.delete_certificate(slot)
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
|
||||
|
||||
def test_chuid_object_round_trip(managed_piv):
|
||||
original = None
|
||||
try:
|
||||
try:
|
||||
original = managed_piv.get_object(OBJECT_ID.CHUID)
|
||||
except ApduError as error:
|
||||
if error.sw != SW.FILE_NOT_FOUND:
|
||||
raise
|
||||
value = b"\x30\x03PIV"
|
||||
managed_piv.put_object(OBJECT_ID.CHUID, value)
|
||||
assert managed_piv.get_object(OBJECT_ID.CHUID) == value
|
||||
finally:
|
||||
managed_piv.authenticate(DEFAULT_MANAGEMENT_KEY)
|
||||
managed_piv.put_object(OBJECT_ID.CHUID, original)
|
||||
|
||||
|
||||
def test_object_write_requires_management_authentication(piv):
|
||||
request = Tlv(0x5C, b"\x5f\xc1\x02") + Tlv(0x53, b"test")
|
||||
assert_apdu_error(lambda: piv.protocol.send_apdu(0, 0xDB, 0x3F, 0xFF, request), SW.SECURITY_CONDITION_NOT_SATISFIED)
|
||||
@@ -0,0 +1,27 @@
|
||||
from itertools import product
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from cryptography import x509
|
||||
|
||||
from piv_helpers import delete_key
|
||||
from yubikit.core import Tlv
|
||||
from yubikit.piv import KEY_TYPE, PIN_POLICY, SLOT, TOUCH_POLICY
|
||||
|
||||
from test_030_keys import SUPPORTED_KEY_TYPES, TEST_SLOTS
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("key_type", "slot"), tuple(product(SUPPORTED_KEY_TYPES, TEST_SLOTS)))
|
||||
def test_attestation_chain_for_all_script_cases(managed_piv, key_type, slot):
|
||||
try:
|
||||
public_key = managed_piv.generate_key(slot, key_type, PIN_POLICY.ONCE, TOUCH_POLICY.NEVER)
|
||||
# Pico stores F9 as raw DER, not as a standard certificate object.
|
||||
response = managed_piv.protocol.send_apdu(0, 0xCB, 0x3F, 0xFF, Tlv(0x5C, b"\x5f\xff\x01"))
|
||||
issuer = x509.load_der_x509_certificate(Tlv.unpack(0x53, response))
|
||||
attested = managed_piv.attest_key(slot)
|
||||
assert attested.issuer == issuer.subject
|
||||
assert attested.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) == public_key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
||||
issuer.public_key().verify(attested.signature, attested.tbs_certificate_bytes, ec.ECDSA(attested.signature_hash_algorithm))
|
||||
finally:
|
||||
delete_key(managed_piv, slot)
|
||||
@@ -0,0 +1,17 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from piv_helpers import DEFAULT_MANAGEMENT_KEY, DEFAULT_PIN, assert_apdu_error
|
||||
from yubikit.core.smartcard import SW
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os.environ.get("PIV_RUN_DESTRUCTIVE"), reason="set PIV_RUN_DESTRUCTIVE=1 to run the destructive PIV reset test")
|
||||
def test_factory_reset_restores_defaults(piv):
|
||||
piv.reset()
|
||||
assert piv.get_pin_metadata().default_value
|
||||
assert piv.get_puk_metadata().default_value
|
||||
assert piv.get_management_key_metadata().default_value
|
||||
piv.verify_pin(DEFAULT_PIN)
|
||||
piv.authenticate(DEFAULT_MANAGEMENT_KEY)
|
||||
assert_apdu_error(lambda: piv.get_slot_metadata(0x82), SW.REFERENCE_DATA_NOT_FOUND)
|
||||
@@ -0,0 +1,4 @@
|
||||
pytest>=8,<9
|
||||
cryptography>=43,<47
|
||||
pyscard>=2,<3
|
||||
yubikey-manager>=5.6,<6
|
||||
Reference in New Issue
Block a user