Работа с квалифицированными сертификатами в свете новой редакции Приказа №795 ФСБ РФ от 29 января 2021 года

zkoik5hiw5kofw8y-_ke8o517ei.jpeg7 сентября 2021 года мне пришло электронное письмо:

fsb795
Добрый день.
не планируете библиотеку подправить под свежие изменения в приказе 795?

Было понятно, что речь идет о пакете fsb795, написанном на Python для разбора квалифицированных сертификатов. Требования к составу и форме квалифицированного сертификата установлены Приказом ФСБ России от 27.12.2011 №795. Но 29 января 2021 года в этот приказ были внесены изменения. Именно об этих изменениях мне и напомнил автор письма. Письмо я получил 7 сентября, а изменения вступили в силу 1-го сентября 2021 года. В этот период времени я был увлечён написанием статьи, связанной с пятидесятилетием окончания Казанского суворовского военного училища и выбора мною стези программиста:

zkg8dp5xtsavum73_g2s_zssv94.jpeg

Я честно ответил, что как только освобожусь, то проведу доработку библиотеки. Тем более, что в связи с выходом новых требований, доработки требовали и другие продукты, в первую очередь утилита crypnjarmpkcs.
И вот время настало. Что же нового в этих требованиях? Первое и самое главное, с моей точки зрения, это то, что будет наведён порядок с идентификационным номером налогоплательщика (далее — ИНН) для физических и юридических лиц. Текущий ИНН (INN) с oid-ом »1.2.643.3.131.1.1» теперь однозначно закрепляется за физическими лицами с длиной в 12 десятичных цифр. Для юридических лиц вводится свой ИНН (INNLE) с oid-ом »1.2.643.100.4». Можно надеяться, что наконец-то исчезнут из тела сертификатов ИНН для юридических лиц длиной в 12 цифр, первые две из которых должны быть нулями. Вообще, я думаю, это какой-то нонсенс для российского программистского сообщества: вместо того, чтобы корректно разбирать сертификаты и строго следовать требованиям Приказа №795, что длина ИНН может быть либо 10 цифр (юридическое лицо) либо 12 цифр (физическое лицо), некая программа при разборе сертификата стала требовать ИНН в 12 цифр. А дальше пошло и поехало, вместо того чтобы привести программу в порядок, стали требовать выдачи сертификатов с полем ИНН в 12 цифр. Изменится ли что-то после выхода новой редакции? Конечно изменится, но мы ещё долго будем видеть в сертификатах старые ИНН для юридических лиц. По крайней мере, до конца срока действия (если не будет принято решение о перевыпуске) сертификатов аккредитованных удостоверяющих центров (УЦ) и корневого сертификата Минкомсвязи. Неделю назад я как физическое лицо получил в аккредитованном УЦ новый сертификат, в котором уживаются, как новые атрибуты для квалифицированного сертификата, так и старые:

j0vhrbjt6-nklfb2lztm6o0rogg.jpeg

В качестве нового атрибута в сертификате здесь выступает дополнение IdentificationKind, которое позволяет определить, как проходила идентификация владельца сертификата при выдачи ему сертификата. В качестве старых атрибутов, это пресловутый ИНН с двумя первыми нулями у издателя сертификата.
Аналогично ИНН, определены различные oid-ы основного государственного регистрационного номера (ОГРН) для юридический лиц и индивидуальных предпринимателей (ОГРНИП). ОГРН для юридических лиц (OGRN) имеет oid »1.2.643.100.1» и состоит из 13-ти цифр. ОГРНИП для индивидуальных предпринимателей (OGRNIP) состоит из 15-ти цифр и имеет oid »1.2.643.100.5».
А теперь вернёмся к дополнению (расширению) IdentidicationKind. Это расширение позволяет в любой момент времени узнать, как производилась идентификация личности владельца сертификата при его получении. Это дополнение имеет oid »1.2.643.100.114» и имеет значение от 0 до 3:

IdentificationKind ::= INTEGER { personal(0), remote_cert(1), remote_passport(2), remote_system(3)}


В случае, если идентификация заявителя при выдаче сертификата ключа проверки ЭП проводилась при его личном присутствии, дополнение identificationKind должно иметь значение 0.
В случае, если идентификация заявителя при выдаче сертификата ключа проверки ЭП проводилась без его личного присутствия с использованием квалифицированной ЭП при наличии действующего квалифицированного сертификата, дополнение identificationKind должно иметь значение 1.
В случае, если идентификация заявителя — гражданина Российской Федерации при выдаче сертификата ключа проверки ЭП проводилась без его личного присутствия с применением информационных технологий путем предоставления информации, указанной в документе, удостоверяющем личность гражданина Российской Федерации за пределами территории Российской Федерации, содержащем электронный носитель информации с записанными на нем персональными данными владельца паспорта, включая биометрические персональные данные, дополнение identificationKind должно иметь значение 2.
В случае, если идентификация заявителя — гражданина Российской Федерации при выдаче сертификата ключа проверки ЭП проводилась без его личного присутствия с применением информационных технологий путем предоставления сведений из единой системы идентификации и аутентификации и единой биометрической системы в порядке, установленном Федеральным законом от 27 июля 2006 г. N 149-ФЗ «Об информации, информационных технологиях и о защите информации», дополнение identificationKind должно иметь значение 3.
Я свой новый сертификат получал за несколько дней до истечения срока действия предыдущего сертификата. Фактически я написал запрос на выдачу нового сертификата и подписал его ещё действующим сертификатом. Отправил его в УЦ и после процедуры проверки получил новый сертификат, которым и воспользовался для доступа в личный кабинет на сайте ГОСУСЛУГИ и уплаты налогов, чтобы спать спокойно. Всё прошло на ура. Если посмотреть на скриншот моего сертификата выше, то можно увидеть, что значение расширения IdentificationKind равно 1 (remote_cert).
При разработке или доработке программного обеспечения, работающего с квалифицированными сертификатами в свете новой редакции Приказа ФСБ №795, следует иметь в виду, что в имени (DN) владельца и издателя сертификата, являющихся юридическими лицами, сегодня может присутствовать ИНН двух видов. Это и новый INNLE с oid-ом »1.2.643.100.4» длиною 10 цифр и старый INN с oid-ом »1.2.643.3.131.1.1» длиною в 12 цифр, из которых две первые цифры нули.
С учётом сказанного и были внесены доработки в пакет fsb795 для Python и в графическую утилиту cryptoarmpkcs.

I. Пакет fsb795


Пакет fsb795 обеспечивает доступ к сертификаты и его полям через класс Certificate:

import os, sys
import fsb795
cert = fsb795.Certificate(<сертификат>)
…


В поле может быть задана переменная с телом сертификата в DER или PEM кодировках или путь к файлу с сертификатом.
Установить пакет fsb795 можно установить традиционным путём для Python:

python –m pip install fsb795


Для учёта при разборе квалифицированного сертификата атрибутов INNLE и OGRNIP имени (в DN) издателя и/или владельца достаточно было включить их в словарь InfoMap метода

parse_issuer_subject:
class Certificate:
. . . 
  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.4": "INNLE",
        "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",
    }
. . .


А вот для доступа к значению дополнения IdentificationKind необходимо было добавить новый метод в класс Certificate:

  def identKind(self):
    if(self.cert == ''):
        return ('')
    for ext in self.cert["extensions"]:
        #Ищем расширение IdentificationKind
        if(str(ext['extnID']) == "1.2.643.100.114"):
#Переводит из двоичной системы счисления (2) в hex
                kc = ext['extnValue'].prettyPrint()
                kc1 = kc[2:4]
#Проверяем tag integer
                if(kc1 != '02'):
#                    print ('not integer')
                    return (-1)
#Проверяем длину
                kc1 = kc[4:6]
                if(kc1 != '01'):
#                    print ('bad length')
                    return (-1)
                type = kc[6:8]
#Переводим в целое из 16-ной системы счисления
                type = int(type, 16)
                if (type > 3):
#            	    print ('Неизвестный тип идентификации = ' + str(type))
            	    return (-1)
                return (type)
    return (-1)


Вот и всё, не считая того что по ходу модификации были устранены некоторые багги. Ниже приводится пример использования пакета для разбора сертификата. Этот пример полностью раскрывает функционал пакета fsb795.

Пример использования пакета fsb795:
# -*- coding: utf-8 -*-

import os, sys
import fsb795

certpem = »«
-----BEGIN CERTIFICATE-----
MIIE9TCCBKCgAwIBAgIEYYlVGzAMBggqhQMHAQEDAgUAMIIB3TELMAkGA1UEBhMC
UlUxLDAqBgNVBAgMI9Cc0L7RgdC60L7QstGB0LrQsNGPINC+0LHQu9Cw0YHRgtGM
MT8wPQYDVQQDDDbQotC10YHRgtC+0LLRi9C5INCj0LTQvtGB0YLQvtCy0LXRgNGP
0Y7RidC40Lkg0KbQtdGC0YAxPzA9BgNVBAoMNtCi0LXRgdGC0L7QstGL0Lkg0KPQ
tNC+0YHRgtC+0LLQtdGA0Y/RjtGJ0LjQuSDQptC10YLRgDEXMBUGCSqGSIb3DQEJ
ARYIY2FAY2EucnUxIjAgBgNVBAcMGdCR0LXRgNC10L3QtNC10LXQsiDQu9C10YEx
KjAoBgNVBAkMIdCi0YDQvtC/0LjQvdC60LAg0Y7QttC90LDRjywg0LQuMTEcMBoG
A1UECwwT0KPRh9C10LHQvdGL0Lkg0KPQpjEZMBcGA1UEDAwQ0JTQuNGA0LXQutGC
0L7RgDEbMBkGA1UEBAwS0JHQsNC70LDQs9Cw0L3QvtCyMS4wLAYDVQQqDCXQkNC7
0LXQutGB0LDQvdC00YAg0J7RgdGC0LDQv9C+0LLQuNGHMRgwFgYFKoUDZAESDTEy
MzQ1Njc4OTAxMjMxFTATBgUqhQNkBBIKMjM0NTY3ODkwMTAeFw0yMTExMDgxNjUx
MzlaFw0yMjExMTgxNjUxMzlaMIIBbTELMAkGA1UEBhMCUlUxLDAqBgNVBAgMI9Cc
0L7RgdC60L7QstGB0LrQsNGPINC+0LHQu9Cw0YHRgtGMMUMwQQYDVQQDDDrQktC+
0YDQvtCx0YzRj9C90LjQvdC+0LIg0JjQv9C/0L7Qu9C40YIg0JzQsNGC0LLQtdC1
0LLQuNGHMSEwHwYDVQQEDBjQktC+0YDQvtCx0YzRj9C90LjQvdC+0LIxKjAoBgNV
BCoMIdCY0L/Qv9C+0LvQuNGCINCc0LDRgtCy0LXQtdCy0LjRhzEXMBUGCSqGSIb3
DQEJARYIdnZAdnYudnYxITAfBgNVBAcMGNCzLiDQp9C10YDQvdC+0LzQvtGA0YHQ
ujEsMCoGA1UECQwj0YPQuy7Qp9C10YDQvdC+0LzQvtGA0YHQutCw0Y8g0LQuMTUx
GjAYBggqhQMDgQMBARIMNDQ0NDQ0NDQ0NDQ0MRYwFAYFKoUDZAMSCzMzMzMzMzMz
MzMzMGgwIQYIKoUDBwEBAQEwFQYJKoUDBwECAQEBBggqhQMHAQECAgNDAARAvwRh
R9Lh8AF2WwiJ6ns3kdPnSBYM26gwYxaPzpSVwPOKGTeWZafBtqkXLlYreClCxgS9
Aqtwav+CijUcP+vweKOBqDCBpTAPBgNVHRMBAQAEBTADAQEAMAsGA1UdDwQEAwIE
8DBFBgUqhQNkbwQ8DDrQndCw0LjQvNC10L3QvtCy0LDQvdC40LUg0KHQmtCX0Jgg
0L/QvtC70YzQt9C+0LLQsNGC0LXQu9GPMB0GA1UdDgQWBBTQW3oAqF+nF6jwfLiR
e2vijovlqjAfBgNVHSMEGDAWgBTHGxxICwmKlpqhbZgevtL8VWD0gjAMBggqhQMH
AQEDAgUAA0EAOsbZ+ky8Acjvrys2ZenOnBFS6vfGeiXD3NSTX/4elK+kmRVbtoBz
ro9C95WRbPdJ5zn0t+/9S8op8aQVb3Op/g==
-----END CERTIFICATE-----
»«

#Если задан параметр, то читаем сертификат из файла
if len (sys.argv) == 2:
c1 = fsb795.Certificate (sys.argv[1])
else:
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 ()
for key in res1:
print (key)
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 ()
for key in key_info.keys ():
print (key + '=' + key_info[key])
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 ('================IdentificationKind============================')
idkind = c1.identKind ()
print ('type identification kind=' + str (idkind))
print ('================Private Key Usage Period==================')
period = c1.keyPeriod ()
print ('not_before=' + str (period['not_before']))
print ('not_after=' + str (period['not_after']))
print ('================END=================================')


II. Графическая утилита cryptoarmpkcs


Основное назначение утилиты cryptoarmpkcs7 — это формирование запроса на квалифицированный сертификат и/или электронной подписи под документами. При этом в качестве криптографического ядра может использоваться любой криптографический токен PKCS#11 с поддержкой российской криптографии:

bixujusejbexfqux3mwa5nbibwq.jpeg

Для доступа к криптографическим и другим возможностях токенов утилита cryptoarmpkcs использует пакет TclPKCS11. Аналогичный пакет с именем pyp11 есть и для Python.
Оба модуля tclpkcs11 и pyp11 написаны на языке Си.
Отметим также, что для подписания документов утилита может использовать и защищенный контейнер PKCS#12. Полный функционал утилиты отображён в левой части скриншота.
Загрузить дистрибутивы утилиты cryptoarmpkcs можно здесь:


Как видим, есть и реализация утилиты на плаnформе Android:
q9gkbl0h8htrs1jkzcsvy_7f3x0.jpeg

Для того, чтобы успешно работать с новыми oid-ами (INNLE, IdentificationKind), их необходимо добавить в массив :: pki: oids пакета pki:

…
package require pki
…
set ::pki::oids(1.2.643.100.1)  "OGRN"
#Для физическлого лица
set ::pki::oids(1.2.643.3.131.1.1) "INN"
set ::pki::oids(1.2.643.100.3) "SNILS"
#ВВЕДЕНЫ Приказом ФСБ России от 29.01.2021 N31
#Для юридического лица
set ::pki::oids(1.2.643.100.4) "INNLE"
#Для ИП
set ::pki::oids(1.2.643.100.5)  "OGRNIP"
#IdentificationKind - как выдавался сертификат
#set ::pki::oids(1.2.643.100.114) "IDKIND"


А вот для доступа к значению дополнения IdentificationKind по аналогии с пакетом fsb795 для Python добавили функцию idkind:

proc idkind {idkind_hex} {
  set ret ""
  set kind [binary format H* $idkind_hex]
  ::asn::asnGetInteger kind idk
  switch $idk {
    0 { set ret [lindex $::listkind 0]}
    1 { set ret [lindex $::listkind 1]}
    2 { set ret [lindex $::listkind 2]}
    3 { set ret [lindex $::listkind 3]}
    default {
	set ret "$idk - неизвестный тип идентификации"
    }
  }
  return $ret
}


Всё это можно увидеть в исходном коде. При этом следует обратить внимание на то, что функция parse_cert в пакете pki меняется на другую, которая позволяет разбирать ГОСТ-овые сертификаты:

. . .
package require pki
#удаляем родную функцию
rename ::pki::x509::parse_cert ::pki::x509::parse_cert_old
#добавляем свою функцию
proc ::pki::x509::parse_cert {cert} {
  array set parsed_cert [::pki::_parse_pem $cert "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"]
  set cert_seq $parsed_cert(data)

  array set ret [list]
. . 

  return [array get ret]
}
. . .


По ходу доработки выяснилось, что Github полностью перешёл на использование протокола tls1.3. Это значит, что для скачивания дистрибутивов напрямую из утилиты надо использовать пакет tls именно с поддержкой протокола tls1.3. Использование в утилите cryptoarmpkcs до последнего времени пакета tls версии 1.6.7 привело к тому, что скачивание дистрибутивов с Github непосредственно в утилите стало невозможным:

8wobhej8ibebefyv-s6-hpoeqrk.jpeg

Пришлось заменить в дистрибутивах пакет tls на более свежий:

fgrnwygengou4i9bxhflwulohvy.jpeg

После этого у утилиты не возникало проблем с загрузкой дистрибутивов из репозитория Github:

zinpzdapxqdbij_fgycjjj9frdc.jpeg

Для использования утилиты в учебных целях на странице «Просмотр запроса/сертификата» добавлен функционал для выпуска сертификатов:

jpgujres0-2hcmsiitbtnzb9m8k.jpeg

При этом корневой сертификат вместе с закрытым ключом должен быть упакован в защищенный контейнер PKCS#12 и находиться в «Папка для сертификатов». Сертификаты могут использоваться не только в учебных целях, но и для внутрикорпоративных целей. Корневой сертификат может быть выпущен на вкладке «Самоподписанный сертификат».
Но главное назначение утилиты это всё же формирование электронной подписи под документами, включая усиленную усовершенствованную квалифицированную подпись (CADES-XLT1).
Можно заканчивать, требования Приказа ФСБ РФ №795 в редакции от 29.01.2021 учтены в пакете fsb795 для Python и в графической утилите cryptoarmpkcs.

© Habrahabr.ru