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

Комментарии (21)


  1. yroman
    24.08.2018 22:27
    -1

    Код — жуть. На PEP8 и юнит тесты наплевали с высокой колокольни. Не стыдно такое выкладывать?


    1. lostpassword
      25.08.2018 00:57
      +2

      Нормальный код. Понятный. Мне бы было не стыдно.
      Вы слишком перфекционист, не надо так.


      1. yroman
        25.08.2018 02:31
        -1

        Пардон, но нет. Для студента-второкурсника — нормальный. Для профессионала, который в разработке не один год (судя по профилю автора), это позорище. Я понимаю, когда люди пишут для себя, для себя можно гавнякать сколько угодно, но выкладывать такое в пакет для общего пользования лично я бы постеснялся. Ладно PEP, но тесты-то, тем более, что основа для тестов в самом коде как раз присутствует.
        PS: И нет, не перфекционизм совершенно, а элементарные стандарты. Вы не знакомы?


        1. lostpassword
          25.08.2018 02:40
          +1

          На мой личный взгляд, работающий пакет без тестов неизмеримо лучше идеально_прекрасного_покрытого_тестами_и_полностью_соответствующего_пеп_стандартам, но несуществующего.


          1. yroman
            25.08.2018 02:52

            Зря вы так. Обычно как раз тесты дают до некоторой степени уверенность в работоспособности кода, тем более кода на таких языках, как питон. Без тестов автору можно разве что поверить на слово.


          1. quarckster
            25.08.2018 10:04
            -2

            Ну тогда и не надейтесь, что кто-нибудь захочет доработать ваш говнокод. Такой код живёт до тех пор автор заинтересован или жив. Потом это никто в здравом уме поддерживать не станет, проще переписать с нуля.


            1. lostpassword
              25.08.2018 10:06
              +2

              Да автор вроде и не предлагает дорабатывать. Тем людям с манией величия)


    1. saipr Автор
      25.08.2018 16:51

      А что такое PEP8? Без него никак код на Python-e из командной строки Linux писать нельзя? То есть у меня как у той обезьяны с "Войной и миром" получилось. По теории вероятности тыкал-тыкал и столько имоций вызвал. А какие юнит тесты для графической утилиты нужно написать? Запускаешь и смотришь то она показывает или нет. Для пакета fsb795 тоже из командной строки его запускаешь и видишь тестовый результат:


      $python fsb795.py
      ...

      Но за критику спасибо. Буду знать, что главное одежка.


      1. tmnhy
        25.08.2018 17:49

        А что такое PEP8? Без него никак код на Python-e из командной строки Linux писать нельзя?

        Т.е., вы кодите на Python, мало того, вы получившийся результат выкладываете на обозрение и при этом специально игнорируете «Style Guide for Python Code»?


        1. saipr Автор
          25.08.2018 19:57

          Не пойму в чем игнорирование, если и python2 и python3 с удовольствием принимают этот код?


          1. tmnhy
            25.08.2018 20:09

            В том, что вы показываете этот код другим людям, а не только интерпретаторам python.


            1. saipr Автор
              25.08.2018 20:17

              Не понял! Я вам ничего не показывал, вы сами смотрите, это ваш выбор.
              А другие спасибо говорят и я на них ориентируюсь.
              Хорошо, в следующий раз учту PEP8 по максимуму. Но вы не назвали ни одного пункта из этих РЕКОМЕНДАЦИЙ, который был бы серьезно нарушен.
              А задача-то решена или нет? Работает или нет?


              1. tmnhy
                25.08.2018 20:25
                -1

                Я вам ничего не показывал, вы сами смотрите, это ваш выбор.

                Уберите тогда вставки кода из статьи, вы же его не показываете, тогда и смотреть не будем?

                А задача-то решена или нет?

                Какая?

                Работает или нет?

                Вам про тесты уже написали.

                А другие спасибо говорят и я на них ориентируюсь.

                А может вы им зп платите, продолжайте ориентироваться.


              1. tmnhy
                25.08.2018 20:54

                Но вы не назвали ни одного пункта из этих РЕКОМЕНДАЦИЙ, который был бы серьезно нарушен.

                Отступы, то 2 пробела, то 4, даже не буду это комментировать.

                Не вычистили непонятные комментарии из кода, не убрали отладочные принты, спасибо что хоть закомментировали. Вы же в публичный репозиторий выложили, неужели даже какой-то попытки ревью не было?


              1. quarckster
                26.08.2018 09:17
                +1

                flake8 my_python_script.py does the job.


      1. yroman
        25.08.2018 20:11

        Для вашей графической утилиты вам решать какие тесты для нее писать и писать ли вообще. Я говорил про пакет, который вы отправили в репозиторий. Вот для него тесты желательны. Вы действительно не понимаете смысл автоматических тестов? Вы пакет выложили для того, чтобы потешить свое самолюбие, или для пользы всеобщей? Вот представьте, что кто-то (допустим, я) решил воспользоваться вашим пакетом в своей работе. Тестов у вас нет. Как убедиться, что пакет хотя бы отчасти работает правильно? Мамой клянетесь? Это несерьезно.


        1. saipr Автор
          25.08.2018 20:27

          Я уже ответил:


          fsb795.py
          #  -*- 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'
          #           print (self.cert_full["signatureAlgorithm"])
          #           print (self.cert_full["signatureAlgorithm"]['algorithm'])
          #           print (self.cert_full["signatureValue"].prettyPrint())
                  except:
                      self.pyver = ''
                      self.formatCert = ''
                  return
          
              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]
          #Проверка наличия последовательности 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 notation_OID(self, oidhex_string):
              ''' Input is a hex string and as one byte is 2 charecters i take an 
                  empty list and insert 2 characters per element of the list.
                  So for a string 'DEADBEEF' it would be ['DE','AD','BE,'EF']. '''
              hex_list = []
              for char in range(0, len(oidhex_string), 2):
                  hex_list.append(oidhex_string[char]+oidhex_string[char+1])
          
              ''' I have deleted the first two element of the list as my hex string
                  includes the standard OID tag '06' and the OID length '0D'. 
                  These values are not required for the calculation as i've used 
                  absolute OID and not using any ASN.1 modules. Can be removed if you
                  have only the data part of the OID in hex string. '''
              del hex_list[0]
              del hex_list[0]
          
              # An empty string to append the value of the OID in standard notation after
              # processing each element of the list.
              OID_str = ''
          
              # Convert the list with hex data in str format to int format for
              # calculations.
              for element in range(len(hex_list)):
                  hex_list[element] = int(hex_list[element], 16)
          
              # Convert the OID to its standard notation. Sourced from code in other
              # languages and adapted for python.
          
              # The first two digits of the OID are calculated differently from the rest.
              x = int(hex_list[0] / 40)
              y = int(hex_list[0] % 40)
              if x > 2:
                  y += (x-2)*40
                  x = 2
          
              OID_str += str(x)+'.'+str(y)
          
              val = 0
              for byte in range(1, len(hex_list)):
                  val = ((val << 7) | ((hex_list[byte] & 0x7F)))
                  if (hex_list[byte] & 0x80) != 0x80:
                      OID_str += "."+str(val)
                      val = 0
          
              # print the OID in dot notation.
              return (OID_str)
          
            def subjectSignTool(self):
              if(self.cert == ''):
                  return ('')
              for ext in self.cert["extensions"]:
                  #Ищем расширение subjectSignTool
                  if(str(ext['extnID']) == "1.2.643.100.111"):
                          #Его значение надо возвращать
                          if sys.platform != "win32":
                              seek = 4
                          else:
                              seek = 4
          #Проверка версии python-а
                          if (self.pyver == '2'):
          #                    print ('V==2')
                              return ext['extnValue'][seek-2:]
                          seek = seek - 2
          #                print ('V!=2')
                          sst = ext['extnValue'][seek:].prettyPrint()
                          if (len(sst) > 1 and sst[0] == '0' and sst[1] == 'x'):
                              sst = binascii.unhexlify(sst[2:])
                              sst = sst.decode('utf-8')
          
                          return (sst)
              return ('')
          
            def issuerSignTool(self):
              if(self.cert == ''):
                  return ([])
              for ext in self.cert["extensions"]:
                  #Ищем расширение subjectSignTool
                  if(str(ext['extnID']) == "1.2.643.100.112"):
                          #Его значение надо возвращать
                          vv = ext['extnValue']
          #                print(vv)
          #Проверка версии python-а
                          of2 = 1
                          if (self.pyver == '2'):
                              of1 = ord(vv[of2])
                          else:
                              of1 = vv[of2]
          
                          if (of1 > 128):
                              of2 += (of1 - 128)
                          of2 += 1
          #Add for 0x30
                          of2 += 1            
                          if (self.pyver == '2'):
                              of1 = ord(vv[of2])
                          else:
                              of1 = vv[of2]
          
                          if (of1 > 128):
                              of2 += (of1 - 128)
                              of2 += 1
          # Поля issuerSignTools          
                          fsbCA = []
          #Длина первого поля
          #                print(of2)
                          for j in range(0,4):
                              if (self.pyver == '2'):
                                  ltek = ord(vv[of2])
                                  stek = of2 + 1
                              else:
                                  ltek = vv[of2 + 0]
                                  stek = of2 + 1
                              fsb = vv[stek: stek+ltek]
                              if (self.pyver == '3'):
                                  fsb = vv[stek: stek+ltek].prettyPrint()
                                  if (len(fsb) > 1 and fsb[0] == '0' and fsb[1] == 'x'):
                                      try:
                                          val1 = binascii.unhexlify(fsb[2:])
                                          fsb = val1.decode('utf-8')
                                      except:
                                          fsb = vv[stek: stek+ltek].prettyPrint()
          
          #                            fsb = val1
          #                    print(fsb)
                              fsbCA.append(fsb)
                              of2 += (ltek + 2)
          #Возврат значений issuerSignTools
                          return(fsbCA)
              return ([])
          
            def classUser(self):
              if(self.cert == ''):
                  return ('')
              for ext in self.cert["extensions"]:
                  #Ищем расширение subjectSignTool
                  if(str(ext['extnID']) == "2.5.29.32"):
                          print ('2.5.29.32')
                          #Классы защищенности
                          #           print (ext['extnValue'])
          #Переводит из двоичной системы счисления (2) в hex
                          kc = ext['extnValue'].prettyPrint()
          #                print(kc)
          #Сдвиг на 0x
          #Проверка версии python-а
                          if (self.pyver == '2'):
                              kc_hex = kc[2:]
                          else:
                              kc_hex = kc[2:]
          #                print(kc_hex)
          # 4 - длина заголовка           
                          kc_hex = kc_hex[4:]
          #                print(kc_hex)
                          i32 = kc_hex.find('300806062a85036471')
                          tmp_kc = ''
                          while (i32 != -1 ) :
                              #'300806062a85036471' - 10 байт бинарных и 20 hex-овых; 4 - это 3008
          #                    print ('НАШЛИ КС i32=' + str(i32))
                              kcc_tek = kc_hex[i32+4: i32 + 20]
          #           print (kcc_tek)
                              oid_kc = self.notation_OID(kcc_tek)
          #                    print (oid_kc)
                              tmp_kc = tmp_kc + oid_kc +';;'
                              kc_hex = kc_hex[i32 + 20:]
                              i32 = kc_hex.find('300806062a85036471')
          
          #           for ext1 in ext:
          #               print (ext1)
          #                print ('2.5.29.32 END')
          #           print (ext['extnID'])
          #           print (ext['extnValue'])
                          return (tmp_kc)
              return ('')
          
            def parse_issuer_subject (self, who):
              if(self.cert == ''):
                  return ({})
              infoMap = {
                  "1.2.840.113549.1.9.2": "unstructuredName",
                  "1.2.643.100.1": "OGRN",
                  "1.2.643.100.5": "OGRNIP",
                  "1.2.643.3.131.1.1": "INN",
                  "1.2.643.100.3": "SNILS",
                  "2.5.4.3": "CN",
                  "2.5.4.4": "SN",
                  "2.5.4.5": "serialNumber",
                  "2.5.4.42": "GN",
                  "1.2.840.113549.1.9.1": "E",
                  "2.5.4.7": "L",
                  "2.5.4.8": "ST",
                  "2.5.4.9": "street",
                  "2.5.4.10": "O",
                  "2.5.4.11": "OU",
                  "2.5.4.12": "title",
                  "2.5.4.6": "Country",
              }
              issuer_or_subject = {}
          #Владелец сертификата: 0 - неизвестно 1 - физ.лицо 2 - юр.лицо
              vlad = 0
              vlad_o = 0
              for rdn in self.cert[who][0]:
                  if not rdn:
                      continue
                  oid = str(rdn[0][0])
          #            oid = str(rdn[0]['type'])
                  value = rdn[0][1]
          #            value = rdn[0]['value']
          #SNILS
                  if(oid == '1.2.643.100.3'):
                      vlad = 1
          #OGRN
                  elif(oid == '1.2.643.100.1'):
                      vlad = 2
          #O
                  elif(oid == '2.5.4.10'):
                      vlad_o = 1
                  value = value[2:]
          #        print(value)
                  if (self.pyver == '3'):
          #           value = str(value).encode('raw_unicode_escape').decode('utf8')
                      val = value.prettyPrint()
          #            if (len(val) > 2 and val[1] != '\''):
          #            if ((len(val) > 1 and val[1] != '\'') or (len(val) == 2 and (val[0] == '\xD0' or val[0] == '\xD1'))):
                      if (len(val) > 1 and val[0] == '0' and val[1] == 'x'):
                          try:
                                val1 = binascii.unhexlify(val[2:])
                                value = val1.decode('utf-8')
                          except:
                                pass
                  try:
                      if not infoMap[oid] == "Type":
                          issuer_or_subject[infoMap[oid]] = value
                      else:
                          try:
                              issuer_or_subject[infoMap[oid]] += ", %s" % value
                          except KeyError:
                              issuer_or_subject[infoMap[oid]] = value
                  except KeyError:
                      issuer_or_subject[oid] = value
                  if(vlad_o == 1):
                      vlad = 2
              return issuer_or_subject, vlad
          
            def issuerCert(self):
              return (self.parse_issuer_subject ("issuer"))
          
            def subjectCert(self):
              return (self.parse_issuer_subject ('subject'))
          
            def signatureCert(self):
              if(self.cert == ''):
                  return ({})
              algosign = self.cert_full["signatureAlgorithm"]['algorithm']
              kk = self.cert_full["signatureValue"].prettyPrint()
              if kk[-3:-1] == "'B":
          #        print(kk[-3:-1])
                  #Избавляемся от "' в начале строки и 'B" и конце строки
                  kk = kk[2:-3]
          #Переводит из двоичной системы счисления (2) в целое
                  kkh=int(kk, 2)
          ##    else: 
                  #В MS из десятичной системы в целое
          ##        kkh=int(kk, 10)
              else:
                 kkh=int(kk, 10)
              sign_hex = hex(kkh)
              sign_hex = sign_hex.rstrip('L')
              return (algosign, sign_hex[2:])
          
            def publicKey(self):
              if(self.cert == ''):
                  return ({})
              pubkey = self.cert['subjectPublicKeyInfo']
              tmp_pk = {}
              ff = pubkey['algorithm']
              algo = ff['algorithm']
              tmp_pk['algo'] = algo
          #Проверка на ГОСТ
              if (str(algo).find("1.2.643") == -1):
                  print ('НЕ ГОСТ')
                  return (tmp_pk)
          
              param = ff['parameters']
              lh = param.prettyPrint()[2:]
          #Со 2-й по 11 позиции, первые два байта тип и длина hex-oid-а
              l1 = int(lh[7:8], 16)
              lh1 = self.notation_OID(lh [4:4+4+l1*2])
          #Длина следующего oid-а
              l2 = int(lh[4+4+l1*2 + 3: 4+4+l1*2  + 4], 16)
          #oid из hex в точечную форму
              lh2 = self.notation_OID(lh [4+4+l1*2:4+4+l1*2 + 4 + l2*2])
          
              key_bytes = pubkey['subjectPublicKey']
          #Читаем значение открытого ключа как битовую строку
              kk = key_bytes.prettyPrint()
          ##    if sys.platform != "win32":
              if kk[-3:-1] == "'B":
          #        print(kk[-3:-1])
                  #Избавляемся от "' в начале строки и 'B" и конце строки
                  kk = kk[2:-3]
          #Переводит из двоичной системы счисления (2) в целое
                  kkh=int(kk, 2)
          ##    else: 
                  #В MS из десятичной системы в целое
          ##        kkh=int(kk, 10)
              else:
                 kkh=int(kk, 10)
          #    print (kkh)
          #Из целого в HEX
              kk_hex = hex(kkh)
          #Значение ключа в hex хранится как 0x440... (длина ключа 512 бит) или 0x48180... (длина ключа 1024 бита)
              if (kk_hex[3] == '4'):
                  kk_hex = kk_hex[5:]
              elif (kk_hex[3] == '8'):
                  kk_hex = kk_hex[7:]
          #Обрезвем концевик
              kk_hex = kk_hex.rstrip('L')
          
              tmp_pk['curve'] = lh1
              tmp_pk['hash'] = lh2
              tmp_pk['valuepk'] = kk_hex
              return (tmp_pk)
          
            def prettyPrint(self):
              if(self.cert == ''):
                  return ('')
              return (self.cert_full.prettyPrint())
          
            def serialNumber(self):
              return(self.cert.getComponentByName('serialNumber'))
          
            def validityCert(self):
              valid_cert = self.cert.getComponentByName('validity')
              validity_cert = {}
              not_before = valid_cert.getComponentByName('notBefore')
              not_before = str(not_before.getComponent())
          
              not_after = valid_cert.getComponentByName('notAfter')
              not_after = str(not_after.getComponent())
          #    if isinstance(not_before, GeneralizedTime):
          #        not_before = datetime.strptime(not_before, '%Y%m%d%H%M%SZ')
          #    else:
              validity_cert['not_before'] = datetime.strptime(not_before, '%y%m%d%H%M%SZ')
          
          #    if isinstance(not_after, GeneralizedTime):
          #        not_after = datetime.strptime(not_after, '%Y%m%d%H%M%SZ')
          #    else:
              validity_cert['not_after'] = datetime.strptime(not_after, '%y%m%d%H%M%SZ')
          
          #    print (validity_cert) 
              return validity_cert
          
            def KeyUsage(self):
              X509V3_KEY_USAGE_BIT_FIELDS = (
              'digitalSignature',
              'nonRepudiation',
              'keyEncipherment',
              'dataEncipherment',
              'keyAgreement',
              'keyCertSign',
              'CRLSign',
              'encipherOnly',
              'decipherOnly',
              )
              if(self.cert == ''):
                  return ([])
              ku = []
              for ext in self.cert["extensions"]:
                  #Ищем расширение keyUsage
                  if(str(ext['extnID']) != "2.5.29.15"):
                       continue
                  print ('2.5.29.15')
                  os16 = ext['extnValue'].prettyPrint()
          #        print(os16)
                  os16 = '0404' + os16[2:]
          #        print(os16)
                  os = binascii.unhexlify(os16[0:])
                  octet_strings = os
                  e, f= decoder.decode(decoder.decode(octet_strings)[0], rfc2459.KeyUsage())
          #        print (e)
                  n = 0
                  while n < len(e):
                    if e[n]:
                      ku.append(X509V3_KEY_USAGE_BIT_FIELDS[n])
          #            print(X509V3_KEY_USAGE_BIT_FIELDS[n])
                    n += 1
                  return(ku)
              return ([])
          
          if __name__ == "__main__":
          
          #For test
              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 = Certificate(certpem)
          #    c1 = Certificate('cert.der')
          #    c1 = Certificate('p10.p10')
              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()
              for ist in range(len(res1)):
                  print (str(ist) + '=' + res1[ist])
              print('=================classUser================================')
              res2 = c1.prettyPrint()
          #    print(res2)
              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()
              if(len(key_info) > 0):
                  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=================================')


          1. yroman
            25.08.2018 20:38

            Я читал ваш код и видел часть, которая генерирует этот результат. Это практически заготовка для нормального теста. Проблема в том, что этот тест понятен только вам, ибо вы знаете, каков должен быть ожидаемый выхлоп. А вот я не знаю, так что для меня, как для человека, которому ваш пакет интересен, результат вашего теста будет нулевым, я же не знаю, правильно вы сертификат распарсили или нет. Плюс не учтены возможные краевые ситуации (если таковые имеются, а они должны быть, судя по обработке except в коде).
            Но вашу позицию я понял, так что дальнейшие пререкания считаю бессмысленными.


      1. quarckster
        26.08.2018 09:24

        Я просто уверен, что вы в команде никогда не работали. Представьте, если каждый будет писать код в одном проекте так, как ему вздумается? Будет хаос, и люди начнут ненавидеть друг друга. Для этого и придумали соглашения по стилю написания, в питоне это pep8.


  1. tmnhy
    25.08.2018 11:10
    -3

    Первое впечатление — «Привет из нулевых».
    Сомнительная польза, код «как не надо писать на Python», ну и застывший в веках Tcl/Tk.

    Не надо так!


  1. tmnhy
    25.08.2018 11:17
    -1

    Мы же хотим получить доступ к атрибутам квалифицированного сертификата через Python и дать графическую утилиту для их просмотра.

    Нееее, мы хотим больше знать о x509, а не читать документацию к утилите в стиле «Очень полезная кнопка ». )
    А уж как получить доступ к атрибутам сертификата как-нибудь сами разберёмся.