Русский
Русский
English
Статистика
Реклама

Certificate

OpenSSL и Network Security Services (NSS) две стороны одной медали

20.07.2020 12:12:02 | Автор: admin
imageО какой медали идет речь в заголовке? Речь идет об инфраструктуре открытых ключей (Public Key Infrastructure PKI/ИОК) на базе стандартов криптографии с открытым ключом (Public Key Cryptography Standards PKCS). Инфраструктура открытых ключей включает в себя множество различных объектов и механизмов работы с ними, а также протоколы взаимодействия объектов друг с другом (например, протоколы TLS, OCSP). В число объектов ИОК/PKI входят сертификаты x509 и ключевые пары (приватные и публичные ключи), подписанные и зашифрованные документы (pkcs#7, CMS). защищенные контейнеры для хранения приватных ключей (pkcs#8) и личных сертификатов с ключами (pkcs#12) и т.д. В число механизмов входят не только криптографические функции, которые позволяют шифровать и подписывать документы по различным алгоритмам, но и функции формирующие конечные объекты ИОК в соответствии со стандартами (сертификаты, запросы, подписанные/зашифрованные документы, пакеты протоколов и т.д. и т.п.). Да, и как не вспомнить центральный объект ИОК/PKI Удостоверяющий Центр (УЦ).
Рядовой пользователь интернет использует различные механизмы и объекты ИОК/PKI, не подозревая того, когда использует протокол HTTPS при доступе к различным сайтам, когда подписывает и/или шифрует свои электронные письма или использует электронную подпись в документообороте.
Наиболее продвинутыми средствами для создания инфраструктуры открытых ключей, программных средств, работающих в составе PKI/ИОК, являются OpenSSL и Network Security Services (NSS).
OpenSSL набор полноценных криптографических библиотек и утилиты с открытым исходным кодом, которые поддерживает почти все низкоуровневые алгоритмы хеширования, шифрования и электронной подписи, а также реализует большинство популярных криптографических стандартов, в том числе: позволяет создавать ключи RSA, DH, DSA, EC, выпускать сертификаты X.509, шифровать и подписывать данные и создавать SSL/TLS соединения.
Следует отметить, что объекты, с которыми работает утилита OpenSSL, хранятся в файлах (сертификаты, ключи).
Таким же набором полноценных криптографических библиотек и утилит с открытым исходным кодом является и Network Security Services (NSS).
Основное отличие OpenSSL от NSS заключается в том, что OpenSSL, предполагает, что сертификаты и ключи хранятся в файлах, а NSS для хранения сертификатов и ключей использует базы данных и токены PKCS#11.
Самым существенным является то, что оба проекта (OpenSSL и NSS) строго придерживаются стандартов и поэтому не возникает проблем при их совместном использовании в различных проектах. Таким хорошим примером их содружества может служить, например, применение протокола HTTPS, когда сайты/порталы строятся на базе Apache с mod_ssl на базе OpenSSL, а доступ к ним ведется, например, посредством Firefox, в котором поддержка TLS 1.0/TLS 1.2 и TLS 1.3 осуществляется с помощью библиотек NSS.
Ниже будет показано как утилиты OpenSSL и NSS могут использоваться для решения одних и тех же задач. В дальнейшем каждый может использовать утилиты по своему вкусу. Познакомиться с библиотечными функциями каждый может просмотрев исходный код той или иной утилиты.

Просмотр сертификатов и других сущностей, хранящихся в файлах


В пакете OpenSSL присутствует одна утилита openssl, первым параметром в которой задается собственно команда (Standard commands), которую необходимо выполнить:
$openssl help
Standard commandsasn1parse         ca                ciphers           cms               crl               crl2pkcs7         dgst              dhparam           dsa               dsaparam          ec                ecparam           enc               engine            errstr            exit              gendsa            genpkey           genrsa            help              list              nseq              ocsp              passwd            pkcs12            pkcs7             pkcs8             pkey              pkeyparam         pkeyutl           prime             rand              rehash            req               rsa               rsautl            s_client          s_server          s_time            sess_id           smime             speed             spkac             srp               ts                verify            version           x509              Message Digest commands (see the `dgst' command for more details)blake2b512        blake2s256        gost              md2               md4               md5               rmd160            sha1              sha224            sha256            sha384            sha512            Cipher commands (see the `enc' command for more details)aes-128-cbc       aes-128-ecb       aes-192-cbc       aes-192-ecb       aes-256-cbc       aes-256-ecb       base64            bf                bf-cbc            bf-cfb            bf-ecb            bf-ofb            camellia-128-cbc  camellia-128-ecb  camellia-192-cbc  camellia-192-ecb  camellia-256-cbc  camellia-256-ecb  cast              cast-cbc          cast5-cbc         cast5-cfb         cast5-ecb         cast5-ofb         des               des-cbc           des-cfb           des-ecb           des-ede           des-ede-cbc       des-ede-cfb       des-ede-ofb       des-ede3          des-ede3-cbc      des-ede3-cfb      des-ede3-ofb      des-ofb           des3              desx              idea              idea-cbc          idea-cfb          idea-ecb          idea-ofb          rc2               rc2-40-cbc        rc2-64-cbc        rc2-cbc           rc2-cfb           rc2-ecb           rc2-ofb           rc4               rc4-40            rc5               rc5-cbc           rc5-cfb           rc5-ecb           rc5-ofb           seed              seed-cbc          seed-cfb          seed-ecb          seed-ofb          zlib              $ 


Как можно заметить, при выполнении команды openssl help, помимо собственно перечня команд, выводится список поддерживаемых хэш-алгоритмов и алгоритмов шифрования (в их перечень включены и функции сжатия и работы с base64).
Для просмотра сертификата (x509), запроса (req) или списка отозванных сертификатов (crl) достаточно выполнить команду следующего вида:
openssl x509[|req|crl] [-nameopt utf8] -inform PEM|DER -noout -in <имя файла>.
Например, команда:
$openssl x509 -text -nameopt utf8 -inform PEM -noout -in cert.pem

отобразит на экране компьютера содержимое сертификата в техтовом виде (x509 -text), хранящегося в файле cert.pem ( -in cert.pem) в кодировке PEM (base64) (-inform PEM) и содержащего символы в кодировке utf-8 (-nameopt utf8). При этом сам сертификат в кодировке PEM на экран выводиться не будет (-noout).
В пакете NSS аналогичные действия выполняет утилита pp.
Утилита pp это утилита элегантной печати (Pretty Print) файла, содержащего ASN.1 структуру в DER или PEM-кодировки:
Usage:  pp [-t type] [-a] [-i input] [-o output] [-w] [-u],  
где type:
  • с сертификат;
  • cr запрос на сертификат;
  • pk публичный ключ;
  • pk файл с открытым ключом;
  • crl списоk отозванных сертификатов.

Отметим еще один тип, который применяется к сертификатам, ci (certificate-identity). Этот тип позволяет получить из сертификата идентифицирующую его информацию, такую как subject (владелец), issuer (издатель), serial number (серийный номер), fingerprint (отпечатки по SHA-1 и SHA-256). Аналогичные параметры есть и в утилиты openssl для x509.
По умолчанию предполагается, что все объекты находятся в DER-кодировке. Если же объекты находятся в PEM-кодировке то необходимо задать параметр "-a" (аналог параметра "-inform PEM" для утилиты openssl). И еще один параметр "-u" задается, если в объекте содержатся символы в кодировке UTF-8. Напомним, что аналогичный параметр есть и у утилиты openssl "-nameopt utf8".

В пакете NSS есть и утилита для просмотра ASN.1-структыры объекта, аналог утилиты openssl asn1.parse. Это утилита derdump:
$derdump -i <просматриваемый файл> [-o <выходной файл>]

Из самого названия утилиты следует, что она работает с файлами в DER-кодировке. Но это не страшно. В состав пакета входит две утилиты, которые конвертируют файлы из PEM/BASE64-кодировки в DER-кодировку и обратно. Это утилиты atob и btoa.
Например, для конвертации сертификата из PEM-формата в DER-формат, в OpenSSL надо выполнить следующую команду:
$openssl x509 -inform der -in CERT.der -out CERT.pem 

В NSS это будет выглядеть так:
$btoa -in CERT.der -out CERT.pem -w "CERTIFICATE"

Параметр "-w" указывает какой текст включить в начало выходного файла и конец. В данном случае "-w CERTIFICATE" приведет к появлению стандартного для PEM-формата в OpenSSL заголовка и концевика:
-----BEGIN CERTIFICATE-----<тело сертификата в кодировке BASE64>-----END CERTIFICATE----- 

И OpenSSL и NSS могут работать с контейнерами pkcs#12. И они оба позволяют не толькл создавать, но и просмотреть содержимое контейнера pkcs12. Но, при использовании утилиты openssl, требуется сначала разобрать контейнер и сохранить сертификаты из контейнера в отдельных файлах. После этого их можно спокойно просмотреть. В NSS просмотр содержимого контейнера можно выполнить за один проход. Для этого используется утилиты pk12util следующего вида:
pk12util -l <файл с контейнером pkcs12> [-W <пароль к контейнеру pkcs12>] [-d <каталог хранилища NSS>] [-h <токен>]

Например:
$pk12util -l cert_RSA.p12 -W 01234567
Certificate(has private key):    Data:        Version: 3 (0x2)        Serial Number: 3 (0x3)        Signature Algorithm: PKCS #1 SHA-1 With RSA Encryption        Issuer: "CN=CA-RSA"        Validity:            Not Before: Thu May 21 09:50:00 2020            Not After : Fri May 21 16:17:00 2021        Subject: "CN=soft.lissi.ru"        Subject Public Key Info:            Public Key Algorithm: PKCS #1 RSA Encryption            RSA Public Key:                Modulus:                    a2:f3:e1:63:fd:4f:60:4e:a9:de:56:37:a6:be:54:f3:                    3d:67:9a:68:9e:f1:47:69:e3:3a:10:b0:2e:88:0c:ef:                    7c:7f:48:47:6b:c0:75:63:c7:13:23:88:9a:ff:9a:48:                    30:6a:a0:52:53:6f:4e:e5:84:c0:a1:b0:50:a2:ab:3d:                    f9:62:2c:d8:30:be:19:1b:c9:f4:b8:20:57:a1:8e:5e:                    61:8c:a7:50:91:44:61:99:71:40:bb:dc:4c:b7:7c:67:                    be:a0:71:26:9f:af:dd:69:63:84:7d:93:3f:92:1b:fb:                    d1:78:d7:95:75:42:8e:14:a8:63:e2:7b:7d:ef:c8:74:                    35:7c:39:44:82:ad:92:1f:98:0e:91:95:c8:d8:bd:41:                    fc:44:7e:4d:f5:94:d1:cc:25:ea:df:69:d7:b1:d7:86:                    ad:4d:03:f1:35:65:03:a6:84:f8:26:6e:9b:d3:c9:67:                    d5:a5:a4:9e:c7:82:76:28:9f:90:14:f1:16:6a:29:5d:                    f8:df:c6:6c:e4:21:0d:6f:c5:87:61:a0:65:e3:97:0f:                    96:42:ad:7d:96:79:ef:1d:ab:6c:e3:a0:da:3a:65:d8:                    39:69:f3:20:e2:b1:27:fe:cb:4c:8c:0c:f5:76:f2:65:                    a0:c7:bb:08:b0:f5:50:c0:96:8a:30:e9:75:f7:56:65                Exponent: 65537 (0x10001)        Signed Extensions:            Name: Certificate Basic Constraints            Critical: True            Data: Is not a CA.            Name: Certificate Key Usage            Usages: Digital Signature                    Non-Repudiation                    Key Encipherment            Name: Certificate Subject Alt Name            DNS name: "lissi.ru"            Name: Certificate Type            Data: <SSL Server>            Name: Certificate Comment            Comment: "xca certificate"    Signature Algorithm: PKCS #1 SHA-1 With RSA Encryption    Signature:        12:8c:c3:7e:e9:18:5a:d7:ee:f8:10:8b:72:55:ba:ee:        8b:85:6c:aa:e3:de:58:26:a2:da:15:c6:3b:15:d9:82:        6d:02:33:16:cc:0c:84:9d:96:b0:67:d4:63:10:b5:42:        a1:c3:de:cb:40:6f:9b:9b:eb:c1:98:b6:66:55:ae:88:        56:14:02:5c:62:8c:bc:22:97:94:cf:53:da:2e:47:c1:        c6:83:dc:39:6f:0b:b8:39:4e:66:89:a3:9d:51:c6:e3:        bd:fc:9e:f3:7b:02:a4:77:bc:08:4e:89:e6:57:54:5c:        c1:cc:83:93:9e:4e:f5:41:4e:b5:13:bc:64:29:a9:8d:        ce:13:ae:48:6c:21:fc:da:2a:a2:87:67:f8:df:23:53:        08:a3:11:93:69:91:84:40:4b:58:c1:f3:d0:78:dc:33:        f6:a5:a6:6f:ed:39:a9:ec:f3:48:e8:06:09:4c:c3:9f:        9c:0f:58:80:7f:f5:09:40:2a:f1:cf:42:d7:5b:57:62:        99:e7:dc:a5:31:f3:9d:1f:5a:88:c2:30:1b:8c:ec:69:        8b:87:dc:4c:08:9e:70:49:3d:5e:7b:8f:6f:98:50:8b:        0d:b9:8f:c1:7e:9b:1f:c2:76:3a:ca:c5:e3:3d:ea:93:        81:c0:3b:e2:b7:d1:5d:e4:fd:48:d6:1b:8f:96:e2:18    Fingerprint (SHA-256):        D3:38:99:C9:8B:A5:49:96:BC:26:7B:10:1E:2A:7C:4B:55:15:E5:94:47:C6:D0:49:44:2E:48:58:1B:CF:83:7E    Fingerprint (SHA1):        D5:26:80:B7:CE:40:5B:54:85:F6:B2:31:58:C3:3E:9D:A4:3D:C1:F3    Friendly Name: soft.lissi.ruCertificate:    Data:        Version: 3 (0x2)        Serial Number: 1 (0x1)        Signature Algorithm: PKCS #1 SHA-1 With RSA Encryption        Issuer: "CN=CA-RSA"        Validity:            Not Before: Wed May 20 16:12:00 2020            Not After : Tue May 21 16:12:00 2030        Subject: "CN=CA-RSA"        Subject Public Key Info:            Public Key Algorithm: PKCS #1 RSA Encryption            RSA Public Key:                Modulus:                    a3:ca:c1:3b:ac:17:1b:32:69:90:8c:70:3b:95:3e:78:                    4c:90:55:27:2a:25:05:16:54:d3:88:69:b0:43:a0:20:                    3d:ca:0d:a2:f9:a5:2f:8c:e1:69:b6:df:79:bd:25:7d:                    aa:71:2b:f2:9f:82:f1:e7:49:cf:fa:3c:b6:6f:80:09:                    b2:ee:d5:18:e3:3d:96:67:38:cb:9c:e8:e5:76:c4:a8:                    0b:b9:ad:dd:42:25:c7:da:cf:d4:15:41:bf:b0:0e:4f:                    d1:9c:b7:d0:b1:32:a0:c7:14:67:ba:a2:9a:e7:23:26:                    d7:7e:32:d9:5d:15:47:9e:4b:b0:b1:8b:04:38:1e:c3:                    b2:fc:17:fe:8e:d1:cb:de:de:fd:13:17:b3:0e:5b:58:                    e1:37:c7:12:32:b6:94:82:77:b8:4c:87:99:c6:c3:7d:                    51:ed:3c:41:73:31:aa:13:de:26:84:e7:f7:a9:34:e9:                    b3:9e:7d:aa:91:65:79:a7:14:9d:fc:45:42:de:e6:43:                    9d:67:96:94:66:38:0b:2e:32:0a:4d:c3:3d:14:b9:06:                    6b:e0:92:e2:35:0c:8f:78:7f:2c:ad:ec:dc:67:66:0b:                    8c:47:82:c5:0e:39:a5:35:75:b5:fb:7f:2d:07:97:ef:                    15:d8:fc:d4:72:6a:da:32:86:9e:64:ea:4a:e3:37:5b                Exponent: 65537 (0x10001)        Signed Extensions:            Name: Certificate Basic Constraints            Critical: True            Data: Is a CA with no maximum path length.            Name: Certificate Key Usage            Usages: Certificate Signing                    CRL Signing            Name: Certificate Type            Data: <SSL CA,S/MIME CA,ObjectSigning CA>            Name: Certificate Comment            Comment: "xca certificate"    Signature Algorithm: PKCS #1 SHA-1 With RSA Encryption    Signature:        24:90:ac:91:3f:13:f6:1a:d4:3c:1b:de:33:e1:4a:0c:        d8:27:a0:00:d2:43:c8:1b:13:90:93:d3:d2:f0:fe:79:        da:14:fd:34:2e:3a:f4:fc:c8:71:c9:4f:0d:65:c0:fd:        40:04:92:ef:7e:72:35:09:4a:08:1e:ed:21:53:06:03:        73:f9:13:e7:a3:9c:e2:17:9c:25:b2:a5:f9:dc:07:7d:        32:9f:cd:82:85:6c:26:79:dd:ee:e7:31:4e:10:55:19:        d6:ac:1e:70:39:01:d2:37:00:3b:41:de:a9:c2:bd:bf:        b4:c1:f8:8d:bd:d4:6b:95:6d:53:f3:17:76:40:d4:05:        a4:1e:69:e8:54:92:91:bf:89:b6:ba:45:c5:14:89:bb:        f4:44:cf:91:ca:16:44:55:86:8f:b9:37:4e:9e:9e:04:        cd:48:e7:57:ec:c8:e2:72:f3:df:34:49:0a:9b:3f:67:        a4:01:dd:f3:a3:bb:ec:b5:b8:20:f5:7e:45:8e:ae:53:        7e:b8:92:38:0a:b7:41:8e:81:15:ab:72:42:f7:37:4a:        6d:d7:4f:aa:0a:99:ee:9b:49:16:54:03:42:d6:fe:c1:        ee:63:71:28:b1:84:c2:e6:d4:7b:f6:10:4c:a0:7a:39:        9d:03:30:ff:78:24:ce:5b:ac:fe:ac:6d:f6:61:77:a6    Fingerprint (SHA-256):        AA:1F:B9:29:D2:F9:CC:AB:3D:F7:8C:26:26:4B:51:A3:71:01:1A:94:F8:FE:47:1D:BD:E3:72:DD:63:17:FE:6C    Fingerprint (SHA1):        B3:7A:A1:65:01:E2:A0:09:F4:55:17:EC:40:88:5C:42:9A:45:F5:36Key(shrouded):    Friendly Name: soft.lissi.ru    Encryption algorithm: PKCS #12 V2 PBE With SHA-1 And 3KEY Triple DES-CBC        Parameters:            Salt:                08:0b:8d:be:fa:fc:a5:a3            Iteration Count: 2048 (0x800)$


Утилита удобная, но без ложки дегтя не обошлось. Ложка дегтя состоит в том, что русские буквы, вернее, UTF-8 кодировка отображается в виде точек (.....). И если у утилиты pp есть параметр -u (присутствует кодировка utf-8), то здесь про нее забыли (мы еще раз сталкнемся с этим при рассмотрении утилиты certutil). Исправить это не составляет труда: достаточно в функцию P12U_ListPKCS12File, находящуюся в файла pk12util.c, добавить одну строку:
PRIntnP12U_ListPKCS12File(char *in_file, PK11SlotInfo *slot,                    secuPWData *slotPw, secuPWData *p12FilePw){    SEC_PKCS12DecoderContext *p12dcx = NULL;    SECItem uniPwitem = { 0 };    SECStatus rv = SECFailure;    const SEC_PKCS12DecoderItem *dip;/*Вызов функции для отображения UTF-8*/    SECU_EnableUtf8Display(PR_TRUE);.   .   .   .   .}

После этого проблем с русскими буквами не будет.
$ pk12util -l 1000.p12 -d "." -W 01234567
Certificate(has private key):
Data:
Version: 3 (0x2)
Serial Number: 4096 (0x1000)
Signature Algorithm: PKCS #1 SHA-256 With RSA Encryption
Issuer: E=ca@test.ru,OGRN=1111111111111,INN=222222222222,CN=Удос
товеряюший Центр,OU=Отдел Удостоверя
юший Центр,O=Удостоверяюший Центр,STR
EET=ул. Хавровская, д. 0",L=Хабраград,ST=М
осковская область,C=RU"
Validity:
Not Before: Tue Jul 07 08:40:14 2020
Not After: Fri Aug 06 08:40:14 2021
Subject: E=test@rsa.ru,CN=Тестовый сертификат
Subject Public Key Info:
Public Key Algorithm: PKCS #1 RSA Encryption
RSA Public Key:
Modulus:
9a:9f:6c:60:94:f7:ec:f7:94:b3:51:01:e2:1a:c5:25:
28:bb:02:77:49:52:4d:99:8a:6e:26:12:55:8f:71:34:
04:da:39:24:f9:b4:6b:d0:0a:42:27:1b:b2:d7:9b:d9:
c3:76:b0:e0:1c:7c:21:ce:79:9f:d5:2b:17:63:cb:94:
5b:d9:b2:53:ff:b9:bf:4f:3d:cf:b7:8d:8a:37:ba:02:
8c:da:d2:0d:fd:46:5b:45:1d:95:64:07:6e:fa:88:0d:
a4:bd:b3:4a:ed:99:f1:fd:73:c5:b6:05:a0:e5:ee:6b:
c3:83:5b:d0:64:05:77:6a:18:d8:c8:28:a1:d0:06:41:
23:0d:bb:87:8a:77:14:fb:6c:5d:af:db:2b:0b:11:a3:
16:1b:2b:05:18:26:a9:b5:00:4a:40:da:b3:05:aa:2a:
67:c0:18:0d:03:f7:d2:b9:ba:7c:36:f9:95:2e:56:81:
a3:09:99:5e:20:10:95:38:10:c9:c1:6f:c3:6c:a6:1b:
78:51:c6:e4:4f:11:bc:c0:22:4b:ca:59:16:f2:45:95:
0d:fd:7b:46:cf:c7:ac:1c:3d:d7:26:fc:ad:80:3e:2c:
21:93:29:32:a6:79:e2:a8:c6:e9:5e:45:34:d3:38:57:
8f:cd:95:5e:91:09:84:34:21:d2:16:29:69:75:4d:a3
Exponent: 65537 (0x10001)
Signed Extensions:
Name: Certificate Basic Constraints
Critical: True
Data: Is not a CA.

Name: Certificate Key Usage
Usages: Digital Signature
Key Encipherment
Key Agreement

Name: Certificate Type
Data: <SSL Client,S/MIME>

Name: Extended Key Usage
TLS Web Client Authentication Certificate
E-Mail Protection Certificate

Name: Certificate Subject Key ID
Data:
26:a1:b3:98:1c:fe:62:ba:23:81:96:37:3f:08:bd:70:
d6:f2:b1:46

Name: Certificate Authority Key Identifier
Key ID:
0a:b6:f6:87:64:1d:8e:b3:63:08:29:9f:21:59:ad:47:
d8:ea:07:f4
Issuer:
Directory Name: E=ca@test.ru,OGRN=1111111111111,INN=22222222
2222,CN=Удостоверяюший Центр,OU=Отд
ел Удостоверяюший Центр,O=Удост
оверяюший Центр,STREET=ул. Хавровс
кая, д. 0",L=Хабраград,ST=Московска
я область,C=RU"
Serial Number:
00:a2:9b:22:32:3e:a7:3d:d8

Name: Certificate Subject Alt Name
RFC822 Name: test@rsa.ru

Name: Certificate Issuer Alt Name
Error: Parsing extension: Certificate extension value is invalid.
Data: Sequence {
}

Signature Algorithm: PKCS #1 SHA-256 With RSA Encryption
Signature:
2f:75:7e:71:9e:15:5c:97:fe:a2:e1:2a:52:39:56:55:
e0:62:60:bc:5f:6d:c2:b6:ec:cd:8b:10:b3:b1:3f:e5:
d6:d1:5f:a5:fa:61:c1:ce:3e:db:6a:2f:b2:13:46:8d:
67:cf:18:09:61:97:01:45:bc:99:bb:0c:d6:0a:a3:03:
87:0a:8e:10:3a:d5:e3:94:6d:4a:24:fa:c3:40:0b:43:
c2:3b:00:56:06:c4:d2:fc:b2:7e:e9:00:e5:2f:4b:e2:
3a:91:49:ce:f8:c3:60:ec:01:74:d8:1a:3b:af:e6:f6:
91:db:c5:f1:d7:de:be:18:38:47:41:8a:e2:ef:80:91:
10:54:41:ae:55:22:6f:d7:8c:fa:46:b6:b6:2a:ee:6a:
0c:c9:03:18:af:4e:93:6c:61:f3:b4:78:0c:61:93:f1:
d8:1b:00:c3:e5:29:9a:08:0a:f8:31:67:88:3d:c3:88:
7a:60:c0:c4:52:94:25:56:e5:a3:df:7d:58:c5:df:9a:
c7:22:7e:2c:f6:fb:2c:bf:b7:7f:c5:ca:2b:0f:8c:20:
77:b9:1f:e0:62:5a:3d:d4:6f:12:ea:c8:51:67:a5:75:
ad:e9:ac:9e:4e:2e:2d:34:80:e7:d8:64:f6:8f:2f:33:
32:1f:8b:bc:9c:e8:77:4a:ee:7b:84:31:ec:28:e9:70
Fingerprint (SHA-256):
96:F4:A5:FA:6D:8A:F8:7E:A6:10:49:BD:43:34:C1:92:C6:7D:FF:63:41:8E:69:C0:AC:34:6B:CB:63:7B:56:31
Fingerprint (SHA1):
B6:91:9B:C6:7A:45:9C:92:FD:E7:C7:33:00:FA:91:DF:7D:5F:00:21

Friendly Name: Тестовый сертификат

Certificate:
Data:
Version: 3 (0x2)
Serial Number:
00:a2:9b:22:32:3e:a7:3d:d8
Signature Algorithm: PKCS #1 SHA-256 With RSA Encryption
Issuer: E=ca@test.ru,OGRN=1111111111111,INN=222222222222,CN=Удос
товеряюший Центр,OU=Отдел Удостоверя
юший Центр,O=Удостоверяюший Центр,STR
EET=ул. Хавровская, д. 0",L=Хабраград,ST=М
осковская область,C=RU"
Validity:
Not Before: Tue Jul 07 08:08:11 2020
Not After: Fri Jul 05 08:08:11 2030
Subject: E=ca@test.ru,OGRN=1111111111111,INN=222222222222,CN=Удос
товеряюший Центр,OU=Отдел Удостоверя
юший Центр,O=Удостоверяюший Центр,STR
EET=ул. Хавровская, д. 0",L=Хабраград,ST=М
осковская область,C=RU"
Subject Public Key Info:
Public Key Algorithm: PKCS #1 RSA Encryption
RSA Public Key:
Modulus:
e7:08:ed:83:08:10:7b:48:56:37:8b:e2:4a:31:1a:7b:
0d:4e:d2:a2:67:d7:04:60:a0:09:db:06:64:21:01:4e:
0d:41:d8:61:15:c6:58:83:66:7e:6b:65:72:0d:2b:c3:
50:26:11:04:82:4b:1a:12:d0:dc:e1:13:1c:76:69:0f:
c2:59:e2:5d:60:6d:fe:8a:48:fa:8b:1e:05:07:34:6d:
8a:e3:76:23:42:9e:7b:64:0b:6a:fb:36:63:31:96:df:
ed:d3:e8:7c:6e:39:d4:7d:da:b8:f4:ec:53:57:60:f1:
d8:a4:3a:3f:3b:4a:63:6c:2a:55:90:21:15:23:4a:37:
21:31:a0:c4:bb:84:0d:96:18:3c:3b:ba:92:e3:e2:17:
56:e5:d9:8c:58:24:8a:a3:53:b6:4f:02:4d:30:a6:0f:
34:ad:20:cf:6f:03:ca:23:1e:d3:15:bc:80:09:d8:1e:
90:07:da:90:a9:34:9e:6e:ed:6b:10:b7:a1:a4:a9:b4:
04:ac:6a:40:d8:00:52:d6:6a:28:f2:8c:c6:84:81:8a:
cd:63:a6:53:82:d2:4e:11:ec:94:81:d7:9c:79:8a:30:
9c:40:75:4d:d9:88:0b:cc:a4:0c:5d:6d:23:a6:ac:56:
8c:49:d9:1f:2b:63:cb:50:fc:a3:e0:3e:35:4e:f4:03
Exponent: 65537 (0x10001)
Signed Extensions:
Name: Certificate Basic Constraints
Critical: True
Data: Is a CA with no maximum path length.

Name: Certificate Subject Key ID
Data:
0a:b6:f6:87:64:1d:8e:b3:63:08:29:9f:21:59:ad:47:
d8:ea:07:f4

Signature Algorithm: PKCS #1 SHA-256 With RSA Encryption
Signature:
17:7d:29:dc:4d:6e:4c:99:7a:bc:b2:2a:a5:80:f9:5f:
0c:60:00:2b:f3:f4:ef:19:d7:ed:56:07:5d:24:e1:b3:
f6:43:e2:05:9b:75:ce:cd:cf:27:1e:1c:cd:d8:cc:43:
77:16:04:7e:8a:dd:89:c4:b2:75:ae:f4:84:23:53:18:
fe:be:c5:1d:40:55:aa:91:9f:f5:96:06:5d:07:22:a8:
1c:b9:29:c4:49:2e:75:10:75:22:95:36:16:58:2f:77:
f5:fa:6d:de:c4:67:ca:f3:e1:98:51:b4:ba:b7:2a:7f:
06:db:33:5a:a6:bb:53:57:f4:18:93:16:9c:0e:43:d0:
46:e6:84:55:bb:ff:68:fe:fa:32:d5:23:2a:d5:65:9b:
d9:63:45:6b:53:71:64:dd:da:e1:40:fa:89:30:b1:73:
8b:f8:7c:3c:2f:72:24:ad:e8:5c:07:89:2f:3a:0d:37:
48:29:1f:0d:5f:9e:11:73:56:b8:d9:24:eb:2d:2e:18:
c7:9b:90:62:09:20:61:75:b9:a1:9a:3f:99:34:8e:06:
30:ce:7d:60:42:7d:e0:14:f2:88:f2:41:a0:46:4d:55:
17:d4:c2:15:64:c9:3e:f5:cc:0a:41:f7:c0:d0:94:96:
ea:64:e0:45:3a:e0:a3:d6:22:a9:d1:e3:c4:51:e8:96
Fingerprint (SHA-256):
F5:DF:15:79:5E:1E:41:84:96:8C:8C:CA:37:0C:A6:BB:C3:21:AE:3D:32:42:8C:63:C2:64:BA:0A:74:DC:37:F8
Fingerprint (SHA1):
CF:C6:B9:D4:3C:16:6F:31:91:2A:09:2F:FE:4C:57:89:0F:5A:F1:DB

Friendly Name: Удостоверяюший Центр

Key(shrouded):
Friendly Name: Тестовый сертификат

Encryption algorithm: PKCS #12 V2 PBE With SHA-1 And 3KEY Triple DES-CBC
Parameters:
Salt:
c4:fa:4a:6a:4f:54:a1:7a
Iteration Count: 2048 (0x800)
$

Говоря о создании контейнера PKCS#12 утилитой openssl, то мы воспользовались графической оболочной CAFL63:



Теперь самое время поговорить о хранилище NSS.

Хранилище NSS


Хранилищем NSS является каталог, в котором хранится три базы данных.
В базе данных (БД) cert8.db/cert9.db хранятся сертификаты. В БД key3.db/key4.db хранятся закрытые ключи. И, наконец, в БД secmod.db/pkcs11.txt хранится информация (прежде всего путь к библиотеки), позволяющая работать со сторонними токенами/смарткартами/облаками с интерфейсом PKCS#11.
Для создания хранилища NSS предназначена утилита modutil следующего формата:
modutil -create -force [-dbdir <хранилище NSS>] , где<хранилище NSS> := [<тип базы данных>:]<каталог хранилища NSS><тип базы данных> := dbm|sql , например:$modutil -create -force -dbdir "sql:/~/TEST_NSS"

Тип базы данных dbm предполагает создание баз данных Berkeley (cert8.db, key3.db и secmod.db). Тип sql предполагает создание баз данных SQLite3 (cert9.db, key9.db и текстовый файл pkcs11.txt). По умолчанию создаются базы данных SQLite3. Каталог хранилища NSS должен быть создан заранее. По умолчанию (если не задан параметр -dbdir или -d) используется хранилище ".netscape" в домашней папке пользователя. Именно это хранилище использует, например, броузер google-chrome.
Также просто из хранилища сертификатов старого формата (DBM) (cert8.db, key3.db и secmod.db) создается и хранилище в новом формате (SQLite3) (cert9.db, key4.db и pkcs11.txt). Для этого достаточно выполнить утилиту работы с сертификатами certutil в режиме просмотра ключей (-K) или сертификатов (-L) с параметром X, например:
$certutil -K -X -d ~/TEST_NSS

или
$certutil -L -X -d ~/TEST_NSS

Отметим, что такие хранилища есть во всех проектах, построенных на NSS, включая Firefox, Thunderbird, Seamonkey, GoogleChrome, LibreOffice.
После создания хранилиша NSS автоматически становится доступным встроенный модуль NSS Internal PKCS #11 Module с двумя встроенными токенами:
$modutil -list -dbdir ~/TEST_NSSListing of PKCS #11 Modules-----------------------------------------------------------  1. NSS Internal PKCS #11 Module           uri: pkcs11:library-manufacturer=Mozilla%20Foundation;library-description=NSS%20Internal%20Crypto%20Services;library-version=3.52         slots: 2 slots attached        status: loaded         slot: NSS Internal Cryptographic Services        token: NSS Generic Crypto Services          uri: pkcs11:token=NSS%20Generic%20Crypto%20Services;manufacturer=Mozilla%20Foundation;serial=0000000000000000;model=NSS%203         slot: NSS User Private Key and Certificate Services        token: NSS Certificate DB          uri: pkcs11:token=NSS%20Certificate%20DB;manufacturer=Mozilla%20Foundation;serial=0000000000000000;model=NSS%203-----------------------------------------------------------$

Токен NSS Generic Crypto Services реализует криптографические функции и механизмы, а токен NSS Certificate DB предназначен для хранения сертификатов и ключей и другой дополнительной информации (например, о доверии корневым сертификатам). Токен NSS Certificate DB (внутренний токен NSS) для хранения сертификатов использует базу данных cert8.db/cert9.db, а закрытые ключи хранит в базе данных key3.db/key4.db.
Получить информацию о встроенных токенах внутреннего модуля NSS Internal PKCS #11 Module, включая поддерживаемые по умолчанию криптографические механизмы, можно путем выполнения следующей команды:
$modutil -list 'NSS Internal PKCS #11 Module' -dbdir ~/TEST_NSS
Name: NSS Internal PKCS #11 Module
Library file: **Internal ONLY module**
Manufacturer: Mozilla Foundation
Description: NSS Internal Crypto Services
PKCS #11 Version 3.0
Library Version: 3.52
Cipher Enable Flags: None
Default Mechanism Flags: RSA:ECC:DH:RC2:RC4:DES:AES:CAMELLIA:SEED:SHA1:SHA256:SHA512:MD5:MD2:SSL:TLS

Slot: NSS Internal Cryptographic Services
Slot Mechanism Flags: RSA:ECC:DH:RC2:RC4:DES:AES:CAMELLIA:SEED:SHA1:SHA256:SHA512:MD5:MD2:SSL:TLS
Manufacturer: Mozilla Foundation
Type: Software
Version Number: 3.52
Firmware Version: 1.0
Status: Enabled
Token Name: NSS Generic Crypto Services
Token Manufacturer: Mozilla Foundation
Token Model: NSS 3
Token Serial Number: 0000000000000000
Token Version: 4.0
Token Firmware Version: 0.0
Access: Write Protected
Login Type: Public (no login required)
User Pin: NOT Initialized

Slot: NSS User Private Key and Certificate Services
Slot Mechanism Flags: None
Manufacturer: Mozilla Foundation
Type: Software
Version Number: 3.52
Firmware Version: 1.0
Status: Enabled
Token Name: NSS Certificate DB
Token Manufacturer: Mozilla Foundation
Token Model: NSS 3
Token Serial Number: 0000000000000000
Token Version: 0.0
Token Firmware Version: 0.0
Access: NOT Write Protected
Login Type: Public (no login required)
User Pin: Initialized
$

Подключение дополнительного модуля для работы с внешними устройствами PKCS#11 делается все той же утилитой modutil:
$modutil -add <имя модуля> -libfile <путь к библиотеке> [-dbdir <хранилище NSS>]

Например, для работы с токенами РУТокен, поддерживающими российскую криптографии, достаточно выполнить следующую команду:
$modutil -add "ruTokenECP" -libfile /usr/lib64/librtpkcs11ecp_2.0.so -dbdir $HOME/.netscape 

Для получения списка модулей, которые поддерживает то или иное хранилище NSS, с информацией о каждом модуле (библиотека, список поддерживаемых слотов и подключенных к ним товенах) необходимо выполнить следующую команду:
$modutil -list [-dbdir <хранилище NSS>]

Для получения полной информации о подключенных токенах для конкретного модуля необходимо будет выполнить следующую команду:
$modutil -list <имя модуля> [-dbdir <хранилище NSS>]

Мы ее уже прльзовались, когда получали информацию о встроенных (внутренних) токенах NSS.
И, если можно добавить модуль, то можно и удалить модуль из базы данных:
$modutil -delete <имя модуля> [-dbdir <хранилище NSS>] 

Доступ к внешним токенам, как правило, защищен PIN-кодом. А вот доступ к встроенному токену NSS Certificate DB по умолчанию не защищен паролем (PIN-кодом). Но установить не составляет труда, например:
$modutil -dbdir $HOME/.netscape -changepw "NSS Certificate DB"  

Аналогичным образом можно поменять PIN-код и у внешнего токена.
Теперь самое время перейти к работе с токенами, его мехамизмами и объектами.

Доступ к объектам токена PKCS#11


Для доступа к объектам (ключи, сертификаты) токенов PKCS#11 служит утилита certutil. Отметим, что своей функциональности утилита certutil не уступает утилите openssl. Для просмотра функциональности утилиты certutil достаточно выполнить команду:
$certutil -H

Но нас сейчас интересует только доступ с сертификатам и ключам. Напомним, что при хранении на токене PKCS#11 и тем и другим, как правило, приписываются атрибуты CKA_ID и CKA_LABEL. Для просмотра списка сертификатов на том или ином токене необходимо выполнить следующую команду:
$certutil -L [-d <хранилище NSS>] [-h <метка токена>]

В качестве метки токена может быть указана реальная метка токена, или одно из ключевых слов all или internal. В первом случае (метка токена all) перебираются все токены, подключенные к хранилищу NSS, а во втором случае (internal или NSS Certificate DB) будет просматриваться внутренний токен хранилища NSS Certificate DB.
Например, для получения списка сертификатов, находящихся на токене с меткой LS11SW2016, модуль доступа к которому зареегистрирован в хранилище NSS "/home/a513/tmp/TEST_NSS" необходимо выполнить следующую команду:
$ certutil -L -d /home/a513/tmp/TEST_NSS -h "LS11SW2016"Enter Password or Pin for "LS11SW2016":Certificate Nickname                                  Trust Attributes                                                      SSL,S/MIME,JAR/XPILS11SW2016:TestCA_P11                                 u,u,uLS11SW2016:clientnss from CryptoArmPKCS               u,u,uLS11SW2016:ТестШифр                                   u,u,uLS11SW2016:Thenderbird-60.3.0 from 32                 u,u,uLS11SW2016:Всесильный Хабр from УЦ 12_512             u,u,uLS11SW2016:Text4Key                                   u,u,uLS11SW2016:KmailKleopatra от GnuPG-2001               u,u,uLS11SW2016:setvernss from CryptoArmPKCS               u,u,uLS11SW2016:Ф И О from УЦ 12_512                       u,u,uLS11SW2016:Test 12 512                                u,u,uLS11SW2016:Kleopatra от GnuPG-2001                    ,,   $

Список сертификатов, находящихся на токене, выводится в две колонки. В первой колонке дается nicknamе сертификата, а во второй атрибуты доверия этого сертификата.
При чем, если сертификат имеет закрытый ключ на токене, где он находится, то в этой колонке
будет значение u,u,u
.
Если атрибуты не устанавливались, то в колонке будет значение ",,".
Сертификаты с аттрибутами доверия u,u,u (у них есть закрытый ключ) могут быть использованы для аутентификации или формирования электронной подписи.
Другие значения атрибутов доверия могут быть установлены для сертификатов, находящихся на встроенном токене. NSS Certificate DB. Но об этом позже.
Что же такое nickname сертификата в NSS?
<nickname> := [<метка токена>:]<CKA_LABEL>

Для внутреннего токена (NSS Certificate DB) метка токена в nickname может отсутствовать.
Если рассматривать мехамизмы токенов PKCS#11, то мы увидим, что операции генерации ключей, импорта сертификатов и ключей сами по себе не предусматривают установку значений аттрибутов CKA_ID и CKA_LABEL. Это все перекладывается на разработчика прикладного ПО. Но, если вы используете, утилиты NSS для работы с токенами, то оказывается, что они берут на себя эти хлопоты.
Для просмотра списка закрытых ключей используется следующая команда:
$certutil -K [-d <хранилиже NSS>] [-h <метка токена>]

При этом распечатывается тип ключа, CKA_ID и CKA_LABEL ключа.
Но вернемся к сертификатам. Для просмотра сертификата, находящегося на токене, в текстовом виде достаточно выполнить команду:
$certutil -L [-d <хранилище NSS>] -n <nickname сертификата>

Например:
certutil -L -d '/home/a513/tmp/TEST_NSS' -n 'NSS Certificate DB:Тестовый сертификат'
$ certutil -L -d "/home/a513/tmp/TEST_NSS" -n NSS Certificate DB: Тестовый сертификат
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4096 (0x1000)
Signature Algorithm: PKCS #1 SHA-256 With RSA Encryption
Issuer: E=ca@test.ru,OGRN=1111111111111,INN=222222222222,CN=Удос
товеряюший Центр,OU=Отдел Удостоверя
юший Центр,O=Удостоверяюший Центр,STR
EET=ул. Хавровская, д. 0",L=Хабраград,ST=М
осковская область,C=RU"
Validity:
Not Before: Tue Jul 07 08:40:14 2020
Not After: Fri Aug 06 08:40:14 2021
Subject: E=test@rsa.ru,CN=Тестовый сертификат
Subject Public Key Info:
Public Key Algorithm: PKCS #1 RSA Encryption
RSA Public Key:
Modulus:
9a:9f:6c:60:94:f7:ec:f7:94:b3:51:01:e2:1a:c5:25:
28:bb:02:77:49:52:4d:99:8a:6e:26:12:55:8f:71:34:
04:da:39:24:f9:b4:6b:d0:0a:42:27:1b:b2:d7:9b:d9:
c3:76:b0:e0:1c:7c:21:ce:79:9f:d5:2b:17:63:cb:94:
5b:d9:b2:53:ff:b9:bf:4f:3d:cf:b7:8d:8a:37:ba:02:
8c:da:d2:0d:fd:46:5b:45:1d:95:64:07:6e:fa:88:0d:
a4:bd:b3:4a:ed:99:f1:fd:73:c5:b6:05:a0:e5:ee:6b:
c3:83:5b:d0:64:05:77:6a:18:d8:c8:28:a1:d0:06:41:
23:0d:bb:87:8a:77:14:fb:6c:5d:af:db:2b:0b:11:a3:
16:1b:2b:05:18:26:a9:b5:00:4a:40:da:b3:05:aa:2a:
67:c0:18:0d:03:f7:d2:b9:ba:7c:36:f9:95:2e:56:81:
a3:09:99:5e:20:10:95:38:10:c9:c1:6f:c3:6c:a6:1b:
78:51:c6:e4:4f:11:bc:c0:22:4b:ca:59:16:f2:45:95:
0d:fd:7b:46:cf:c7:ac:1c:3d:d7:26:fc:ad:80:3e:2c:
21:93:29:32:a6:79:e2:a8:c6:e9:5e:45:34:d3:38:57:
8f:cd:95:5e:91:09:84:34:21:d2:16:29:69:75:4d:a3
Exponent: 65537 (0x10001)
Signed Extensions:
Name: Certificate Basic Constraints
Critical: True
Data: Is not a CA.

Name: Certificate Key Usage
Usages: Digital Signature
Key Encipherment
Key Agreement

Name: Certificate Type
Data: <SSL Client,S/MIME>

Name: Extended Key Usage
TLS Web Client Authentication Certificate
E-Mail Protection Certificate

Name: Certificate Subject Key ID
Data:
26:a1:b3:98:1c:fe:62:ba:23:81:96:37:3f:08:bd:70:
d6:f2:b1:46

Name: Certificate Authority Key Identifier
Key ID:
0a:b6:f6:87:64:1d:8e:b3:63:08:29:9f:21:59:ad:47:
d8:ea:07:f4
Issuer:
Directory Name: E=ca@test.ru,OGRN=1111111111111,INN=22222222
2222,CN=Удостоверяюший Центр,OU=Отд
ел Удостоверяюший Центр,O=Удост
оверяюший Центр,STREET=ул. Хавровс
кая, д. 0",L=Хабраград,ST=Московска
я область,C=RU"
Serial Number:
00:a2:9b:22:32:3e:a7:3d:d8

Name: Certificate Subject Alt Name
RFC822 Name: test@rsa.ru

Name: Certificate Issuer Alt Name
Error: Parsing extension: Certificate extension value is invalid.
Data: Sequence {
}

Signature Algorithm: PKCS #1 SHA-256 With RSA Encryption
Signature:
2f:75:7e:71:9e:15:5c:97:fe:a2:e1:2a:52:39:56:55:
e0:62:60:bc:5f:6d:c2:b6:ec:cd:8b:10:b3:b1:3f:e5:
d6:d1:5f:a5:fa:61:c1:ce:3e:db:6a:2f:b2:13:46:8d:
67:cf:18:09:61:97:01:45:bc:99:bb:0c:d6:0a:a3:03:
87:0a:8e:10:3a:d5:e3:94:6d:4a:24:fa:c3:40:0b:43:
c2:3b:00:56:06:c4:d2:fc:b2:7e:e9:00:e5:2f:4b:e2:
3a:91:49:ce:f8:c3:60:ec:01:74:d8:1a:3b:af:e6:f6:
91:db:c5:f1:d7:de:be:18:38:47:41:8a:e2:ef:80:91:
10:54:41:ae:55:22:6f:d7:8c:fa:46:b6:b6:2a:ee:6a:
0c:c9:03:18:af:4e:93:6c:61:f3:b4:78:0c:61:93:f1:
d8:1b:00:c3:e5:29:9a:08:0a:f8:31:67:88:3d:c3:88:
7a:60:c0:c4:52:94:25:56:e5:a3:df:7d:58:c5:df:9a:
c7:22:7e:2c:f6:fb:2c:bf:b7:7f:c5:ca:2b:0f:8c:20:
77:b9:1f:e0:62:5a:3d:d4:6f:12:ea:c8:51:67:a5:75:
ad:e9:ac:9e:4e:2e:2d:34:80:e7:d8:64:f6:8f:2f:33:
32:1f:8b:bc:9c:e8:77:4a:ee:7b:84:31:ec:28:e9:70
Fingerprint (SHA-256):
96:F4:A5:FA:6D:8A:F8:7E:A6:10:49:BD:43:34:C1:92:C6:7D:FF:63:41:8E:69:C0:AC:34:6B:CB:63:7B:56:31
Fingerprint (SHA1):
B6:91:9B:C6:7A:45:9C:92:FD:E7:C7:33:00:FA:91:DF:7D:5F:00:21

Mozilla-CA-Policy: false (attribute missing)
Certificate Trust Flags:
SSL Flags:
User
Email Flags:
User
Object Signing Flags:
User
$

Если у просматриваемого сертификата на этом же токене имеется закрытый ключ, то флаги доверия сертификата (Certificate Trust Flags) будут иметь значение User:
Certificate Trust Flags:
SSL Flags:
User
Email Flags:
User
Object Signing Flags:
User

Для экспорта сертификата с токена в стандартный вывод достаточно добавить дополнительный параметр "-a" или "-r". Параметр "-a" предписывает выводить сертификат в формате PEM:
$certutil -L -d '/home/a513/tmp/TEST_NSS' -n 'NSS Certificate DB:Тестовый сертификат' -a
-----BEGIN CERTIFICATE-----
MIIGiTCCBXGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwggF2MQswCQYDVQQGEwJS
VTEsMCoGA1UECAwj0JzQvtGB0LrQvtCy0YHQutCw0Y8g0L7QsdC70LDRgdGC0Ywx
GzAZBgNVBAcMEtCl0LDQsdGA0LDQs9GA0LDQtDEqMCgGA1UECQwh0YPQuy4g0KXQ
sNCy0YDQvtCy0YHQutCw0Y8sINC0LiAwMTAwLgYDVQQKDCfQo9C00L7RgdGC0L7Q
stC10YDRj9GO0YjQuNC5INCm0LXQvdGC0YAxOzA5BgNVBAsMMtCe0YLQtNC10Lsg
0KPQtNC+0YHRgtC+0LLQtdGA0Y/RjtGI0LjQuSDQptC10L3RgtGAMTAwLgYDVQQD
DCfQo9C00L7RgdGC0L7QstC10YDRj9GO0YjQuNC5INCm0LXQvdGC0YAxGjAYBggq
hQMDgQMBARIMMjIyMjIyMjIyMjIyMRgwFgYFKoUDZAESDTExMTExMTExMTExMTEx
GTAXBgkqhkiG9w0BCQEWCmNhQHRlc3QucnUwHhcNMjAwNzA3MDg0MDE0WhcNMjEw
ODA2MDg0MDE0WjBMMS4wLAYDVQQDDCXQotC10YHRgtC+0LLRi9C5INGB0LXRgNGC
0LjRhNC40LrQsNGCMRowGAYJKoZIhvcNAQkBFgt0ZXN0QHJzYS5ydTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJqfbGCU9+z3lLNRAeIaxSUouwJ3SVJN
mYpuJhJVj3E0BNo5JPm0a9AKQicbsteb2cN2sOAcfCHOeZ/VKxdjy5Rb2bJT/7m/
Tz3Pt42KN7oCjNrSDf1GW0UdlWQHbvqIDaS9s0rtmfH9c8W2BaDl7mvDg1vQZAV3
ahjYyCih0AZBIw27h4p3FPtsXa/bKwsRoxYbKwUYJqm1AEpA2rMFqipnwBgNA/fS
ubp8NvmVLlaBowmZXiAQlTgQycFvw2ymG3hRxuRPEbzAIkvKWRbyRZUN/XtGz8es
HD3XJvytgD4sIZMpMqZ54qjG6V5FNNM4V4/NlV6RCYQ0IdIWKWl1TaMCAwEAAaOC
AkcwggJDMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgOoMBEGCWCGSAGG+EIBAQQE
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwHQYDVR0OBBYEFCah
s5gc/mK6I4GWNz8IvXDW8rFGMIIBsAYDVR0jBIIBpzCCAaOAFAq29odkHY6zYwgp
nyFZrUfY6gf0oYIBfqSCAXowggF2MQswCQYDVQQGEwJSVTEsMCoGA1UECAwj0JzQ
vtGB0LrQvtCy0YHQutCw0Y8g0L7QsdC70LDRgdGC0YwxGzAZBgNVBAcMEtCl0LDQ
sdGA0LDQs9GA0LDQtDEqMCgGA1UECQwh0YPQuy4g0KXQsNCy0YDQvtCy0YHQutCw
0Y8sINC0LiAwMTAwLgYDVQQKDCfQo9C00L7RgdGC0L7QstC10YDRj9GO0YjQuNC5
INCm0LXQvdGC0YAxOzA5BgNVBAsMMtCe0YLQtNC10Lsg0KPQtNC+0YHRgtC+0LLQ
tdGA0Y/RjtGI0LjQuSDQptC10L3RgtGAMTAwLgYDVQQDDCfQo9C00L7RgdGC0L7Q
stC10YDRj9GO0YjQuNC5INCm0LXQvdGC0YAxGjAYBggqhQMDgQMBARIMMjIyMjIy
MjIyMjIyMRgwFgYFKoUDZAESDTExMTExMTExMTExMTExGTAXBgkqhkiG9w0BCQEW
CmNhQHRlc3QucnWCCQCimyIyPqc92DAWBgNVHREEDzANgQt0ZXN0QHJzYS5ydTAJ
BgNVHRIEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQAvdX5xnhVcl/6i4SpSOVZV4GJg
vF9twrbszYsQs7E/5dbRX6X6YcHOPttqL7ITRo1nzxgJYZcBRbyZuwzWCqMDhwqO
EDrV45RtSiT6w0ALQ8I7AFYGxNL8sn7pAOUvS+I6kUnO+MNg7AF02Bo7r+b2kdvF
8dfevhg4R0GK4u+AkRBUQa5VIm/XjPpGtrYq7moMyQMYr06TbGHztHgMYZPx2BsA
w+UpmggK+DFniD3DiHpgwMRSlCVW5aPffVjF35rHIn4s9vssv7d/xcorD4wgd7kf
4GJaPdRvEurIUWelda3prJ5OLi00gOfYZPaPLzMyH4u8nOh3Su57hDHsKOlw
-----END CERTIFICATE-----

Для вывода в DER-формате используется параметр "-r".

Установить сертификат на токен


Предположим у вас есть контейнер PKCS#12 с личным сертификатом и закрытым ключои, который вы сформировали средствами OpenSSL. Для экспорта личного сертификата в хранилище NSS используется команда pk12util следующего вида:
$pk12util -i <контейнер pkcs12> [-d <хранилище NSS>] [-h <метка токена>]

Если вы хотите импортировать сертификат на конкретный токен, то необходимо задать его метку. Отметим еще одну особенность. Если контейнер содержит в своем составе корневые сертификаты, то они будут сохранены на внутреннем токене NSS Certificate DB. При этом атрибуты доверия по умолчанию не устанавливаются. Если по каким либо причинам требуется установка атрибутов доверия к тем или иным сертификатам центров регистрации (корневым сертификатам) или SSL-сертификатам Для установки аттрибутов доверия используется утилита certutil следующего вида:
$certutil -M -n <nickname-сертификата> -t <аттрибуты доверия> [-d <хранилище NSS>]

Для каждого сертификата есть три доступные категории доверия, выраженные в следующем порядке: SSL, S/MIME, подпись кода для каждого параметра доверия:
<аттрибуты доверия> := x,y,z , где

  • x аттрибуты для SSL,
  • y атрибуты для S/MIME,
  • z атрибуты для подписи кода.

Каждая позиция x, y и z может быть пустой (используйте ,, без явного доверия) или содержать один или несколько атрибутов:
  • p Действительный узел (Valid peer);
  • P доверенный узел (подразумевается и p)
  • c Действительный сертификат центра регистриции (Valid CA)
  • T доверенный CA для выдачи клиентских сертификатов (подразумевается и c)
  • C Доверенный CA для выдачи сертификатов сервера (только SSL) (подразумевается c)

Отметим, что не все внешние токены допускают операцию импорта закрытого ключа на токен.
Для установки просто сертификата из файла используется утилита certutil следующего вида:
$certutil -A -n <метка сертификата> -t <аттрибуты доверия> [-d <хранилище NSS>] [-h <метка токена>] [-a] [-i <файл с сертификатом>]

К сожалению, в данном контексте <метка сертификата> не эквивалентна nickname сертификата, рассмотренному выше. Здесь метка сертификата соответствует просто CKA_LABEL, просто метки без указания метки токена. Какой-то разнобой.
По умолчанию предполагается, что сертификат находится в файле в DER-кодировке. Если сертификат в PEM-кодировке, то необходимо задавать параметр "-a". Параметр "-i" не задан, то сертификат будет браться из стандартного ввода (stdin). По умолчанию сертификаты устанавливаются в токен NSS Certificate DB, но можно установить сертификат и на внешний токен (параметр "-h"). При установке сертификата на внешний токен, сертификат будет установлен как на внутренний токен (NSS Certificate DB), так и внешний токен. При этом аттрибуты довермя для внешнего токена будут проигнорированы, будет выдано предупреждение: could not change trust on certificate.
Дубликат сертификата на внутреннем токене при желании можно удалить.
Для удаления сертификата с любого токена используется следующая команда:
$certutil -D [-d <хранилище NSS>] -n <nickname-сертификата>

Например, для удаления с токена RuTokenECP20 сертификата с меткой (CKA_LABEL) Пользователь1 достаточно выполнить следующую команду:
$certutil -D -d /home/a513/tmp/TEST_NSS -n "RuTokenECP20:Пользователь1"

При удалении сертификата его закрытый ключ не удаляется (если он есть, конечно). Для удаления закрытого ключа надо выполнить команду вида:
$certutil -F [-k <тип ключа>] [-d <хранилище NSS>] -n <nickname-сертификата>, где<тип ключа> := rsa|dsa|ec

Теперь, когда у нас имеется хранилище NSS, токены с личными сертификатами, можно поработать и с электронной подписью.

Формирование и проверка электронной подписи


Для работы с электронной подписью в пакете NSS есть три утилиты.
Для формирования электронной подписи используется утилита p7sign:
$pksign -k <nickname закрытого ключа> [-d <хранилище NSS>] [-e] [-i <подписываемый файл >] [-o <файл для электронной подписи>], где<nickname закрытого ключа> := [<метка токена>:]<CKA_LABEL>

К сожалению, утилита формирует подпись без привязки ко времени ее формирования. Но это легко исправляется. Достаточно в функцию SignFile в утилите p7sign.c добавть струку с вызовом функции добавления времени формирования подписи:
SignFile(FILE *outFile, PRFileDesc *inFile, CERTCertificate *cert,         PRBool encapsulated){  . . .     /* XXX Need a better way to handle that usage stuff! */    cinfo = SEC_PKCS7CreateSignedData(cert, certUsageEmailSigner, NULL,                                      SEC_OID_SHA1,                                      encapsulated ? NULL : &digest,                                      NULL, NULL);    if (cinfo == NULL)        return -1;/*Добавляем время формирования подписи*/    SEC_PKCS7AddSigningTime(cinfo);    if (encapsulated) {        SEC_PKCS7SetContent(cinfo, (char *)data2sign.data, data2sign.len);    } . . . }

Теперь будет формироваться электронная подпись в формате CAdes-BES.
В NSS принято, что сертификат и его закрытый ключ хранятся на одном токене и их метки (CKA_LABEL), также как и CKA_ID, совпадают. Соответственно совпадают и nickname закрытого ключа и nickname самого сертификата. Напомним, что о наличии закрытого ключа у сертификата можно узнать по значению u,u,u атрибутов доверия у просматриваемого сертификата (команда certutil -L).
По умолчанию утилита p7sign формирует отсоединенную подпись. Если нужна присоединенная подпись, то необходимо указать параметр "-e". Надо иметь ввиду, что подпись не будет сформирована, если в хранилище NSS, на его внутреннем токене, отсутствует цепочка корневых сертификатов для сертификата подписанта или не выставлены атрибуты доверия для них.
Для проверки электронной подписи используется утилита p7verify:
$p7verify -c <подписанный файл> -s <файл с ЭП> [-d <хранилище NSS>] [-u <использование сертификата>] 

Интерес представляет параметр "-u". Он предписывает проверить тип сертификата подписанта и если он не соответствует заданному типу, то подпись признать недействительной.
Значение <использование сертификата> может принимать следующие значения:
0 certUsageSSLClient
1 certUsageSSLServer
2 certUsageSSLServerWithStepUp
3 certUsageSSLCA
4 certUsageEmailSigner
5 certUsageEmailRecipient
6 certUsageObjectSigner
7 certUsageUserCertImport
8 certUsageVerifyCA
9 certUsageProtectedObjectSigner
10 certUsageStatusResponder
11 certUsageAnyCA
12 certUsageIPsec


По умолчанию, используется certUsageEmailSigner (4).
Утилита p7content позволяет получить информацию о времени подписания документа, кем он был подписан и адрес электронной почты подписата. В случае, если подпись была присоединенной, то и извлекается и сам контент, который был подписан.
Утилита p7content имеет следующий формат:
$p7content [-d <хранилище NSS>] [-i <файл с ЭП>] [-o <выходной файл>]

Например:
$ p7content -d "sql:/home/a513/tmp/TEST_NSS" -i "/home/a513/DATE_NSS.txt.p7s"Content printed between bars (newline added before second bar):#Подписанный контент находится между двумя строками, состоящими из символа "-" (тире)#Перед второй строкой добавлен перевод строки---------------------------------------------Тестирование пакета NSS---------------------------------------------Content was not encrypted.Signature is valid.The signer's common name is Пользователь 1The signer's email address is user1@mail.ruSigning time: Fri Jul 17 10:00:45 2020There were certs or crls included.$

Это только часть утилит, входящих в состав пакета NSS. Естественно, как и в openssl, так есть утилиты (или команды), которые позволяют создать запросы на сертификаты и выпускать сертификаты, т.е. можно развернуть полнофункциональный удостоверящий центр, как это делается с использованием openssl. Можно поднять tls-сервер утилитой selfserv(аналог openssl s_server) и воспользоваться tls-клиентом tstclnt (аналог openssl s-client) и многое, многое другое.
Кстати, список шифрсьютов (ciphersuite) в NSS можно получить утилитой listsuites. В OpenSSL для этих целей служит команда openssl ciphers. Соответствие имен набора шифров OpenSSL в имена IANA шифрсьютов можно найти здесь.
Действительно, openssl и NSS две стороны (два типа интерфейсов для одних и тех же протоколов и стандартов) одной медали Инфраструктуры Открытых ключей.
И, если будут востребованность, повествование будет продолжено.
В завершении, хотелось бы еще остановиться на графической оболочке для NSS. Для OpenSSL существуют различные графические оболочки. Отметим только две из них XCA и CAFL63.

Графическая оболочка guinsspy для пакета NSS


Первых вопрос, на чем разрабатывать? Было решено на Python-е.
Второй вопрос, на чем писать графический интерфейс? Ответ Tkinter.
Первоначально gui разрабатывалось на PAGE. Но потом от него отошли. Но контекст остался.
Функционал утилиты базируется на рассмотренных выше утилитах и командах. Помимо них была добавлена еще одна функция Создать запрос на сертификат, которая базируется на команде certutil -R:



В качестве темы оформления виджетов решено было использовать тему Breeze для python3 и тему Arc для python2, поскольку для последнего отсутствует тема Breeze. Для этого надо установить пакет с темами для pythona:
$pip install ttkthemes

Еще нам потребуется пакет fsb795 для работы с сертификатами:
$pip install fsb795

Утилиты NSS, на базе которых строится графическая оболочка guinsspy, очень часто запрашавают пароли или PIN-оды через консоль. Единственным способом взаимодействия с ними через графический интерфейс является использование пакета pexpect:
$pip install pexpect

В качестве примере использования пакета pexpect приведем код процедуры импорта контейнера PKCS#12:
importP12
def importP12(frameManager):    global readpw    global filename    tokname = frameManager.STCombobox1.get()    fp12 = frameManager.SEntry1.get()    if (fp12 == () or fp12 == ''):        tkMessageBox.showinfo(title="Импорт контейнера PKCS#12", message='Контейнер не выбран\n')        return (-1, "", "")    filename = fp12    if sys.platform != "win32":        cmd_import_p12 = '"' + patch_win + 'pk12util" -i "' + fp12 + '" -h "' + tokname + '"  -d "' + NSSname + '"'        id = pexpect.spawn(cmd_import_p12, timeout=1)    else:        cmd_import_p12 = '"' + patch_win + 'pk12util" -i "' + fp12 + '" -h "'  + tokname + '"  -d "' + NSSname + '"'        id = pexpect.popen_spawn.PopenSpawn(cmd_import_p12, timeout=10)    while(True):        ret = id.expect(["Enter Password or Pin", "Enter password for PKCS12 file",pexpect.EOF, pexpect.TIMEOUT])        if (ret == 0 or ret == 1):            root.update()            if (ret == 0):                password('', tokname, 0)                pasP11 = readpw            else:                password('', os.path.basename(fp12), 1)                pasP12 = readpw            if (readpwok == 0):                if sys.platform != "win32":                    id.close()                return (-3, fp12, "")            if sys.platform != "win32":                id.send(readpw)                id.send("\r")            else:                id.sendline(readpw)            lseek = 1        elif (ret == 2):            break        elif (ret == 3):            break            if sys.platform != "win32":                id.close()            return (-1, fp12, "")    if sys.platform != "win32":        res = id.before.decode('UTF-8')        id.close()    else:        res = id.before.decode('UTF-8')    if (res.find("PKCS12 IMPORT SUCCESSFUL") != -1):        ret = 0    elif (res.find("SEC_ERROR_BAD_PASSWORD") != -1):        ret = -1        return (ret, fp12, "")    else:        ret = -2    return (ret, fp12, res)


Бесконечный цикл (while(True):) в процедуре ждет наступления одного из четырех событий:
ret = id.expect(["Enter Password or Pin", "Enter password for PKCS12 file",pexpect.EOF, pexpect.TIMEOUT])

Первое событие связано с приглашением на ввод пароля или PIN-кода (Enter Password or Pin).
При его наступлении на экране будет отбражаться окно для ввода PIN-кода(левое окно на скриншоте):



Второе событие связано с вводом пароля к контейнеру PKCS#12 (Enter password for PKCS12 file). При его наступлении на экране будет отбражаться окно для ввода пароля к файлу с контейнером PKCS#12 (правое окно на скриншоте).
Третье событие связано с завершением работы утилиты pk12util (pexpect.EOF), и четвертое событие связано с завершением работы утилиты по таймауту (pexpect.TIMEOUT).
Исходный код утилиты guinsspy можно найти здесь. Дистрибутив пакета NSS для работы с токена PKCS#11 с российской криптографией для платформы Linux x86_64 можно найти там же.
Для тестирования токенов с российской криптографией скопируйте папку NSS_GOST_3.52.1_Linux_x86_64 в свой домашний каталог. Создайте скрипт guinsspy_gost.sh:
export LD_LIBRARY_PATH=~/NSS_GOST_3.52.1_Linux_x86_64:$LD_LIBRARY_PATHexport PATH=~/NSS_GOST_3.52.1_Linux_x86_64:$PATHpython3 guinsspy.py

Теперь запустите этот скрипт и работайте с российскими токенами, например, с облачным токеном.

И напоследок, скриншоты создания запроса на сертификат:



Полученный запрос можно будет передать в УЦ CAFL63, выпустить там сертификат, установить на токен, на котором был создан закрытый ключ. А дальше использовать этот сертификат, например, для подписания документов.
Подробнее..

Конструктор Lego и объектно-ориентированное программирование в Tcl. Разбор сертификата x509.v3

21.12.2020 18:22:45 | Автор: admin
imageЧасто приходится слышать, что скриптовому языку Tcl не хватает поддержки объектно-ориентированного стиля программирования. Сам я до последнего времени мало прибегал к объектно-ориентированному программированию, тем более в среде Tcl. Но за Tcl стало обидно. Я решил разобраться. И оказалось, что практически с момента своего появления появилась возможность объектно-ориентированного программирования (ООП) в среде Tcl. Все неудобство заключалось в необходимости подключить пакет с поддержкой ООП. А таких пакетом было и есть несколько, как говорится на любой вкус. Это и Incr Tcl, Snit и XoTcl.
Программисты, привыкшие к языку C++, чувствуют себя как дома, программируя в среде Incr Tcl. Это было одним из первых широко используемых расширений для OOП на основе Tcl.
Пакет Snit в основном используется при построении Tk-виджетов, а XoTcl и его преемник nx предназначались для исследования динамического объектно-ориентированного программирования.
Обобщение опыта, полученного при использовании этих систем, позволило внедрить ООП в ядро Tcl начиная с версии 8.6. Так появился TclOO Tcl Object Oriented.
Сразу отметим, что Tcl не просто поддерживает объектно-ориентированное программирование, а в полном смысле динамическое объектно-ориентированное программирование.
Разрабатывая приложения на Tcl/Tk, например удостоверяющий центр CAFL63, я не прибегал к ООП. И, как сейчас понимаю, зря. Где, где, а в УЦ объектов хватает. Это и запросы на сертификаты, это и сами сертификаты, списки отозванных сертификатов и много чего другого:



Начать было решено с рассмотрения сертификата x509.v3 с учетом российской специфики как объекта при ООП. Тем более, что имеется опыт разбора квалифицированного сертификата на Python. Именно на примере разбора и работы с сертификатом мы и покажем объектно-ориентированный стиль программирования в TclOO.

О DER и BER кодировках

Для доступа к сертификату будет создан класс certificate, в конструкторе которого при создании объекта конкретного сертификата будет проводится разбор его на составные части. Для этого нам потребуется в первую очередь пакет asn (package require asn), который поможет с разбором asn-структуры сертификата. К сожалению, этот пакет (кстати, в других скриптовых языках встречается аналогичная проблема) заточен на разбор asn-структур в DER-кодировке. Но сегодня еще встречаются сертификаты (и электронные подписи и много чего другого) в BER-кодировке. Но оказалось решить эту проблему можно достаточно просто, заменив процедуру ::asn::asnLength из пакета ASN на новую, которая будет подсчитывать длины тега как в DER, так и BER-кодировках:
package require asn#Переименовываем оригинальную процедуру подсчета длины rename ::asn::asnGetLength ::asn::asnGetLength.orig#Новая процедура подсчета длиныproc ::asn::asnGetLength {data_var length_var} {    upvar 1 $data_var data  $length_var length    asnGetByte data length    if {$length == 0x080} {#Поддержка BER-кодировкиset lendata [string length $data]set tvl 1set length 0set data1 $datawhile {$tvl != 0} {    ::asn::asnGetByte data1 peek_tag     ::asn::asnPeekByte data1 peek_tag1    if {$peek_tag == 0x00 && $peek_tag1 == 0x00} {incr tvl -1::asn::asnGetByte data1 tag incr length 2continue    }    if {$peek_tag1 == 0x80} {incr tvlif {$tvl > 0} {    incr length 2}::asn::asnGetByte data1 tag     } else {set l1 [string length $data1]::asn::asnGetLength data1 llset l2 [string length $data1]set l3 [expr $l1 - $l2]incr length $l3incr length $llincr length::asn::asnGetBytes data1 $ll strt    }}return    }    if {$length > 0x080} {        set len_length [expr {$length & 0x7f}]          if {[string length $data] < $len_length} {            return -code error \"length information invalid, not enough octets left"         }        asnGetBytes data $len_length lengthBytes        switch $len_length {            1 { binary scan $lengthBytes     cu length }            2 { binary scan $lengthBytes     Su length }            3 { binary scan \x00$lengthBytes Iu length }            4 { binary scan $lengthBytes     Iu length }            default {                                binary scan $lengthBytes H* hexstrscan $hexstr %llx length            }        }    }    return}

Что нам еще потребуется? Любая ASN-структура, особенно такая как сертификат X509.v3 содержит большое количество OID-ов, для которых могут существовать достаточно общепризнанные символьные обозначения. Значительная часть OID-ов, которые используются в сертификатах, присутствует в пакете pki. Мы его тоже будем использовать (package require pki). Естественно, что в этом пакете ничего не известно об OID-ах, которые используются в квалифицированных сертификатах и об OID-ах для российской криптографии. Их тоже целесообразно добавить в массив ::pki::oids:
set ::pki::oids(1.2.643.100.1)  "OGRN"set ::pki::oids(1.2.643.100.5)  "OGRNIP"set ::pki::oids(1.2.643.3.131.1.1) "INN"set ::pki::oids(1.2.643.100.3) "SNILS"#Для КПП ЕГАИСset ::pki::oids(1.2.840.113549.1.9.2) "UN"#set ::pki::oids(1.2.840.113549.1.9.2) "unstructuredName"#Алгоритмы подписиset ::pki::oids(1.2.643.2.2.3) "GOST R 34.10-2001 with GOST R 34.11-94"set ::pki::oids(1.2.643.2.2.19) "GOST R 34.10-2001"set ::pki::oids(1.2.643.7.1.1.1.1) "GOST R 34.10-2012-256"set ::pki::oids(1.2.643.7.1.1.1.2) "GOST R 34.10-2012-512"set ::pki::oids(1.2.643.7.1.1.3.2) "GOST R 34.10-2012-256 with GOSTR 34.11-2012-256"set ::pki::oids(1.2.643.7.1.1.3.3) "GOST R 34.10-2012-512 with GOSTR 34.11-2012-512"set ::pki::oids(1.2.643.100.113.1) "KC1 Class Sign Tool"set ::pki::oids(1.2.643.100.113.2) "KC2 Class Sign Tool"set ::pki::oids(2.5.4.42)  "givenName"

Для полноты не мешает также добавить символьное представление параметров подписи:
#Параметры подписи
#Параметры подписиset ::pki::oids((1.2.643.2.2.35.1)"id-GostR3410-2001-CryptoPro-A-ParamSet"set ::pki::oids(1.2.643.2.2.35.2)"id-GostR3410-2001-CryptoPro-B-ParamSet"set ::pki::oids(1.2.643.2.2.35.3)"id-GostR3410-2001-CryptoPro-C-ParamSet"set ::pki::oids(1.2.643.2.2.36.0)"id-GostR3410-2001-CryptoPro-XchA-ParamSet"set ::pki::oids(1.2.643.2.2.36.1)"id-GostR3410-2001-CryptoPro-XchB-ParamSet"set ::pki::oids(1.2.643.7.1.2.1.1.1)"id-tc26-gost-3410-2012-256-paramSetA"set ::pki::oids(1.2.643.7.1.2.1.1.2)"id-tc26-gost-3410-2012-256-paramSetB"set ::pki::oids(1.2.643.7.1.2.1.1.3)"id-tc26-gost-3410-2012-256-paramSetC"set ::pki::oids(1.2.643.7.1.2.1.1.4)"id-tc26-gost-3410-2012-256-paramSetD"set ::pki::oids(1.2.643.7.1.2.1.2.1)"id-tc26-gost-3410-2012-512-paramSetA"set ::pki::oids(1.2.643.7.1.2.1.2.2)"id-tc26-gost-3410-2012-512-paramSetB"set ::pki::oids(1.2.643.7.1.2.1.2.3)"id-tc26-gost-3410-2012-512-paramSetC"


Создание класса

Объявление класса в TclOO мало чем отличается от объявления класса в других языках. Класс в TclOO также содержит область данных, конструктор, область объектно-ориентированных методов и деструктор. При этом область данных, конструктор и деструктор могут опускаться. Напомним, что конструктор вызывается при создание объекта (экземпляра объекта) заданного класса, а деструктор при его уничтожении. Конструктор (в отличии от деструктора), также как и методы, может иметь параметры. В нашем случае параметром для конструктора выступает сертификат в DER или PEM кодировке.
Области данных может предшествовать область наследуемых классов (superclass). Её будем рассматривать ниже. Но, для написания универсального класса certificate, эта область будет нами задействована.
В TclOO можно узнать какие классы в данный момент доступны в программе. Для этих целей служит команда следующего вида:
info class instances oo::class

В последующем, мы будем задействовать для наследования класс pubkey. Поэтому в нашем определении класса certificate присутствует проверка наличия класса pubkey и, если он присутствует, он объявляется как наследуемый (superclass pubkey).
Итак, ниже представлен класс для сертификата пока что с одним методом parse_cert, который возвращает список элементов сертификата:
Объявление класса certificate
oo::class create certificate {#Список доступных классов    foreach cl  "[info class instances oo::class]" {if {$cl == "::pubkey" } {#Если класс pubkey есть, то наследуем его. Это будет использовано в примере 3    superclass pubkey    break}    }#Переменные класса#Доступны только в пределах класса#Переменная для хранения разобранного сертификата.     variable ret#Переменная для хранения расширений сертификата    variable extcert#Конструктор    constructor {cert} {array set parsed_cert [::pki::_parse_pem $cert "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"]set cert_seq $parsed_cert(data)array set ret [list]#Полный сертификат der в hexbinary scan $cert_seq H* ret(cert_full)  # Decode X.509 certificate, which is an ASN.1 sequence::asn::asnGetSequence cert_seq wholething::asn::asnGetSequence wholething cert#tbs - сертификатset ret(tbsCert) [::asn::asnSequence $cert]binary scan $ret(tbsCert) H* ret(tbsCert)::asn::asnPeekByte cert peek_tagif {$peek_tag != 0x02} {    # Version number is optional, if missing assumed to be value of 0    ::asn::asnGetContext cert - asn_version    ::asn::asnGetInteger asn_version ret(version)    incr ret(version)} else {    set ret(version) 1}::asn::asnGetBigInteger cert ret(serial_number)::asn::asnGetSequence cert data_signature_algo_seq::asn::asnGetObjectIdentifier data_signature_algo_seq ret(data_signature_algo)::asn::asnGetSequence cert issuer    set ret(issuer) $issuer::asn::asnGetSequence cert validity::asn::asnGetUTCTime validity ret(notBefore)::asn::asnGetUTCTime validity ret(notAfter)::asn::asnGetSequence cert subject    set ret(subject) $subject::asn::asnGetSequence cert pubkeyinfobinary scan $pubkeyinfo H* ret(pubkeyinfo_hex)::asn::asnGetSequence pubkeyinfo pubkey_algoid::asn::asnGetObjectIdentifier pubkey_algoid ret(pubkey_algo)::asn::asnGetBitString pubkeyinfo pubkeyset extensions_list [list]while {$cert != ""} {    ::asn::asnPeekByte cert peek_tag    switch -- [format {0x%02x} $peek_tag] {    "0x81" {    ::asn::asnGetContext cert - issuerUniqueID        }    "0x82" {    ::asn::asnGetContext cert - subjectUniqueID        }    "0xa1" {    ::asn::asnGetContext cert - issuerUniqID        }    "0xa2" {    ::asn::asnGetContext cert - subjectUniqID        }    "0xa3" {    ::asn::asnGetContext cert - extensions_ctx    ::asn::asnGetSequence extensions_ctx extensions#Убираем перевод oid в текстset ::pki::oids1 [array get ::pki::oids]array unset ::pki::oids     while {$extensions != ""} {            ::asn::asnGetSequence extensions extension            ::asn::asnGetObjectIdentifier extension ext_oid        ::asn::asnPeekByte extension peek_tag        if {$peek_tag == 0x1} {        ::asn::asnGetBoolean extension ext_critical            } else {        set ext_critical false            }        ::asn::asnGetOctetString extension ext_value_seq        set ext_oid [::pki::_oid_number_to_name $ext_oid]        set ext_value [list $ext_critical]        switch -- $ext_oid {                        id-ce-basicConstraints {                ::asn::asnGetSequence ext_value_seq ext_value_bin                if {$ext_value_bin != ""} {            ::asn::asnGetBoolean ext_value_bin allowCA                } else {            set allowCA "false"                }            if {$ext_value_bin != ""} {            ::asn::asnGetInteger ext_value_bin caDepth                } else {            set caDepth -1                }                lappend ext_value $allowCA $caDepth                        }                        default {                binary scan $ext_value_seq H* ext_value_seq_hex                lappend ext_value $ext_value_seq_hex                        }                    }        lappend extensions_list $ext_oid $ext_value    }#Возвращаем перевод oid-ов в текстarray set ::pki::oids $::pki::oids1        }    }}set ret(extensions) $extensions_listarray set extcert $extensions_list::asn::asnGetSequence wholething signature_algo_seq::asn::asnGetObjectIdentifier signature_algo_seq ret(signature_algo)::asn::asnGetBitString wholething ret(signature)set ret(serial_number) [::math::bignum::tostr $ret(serial_number)]set ret(signature) [binary format B* $ret(signature)]binary scan $ret(signature) H* ret(signature)#Инициируем класс pubkeyinfo при наследовании - superclassif {[llength [self next]]} {#Если есть наследуемый класс, то вызываем его конструкторnext $ret(pubkeyinfo_hex)}    }    method parse_cert {} {        return [array get ret]    }}


В области данных командой variable определяются данные/переменные объекта через, которые доступны во всех методах класса.
Метод method определяется точно так же, как процедура proc Tcl. Методы могут иметь произвольное количество параметров. Внутри метода можно определять свои данные командой
my variable <идентификатор переменной>
. Методы могут быть публичными (экспортируемыми) и приватными.
Экспортируемые методы методы видимы за пределами класса. По умолчанию экспортируются методы начинаются со строчной буквы. По умолчанию методы, чьи имена начинаются с прописной буквы считаются неэкспортируемыми (приватными) методами. Область видимости независимо от первого символа можно задать явно. Для указания того, что метод является публичным служит следующая команда:
export <идентификатор метода>
.
Для запрета экспорта метода используется следующая команда:
unexport <идентификатор метода>
.
Для вызова одного метода из другого метода внутри класса используется команда my:
my <идентификатор метода>
.
Для этой же цели можно использовать внутреннюю команда класса self, которая возвращает идентификатор текущего объекта:
[self] <идентификатор метода>

Ниже мы увидим всё это.
Для дальнейшей работы соберем весь рассмотренный код в файле classparsecert.tcl.
Содержимое файла classparsecert.tcl
package require asn#Переименовываем оригинальную процедуру подсчета длины rename ::asn::asnGetLength ::asn::asnGetLength.orig#Новая процедура подсчета длиныproc ::asn::asnGetLength {data_var length_var} {    upvar 1 $data_var data  $length_var length    asnGetByte data length    if {$length == 0x080} {#Поддержка BER-кодировкиset lendata [string length $data]set tvl 1set length 0set data1 $datawhile {$tvl != 0} {    ::asn::asnGetByte data1 peek_tag     ::asn::asnPeekByte data1 peek_tag1    if {$peek_tag == 0x00 && $peek_tag1 == 0x00} {incr tvl -1::asn::asnGetByte data1 tag incr length 2continue    }    if {$peek_tag1 == 0x80} {incr tvlif {$tvl > 0} {    incr length 2}::asn::asnGetByte data1 tag     } else {set l1 [string length $data1]::asn::asnGetLength data1 llset l2 [string length $data1]set l3 [expr $l1 - $l2]incr length $l3incr length $llincr length::asn::asnGetBytes data1 $ll strt    }}return    }    if {$length > 0x080} {        set len_length [expr {$length & 0x7f}]        if {[string length $data] < $len_length} {            return -code error \"length information invalid, not enough octets left"         }        asnGetBytes data $len_length lengthBytes        switch $len_length {            1 { binary scan $lengthBytes     cu length }            2 { binary scan $lengthBytes     Su length }            3 { binary scan \x00$lengthBytes Iu length }            4 { binary scan $lengthBytes     Iu length }            default {                                binary scan $lengthBytes H* hexstrscan $hexstr %llx length            }        }    }    return}package require pkiset ::pki::oids(1.2.643.100.1)  "OGRN"set ::pki::oids(1.2.643.100.5)  "OGRNIP"set ::pki::oids(1.2.643.3.131.1.1) "INN"set ::pki::oids(1.2.643.100.3) "SNILS"#Для КПП ЕГАИСset ::pki::oids(1.2.840.113549.1.9.2) "UN"#set ::pki::oids(1.2.840.113549.1.9.2) "unstructuredName"#Алгоритмы подписиset ::pki::oids(1.2.643.2.2.3) "GOST R 34.10-2001 with GOST R 34.11-94"set ::pki::oids(1.2.643.2.2.19) "GOST R 34.10-2001"set ::pki::oids(1.2.643.7.1.1.1.1) "GOST R 34.10-2012-256"set ::pki::oids(1.2.643.7.1.1.1.2) "GOST R 34.10-2012-512"set ::pki::oids(1.2.643.7.1.1.3.2) "GOST R 34.10-2012-256 with GOSTR 34.11-2012-256"set ::pki::oids(1.2.643.7.1.1.3.3) "GOST R 34.10-2012-512 with GOSTR 34.11-2012-512"set ::pki::oids(1.2.643.100.113.1) "KC1 Class Sign Tool"set ::pki::oids(1.2.643.100.113.2) "KC2 Class Sign Tool"set ::pki::oids(2.5.4.42)  "givenName"#Параметры подписиset ::pki::oids((1.2.643.2.2.35.1)"id-GostR3410-2001-CryptoPro-A-ParamSet"set ::pki::oids(1.2.643.2.2.35.2)"id-GostR3410-2001-CryptoPro-B-ParamSet"set ::pki::oids(1.2.643.2.2.35.3)"id-GostR3410-2001-CryptoPro-C-ParamSet"set ::pki::oids(1.2.643.2.2.36.0)"id-GostR3410-2001-CryptoPro-XchA-ParamSet"set ::pki::oids(1.2.643.2.2.36.1)"id-GostR3410-2001-CryptoPro-XchB-ParamSet"set ::pki::oids(1.2.643.7.1.2.1.1.1)"id-tc26-gost-3410-2012-256-paramSetA"set ::pki::oids(1.2.643.7.1.2.1.1.2)"id-tc26-gost-3410-2012-256-paramSetB"set ::pki::oids(1.2.643.7.1.2.1.1.3)"id-tc26-gost-3410-2012-256-paramSetC"set ::pki::oids(1.2.643.7.1.2.1.1.4)"id-tc26-gost-3410-2012-256-paramSetD"set ::pki::oids(1.2.643.7.1.2.1.2.1)"id-tc26-gost-3410-2012-512-paramSetA"set ::pki::oids(1.2.643.7.1.2.1.2.2)"id-tc26-gost-3410-2012-512-paramSetB"set ::pki::oids(1.2.643.7.1.2.1.2.3)"id-tc26-gost-3410-2012-512-paramSetC"#Класс certificateoo::class create certificate {#Наследуем класс pubkey#    superclass pubkey#Переменные класса#Доступны только в пределах класса#Переменная для хранения разобранного сертификата.     variable ret#Переменная для хранения расширений сертификатаvariable extcert#Конструктор    constructor {cert} {array set parsed_cert [::pki::_parse_pem $cert "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"]set cert_seq $parsed_cert(data)array set ret [list]#Полный сертификат der в hexbinary scan $cert_seq H* ret(cert_full)  # Decode X.509 certificate, which is an ASN.1 sequence::asn::asnGetSequence cert_seq wholething::asn::asnGetSequence wholething cert#tbs - сертификатset ret(tbsCert) [::asn::asnSequence $cert]binary scan $ret(tbsCert) H* ret(tbsCert)::asn::asnPeekByte cert peek_tagif {$peek_tag != 0x02} {    # Version number is optional, if missing assumed to be value of 0    ::asn::asnGetContext cert - asn_version    ::asn::asnGetInteger asn_version ret(version)    incr ret(version)} else {    set ret(version) 1}::asn::asnGetBigInteger cert ret(serial_number)::asn::asnGetSequence cert data_signature_algo_seq::asn::asnGetObjectIdentifier data_signature_algo_seq ret(data_signature_algo)::asn::asnGetSequence cert issuer    set ret(issuer) $issuer::asn::asnGetSequence cert validity::asn::asnGetUTCTime validity ret(notBefore)::asn::asnGetUTCTime validity ret(notAfter)::asn::asnGetSequence cert subject    set ret(subject) $subject::asn::asnGetSequence cert pubkeyinfobinary scan $pubkeyinfo H* ret(pubkeyinfo_hex)::asn::asnGetSequence pubkeyinfo pubkey_algoid::asn::asnGetObjectIdentifier pubkey_algoid ret(pubkey_algo)::asn::asnGetBitString pubkeyinfo pubkeyset extensions_list [list]while {$cert != ""} {    ::asn::asnPeekByte cert peek_tag    switch -- [format {0x%02x} $peek_tag] {    "0x81" {    ::asn::asnGetContext cert - issuerUniqueID        }    "0x82" {    ::asn::asnGetContext cert - subjectUniqueID        }    "0xa1" {    ::asn::asnGetContext cert - issuerUniqID        }    "0xa2" {    ::asn::asnGetContext cert - subjectUniqID        }    "0xa3" {    ::asn::asnGetContext cert - extensions_ctx    ::asn::asnGetSequence extensions_ctx extensions#Убираем перевод oid в текстset ::pki::oids1 [array get ::pki::oids]array unset ::pki::oids     while {$extensions != ""} {            ::asn::asnGetSequence extensions extension            ::asn::asnGetObjectIdentifier extension ext_oid        ::asn::asnPeekByte extension peek_tag        if {$peek_tag == 0x1} {        ::asn::asnGetBoolean extension ext_critical            } else {        set ext_critical false            }        ::asn::asnGetOctetString extension ext_value_seq        set ext_oid [::pki::_oid_number_to_name $ext_oid]        set ext_value [list $ext_critical]        switch -- $ext_oid {                        id-ce-basicConstraints {                ::asn::asnGetSequence ext_value_seq ext_value_bin                if {$ext_value_bin != ""} {            ::asn::asnGetBoolean ext_value_bin allowCA                } else {            set allowCA "false"                }            if {$ext_value_bin != ""} {            ::asn::asnGetInteger ext_value_bin caDepth                } else {            set caDepth -1                }                           lappend ext_value $allowCA $caDepth                        }                        default {                binary scan $ext_value_seq H* ext_value_seq_hex                lappend ext_value $ext_value_seq_hex                        }                    }        lappend extensions_list $ext_oid $ext_value    }#Возвращаем перевод oid-ов в текстarray set ::pki::oids $::pki::oids1        }    }}set ret(extensions) $extensions_listarray set extcert $extensions_list::asn::asnGetSequence wholething signature_algo_seq::asn::asnGetObjectIdentifier signature_algo_seq ret(signature_algo)::asn::asnGetBitString wholething ret(signature)set ret(serial_number) [::math::bignum::tostr $ret(serial_number)]set ret(signature) [binary format B* $ret(signature)]binary scan $ret(signature) H* ret(signature)#Инициируем класс pubkeyinfo при наследовании - superclass#next $ret(pubkeyinfo_hex)    }    method parse_cert {} {        return [array get ret]    }}


После того как был определен класс можно создавать конкретный объект (экземпляр объекта). Для этого может быть использована одна из следующих команд:
<имя класса> create <идентификатор экземпляра класса> [параметры для констуктура]

или
set <переменная для идентификатора экземпляра класса > <имя класса> new [параметры для констуктура]

В первом случае программист сам назначает идентификатор для создаваемого экземпляра объекта. Этот идентификатор фактически будет командой, через которую осуществляется доступ к объекту и его методам:
<идентификатор объекта>  <идентификатор метода> [<параметры>]

Во втором случае идентификатор создаваемого объекта назначается интерпретатором и возвращается как результат выполнения команды new для указанного класса. В этом случае идентификатор объекта будет браться из этой переменной.
Интересно сравнить с созданием объекта в Python. И что мы видим? Несущественную синтаксическую разницу.

Напишем небольшой пример example1.tcl использования этого класса:
#Загружаем описание классаsource ./classparsecert.tcl#Загружаем сертификатset file [lindex $argv 0]if {$argc != 1 || ![file exists $file]} {    puts "Usage: tclsh example1 <файл с сертификатом>"    exit}puts "Loading file: $file"set fd [open $file]chan configure $fd -translation binaryset data [read $fd]close $fdif {[catch {certificate create cert1 $data} er1]} {puts "Файл не содержит СЕРТИФИКАТ"exit}array set cert_parse [cert1 parse_cert]#parray cert_parseputs "Распарсенный сертификат"foreach ind [array names cert_parse] {    puts "\tcert_parse($ind)"}

Выполним пример:
$tclsh ./example1.tclLoading file: minenergo.cerРаспарсенный сертификат        cert_parse(subject)        cert_parse(pubkeyinfo_hex)        cert_parse(extensions)        cert_parse(issuer)        cert_parse(data_signature_algo)        cert_parse(cert_full)        cert_parse(serial_number)        cert_parse(signature)        cert_parse(pubkey_algo)        cert_parse(notAfter)        cert_parse(signature_algo)        cert_parse(notBefore)        cert_parse(version)        cert_parse(tbsCert)$ 

О конструкторе Lego

У читателя, наверное, так и хочет сорваться с языка вопрос:- А причем здесь конструктор Lego? А вот при чем. Если, скажем в C++ класс объекта должен быть определен сразу, то в TclOO класс может собираться постепенно как модель в конструкторе. Более того одни части класса могут удаляться и заменяться другими и т.д. Более того, такой метод конструирования класса распространяется и на объекты, да на конкретные объекты.
Предположим, что необходимо вывести информацию и о владельце и об издателе сертификата. Для этого нам потребуется два публичных issur и subject и один приватный метод parse_dn для разбора отличительного имени (DN) издателя и владельца. Традиционно нам пришлось бы переписать класс certificate, добавив в него указанные методы. В TclOO можно поступить по другому. Можно просто в нужном месте программы выполнить оператор добавления в существующий класс новых членов.
Для добавления в класс новых членов в область данных используется команда (модуль конструктора) вида:
oo::define <идентификатор класса>  {#Область данных классаvariable <идентификатор переменной>  [<идентификатор переменной>][ variable <идентификатор переменной> ]}

Может быть несколькокоманд variable, каждая из которых определяет один или несколько элементов данных.
Аналогично добавляются методы:
oo::define <идентификатор класса>  {#методыmethod <идентификатор метода 1>  {<параметры>} {<тело метода>}[method <идентификатор метода N>  {<параметры>} {<тело метода>}]}

Любой метод можно удалить в любое время с помощьюкоманды deletemethod внутри сценария определения класса. Эта команды будет рассмотрена ниже при рассмотрении примера с отзывом сертификата.
Про видимость методов (публичные, приватные методы) мы уже говорили выше.
Отметим, что первоначально класс может создаваться абсолютно пустым:
oo::class create <Идентификатор класса>

с последующим наполнением его через команду:
oo::define <идентификатор класса>  {}

Итак, добавляем новые методы в класс Certificate:
oo::define certificate {    method issuer {} {return [ my parse_dn $ret(issuer)]    }    method subject {} {return [ my parse_dn $ret(subject)]    }    method parse_dn {asnblock} {set lret {}      while {[string length $asnblock]} {        asn::asnGetSet asnblock AttributeValueAssertion        asn::asnGetSequence AttributeValueAssertion valblock        asn::asnGetObjectIdentifier valblock oidset name [::pki::_oid_number_to_name $oid]::asn::asnGetString valblock  valuelappend lret [string toupper $name]lappend lret $value      }return $lret    }    unexport parse_dn}

Теперь дополним наш пример кодом для распечатки информации об издателе и владельце:
...puts "Сведения о владельце:"foreach {oid value} [cert1 subject] {    puts "\t$oid=$value"}puts "Сведения об издателе:"foreach {oid value} [cert1 issuer] {    puts "\t$oid=$value"}...

Таким образом мы получим второй пример.
Тестовый пример example2.tcl
source ./classparsecert.tcl
#Загружаем сертификат
set file [lindex $argv 0]
if {$argc != 1 || ![file exists $file]} {
puts Usage: tclsh example1 <файл с сертификатом>
exit
}
puts Loading file: $file
set fd [open $file]
chan configure $fd -translation binary
set data [read $fd]
close $fd
if {[catch {certificate create cert1 $data} er1]} {
puts Файл не содержит СЕРТИФИКАТ
exit
}
array set cert_parse [cert1 parse_cert]
#parray cert_parse
puts Распарсенный сертификат
foreach ind [array names cert_parse] {
puts "\tcert_parse($ind)"
}
#Добавляем новые методы
oo::define certificate {
method issuer {} {
return [ my parse_dn $ret(issuer)]
}
method subject {} {
return [ my parse_dn $ret(subject)]
}
method parse_dn {asnblock} {
set lret {}
while {[string length $asnblock]} {
asn::asnGetSet asnblock AttributeValueAssertion
asn::asnGetSequence AttributeValueAssertion valblock
asn::asnGetObjectIdentifier valblock oid
set name [::pki::_oid_number_to_name $oid]
::asn::asnGetString valblock value
lappend lret [string toupper $name]
lappend lret $value
}
return $lret
}
#Приватный метод
unexport parse_dn
}
#Применяем методы
puts Сведения о владельце:
foreach {oid value} [cert1 subject] {
puts "\t$oid=$value"
}
puts Сведения об издателе:
foreach {oid value} [cert1 issuer] {
puts "\t$oid=$value"
}

Попробуем выполнить этот пример:
$tclsh example2.tcl minenergo.cer  Loading file: minenergo.cerРаспарсенный сертификат        cert_parse(subject)        . . .         cert_parse(tbsCert)Сведения о владельце:        EMAIL=xxxxxxxxxxx        INN=xxxxxxxxxxx        OGRN=............. . .        ST=77 г. Москва        C=RU        CN=Мин РоссииСведения об издателе: . . .        C=RU        ST=77 Москва        L=Москва        CN=Тестовый удостоверяющий центр$

О наследовании


Определяющей характеристикой объектно-ориентированных систем является поддержка наследования.Наследование относится к способностипроизводногокласса (также называемогоподклассом) наследовать область данных и методы из наследуемого класса (из супер класса).
При разборе сертификата, естественно, требуется получить и полную информацию о его публичном ключе. Предположим у нас уже есть класс pubkey, который на основе asn-структуры pubkeyinfo выдает полную информацию о публичном ключе, включая RSA, EC, GOST:
oo::class create pubkey {#Внутренняя переменная класса для хранения asn-структуры pubkeyinfo    variable infopk    constructor {pubkinfo} {set infopk $pubkinfo    }    method infopubkey {} {array set retpk [list]set pubkeyinfo [binary format H* $infopk]::asn::asnGetSequence pubkeyinfo pubkey_algoid::asn::asnGetObjectIdentifier pubkey_algoid retpk(pubkey_algo)::asn::asnGetBitString pubkeyinfo pubkeyset pubkey [binary format B* $pubkey]binary scan $pubkey H* retpk(pubkey)set retpk(pkcs11id_hex) [::sha1::sha1  $pubkey]if {"1 2 643" == [string range $retpk(pubkey_algo) 0 6]} {#ГОСТ-ключ        set retpk(type) gost    ::asn::asnGetSequence pubkey_algoid pubalgost  #OID - параметра    ::asn::asnGetObjectIdentifier pubalgost retpk(paramkey)    set retpk(paramkey) [::pki::_oid_number_to_name $retpk(paramkey)]    if {$pubalgost != ""} {  #OID - Функция хэша::asn::asnGetObjectIdentifier pubalgost retpk(hashkey)    } else {set retpk(hashkey) ""    }} elseif {"1 2 840 10045 2 1" == $retpk(pubkey_algo) } {#EC-key        set retpk(type) ec    ::asn::asnGetObjectIdentifier pubkey_algoid retpk(pubkey_algo_par)} elseif {"1 2 840 113549 1 1 1" == $retpk(pubkey_algo) }  {#RSA- key        set retpk(type) rsa    binary scan $pubkey H* retpk(pubkey)    ::asn::asnGetSequence pubkey pubkey_parts    ::asn::asnGetBigInteger pubkey_parts retpk(n)    ::asn::asnGetBigInteger pubkey_parts retpk(e)    set retpk(n) [::math::bignum::tostr $retpk(n)]    set retpk(e) [::math::bignum::tostr $retpk(e)]    set retpk(l) [expr {int([::pki::_bits $retpk(n)] / 8.0000 + 0.5) * 8}]} else {        set retpk(type) unknown}return [array get retpk]    }}

Сохраним этот класс в файле classpubkeyinfo.tcl.
Для того, чтобы наследовать метод infopubkey для объектов класса certificate, в определение класса certificate добавляется определение суперкласса, методы которого будут наследоваться:
superclass pubkey

Также добавляем в конструктор класса certificate вызов конструктора класса pubkey с передачей ему в качестве параметра asn-структуры pubkeyinfo:
next $ret(pubkeyinfo_hex)

Команда next вызывает одноименный метод (в данном случае constructor) из суперкласса, т.е. из класса pubkey. Конструктор в классе pubkey просто сохранит в переменной класса infopk asn-структуру публичного ключа. Этот код с соответствующей проверкой наличия в теле программы класса pubkey и его конструктора был включен при определении класса certificate.
Полный техт example3.tcl здесь.
source ./classpubkeyinfo.tclsource ./classparsecert_and_pk.tclset file [lindex $argv 0]if {$argc != 1 || ![file exists $file]} {    puts "Usage: tclsh example1 <файл с сертификатом>"    exit}puts "Loading file: $file"set fd [open $file]chan configure $fd -translation binaryset data [read $fd]close $fdif {[catch {certificate create cert1 $data} er1]} {puts "Файл не содержит сертификата"exit}array set cert_parse [cert1 parse_cert]puts "Распарсенный сертификат"foreach ind [array names cert_parse] {    puts "\tcert_parse($ind)"}#Добавляем новые методыoo::define certificate {    method issuer {} {return [ my parse_dn $ret(issuer)]    }    method subject {} {return [ my parse_dn $ret(subject)]    }    method parse_dn {asnblock} {set lret {}      while {[string length $asnblock]} {        asn::asnGetSet asnblock AttributeValueAssertion        asn::asnGetSequence AttributeValueAssertion valblock        asn::asnGetObjectIdentifier valblock oidset name [::pki::_oid_number_to_name $oid]::asn::asnGetString valblock  valuelappend lret [string toupper $name]lappend lret $value      }return $lret    }    unexport parse_dn}puts "Сведения о владельце:"foreach {oid value} [cert1 subject] {    puts "\t$oid=$value"}puts "Сведения об издателе:"foreach {oid value} [cert1 issuer] {    puts "\t$oid=$value"}puts "INFO PUB KEY"foreach {oid value} [cert1 infopubkey] {    puts "\t$oid=$value"}#Создаем объект pubkeyputs "КЛАСС INFO PUB KEY"if {[catch {pubkey create pk1 $cert_parse(pubkeyinfo_hex)} er1]} {puts "НЕ PUBKEYINFO"exit}foreach {oid value} [pk1 infopubkey] {    puts "\t$oid=$value"}puts "Публичные методы класса certificate"puts "\t[info class methods certificate]"puts "Все методы класса certificate, включая приватные"puts "\t[info class methods certificate -private]"

Выполним пример example3.tcl:
$ tclsh example3.tcl minenergo.cer Loading file: minenergo.cerРаспарсенный сертификат        cert_parse(subject)        . . .        cert_parse(tbsCert)Сведения о владельце:        . . .        ST=77 г. Москва        C=RU        CN=Мин РоссииСведения об издателе:        C=RU        ST=77 Москва        . . .        CN=Тестовый удостоверяющий центрINFO PUB KEY        pkcs11id_hex=842205ac57465fd853a158544f1ea1ba1de58569        pubkey=04401dc81447918c7694a74dbe6bb4e4c10a63ca21d6b95a41ae20837deda4700f2404a0c1141d9b535b95707bb751791eb684bd09ce8f0c98d912dea947e4b8bbdb        hashkey=1 2 643 7 1 1 2 2        paramkey=id-GostR3410-2001-CryptoPro-XchA-ParamSet        type=gost        pubkey_algo=1 2 643 7 1 1 1 1Публичные методы класса certificate        subject parse_cert issuerВсе методы класса certificate, включая приватные        parse_dn subject parse_cert issuer . . .$

Отметим также, что TclOO допускает и множественное наследование, но это тема для отдельной публикации.

Информационная поддержка


В результатах выполнения примера мы видим перечень методов доступных в классе certificate.
Для получения списка методов используется следующая команда:
info class methods <идентификатор класса> [-private]

Если флаг "-private" не задан, то выдается список публичных методов. В противном случае, выдается весь перечень методов, включая приватные.
Проверить принадлежность объекта тому или иному классу можно командой:
info object clacc <идентификатор объекта>
.
В нашем примере объект cert1 принадлежит двум классам: certuficate и pubkey.
Если требуется узнать какие классы наследует тот или иной класс, достаточно выполнить коиманду:
info class superclasses <идентификатор класса>

А если требуется получить информацию о том, какими классами наследуется тот или иной класс, то достаточно выполнить следующую команду:
info class subclasses <идентификатор класса>
.
В нашем примере мы имеем:
$ . . .Публичные методы класса certificate        subject parse_cert issuerВсе методы класса certificate, включая приватные        parse_dn subject parse_cert issuerПринадлежность объекта cert1 классу certificate        1Принадлежность объекта cert1 классу pubkey        1Супер классы класса certificate        ::pubkeyСупер классы класса pubkey        ::oo::objectПодклассы класса certificateПодклассы класса pubkey        ::certificate$ 

Подмешивание (mix in) методов в класс


Для расширения возможность класса, прежде всего с точки зрения его функциональности, помимо наследования можно использовать так называемый метод подмешивания (mix in).
Если мы хотим распечатать сертификат в текстовом виде, то нам потребуется разбор asn-структур расширений сертификата. Это и начначение ключа сертификата, это свойства квалифицированного сертификата и многое другое. Оформим разбор расширений сертификата в отдельный класс parseexts, в котором отсутствует констуктор и деструктор:
#Класс разбора расширений сертификатаoo::class create parseexts {#Переменные с распарсенным сертификатом и его расширениями#Область данных берется их класса, к которому будем плдмешивать    variable ret    variable extcert#Подмешиваемые методы    method issuerSignTool {} {set member {"Наименование СКЗИ УЦ" "Наименование УЦ" "Сертификат СКЗИ УЦ" "Сертификат УЦ"}#Проверка наличия расширенияif {![info exists extcert(1.2.643.100.112)]} {    return [list ]}set rr [list]set iss [binary format H* [lindex $extcert(1.2.643.100.112) 1]]::asn::asnGetSequence iss iss_polfor {set i 0} {[string length $iss_pol] > 0}  {incr i} {    ::asn::asnGetUTF8String iss_pol retist    lappend rr [lindex $member $i]    lappend rr $retist}return $rr  }    method subjectSignTool {} {#Проверка наличия расширенияif {![info exists extcert(1.2.643.100.111)]} {    return [list ]}set iss [binary format H* [lindex $extcert(1.2.643.100.111) 1]]lappend rr "User CKZI"::asn::asnGetUTF8String iss retsstlappend rr $retsstreturn $rr    }    method keyUsage {} {    #keyUsageset critcert "No"array set ist [list]#Проверка наличия расширенияif {![info exists extcert(2.5.29.15)]} {    return [array get ist]}    set ku_hex [lindex $extcert(2.5.29.15) 1]if {[lindex $extcert(2.5.29.15) 0] == 1} {    set critcert "Yes"}set ku_options {"Digital signature" "Non-Repudiation" "Key encipherment" "Data encipherment" "Key agreement" "Certificate signature" "CRL signature" "Encipher Only" "Decipher Only" "Revocation list signature"}set ku [binary format H* $ku_hex]::asn::asnGetBitString ku ku_binset retku {}for {set i 0} {$i < [string length $ku_bin]}  {incr i} {    if {[string range $ku_bin $i $i] > 0 } {    lappend retku [lindex $ku_options $i]    }}array set aku [list]set aku(keyUsage) $retkuset aku(critcert) $critcertreturn [array get aku]    }}

Область данных подмешиваемого класса должна включать те данные, из класса к которому будет подмешиваться данный класс, которые будут использоваться в его методах.
Для подмещивания используется команда mixin:
mixin <идентификатор подмешиваемого класса> 

Для нашего примера это будет выглядеть следующим образом:
oo::define certificate {mixin parseexts}

Полный пример использования подмешивания example4.tcl находится здесь.

source ./classpubkeyinfo.tcl
source ./classparsecert.tcl
set file [lindex $argv 0]
if {$argc != 1 || ![file exists $file]} {
puts Usage: tclsh example1 <файл с сертификатом>
exit
}
puts Loading file: $file
set fd [open $file]
chan configure $fd -translation binary
set data [read $fd]
close $fd
if {[catch {certificate create cert1 $data} er1]} {
puts Файл не содержит сертификата
exit
}
array set cert_parse [cert1 parse_cert]
if {0} {
puts Распарсенный сертификат
foreach ind [array names cert_parse] {
puts "\tcert_parse($ind)"
}
}
#Добавляем новые методы
oo::define certificate {
method issuer {} {
return [ my parse_dn $ret(issuer)]
}
method subject {} {
return [ my parse_dn $ret(subject)]
}
method parse_dn {asnblock} {
set lret {}
while {[string length $asnblock]} {
asn::asnGetSet asnblock AttributeValueAssertion
asn::asnGetSequence AttributeValueAssertion valblock
asn::asnGetObjectIdentifier valblock oid
set name [::pki::_oid_number_to_name $oid]
::asn::asnGetString valblock value
lappend lret [string toupper $name]
lappend lret $value
}
return $lret
}
unexport parse_dn
}
puts Сведения о владельце:
foreach {oid value} [cert1 subject] {
puts "\t$oid=$value"
}
puts Сведения об издателе:
foreach {oid value} [cert1 issuer] {
puts "\t$oid=$value"
}
puts INFO PUB KEY
foreach {oid value} [cert1 infopubkey] {
puts "\t$oid=$value"
}
#Класс разбора расширений сертификата
oo::class create parseexts {
#Переменная с распарсенным сертификатом
variable ret
variable extcert
method issuerSignTool {} {
set member {Наименование СКЗИ УЦ Наименование УЦ Сертификат СКЗИ УЦ Сертификат УЦ}
#Проверка наличия расширения
if {![info exists extcert(1.2.643.100.112)]} {
return [list ]
}
set rr [list]
set iss [binary format H* [lindex $extcert(1.2.643.100.112) 1]]
::asn::asnGetSequence iss iss_pol
for {set i 0} {[string length $iss_pol] > 0} {incr i} {
::asn::asnGetUTF8String iss_pol retist
lappend rr [lindex $member $i]
lappend rr $retist
}
# unset extcert(1.2.643.100.112)
return $rr
}
method subjectSignTool {} {
#Проверка наличия расширения
if {![info exists extcert(1.2.643.100.111)]} {
return [list ]
}
set iss [binary format H* [lindex $extcert(1.2.643.100.111) 1]]
lappend rr User CKZI
::asn::asnGetUTF8String iss retsst
lappend rr $retsst
# unset extcert(1.2.643.100.111)
return $rr
}
method keyUsage {} {
#keyUsage
set critcert No
array set ist [list]
#Проверка наличия расширения
if {![info exists extcert(2.5.29.15)]} {
return [array get ist]
}
set ku_hex [lindex $extcert(2.5.29.15) 1]
if {[lindex $extcert(2.5.29.15) 0] == 1} {
set critcert Yes
}
set ku_options {Digital signature Non-Repudiation Key encipherment Data encipherment Key agreement Certificate signature CRL signature Encipher Only Decipher Only Revocation list signature}
set ku [binary format H* $ku_hex]
::asn::asnGetBitString ku ku_bin
set retku {}
for {set i 0} {$i < [string length $ku_bin]} {incr i} {
if {[string range $ku_bin $i $i] > 0 } {
lappend retku [lindex $ku_options $i]
}
}
array set aku [list]
set aku(keyUsage) $retku
set aku(critcert) $critcert
return [array get aku]
}
}
oo::define certificate {
mixin parseexts
}
puts keyUsage
foreach {oid value} [cert1 keyUsage] {
puts "\t$oid=$value"
}
puts issuerSignTool
foreach {oid value} [cert1 issuerSignTool] {
puts "\t$oid=$value"
}
puts subjectSignTool
foreach {oid value} [cert1 subjectSignTool] {
puts "\t$oid=$value"
}
puts Публичные методы класса certificate
puts "\t[info class methods certificate]"
puts Все методы класса certificate, включая приватные
puts "\t[info class methods certificate -private]"
puts Принадлежность объекта cert1 классу certificate
puts "\t[info object class cert1 certificate]"
puts Принадлежность объекта cert1 классу pubkey
puts "\t[info object class cert1 pubkey]"
puts Супер классы класса certificate
puts "\t[info class superclasses certificate]"
puts Супер классы класса pubkey
puts "\t[info class superclasses pubkey]"
puts Подклассы класса certificate
puts "\t[info class subclasses certificate]"
puts Подклассы класса pubkey
puts "\t[info class subclasses pubkey]"
puts Mixin-ы класса certificate
puts "\t[info class mixins certificate]"

Результат выполнения примера:
$tclsh example4.tcl cert.cer. . .Сведения об издателе:. . .        C=RU        ST=77 Москва        L=Москва. . .        CN=Тестовый удостоверяющий центрINFO PUB KEY        pkcs11id_hex=842205ac57465fd853a158544f1ea1ba1de58569        pubkey=04401dc81447918c7694a74dbe6bb4e4c10a63ca21d6b95a41ae20837deda4700f2404a0c1141d9b535b95707bb751791eb684bd09ce8f0c98d912dea947e4b8bbdb        hashkey=1 2 643 7 1 1 2 2        paramkey=id-GostR3410-2001-CryptoPro-XchA-ParamSet        type=gost        pubkey_algo=1 2 643 7 1 1 1 1keyUsage        critcert=Yes        keyUsage={Digital signature} Non-Repudiation {Key encipherment} {Data encipherment}issuerSignTool        Наименование СКЗИ УЦ="CSP"         Наименование УЦ="Удостоверяющий центр" версии         Сертификат СКЗИ УЦ=Сертификат соответствия         Сертификат УЦ=Сертификат соответствия  subjectSignTool        User CKZI=CSP Публичные методы класса certificate        subject parse_cert issuerВсе методы класса certificate, включая приватные        parse_dn subject parse_cert issuerПринадлежность объекта cert1 классу certificate        1Принадлежность объекта cert1 классу pubkey        1Супер классы класса certificate        ::pubkeyСупер классы класса pubkey        ::oo::objectПодклассы класса certificateПодклассы класса pubkey        ::certificateMixin-ы класса certificate        ::parseexts

Добавление/переопределение методов у объектов


В принципе этого материала достаточно, чтобы начать использовать ООП в Tcl. Но мы упомянули и то, что в TcllOO можно динамически конструировать не только сам класс, то и экземпляры класса, т.е. объекты. На одной из таких возможностей хотелось бы остановится.
Для этого добавим в класс certificate еще один метод для подписания этим сертификатом некоторого документа:
#Метод для Подписания документаoo::define certificate {method signDoc {doc} {set sign "Здесь должна находиться подпись документа  $doc"#Счетчик подписанных документовmy variable signedDoc#Количество подписанных документовincr signedDocreturn [list $signedDoc $sign]}}

При вызове этого метода должно происходить подписание документа и увеличение счетчика подписанных документов на единицу. В качестве результата работы этого метода возвращается общее число подписанных на данный момент документов и сама подпись:
. . . set doc "Подпись1"puts "Подписание документа $doc"foreach {count sign} [cert1 signDoc $doc] {    puts "\tПодписано документов на данный момент=$count"    puts "\tПодпись документа=\"$sign\""}. . .

Результат будет выглядеть так:
. . .Подписание документа Подпись1        Подписано документов на данный момент=1        Подпись документа="Здесь должна находиться подпись документа  Подпись1". . .

Сам алгорит подписи здесь не рассматривается, но его можно найти в утилите cryptoarmpkcs:

image

А теперь представим, что владелец сертификата убывает в отпуск. Он знает, что в отпуске он будет отдыхать и не в коем случае не будет работать с документами и тем более что-либо подписывать. Он хочет отозвать сертификат, а когда вернется восстановить его действие. Для этих целей служит следующая функция:
#Процедура отзыва сертификатаproc revoke {cert_obj} {    oo::objdefine $cert_obj {#Переопределяем метод подписи для конкретного объекта        method signDoc {args} {#Переменная accessCert хранит число несанкционированных попыток подписания            my variable accessCert             set sign "Сертификат временно отозван. Не пытайтесь им подписывать!"#Число попыток несанкционированного использования возрастает на 1            incr accessCert            return [list $accessCert $sign]        }        method unrevoke {} {            my variable accessCert#Вызов метод  unrevoke удалит метод подписи для конкретного объекта,#восстанавливая тем самым действие  метода signDoc из класса и #удалит сам метод unrevoke            oo::objdefine [self] { deletemethod signDoc unrevoke }            if {![info exist accessCert]} {                return 0            }            return $accessCert        }    }}

Вызов этой функции определяет новый функционал методв signDoc для конкретного объекта. Для остальных объектов, как существующих и так и новых, сохраняется действие метода, определенного для класса. Также определяется новый метод unrevoke, вызов которого сотрудником по возвращению из отпуска приведет к восстановлению метода signDoc из класса certificate, путем удаления метода signDoc для объекта, а также удалит и сам метод unrevoke.
Полный текст примера example5.tcl находится здесь
source ./classpubkeyinfo.tclsource ./classparsecert.tcl#Примерset file [lindex $argv 0]if {$argc != 1 || ![file exists $file]} {    puts "File $file not exist"    puts "Usage: tclsh example1 <файл с сертификатом>"    exit}puts "Loading file: $file"set fd [open $file]chan configure $fd -translation binaryset data [read $fd]close $fdif {$data == "" } {    puts "Bad file with certificate=$file"    usage 1    exit}if {[catch {certificate create cert1 $data} er1]} {puts "НЕ СЕРТИФИКАТ"exit}array set cert_parse [cert1 parse_cert]#parray cert_parseif {0} {puts "Распарсенный сертификат"foreach ind [array names cert_parse] {    puts "\tcert_parse($ind)"}}#Добавляем новые методыoo::define certificate {    method issuer {} {return [ my parse_dn $ret(issuer)]    }    method subject {} {return [ my parse_dn $ret(subject)]    }    method parse_dn {asnblock} {set lret {}      while {[string length $asnblock]} {        asn::asnGetSet asnblock AttributeValueAssertion        asn::asnGetSequence AttributeValueAssertion valblock        asn::asnGetObjectIdentifier valblock oidset name [::pki::_oid_number_to_name $oid]::asn::asnGetString valblock  valuelappend lret [string toupper $name]lappend lret $value      }return $lret    }    unexport parse_dn}puts "Сведения о владельце:"foreach {oid value} [cert1 subject] {    puts "\t$oid=$value"}puts "Сведения об издателе:"foreach {oid value} [cert1 issuer] {    puts "\t$oid=$value"}puts "INFO PUB KEY"foreach {oid value} [cert1 infopubkey] {    puts "\t$oid=$value"}#Метод для Подписания документаoo::define certificate {method signDoc {doc} {set sign "Здесь должна находиться подпись документа  $doc"#Счетчик подписанных документовmy variable signedDoc#Количество подписанных документовincr signedDocreturn [list $signedDoc $sign]}}set doc "Подпись1"puts "Подписание документа $doc"foreach {count sign} [cert1 signDoc $doc] {    puts "\tПодписано документов на данный момент=$count"    puts "\tПодпись документа=\"$sign\""}set doc "Подпись2"puts "Подписание документа $doc"foreach {count sign} [cert1 signDoc $doc] {    puts "\tПодписано документов на данный момент=$count"    puts "\tПодпись документа=\"$sign\""}#Процедура отзыва сертификатаproc revoke {cert_obj} {    oo::objdefine $cert_obj {#Переопределяем метод подписи для конкретного объекта        method signDoc {args} {#Переменная accessCert хранит число несанкционированных попыток подписания            my variable accessCert             set sign "Сертификат временно отозван. Не пытайтесь им подписывать!"#Число попыток несанкционированного использования возрастает на 1            incr accessCert            return [list $accessCert $sign]        }        method unrevoke {} {            my variable accessCert#Вызов метод  unrevoke удалит метод подписи для конкретного объекта,#восстанавливая тем самым действие  метода signDoc из класса и #удалит сам метод unrevoke            oo::objdefine [self] { deletemethod signDoc unrevoke }            if {![info exist accessCert]} {                return 0            }            return $accessCert        }    }}#Клонируем объектoo::copy cert1 cert11#Отзыв сертификатаputs "Отзыв сертификата"revoke cert1foreach doc "Подпись3 подпись4" {    puts "Попытка подписать документ $doc"    foreach {count sign} [cert1 signDoc $doc] {puts "\tПопыток несанкционированного доступа=$count"puts "\tПодпись документа=\"$sign\""    }}#Для клонированного объекта отзыв не действуетforeach doc "Подпись3к подпись4к" {    puts "Попытка подписать документ $doc клонированным объектом"    foreach {count sign} [cert11 signDoc $doc] {    puts "\tПодписано документов на данный момент=$count"    puts "\tПодпись документа=\"$sign\""    }}#Восстанавливаем действие сертификатаforeach {count info} [cert1 unrevoke] {    puts "Действие сертификата восстанвлено"    puts "\tЗа время его отзыва было $count попытки несанкционированного досьупа"}foreach doc "\"Подпись после восстановления\"" {    puts "Попытка подписать документ $doc"    foreach {count sign} [cert1 signDoc $doc] {puts "\tПодписано документов на данный момент=$count"puts "\tПодпись документа=\"$sign\""    }}

Ниже приведен фрагмент выполнения примера example5.tcl:
. . . Подписание документа Подпись1        Подписано документов на данный момент=1        Подпись документа="Здесь должна находиться подпись документа  Подпись1"Подписание документа Подпись2        Подписано документов на данный момент=2        Подпись документа="Здесь должна находиться подпись документа  Подпись2"Отзыв сертификатаПопытка подписать документ Подпись3        Попыток несанкционированного доступа=1        Подпись документа="Сертификат временно отозван. Не пытайтесь им подписывать!"Попытка подписать документ подпись4        Попыток несанкционированного доступа=2        Подпись документа="Сертификат временно отозван. Не пытайтесь им подписывать!"Действие сертификата восстанвлено        За время его отзыва было 2 попытки несанкционированного досьупаПопытка подписать документ Подпись после восстановления        Подписано документов на данный момент=3        Подпись документа="Здесь должна находиться подпись документа  Подпись после восстановления". . .

Упомянем еще один оператор. Это оператор клонирования объекта:
oo::copy <идентификатор исходного объекта> <идентификатор клона>
Говорить и писать об ООП на TclOO можно долго и долго.
Еще интересней его исследовать.
Подробнее..

Поддержка токенов PKCS11 с ГОСТ-криптографией в Python. Часть I

20.02.2021 18:04:03 | Автор: admin
imageПоддержка криптографических токенов PKCS#11 с российской криптографией в скриптовых языках (Python, Tcl) давно находится в моём поле зрения. Это, прежде всего, пакет TclPKCS11 и реализованная на его базе кроссплатформенная утилита cryptoarmpkcs. Утилита cryptoarmpkcs написана на tcl/tk и функционирует на различных платформах, включая Android. Пакет TclPKCS11 версии 1.0.0 заточен на работу именно с токенами, поддерживающими ГОСТ Р 34.11-2012 и ГОСТ Р 34.10-2012. Он позволяет генерировать ключевые пары по ГОСТ Р 34.10-2012 с длиной закрытого ключа 256 и 512 бит, формировать и проверять электронную подпись. Все это можно наглядно видеть в утилите cryptoarmpkcs, в которой в качестве криптодвижка используется именно этот пакет:

image

Первым желанием было портировать этот модуль в среду Python. Но прежде чем это сделать, я посмотрел, что уже есть для работы с криптографическим токенами PKCS#11 в Python.
Вне конкуренции, на мой взгляд, здесь проект PyKCS11. Изучив его внимательно, я понял, что не составит труда добавить в него поддержку новой российской криптографии: ГОСТ Р 34.10-2012 (электронная подпись), ГОСТ Р 34.11-2012 (хэширование), ГОСТ Р 34.12-2015 и ГОСТ Р 34.13-2015 (алгоритмы шифрования Кузнечик и Магма). Я написал письмо авторам с предложением добавить российские алгоритмы, предлагая свою помощь. К сожалению, ответ меня несколько обескуражил:



И тогда я решил вернуться к этой теме немного позже, а сейчас всё внимание сосредоточил на портировании проект TckPKCS11-1.0.1 в Python. Почему всё же проект TclPKCS11? Да всё очень просто. Основная задача, которую необходимо решить на Python, связана с электронной подписью по ГОСТ Р 34.10-2012 и использование шифрования на данном этапе не предполагается. В этом контексте проект TclPKCS11 абсолютно подходит. В нём реализована поддержка следующих криптографических функций:
генерация ключевых пар по ГОСТ Р 34.10-2012 (512 и 1024 бита для открытого ключа), и даже по ГОСТ Р 34.10-2001;
подсчет хэша по ГОСТ Р 34.10-2012 (256 и 512 бит), а также по ГОСТ Р 34.11-94 и SHA1;
подписание и проверка подписи.
Из общих функций реализованы:
управление токенами (инициализация токена, установка и смена PIN-кодов);
получения списка слотов и информации о них;
импорт сертификатов и ключей (только для ГОСТ-криптографии):
установка меток для объектов (сертификаты, ключи);
и другие.
Самое главное то, что использование этих механизмов намного проще, чем использование стандартного интерфейса PKCS#11, а следовательно и проще использования пакета PyKCS11. Всё это будет видно на примерах.

I. Портирование кода модуля tclpkcs11 в модуль pyp11 для Python


Портирование заключается в адаптации кода модуля tclpkcs11 к требованиям со стороны Python. Все изменения в проекте будут касаться только модуля tclpkcs11.c. Поэтому, первое, что мы сделаем, скопируем модуль tclpkcs11.c в файл pythonpkcs11.c и в дальнейшем будем работать именно с ним. Модуль для Python назовем pyp11. Использовать для его создания будем C API Python. Почему-то этот способ многие (но не я) считают самым трудным, но зато он самый эффективный. Анализ C API для Tcl и C API для Python показал их значительное сходство, что и позволило очень быстро провести портирование. Отметим основные этапы портирования, которые вполне возможно кому-то помогут перенести те или иные модули (библиотеки) из Tcl в Python или наоборот.
Первое, в файле pythonpkcs11.c заменяем все объявления Tcl_Obj на PyObject, что вполне естественно: Tcl работает со своими объектами, а Python со своими.
Второе касается передачи параметров.
В общем виде объявление функции, реализующей ту или иную команду Tcl, в С-коде выглядит следующим образом (применительно к нашему коду):
name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){          . . .};

В Python аналогичный заголовок функции будет выглядеть так:
name_proc_py (PyObject *self, PyObject *args){          . . .};

В C-коде для tcl проверка количества входных параметров проводится с использованием переменной objc.
name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){  if (objc != 4) {          . . .    Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"pki::pkcs11::login handle slot password\"", -1));    return(TCL_ERROR);  }          . . .};

В Python параметры передаются в виде кортежа. Поэтому число переданных параметров вычисляется функцией PyTuple_Size(args):
name_proc_py (PyObject *self, PyObject *args){//Вводим переменную для числа параметров  int objc;  objc = PyTuple_Size(args);          . . .  if (objc != 3) {        PyErr_SetString(PyExc_TypeError, "pyp11_login args error (count args != 3)");return NULL;  }          . . .};

Отметим, что число параметров в коде для Tcl на единицу больше, т.к. в objv[0] хранится имя функции (аналогично функции main в C).
В приведенном коде наглядно видно как обрабатываются ошибки в Tcl и Python.
Вызов прерывания в случае ошибки для Tcl выполняется оператором
return (TCL_ERROR);
Текстовое сообщение об ошибке формируется оператором TclSetObjResult.
Для Python будут использоваться операторы return NULL и PyErr_SetString.
Теперь самое главное разбор параметров.
В Tcl каждый параметр передается как отдельный Tcl-объект, а в Python как кортеж параметров в виде Python-объектов. Поэтому, если мы хотим вносить минимальные изменения в код, целесообразно сначала распаковать кортеж по отдельным объектам, например (применительно к функции pyp11_login):
char *tcl_handle;long slotid_long;char *password;//Массив PyObject-ов для входных параметровPyObject *argspy[3];//Растаскиваем входные параметры/объекты   ("OOO" - три объекта) по своим ячейкам PyArg_ParseTuple(args, "OOO", &argspy[0], &argspy[1], &argspy[2])

Полученные объекты распаковываем с их функциональным назначением:
//Получаем строку (s) с handle библиотеки PKCS11PyArg_Parse(argspy[0], "s", &tcl_handle);//Получаем номер слота (l), в котором находится токенPyArg_Parse(argspy[1], "l", &slotid_long);//Получаем строку (s) с PIN-кодом владельца PyArg_Parse(argspy[2], "s", &password);...

Сразу оговоримся, что в C API Python имеется функция, которая позволяет сразу разбирать кортеж параметров. В этом случае можно обойтись одним оператором:
PyArg_ParseTuple(args, sls, &tcl_handle, &slotid_long, &password);

Как ни парадоксально, это практически все рекомендации.
Осталось последнее, возвращаемые значения.
Результаты выполнения команд возвращаются либо в виде строки, либо в виде списка, либо в виде словаря.
Приведём некоторые соответствия. Так для создания списка в коде для Tcl используется функция Tcl_NewObj(), а в коде для Python используется функция PyListNew(0).
Для добавления элемента в список для Tcl используется функция TclListObjAppendElement, а для Python функция PyList_Append. Все эти соответствия можно найти, сравнив код TclPKCS11 и код pyp11 (ССЛКА).
Также вместо используемых функций ckalloc и ckfree в tclpkcs11.c для Tcl, в модуле pythonpkcs11.c используются стандартные функции работы с памятью malloc и free.
После проведенного анализа модификация кода вместе с тестированием заняла пару рабочих дней.

II. Сборка и установка модуля pyp11


Итак, скачиваем архив и распаковываем его. Заходим в папку PythonPKCS11 и выполняем команду установки:
#python3 setup.py install

Лично я тестировал на платформах Windows, Linux, OS X. Отметим, что пакет TclPKCS11 успешно работает и на платформе Android.
После установки модуля переходим в папку tests и начинаем тестирование.
Pаботоспособность модуля pyp11 можно проверить даже без токена. В составе модуля есть функция pyp11.dgst, которая не привязана к токенам и позволяет посчитать хэш по ГОСТ Р 34.10-2012:
bash-4.4$ python3Python 3.7.9 (default, Feb  1 2021, 16:55:33) [GCC 8.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import pyp11#Считаем хэш по ГОСТ Р 34.11-2012-256 (stribog256)>>> hash256 = pyp11.dgst("stribog256", "Текст для хэширования")#Считаем хэш по ГОСТ Р 34.11-2012-512 (stribog512)>>> hash512 = pyp11.dgst("stribog512", "Текст для хэширования")>>> print("STRIBOG256=" + hash256)STRIBOG256=26b8865c37831aa254706e6c3514fb23f386358e9dd858703a24d4825d2c4794>>> print("STRIBOG512=" + hash512)STRIBOG512=e92ff2063c586ec6e9c9569dad7dd503de1c88faafc8b1bf43909bfa36db92ccbf3823f0b8f5d877f10933ed7e670081018dac0929d17729422f05ce1f4c4f25>>> quit()bash-4.4$

Значение хэш возвращается в шестнадцатеричном виде.
Для перевода хэш-а в бинарный вид можно воспользоваться следующей функцией:
>>> hash256_bin = bytes(bytearray.fromhex(hash256))

Напомним, как перевести бинарный код в шестнадцатеричный:
>>> hash256 = bytes(hash256_bin).hex()>>> print("STRIBOG256_NEW=" + hash256)STRIBOG256_NEW=26b8865c37831aa254706e6c3514fb23f386358e9dd858703a24d4825d2c4794>>>

Есть еще одна функция, которая также может работать без токена. Это функция parsecert. На вход этой функции подается сертификат в DER-формате, упакованный в шестнадцатеричную кодировку:
bash-4.4$ python3Python 3.7.9 (default, Feb  1 2021, 16:55:33) [GCC 8.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import pyp11>>> #Читаем серификат в DER-кодировке из файла>>> with open("cert_256.der", "rb") as f:...    cert_der = f.read()... >>> #Упаковываем сертификат der в hex>>> cert_der_hex = bytes(cert_der).hex()>>> #Распарсиваем сертификат>>> pubk = pyp11.parsecert(cert_der_hex)>>> 

Результатом выполнения команды pyp11.parsecert является словарь (ассоциированный список):
>>>print (pubk.keys())dict_keys(['pkcs11_id', 'pubkeyinfo', 'pubkey', 'subject', 'issuer', 'serial_number', 'tbsCertificate', 'signature_algo', 'signature'])>>>

В этом словаре находятся as1-структуры элементов сертификата. Все элементы закодированы в шестнадцатеричный формат. Среди элементов находится элемент pubkeyinfo со значением asn1-структуры subjectpublickeyinfo, элемент pubkey со значением публичного ключа, серийный номер сертификата, tbs-сертификат, который будет использоваться для проверки подписи сертификата, алгоритм подписи сертификата и значение самой подписи, а также элементы с информацией о владельце и издателе сертификата, полученные из сертификата и закодированные в шестнадцатеричное представление:
>>> subject = pubk['subject']>>> print ('SUBJECT=' + subject)SUBJECT=30820205310b3009060355040613025255312a3028060355042a0c21d09fd0b0d0b2d0b5d0bb20d090d0bdd0b0d182d0bed0bbd18cd0b5d0b2d0b8d1873135303306035504030c2cd09ed09ed09e20d09ad09ed09cd09fd090d09dd098d0af20d0add09ad09e2dd0a1d0a2d0a0d09ed099203937311d301b06092a864886f70d010901160e696e666f4072746564632e6f72673118301606052a85036401120d313137373734363733343433393116301406052a85036403120b3133383632313537373734311a301806082a85030381030101120c3030393732393131303536393130302e060355040c0c27d093d0b5d0bdd0b5d180d0b0d0bbd18cd0bdd18bd0b920d0b4d0b8d180d0b5d0bad182d0bed180310a3008060355040b0c013031353033060355040a0c2cd09ed09ed09e20d09ad09ed09cd09fd090d09dd098d0af20d0add09ad09e2dd0a1d0a2d0a0d09ed099203937315f305d06035504090c5631313931333620d0b32e20d09cd0bed181d0bad0b2d0b020d0bfd1802dd0b420312dd0b920d0a1d0b5d182d183d0bdd18cd181d0bad0b8d0b920d0b42e203130d09020d181d182d1802e203120d0bfd0bed0bc2e20323115301306035504070c0cd09cd0bed181d0bad0b2d0b0311c301a06035504080c13373720d0b32e20d09cd0bed181d0bad0b2d0b0311b301906035504040c12d0a5d0b0d180d0b8d182d0bed0bdd0bed0b2>>>

Элемент pkcs11_id берётся не из сертификата, а рассчитывается как значение хэш по SHA-1 от значения публичного ключа. При использовании функции pyp11.parsecert в данном контексте (без подключенного токена) pkcs11_id будет равен -1:
>>> pkcs11_id = pubk['pkcs11_id']>>> print ('PKCS11_ID=' + pkcs11_id)PKCS11_ID=-1>>>

Кто-то может сказать, а что, разве в Python нет средств разбора сертификатов? А как же, например, asn1crypto? Ответ заключается в том, что в этих средствах не учтены особенности российской криптографии. И вот, чтобы получить максимальную самодостаточность пакета pyp11, в него помимо функций, связанных с генерацией ключевой пары, формирования и проверки подписи, включены дополнительные функции. Например, asn1-структура pubkeyinfo необходима при проверке электронной подписи. И именно поэтому и была включена функция parsecert для частичного разбора сертификата x509.v3 и получения, в частности, asn1-структуры subjectpublickeyinfo (pubkeyinfo).
В папке tests проекта в файлах test0_* находятся соответствующие тесты.
############УБРАТЬ про FSB795 ################################
Отметим также, что для разбора сертификатов с российской криптографией можно воспользоваться пакетом fsb795:
>>> import fsb795>>> #Парсим наш сертификат с помощью fsb795>>> mycert = fsb795.Certificate(cert_der)>>> #читаем данные о владельце сертификата и типе владельце>>> dn, type = mycert.subjectCert()>>> #DN - это словарь/ассоциированный список>>> for key in dn.keys():...     print (key + '=' + dn[key])... Country=RUGN=Имя ОтчествоCN=ООО КОМПАНИЯ E=info@ooo.orgOGRN=xxxxxxxxxxxxSNILS=xxxxxxxxxxxINN=xxxxxxxxxxxxtitle=Генеральный директорOU=0O=ООО КОМПАНИЯ street=119136 г. Москва L=МоскваST=77 г. МоскваSN=Харитонов>>> 

Теперь можно переходить к работе с токенами.

III. Управление токенами PKCS#11


Для тестирования функций управления подойдет любой токен PKCS#11, даже токен без поддержки какой-либо криптографии, например RuTokenLite. Но поскольку мы ведём речь о российской криптографии, то целесообразно сразу иметь токен с поддержкой российской криптографии. Здесь мы имеем в виду ГОСТ Р 34.10-2012 и ГОСТ Р 34.11-2012. Это может быть как аппаратный токен, например RuTokenECP-2.0, так и программные или облачные токены.
Установить программный токен или получить доступ к облачному токену можно, воспользовавшись утилитой cryptoarmpkcs.
Скачать утилиту cryptoarmpkcs можно здесь.
После запуска утилиты необходимо зайти на вкладку Создать токены:



На вкладке можно найти инструкции для получения токенов.
Итак, у нас токен и библиотека для работы с ним. После загрузки модуля pyp11 требуется загрузить библиотеку для работы с нашим токеном. В примерах будут использоваться библиотека librtpkcs11ecp-2.0 для работы с аппаратным токеном, библиотека libls11sw2016 для работы с программным токеном и библиотека libls11cloud.so для работы с облачным токеном. Читатели могут использовать любые токены, даже те, на которых нет российской криптографии, на них тоже можно проверить функции управления.
Итак, загружаем библиотеку командой loadmodule:
bash-4.4$ python3  Python 3.7.9 (default, Feb  1 2021, 16:55:33) [GCC 8.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import sys>>> import pyp11>>> #Выбираем библиотеку pkcs11>>> lib = "/usr/local/lib64/librtpkcs11ecp_2.0.so">>> #Обработка ошибки при загрузке библиотеки PKCS#11>>> try:... #Вызываем команду загрузки библиотеки и плохим числом параметров...     handlelib = pyp11.loadmodule(lib, 2)... except:...     print ('Ошибка загрузки библиотеки: ')...     e = sys.exc_info()[1]...     e1 = e.args[0]...     print (e1)... Ошибка загрузки библиотеки: pyp11_load_module args error (count args != 1)>>> #Загружаем с правильным синтаксисом>>> idlib = pyp11.loadmodule(lib)>>> #Печатаем дескриптор библиотеки>>> print (idlib)pkcs0>>> 

Дескриптор загруженной библиотеки используется при её выгрузке:
>>> pyp11.unloadmodule(idlib) 

Теперь, когда библиотека загружена, можно получить список поддерживаемых её слотов и узнать есть ли в каких слотах токены. Для получения списка слотов с полной информацией о них и содержащихся в них токенах используется команда:
>>> slots = pyp11.listslots(idlib)>>>

Команда pyp11.listslots возвращает список, каждый элемент которого содержит информацию о слоте:
[<info slot1>, <info slot2>, ... , <info slotN>]
.
В свою очередь, каждый элемент этого списка также является списком, состоящим из четырех элементов:
[<номер слота>, <метка токена, находящегося в слоте>, <флаги слота и токена>, <информация о токене>]

Если слот не содержит токен, то элементы <метка токена ...> и <информация о слоте> содержат пустое значение.
Наличие токена в слоте определяется по наличию флага TOKEN_PRESENT в списке <флаги слота и токена>:
bash-4.4$ python3Python 3.7.9 (default, Feb  1 2021, 16:55:33) [GCC 8.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import sys>>> import pyp11>>> #Выбираем библиотеку>>> #lib = '/usr/local/lib64/libls11sw2016.so'>>> lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'>>> #Загружаем библиотеку>>> libid = pyp11.loadmodule(lib)>>> #Дескриптор библиотеки>>> print (libid)pkcs0>>> #Загружаем список слотов>>> slots = pyp11.listslots(libid)>>> tokpr = 0>>> #Ищем первый подключенный токен>>> while (tokpr == 0):... #Перебираем слоты...     for v in slots:...         #Список флагов текущего слота...         flags = v[2]... #Проверяем наличие в стоке токена...         if (flags.count('TOKEN_PRESENT') !=0):...             tokpr = 1... #Избавляемся от лишних пробелов у метки слота...             lab = v[1].strip()...             infotok = v[3]...             slotid = v[0]...             break...     if (tokpr == 0):...         input ('Нет ни одного подключенного токена.\nВставьте токен и нажмите ВВОД')...     slots = pyp11.listslots(libid)... #Информация о подключенном токене... Нет ни одного подключенного токена.Вставьте токен и нажмите ВВОД''>>> #Информация о подключенном токене>>> print ('LAB="' + lab + '", SLOTID=' + str(slotid))LAB="Rutoken lite <no label>", SLOTID=0>>> print ('FLAGS:', flags)FLAGS: ['TOKEN_PRESENT', 'RNG', 'LOGIN_REQUIRED', 'SO_PIN_TO_BE_CHANGED', 'REMOVABLE_DEVICE', 'HW_SLOT']>>>

Если взглянуть на флаги (FLAGS:) подключенного токена, то в них отсутствует флаг 'TOKEN_INITIALIZED'. Отсутствие этого флага говорит о том, что токен не инициализирован и требуется его инициализация:
#Проверяем, что токен проинициализирован>>> if (flags.count('TOKEN_INITIALIZED') == 0''):...         #Инициализируем токен...         dd = pyp11.inittoken (libid, 0, '87654321',"TESTPY2")...         >>>

Как видим, для инициализации токена используется следующая команда:
pyp11.inittoken (<дискриптор библиоткети>, <номер слота>, <SO-PIN>, <метка токена>)

Естественно, токен можно переинициализировать независимо от наличия флага 'TOKEN_INITIALIZED', только надо иметь в виду, что переинициализация токена ведет к уничтожению на нем всех объектов (ключи, сертификаты и т.д).
После инициализации токена должен быть проинициализирован USER-PIN. Эту операцию, как правило, делает производитель или продавец токена:
pyp11.inituserpin (<дискриптор библиоткети>, <номер слота>, <SO-PIN>, <USER-PIN>)

При этом выставляется флаг 'USER_PIN_TO_BE_CHANGED', который напоминает владельцу токена, что надо бы сменить свой USER-PIN (параметр 'user'):
pyp11.setpin (<дискриптор библиоткети>, <номер слота>, <'user'|'so'>,<текущий PIN-код>, <новый PIN-код>)

Сегодня модно получать в УЦ токены с закрытыми ключами и предустановленными PIN-кодами и ключевой парой. И, как правило, получателей не предупреждают, что целесообразно PIN-коды поменять, и не говорят, как это сделать. Я бы рекомендовал использовать для этого уже упоминавшуюся утилиту cryptoarmpkcs:

image

В папке tests проекта pyp11 лежат три теста test1_0_inittoken.py, test1_1_inituserpin.py и test1_2_change_userpin, которые наглядно демонстрируют инициализацию токена. Выполнять их надо в порядке перечисления.
Было бы несправедливо не показать инициализацию токена с использованием уже упоминавшегося пакета PyKCS11:
$ python3Python 3.7.9 (default, Feb  1 2021, 16:55:33) [GCC 8.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import PyKCS11>>> #Библиотека PKCS#11>>> lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'>>> pkcs11.load(lib)>>> #Получаем список слотов с токенами>>> slots = pkcs11.getSlotList(tokenPresent=True)>>> #Ищем первый подключенный токен>>> while (len(slots) == 0):...     input ('Нет ни одного подключенного токена.\nВставьте токен и нажмите ВВОД')...     #Получаем список слотов с токенами...     slots = pkcs11.getSlotList(tokenPresent=True)... Нет ни одного подключенного токена.Вставьте токен и нажмите ВВОД''>>>>>> #Берём первый подключенный токен>>> slot = slots[0]>>> #Закрываем все сессии на токене >>> #SO-PIN>>> so_pin = '87654321'>>> lab_tok = "myLabel">>> #Инициализация токена>>> pkcs11.initToken(slot, so_pin, lab_tok)>>> session = pkcs11.openSession(slot, PyKCS11.CKF_SERIAL_SESSION | PyKCS11.CKF_RW_SESSION)>>> #Установка первичного USER-PIN >>> init_pin = '1234'>>> session.login(so_pin, user_type=PyKCS11.CKU_SO)>>> session.initPin(init_pin)>>> session.logout()>>> #Новый USER-PIN>>> user_pin = '01234567'>>> session.login(init_pin)>>> # change PIN>>> session.setPin(init_pin, user_pin)>>> session.logout()>>> quit()$ 

IV. Ключевая пара, электронная подпись и её проверка


Итак, наш токен готов в работе: мы его проинициализировали и установили метку, но самое главное, мы поменяли PIN-коды (USER, SO).
Первым делом необходимо убедиться, что наш токен поддерживает необходимые нам криптографические механизмы. Поскольку речь идет о ГОСТ Р 34.10-2012 и ГОСТ Р 34.11-2012, то токен должен поддерживать механизмы CKM_GOST* в соответствии рекомендациями ТК-26.
Для получения списка механизмов используется команда pyp11.lictmechs:
<список механизмов> = pyp11.listmech(<идентификатор библиотеки>, <номер слота>)

Как ни странно, но токены могут не иметь поддержки криптомеханизмов, например, RuToken Lite. Они нас интересовать не будут. Мы будем использовать только токены с поддержкой ГОСТ Р 34.10-2012 и ГОСТ Р 34.11-2012:
$ python3Python 3.7.9 (default, Feb  1 2021, 16:55:33) [GCC 8.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import pyp11>>> #Выбираем библиотеку>>> #Программный токен>>> lib = '/usr/local/lib64/libls11sw2016.so'>>> #Для Windows>>> #lib='C:\Temp\ls11sw2016.dll'>>> #Облачный токен>>> #lib = '/usr/local/lib64/libls11cloud.so'>>> #Аппаратный токен>>> #lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'>>> #Закгружаем выбранную библиотеку>>> aa = pyp11.loadmodule(lib)>>> listmech = pyp11.listmechs(aa, 0)>>> print ('\tКриптографические механизмы токена')        Криптографические механизмы токена>>> for mech in listmech:...     print(mech)... 
Криптографические механизмы
CKM_GOSTR3410_KEY_PAIR_GEN (0x1200)CKM_GOSTR3410_512_KEY_PAIR_GEN (0xD4321005)CKM_GOSTR3410 (0x1201)CKM_GOSTR3410_512 (0xD4321006)CKM_GOSTR3410_WITH_GOSTR3411 (0x1202)CKM_GOSTR3410_WITH_GOSTR3411_12_256 (0xD4321008)CKM_GOSTR3410_WITH_GOSTR3411_12_512 (0xD4321009)CKM_GOSTR3410_DERIVE (0x1204)CKM_GOSTR3410_12_DERIVE (0xD4321007)CKM_GOSR3410_2012_VKO_256 (0xD4321045)CKM_GOSR3410_2012_VKO_512 (0xD4321046)CKM_KDF_4357 (0xD4321025)CKM_KDF_GOSTR3411_2012_256 (0xD4321026)CKM_KDF_TREE_GOSTR3411_2012_256 (0xD4321044)CKM_GOSTR3410_KEY_WRAP (0x1203)CKM_GOSTR3410_PUBLIC_KEY_DERIVE (0xD432100A)CKM_LISSI_GOSTR3410_PUBLIC_KEY_DERIVE (0xD4321037)CKM_GOST_GENERIC_SECRET_KEY_GEN (0xD4321049)CKM_GOST_CIPHER_KEY_GEN (0xD4321048)CKM_GOST_CIPHER_ECB (0xD4321050)CKM_GOST_CIPHER_CBC (0xD4321051)CKM_GOST_CIPHER_CTR (0xD4321052)CKM_GOST_CIPHER_OFB (0xD4321053)CKM_GOST_CIPHER_CFB (0xD4321054)CKM_GOST_CIPHER_OMAC (0xD4321055)CKM_GOST_CIPHER_KEY_WRAP (0xD4321059)CKM_GOST_CIPHER_ACPKM_CTR (0xD4321057)CKM_GOST_CIPHER_ACPKM_OMAC (0xD4321058)CKM_GOST28147_KEY_GEN (0x1220)CKM_GOST28147 (0x1222)CKM_GOST28147_KEY_WRAP (0x1224)CKM_GOST28147_PKCS8_KEY_WRAP (0xD4321036)CKM_GOST_CIPHER_PKCS8_KEY_WRAP (0xD432105A)CKM_GOST28147_ECB (0x1221)CKM_GOST28147_CNT (0xD4321825)CKM_GOST28147_MAC (0x1223)CKM_KUZNYECHIK_KEY_GEN (0xD4321019)CKM_KUZNYECHIK_ECB (0xD432101A)CKM_KUZNYECHIK_CBC (0xD432101E)CKM_KUZNYECHIK_CTR (0xD432101B)CKM_KUZNYECHIK_OFB (0xD432101D)CKM_KUZNYECHIK_CFB (0xD432101C)CKM_KUZNYECHIK_OMAC (0xD432101F)CKM_KUZNYECHIK_KEY_WRAP (0xD4321028)CKM_KUZNYECHIK_ACPKM_CTR (0xD4321042)CKM_KUZNYECHIK_ACPKM_OMAC (0xD4321043)CKM_MAGMA_KEY_GEN (0xD432102A)CKM_MAGMA_ECB (0xD4321018)CKM_MAGMA_CBC (0xD4321023)CKM_MAGMA_CTR (0xD4321020)CKM_MAGMA_OFB (0xD4321022)CKM_MAGMA_CFB (0xD4321021)CKM_MAGMA_OMAC (0xD4321024)CKM_MAGMA_KEY_WRAP (0xD4321029)CKM_MAGMA_ACPKM_CTR (0xD4321040)CKM_MAGMA_ACPKM_OMAC (0xD4321041)CKM_GOSTR3411 (0x1210)CKM_GOSTR3411_12_256 (0xD4321012)CKM_GOSTR3411_12_512 (0xD4321013)CKM_GOSTR3411_HMAC (0x1211)CKM_GOSTR3411_12_256_HMAC (0xD4321014)CKM_GOSTR3411_12_512_HMAC (0xD4321015)CKM_PKCS5_PBKD2 (0x3B0)CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC (0xD4321035)CKM_TLS_GOST_KEY_AND_MAC_DERIVE (0xD4321033)CKM_TLS_GOST_PRE_MASTER_KEY_GEN (0xD4321031)CKM_TLS_GOST_MASTER_KEY_DERIVE (0xD4321032)CKM_TLS_GOST_PRF (0xD4321030)CKM_TLS_GOST_PRF_2012_256 (0xD4321016)CKM_TLS_GOST_PRF_2012_512 (0xD4321017)CKM_TLS12_MASTER_KEY_DERIVE (0x3E0)CKM_TLS12_KEY_AND_MAC_DERIVE (0x3E1)CKM_TLS_MAC (0x3E4)CKM_TLS_KDF (0x3E5)CKM_TLS_TREE_GOSTR3411_2012_256 (0xD4321047)CKM_EXTRACT_KEY_FROM_KEY (0x365)CKM_SHA_1 (0x220)CKM_MD5 (0x210)

>>> quit()$

Теперь, когда мы убедились, что токен поддерживает российскую криптографию, можно приступить к созданию ключевой пары на токене и использовать ее закрытый ключ для подписания различных документов.
Напомним, что закрытый и открытый ключи это не только их значения (для открытого ключа ГОСТ Р 34.10-2012-256 это 512 бит, а для открытого ключа ГОСТ Р 34.10-2012-512 это 1024 бита), но и параметры схемы цифровой подписи (п. 5.2 ГОСТ Р 34.10-2012). В дальнейшем параметры схемы цифровой подписи для простоты будем называть параметрами (криптопараметрами) ключевой пары.
Криптопараметры при генерации ключевой пары задаются OID-ами. В настоящее время TK-26 определил следующие oid-ы для криптопараметров алгоритма подписи ГОСТ Р 34.10-2012 с ключом 256:
  • 1.2.643.7.1.2.1.1.1 (id-tc26-gost-3410-12-256-paramSetA);
  • 1.2.643.7.1.2.1.1.2 (id-tc26-gost-3410-12-256-paramSetB;
  • 1.2.643.7.1.2.1.1.3 (id-tc26-gost-3410-12-256-paramSetC);
  • 1.2.643.7.1.2.1.1.4 (id-tc26-gost-3410-12-256-paramSetD).

При этом продолжают действовать так называемые OID-ы параметров от КриптоПро:
  • 1.2.643.2.2.35.1 (id-GostR3410-2001-CryptoPro-A-ParamSet);
  • 1.2.643.2.2.35.2 (d-GostR3410-2001-CryptoPro-B-ParamSet);
  • 1.2.643.2.2.35.3 (id-GostR3410-2001-CryptoPro-C-ParamSet);
  • 1.2.643.2.2.36.0 (id-GostR3410-2001-CryptoPro-XchA-Param)Set;
  • 1.2.643.2.2.36.1 (id-GostR3410-2001-CryptoPro-XchB-Param)Set.

Кто-то может сказать, а что это за каша такая? Но если смотреть по сути, то окажется, что параметры КриптоПро с OID-ами 1.2.643.2.2.35.1, 1.2.643.2.2.35.2, 1.2.643.2.2.35.3 соответствуют параметрам ТК-26 с OID-ами 1.2.643.7.1.2.1.1.1, 1.2.643.7.1.2.1.1.2, 1.2.643.7.1.2.1.1.3 соответственно. Далее ещё интереснее. Параметр КриптоПро id-GostR3410-2001-CryptoPro-XchA-Param соответствует параметру id-GostR3410-2001-CryptoPro-A-ParamSet, а параметр id-GostR3410-2001-CryptoPro-XchB-Param параметру id-GostR3410-2001-CryptoPro-C-ParamSet того же КриптоПро. Если не запутались, то идём дальше.
С криптопараметрам для алгоритма подписи ГОСТ Р 34.10-2012 с ключом 512 проще:
  • 1.2.643.7.1.2.1.2.1 (id-tc26-gost-3410-2012-512-paramSetA);
  • 1.2.643.7.1.2.1.2.2 (id-tc26-gost-3410-2012-512-paramSetB);
  • 1.2.643.7.1.2.1.2.3 (id-tc26-gost-3410-2012-512-paramSetC);

Для генерации ключевой пары используется следующая команда:
<идентификатор словаря> = pyp11.keypair(<идентификатор библиотеки>, <номер слота с токеном>, <тип ключевой пары>, <OID криптопараметра>, <метка/CKA_LABEL>)

Единственное, с чем мы не сталкивались, это <тип ключевой пары>:
<тип ключевой пары> := 'g12_256' | 'g12_512'
Таким образом, если мы хотим получить пару по алгоритму подписи ГОСТ Р 34.10-2012 с ключом 512, то задаем тип 'g12_512', например:
genkey = pyp11.keypair(libid, slotid, 'g12_512', '1.2.643.7.1.2.1.2.2', 'KeyGost512')

Для алгоритма подписи ГОСТ Р 34.10-2012 с ключом 256 генерация может выглядеть так:
genkey256 = pyp11.keypair(libid, slotid, 'g12_256', '1.2.643.7.1.2.1.1.3', 'KeyGost256')

Перед генерацией ключевой пары необходимо обязательно залогиниться на токене:
pyp11.login(<идентификатор библиотеки>, <номер слота>, 'USER-PIN')

После выполнения требуемой операции целесообразно выполнить logout:
pyp11.logout(<идентификатор библиотеки>, <номер слота>)

При успешной генерации ключевой пары возвращается ассоциированный список (словарь), например:
>>> pyp11.login(libid, slotid, '01234567')
1
>>> genkey256 = pyp11.keypair(libid, slotid, 'g12_256', '1.2.643.7.1.2.1.1.3', 'KeyGost256')
>>> print (genkey256.keys())
dict_keys(['pkcs11_handle', 'pkcs11_slotid', 'hobj_pubkey', 'hobj_privkey', 'pkcs11_id', 'pkcs11_label', 'pubkey', 'pubkey_algo', 'pubkeyinfo', 'type'])
>>> pyp11.logout(libid, slotid)
1
>>>
Среди возвращаемых значений находятся указатели на открытый ('hobj_pubkey') и закрытый ключи ('hobj_privkey'). Последний мы будем использовать при подписании. Среди возвращаемых значений находится и CKA_ID открытого и закрытого ключей ('pkcs11_id'). Элемент pkcs11_id также может использоваться при подписании для поиска закрытого ключа. Напомним, CKA_ID это значение хэша SHA-1 от значения открытого ключа, которое находится в элементе 'pubkey'. При генерации ключевой пары CKA_ID автоматически выставляется для закрытого и открытого ключей. Именно по ним, как правило, ищут соответствие между ключами. Можно распечатать все возвращаемые значения:
>>> for key in genkey256.keys():...     print (key + '= ' + str(genkey256.get(key)))... pkcs11_handle= pkcs0pkcs11_slotid= 0hobj_pubkey= hobj0100000000000000hobj_privkey= hobj0200000000000000pkcs11_id= dd22fe35aeb7eb2ebcad7199b117eb3a7b5f5813pkcs11_label= KeyGost256pubkey= 4c2ed60bc5771b2a6616af58c8dd202b9463dde9bd1de028335e718634761e360a25b2f337c2e67c28402cd49fff4f708130a80dc479301b21ceb9324c47464bpubkey_algo= 1 2 643 7 1 1 1 1pubkeyinfo= 302106082a85030701010101301506092a850307010201010306082a8503070101020203430004404c2ed60bc5771b2a6616af58c8dd202b9463dde9bd1de028335e718634761e360a25b2f337c2e67c28402cd49fff4f708130a80dc479301b21ceb9324c47464btype= pkcs11>>> 

Для формирования электронной подписи и её проверки сохраним следующие значения:
>>> hprivkey = genkey256.get("hobj_privkey")>>> pkcs11_id = genkey256.get("pkcs11_id")>>> pubkeyinfo = genkey256.get("pubkeyinfo")>>> 

Электронная подпись (ЭП) документа представляет собой подписанный хэш от этого документа.
Поэтому сначала считается соответствующий хэш:
<переменная для хранения хэш > = pyp11.digest(<идентификатор библиотеки>, <слот токена>, 'stribog256' | 'stribog512', <документ>)
Значение хэш всегда возвращается в шестнадцатеричном виде.
Итак, если мы хотим получить подпись по алгоритму ГОСТ Р 34.10-2012 с ключом 256 бит, то нам сначала надо посчитать хэш по алгоритму хэширования ГОСТ Р 34.11-2012 с длиной 256 бит, а затем подписать полученный хэш с использованием механизма 'CKM_GOSTR3410':
<переменная для  ЭП> = pyp11.sign(<идентификатор библиотеки>, <слот токена>, 'CKM_GOSTR3410' | 'CKM_GOSTR3410_512', <хэш документа>, <hobj_privkey|pkcs11_id>)

Хэш документа должен быть в шестнадцатеричном виде. Электронная подпись также возвращается в шестнадцатеричном виде.
Ниже приведем пример кода формирования ЭП:
bash-4.4$ python3Python 3.7.9 (default, Feb  1 2021, 16:55:33) [GCC 8.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import pyp11>>> lib = '/usr/local/lib64/libls11sw2016.so'>>> libid = pyp11.loadmodule(lib)>>> slotid = 0>>> #Незабывайте лигиниться на токене!!!>>> pyp11.login(libid, slotid, '01234567')1>>> genkey256 = pyp11.keypair(libid, slotid, 'g12_256', '1.2.643.7.1.2.1.1.3', 'KeyGost256')>>> hprivkey = genkey256.get("hobj_privkey")>>> pkcs11_id = genkey256.get("pkcs11_id")>>> pubkeyinfo = genkey256.get("pubkeyinfo")>>> hashdoc_hex = pyp11.digest(libid, slotid, 'stribog256', 'Подписываемый документ')>>> #Для ЭП используется hobj_privkey>>> sign1 = pyp11.sign(libid, slotid, 'CKM_GOSTR3410', hashdoc_hex, hprivkey)>>> #Для ЭП используется pkcs11_id (CKA_ID)>>> sign2 = pyp11.sign(libid, slotid, 'CKM_GOSTR3410', hashdoc_hex, pkcs11_id)>>> print ('SIGN1=' + sign1 )SIGN1=5b3f881153f50d9a8da6bb37bb83f54fe997d074672c29c2c0aeb22739a14f1e776b8427e262b098c75abe3a4faffe383d3e2cc406afa09efb3e783919b4ca11>>> print ('SIGN2=' + sign2) SIGN2=441a29206a3622a9c76282b71b4fcdbf4c15034d0f0be7b1f711c6d5eef8162a2a2876a5d375cb56e23fc76173cacf88b620fd793cf756589a76cbee6b1fd27a>>> pyp11.logout(libid, slotid)1>>> 

Для проверки подписи используется asn1-структура открытого ключа subjectPublicKeyInfo, которую мы сохранили после генерации ключевой пары в переменной pubkeyinfo:
 pubkeyinfo = genkey256.get("pubkeyinfo")

Для проверки подписи используется следующая команда:
pyp11.sign(<идентификатор библиотеки>, <слот токена>, <хэш документа в hex>, <подпись документа в hex>, <asn1-subjectPublicKeyInfo>)

Команда возвращает 1 (единицу), если подпись прошла проверку, и 0 (ноль), если проверка не прошла.
Продолжим наш пример проверкой двух полученных подписей:
>>> verify1 = pyp11.verify(libid, slotid, hashdoc_hex, sign1, pubkeyinfo)>>> print (verify1)1>>> verify2 = pyp11.verify(libid, slotid, hashdoc_hex, sign2, pubkeyinfo)>>> print (verify2)1>>> 

Как видим обе полученные подписи корректны.

V. Проверка электронной подписи сертификата


Используя полученные знания, напишем пример проверки электронной подписи сертификата:
#!/usr/bin/python3#-*- coding: utf-8 -*-import pyp11print('Проверка подписи сертификата')#Библиотека для токенаlib = '/usr/local/lib64/libls11sw2016.so'aa = pyp11.loadmodule(lib)print (aa)#Файл с корневым сертификатом в DER-кодировкеfileCA = "CA_12_512.der"#Файл с сертификатом пользователя в DER-кодировкеfileUser = "habrCA_12_512.der"#Читаем корневой сертификат в DER-кодировке из файлаwith open(fileCA, "rb") as f:    certCA = f.read()#Упаковываем der в hexcertCA_hex = bytes(certCA).hex()#Читаем сертификат пользователя в DER-кодировке из файлаwith open(fileUser, "rb") as f:    certHabr = f.read()#Упаковываем der в hexcertHabr_hex = bytes(certHabr).hex()print ('Разбираем корневой сертификат')parseCA = pyp11.parsecert (aa, 0, certCA_hex)print ('Разбираем сертификат пользователя')parseHabre = pyp11.parsecert (certHabr_hex)print (parseHabre.keys())#Проверяем, что издатель сертификата совпадает с владельцем корневого сертификатаif (parseCA.get('subject') != parseHabre.get('issuer')):    print ('Сертификат выдан на другом УЦ')    quit()print ('Сертификат выдан на данном УЦ')#Переводим tbsCertificate пользователь в binarytbs_hex = parseHabre.get('tbsCertificate')tbsHabrDer = bytes(bytearray.fromhex(tbs_hex))#tbsHabrDer = '1111'#Получаем хэш для tbs-сертификатаhashTbs_hex = pyp11.digest(aa, 0, "stribog512", tbsHabrDer)#hashTbs_hex = pyp11.digest(aa, 0, "stribog256", tbsHabrDer)verify = pyp11.verify(aa, 0,  hashTbs_hex, parseHabre.get('signature'), parseCA.get('pubkeyinfo'))#verify = pyp11.verify(aa, 0,  hashTbs_hex, parseHabre.get('signature'), parseHabre.get('pubkeyinfo'))print (verify)if (verify != 1):    print ('Подпись сертификата не прошла проверку')    quit()print ('Подпись сертификата прошла проверку')quit()

VI. Работа с объектами токена


Основными объектами, с которыми приходится иметь дело, работая с токенами PKCS#11, являются сертификаты и ключи. И те и другие имеют атрибуты. Нас в первую очередь интересуют атрибуты CKA_LABEL или метка объекта и СКА_ID или идентификатор объекта. Именно атрибут CKA_ID используется для доступа и к сертификатам и ключам.
Уже имея в своем распоряжении рассмотренные выше команды модуля pyp11, можно создать ключевую пару и сформировать подписанный запрос на сертификат. Отправить полученный запрос в удостоверяющий центр и получить там сертификат. Но получив сертификат, возникает вопрос как его поставить на токен и привязать к ключевой паре? Именно эту задачу решает команда pyp11.importcert:
<переменная для CKA_ID> = pyp11.importcert(<идентификатор библиотеки>, <слот токена>, <сертификат в DER-формате и hex-кодировке>, <метка CKA_LABEL>)

Как работает команда? Первым делом она вычисляет по открытому ключу сертификата идентификатор CKA_ID. Именно этот идентификатор будет возвращен в hex-кодировке после успешного размещения сертификата на токене. После установки сертификата на токен в DER-формате, устанавливаются его атрибуты CKA_ID и CKA_LABEL.
Если вам необходимо связать тройку <сертификат> x <открытый ключ> x <закрытый ключ> не только по CKA_ID, но и по метке CKA_LABEL, то необходимо установить метку у ключевой пары аналогичную метке сертификата. Для этого используется команда rename:
pyp11.rename(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)

В <типе объекта> указывается, к каким типам объектов будет применяться команда: 'cert' | 'key' | 'all' (сертификаты, ключевая пара, к тому и другому).
Команда rename позволяет менять не только CKA_LABEL, но и CKA_ID. Конкретные объекты могут задаваться идентификаторами объектов CKA_ID (pkcs11_id), например:
#Импортируем сертификат и получаем его CKA_IDlabcert = 'LabelNEW'ckaid = pyp11.importcert(aa, 0, cert_der_hex, labcert)#Устанавливаем метку сертификата и для ключей#Готовим словарьldict = dict(pkcs11_id=ckaid, pkcs11_label=labcert)#Меняем метки у ключейpyp11.rename(aa, 0, 'key', ldict)

Аналогичным образом меняется атрибут CKA_ID. В этом случае в словарь вместо метки указывается новый CKA_ID:
ldict = dict(pkcs11_id=ckaid, pkcs11_id_new=11111)

Аналогичным образом можно удалить объекты:
pyp11.delete(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)

При уничтожении в словарь попадает только один элемент, который будет указывать на удаляемые объекты. Это либо CKA_ID (ключ pkcs11_id) либо непосредственно handle-объекта (как правило, его можно получить по команде pyp11.listobjects, ключ pkcs11_handle):
ldict = dict(pkcs11_id=ckaid)#Или с handle-объекта:#ldict = dict(hobj=pkcs11_handle)#Уничтожить личный сертификат с ключамиpyp11.login(aa. 0, '01234567')pyp11.delete(aa, 0, 'all', ldict)pyp11.logout(aa, 0)

Упомянем еще об одной очень редко используемой команде. Это команда закрытия сессий на токене:
pyp11.closesession(<идентификатор библиотеки>)

Эту команду следует вызывать, когда возникнет ошибка PKCS11_ERROR SESSION_HANDLE_INVALID, а затем повторить команду, на которой возникла ошибка. Эта ошибка может возникнуть при кратковременном извлечении токена из компьютера при работе вашей программы.
И завершим мы рассмотрение командой pyp11.listcertsder:
<список сертификатов> = pyp11.listcerts(<идентификатор библиотеки>, <слот токена>)

Вот пример кода:
#!/usr/bin/python3#-*- coding: utf-8 -*-import sysimport timeimport pyp11print('Список сертификатов токена')aa = pyp11.loadmodule('/usr/local/lib64/libls11sw2016.so')lcerts = pyp11.listcerts(aa, 0)if (len(lcerts) == 0):    print ('На токене нет сертификатов')    quit()#Перебираем сертификатыfor cert in lcerts:    #Информация о сертификате    for key in cert:        print (key + ': ' + cert[key])#Сравним с pyp11.listobjectslm = pyp11.listobjects(aa, 0, 'cert', 'value')print('Работа с listobjects:')for obj in lm:    for key in obj:        print (key + ': ' + obj[key])quit()

Команды pyp11.listobjects для сертификатов и команда pyp11.listcerts фактически дублируют друг друга, но так сложилось исторически.

Заключение


Опыт использования аналогичного модуля tclpkcs11 показывает, что функциональности, заложенной в модуль pyp11 для Python, с лихвой хватит для его использования в ИОК для работы с электронной подписью на базе российской криптографии. Более того, во второй части статьи будет рассмотрен класс token, в рамках которого будут создаваться объекты для подключенных токенов. И это позволит ещё больше упростить работу с токенами. Кстати, аналогичный класс для tclpkcs11 уже имеется.
Но в заключении я хотел бы вернуться к началу статьи, а именно к проекту PyKCS11.
Когда я писал письмо авторам проекта PyKCS11, то я уже добавил в него поддержку российской криптографии и сообщал им об этом:



Сейчас заканчивается тестирования PyKCS11 для российской криптографии. Кстати, модуль pyp11 хорошо дополняет PyPCS11. Поэтому должна появиться и третья часть статьи, в которой будет рассказано, как добавить поддержку российской криптографии в проект PyKCS11.
Подробнее..

Поддержка токенов PKCS11 с ГОСТ-криптографией в Python. Часть II Объекты класса Token

18.03.2021 18:13:59 | Автор: admin
imageВ предыдущей статье был представлен модуль pyp11, написанный на языке Си и обеспечивающий поддержку токенов PKCS#11 с российской криптографией. В этой статье будет рассмотрен класс Token, который позволит упростить использование функционала модуля pyp11 в скриптах, написанных на Python-е. Отметим, что в качестве прототипа этого класса был взят класс token, написанный на TclOO и который используется в утилите cryptoarmpkcs:


Прототип класса Token
oo::class create token {  variable libp11  variable handle  variable infotok  variable pintok  variable nodet#Конструктор  constructor {handlelp11 labtoken slottoken} {    global pass    global yespas    set handle $handlelp11    set slots [pki::pkcs11::listslots $handle]    array set infotok []    foreach slotinfo $slots {      set slotflags [lindex $slotinfo 2]      if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} {        if {[string first $labtoken [lindex $slotinfo 1]] != -1} {          set infotok(slotlabel) [lindex $slotinfo 1]          set infotok(slotid) [lindex $slotinfo 0]          set infotok(slotflags) [lindex $slotinfo 2]          set infotok(token) [lindex $slotinfo 3]          #Берется наш токен          break        }      }    }    #Список найденных токенов в слотах    if {[llength [array names infotok]] == 0 } {      error "Constructor: Token not present for library   : $handle"    }    #Объект какого токена    set nodet [dict create pkcs11_handle $handle]    dict set nodet pkcs11_slotid $infotok(slotid)    set tit "Введите PIN-код для токене $infotok(slotlabel)"    set xa [my read_password $tit]    if {$xa == "no"} {      error "Вы передумали вводить PIN для токена $infotok(slotlabel)"    }    set pintok $pass    set pass ""    set rr [my login ]    if { $rr == 0 } {      unset pintok      error "Проверьте PIN-код токена $infotok(slotlabel)."    } elseif {$rr == -1} {      unset pintok      error "Отсутствует токен."    }    my logout  }#Методы класса  method infoslot {} {    return [array get infotok]  }  method listcerts {} {    array set lcerts []    set certsder [pki::pkcs11::listcertsder $handle $infotok(slotid)]    #Перебираем сертификаты    foreach lc $certsder {      array set derc $lc      set lcerts($derc(pkcs11_label)) [list $derc(cert_der) $derc(pkcs11_id)]      #parray derc    }    return [array get lcerts]  }  method read_password {tit} {    global yespas    global pass    set tit_orig "$::labpas"    if {$tit != ""} {      set ::labpas "$tit"    }    tk busy hold ".st.fr1"    tk busy hold ".st.fr3"    #place .topPinPw -in .st.fr1.fr2_certs.labCert  -relx 1.0 -rely 3.0 -relwidth 3.5    place .topPinPw -in .st.labMain  -relx 0.35 -rely 5.0 -relwidth 0.30    set yespas ""    focus .topPinPw.labFrPw.entryPw    vwait yespas    catch {tk busy forget ".st.fr1"}    catch {tk busy forget ".st.fr3"}    if {$tit != ""} {      set ::labpas "$tit_orig"    }    place forget .topPinPw    return $yespas  }  unexport read_password  method rename {type ckaid newlab} {    if {$type != "cert" && $type != "key" && $type != "all"} {      error "Bad type for rename "    }    set uu $nodet    lappend uu "pkcs11_id"    lappend uu $ckaid    lappend uu "pkcs11_label"    lappend uu $newlab    if { [my login ] == 0 } {      unset uu      return 0    }    pki::pkcs11::rename $type $uu    my logout    return 1  }  method changeid {type ckaid newid} {    if {$type != "cert" && $type != "key" && $type != "all"} {      error "Bad type for changeid "    }    set uu $nodet    lappend uu "pkcs11_id"    lappend uu $ckaid    lappend uu "pkcs11_id_new"    lappend uu $newid    if { [my login ] == 0 } {      unset uu      return 0    }    pki::pkcs11::rename $type $uu    my logout    return 1  }  method delete {type ckaid} {    if {$type != "cert" && $type != "key" && $type != "all" && $type != "obj"} {      error "Bad type for delete"    }    set uu $nodet    lappend uu "pkcs11_id"    lappend uu $ckaid    my login    ::pki::pkcs11::delete $type $uu    my logout    return 1  }  method deleteobj {hobj} {    set uu $nodet    lappend uu "hobj"    lappend uu $hobj#tk_messageBox -title "class deleteobj" -icon info -message "hobj: $hobj\n" -detail "$uu"    return [::pki::pkcs11::delete obj $uu ]  }  method listmechs {} {    set llmech [pki::pkcs11::listmechs $handle $infotok(slotid)]    return $llmech  }  method pubkeyinfo {cert_der_hex} {    array set linfopk [pki::pkcs11::pubkeyinfo $cert_der_hex $nodet]    return [array get linfopk]  }  method listobjects {type} {    if {$type != "cert" && $type != "pubkey" && $type != "privkey" && $type != "all" && $type != "data"} {      error "Bad type for listobjects "    }    set allobjs [::pki::pkcs11::listobjects $handle $infotok(slotid) $type]    return $allobjs  }  method importcert {cert_der_hex cka_label} {    set uu $nodet    dict set uu pkcs11_label $cka_label    if {[catch {set pkcs11id [pki::pkcs11::importcert $cert_der_hex $uu]} res] } {      error "Cannot import this certificate:$res"      #          return 0    }    return $pkcs11id  }  method login {} {    set wh 1    set rl -1    while {$wh == 1} {      if {[catch {set rl [pki::pkcs11::login $handle $infotok(slotid) $pintok]} res]} {        if {[string first "SESSION_HANDLE_INVALID" $res] != -1} {          pki::pkcs11::closesession $handle          continue        } elseif {[string first "TOKEN_NOT_PRESENT" $res] != -1} {          set wh 0          continue        }      }      break    }    if {$wh == 0} {      return -1    }    return $rl  }  method logout {} {    return [pki::pkcs11::logout $handle $infotok(slotid)]  }  method keypair {typegost parkey} {    my login    set skey [pki::pkcs11::keypair $typegost $parkey $nodet]    my logout    return $skey  }  method digest {typehash source} {    return [pki::pkcs11::digest $typehash $source $nodet]  }  method signkey {ckm digest hobj_priv} {    set uu $nodet    dict set uu hobj_privkey $hobj_priv    my login    set ss [pki::pkcs11::sign $ckm $digest $uu]    my logout    return $ss  }  method signcert {ckm digest pkcs11_id} {    set uu $nodet    dict set uu pkcs11_id $pkcs11_id    my login    set ss  [pki::pkcs11::sign $ckm $digest $uu]    my logout    return $ss  }  method verify {digest signature asn1pubkey} {    set uu $nodet    dict set uu pubkeyinfo $asn1pubkey    return [pki::pkcs11::verify $digest $signature $uu]  }  method tokenpresent {} {    set slots [pki::pkcs11::listslots $handle]    foreach slotinfo $slots {      set slotid [lindex $slotinfo 0]      set slotlabel [lindex $slotinfo 1]      set slotflags [lindex $slotinfo 2]      if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} {        if {infotok(slotlabel) == $slotlabel && $slotid == $infotok(slotid)} {          return 1        }      }    }    return 0  }  method setpin {type tpin newpin} {    if {$type != "user" && $type != "so"} {      return 0    }    if {$type == "user"} {      if {$tpin != $pintok} {        return 0      }    }    set ret [::pki::pkcs11::setpin  $handle $infotok(slotid) $type $tpin $newpin]    if {$type == "user"} {      if {$ret} {        set pitok $newpin      }    }    return $ret  }  method inituserpin {sopin upin} {    set ret [::pki::pkcs11::inituserpin $handle $infotok(slotid) $sopin $upin]    return $ret  }  method importkey {uukey} {    set uu $nodet    append uu " $uukey"    my login    if {[catch {set impkey [pki::pkcs11::importkey $uu ]} res] } {        set impkey 0    }    my logout    return $impkey  }#Деструктор  destructor {    variable handle    if {[info exists pintok]} {      my login    }    #    ::pki::pkcs11::unloadmodule  $handle  }}

Класс Token, как и любой класс в Python, включает конструктор, методы и деструктор. Конструктор и деструктор это те же методы, только с предопределёнными именами. Конструктор имеет имя __init__, а деструктор имя __del__. Объявление конструктора и деструктора можно опускать. И в классе Token мы опустим объявление деструктора, а вот конструктор будет необходим. Конструктор будет создавать экземпляр класса Token для конкретного токена с конкретными атрибутами.

I. Конструктор класса Token


Итак, конструктор класса Token выглядит следующим образом:
import sysimport pyp11class Token:  def __init__ (self, handlelp11, slottoken, serialnum):    flags = ''    self.pyver = sys.version[0]    if (self.pyver == '2'):        print ('Только для python3')        quit()#Сохраняем handle библиотеки PKCS#11    self.handle = handlelp11#Сохраняем номер слота с токеном    self.slotid = slottoken#Сохраняем серийный номер токена    self.sn = serialnum#Проверяем наличие в указанном слоте токена с заданным серийным номером    ret, stat = self.tokinfo()#Проверяем код возврата    if (stat != ''):#Возвращаем информацию об ошибке        self.returncode = stat        return#Экземпляр класса (объект) успешно создан


Параметрами конструктора (метода __init__) являются (помимо обязательного self) handle библиотеки токена (handlelp11), номер слота (slottoken), в котором должен находиться токен, и серийный номер токена (serialnum).
Для получения handle библиотеки pkcs#11, номеров слотов и информации о находящихся в них токенах можно использовать следующий скрипт:
#!/usr/bin/python3import sysimport pyp11from Token import Tokendef listslots (handle):    slots = pyp11.listslots(aa)    i = 0    lslots = []    for v in slots:        for f in v[2]:        if (f == 'TOKEN_PRESENT'):                i = 1                lslots.append(v)                break    i += 1    return (lslots)#Библиотеки для Linux#Программный токенlib = '/usr/local/lib64/libls11sw2016.so'#Облачный токен#lib = '/usr/local/lib64/libls11cloud.so'#Аппаратный токен#lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'#Библиотеки для Windows#lib='C:\Temp\ls11sw2016.dll'try:#Вызываем команду загрузки библиотеки и получаем её handle (дескриптор библиотеки)    aa = pyp11.loadmodule(lib)    print('Handle библиотеки ' + lib + ': ' + aa)except:    print('Except load lib: ')    e = sys.exc_info()[1]    e1 = e.args[0]#Печать ошибки    print (e1)    quit()#Список слотовslots = listslots(aa)i = 0for v in slots:    for f in v[2]:        if (f == 'TOKEN_PRESENT'):        if (i == 0):            print ('\nИнформация о токенах в слотах\n')        it = v[3]        print ('slotid=' + str(v[0]))        print ('\tFlags=' + str(v[2]))        print ('\tLabel="' + it[0].strip() + '"')        print ('\tManufacturer="' + it[1].strip() + '"')        print ('\tModel="' + it[2].strip() + '"')        print ('\tSerialNumber="' + it[3].strip() + '"')        i = 1        break    i += 1pyp11.unloadmodule(aa)if (i == 0):    print ('Нет ни одного подключенного токена. Вставьте токен и повторите операцию')quit()

Если с библиотекой и слотом всё ясно, то с серийным номером токена может возникнуть вопрос а зачем этот параметр нужен и почему именно он, а, например, не метка токена. Сразу оговоримся, что это принципиально для извлекаемых токенов, когда злоумышленником (или случайно) один токен в слоте будет заменён другим токеном. Более того, различные экземпляры токена могут иметь одинаковые метки. И наконец, токен может быть еще не проинициализирован или владелец будет его переинициализировать, в частности, сменит метку токена. Теоретически даже серийный номер не гарантирует его идентичность, оптимально учитывать всю информацию о токене (серийный номер, модель, производитель). В задачи конструктора и входит сохранение в переменных создаваемого экземпляра класса аргументов объекта токен:
...#Сохраняем handle библиотеки PKCS#11    self.handle = handlelp11#Сохраняем номер слота с токеном    self.slotid = slottoken#Сохраняем серийный номер токена    self.sn = serialnum...

Проверкой наличия указанного токена в указанном слоте занимается метод tokinfo(), определенный в данном классе.
Метод tokinfo возвращает два значения (см. выше в конструкторе):
#Проверяем наличие в указанном слоте токена с заданным серийным номером    ret, stat = self.tokinfo()

В первой переменной (ret) содержится результат выполнения метода, а во второй (stat) информация о том, как завершилось выполнение метода. Если вторая переменная пуста, то метод tokinfo успешно выполнился. Если вторая переменная не пуста, то выполнение метода завершилось с ошибкой. Информация об ошибке будет находиться в этой переменной. При обнаружении ошибки выполнения метода self.tokinfo конструктор записывает её в переменную returncode:
#Проверяем наличие в указанном слоте токена с заданным серийным номером    ret, stat = self.tokinfo()#Проверяем код возврата    if (stat != ''):#Возвращаем информацию об ошибке в переменной returncode        self.returncode = stat        return

После создания объекта (экземпляра класса) необходимо проверить значение переменной returncode, чтобы быть уверенным в том, что объект для указанного токена создан:
#!/usr/bin/python3import sysimport pyp11from Token import Token#Выбираем библиотеку#Аппаратный токенlib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'try:    aa = pyp11.loadmodule(lib)except:    e = sys.exc_info()[1]    e1 = e.args[0]    print (e1)    quit()#Серийный номер токенаsn = '9999999999999999'slot = 110#Создаем объект токенаt1 = Token(aa, slot, sn)#Проверка переменной returncodeif (t1.returncode != ''):#Объект создан с ошибкой    print (t1.returncode)#Уничтожение объекта    del t1#Завершение скрипта    quit()#объект успешно создан. . .

Если обнаружена ошибка при создании объекта, то целесообразно этот объект уничтожить:
del <идентификатор объекта>

II. Архитектура методов в классе Token


Главным принципом при написании методов было то, чтобы обработка исключений была внутри методов, а информация об исключениях (ошибках) возвращалась в текстовом виде. Исходя из этого все методы возвращают два значения: собственно результат выполнения и информацию об ошибке. Если ошибок нет, то второе значение пустое. Мы это уже видели на примере использования метода tokinfo в конструкторе. А вот и сам код метода tokinfo:
  def tokinfo(self):    status = ''#Получаем список слотов    try:        slots = pyp11.listslots(self.handle)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        dd = ''        status = e1        return (dd, status)    status = ''#Ищем заданный слот с указанным  токеном#Перебираем слоты    for v in slots:#Ищем заданный слот            if (v[0] != self.slotid):                status = "Ошибочный слот"                continue            self.returncode = ''#Список флагов текущего слота            self.flags = v[2]#Проверяем наличие в стоке токена            if (self.flags.count('TOKEN_PRESENT') !=0):#Проверяем серийный номер токена                tokinf = v[3]                sn = tokinf[3].strip()                if (self.sn != sn):                    status = 'Серийный номер токена=\"' + sn + '\" не совпадает с заданным \"' + self.sn + '\"'                    dd = ''                    return (dd, status)                status = ''                break            else:                dd = ''                status = "В слоте нет токена"                return (dd, status)    tt = tokinf    dd = dict(Label=tt[0].strip())    dd.update(Manufacturer=tt[1].strip())    dd.update(Model=tt[2].strip())    dd.update(SerialNumber=tt[3].strip())    self.infotok = dd#Возвращаемые значения    return (dd, status)

Полное описание класса Token находится здесь.
import sysimport pyp11class Token:  def __init__ (self, handlelp11, slottoken, serialnum):    flags = ''    self.pyver = sys.version[0]    if (self.pyver == '2'):        print ('Только для python3')        quit()#Сохраняем handle библиотеки PKCS#11    self.handle = handlelp11#Сохраняем номер слота с токеном    self.slotid = slottoken#Сохраняем серийный номер токена    self.sn = serialnum#Проверяем наличие в указанном слоте с токена с заданным серийным номером    ret, stat = self.tokinfo()#Проверяем код возврата    if (stat != ''):#Возвращаем информацию об ошибке        self.returncode = stat        return#Экземпляр класса (объект) успешно создан  def tokinfo(self):    status = ''#Получаем список слотов    try:        slots = pyp11.listslots(self.handle)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        dd = ''        status = e1        return (dd, status)    status = ''#Ищем заданный слот с указанным  токеном#Перебираем слоты    for v in slots:#Ищем заданный слот            if (v[0] != self.slotid):                status = "Ошибочный слот"                continue            self.returncode = ''#Список флагов текущего слота            self.flags = v[2]#Проверяем наличие в стоке токена            if (self.flags.count('TOKEN_PRESENT') !=0):#Проверяем серийный номер токена                tokinf = v[3]                sn = tokinf[3].strip()                if (self.sn != sn):                    status = 'Серийный номер токена=\"' + sn + '\" не совпадает с заданным \"' + self.sn + '\"'                    dd = ''                    return (dd, status)                status = ''                break            else:                dd = ''                status = "В слоте нет токена"                return (dd, status)    tt = tokinf    dd = dict(Label=tt[0].strip())    dd.update(Manufacturer=tt[1].strip())    dd.update(Model=tt[2].strip())    dd.update(SerialNumber=tt[3].strip())    self.infotok = dd    return (dd, status)  def listcerts(self):    try:        status = ''        lcerts = pyp11.listcerts(self.handle, self.slotid)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        lcerts = ''        status = e1    return (lcerts, status)  def listobjects(self, type1, value = '' ):    try:        status = ''        if (value == ''):        lobjs = pyp11.listobjects(self.handle, self.slotid, type1)        else:        lobjs = pyp11.listobjects(self.handle, self.slotid, type1, value)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        lobjs = ''        status = e1    return (lobjs, status)  def rename(self, type, pkcs11id, label):    try:        status = ''        dd = dict(pkcs11_id=pkcs11id, pkcs11_label=label)        ret = pyp11.rename(self.handle, self.slotid, type, dd)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        ret = ''        status = e1    return (ret, status)  def changeckaid(self, type, pkcs11id, pkcs11idnew):    try:        status = ''        dd = dict(pkcs11_id=pkcs11id, pkcs11_id_new=pkcs11idnew)        ret = pyp11.rename(self.handle, self.slotid, type, dd)    except:#Проблемы с библиотекой токена        e = sys.exc_info()[1]        e1 = e.args[0]        ret = ''        status = e1    return (ret, status)  def login(self, userpin):    try:        status = ''        bb = pyp11.login (self.handle, self.slotid, userpin)    except:        e = sys.exc_info()[1]        e1 = e.args[0]        bb = 0        status = e1    return (bb, status)  def logout(self):    try:        status = ''        bb = pyp11.logout (self.handle, self.slotid)    except:        e = sys.exc_info()[1]        e1 = e.args[0]        bb = 0        status = e1    return (bb, status)  def keypair(self, typek, paramk, labkey):#Параметры для ключей    gost2012_512 = ['1.2.643.7.1.2.1.2.1', '1.2.643.7.1.2.1.2.2', '1.2.643.7.1.2.1.2.3']    gost2012_256 = ['1.2.643.2.2.35.1', '1.2.643.2.2.35.2',  '1.2.643.2.2.35.3',  '1.2.643.2.2.36.0', '1.2.643.2.2.36.1', '1.2.643.7.1.2.1.1.1', '1.2.643.7.1.2.1.1.2', '1.2.643.7.1.2.1.1.3', '1.2.643.7.1.2.1.1.4']    gost2001 = ['1.2.643.2.2.35.1', '1.2.643.2.2.35.2',  '1.2.643.2.2.35.3',  '1.2.643.2.2.36.0', '1.2.643.2.2.36.1']#Тип ключа    typekey = ['g12_256', 'g12_512', 'gost2001']    genkey = ''    if (typek == typekey[0]):    gost = gost2012_256    elif (typek == typekey[1]):    gost = gost2012_512    elif (typek == typekey[2]):    gost = gost2001    else:    status = 'Неподдерживаемый тип ключа'    return (genkey, status)    if (gost.count(paramk) == 0) :    status = 'Неподдерживаемые параметры ключа'    return (genkey, status)    try:#Ошибок нет, есть ключевая пара    status = ''    genkey = pyp11.keypair(self.handle, self.slotid, typek, paramk, labkey)    except:#Не удалось создать ключевую пару    e = sys.exc_info()[1]    e1 = e.args[0]    print (e1)#Возвращаеи текст ошибки в словаре    status = e1    return (genkey, status)     def digest(self, typehash, source):#Считаем хэш    try:        status = ''        digest_hex = pyp11.digest (self.handle, self.slotid, typehash, source)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в словаре    status = e1    digest_hex = ''    return (digest_hex, status)#Формирование подписи  def sign(self, ckmpair, digest_hex, idorhandle):#Для подписи можно использовать CKA_ID или handle закрытого ключа    try:        status = ''        sign_hex = pyp11.sign(self.handle, self.slotid, ckmpair, digest_hex, idorhandle)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в словаре    status = e1    sign_hex = ''    return (sign_hex, status)#Проверка подписи  def verify(self, digest_hex, sign_hex, pubkeyinfo):#Для подписи можно использовать CKA_ID или handle закрытого ключа    try:        status = ''        verify = pyp11.verify(self.handle, self.slotid, digest_hex, sign_hex, pubkeyinfo)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    verify = 0    status = e1    return (verify, status)#Инициализировать токен  def inittoken(self, sopin, labtoken):    try:        status = ''        dd = pyp11.inittoken (self.handle, self.slotid, sopin, labtoken)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    return (dd, status)#Инициализировать пользовательский PIN-код  def inituserpin(self, sopin, userpin):    try:        status = ''        dd = pyp11.inituserpin (self.handle, self.slotid, sopin, userpin)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    return (dd, status)#Сменить пользовательский PIN-код  def changeuserpin(self, oldpin, newpin):    try:        status = ''        dd = pyp11.setpin (self.handle, self.slotid, 'user', oldpin, newpin)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    self.closesession ()    return (dd, status)  def closesession(self):    try:        status = ''        dd = pyp11.closesession (self.handle)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = 0    status = e1    return (dd, status)  def parsecert(self, cert_der_hex):    try:        status = ''        dd = pyp11.parsecert (self.handle, self.slotid, cert_der_hex)    except:#Не удалось разобрать сертификат    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def importcert(self, cert_der_hex, labcert):    try:        status = ''        dd = pyp11.importcert (self.handle, self.slotid, cert_der_hex, labcert)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def delobject(self, hobject):    try:        status = ''        hobjc = dict(hobj=hobject)        dd = pyp11.delete(self.handle, self.slotid, 'obj', hobjc)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def delete(self, type, pkcs11id):    if (type == 'obj'):        dd = ''        status = 'delete for type obj use nethod delobject'        return (dd, status)    try:        status = ''        idobj = dict(pkcs11_id=pkcs11id)        dd = pyp11.delete(self.handle, self.slotid, type, idobj)    except:    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)  def listmechs(self):    try:        status = ''        dd = pyp11.listmechs (self.handle, self.slotid)    except:#Не удалось получить список механизмов токена    e = sys.exc_info()[1]    e1 = e.args[0]#Возвращаеи текст ошибки в status    dd = ''    status = e1    return (dd, status)


Рассмотрим импользование функционала модуля pyp11 и аналогичных операторов с использованием класса Token.
В последнем случае необходимо будет создать и объект токена:
<дескриптор объекта> = Token(<дескриптор библиоткети>, <номер слота>, <серийный номер>)if (<дескриптор объекта>.returncode != ''):   print('Ошибка при создании объекта:')#Печать ошибки   print(<дескриптор объекта>.returncode)#Уничтожение объекта   del <дескриптор объекта>   quit()

Начнем с инициализации токена:
try:    ret = pyp11.inittoken (<дескриптор библиоткети>, <номер слота>, <SO-PIN>, <метка токена>)except:#Не удалось проинициализировать токен    e = sys.exc_info()[1]    e1 = e.args[0]    print (e1)    quit()

Аналогичный код при использовании класса Token выглядит так (идентификатор объекта t1):
ret, stat = t1.inittoken(<SO-PIN>, <метка токена>)#Проверка корретности инициализацииif (stat != ''):   print('Ошибка при инициализации токена:')#Печать ошибки   print(stat)   quit()  

Далее мы просто дадим соответствие основных операторов модуля pyp11 и методов класса Token без обработки исключений и ошибок:
<handle> := <дескриптор библиотеки pkcs11><slot> := <дескриптор слота с токеном><error> := <переменная с текстом ошибки><ret> := <результат выполнения оператора><cert_der_hex> := <сертификат в DER-формате в HEX-кодировке>=================================================#Инициализация пользовательского PIN-кода<ret> = pyp11.inituserpin (<handle>, <slot>, <SO-PIN>, <USER-PIN>)<ret>, <error> = <идентификатор объекта>.inituserpin (<SO-PIN>, <USER-PIN>)#Смена USER-PIN кода<ret> = pyp11.setpin (<handle>, <slot>, 'user', <USER-PIN старый>, <USER-PIN новый>)<ret>, <error> = t1.changeuserpin (<USER-PIN старый>, <USER-PIN новый>)#Смена SO-PIN кода<ret> = pyp11.setpin (<handle>, <slot>, 'so', <SO-PIN старый>, <SO-PIN новый>)<ret>, <error> = t1.changesopin (<SO-PIN старый>, <SO-PIN новый>)#Login<ret> = pyp11.login (<handle>, <slot>, <USER-PIN>)<ret>, <error> = t1.login (<USER-PIN>)#Logout<ret> = pyp11.logout (<handle>, <slot>)<ret>, <error> = t1.logout ()#Закрытие сессии<ret> = pyp11.closesession (<handle>)<ret>, <error> = t1.closesession ()#Список сертификатов на токене<ret> = pyp11.listcerts (<handle>, <slot>)<ret>, <error> = t1.listcerts ()#Список объектов на токене<ret> = pyp11.listobjects (<handle>, <slot>, <'cert' | 'pubkey' | 'privkey' | 'data' | 'all'> [, 'value'])<ret>, <error> = t1.listobjects (<'cert' | 'pubkey' | 'privkey' | 'data' | 'all'> [, 'value'])#Разбор сертификата<ret> = pyp11.parsecert (<handle>, <slot>, <cert_der_hex>)<ret>, <error> = t1.parsecert(<cert_der_hex>)#Импорт сертификата<ret> = pyp11.importcert (<handle>, <slot>, <cert_der_hex>, <Метка сертификата>)<ret>, <error> = t1.importcert(<cert_der_hex>, <Метка сертификата>)#Вычисление хэша<ret> = pyp11.digest (<handle>, <slot>, <тип алгоритма>, <контент>)<ret>, <error> = t1.digest(<тип алгоритма>, <контент>)#Вычисление электронной подписи<ret> = pyp11.digest (<handle>, <slot>, <механизм подписи>, <хэш от контента>, <CKA_ID | handle закрытого ключа>)<ret>, <error> = t1.digest(<механизм подписи>, <хэш от контента>, <CKA_ID | handle закрытого ключа>)#Проверка электронной подписи<ret> = pyp11.verify (<handle>, <slot>, <хэш от контента>, <подпись>, <asn1-структура subjectpublickeyinfo в hex>)<ret>, <error> = t1.verify(<хэш от контента>, <подпись>, <asn1-структура subjectpublickeyinfo в hex>)#Генерация ключевой пары<ret> = pyp11.keypair (<handle>, <slot>, <тип ключа>, <OID криптопараметра>, <CKA_LABEL>)<ret>, <error> = t1.keypair(<тип ключа>, <OID криптопараметра>, <CKA_LABEL>)

III. Сборка и установка модуля pyp11 с классом Token


Сборка и установка модуля pyp11 с классом Token ничем не отличается от описанной в первой части.
Итак, скачиваем архив и распаковываем его. Заходим в папку PythonPKCS11 и выполняем команду установки:
python3 setup.py install

После установки модуля переходим в папку tests и запускаем тесты для модуля pyp11.
Для тестирования класса Token переходим в папку test/classtoken.
Для подключения модуля pyp11 и класса Token в скрипты достаточно добавить следующие операторы:
import pyp11from Token import Token


IV. Заключение


В ближайшее время должна появиться и третья часть статьи, в которой будет рассказано, как добавить поддержку российской криптографии в проект PyKCS11.

P.S. Хочу сказать спасибо svyatikov за то, что помог протестировать проект на платформе Windows.
Подробнее..

Поддержка токенов PKCS11 с ГОСТ-криптографией в Python. Часть II Обёртка PyKCS11

26.03.2021 18:19:00 | Автор: admin
image Подошло время рассказать как была добавлена поддержка поддержка российской криптографии в проект PyKCS11. Всё началось с того, что мне на глаза попалась переписка разработчика проекта PyKCS11 с потенциальными потребителями по поводу возможной поддержки алгоритмов ГОСТ Р 34.10-2012 в нём. В этой переписке автор PkCS11 сказал, что не собирается включать поддержку российских криптоалгоритмов до тех пор, пока они не будут стандартизованы.
Ту же самую мысль он выразил и мне, когда я предложил ему это сделать. И не просто сделать, а выслал соответствующий программный код:

image

После этого я посчитал возможным форкнуть код в свой репозиторий и внести в него соответствующие правки. Проект PyKCS11 с поддержкой российской криптографии находится здесь.

I. Добавляем поддержку российских криптоалгоритмов


Итак, что же было сделано. Я фактически последовал одному из советов автора проекта PyKCS11:
What I can propose you is to create a PyKCS11_GOST.py file with the constant names and functions you want in order to extend PyKCS11 with GOST support.
(Я могу предложить вам создать файл PyKCS11_GOST.py с именами констант и функциями, которыми вы хотите расширить PyKCS11 для поддержки ГОСТ.)

Все константы, утвержденные ТК-26 для PKCS#11, были сведены в один файл pkcs11t_gost.h, помещенный в папку src:
//ТК-26#define NSSCK_VENDOR_PKCS11_RU_TEAM 0xd4321000 #define NSSCK_VENDOR_PKSC11_RU_TEAM NSSCK_VENDOR_PKCS11_RU_TEAM#define CK_VENDOR_PKCS11_RU_TEAM_TC26 NSSCK_VENDOR_PKCS11_RU_TEAM#define CKK_GOSTR3410_512 0xd4321003UL#define CKK_KUZNYECHIK 0xd4321004UL#define CKK_MAGMA 0xd4321005UL#define CKK_GOSTR3410_256 0xd4321006UL#define CKP_PKCS5_PBKD2_HMAC_GOSTR3411_TC26_V1 0xd4321801UL#define CKP_PKCS5_PBKD2_HMAC_GOSTR3411_2012_256 0xd4321002UL#define CKP_PKCS5_PBKD2_HMAC_GOSTR3411_2012_512 0xd4321003UL#define CKM_GOSTR3410_512_KEY_PAIR_GEN0xd4321005UL#define CKM_GOSTR3410_5120xd4321006UL#define CKM_GOSTR3410_WITH_GOSTR34110x00001202#define CKM_GOSTR3410_WITH_GOSTR3411_12_2560xd4321008UL#define CKM_GOSTR3410_WITH_GOSTR3411_12_5120xd4321009UL#define CKM_GOSTR3410_12_DERIVE0xd4321007UL#define CKM_GOSR3410_2012_VKO_2560xd4321045UL#define CKM_GOSR3410_2012_VKO_5120xd4321046UL#define CKM_KDF_43570xd4321025UL#define CKM_KDF_GOSTR3411_2012_2560xd4321026UL#define CKM_KDF_TREE_GOSTR3411_2012_2560xd4321044UL#define CKM_GOSTR3410_PUBLIC_KEY_DERIVE0xd432100AUL#define CKM_LISSI_GOSTR3410_PUBLIC_KEY_DERIVE0xd4321037UL#define CKM_GOST_GENERIC_SECRET_KEY_GEN0xd4321049UL#define CKM_GOST_CIPHER_KEY_GEN0xd4321048UL#define CKM_GOST_CIPHER_ECB0xd4321050UL#define CKM_GOST_CIPHER_CBC0xd4321051UL#define CKM_GOST_CIPHER_CTR0xd4321052UL#define CKM_GOST_CIPHER_OFB0xd4321053UL#define CKM_GOST_CIPHER_CFB0xd4321054UL#define CKM_GOST_CIPHER_OMAC0xd4321055UL#define CKM_GOST_CIPHER_KEY_WRAP0xd4321059UL#define CKM_GOST_CIPHER_ACPKM_CTR0xd4321057UL#define CKM_GOST_CIPHER_ACPKM_OMAC0xd4321058UL#define CKM_GOST28147_PKCS8_KEY_WRAP0xd4321036UL#define CKM_GOST_CIPHER_PKCS8_KEY_WRAP0xd432105AUL#define CKM_GOST28147_CNT0xd4321825UL#define CKM_KUZNYECHIK_KEY_GEN0xd4321019UL#define CKM_KUZNYECHIK_ECB0xd432101AUL#define CKM_KUZNYECHIK_CBC0xd432101EUL#define CKM_KUZNYECHIK_CTR0xd432101BUL#define CKM_KUZNYECHIK_OFB0xd432101DUL#define CKM_KUZNYECHIK_CFB0xd432101CUL#define CKM_KUZNYECHIK_OMAC0xd432101FUL#define CKM_KUZNYECHIK_KEY_WRAP0xd4321028UL#define CKM_KUZNYECHIK_ACPKM_CTR0xd4321042UL#define CKM_KUZNYECHIK_ACPKM_OMAC0xd4321043UL#define CKM_MAGMA_KEY_GEN0xd432102AUL#define CKM_MAGMA_ECB0xd4321018UL#define CKM_MAGMA_CBC0xd4321023UL#define CKM_MAGMA_CTR0xd4321020UL#define CKM_MAGMA_OFB0xd4321022UL#define CKM_MAGMA_CFB0xd4321021UL#define CKM_MAGMA_OMAC0xd4321024UL#define CKM_MAGMA_KEY_WRAP0xd4321029UL#define CKM_MAGMA_ACPKM_CTR0xd4321040UL#define CKM_MAGMA_ACPKM_OMAC0xd4321041UL#define CKM_GOSTR3411_12_2560xd4321012UL#define CKM_GOSTR3411_12_5120xd4321013UL#define CKM_GOSTR3411_12_256_HMAC0xd4321014UL#define CKM_GOSTR3411_12_512_HMAC0xd4321015UL#define CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC0xd4321035UL#define CKM_TLS_GOST_KEY_AND_MAC_DERIVE0xd4321033UL#define CKM_TLS_GOST_PRE_MASTER_KEY_GEN0xd4321031UL#define CKM_TLS_GOST_MASTER_KEY_DERIVE0xd4321032UL#define CKM_TLS_GOST_PRF0xd4321030UL#define CKM_TLS_GOST_PRF_2012_2560xd4321016UL#define CKM_TLS_GOST_PRF_2012_5120xd4321017UL#define CKM_TLS_TREE_GOSTR3411_2012_2560xd4321047UL

В этот перечень вошли механизмы как необходимые для формирования и проверки подписи по (ГОСТ Р 34.10-2012) ГОСТ Р 34.10-2012, так и шифрования (ГОСТ Р 34.12-2015 и ГОСТ Р 34.13-2015 алгоритмы шифрования Кузнечик и Магма). Естественно, здесь же присутствуют и алгоритмы хэширования ГОСТ Р 34.11-2012.
Для того, чтобы ГОСТ-овые константы попали в процесс сборки модуля, необходимо добавить в файл pkcs11.i (файл для SWIG) оператор включения файла pkcs11t_gost.h
%include "pkcs11t_gost.h"

перед оператором
%include "pkcs11lib.h"

Но это еще не всё. В методе getMechanismList (script PKCS11/__init__.py) заблокирован вывод механизмов чей код больше CKM_VENDOR_DEFINED (именно об этом и пишет автор проекта PyKCS11) (0x80000000L). Заметим, что ГОСТ-овые константы для новых алгоритмов попадают под это ограничение. Необходимо его снять хотя бы для ГОСТ-ов, заменим код метода getMechanismList на новый:
    def getMechanismList(self, slot):        """        C_GetMechanismList        :param slot: slot number returned by :func:`getSlotList`        :type slot: integer        :return: the list of available mechanisms for a slot        :rtype: list        """        mechanismList = PyKCS11.LowLevel.ckintlist()        rv = self.lib.C_GetMechanismList(slot, mechanismList)        if rv != CKR_OK:            raise PyKCS11Error(rv)        m = []#Правки для ГОСТ#define NSSCK_VENDOR_PKCS11_RU_TEAM 0xd4321000         for x in range(len(mechanismList)):            mechanism = mechanismList[x]            if mechanism >= CKM_VENDOR_DEFINED:                if mechanism >= CKM_VENDOR_DEFINED and mechanism < 0xd4321000:                    k = 'CKM_VENDOR_DEFINED_0x%X' % (mechanism - CKM_VENDOR_DEFINED)                    CKM[k] = mechanism                    CKM[mechanism] = k            m.append(CKM[mechanism])        return m#ORIGINAL#        for x in range(len(mechanismList)):#            mechanism = mechanismList[x]#            if mechanism >= CKM_VENDOR_DEFINED:#                k = 'CKM_VENDOR_DEFINED_0x%X' % (mechanism - CKM_VENDOR_DEFINED)#                CKM[k] = mechanism#                CKM[mechanism] = k#            m.append(CKM[mechanism])#        return m


Отметим также, что несмотря на то, что в модуль включены все механизмы, которые определены во включаемых файлах pkcs11t.h и pkcs11t_gost.h для pkcs11 v.2.40, все эти механизмы могут быть выполнены. Проблема состоит в том, что для некоторых из них требуется определенная структура параметров. Это, в частности, относится к механизму CKM_RSA_PKCS_OAEP, которому требуются параметры в виде структуры CK_RSA_PKCS_OAEP_PARAMS, и механизму CKM_PKCS5_PBKD2, который ждет параметров в виде структуры CK_PKCS5_PBKD2_PARAMS. Есть и другие механизмы. Но поскольку автор реализовал отдельные структуры для отдельных механизмов (для того же CKM_RSA_PKCS_OAEP), то не составит труда реализовать поддержку структур параметров и для других механизмов. Так, если кому потребуется работа с контейнером PKCS#12, то придется реализовать поддержку структуры CK_PKCS5_PBKD2_PARAMS.
Всё это относится к довольно сложным криптографическим механизмам.
А вот всё то, что касается хэширования, формирования проверки электронной подписи, наконец, шифрования, то всё работает замечательно. Но для начала надо собрать проект

II. Сборка обертки PyKCS11 с поддержкой ГОСТ-ов


Она ничем не отличается от сборки родной обёртки PkCS11 за исключением того, что исходный код необходимо получить здесь.
Далее следуем инструкции по сборке и установке пакета PyKCS11.
Для тестирования потребуется токен с поддержкой российской криптографии. Здесь мы имеем в виду ГОСТ Р 34.10-2012 и ГОСТ Р 34.11-2012. Это может быть как аппаратный токен, например RuTokenECP-2.0, так и программные или облачные токены.
Установить программный токен или получить доступ к облачному токену можно, воспользовавшись утилитой cryptoarmpkcs.
Скачать утилиту cryptoarmpkcs можно здесь.
Скачать утилиту cryptoarmpkcs можно здесь.

После запуска утилиты необходимо зайти на вкладку Создать токены:

image

На вкладке можно найти инструкции для получения и установки токенов.

II. Тестирование российских алгоритмов

Для тестирования можно использовать скрипты, которые лежат в папке testGost:
  • ckm_kuznyechik_cbc.py
  • ckm_gostr3411_12_256.py
  • ckm_gostr3410_with_gostr3411_12_256.py
  • ckm_gostr3410_512.py

Для тестирования исходные данные брались как из соответствующих ГОСТ-ов, так и из рекомендаций ТК-26.
В данных скриптах тестируются следующие механизмы:
1. Генерация ключевых пар:
  • CKM_GOSTR3410_512_KEY_PAIR_GEN (ГОСТ Р 34.10-2012 с длиной ключа 1024 бита)
  • CKM_GOSTR3410_KEY_PAIR_GEN (ГОСТ Р 34.10-2012 с длиной ключа 512 бит)

2. Формирование и проверка электронной подписи:
  • CKM_GOSTR3410
  • CKM_GOSTR3410_512
  • CKM_GOSTR3410_WITH_GOSTR3411_12_256

3. Хэширования:
  • CKM_GOSTR3411_12_256

4. Шифрование/расшифровка
  • CKM_KUZNYECHIK_CBC


Генерация ключевых пар позволяет владельцу токена получить закрытый ключ, которым он может подписать, например, запрос на сертификат. Запрос на сертификат может быть отправлен в удостоверяющий центр и там по нему могут выдать сертификат. Владелец сертификата может импортировать его на токен, где хранится закрытый ключ. Теперь у владельца токена есть личный сертификат с закрытым ключом, который он может использовать для подписи документы.
Ну а если ему требуется особый режим секретности, то он может зашифровать документ по одному из алгоритмов, а именно Магме или Кузнечику. Всё это конечно в том случае, если сам токен поддерживает эти механизмы, пакет PyKCS11 является всего лишь посредником.
На этом наше повествование, связанное с поддержкой в Python токенов с российской криптографией заканчивается.
Подробнее..

Консольный менеджер сертификатов для JKSPCKS12

05.02.2021 14:16:53 | Автор: admin

Привет Хабр!

С момента популяризации https, работа с сертификатами теперь есть практически в каждом проекте.

habr-certificatehabr-certificate

Сертификаты, которые любой может сгенерировать сам;
Сертификаты которые выдает местный СА в компании, доверие к которому есть только внутри компании;
Бесплатный публичный let's encrypt
Платный сертификат от крупных центров авторизации с дополнительными услугами, типа проверки компании.

Сертификаты для tls/ssl, сертификаты для авторизации, сертификаты для two-say-tls/ssl - даже в пределах одного небольшого проекта с десятком микросервисов и несколькими тестовыми окружениями - сертификатов набирается уйма.

При работе с множеством сертификатов, их удобно хранить не отдельными файлами, а складывать в хранилища (keystore). Самые популярные keystore форматы на сегодня - JKS (java keystore, который считается legacy с выходом 9-й джавы) и PKCS12. Последний вышел уже около 20 лет назад, и несмотря на некоторую критику, является одним из самых надежных, открытых и популярных форматов, который в скором будущем должен уже полностью вытеснить JKS.

Я много работал в проектах, где основным языком разработки был java, поэтому при работе с keystore основной инструмент для меня это консольная утилита keytool, которая поставляется с JDK. И для большинства популярных действий у меня конечно есть микро-шпаргалка:

Посмотреть список сертификатов:

keytool -list -v -keystore "файл.jks" -storepass "пароль"|grep -P '(Alias name:|Entry type:|Serial number:|Owner:|Valid from:)'

Скопировать сертификат из одного keystore в другой keystore:

keytool -importkeystore -srckeystore "откуда.jks" -srcstorepass "пароль" -destkeystore "куда.jks" -deststorepass "пароль" -srcalias "имя сертификата" [ -destalias "новое имя" ]

Импортировать сертификат в keystore из PEM файла:

keytool -import -file "откуда.pem" -keystore "куда.jks" -storepass "пароль" -noprompt -alias "имя_сертификата"

Экспортировать сертификат из keystore в файл:

keytool -exportcert -v -alias "alias" -keystore "keystore.jks" -storepass "storepassword" -rfc -file "exportedcert.cer"

Но однажды мне пришлось поработать в проекте, где сертификатов оказалось мягко говоря много. Плюс несколько сотен виртуалок и контейнеров, где все сертификаты обновлялись вручную. Перед тем? как подойти к вопросу автоматизации, было необходимо все это разобрать и причесать. Но долгое время руки просто не доходили - с одной стороны были вещи более приоритетные а сертификаты в общем напрягали нечасто. С другой стороны, опыта разгребания такого объема у меня раньше не было, и навскидку казалось, что в лоб задача решается слишком уныло и кропотливо, браться за нее по своей инициативе не хотелось - задача чисто техническая, никакие менеджеры и представители бизнеса такое перед нашей командой не ставили. Но где-то в подсознании желание все это перебрать и привести в порядок крутилось. Изредка я почитывал статьи, просматривал сертификат эксплореры, но все они были под графическую оболочку Windows, а политика безопасности не позволяла ставить в компании неодобренный софт на десктопы.

Возможно, я бы собрался силой воли, помучился бы недельку, сделал бы все стандартными однострочниками+notepad+excel, но тут внезапно у меня случился больничный отпуск, а когда температура меня попустила, а на работу еще можно было не выходить, я случайно вспомнил, что считаю себя спецом по bash.

И пусть я не хватаю лавры автора PIUPIU), но тем не менее встречайте:

Консольный двух-панельный keystore менеджер на Linux shell.

Сперва я написал рабочий вариант на bash, но в какой-то момент подумал и переписал все башизмы в пользу POSIX совместимости. Код стал выглядеть более громоздко, зато скрипт проверенно работает в bash/ksh/dash без проблем.
Если я что-то пропустил - напишите в комментариях, ну или форкайте на здоровье ;)

Также используется sed и grep, по поводу последнего - я не очень уверен, насколько POSIX-совместимо grep -P. Но если что, там несложно переписать на sed.

Некоторое сожаление: привычка работы с java вынудила меня всю работу с хранилищами выполнять именно через keystore, который должен быть доступен в PATH (а может быть стоило разобраться и сделать все через openssl?).

Но давайте к делу. Что умеет менеджер вкратце можно наглядно увидеть на скриншотах.

В одно-панельном режиме:

В двух-панельном режиме:

Для поклонников панельных менеджеров (NC, VC, MC, FAR и др), функциональные клавиши и навигация должны быть интуитивно понятны.

Весьма важным плюсом я считаю, что jks_mgr.sh - это просто шелл скрипт, одним файлом размером 20 килобайт (update: уже 30+ кб) (update2: уже 35+ кб).

Никаких обсфукаций, код максимально простой - проверить на отсутствие закладок может в принципе любой джуниор - то есть такой скрипт можно использовать в любом проекте, не нарушая никаких требований по безопасности, требований по лицензионности, лоялен к производительности, установка не нужна - лишь бы на целевой машине был доступен шелл, keytool, sed и grep.

Что было самое интересное во время разработки:

Автоматическая подстройка панелей под высоту/ширину экрана.

Так как экран у меня перерисовывается почти с каждым нажатием, то менять ширину экрана (например если сидеть через графический ssh клиент) можно в произвольное время - после первого же нажатия любой клавиши экран перерисуется и все адаптируется. Пару вечеров ушло на опции отображения/скрытия столбцов - нужно было высчитать и отладить подстройку для одно-панельного и двух-панельного режима, плюс заголовки и текст считается разными формулами. Вот кусочек для определения насколько надо обрезать Certificate Alias, если экран слишком узкий и затем пример вывода заголовка:

WindowWidth="$(tput cols)"if [ -n "$RFILE" ]; then # two-panel    used=24    [ -n "$SHOW_TYPE" ] && used=$(( $used+34 ))    localWidth=$(( ( $WindowWidth - $used ) / 2 - 1 ))    if [ $localWidth -ne $aliasWidth ]; then        aliasWidth=$localWidth        [ $aliasWidth -lt 1 ] && aliasWidth=1        clear    fi..................................headerWidth=$(( $aliasWidth + 5 ))[ -n "$SHOW_TYPE" ] && headerWidth=$(( $headerWidth + 17 ))printf " store: ${blue}%-$(( $headerWidth ))s${rst}" "$LFILE"printf "| store: ${blue}%-$(( $headerWidth -1 ))s${rst}\n" "$RFILE"printf " %-10s" "Valid to"[ -n "$SHOW_TYPE" ] && printf " %-16s" "Storetype"printf " %-${aliasWidth}s |" "Alias"printf " %-10s" "Valid to"[ -n "$SHOW_TYPE" ] && printf " %-16s" "Storetype"printf " %-${aliasWidth}s\n" "Alias"

Определение нажатия функциональных клавиш.

Некоторые специальные keypress могут иметь длину до 4 символов, поэтому просто через read было сделать непросто - он не позволяет вариативно менять длину читаемой строки, но после поиска в гугле и экспериментов c read в bash/dash, это выглядит так:

# Special keypress could take up to 4 charactersread -rsN1 keypressif [ "$keypress" == "$escape_char" ]; then    read -sn1 -t 0.01 k1    read -sn1 -t 0.01 k2    read -sn1 -t 0.01 k3    read -sn1 -t 0.01 k4    keypress=${k1}${k2}${k3}${k4}    unset k1 k2 k3 k4fi

Теперь можно сравнивать $keypress с комбинациями типа '[A' (стрелка вверх), '[B' (стрелка вниз), '[13~' (F3) и так далее. Я не совсем уверен, что мой вариант будет работать везде идеально - поэтому на всякий случай почти все хоткеи продублированы обычными буквами.

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

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

Я догадываюсь, что можно заметно ускорить навигацию - производительность в git-bash ужасна - мне быстрее скопировать keystore на ближайшую виртуалку, поковырять там менеджером и скопировать исправленный keystore назад. Но нужно будет переписывать много вычислений, делать независимую пагинацию для каждой панели, а на Linux все работает отлично, поэтому в текущем варианте скрипт меня устраивает и дойдут ли руки до переделки - не уверен.

В конце нужно написать какой-то умный итог и заключение ну он простой:

Хочешь получить от практики удовольствие - придумай задачу, результат которой будет полезен для тебя.

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

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

P.S. На фоне некоторых проблем с nginx, Wargaming и др., сразу хочу уточнить, что jks_mgr.sh был написан ИСКЛЮЧИТЕЛЬНО в нерабочее время на личном компьютере.

Подробнее..

Перевод Вышел cert-manager 1.0

03.09.2020 20:17:52 | Автор: admin

Если спросить опытного многомудрого инженера, что он думает о cert-manager и почему все им пользуются, то спец вздохнёт, доверительно обнимет и устало скажет: Все им пользуются, потому что нет альтернатив вменяемых. Наши мыши плачут, колются, но продолжают жить с этим кактусом. Почему любим? Потому что работает. Почему не любим? Потому что постоянно выходят новые версии, которые используют новые фичи. И приходится раз за разом обновлять кластер. А старые версии перестают работать, потому что заговор есть и великое таинственное шаманство.

Но разработчики уверяют, что с cert-manager 1.0 всё изменится.

Поверим?

Cert-manager - родной контроллер управления сертификатами Kubernetes. С его помощью можно выпустить сертификаты из различных источников: Let's Encrypt, HashiCorp Vault, Venafi, пары ключей для подписи и самоподписанных. Он также позволяет поддерживать ключи актуальными по времени действия, а также пытается автоматически обновлять сертификаты в заданное до их истечения время. Cert-manager основан на kube-lego, а также использовал некоторые приемы из других схожих проектов, например kube-cert-manager.

Примечания к выпуску

Версией 1.0 мы ставим знак доверия за три года разработки проекта cert-manager. За это время он значительно развился в функциональности и стабильности, но больше всего - в сообществе. Сегодня мы видим, как многие люди используют его для защиты своих кластеров Kubernetes, а также проводят внедрение в различные части экосистемы. В последних 16 выпусках было исправлено куча ошибок. А то, что надо было сломать - сломано. Несколько заходов по работе с API улучшили его взаимодействие с пользователями. Мы решили 1500 проблем на GitHub с еще большим числом запросов на слияние от 253 участников сообщества.

Выпуская 1.0 мы официально заявляем, что cert-manager - зрелый проект. Мы также обещаем поддерживать совместимость нашего API v1.

Огромная благодарность всем, кто нам помогал делать cert-manager все эти три года! Пусть версия 1.0 станет первым из многих будущих больших достижений.

Выпуск 1.0 - стабильный выпуск с несколькими приоритетными направлениями:

  • v1 API;

  • Команда kubectl cert-manager status, для помощи при анализе проблем;

  • Использование новейших стабильных API Kubernetes;

  • Улучшенное журналирование;

  • Улучшения ACME.

Перед обновлением обязательно прочитайте примечания к обновлению.

API v1

Версия v0.16 работала с API v1beta1. Это добавило некоторые структурные изменения, а также улучшило документацию по полям API. Версия 1.0 опирается на это все с помощью API v1. Этот API является нашим первым стабильным, в то же время мы уже давали гарантии совместимости, но с API v1 мы обещаем поддерживать совместимость на годы вперед.

Внесенные изменения (примечание: наши средства для конвертирования позаботятся обо всем для вас):

Сертификат:

  • emailSANs теперь называется emailAddresses

  • uriSANs - uris

Эти изменения добавляют совместимость с другими SAN (subject alt names, прим. переводчика), а также с Go API. Мы убираем этот термин из нашего API.

Обновление

Если вы используете Kubernetes 1.16+ - конвертирующие webhooks позволят вам одновременно и бесшовно работать с версиями API v1alpha2, v1alpha3, v1beta1 и v1. С их помощью вы сможете использовать новую версию API без изменения или повторного развертывания ваших старых ресурсов. Мы настоятельно рекомендуем выполнить обновление манифестов до API v1, поскольку предыдущие версии скоро будут объявлены устаревшими. Пользователи legacy версии cert-manager будут по-прежнему иметь доступ только к v1, шаги по обновлению можно найти здесь.

Команда kubectl cert-manager status

C нашими улучшениями в нашем расширении к kubectl стало проще исследовать проблемы, связанные с невыдачей сертификатов. kubectl cert-manager status теперь выдает намного больше информации о том, что происходит с сертификатами, а также показывает этап выдачи сертификата.

После установки расширения вы можете запустить kubectl cert-manager status certificate <имя-сертификата>, что приведет к поиску сертификата с указанным именем и любых связанных ресурсов, например CertificateRequest, Secret, Issuer, а также Order и Challenges в случае использования сертификатов от ACME.

Пример отладки еще не готового сертификата:

$ kubectl cert-manager status certificate acme-certificateName: acme-certificateNamespace: defaultCreated at: 2020-08-21T16:44:13+02:00Conditions:  Ready: False, Reason: DoesNotExist, Message: Issuing certificate as Secret does not exist  Issuing: True, Reason: DoesNotExist, Message: Issuing certificate as Secret does not existDNS Names:- example.comEvents:  Type    Reason     Age   From          Message  ----    ------     ----  ----          -------  Normal  Issuing    18m   cert-manager  Issuing certificate as Secret does not exist  Normal  Generated  18m   cert-manager  Stored new private key in temporary Secret resource "acme-certificate-tr8b2"  Normal  Requested  18m   cert-manager  Created new CertificateRequest resource "acme-certificate-qp5dm"Issuer:  Name: acme-issuer  Kind: Issuer  Conditions:    Ready: True, Reason: ACMEAccountRegistered, Message: The ACME account was registered with the ACME servererror when finding Secret "acme-tls": secrets "acme-tls" not foundNot Before: <none>Not After: <none>Renewal Time: <none>CertificateRequest:  Name: acme-certificate-qp5dm  Namespace: default  Conditions:    Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/acme-certificate-qp5dm-1319513028: "pending"  Events:    Type    Reason        Age   From          Message    ----    ------        ----  ----          -------    Normal  OrderCreated  18m   cert-manager  Created Order resource default/acme-certificate-qp5dm-1319513028Order:  Name: acme-certificate-qp5dm-1319513028  State: pending, Reason:  Authorizations:    URL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/97777571, Identifier: example.com, Initial State: pending, Wildcard: falseChallenges:- Name: acme-certificate-qp5dm-1319513028-1825664779, Type: DNS-01, Token: J-lOZ39yNDQLZTtP_ZyrYojDqjutMAJOxCL1AkOEZWw, Key: U_W3gGV2KWgIUonlO2me3rvvEOTrfTb-L5s0V1TJMCw, State: pending, Reason: error getting clouddns service account: secret "clouddns-accoun" not found, Processing: true, Presented: false

Команда также может помочь узнать подробнее о содержимом сертификата. Пример детализации для сертификата, выданного Letsencrypt:

$ kubectl cert-manager status certificate exampleName: example[...]Secret:  Name: example  Issuer Country: US  Issuer Organisation: Let's Encrypt  Issuer Common Name: Let's Encrypt Authority X3  Key Usage: Digital Signature, Key Encipherment  Extended Key Usages: Server Authentication, Client Authentication  Public Key Algorithm: RSA  Signature Algorithm: SHA256-RSA  Subject Key ID: 65081d98a9870764590829b88c53240571997862  Authority Key ID: a84a6a63047dddbae6d139b7a64565eff3a8eca1  Serial Number: 0462ffaa887ea17797e0057ca81d7ba2a6fb  Events:  <none>Not Before: 2020-06-02T04:29:56+02:00Not After: 2020-08-31T04:29:56+02:00Renewal Time: 2020-08-01T04:29:56+02:00[...]

Использование новейших стабильных API Kubernetes

Cert-manager был одним из первых, кто внедрил Kubernetes CRDs. Это, а также наша поддержка версий Kubernetes вплоть до 1.11, привели к тому, что нам надо было поддерживать устаревший apiextensions.k8s.io/v1beta1 для наших CRD, а также admissionregistration.k8s.io/v1beta1 для наших webhooks. Сейчас они устарели и будут удалены в Kubernetes с версии 1.22. С нашей 1.0 мы теперь предлагаем полную поддержку apiextensions.k8s.io/v1 и admissionregistration.k8s.io/v1 для Kubernetes 1.16 (где они были добавлены) и новее. Для пользователей предыдущих версий мы продолжаем предлагать поддержку v1beta1 в нашей legacy версии.

Улучшенное журналирование

В этой версии мы обновили библиотеку для журналирования до klog/v2, используемой в Kubernetes 1.19. Мы также проверяем каждый журнал, который пишем, для назначения ему соответствующего уровня. Мы руководствовались при этом руководством от Kubernetes. Есть пять (по факту - шесть, прим. переводчика) уровней журналирования, начиная с Error (уровень 0), который выводит только важные ошибки, и заканчивая Trace (уровень 5), который поможет узнать точно, что происходит. Этим изменением мы сократили количество журналов, если вым не нужна отладочная информация при работе cert-manager.

Совет: по-умолчанию cert-manager работает на уровне 2 (Info), вы можете переопределить это используя global.logLevel в Helm chart.

Примечание: просмотр журналов - последнее средство при устранении неполадок. Для дополнительной информации ознакомьтесь с нашим руководством.

N.B. редактора: Чтобы подробнее узнать, как это всё работает под капотом у Kubernetes, получить ценные советы у практиков-преподавателей, а также качественную помощь техподдержки, можно принять участие в онлайн-интенсивах Kubernetes База, который пройдёт 28-30 сентября, и Kubernetes Мега, который пройдёт 1416 октября.

Улучшения ACME

Наиболее частое применение cert-manager возможно связано с выпуском сертификатов от Let's Encrypt используя ACME. Версия 1.0 примечательна использованием отзывов от сообщества для добавления двух небольших, но важных улучшений в наш ACME issuer.

Отключение создания ключа учетной записи

Если вы используете сертификаты ACME в больших объемах, вы скорее всего используете одну и ту же учетную запись на нескольких кластерах, так что ваши ограничения по выпуску сертификатов будут касаться их всех. Это уже было возможно в cert-manager при копировании секрета, указанного в privateKeySecretRef. Такой вариант использования был достаточно глючный, поскольку cert-manager пытался быть полезным и радостно создавал новый ключ учетной записи, если его не находил. Поэтому мы и добавили disableAccountKeyGeneration, чтобы защитить вас от такого поведения, если установить этот параметр в true - cert-manager не будет создавать ключ и предупредит вас о том, что ему не был предоставлен ключ учетной записи.

apiVersion: cert-manager.io/v1kind: Issuermetadata:  name: letsencryptspec:  acme:    privateKeySecretRef:      name: example-issuer-account-key    disableAccountKeyGeneration: false

Предпочитаемая цепочка

29 сентября Let's Encrypt перейдет на собственный корневой центр сертификации ISRG Root. Сертификаты с перекрестными подписями будут заменены на Identrust. Это изменение не требует правок в настройках cert-manager, все обновленные или новые сертификаты, выпущенные после этой даты, будут использовать новый корневой CA.

Let's Encrypt уже подписывает сертификаты с помощью этого CA и предлагает их в качестве альтернативной цепочки сертификатов через ACME. В этой версии cert-manager есть возможность задания доступа к этим цепочкам в настройках issuer. В параметре preferredChain можно указать имя используемого CA, с помощью которого будет выдан сертификат. Если будет доступен сертификат, соотвествующий запросу, он выдаст вам сертификат. Обратите внимание, что это предпочтительный вариант, если ничего не будет найдено - будет выдан сертификат по-умолчанию. Это даст гарантию того, что вы все равно произведете обновление своего сертификата после удаления альтернативной цепочки на стороне ACME issuer.

Уже сегодня можно получать сертификаты, подписанные ISRG Root, так:

apiVersion: cert-manager.io/v1kind: Issuermetadata:  name: letsencryptspec:  acme:    server: https://acme-v02.api.letsencrypt.org/directory    preferredChain: "ISRG Root X1"

Если вы предпочитаете оставить цепочку IdenTrust - выставляйте этот параметр в DST Root CA X3:

apiVersion: cert-manager.io/v1kind: Issuermetadata:  name: letsencryptspec:  acme:    server: https://acme-v02.api.letsencrypt.org/directory    preferredChain: "DST Root CA X3"

Обратите внимание, что этот корневой центр сертификации скоро устареет, Let's Encrypt будет поддерживать эту цепочку активной до 29 сентября 2021 года.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru