Розбираємо і переглядаємо кваліфіковані сертифікати засобами 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 і т. д., вказує на те, що він призначений для роботи з сертифікатами.».

Доступ до сертифікату в пакеті 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['subject publickeyinfo']
>>> 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()).
Читайте також  Як встановити VPN і обходити блокування. П'ять надійних сервісів

В спойлері лежить тестовий приклад, який наочно демонструє роботу цих методів.
Тест 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')

Дуже корисна кнопка. Отже, після вибору сертифіката вкладка «Про сертифікат» прийме вигляд:

Що тут примітного так це те, що якщо під час перегляду сертифіката закінчиться термін його дії, то на іконку в лівому верхньому куті друк розколеться на дві половинки. Кожен може переконатися в цьому, переставивши на комп’ютері годинник на один рік вперед.
На вкладці «Деталі» можна детально переглянути характеристики обраного атрибуту кваліфікованого сертифікату:

І, нарешті, третя вкладка «Текст». У цій вкладці відображається вміст всього сертифіката:

Читайте також  SEO в 2019 році не працює?

Для перегляду сертифіката можна використовувати не тільки 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.
Збірка дистрибутивів була зроблена з використанням пакету pyinstaller:

$python pyinstaller.py --noconsole -F viewCertFL63.py

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *