Разбираем и просматриваем квалифицированные сертификаты средствами Python/Tkinter

Квалифицированные сертификаты быстро стали неотъемлемой частью повседневной жизни. И все больше людей хотят увидеть этого «зверя» изнутри. Это с одной стороны. А с другой стороны разрабатывается все больше приложений, в которых задействуется информация иэ этих сертификатов. И это не только атрибуты ИНН или ОГРН владельца или издателя сертификата. Это может быть и информация о том какой криптопровайдер использован владельцем сертификата (атрибут subjectSignTool) для генерации закрытого ключа или на базе каких сертифицированных средств создан удостоверяющий центр (УЦ), выпустивший тот или иной сертификат. И если написать программку, которая будет анализировать выпускаемые сертификаты, то можно будут собрать интересную статистику по тому какие СКЗИ используют владельцы сертификатов и на базе каких (правда это менее интересно) сертифицированных (или несертифицированных) средств развернуты УЦ (атрибут issuerSignTools):

На просторах Хабра уже предпринималась успешная попытка разобрать квалифицированный сертификат. К сожалению, разбор коснулся только получения атрибутов ИНН, ОГРН и СНИЛС, входящие в состав отличительного имени DN (Distinguished Name). Хотя, почему к сожалению? Перед автором стояла конкретная задача и она была решена. Мы же хотим получить доступ к атрибутам квалифицированного сертификата через Python и дать графическую утилиту для их просмотра.

Для доступа к атрибутам сертификата будем использовать пакет fsb795. Пакет доступен как для Pytho2, так и для Python3, как для Linux, так и для Windows. Для его установки достаточно выполнить традиционную команду:

# python -m pip install fsb795 Collecting fsb795 Requirement already satisfied: pyasn1-modules>=0.2.2 in /usr/lib/python2.7/site-packages (from fsb795) (0.2.2) Collecting pyasn1>=0.4.4 (from fsb795)   Using cached https://files.pythonhosted.org/packages/d1/a1/7790cc85db38daa874f6a2e6308131b9953feb1367f2ae2d1123bb93a9f5/pyasn1-0.4.4-py2.py3-none-any.whl Requirement already satisfied: six in /usr/lib/python2.7/site-packages (from fsb795) (1.11.0) Installing collected packages: pyasn1, fsb795 Successfully installed fsb795-1.5.2 pyasn1-0.4.4 [root@localhost GCryptGOST]# 

Пакет fsb795 необходимы пакеты pyasn1 и pyasn1-modules. Поэтому если они не установлены, то будет предпринята попытка их установить.

Для python3 эта команда выглядит следующим образом:

# python -m pip install fsb795 ... #

Можно также скачать установочные пакеты python3 и python2 и локально их установить.
Название пакета, по аналогии с модулями из пакета pyasn1-modules, например, rfc2459 и т.д., указывает на то, что он предназначен для работы с сертификатами, соответствующими требованиям Приказа ФСБ РФ от 27 декабря 2011 г. № 795 «Об утверждении требований к форме квалифицированного сертификата…».

Доступ к сертификату в пакете fsb795 реализован через класс Certificate:

#  -*- coding: utf-8 -*- import os, sys import pyasn1 import binascii import six from pyasn1_modules import rfc2459, pem from pyasn1.codec.der import decoder from datetime import datetime, timedelta  class Certificate: #Атрибуты класса   cert_full = ''   cert = ''   pyver = ''   formatCert = ''   def __init__ (self,fileorstr): #Проверка наличия файла с сертификатом     if not os.path.exists(fileorstr): #Если файла нет, то предполагается, что это может быть #строка с сертификатом в PEM-формате         strcert = fileorstr.strip('\n')         if (strcert[0:27] != '-----BEGIN CERTIFICATE-----'):     	    return         idx, substrate = pem.readPemBlocksFromFile(six.StringIO( 	    strcert), ('-----BEGIN CERTIFICATE-----',                     '-----END CERTIFICATE-----') 	)         self.pyver = sys.version[0]         try:     	    self.cert_full, rest = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())     	    self.cert = self.cert_full["tbsCertificate"]     	    self.formatCert = 'PEM'         except:             self.pyver = ''             self.formatCert = ''         return #Сертификат в фвйле #В атрибут self.pyver заносится версия python     self.pyver = sys.version[0]     filename = fileorstr     if (self.pyver == '2'):         if sys.platform != "win32":             filename = filename.encode("UTF-8")         else:             filename = filename.encode("CP1251") #Проверяем на DER     file1 = open(filename, "rb")     substrate = file1.read()     if (self.pyver == '2'):             b0 = ord(substrate[0])             b1 = ord(substrate[1])     else:             b0 = substrate[0]             b1 = substrate[1] #Проверка на PEM/DER, наличие последовательности 0x30, длина сертификата не может быть меньше 127 байт     if (b0 == 48 and b1 > 128) :     	self.formatCert = 'DER'     else:         self.formatCert = 'PEM'         file1 = open(filename, "r")         idx, substrate = pem.readPemBlocksFromFile(     	    file1, ('-----BEGIN CERTIFICATE-----',                 '-----END CERTIFICATE-----')         )     file1.close()     try:         self.cert_full, rest = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())         self.cert = self.cert_full["tbsCertificate"]     except:         self.pyver = ''         self.formatCert = '' #Методы класса для доступа к атрибутам сертификата   def subjectSignTool(self):  . . . #Тест, запускаемый из командной строки if __name__ == "__main__":  . . .

Для создании экземпляра объекта для конкретного сертификата достаточно выполнить следующий оператор:

$ python Python 2.7.15 (default, May 23 2018, 14:20:56)  [GCC 5.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>import fsb795 >>tek_cert = fsb795.Certificate(<файл/строка с сертификатом>) >> 

В качестве параметра при создании экземпляра класса указывается сертификат, который может находится либо в файле формата PEM или DER или быть строкой в формате PEM.

После создания каждый экземпляр имеет четыре атрибута: pyver, formatCert, cert_full и cert.
По атрибуту pyver можно проверить как прошло распарсивание сертификата. Если pyver равен пустой строке, то файл или строка не содержит сертификата. В противном случае атрибут pyver содержит версию языка python:

>>> c1=fsb795.Certificate('Эта строка не может быть сертификатом')  >>> if (c1.pyver == ''):                         ...    print  ('Вы не предоставили сертификат')     ...   Вы не предоставили сертификат  >>> c2 = fsb795.Certificate('/home/a513/cert_nss.der')  >>> if (c2.pyver != ""):  ...    print(c2.pyver)  ...   2  >>> print(c2.formatCert)  DER  >>>

Атрибут formatCert при успешном создании экземпляра класса Certificate содержит тип формата файла/строки с сертификатом. Это может быть PEM или DER. Зачем этот атрибут нужен станет ясно ниже.

Пакет fsb795 создавался с использованием пакета pyasn1. Итак, осталось нерассмотренными два атрибута. В атрибуте cert хранится tbs-сертификат, готовый к использованию с пакетом pyasn1. Другой атрибут cert_full хранит весь декодированный сертификат с учетом rfc2459. Покажем, как можно получить алгоритм публичного ключа, имея атрибут cert и подключенный пакет pyasn1:

>>> pubkey = c2.cert['subjectPublicKeyInfo'] >>> ff = pubkey['algorithm'] >>> ff1 = ff['algorithm'] >>> print (ff1)  1.2.643.2.2.19  >>>

В конце можно будет оценить возможности пакета fsb795 по получению информации о публичном ключе квалифицированного сертификата.

Когда экземпляр класса Certificate успешно создан, то в нашем распоряжении оказываются методы которые позволяют легко получить необходимые данные из сертификата. Всю информацию о публичном ключе мы можем получить следующим образом:

>>> c3 = fsb795.Certificate('cert.der')                  >>> key_info=c3.publicKey()  >>> for opt in key_info.keys(): ...   val = str(key_info[opt])     ...   print (opt + '=' + val)      ...   curve=1.2.643.2.2.36.0  hash=1.2.643.2.2.30.1  valuepk=5b785f86f0dd5316ba37c8440e398e83f2ec0c34478f90da9c0c8046d341ff66f9044cd00a0e25530 acefd51e6be852dbecacbaabc55e807be8e1f861658bd58  algo=1.2.643.2.2.19  >>> 

На данный момент класс Certificate содержит следующие методы:

  • subjectSignTool() – возвращает строку с наименованием СКЗИ владельца сертификата;
  • issuerSignTool() – возвращает список из четырех элементов с информацией криптографических средствах издателя сертификата;
  • classUser() – возвращает строку с oid-ами классов защищенности СКЗИ владельца сертификата, разделенными символами «;;»;
  • issuerCert() – возвращает словарь с полями и значениями отличительного имени DN издателя сертификата и число, определяющее принадлежность сертификата (2 – юридическое лицо);
  • subjectCert() – возвращает словарь с полями и значениями отличительного имени DN владельца сертификата и число, определяющее принадлежность сертификата (2 – юридическое лицо);
  • publicKey() – возвращает словарь, содержащий значение ключа (‘valuepk’) и параметры ключа (‘curve’ и ‘hash’);
  • signatureCert – возвращает два значения: алгоритм подписи и значение подписи;
  • validityCert – возвращает словарь с двумя ключами ‘not_after’ и ‘not_before’;
  • keyUsage() – возвращает список областей действия ключа;
  • serialNumber() – возвращает серийный номер сертификата в десятичном виде;
  • prettyPrint() – возвращает строку с ‘распечаткой’ сертификата в терминах pyasn1 (self.cert_full.prettyPrint()).

В спойлере лежит тестовый пример, который наглядно демонстрирует работу этих методов.

Тест test795.py для тестирования пакета fsb795

import fsb795  certpem = """ -----BEGIN CERTIFICATE----- MIIG3DCCBougAwIBAgIKE8/KkAAAAAAC4zAIBgYqhQMCAgMwggFKMR4wHAYJKoZI hvcNAQkBFg9kaXRAbWluc3Z5YXoucnUxCzAJBgNVBAYTAlJVMRwwGgYDVQQIDBM3 NyDQsy4g0JzQvtGB0LrQstCwMRUwEwYDVQQHDAzQnNC+0YHQutCy0LAxPzA9BgNV BAkMNjEyNTM3NSDQsy4g0JzQvtGB0LrQstCwLCDRg9C7LiDQotCy0LXRgNGB0LrQ sNGPLCDQtC4gNzEsMCoGA1UECgwj0JzQuNC90LrQvtC80YHQstGP0LfRjCDQoNC+ 0YHRgdC40LgxGDAWBgUqhQNkARINMTA0NzcwMjAyNjcwMTEaMBgGCCqFAwOBAwEB EgwwMDc3MTA0NzQzNzUxQTA/BgNVBAMMONCT0L7Qu9C+0LLQvdC+0Lkg0YPQtNC+ 0YHRgtC+0LLQtdGA0Y/RjtGJ0LjQuSDRhtC10L3RgtGAMB4XDTE4MDcwOTE1MjYy NFoXDTI3MDcwOTE1MjYyNFowggFVMR4wHAYJKoZIhvcNAQkBFg9jb250YWN0QGVr ZXkucnUxITAfBgNVBAMMGNCe0J7QniDCq9CV0LrQtdC5INCj0KbCuzEwMC4GA1UE Cwwn0KPQtNC+0YHRgtC+0LLQtdGA0Y/RjtGJ0LjQuSDRhtC10L3RgtGAMSEwHwYD VQQKDBjQntCe0J4gwqvQldC60LXQuSDQo9CmwrsxCzAJBgNVBAYTAlJVMRgwFgYD VQQIDA83NyDQnNC+0YHQutCy0LAxRDBCBgNVBAkMO9Cj0JvQmNCm0JAg0JjQm9Cs 0JjQndCa0JAsINCULjQsINCQ0J3QotCgIDMg0K3Qojsg0J/QntCcLjk0MRgwFgYD VQQHDA/Qsy7QnNC+0YHQutCy0LAxGDAWBgUqhQNkARINMTE0Nzc0NjcxNDYzMTEa MBgGCCqFAwOBAwEBEgwwMDc3MTA5NjQzNDgwYzAcBgYqhQMCAhMwEgYHKoUDAgIk AAYHKoUDAgIeAQNDAARAW3hfhvDdUxa6N8hEDjmOg/LsDDRHj5DanAyARtNB/2b5 BEzQCg4lUwrO/VHmvoUtvsrLqrxV6Ae+jh+GFli9WKOCA0AwggM8MBIGA1UdEwEB /wQIMAYBAf8CAQAwHQYDVR0OBBYEFMQYnG5GfYRnj2ehEQ5tv8Fso/qBMAsGA1Ud DwQEAwIBRjAdBgNVHSAEFjAUMAgGBiqFA2RxATAIBgYqhQNkcQIwKAYFKoUDZG8E Hwwd0KHQmtCX0JggwqvQm9CY0KDQodCh0JstQ1NQwrswggGLBgNVHSMEggGCMIIB foAUi5g7iRhR6O+cAni46sjUILJVyV2hggFSpIIBTjCCAUoxHjAcBgkqhkiG9w0B CQEWD2RpdEBtaW5zdnlhei5ydTELMAkGA1UEBhMCUlUxHDAaBgNVBAgMEzc3INCz LiDQnNC+0YHQutCy0LAxFTATBgNVBAcMDNCc0L7RgdC60LLQsDE/MD0GA1UECQw2 MTI1Mzc1INCzLiDQnNC+0YHQutCy0LAsINGD0LsuINCi0LLQtdGA0YHQutCw0Y8s INC0LiA3MSwwKgYDVQQKDCPQnNC40L3QutC+0LzRgdCy0Y/Qt9GMINCg0L7RgdGB 0LjQuDEYMBYGBSqFA2QBEg0xMDQ3NzAyMDI2NzAxMRowGAYIKoUDA4EDAQESDDAw NzcxMDQ3NDM3NTFBMD8GA1UEAww40JPQvtC70L7QstC90L7QuSDRg9C00L7RgdGC 0L7QstC10YDRj9GO0YnQuNC5INGG0LXQvdGC0YCCEDRoHkDLQe8zqaC3yHaSmikw WQYDVR0fBFIwUDAmoCSgIoYgaHR0cDovL3Jvc3RlbGVjb20ucnUvY2RwL2d1Yy5j cmwwJqAkoCKGIGh0dHA6Ly9yZWVzdHItcGtpLnJ1L2NkcC9ndWMuY3JsMIHGBgUq hQNkcASBvDCBuQwj0J/QkNCa0JwgwqvQmtGA0LjQv9GC0L7Qn9GA0L4gSFNNwrsM INCf0JDQmiDCq9CT0L7Qu9C+0LLQvdC+0Lkg0KPQpsK7DDbQl9Cw0LrQu9GO0YfQ tdC90LjQtSDihJYgMTQ5LzMvMi8yLTk5OSDQvtGCIDA1LjA3LjIwMTIMONCX0LDQ utC70Y7Rh9C10L3QuNC1IOKEliAxNDkvNy8xLzQvMi02MDMg0L7RgiAwNi4wNy4y MDEyMAgGBiqFAwICAwNBALvjFGhdFE9llvlvKeQmZmkI5J+yO2jFWTh8nXPjIpiL OutUew2hIZv15pJ1QM/VgRO3BTBGDOoIrq8LvgC+3kA= -----END CERTIFICATE----- """  #c1 = fsb795.Certificate('OOO_VOLGA.der') #c1 = fsb795.Certificate('cert.der') c1 = fsb795.Certificate(certpem) if (c1.pyver == ''):     print('Context for certificate not create')     exit(-1) print('=================formatCert================================') print(c1.formatCert) res = c1.subjectSignTool() print('=================subjectSignTool================================') print (res) print('=================issuerSignTool================================') res1 = c1.issuerSignTool() print (res1[0]) print (res1[1]) print (res1[2]) print (res1[3]) print('=================prettyPrint================================') res2 = c1.prettyPrint() #print(res2) print('=================classUser================================') res3 = c1.classUser() print (res3) print('=================issuerCert================================') iss, vlad_is = c1.issuerCert() print ('vlad_is=' + str(vlad_is)) for key in iss.keys():     print (key + '=' + iss[key]) print('=================subjectCert================================') sub, vlad_sub = c1.subjectCert() print ('vlad_sub=' + str(vlad_sub)) for key in sub.keys():     print (key + '=' + sub[key]) print('================publicKey=================================') key_info = c1.publicKey() print(key_info['curve']) print(key_info['hash']) print(key_info['valuepk']) print('================serialNumber=================================') print(c1.serialNumber()) print('================validityCert=================================') valid = c1.validityCert() print(valid['not_after']) print(valid['not_before']) print('================signatureCert=================================') algosign, value = c1.signatureCert() print(algosign) print(value) print('================KeyUsage=================================') ku = c1.KeyUsage() for key in ku:     print (key) #    print(ku) print('================END=================================') 

Для запуска тестового примера достаточно выполнить команду:

$python test795.py

Имея в своем распоряжении пакет fsb795 было естественным написать на языке python самодостаточную платформонезависимую графическую утилиту для просмотра квалифицированных сертификатов. В качестве графической поддержки использован пакет Tkinter:

Утилита viewCertFL63 имеет три вкладки. На вкладке «О сертификате » помимо прочего отображается текущее время. Мы еще вернемся к нему ниже. Для выбора сертификата достаточно нажать кнопку «Выбрать»:

Обратите внимание на кнопку (те кто работают на Windows этой кнопки не увидят), она позволяет скрывать так называемые невидимые файлы/каталоги (hidden). Для того чтобы эта кнопка появилась достаточно выполнить следующие команды:

if sys.platform != "win32":         root.tk.call('set', '::tk::dialog::file::showHiddenBtn', '1')         root.tk.call('set', '::tk::dialog::file::showHiddenVar', '0') 

Очень полезная кнопка. Итак, после выбора сертификата вкладка «О сертификате» примет вид:

Что здесь примечательного так это то, что если во время просмотра сертификата закончится срок его действия, то на иконке в левом верхнем углу печать разломается на две половинки. Каждый может убедится в этом, переставив на компьютере часы на один год вперед.
На вкладке «Детали» можно подробно просмотреть характеристики выбранного атрибута квалифицированного сертификата:

И, наконец, третья вкладка «Текст». В этой вкладке отображается содержимое всего сертификата:

Для просмотра сертификата можно использовать не только Python (кнопка «Python»), то и утилиты openssl и pp из состава Network Serurity Services (NSS). Если у кого-то не окажется этих утилит, то первую можно получить, собрав openssl с поддержкой российской криптографии. Что касается второй утилиты, то можно заглянуть вот сюда:

Выше мы упоминали про атрибут formatCert класса Certificate пакета fsb795. Так вот значение этого атрибута нам необходимо для указания формата файла с сертификатом при запуске той или утилиты. Например, вызов утилиты pp при формате файле PEM выглядит следующим образом:

$pp –t c –u –a –i <файл сертификата>

Параметр «-a» и указывает на формат файла PEM. Для формата DER он не указывается.
Аналогичным образом задается и параметр «–inform » для openssl.
Кнопка «Утилита» служит для указания пути к утилитам openssl или pp.
Дистрибутивы утилиты viewCertFL63 находятся здесь.
Сборка дистрибутивов была сделана с использованием пакета pyinstaller:

$python pyinstaller.py --noconsole -F viewCertFL63.py 
FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *