Files
2026-08-26 01:51:53 +02:00

626 lines
22 KiB
Python

import hashlib
import os
import pytest
from card_const import FACTORY_PASSPHRASE_PW1, FACTORY_PASSPHRASE_PW3
from constants_for_test import PW1_TEST4, PW3_TEST0
OPENPGP_AID = b"\xD2\x76\x00\x01\x24\x01"
PIV_AID = bytes.fromhex("A000000308")
PW1_DEFAULT = b"123456"
PW3_DEFAULT = b"12345678"
PIV_MGM_KEY = bytes.fromhex("0102030405060708" * 3)
MODE_PW1 = 0x81
MODE_PW2 = 0x82
MODE_PW3 = 0x83
INS_VERIFY = 0x20
INS_CHANGE_PIN = 0x24
INS_RESET_RETRY = 0x2C
INS_PSO = 0x2A
INS_INTERNAL_AUT = 0x88
INS_PUT_DATA = 0xDA
INS_IMPORT = 0xDB
INS_GET_DATA = 0xCA
INS_KEYPAIR_GEN = 0x47
INS_MSE = 0x22
INS_CHALLENGE = 0x84
INS_ACTIVATE = 0x44
INS_TERMINATE = 0xE6
INS_SELECT_DATA = 0xA5
OID_P256 = bytes.fromhex("2A8648CE3D030107")
OID_ED25519 = bytes.fromhex("2B06010401DA470F01")
OID_CV25519 = bytes.fromhex("2B060104019755010501")
ATTR_P256_ECDSA = bytes([0x13]) + OID_P256
ATTR_P256_ECDH = bytes([0x12]) + OID_P256
ATTR_ED25519 = bytes([0x16]) + OID_ED25519
ATTR_CV25519 = bytes([0x12]) + OID_CV25519
ATTR_RSA2K = bytes([0x01, 0x08, 0x00, 0x00, 0x20, 0x00])
CRT_SIG, CRT_DEC, CRT_AUT = 0xB6, 0xB8, 0xA4
DI_SHA256 = bytes.fromhex("3031300d060960864801650304020105000420")
def reader(card):
return card._OpenPGP_Card__reader
def compose_apdu(ins, p1, p2, data=b"", le=None, cls=0x00):
data = bytes(data)
apdu = bytearray([cls, ins, p1, p2])
if data:
if len(data) < 256:
apdu.append(len(data))
else:
apdu += bytes([0x00, len(data) >> 8, len(data) & 0xFF])
apdu += data
if le is not None:
if le < 256:
apdu.append(le)
else:
apdu += bytes([le >> 8, le & 0xFF])
return bytes(apdu)
def raw(card, ins, p1, p2, data=b"", le=None, cls=0x00):
cmd = compose_apdu(ins, p1, p2, data, cls=cls, le=le)
resp = reader(card).send_cmd(cmd)
body, sw = resp[:-2], resp[-2:]
while sw[0] == 0x61:
more = reader(card).send_cmd(compose_apdu(0xC0, 0x00, 0x00, le=sw[1]))
body += more[:-2]
sw = more[-2:]
return body, sw
def expect(card, ins, p1, p2, data=b"", le=None, sw=b"\x90\x00", cls=0x00):
body, got = raw(card, ins, p1, p2, data, le=le, cls=cls)
assert got == sw
return body
def select_openpgp(card):
return expect(card, 0xA4, 0x04, 0x00, OPENPGP_AID, le=0)
def select_piv(card):
return expect(card, 0xA4, 0x04, 0x00, PIV_AID, le=0)
def is_retry_error(exc):
return exc.args and isinstance(exc.args[0], str) and exc.args[0].startswith("63c")
def verify_pw3(card):
last_error = None
for passwd in (FACTORY_PASSPHRASE_PW3, PW3_TEST0):
try:
assert card.verify(3, passwd)
return passwd
except ValueError as exc:
if not is_retry_error(exc):
raise
last_error = exc
raise last_error
def verify_pw1(card):
try:
assert card.verify(1, PW1_TEST4)
return PW1_TEST4
except ValueError as exc:
if not is_retry_error(exc):
raise
assert card.verify(1, FACTORY_PASSPHRASE_PW1)
assert card.change_passwd(1, FACTORY_PASSPHRASE_PW1, PW1_TEST4)
assert card.verify(1, PW1_TEST4)
return PW1_TEST4
def ber_len(n):
if n < 0x80:
return bytes([n])
if n < 0x100:
return bytes([0x81, n])
return bytes([0x82, n >> 8, n & 0xFF])
def import_ec_apdu(crt, scalar):
tmpl = bytes([0x92, len(scalar)])
f7f48 = bytes([0x7F, 0x48, len(tmpl)]) + tmpl
f5f48 = bytes([0x5F, 0x48, len(scalar)]) + scalar
body = bytes([crt, 0x00]) + f7f48 + f5f48
return bytes([0x4D, len(body)]) + body
def rsa_import_header(crt, e, p, q):
tmpl = b"".join(bytes([tag]) + ber_len(len(v)) for tag, v in ((0x91, e), (0x92, p), (0x93, q)))
f7f48 = bytes([0x7F, 0x48]) + ber_len(len(tmpl)) + tmpl
kd = e + p + q
f5f48 = bytes([0x5F, 0x48]) + ber_len(len(kd)) + kd
body = bytes([crt, 0x00]) + f7f48 + f5f48
return bytes([0x4D]) + ber_len(len(body)) + body
def rsa_key_parts(priv):
nums = priv.private_numbers()
e = priv.public_key().public_numbers().e.to_bytes(3, "big")
p = nums.p.to_bytes(128, "big")
q = nums.q.to_bytes(128, "big")
return e, p, q
def gen_apdu_data(crt):
return bytes([crt, 0x00])
def parse_tlv(data):
out, i = {}, 0
data = bytes(data)
while i < len(data):
tag = data[i]
i += 1
ln = data[i]
i += 1
if ln == 0x81:
ln = data[i]
i += 1
elif ln == 0x82:
ln = (data[i] << 8) | data[i + 1]
i += 2
out[tag] = data[i:i + ln]
i += ln
return out
def tlv(tag, value):
value = bytes(value)
return bytes([tag]) + ber_len(len(value)) + value
def authenticate_piv_management(card):
pytest.importorskip("cryptography")
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
response = expect(card, 0x87, 0x0A, 0x9B, tlv(0x7C, tlv(0x80, b"")))
encrypted_witness = parse_tlv(parse_tlv(response)[0x7C])[0x80]
decryptor = Cipher(algorithms.AES(PIV_MGM_KEY), modes.ECB()).decryptor()
witness = decryptor.update(encrypted_witness) + decryptor.finalize()
challenge = os.urandom(16)
request = tlv(0x7C, tlv(0x80, witness) + tlv(0x81, challenge))
response = expect(card, 0x87, 0x0A, 0x9B, request)
encrypted_challenge = parse_tlv(parse_tlv(response)[0x7C])[0x82]
encryptor = Cipher(algorithms.AES(PIV_MGM_KEY), modes.ECB()).encryptor()
assert encrypted_challenge == encryptor.update(challenge) + encryptor.finalize()
def parse_ec_point(do):
do = bytes(do)
assert do[:2] == b"\x7f\x49"
i = 2
i += 1 + (do[i] & 0x7F) if do[i] & 0x80 else 1
assert do[i] == 0x86
i += 1
if do[i] & 0x80:
nl = do[i] & 0x7F
plen = int.from_bytes(do[i + 1:i + 1 + nl], "big")
i += 1 + nl
else:
plen = do[i]
i += 1
return do[i:i + plen]
def parse_rsa_pub(do):
do = bytes(do)
assert do[:3] == b"\x7f\x49\x82"
i = 5
assert do[i:i + 2] == b"\x81\x82"
i += 2
nlen = int.from_bytes(do[i:i + 2], "big")
i += 2
n = int.from_bytes(do[i:i + nlen], "big")
i += nlen
assert do[i] == 0x82
elen = do[i + 1]
e = int.from_bytes(do[i + 2:i + 2 + elen], "big")
return n, e
def decipher_apdu_data(peer_point):
f86 = bytes([0x86, len(peer_point)]) + peer_point
f7f49 = bytes([0x7F, 0x49, len(f86)]) + f86
return bytes([0xA6, len(f7f49)]) + f7f49
def test_openpgp_status_objects(card):
select_openpgp(card)
assert expect(card, INS_GET_DATA, 0x00, 0x4F, le=0).startswith(OPENPGP_AID)
assert expect(card, INS_GET_DATA, 0x5F, 0x52, le=0) == b"\x00\x31\x84\x73\x80\x01\xc0\x05\x90\x00"
pw = expect(card, INS_GET_DATA, 0x00, 0xC4, le=0)
assert len(pw) == 7
assert pw[0] in (0x00, 0x01)
assert pw[1:5] == bytes([127, 127, 127, 3])
assert pw[5] in (0, 3)
assert pw[6] == 3
app = expect(card, INS_GET_DATA, 0x00, 0x6E, le=0)
assert app and app[0] == 0x4F
assert OPENPGP_AID in app
assert expect(card, 0xF1, 0x00, 0x00, le=0) == b"\x04\x06\x00"
def test_openpgp_fixed_width_status_dos_are_zero_padded(card):
verify_pw3(card)
card.cmd_put_data(0x00, 0xC7, b"\xAA")
card.cmd_put_data(0x00, 0xC8, b"\xBB\xCC")
card.cmd_put_data(0x00, 0xC9, b"")
fp = card.cmd_get_data(0x00, 0xC5)
assert len(fp) == 60
assert fp[:20] == b"\xAA" + bytes(19)
assert fp[20:40] == b"\xBB\xCC" + bytes(18)
assert fp[40:] == bytes(20)
card.cmd_put_data(0x00, 0xCA, b"\x11")
card.cmd_put_data(0x00, 0xCB, b"")
card.cmd_put_data(0x00, 0xCC, b"\x22\x33")
cafp = card.cmd_get_data(0x00, 0xC6)
assert len(cafp) == 60
assert cafp[:20] == b"\x11" + bytes(19)
assert cafp[20:40] == bytes(20)
assert cafp[40:] == b"\x22\x33" + bytes(18)
card.cmd_put_data(0x00, 0xCE, b"\x01")
card.cmd_put_data(0x00, 0xCF, b"")
card.cmd_put_data(0x00, 0xD0, b"\x02\x03")
ts = card.cmd_get_data(0x00, 0xCD)
assert len(ts) == 12
assert ts[:4] == b"\x01" + bytes(3)
assert ts[4:8] == bytes(4)
assert ts[8:] == b"\x02\x03" + bytes(2)
def test_openpgp_rejects_invalid_algorithm_attributes(card):
verify_pw3(card)
for tag in (0xC1, 0xC2, 0xC3):
_, sw = raw(card, INS_PUT_DATA, 0x00, tag, bytes([0x13]) + bytes(16))
assert sw == b"\x67\x00"
_, sw = raw(card, INS_PUT_DATA, 0x00, tag, b"\x01\x08")
assert sw == b"\x67\x00"
def test_openpgp_pin_put_data_and_change(card):
verify_pw3(card)
assert card.cmd_put_data(0x00, 0x5E, b"alice@example")
assert card.cmd_put_data(0x00, 0x5B, b"Doe<<John")
assert card.cmd_put_data(0x5F, 0x50, b"https://example/key.asc")
assert card.cmd_get_data(0x00, 0x5E) == b"alice@example"
expect(card, INS_VERIFY, 0xFF, MODE_PW3)
_, sw = raw(card, INS_PUT_DATA, 0x00, 0x5E, b"nope")
assert sw == b"\x69\x82"
verify_pw1(card)
with pytest.raises(ValueError, match=r"^63c[0-9a-f]$"):
card.verify(1, b"000000")
verify_pw1(card)
assert card.change_passwd(1, PW1_TEST4, b"654321")
assert card.verify(1, b"654321")
with pytest.raises(ValueError, match=r"^63c[0-9a-f]$"):
card.verify(1, PW1_TEST4)
assert card.change_passwd(1, b"654321", PW1_TEST4)
verify_pw1(card)
def test_openpgp_imported_ec_crypto(card):
crypto = pytest.importorskip("cryptography")
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
verify_pw3(card)
sig_priv = ec.generate_private_key(ec.SECP256R1())
sig_scalar = sig_priv.private_numbers().private_value.to_bytes(32, "big")
card.cmd_put_data(0x00, 0xC1, ATTR_P256_ECDSA)
card.cmd_put_data_odd(0x3F, 0xFF, import_ec_apdu(CRT_SIG, sig_scalar))
msg = b"sign with the imported P-256 key"
sig = card.cmd_pso(0x9E, 0x9A, hashlib.sha256(msg).digest())
assert len(sig) == 64
sig_priv.public_key().verify(
encode_dss_signature(int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big")),
msg,
ec.ECDSA(hashes.SHA256()),
)
aut_priv = ed25519.Ed25519PrivateKey.generate()
aut_seed = aut_priv.private_bytes(
serialization.Encoding.Raw,
serialization.PrivateFormat.Raw,
serialization.NoEncryption(),
)
card.cmd_put_data(0x00, 0xC3, ATTR_ED25519)
card.cmd_put_data_odd(0x3F, 0xFF, import_ec_apdu(CRT_AUT, aut_seed))
chal = b"internal-authenticate challenge"
asig = card.cmd_internal_authenticate(chal)
assert len(asig) == 64
aut_priv.public_key().verify(asig, chal)
dec_priv = ec.generate_private_key(ec.SECP256R1())
dec_scalar = dec_priv.private_numbers().private_value.to_bytes(32, "big")
card.cmd_put_data(0x00, 0xC2, ATTR_P256_ECDH)
card.cmd_put_data_odd(0x3F, 0xFF, import_ec_apdu(CRT_DEC, dec_scalar))
eph_priv = ec.generate_private_key(ec.SECP256R1())
eph_pub = eph_priv.public_key().public_bytes(
serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint
)
z = card.cmd_pso(0x80, 0x86, decipher_apdu_data(eph_pub))
assert z == dec_priv.exchange(ec.ECDH(), eph_priv.public_key())
def test_openpgp_imported_rsa_crypto(card):
pytest.importorskip("cryptography")
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
verify_pw3(card)
sig_priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
card.cmd_put_data(0x00, 0xC1, ATTR_RSA2K)
card.cmd_put_data_odd(0x3F, 0xFF, rsa_import_header(CRT_SIG, *rsa_key_parts(sig_priv)))
msg = b"sign with the imported RSA-2048 key"
sig = card.cmd_pso(0x9E, 0x9A, DI_SHA256 + hashlib.sha256(msg).digest())
assert len(sig) == 256
sig_priv.public_key().verify(sig, msg, padding.PKCS1v15(), hashes.SHA256())
dec_priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
card.cmd_put_data(0x00, 0xC2, ATTR_RSA2K)
card.cmd_put_data_odd(0x3F, 0xFF, rsa_import_header(CRT_DEC, *rsa_key_parts(dec_priv)))
session = os.urandom(24)
ct = dec_priv.public_key().encrypt(session, padding.PKCS1v15())
assert card.cmd_pso(0x80, 0x86, b"\x00" + ct) == session
aut_priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
card.cmd_put_data(0x00, 0xC3, ATTR_RSA2K)
card.cmd_put_data_odd(0x3F, 0xFF, rsa_import_header(CRT_AUT, *rsa_key_parts(aut_priv)))
chal = b"internal-authenticate challenge"
asig = card.cmd_internal_authenticate(DI_SHA256 + hashlib.sha256(chal).digest())
aut_priv.public_key().verify(asig, chal, padding.PKCS1v15(), hashes.SHA256())
def test_openpgp_x25519_decipher(card):
pytest.importorskip("cryptography")
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
verify_pw3(card)
dec = X25519PrivateKey.generate()
scalar_be = dec.private_bytes(
serialization.Encoding.Raw,
serialization.PrivateFormat.Raw,
serialization.NoEncryption(),
)[::-1]
card.cmd_put_data(0x00, 0xC2, ATTR_CV25519)
card.cmd_put_data_odd(0x3F, 0xFF, import_ec_apdu(CRT_DEC, scalar_be))
eph = X25519PrivateKey.generate()
eph_pub = eph.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
assert card.cmd_pso(0x80, 0x86, decipher_apdu_data(b"\x40" + eph_pub)) == dec.exchange(eph.public_key())
def test_openpgp_keygen_public_key_round_trip(card):
pytest.importorskip("cryptography")
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
verify_pw3(card)
card.cmd_put_data(0x00, 0xC1, ATTR_P256_ECDSA)
sig_do = card.cmd_genkey(1)
assert card.cmd_get_public_key(1) == sig_do
sig_pub = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), parse_ec_point(sig_do))
msg = b"sign with generated P-256 key"
sig = card.cmd_pso(0x9E, 0x9A, hashlib.sha256(msg).digest())
sig_pub.verify(
encode_dss_signature(int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big")),
msg,
ec.ECDSA(hashes.SHA256()),
)
card.cmd_put_data(0x00, 0xC3, ATTR_ED25519)
aut_do = card.cmd_genkey(3)
aut_pub = ed25519.Ed25519PublicKey.from_public_bytes(parse_ec_point(aut_do))
chal = b"generated internal-authenticate challenge"
aut_pub.verify(card.cmd_internal_authenticate(chal), chal)
card.cmd_put_data(0x00, 0xC2, ATTR_P256_ECDH)
dec_do = card.cmd_genkey(2)
dec_pub = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), parse_ec_point(dec_do))
eph = ec.generate_private_key(ec.SECP256R1())
eph_pub = eph.public_key().public_bytes(
serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint
)
assert card.cmd_pso(0x80, 0x86, decipher_apdu_data(eph_pub)) == eph.exchange(ec.ECDH(), dec_pub)
def test_openpgp_mse_decipher_slot_swap(card):
pytest.importorskip("cryptography")
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
verify_pw3(card)
dec_priv = ec.generate_private_key(ec.SECP256R1())
aut_priv = ec.generate_private_key(ec.SECP256R1())
card.cmd_put_data(0x00, 0xC2, ATTR_P256_ECDH)
card.cmd_put_data_odd(0x3F, 0xFF, import_ec_apdu(CRT_DEC, dec_priv.private_numbers().private_value.to_bytes(32, "big")))
card.cmd_put_data(0x00, 0xC3, ATTR_P256_ECDH)
card.cmd_put_data_odd(0x3F, 0xFF, import_ec_apdu(CRT_AUT, aut_priv.private_numbers().private_value.to_bytes(32, "big")))
eph = ec.generate_private_key(ec.SECP256R1())
peer = eph.public_key().public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint)
z_dec = card.cmd_pso(0x80, 0x86, decipher_apdu_data(peer))
expect(card, INS_MSE, 0x41, 0xB8, b"\x83\x01\x03")
z_aut = card.cmd_pso(0x80, 0x86, decipher_apdu_data(peer))
assert z_dec == dec_priv.exchange(ec.ECDH(), eph.public_key())
assert z_aut == aut_priv.exchange(ec.ECDH(), eph.public_key())
assert z_dec != z_aut
def test_openpgp_reset_code_and_pw_status(card):
verify_pw3(card)
before = card.cmd_get_data(0x00, 0xC4)
new_flag = 0x00 if before[0] else 0x01
card.cmd_put_data(0x00, 0xC4, bytes([new_flag]))
after = card.cmd_get_data(0x00, 0xC4)
assert after[0] == new_flag
assert after[4:7] == before[4:7]
card.cmd_put_data(0x00, 0xC4, bytes([before[0]]))
card.cmd_put_data_remove(0x00, 0xD3)
pw = card.cmd_get_data(0x00, 0xC4)
assert pw[5] == 0
_, sw = raw(card, INS_RESET_RETRY, 0x00, MODE_PW1, b"12345678" + b"222222")
assert sw == b"\x6A\x88"
verify_pw1(card)
assert card.setup_reset_code(b"reset123")
select_openpgp(card)
pw = card.cmd_get_data(0x00, 0xC4)
assert pw[5] == 3
_, sw = raw(card, INS_CHANGE_PIN, 0x00, 0x82, b"reset123" + b"changed1")
assert sw == b"\x6A\x88"
assert card.reset_passwd_by_resetcode(b"reset123", b"111111")
select_openpgp(card)
assert card.verify(1, b"111111")
select_openpgp(card)
with pytest.raises(ValueError, match=r"^63c[0-9a-f]$"):
card.verify(1, PW1_TEST4)
select_openpgp(card)
assert card.change_passwd(1, b"111111", PW1_TEST4)
select_openpgp(card)
verify_pw1(card)
assert card.change_passwd(1, PW1_TEST4, FACTORY_PASSPHRASE_PW1)
select_openpgp(card)
assert card.verify(1, FACTORY_PASSPHRASE_PW1)
def test_openpgp_aes_pso_round_trip(card):
verify_pw3(card)
card.cmd_put_data(0x00, 0xC2, ATTR_P256_ECDH)
card.cmd_genkey(2)
pt = bytes(range(32))
enc = card.cmd_pso(0x86, 0x80, pt)
assert enc[:1] == b"\x02"
assert enc[1:] != pt
assert card.cmd_pso(0x80, 0x86, enc) == pt
_, sw = raw(card, INS_PSO, 0x86, 0x80, bytes(15), le=0)
assert sw == b"\x67\x00"
def test_openpgp_cardholder_certificate_occurrences(card):
verify_pw3(card)
certs = [bytes([0x30, 0x06, 0xC0 + i] + [i] * 5) for i in range(3)]
for occ, cert in enumerate(certs):
expect(card, INS_SELECT_DATA, occ, 0x04, b"\x60\x04\x5C\x02\x7F\x21")
expect(card, INS_PUT_DATA, 0x7F, 0x21, cert)
for occ, cert in enumerate(certs):
expect(card, INS_SELECT_DATA, occ, 0x04, b"\x60\x04\x5C\x02\x7F\x21")
assert card.cmd_get_data(0x7F, 0x21) == cert
_, sw = raw(card, INS_SELECT_DATA, 0, 0x04, b"\x60\x04\x5C\x02\x00\x65")
assert sw == b"\x6A\x88"
_, sw = raw(card, INS_SELECT_DATA, 3, 0x04, b"\x60\x04\x5C\x02\x7F\x21")
assert sw == b"\x6B\x00"
for occ in range(3):
expect(card, INS_SELECT_DATA, occ, 0x04, b"\x60\x04\x5C\x02\x7F\x21")
expect(card, INS_PUT_DATA, 0x7F, 0x21)
assert card.cmd_get_data(0x7F, 0x21) in (b"", None)
def test_openpgp_get_challenge_activate_and_terminate(card):
c1 = expect(card, INS_CHALLENGE, 0x00, 0x00, le=8)
c2 = expect(card, INS_CHALLENGE, 0x00, 0x00, le=8)
assert len(c1) == len(c2) == 8
assert c1 != c2
assert len(expect(card, INS_CHALLENGE, 0x00, 0x00, le=32)) == 32
expect(card, INS_ACTIVATE, 0x00, 0x00)
expect(card, INS_VERIFY, 0xFF, MODE_PW3)
select_openpgp(card)
_, sw = raw(card, INS_TERMINATE, 0x00, 0x00)
assert sw == b"\x69\x82"
def test_management_applet_config(card):
mgmt_aid = bytes.fromhex("A000000527471117")
expect(card, 0xA4, 0x04, 0x00, mgmt_aid)
data = expect(card, 0x1D, 0x00, 0x00, le=0)
assert data and data[0] == len(data) - 1
fields = parse_tlv(data[1:])
assert fields[0x05] == b"\x05\x07\x00"
assert fields[0x01] in (b"\x18", b"\x00\x18")
assert len(fields[0x02]) == 4
assert fields[0x04] == b"\x01"
select_openpgp(card)
def test_piv_management_auth_flow_binding(card):
pytest.importorskip("cryptography")
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
# A single-auth challenge cannot be replayed as a mutual-auth witness.
select_piv(card)
response = expect(card, 0x87, 0x0A, 0x9B, tlv(0x7C, tlv(0x81, b"")))
challenge = parse_tlv(parse_tlv(response)[0x7C])[0x81]
request = tlv(0x7C, tlv(0x80, challenge) + tlv(0x81, os.urandom(16)))
_, sw = raw(card, 0x87, 0x0A, 0x9B, request)
assert sw == b"\x69\x84"
_, sw = raw(card, 0xDB, 0x3F, 0xFF,
tlv(0x5C, bytes.fromhex("5FC102")) + tlv(0x53, b"blocked"))
assert sw == b"\x69\x82"
# A caller-supplied tag 0x81 is not a management-key encryption oracle.
select_piv(card)
response = expect(card, 0x87, 0x0A, 0x9B, tlv(0x7C, tlv(0x81, b"")))
challenge = parse_tlv(parse_tlv(response)[0x7C])[0x81]
_, sw = raw(card, 0x87, 0x0A, 0x9B, tlv(0x7C, tlv(0x81, challenge)))
assert sw == b"\x6A\x80"
# A mutual-auth witness is bound to the algorithm that issued it.
select_piv(card)
response = expect(card, 0x87, 0x0A, 0x9B, tlv(0x7C, tlv(0x80, b"")))
encrypted_witness = parse_tlv(parse_tlv(response)[0x7C])[0x80]
decryptor = Cipher(algorithms.AES(PIV_MGM_KEY), modes.ECB()).decryptor()
witness = decryptor.update(encrypted_witness) + decryptor.finalize()
request = tlv(0x7C, tlv(0x80, witness[:8]) + tlv(0x81, os.urandom(8)))
_, sw = raw(card, 0x87, 0x03, 0x9B, request)
assert sw == b"\x69\x84"
# Both sanctioned management authentication flows still work.
select_piv(card)
response = expect(card, 0x87, 0x0A, 0x9B, tlv(0x7C, tlv(0x81, b"")))
challenge = parse_tlv(parse_tlv(response)[0x7C])[0x81]
encryptor = Cipher(algorithms.AES(PIV_MGM_KEY), modes.ECB()).encryptor()
encrypted_challenge = encryptor.update(challenge) + encryptor.finalize()
expect(card, 0x87, 0x0A, 0x9B, tlv(0x7C, tlv(0x82, encrypted_challenge)))
select_piv(card)
authenticate_piv_management(card)
select_openpgp(card)
def test_piv_basic_version_serial_and_object_round_trip(card):
select_piv(card)
assert expect(card, 0xFD, 0x00, 0x00, le=0) == b"\x05\x07\x00"
assert len(expect(card, 0xF8, 0x00, 0x00, le=0)) == 4
authenticate_piv_management(card)
expect(card, 0x20, 0x00, 0x80, b"123456\xff\xff")
chuid = bytes([0x30, 0x19, 0xD4, 0xE7, 0x39, 0xDA, 0x73, 0x9C, 0xED])
expect(card, 0xDB, 0x3F, 0xFF, tlv(0x5C, bytes.fromhex("5FC102")) + tlv(0x53, chuid))
got = expect(card, 0xCB, 0x3F, 0xFF, tlv(0x5C, bytes.fromhex("5FC102")), le=0)
assert parse_tlv(got)[0x53] == chuid
select_openpgp(card)