Pull to refresh

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

Reading time 10 min
Views 12K
Квалифицированные сертификаты быстро стали неотъемлемой частью повседневной жизни. И все больше людей хотят увидеть этого «зверя» изнутри. Это с одной стороны. А с другой стороны разрабатывается все больше приложений, в которых задействуется информация иэ этих сертификатов. И это не только атрибуты ИНН или ОГРН владельца или издателя сертификата. Это может быть и информация о том какой криптопровайдер использован владельцем сертификата (атрибут 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 с поддержкой российской криптографии. При использовании утилиты pp вывод сертификата выглядит так:



Выше мы упоминали про атрибут 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
Tags:
Hubs:
+9
Comments 24
Comments Comments 24

Articles