From c1cc75f5b03234ef9fe53b84c5dc844053175eee Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 30 Sep 2016 16:38:27 +0900 Subject: [PATCH] New test suite for OpenPGP card --- .gitignore | 2 + tests/README | 9 ++ tests/card_reader.py | 225 ++++++++++++++++++++++++++++++ tests/conftest.py | 3 + tests/openpgp_card.py | 289 +++++++++++++++++++++++++++++++++++++++ tests/test_empty_card.py | 162 ++++++++++++++++++++++ 6 files changed, 690 insertions(+) create mode 100644 tests/README create mode 100644 tests/card_reader.py create mode 100644 tests/conftest.py create mode 100644 tests/openpgp_card.py create mode 100644 tests/test_empty_card.py diff --git a/.gitignore b/.gitignore index 780e01e..cf834de 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ regnual/regnual.bin regnual/regnual.hex regnual/regnual.elf doc/_build +tests/.cache +tests/__pycache__ diff --git a/tests/README b/tests/README new file mode 100644 index 0000000..e51c643 --- /dev/null +++ b/tests/README @@ -0,0 +1,9 @@ +Here is a test suite for OpenPGP card. + +You need to install: + + $ sudo apt-get install python3-pytest python3-usb + +Please run test by typing: + + $ py.test-3 -x diff --git a/tests/card_reader.py b/tests/card_reader.py new file mode 100644 index 0000000..70825ab --- /dev/null +++ b/tests/card_reader.py @@ -0,0 +1,225 @@ +""" +card_reader.py - a library for smartcard reader + +Copyright (C) 2016 Free Software Initiative of Japan +Author: NIIBE Yutaka + +This file is a part of Gnuk, a GnuPG USB Token implementation. + +Gnuk is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gnuk is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import usb.core +from struct import pack +from usb.util import find_descriptor, claim_interface, get_string, \ + endpoint_type, endpoint_direction, \ + ENDPOINT_TYPE_BULK, ENDPOINT_OUT, ENDPOINT_IN + +# USB class, subclass, protocol +CCID_CLASS = 0x0B +CCID_SUBCLASS = 0x00 +CCID_PROTOCOL_0 = 0x00 + +def ccid_compose(msg_type, seq, slot=0, rsv=0, param=0, data=b""): + return pack(' None + Initialize the DEV of CCID. + device: usb.core.Device object. + """ + + cfg = dev.get_active_configuration() + intf = find_descriptor(cfg, bInterfaceClass=CCID_CLASS, + bInterfaceSubClass=CCID_SUBCLASS, + bInterfaceProtocol=CCID_PROTOCOL_0) + if intf is None: + raise ValueError("Not a CCID device") + + claim_interface(dev, intf) + + for ep in intf: + if endpoint_type(ep.bmAttributes) == ENDPOINT_TYPE_BULK and \ + endpoint_direction(ep.bEndpointAddress) == ENDPOINT_OUT: + self.__bulkout = ep.bEndpointAddress + if endpoint_type(ep.bmAttributes) == ENDPOINT_TYPE_BULK and \ + endpoint_direction(ep.bEndpointAddress) == ENDPOINT_IN: + self.__bulkin = ep.bEndpointAddress + + assert len(intf.extra_descriptors) == 54 + assert intf.extra_descriptors[1] == 33 + + if (intf.extra_descriptors[42] & 0x02): + # Short APDU level exchange + self.__use_APDU = True + elif (intf.extra_descriptors[42] & 0x04): + # Short and extended APDU level exchange + self.__use_APDU = True + elif (intf.extra_descriptors[42] & 0x01): + # TPDU level exchange + self.__use_APDU = False + else: + raise ValueError("Unknown exchange level") + + # Check other bits??? + # intf.extra_descriptors[40] + # intf.extra_descriptors[41] + + self.__dev = dev + self.__timeout = 10000 + self.__seq = 0 + + def get_string(self, num): + return get_string(self.__dev, num) + + def increment_seq(self): + self.__seq = (self.__seq + 1) & 0xff + + def reset_device(self): + try: + self.__dev.reset() + except: + pass + + def ccid_get_result(self): + msg = self.__dev.read(self.__bulkin, 1024, self.__timeout) + if len(msg) < 10: + print(msg) + raise ValueError("ccid_get_result") + msg_type = msg[0] + data_len = msg[1] + (msg[2]<<8) + (msg[3]<<16) + (msg[4]<<24) + slot = msg[5] + seq = msg[6] + status = msg[7] + error = msg[8] + chain = msg[9] + data = msg[10:] + # XXX: check msg_type, data_len, slot, seq, error + return (status, chain, data.tobytes()) + + def ccid_get_status(self): + msg = ccid_compose(0x65, self.__seq) + self.__dev.write(self.__bulkout, msg, self.__timeout) + self.increment_seq() + status, chain, data = self.ccid_get_result() + # XXX: check chain, data + return status + + def ccid_power_on(self): + msg = ccid_compose(0x62, self.__seq, rsv=1) # Vcc=5V + self.__dev.write(self.__bulkout, msg, self.__timeout) + self.increment_seq() + status, chain, data = self.ccid_get_result() + # XXX: check status, chain + self.atr = data + # + if self.__use_APDU == False: + # TPDU reader configuration + pps = b"\xFF\x11\x18\xF6" + ret_pps = self.ccid_send_data_block(pps) + # + param = b"\x18\x10\xFF\x75\x00\xFE\x00" + # ^--- This shoud be adapted by ATR string, see update_param_by_atr + msg = ccid_compose(0x6d, self.__seq, rsv=0x1, data=param) + self.__dev.write(self.__bulkout, msg, self.__timeout) + self.increment_seq() + ret_param = self.ccid_get_result() + # Send an S-block + sblk = b"\x00\xC1\x01\xFE\x3E" + ret_sblk = self.ccid_send_data_block(sblk) + return self.atr + + def ccid_power_off(self): + msg = ccid_compose(0x63, self.__seq) + self.__dev.write(self.__bulkout, msg, self.__timeout) + self.increment_seq() + status, chain, data = self.ccid_get_result() + # XXX: check chain, data + return status + + def ccid_send_data_block(self, data): + msg = ccid_compose(0x6f, self.__seq, data=data) + self.__dev.write(self.__bulkout, msg, self.__timeout) + self.increment_seq() + return self.ccid_get_result() + + def ccid_send_cmd(self, data): + status, chain, data_rcv = self.ccid_send_data_block(data) + if chain == 0: + while status == 0x80: + status, chain, data_rcv = self.ccid_get_result() + return data_rcv + elif chain == 1: + d = data_rcv + while True: + msg = ccid_compose(0x6f, self.__seq, param=0x10) + self.__dev.write(self.__bulkout, msg, self.__timeout) + self.increment_seq() + status, chain, data_rcv = self.ccid_get_result() + # XXX: check status + d += data_rcv + if chain == 2: + break + elif chain == 3: + continue + else: + raise ValueError("ccid_send_cmd chain") + return d + else: + raise ValueError("ccid_send_cmd") + + def send_cmd(self, cmd): + if self.__use_APDU: + return self.ccid_send_cmd(cmd) + # TPDU + raise ValueError("TPDU not implemented yet") + +class find_class(object): + def __init__(self, usb_class): + self.__class = usb_class + def __call__(self, device): + if device.bDeviceClass == self.__class: + return True + for cfg in device: + intf = find_descriptor(cfg, bInterfaceClass=self.__class) + if intf is not None: + return True + return False + +def get_ccid_device(): + ccid = None + dev_list = usb.core.find(find_all=True, custom_match=find_class(CCID_CLASS)) + for dev in dev_list: + try: + ccid = CardReader(dev) + print("CCID device: Bus %03d Device %03d" % (dev.bus, dev.address)) + break + except: + pass + if not ccid: + raise ValueError("No CCID device present") + status = ccid.ccid_get_status() + if status == 0: + # It's ON already + atr = ccid.ccid_power_on() + elif status == 1: + atr = ccid.ccid_power_on() + else: + raise ValueError("Unknown CCID status", status) + print(atr) + return ccid diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..51aea58 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,3 @@ +def pytest_addoption(parser): + parser.addoption("--reader", dest="reader", type=str, action="store", + default="gnuk", help="specify reader: gnuk or gemalto") diff --git a/tests/openpgp_card.py b/tests/openpgp_card.py new file mode 100644 index 0000000..c1dd769 --- /dev/null +++ b/tests/openpgp_card.py @@ -0,0 +1,289 @@ +""" +openpgp_card.py - a library for OpenPGP card + +Copyright (C) 2011, 2012, 2013, 2015, 2016 + Free Software Initiative of Japan +Author: NIIBE Yutaka + +This file is a part of Gnuk, a GnuPG USB Token implementation. + +Gnuk is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gnuk is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from struct import pack + +def iso7816_compose(ins, p1, p2, data, cls=0x00, le=None): + data_len = len(data) + if data_len == 0: + if not le: + return pack('>BBBB', cls, ins, p1, p2) + else: + return pack('>BBBBB', cls, ins, p1, p2, le) + else: + if not le: + return pack('>BBBBB', cls, ins, p1, p2, data_len) + data + else: + return pack('>BBBBB', cls, ins, p1, p2, data_len) \ + + data + pack('>B', le) + +class OpenPGP_Card(object): + def __init__(self, reader): + """ + __init__(reader) -> None + Initialize a OpenPGP card with a CardReader. + reader: CardReader object. + """ + + self.__reader = reader + + def cmd_get_response(self, expected_len): + result = b"" + while True: + cmd_data = iso7816_compose(0xc0, 0x00, 0x00, b'') + pack('>B', expected_len) + response = self.__reader.send_cmd(cmd_data) + result += response[:-2] + sw = response[-2:] + if sw[0] == 0x90 and sw[1] == 0x00: + return result + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + else: + expected_len = sw[1] + + def cmd_verify(self, who, passwd): + cmd_data = iso7816_compose(0x20, 0x00, 0x80+who, passwd) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_read_binary(self, fileid): + cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, b'') + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_write_binary(self, fileid, data, is_update): + count = 0 + data_len = len(data) + if is_update: + ins = 0xd6 + else: + ins = 0xd0 + while count*256 < data_len: + if count == 0: + if len(data) < 128: + cmd_data0 = iso7816_compose(ins, 0x80+fileid, 0x00, data[:128]) + cmd_data1 = None + else: + cmd_data0 = iso7816_compose(ins, 0x80+fileid, 0x00, data[:128], 0x10) + cmd_data1 = iso7816_compose(ins, 0x80+fileid, 0x00, data[128:256]) + else: + if len(data[256*count:256*count+128]) < 128: + cmd_data0 = iso7816_compose(ins, count, 0x00, data[256*count:256*count+128]) + cmd_data1 = None + else: + cmd_data0 = iso7816_compose(ins, count, 0x00, data[256*count:256*count+128], 0x10) + cmd_data1 = iso7816_compose(ins, count, 0x00, data[256*count+128:256*(count+1)]) + sw = self.__reader.send_cmd(cmd_data0) + if len(sw) != 2: + raise ValueError("cmd_write_binary 0") + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("cmd_write_binary 0", "%02x%02x" % (sw[0], sw[1])) + if cmd_data1: + sw = self.__reader.send_cmd(cmd_data1) + if len(sw) != 2: + raise ValueError("cmd_write_binary 1", sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("cmd_write_binary 1", "%02x%02x" % (sw[0], sw[1])) + count += 1 + + def cmd_select_openpgp(self): + cmd_data = iso7816_compose(0xa4, 0x04, 0x0c, b"\xD2\x76\x00\x01\x24\x01") + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_get_data(self, tagh, tagl): + cmd_data = iso7816_compose(0xca, tagh, tagl, b"") + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if sw[0] == 0x90 and sw[1] == 0x00: + return b"" + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_change_reference_data(self, who, data): + cmd_data = iso7816_compose(0x24, 0x00, 0x80+who, data) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_put_data(self, tagh, tagl, content): + cmd_data = iso7816_compose(0xda, tagh, tagl, content) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_put_data_odd(self, tagh, tagl, content): + cmd_data0 = iso7816_compose(0xdb, tagh, tagl, content[:128], 0x10) + cmd_data1 = iso7816_compose(0xdb, tagh, tagl, content[128:]) + sw = self.__reader.send_cmd(cmd_data0) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + sw = self.__reader.send_cmd(cmd_data1) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_reset_retry_counter(self, how, data): + cmd_data = iso7816_compose(0x2c, how, 0x00, data) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_pso(self, p1, p2, data): + cmd_data = iso7816_compose(0x2a, p1, p2, data) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if sw[0] == 0x90 and sw[1] == 0x00: + return b"" + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_pso_longdata(self, p1, p2, data): + cmd_data0 = iso7816_compose(0x2a, p1, p2, data[:128], 0x10) + cmd_data1 = iso7816_compose(0x2a, p1, p2, data[128:]) + sw = self.__reader.send_cmd(cmd_data0) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + sw = self.__reader.send_cmd(cmd_data1) + if len(sw) != 2: + raise ValueError(sw) + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_internal_authenticate(self, data): + cmd_data = iso7816_compose(0x88, 0, 0, data) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if sw[0] == 0x90 and sw[1] == 0x00: + return b"" + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_genkey(self, keyno): + if keyno == 1: + data = b'\xb6\x00' + elif keyno == 2: + data = b'\xb8\x00' + else: + data = b'\xa4\x00' + cmd_data = iso7816_compose(0x47, 0x80, 0, data) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if sw[0] == 0x90 and sw[1] == 0x00: + return b"" + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + pk = self.cmd_get_response(sw[1]) + return (pk[9:9+256], pk[9+256+2:9+256+2+3]) + + def cmd_get_public_key(self, keyno): + if keyno == 1: + data = b'\xb6\x00' + elif keyno == 2: + data = b'\xb8\x00' + else: + data = b'\xa4\x00' + cmd_data = iso7816_compose(0x47, 0x81, 0, data) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + pk = self.cmd_get_response(sw[1]) + return (pk[9:9+256], pk[9+256+2:9+256+2+3]) + + def cmd_put_data_remove(self, tagh, tagl): + cmd_data = iso7816_compose(0xda, tagh, tagl, b"") + sw = self.__reader.send_cmd(cmd_data) + if sw[0] != 0x90 and sw[1] != 0x00: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + + def cmd_put_data_key_import_remove(self, keyno): + if keyno == 1: + keyspec = b"\xb6\x00" # SIG + elif keyno == 2: + keyspec = b"\xb8\x00" # DEC + else: + keyspec = b"\xa4\x00" # AUT + cmd_data = iso7816_compose(0xdb, 0x3f, 0xff, b"\x4d\x02" + keyspec) + sw = self.__reader.send_cmd(cmd_data) + if sw[0] != 0x90 and sw[1] != 0x00: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + + def cmd_get_challenge(self): + cmd_data = iso7816_compose(0x84, 0x00, 0x00, '') + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_external_authenticate(self, keyno, signed): + cmd_data = iso7816_compose(0x82, 0x00, keyno, signed[0:128], cls=0x10) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + cmd_data = iso7816_compose(0x82, 0x00, keyno, signed[128:]) + sw = self.__reader.send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) diff --git a/tests/test_empty_card.py b/tests/test_empty_card.py new file mode 100644 index 0000000..76847a1 --- /dev/null +++ b/tests/test_empty_card.py @@ -0,0 +1,162 @@ +""" +test_empty_card.py - testing empty card + +Copyright (C) 2016 g10 Code GmbH +Author: NIIBE Yutaka + +This file is a part of Gnuk, a GnuPG USB Token implementation. + +Gnuk is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gnuk is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import pytest +from re import match, DOTALL + +EMPTY_60=bytes(60) + +FACTORY_PASSPHRASE_PW1=b"123456" +FACTORY_PASSPHRASE_PW3=b"12345678" + +@pytest.fixture(scope="module") +def card(): + if pytest.config.option.reader == "gnuk": + from card_reader import get_ccid_device + reader = get_ccid_device() + from openpgp_card import OpenPGP_Card + card = OpenPGP_Card(reader) + else: + raise ValueError("Reader Not Supported") + card.cmd_select_openpgp() + yield card + del card + +def get_data_object(card, tag): + tagh = tag >> 8 + tagl = tag & 0xff + return card.cmd_get_data(tagh, tagl) + +def check_null(data_object): + return len(data_object) == 0 + +def test_login(card): + login = get_data_object(card, 0x5e) + assert check_null(login) + +def test_name(card): + name = get_data_object(card, 0x5b) + assert check_null(name) + +def test_lang(card): + lang = get_data_object(card, 0x5f2d) + assert check_null(lang) + +def test_sex(card): + sex = get_data_object(card, 0x5f35) + assert check_null(sex) + +def test_url(card): + url = get_data_object(card, 0x5f50) + assert check_null(url) + +def test_ds_counter(card): + c = get_data_object(card, 0x93) + assert c == b'\x00\x00\x00' + +def test_pw1_status(card): + s = get_data_object(card, 0xc4) + assert s == b'\x00\x7f\x7f\x7f\x03\x03\x03' + +def test_fingerprint_0(card): + fprlist = get_data_object(card, 0xC5) + assert fprlist == EMPTY_60 + +def test_fingerprint_1(card): + fpr = get_data_object(card, 0xC7) + assert check_null(fpr) + +def test_fingerprint_2(card): + fpr = get_data_object(card, 0xC8) + assert check_null(fpr) + +def test_fingerprint_3(card): + fpr = get_data_object(card, 0xC9) + assert check_null(fpr) + +def test_ca_fingerprint_0(card): + cafprlist = get_data_object(card, 0xC6) + assert cafprlist == EMPTY_60 + +def test_ca_fingerprint_1(card): + cafp = get_data_object(card, 0xCA) + assert check_null(cafp) + +def test_ca_fingerprint_2(card): + cafp = get_data_object(card, 0xCB) + assert check_null(cafp) + +def test_ca_fingerprint_3(card): + cafp = get_data_object(card, 0xCC) + assert check_null(cafp) + +def test_timestamp_0(card): + t = get_data_object(card, 0xCD) + assert t == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + +def test_timestamp_1(card): + t = get_data_object(card, 0xCE) + assert check_null(t) + +def test_timestamp_2(card): + t = get_data_object(card, 0xCF) + assert check_null(t) + +def test_timestamp_3(card): + t = get_data_object(card, 0xD0) + assert check_null(t) + +def test_verify_pw1_1(card): + v = card.cmd_verify(1, FACTORY_PASSPHRASE_PW1) + assert v + +def test_verify_pw1_2(card): + v = card.cmd_verify(2, FACTORY_PASSPHRASE_PW1) + assert v + +def test_verify_pw3(card): + v = card.cmd_verify(3, FACTORY_PASSPHRASE_PW3) + assert v + +def test_historical_bytes(card): + h = get_data_object(card, 0x5f52) + assert h == b'\x00\x31\x84\x73\x80\x01\x80\x00\x90\x00' + +def test_extended_capabilities(card): + a = get_data_object(card, 0xc0) + assert match(b'[\x70\x74]\x00\x00\x20[\x00\x08]\x00\x00\xff\x01\x00', a) + +def test_algorithm_attributes_1(card): + a = get_data_object(card, 0xc1) + assert a == b'\x01\x08\x00\x00\x20\x00' + +def test_algorithm_attributes_2(card): + a = get_data_object(card, 0xc2) + assert a == b'\x01\x08\x00\x00\x20\x00' + +def test_algorithm_attributes_3(card): + a = get_data_object(card, 0xc3) + assert a == b'\x01\x08\x00\x00\x20\x00' + +def test_AID(card): + a = get_data_object(card, 0x4f) + assert match(b'\xd2\x76\x00\x01\\$\x01\x02\x00......\x00\x00', a, DOTALL)