В данной статье я коротко постараюсь описать, что такое карты тахографа, структру данных на карте, и как считать и разобрать информацию с неё. Все примеры будут рассматриваться на карте водителя, но справедливы и для остальных.
Что такое карта тахографа.
С помощью неё в Европе и РФ контролируют режим работы и отдыха водителей, с целью не нарушения им установленных законом норм.
С технической точки зрения это смарт-карта на которую тахограф записывает некоторые данные, о том как водитель управлял транспортным средством, были ли сбои в работе тахографа и т.д. Каждая карта выдаётся на конкретного водителя, и содержит его данные такие как ФИО, дату рождения, персональный сертификат(причём для Европы и РФ они отличаются). Также на карте содержится закрытый ключ, с помощью которого можно подписать данные с карты при выгрузке, с целью дальней защиты их от фальсификаций.
Корме карты водителя, есть также:
- карты мастерской (для настройки и калибровки тахографа)
- карты предприятия
- карты контролера (с помощью неё можно получить информацию о режимах работы через тахограф)
Далее все рассмотрение будем проводить для карты водителя, так как она самая распространённая.
Структура карты тахографа.
Данные на карте хранятся в виде бинарных файлов, для каждой секции свой. В европейская карта водителя имеет следующую структуру:
Описывать все структуры данных в файлах я не буду, так как её можно посмотреть в документации.
Я бы отметил файл EF Driver_Activity_Data, т.к. это едиственная секция в которую входят структуры данных с переменной длиной, а сама структура секции циклична, то надо быть внимательным при ее разборе, для избежания различных сторонних эффектов.
Также надо сказать что в секции EF Application_Identification, содержит данные от которых зависит длина секций с переменной длиной, таких как Я бы отметил файл EF Driver_Activity_Data, EF Events_Data, EF Faults_Data и других.
Чтение данных с карты.
Данные с карты можно прочитать либо с помощью тахографа или же специального ПО при помощи смарт-картридера.
С технической точки зрения более интересен второй способ. Внутри нашей карты стоит собственная ОС которая реагирует на некоторые команды. Все команды нет смысла рассматривать, так как они есть в документации. Поэтому рассмотрим основные:
- Выбор файла (секции)
- Чтение файла
- Цифровая подпись
Все команды отправляются на карту в виде последовательности байт. Для примера реализации я выбрал язык Go. Работа с картой будет использоваться библиотека SCard, которая является врапером pcsc-lite.
Для начала опишем функцию, для отправки команды на карту:
func sendApdu(cmd []byte, card *scard.Card) ([]byte, error) {
var sw_idx, resp_len int
var result, sw_byte []byte
card_resp, err := card.Transmit(cmd)
if err != nil {
return nil, err
}
resp_len = len(card_resp)
sw_idx = resp_len - 2
sw_byte = card_resp[sw_idx:]
result = card_resp[:sw_idx]
if !(sw_byte[0] == 0x90 && sw_byte[1] == 0x00) {
error_msg := fmt.Sprintf("Ошибка выполнения команды % x. Код ошибки: %x.\n", cmd, sw_byte)
return nil, errors.New(error_msg)
}
return result, err
}
Функция принимает на вход команду и указатель на устройство с картой для чтения. На выходе она возвращает либо ошибку, либо результат. Карта присылает ответ в следующем формате: 2 байта статуса операции(90 00
если операция завершилась удачно) затем набор байт результата (если он есть).
Порядок чтения файла следующий: выбираем файл -> читаем содержимое -> подписываем (если нужно).
Команда для выбора файла выглядит так:
00 A4 04 0C 06 <file id>
Напиример для чтения файла EF ICC команда будет следующей: 00 A4 04 0C 00 02
Также, как видно по структуре, есть необходимость выбрать секцию, команда для нее следующая:
00 A4 02 0C 02
Итоговая функция будет выглядеть следующим образом:
func selectFile(fid []byte, card *scard.Card) ([]byte, error) {
var cmd []byte
if fid[0] == 0xFF {
cmd = []byte{0x00, 0xA4, 0x04, 0x0C, 0x06}
} else {
cmd = []byte{0x00, 0xA4, 0x02, 0x0C, 0x02}
}
cmd = append(cmd, fid...)
return sendApdu(cmd, card)
}
После того как файл выбран, его можно считать отправив следующий набор байт:
00 B0 <индекс первого байта> <индекс последнего байта> <длина>
Надо отметить что команда выполняется сразу после выбора файла, и за один проход не может считать больше 255 байт (0xFF). При удачном выполнении команда вернет набор байт, заданной для считывания длины. Полность функция для чтения выглядит так:
func readBinary(size int, card *scard.Card) ([]byte, error) {
READ_BLOCK_SIZE := 200
pos := 0
cmd := []byte{}
val := []byte{}
tmp_val := []byte{}
var expected, begin_byte, end_byte byte
var err error
for pos < size {
if size-pos < READ_BLOCK_SIZE {
expected = int8ToByte(size - pos)
} else {
expected = int8ToByte(READ_BLOCK_SIZE)
}
begin_byte = int8ToByte(pos >> 8 & 0xFF)
end_byte = int8ToByte(pos & 0xFF)
cmd = []byte{0x00, 0xB0, begin_byte, end_byte, expected}
tmp_val, err = sendApdu(cmd, card)
if err != nil {
return nil, err
}
val = append(val, tmp_val...)
pos = pos + len(tmp_val)
}
return val, err
}
После выбора секции и чтения файла осталось разобраться с подписью. Подпись считанного файла делается в 2 этапа:
- Сначала происходит генерация хэша, командой:
80 2A 90 00
- Затем вычисляется сама подпись, командой:
00 2A 9E 9A 40
Данная функция вернет набор байт подписи.
Код функций следующий:
func performHash(card *scard.Card) ([]byte, error) {
cmd := []byte{0x80, 0x2A, 0x90, 0x00}
return sendApdu(cmd, card)
}
func computeDigitalSignature(card *scard.Card) ([]byte, error) {
cmd := []byte{0x00, 0x2A, 0x9E, 0x9A, 0x40}
return sendApdu(cmd, card)
}
По спецификации данные с карты должны выгружаться в TLV формате. Формат имеет следующий вид:
<tag><len><value>
где
tag
равен имени файла (2 байта) + байта подписи (00-данные, 01 - подпись);len
2 байта, в которых записывается длинна считанного файла или подписи;value
значение считанного файла или подписи.
У не подписанного файла будет только одна структура TLV, у подписанного соответственно TLV файла + TLV подписи.
Совокупность этих TLV и будет файлом выгрузки, которая иногда называется ddd файлом.
Функция для выгрузки файла в формате TLV:
func readFile(cf *CardFile, card *scard.Card) ([]byte, error) {
var fid, result_len, result []byte
var resp_len int
fid = cf.Tag[:]
resp, err := selectFile(fid, card)
if err != nil {
return nil, err
}
resp_len = cf.size()
resp, err = readBinary(resp_len, card)
if err != nil {
return nil, err
}
result_len = lenToByte(resp_len)
result = append(fid, 0x00)
result = append(result, result_len...)
result = append(result, resp...)
if cf.Signed {
_, err = performHash(card)
if err != nil {
return nil, err
}
signature, err := computeDigitalSignature(card)
if err != nil {
return nil, err
}
result = append(result, fid...)
result = append(result, []byte{0x81, 0x00, 0x40}...)
result = append(result, signature...)
}
return result, err
}
Полный пример программы для выгрузки данных с карты находится здесь.
Разбор
Разбор выгруженного файла довольно тривиальный. Нужно отфильтровать все секции с данными, выделить из них знчение файлов и разобрать их в соответствии со спецификацией. Для примера я приведу пример разбора одной секции, остальные будут по аналогии.
Для примера возьмем секцию EF Identification. Если посмотреть в документацию, то секция имеет следующий вид:
Здесь можно увидеть описания стуруктур, которые входят в состав файла, их размер и значения по умолчанию.
Чтобы понять что хранится в этих структурах надо перейти в раздел DATA DICTIONARY в документации и найти соотвтетсвующую струткуру, например CardIdentification
хранит информацию по идентификации и в нее входит следующая инфа:
cardNumber
содержт строку из 16 байт с номером картыcardIssueDate
содержит дату выдачи карты размером 4 байта (формат unixtime)cardValidityBegin
содержит дату начала действия карты размером 4 байта (формат unixtime)cardExpiryDate
содержит дату окончания действия карты размером 4 байта (формат unixtime)
Примерный парсер, реализованный в виде сервиса, можно взять здесь
Заключение
Как видно чтение и разбор данных с карты тахографа довольно простая задача, если понять как правильно работать со спецификацией.
Ссылки
- Спецификация на карту тахографа
- Сервис для разбора ddd
- Ридер для чтения карт тахографа
- scard
- pcsc-lite