В предыдущей статье был представлен модуль pyp11, написанный на языке Си и обеспечивающий поддержку токенов PKCS#11 с российской криптографией. В этой статье будет рассмотрен класс Token, который позволит упростить использование функционала модуля pyp11 в скриптах, написанных на Python-е. Отметим, что в качестве прототипа этого класса был взят класс token, написанный на TclOO и который используется в утилите cryptoarmpkcs:
Класс Token, как и любой класс в Python, включает конструктор, методы и деструктор. Конструктор и деструктор — это те же методы, только с предопределёнными именами. Конструктор имеет имя __init__, а деструктор имя __del__. Объявление конструктора и деструктора можно опускать. И в классе Token мы опустим объявление деструктора, а вот конструктор будет необходим. Конструктор будет создавать экземпляр класса Token для конкретного токена с конкретными атрибутами.
Итак, конструктор класса Token выглядит следующим образом:
Параметрами конструктора (метода __init__) являются (помимо обязательного self) handle библиотеки токена (handlelp11), номер слота (slottoken), в котором должен находиться токен, и серийный номер токена (serialnum).
Для получения handle библиотеки pkcs#11, номеров слотов и информации о находящихся в них токенах можно использовать следующий скрипт:
Если с библиотекой и слотом всё ясно, то с серийным номером токена может возникнуть вопрос — а зачем этот параметр нужен и почему именно он, а, например, не метка токена. Сразу оговоримся, что это принципиально для извлекаемых токенов, когда злоумышленником (или случайно) один токен в слоте будет заменён другим токеном. Более того, различные экземпляры токена могут иметь одинаковые метки. И наконец, токен может быть еще не проинициализирован или владелец будет его переинициализировать, в частности, сменит метку токена. Теоретически даже серийный номер не гарантирует его идентичность, оптимально учитывать всю информацию о токене (серийный номер, модель, производитель). В задачи конструктора и входит сохранение в переменных создаваемого экземпляра класса аргументов объекта токен:
Проверкой наличия указанного токена в указанном слоте занимается метод tokinfo(), определенный в данном классе.
Метод tokinfo возвращает два значения (см. выше в конструкторе):
В первой переменной (ret) содержится результат выполнения метода, а во второй (stat) — информация о том, как завершилось выполнение метода. Если вторая переменная пуста, то метод tokinfo успешно выполнился. Если вторая переменная не пуста, то выполнение метода завершилось с ошибкой. Информация об ошибке будет находиться в этой переменной. При обнаружении ошибки выполнения метода self.tokinfo конструктор записывает её в переменную returncode:
После создания объекта (экземпляра класса) необходимо проверить значение переменной returncode, чтобы быть уверенным в том, что объект для указанного токена создан:
Если обнаружена ошибка при создании объекта, то целесообразно этот объект уничтожить:
Главным принципом при написании методов было то, чтобы обработка исключений была внутри методов, а информация об исключениях (ошибках) возвращалась в текстовом виде. Исходя из этого все методы возвращают два значения: собственно результат выполнения и информацию об ошибке. Если ошибок нет, то второе значение пустое. Мы это уже видели на примере использования метода tokinfo в конструкторе. А вот и сам код метода tokinfo:
Рассмотрим импользование функционала модуля pyp11 и аналогичных операторов с использованием класса Token.
В последнем случае необходимо будет создать и объект токена:
Начнем с инициализации токена:
Аналогичный код при использовании класса Token выглядит так (идентификатор объекта t1):
Далее мы просто дадим соответствие основных операторов модуля pyp11 и методов класса Token без обработки исключений и ошибок:
Сборка и установка модуля pyp11 с классом Token ничем не отличается от описанной в первой части.
Итак, скачиваем архив и распаковываем его. Заходим в папку PythonPKCS11 и выполняем команду установки:
После установки модуля переходим в папку tests и запускаем тесты для модуля pyp11.
Для тестирования класса Token переходим в папку test/classtoken.
Для подключения модуля pyp11 и класса Token в скрипты достаточно добавить следующие операторы:
В ближайшее время должна появиться и третья часть статьи, в которой будет рассказано, как добавить поддержку российской криптографии в проект PyKCS11.
P.S. Хочу сказать спасибо svyatikov за то, что помог протестировать проект на платформе Windows.
Прототип класса 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 sys
import pyp11
class 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/python3
import sys
import pyp11
from Token import Token
def 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 = 0
for 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 += 1
pyp11.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/python3
import sys
import pyp11
from 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)
#Проверка переменной returncode
if (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 sys
import pyp11
class 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 pyp11
from Token import Token
IV. Заключение
В ближайшее время должна появиться и третья часть статьи, в которой будет рассказано, как добавить поддержку российской криптографии в проект PyKCS11.
P.S. Хочу сказать спасибо svyatikov за то, что помог протестировать проект на платформе Windows.