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



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



В качестве нового атрибута в сертификате здесь выступает дополнение 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


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



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

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


Для того, чтобы успешно работать с новыми 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 непосредственно в утилите стало невозможным:



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



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



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



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

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


  1. AVDerov
    11.11.2021 22:56
    +2

    Наивный вопрос, а где это можно использовать?

    Спасибо!


    1. beerchaser
      12.11.2021 09:08
      +3

      "если вы этого не знаете, значить вам это не нужно" :)))


    1. saipr Автор
      12.11.2021 09:51

      А чем вы занимаетесь?
      Это можно использовать как хорошее учебное пособие.
      Я, например, с помощью утилиты cryptoarmpkcs кладу боевой сертификат (сертификат, полученный на аккредитованном УЦ) на токен, и хожу в личный кабанет на ГОСУСЛУГИ и там оплачиваю налоги. Хотел сейчас сделать скриншот на ГОСУСЛУГ-ах, а там профилактика:


      image


      Когда вам понадобится, то вспомните о ней.


      Те кто, предпочитает MS Windows другие платформы (MAC, Linux), используют утилиту для подписания необходимых документов и передачи их, скажем, в арбитражные суды.
      Так что область применения имеется.


      1. AVDerov
        12.11.2021 11:33
        +1

        Спасибо, примерно понял. Посмотрел предыдущие статьи.


  1. elobachev
    12.11.2021 19:17
    +1

    Какая прелесть =)

    Владимир Николаевич, скажите, а в утилите при использовании токена с аппаратно реализованными ГОСТовыми криптоалгоритмами, скажем Рутокен ЭЦП 2.0 - при подписании используются алгоритмы токена или программные?


    1. saipr Автор
      12.11.2021 19:31

      Конечно, токена! Вот механизмы, которые поддерживает скажем Рутокен ЭЦП 2.0 и доступны и используются утилитой:
      image


  1. svyatikov
    17.11.2021 22:10

    Планируется ли доработка утилиты CAFL63 для создания удостоверяющих центров в свете последних изменений, про которые вы говорите в статье?


    1. saipr Автор
      20.11.2021 20:55
      +1

      Вы зрите в корень, как говаривал Козьма Прутков. Не сегодня-завтра я завершу переработку утилиты CAFL63 и она будет представлена во второй части статьи:
      image
      P.S. Извините за задержку с ответом, она связана с работой над утилитой.


      1. svyatikov
        21.11.2021 16:06

        Понял