Сложно не согласиться с тем фактом, что мы живем в мире, который производит много контента, нужно еще успеть отделить полезные знания от информационного шума.
Книги и статьи выходят быстрее, чем когда-либо, а времени на их изучение катастрофически мало, ведь нужно еще руководить проектами, продумывать методы продвижения и выделить пару часов на отдых. Есть ли из этой ситуации выход? Да! О нем и пойдет речь в нашей статье.
Работаем со смарт-картами, используя Python (часть 1)
Сначала, на момент задумки, в 2014 году, данная статья планировалась как единая публикация, но, проработав материал (лень вынудила растянуть этот процесс), я понял, что необходимо её разделить на две части:
- Знакомство с библиотекой и написание/разбор кода специального командного процессора, который ее использует.
- Использование командного процессора из ч.1 для чтения содержимого файла с симки, которую я, однажды, подобрал на улице (никаких персональных данных раскрыто не будет). Узнаем, как отучить Windows встревать в наше взаимодействие с картой, а также, возможно, затронем тему выбора (активации) системного приложения на карте (если моя экспериментальная карта окажется UICC).
Думаю, для профи-карточников первая часть будет представлять бо́льший интерес, а вторая часть будет интересна, прежде всего, новичкам в этой теме (и будет иметь метку Tutorial).
Что такое «Smart-ID»
Среди множества Python-библиотек, обзоры которых есть на Хабре, я не обнаружил pyscard — библиотеки для взаимодействия со смарт-картами.
В этой статье я постараюсь дать краткое описание основных фич pyscard, а на сладкое напишем простенький командный процессор, работающий с картой посредством APDU .
Прошу учесть, что для понимания того, как использовать эту библиотеку, и окружающей терминологии требуется знакомство со стандартом ISO 7816-4 или, хотя бы, GSM 11.11 . К GSM-стандарту проще получить официальный доступ, скачав его с сайта ETSI, впрочем и ISO 7816-4 (pdf, старенькая версия) гуглится, несмотря на то, что за него на оф. сайте хотят денег).
Pyscard существует с 2007 года и является кроссплатформенной (win/mac/linux) надстройкой над PC/SC API.
мой опыт использования на платформах, если интересно.
Мое рабочее окружение, где я использую pyscard — Windows7
Материал данной статьи я тестировал, в основном, на mac OS, но на Windows7 тоже погонял, в виртуалке. Должен отметить, что, в отличие от XP , «семерка» и, вероятно, «десятка», с настройками по умолчанию, «ставит палки в колеса» при работе с картой в ридере:
- При помещении каждой карты в ридер эта карта считается устройством Plug
- Бросим взгляд на, по моему мнению, важнейшие объекты библиотеки, что должно убедить пользоваться не низкоуровневым smartcard.scard , а именно smartcard ;
- Проиллюстрируем применение библиотеки на реальном примере — напишем командный процессор (шелл) на Python 3.6, где командами будут прямо «APDU в хексе» и ответ с карты будет выводиться в консоль. Также будут поддерживаться текстовые команды exit и atr.
Типовой шаблон программы
Пора уже сделать вброс порции кода, а то всё скучные вступительные «бубубу».
Как открыть домофон при помощи телефона с nfc / нужен root и Магиск
from smartcard.CardRequest import CardRequest cardrequest = CardRequest() # метод waitforcard(), в нашем случае ждем любую карту cardservice = cardrequest.waitforcard() # здесь выполнение будет приостановлено до помещения карты в ридер APDU = [0xA0, 0xA4, 0, 0, 2] # Это команда SELECT из GSM 11.11 # smartcard.CardConnection.CardConnection является контекст-менеджером with cardservice.connection as connection: connection.connect() #далее — обмен данными с картой data, sw1, sw2 = connection.transmit(APDU)
Какие задачи решает (практически любая) программа, работающая со смарт-картами в ридере? А вот эти:
- Выбор ридера, с которым мы будем взаимодействовать
- Определение момента, когда карта окажется в этом ридере
- Установка канала связи с картой
- Проверка карты на соответствие нашим критериям (мы можем захотеть работать не с каждой картой, которую пользователь нам подсунет)
- Обмен данными с картой посредством APDU
- Закрытие канала связи с картой
- Определение момента, когда карта будет извлечена из ридера
Замечу, что перечисленные задачи решает, например, прошивка мобильного телефона.
Важнейшие объекты пакета smartcard
В этом разделе все имена указаны относительно пакета smartcard .
Подклассы CardType
Позволяют нам указать точный тип карт, с которыми наше приложение собирается работать. Можно сделать так, чтобы наше приложение даже не реагировало на помещение в ридер карты, которая нам не подходит.
Примеры:
- CardType.ATRCardType (существует в библиотеке) — фильтрация карт по значению ATR. Наше приложение будет реагировать только на карты с определенным значением ATR.
- USIMCardType (я нафантазировал, можно реализовать) — допустимыми картами являются только USIM, внутри проверяем возможность выбора USIM-приложения.
CardRequest и его подклассы
Позволяют свести воедино все требования нашего приложения, касающиеся установления связи с картой:
- строго задать тип карты (см. выше)
- ограничить список допустимых ридеров (из уже установленных в системе)
- изменить таймаут ожидания карты в ридере
По умолчанию никаких ограничений в CardRequest не ставится.
CardConnection
Канал коммуникации нашего приложения с картой, позволяет отправлять на карту APDU и получать ответ, ключевой метод здесь — transmit() . Именно с его помощью происходит непосредственное взаимодействие нашего приложения с картой. Необходимо отметить, что метод transmit() всегда возвращает триплет (кортеж), состоящий из:
- Ответных данных (содержит реальные данные или None , в зависимости от типа APDU, не все APDU возвращают данные)
- Первого байта статуса (StatusWord) SW1
- Второго байта статуса (StatusWord) SW2
CardConnection является контекст-менеджером, что добавляет удобства при его использовании.
CardConnectionDecorator
Слово «декоратор» используется здесь в том же контексте, что и в Java, а не в том, к которому привыкли Python-разработчики.
Позволяет придать особые свойства объекту CardConnection . Библиотека предоставляет рабочие декораторы с говорящими названиями: ExclusiveConnectCardConnection и ExclusiveTransmitCardConnection . Лично я не ощутил эффекта от использования этих декораторов — если система (Windows) уж решила вклиниться со своими APDU в нашу сессию, то ни один из этих декораторов не спасет, но, возможно, я что-то не так делал.
Функция System.readers()
Позволяет получить список подключенных к системе кардридеров и установить связь с картой в определенном ридере.
sw.ErrorChecker , sw.ErrorCheckingChain
По умолчанию, в ходе обмена данными между картой и нашего приложением, никакие ошибочные значения StatusWord (SW1, SW2) не возбуждают исключений. Это можно изменить, задействовав потомков ErrorChecker , которые:
- объединяются в последовательности sw.ErrorCheckingChain
- привязываются к CardConnection и проверяют на отсутствие ошибок результат каждого вызова метода transmit() .
Встроенные в библиотеку «чекеры» позволяют получить в исключении подробную информацию о проблеме без необходимости залезать в спеки и искать необходимые значения SW1 , SW2 .
Потомки CardConnectionObserver
Присоединяются к экземпляру CardConnection и получают информацию обо всех командных APDU и ответах карты, которые проходят через наблюдаемое соединение. Пример применения — ведение лога команд и ответов от карты.
Вооруженные таким знанием, мы вполне можем замахнуться на написание командного процессора, использующего описываемую библиотеку.
Командный процессор с APDU (CLI)
Не буду подробно останавливаться на модуле cmd, который любезно предоставляет нам стандартная библиотека, о нем уже писали здесь, перейду к реализации.
Весь исходный код процессора находится на гитхабе.
Пройдемся по главным моментам, не размениваясь на мелочи.
Функция select_reader()
Возвращает первый ридер, подключенный к компьютеру или None , если подключенных ридеров нет.
def select_reader(): «»»Select the first of available readers. Return smartcard.reader.Reader or None if no readers attached. «»» readers_list = readers() if readers_list: return readers_list[0]
Есть вариант этой функции (зависит от модуля msvcrt, т.е. только для Windows), который позволяет выбрать ридер, если их в компьютере несколько.