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

    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 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. Архитектура методов в классе Tokeh


    Главным принципом при написании методов было то, чтобы обработка исключений была внутри методов, а информация об исключениях (ошибках), возвращалась в текстовом виде. Исходя из этого все методы возвращают два значения: собственно результат выполнения и информацию об ошибке. Если ошибок нет, то второе значение пустое. Мы это уже видели на примере использования метода 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.

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое