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

Гост 34.10-2012

Поддержка токенов 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.
Подробнее..

Рутокен ЭЦП 2.0 3000, COVID-19, УЦ Росреестра и операции с Росреестом онлайн ver. 2.0

01.09.2020 16:17:46 | Автор: admin
Привет, хабровчане!

К написанию данной статьи меня подтолкнули сразу несколько вещей:
  1. Должок перед компанией Актив, которая любезно предоставила мне их новый крипто-токен Рутокен ЭЦП 2.0 модификации 3000. Nastya_d, тэгну вас, т.к. вы последняя, кто постил от лица компании
  2. COVID-19, который перевел работу Росреестра в режим только по предварительной записи с хронической невозможностью туда записаться
  3. Изменения в законодательстве, которые были приняты после череды прошлогодних скандалов, связанных с применением электронной подписи
  4. Обновление Росреестра по части проведения электронных сделок и подачи каких-либо иных электронных заявлений


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

Рутокен ЭЦП 2.0 3000



Факты


Новый токен под старым брендом. Вроде какая-то добавка 3000 в конце и вообще не понятно о чем это. А между тем перед нами принципиально новый носитель ключевой информации. С классическим Рутокен ЭЦП 2.0 его объединяет пожалуй лишь то, что он может быть с ним совместим, и по прежнему на нём можно хранить неизвлекамые закрытые ключи, криптографические операции с которыми происходят прямо в контроллере девайса, а закрытые ключи никогда не покидают его пределов.

А в чем же разница? Пожалуй, отличия четыре:
  1. Это функциональный ключевой носитель (ФКН), работающий по протоколу (на самом деле метапротоколу) SESPAKE, что дает нам защиту канала между драйвером и контроллером от прослушивания
  2. Многократное ускорение при работе с токеном, на котором есть несколько ключей с сертификатами
  3. Невозможность работы в режиме ФКН через PKCS#11
  4. Отсутствие режима работы без КриптоПро


Давайте теперь по порядку.

ФКН с SESPAKE дает возможность защитить канал обмена между драйвером/криптопровайдером и контроллером хранилища. Раньше подобный перехват был возможен, и любой любитель Wireshark с установленным USBcap мог лицезреть открытые PIN-коды при работе с классическим Рутокен ЭЦП 2.0, которые транслируются в APDU командах контролера, что потенциально даёт вектор атаки по подслушиванию PIN'а и дальнейшему подписыванию всего и вся без взаимодействия с пользователем. И хотя формально закрытые ключи всё равно остаются неизвлекаемыми, это может перестать быть принципиальным моментом.

Ускорение. Уж не знаю как и почему, но если у вас было несколько сертификатов на одном токене Рутокен ЭЦП 2.0, то тормоза были обеспечены. Особенно при попытке софта перебрать все сертификаты или найти контейнер с нужным. Ваш покорный слуга хватал лично ситуации, когда в некоторых системах ЭДО дешифрация маленького документа занимала более минуты. Всё кончилось тем, что Тензор в своих плагинах даже запретил использование аппаратных токенов совместно с КриптоПро 5-й версии, чем вызвал много подозрений. Теперь всё не так, я даже не замечаю разницы между тем, когда на токене был один сертификат, и теперь, когда их четыре (Тензор при этом всё равно не работает с аппаратными ключами в версиях плагинов где-то с осени 2019 года).

Режим PKCS#11 это возможность использовать библиотеку производителя, которая реализует стандартное API PKCS#11 по работе с ним (напрямую без КриптоПровайдера). Под Windows применяется мало, т.к. Windows Crypto API является доминирующим способом. Под Linux похоже увы, если не брать гипотетическую возможность установить КриптоПро под Linux и использовать его. КриптоПро под Linux представляет собой по сути реализацию Windows Crypto API, то есть ни одна традиционная софтина под Linux это не поддерживала и не будет поддерживать. Скорее это возможность для разработчиков пилить госзаказ и разрабатывать соответствующие серверные продукты под Linux с возможность криптографии ГОСТ. О применении в Linux Desktop речи не идет. Тем не менее Рутокен поставляет свою библиотеку PKCS#11 для старого Рутокен ЭЦП 2.0 и мне даже удавалось как-то подружить её с плагином ГосУслуг для аутентификации по электронной подписи. С ФКН возможности такой, как я понимаю нет, но возможно компания Актив меня поправит.

Отсутствие режима работы без КриптоПро. Скорее всего это связано с предыдущим пунктом. Но на практике сложилась некоторая путаница при использовании Рутокен ЭЦП 2.0. Почему-то мало кто знает, что Рутокен ЭЦП 2.0 умеет прекрасно работать с КриптоПро 5-й версии. Возможно, из-за того, что он появился задолго до КриптоПро 5. В результате, различные участники рынка наработали кастомные решения, которые позволяют работать с Рутокен ЭЦП 2.0 без КриптоПро. Это приводило к достаточно серьезным трудностям в диагностике проблем на сайтах. Ты говоришь, что у тебя КриптоПро и Рутокен ЭЦП 2.0, а тебе говорят, что Рутокен ЭЦП 2.0 не работает через КриптоПро. С 3000-м альтернативы, похоже, никакой нет. Только КриптоПро 5. Хорошо это или плохо время покажет.

Картинки


Если вы используете КриптоПро 5-й версии, то поддержка идет из коробки. Отличия для пользователя минимальны. Создание ключевого контейнера:


В отличие от тупого носителя у вас теперь есть выбор в каком режиме вырабатывать ключевую пару. Верхний новый режим ФКН, внизу старый режим Рутокен ЭЦП 2.0 с поддержкой PKCS#11, посередине режим тупого токена, в котором всю работу делает криптопровайдер.

При создании первого контейнера в режиме ФКН КриптоПро попросит задать PUK-код и пароль:



В результате получим ключевую пару, которую можно посмотреть через Панель управления Рутокен:


Резюме


Рутокен ЭЦП 2.0 3000 стал для меня долгожданной заменой для ранее использованного функционального ключевого носителя из АПК КриптоПро Рутокен CSP 3.6, который умел только старые ГОСТы. Тем не менее, я очень расстраиваюсь, что для работы с этим всем добром приходится держать виртуалку с виндой. Было бы здорово, если бы Рутокен раскрыл описание своей реализации протокола SESPAKE. Думаю, что прикрутить стандартные линуксовые библиотеки для работы с этим токеном можно будет будет без особых проблем.

COVID-19 и РосРеестр


Пандемия нехорошим образом сказалась на возможности работы с РосРеестром. Не знаю как других регионах, но в Питере случилась прямо какая-то вакханалия. С одной стороны, МФЦ полностью перешли на работу по предварительной записи (сейчас вышли, но по услугам РосРеестра продолжают оставаться). С другой стороны, записаться на это стало возможным только в первые минуты начала дня после 9:00 и где-то на 2-3 недели вперед. СМИ сообщали, что риелторы бронировали всё под себя, а потом продавали свою очередь но так как запись была именная, то помимо покупки очереди приходилось ещё и оформлять доверку на такого дельца, потому что кроме него никто пойти по очереди не мог.

Альтернативой этому стали нотариусы, которые могут в электронном виде отправлять документы на регистрацию сделки в Росреестр, но они берут очень дорого за нотариальное удостоверение сделки. Другая альтернатива ДомКлик от Сбербанка: если покупатель покупает в ипотеку, то ДомКлик позволяет зарегистрировать всё электронно (даже с небольшой выгодой по процентной ставке).

Покупатель нашелся по ипотеке, и дружно решили оформляться через ДомКлик. Но не тут-то было. Недвижимость оказалась старой, и регистрация прав была осуществлена в 1995 году. Если вы регистрировали свои права до 31.01.1998, то нужно:
  • либо заранее внести заявление о регистрации ранее возникших прав (требует уплаты госпошлины)
  • либо такое заявление подать одновременно со сделкой, тогда это бесплатно


Однако ДомКлик такие заявления подавать не умеет. Нотариусы тоже за это не берутся по неизвестным мне причинам, которые я не выяснял. Сделка оказалась под угрозой, так как МФЦ предложил запись аж на 23 сентября.

И тут выхожу я весь в белом и говорю: А давайте сделаем это в электронном виде сами?

Изменения в законодательстве


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

После череды скандалов(раз, два, три), прокатившихся в 2019 году, связанных с регистрацией прав на недвижимость и создания ЮЛ с использованием электронной подписи, были приняты некоторые изменения в законе. Вкратце: теперь по дефолту использовать ЭП для сделок по недвижимости можно только в том случае, если сертификат ЭП выдан удостоверяющим центром Росреестра, а точнее его дочки ФГБУ Кадастровая палата. Вы можете этот дефолт отменить, явно указав, что хотите это делать с сертификатом любого УЦ. Однако, для этого вам необходимо подать заявление в Росреестр через МФЦ, а для этого предварительно записаться на 2-3 недели вперед (смотри выше) то есть никакого выигрыша по времени.

Что ж? УЦ Росреестра оказывается единственной альтернативой. Начинаем изучать. УЦ располагается по ссылке. Нажимаем Сколько стоит?. 2200 рублей с записью на токен. 700 рублей Предоставляется в электронном виде личность удостоверяется в офисе. Опаньки! В электронном виде, это означает, что работают по схеме с запросом на сертификат. То есть можно самым правильным способом сгенерировать пару у себя на рабочем месте, отправить им запрос, а в офисе только пройти процедуру удостоверения личности то чем и должен заниматься УЦ.

Регистрируемся, заводим все данные в профиль. Жмем Отправить запрос. Сайт делает автоматическую диагностику установленного ПО: всё удовлетворяет необходимым требованиям, т.к. используются только общепринятые плагины к браузеру, а не так как у Тензора. Генерируем пару в режиме ФКН на нашем новом Рутокен ЭЦП 2.0 3000. Ждем. Через несколько минут приходят на электронную почту письма о дальнейших действия. Сказано, ждать документа на оплату. Где-то через полчаса приходит квитанция на оплату с QR-кодом. Платеж в бюджет, поэтому используется УИН. Оплачиваем 700 рублей через онлайн-банк. Ещё через 20 минут, заявка переходит в состояние оплачено. Связка Банк<->ГИС ГМП<->Росреестр работает быстрее чем платежи по реквизитам счета, но конкретная скорость может зависеть от выбранного банка. Звоним по указанному в письме телефону для записи на время для удостоверения личности номерки есть даже на сегодня. Бегом в офис кадастровой палаты. И вот результат в 14:00 этим вопросом озадачились. В 16:00 прошли процедуру удостоверения личности и пока ехали домой, выпустились сертификаты. Процедура достаточно тщательная, помимо всех документальных проверок, также производят фотографирование. Сертификаты, к слову, на 15 месяцев, а не как обычно на год.

Резюме


Несмотря на то, что Росреестр остался фактическим монолистом на рынке ЭП для сделок по недвижимости, работает четко. До этого я пользовался УЦ Тензор, где физлицу можно получить сертификат ЭП за 500 рублей. Но скорость работы, факт того, что не нужно ставить дополнительный софт, как у Тензора, сертификат на 15 месяцев всё это даёт мне основание похвалить впервые за много лет Росреестр. 200 рублей переплаты относительно Тензора того стоят, ИМХО.

Обновление Росреестра по части проведения электронных сделок



В предыдущей статье на эту тему мы рассматривали оформление сделок с помощью основного портала Росреестра (не доступен в момент написания). Вначале я решил пойти по проторенной тропе, но очень быстро упёрся в то, что введенный кадастровый номер помещения не валидируется, хотя введен абсолютно правильно. Анализ HTML-кода показал, что в валидатор вставлен костыль, который отвергает большинство кадастровых номеров по первой группе цифр, которая обозначает регион. Все регионы кроме шести штук (16, 26, 47, 61, 63 и 76) оказались таким образом как бы забаненными. Нигде об этом естественно не написано. Хотелось разбомбить.

Однако я быстро вспомнил, что аналогичный функционал, но с другим интерфейсом был представлен в личном кабинете гражданина в Росреестре (авторизация через ЕСИА), который мне не удалось протестить в прошлой статье. Сходил туда и беглый осмотр показал, что для Питера никаких ограничений нет. Значит будем делать так.
О возможных причинах такого поведения Росреестра
В настоящий момент в Росреестре идет активная миграция на новую информационную систему ФГИС ЕГРН. Миграция происходит по регионам. Возможно, что старый интерфейс для электронных заявлений не предназначен для работы с ФГИС ЕГРН. А новый, который в личном кабинете, предназначен.


Переходим на вкладку Услуги и сервисы и выбираем нужную услугу:


Далее нас ждет несколько шагов по заполнению заявления (в зависимости от выбранной услуги):


На первом шаге необходимо просто согласиться и поставить галочку.

Далее, к сожалению, скриншоты не публикую, так как в них содержится много персональных данных.

На втором шаге необходимо проверить и заполнить данные заявителя. Данные подтягиваются из ЕСИА, но если у вас профиль заполнен не полностью, то что-то придется дописать. Также Росреестр плохо подтягивает из ЕСИА адреса проживания и регистрации скорее всего вам придется указать их вручную. Также отмечу, что на данном шаге есть возможность добавить других заявителей. Это необходимо сделать, если у квартиры несколько собственников.

На третьем шаге указываются (в данном случае) данные о собственности (реквизиты помещения, доли и т.д.).

Четвертый шаг самый сложный. Тут необходимо загрузить все документы. Начиная со сканов паспорта, которые каждый заявитель заверяет своей электронной подписью, заканчивая договорами и прочими документами, которые должны заверяться электронными подписями тех, кто их подписывает. Например, договор, должен быть подписан всеми сторонами сделки. Кроме того, может понадобиться документ, который у вас есть только в бумажном виде или от стороны, которая в сделке не участвует например, согласие супруга. Тут можно прибегнуть к нотариусу нотариусы умеют сканить документы и своей электронной подписью подписывать сканы и факт того, что получившийся электронный документ полностью тождественен документу на бумажном носителе. Возможно, если весь документ составляет сам нотариус, то он может сразу сделать его электронным со своей подписью без вывода на бумагу не поверял работают ли так нотариусы на практике.

Для подписи одного документа несколькими электронными подписями так, чтобы все подписи попали в один файл (Росреестр не поддерживает возможность указать несколько файлов с подписями, которые относятся к одному документу), в каментах к прошлому посту рекомендовали использовать бесплатную утилиту Криптолайн фирмы Такском. Действительно, утилита огонь, хоть интерфейс и чудаковат.

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

Резюме


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

Категории

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

  • Имя: Макс
    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