Задача: Дать пользователю универсальный инструмент для работы с подписью, ее выгрузкой, установкой, передачей и загрузкой (если на то будет необходимость). Это необходимо для дальнейшего использования и выгрузок подписанных документов с УКЭП.
Как ни странно, но информации по способам подписания документов было более чем достаточно в сети, а вот конкретики по настройке или хотя бы идеи о том, как сопрягать имеющиеся сертификаты и УКЭП с файлами - кот наплакал. Однако собрать воедино кое-что получилось.
Пришлось искать информацию и собирать все по кусочкам.
Итак. Кусочек первый:
Определение командной строки для подписания. Тут все просто - форум КриптоПро в помощь:
'chcp 1251
cd "C:\Program Files (x86)\Crypto Pro\CSP"
csptest.exe -sfsign -sign -in "ИмяДокумента" -out "ИмяДокумента.sig" -my "Sertificate" -addsigtime -add -detached'
Вроде бы все понятно. Облачаем это во временный файл для запуска:
TextInCMD = 'chcp 1251
cd "C:\Program Files (x86)\Crypto Pro\CSP"
csptest.exe -sfsign -sign -in "' & DocName & '" -out "' & DocName & '.sig" -my "' & Certificate & '" -addsigtime -add -detached'
CMDFile = GetTempFolder() & 'sig.bat'
ФайлЗаписать(CMDFile;'N';TextInCMD)
CMD = CreateObject("WScript.Shell")
CMD.Run(CMDFile;1;True)
ФайлУдалить(CMDFile)
Далее. Кусочек два.
Отлично, теперь нам надо передать в эту строку Имя файла и указатель на конкретный сертификат. С именем все понятно, там по классике: DocName = GetTempFolder() & Doc.SYSREQ_EDOC_NAME & '.' & Doc.Info.Editor.Extension, а вот с сертификатом возникли сложности, так как их может быть много, а значит нам надо передавать только тот, который будет подписывать внешний файл. Для этого в списке сертификатов пользователя мы, пометив нужный нам сертификат тэгом "Внешний", и будем получать его ИД:
И в коде:
UserRef = References.SYSREF_USERS_REFERENCE.GetObjectByID(tasks.CurrentUser.ID)
DDS2 = UserRef.DetailDataSet(2)
foreach Sert in DDS2
if Sert.ISBCertificateInfo == "Внешний"
Certificate = Sert.СодержаниеТ2 // -------- То что нам и надо!
endif
endforeach
Теперь у нас есть все необходимое для того, чтобы получить строку для подписания документа находящегося во "внешнем мире", осталось только добавить Экспорт документа, Импорт подписанного документа и связи между ними. Плюс уборка за собой.
Reslt = 0
UserRef = References.SYSREF_USERS_REFERENCE.GetObjectByID(tasks.CurrentUser.ID)
DDS2 = UserRef.DetailDataSet(2)
foreach Sert in DDS2
if Sert.ISBCertificateInfo == CertID
Certificate = Sert.СодержаниеТ2
Doc = EDocuments.GetObjectByID(DocID)
DocName = GetTempFolder() & Doc.SYSREQ_EDOC_NAME & '.' & Doc.Info.Editor.Extension
LastVersion = _LastVersionDoc(Doc)
Doc.Export(LastVersion;DocName;;;;)
TextInCMD = 'chcp 1251
cd "C:\Program Files (x86)\Crypto Pro\CSP"
csptest.exe -sfsign -sign -in "' & DocName & '" -out "' & DocName & '.sig" -my "' & Certificate& '" -addsigtime -add -detached'
CMDFile = GetTempFolder() & 'sig.bat'
ФайлЗаписать(CMDFile;'N';TextInCMD)
CMD = CreateObject("WScript.Shell")
CMD.Run(CMDFile;1;True)
ФайлУдалить(CMDFile)
CryptoFile = DocName & '.SIG'
if ФайлСуществует(CryptoFile)
FileSign = EDocuments.CreateNewFromFile('Файл_типа_Sign';'Д000099';'Crypt';CryptoFile)
FileSign.EDocument = Doc.ID
FileSign.Save
ФайлУдалить(CryptoFile)
EDocuments.BindTo(Doc.Info;FileSign.Info)
Reslt = FileSign.ID
else
Reslt = 0
endif
endif
endforeach
Result = Reslt
Далее. Кусочек три.
Рассмотрим вариант, когда у нас уже есть документы подписанные УКЭП в самом Директуме. Подпись надо достать и из него. Ищем информацию, получаем на выходе:
Doc = EDocuments.GetObjectByID(DocID)
FileName = GetTempFolder() & Doc.SYSREQ_EDOC_NAME & '.' & Doc.Info.Editor.Extension
FileNamesig = FileName & '.sig'
VerNumber = _LastVersionDoc(Doc)
Doc.Export(VerNumber; FileName; FALSE; FALSE; TRUE)
ESDDoc = CreateObject('MSXML.DOMDocument')
ESDDoc.Load(FileName)
Root = ESDDoc.DocumentElement
DeleteFile(FileName)
SignaturesNode = Root.SelectSingleNode('DigitalSignatures')
SignNodeCount = SignaturesNode.ChildNodes.length
I = 0
while I < SignNodeCount
SignNode = SignaturesNode.ChildNodes.Item(I)
DataNode = SignNode.ChildNodes.Item(1)
WriteFile(FileNamesig;; DataNode.Text)
I = I + 1
Reslt = 1
endwhile
Замечательно.
Теперь мы должны к обоим алгоритмам предусмотреть вариант с выбором конечного адреса и отказом от удаления документа, чтобы файл подписи остался там, куда мы его отправили. А так же быть готовыми к варианту, что у пользователя нет УКЭП в Директум, но есть на внешнем носителе (Certificate = '1'). Ну и в параметры добавим Режим работы (MODE).
Затем собираем все в кучу. Немного магии, и вот мы уже имеем функцию с 3 параметрами: "DocID", "CertID" и MODE:
Описание:
Функция для выгрузки подписанного документа.
Имеется 2 режима работы: Подписание в момент выгрузки или выгрузка ранее подписанного документа.
При запуске срабатывает Диалог - путь, куда нужно выгрузить подписанные документы. Если не выбирать каталог - будет выбрано временное хранилище и файлы после выгрузки будут удалены.
Результат выполнения:
0 - системе не удалось загрузить подписанный документ.
1 - Система успешно выгрузила подписанный ранее документ.
ИД документа - Система выгрузила документ и загрузила подписанную версию в вид документа типа SIG, вернув его ИД для работы.
PathK = CreateFolderDialog()
PathK.Execute
if Assigned(PathK.Result)
Path = PathK.Result & '\'
Load = FALSE
else
Path = GetTempFolder()
Load = TRUE
endif
if Mode
Reslt = 0
UserRef = References.SYSREF_USERS_REFERENCE.GetObjectByID(tasks.CurrentUser.ID)
DDS2 = UserRef.DetailDataSet(2)
if DDS2.RecordCount = 0
try
Certificate = '1'
Doc = EDocuments.GetObjectByID(DocID)
DocName = Path & Doc.SYSREQ_EDOC_NAME & '.' & Doc.Info.Editor.Extension
LastVersion = _LastVersionDoc(Doc)
Doc.Export(LastVersion;DocName;;;;)
TextInCMD = 'chcp 1251
cd "C:\Program Files (x86)\Crypto Pro\CSP"
csptest.exe -sfsign -sign -in "' & DocName & '" -out "' & DocName & '.sig" -my "' & Certificate & '" -addsigtime -add -detached'
CMDFile = GetTempFolder() & 'sig.bat'
ФайлЗаписать(CMDFile;'N';TextInCMD)
CMD = CreateObject("WScript.Shell")
CMD.Run(CMDFile;1;True)
ФайлУдалить(CMDFile)
if Load
ФайлУдалить(DocName)
endif
CryptoFile = DocName & '.SIG'
if ФайлСуществует(CryptoFile)
FileSign = EDocuments.CreateNewFromFile('TFD_Sign';'Д000099';'Crypt';CryptoFile)
FileSign.EDocument = Doc.ID
FileSign.Save
//if Load
// ФайлУдалить(CryptoFile)
//endif
EDocuments.BindTo(Doc.Info;FileSign.Info)
Reslt = FileSign.ID
else
Reslt = 0
endif
except
ShowMessage('Не обнаружены сертификаты пригодняе для подписания документа.')
exit()
endexcept
else
foreach Sert in DDS2
if Sert.ISBCertificateInfo == CertID
Certificate = Sert.СодержаниеТ2
Doc = EDocuments.GetObjectByID(DocID)
DocName = Path & Doc.SYSREQ_EDOC_NAME & '.' & Doc.Info.Editor.Extension
LastVersion = _LastVersionDoc(Doc)
Doc.Export(LastVersion;DocName;;;;)
TextInCMD = 'chcp 1251
cd "C:\Program Files (x86)\Crypto Pro\CSP"
csptest.exe -sfsign -sign -in "' & DocName & '" -out "' & DocName & '.sig" -my "' & Certificate & '" -addsigtime -add -detached'
CMDFile = GetTempFolder() & 'sig.bat'
ФайлЗаписать(CMDFile;'N';TextInCMD)
CMD = CreateObject("WScript.Shell")
CMD.Run(CMDFile;1;True)
ФайлУдалить(CMDFile)
if Load
ФайлУдалить(DocName)
endif
CryptoFile = DocName & '.SIG'
if ФайлСуществует(CryptoFile)
FileSign = EDocuments.CreateNewFromFile('TFD_Sign';'Д000099';'Crypt';CryptoFile)
FileSign.EDocument = Doc.ID
FileSign.Save
EDocuments.BindTo(Doc.Info;FileSign.Info)
Reslt = FileSign.ID
else
Reslt = 0
endif
endif
endforeach
endif
Result = Reslt
else
Reslt = 0
Doc = EDocuments.GetObjectByID(DocID)
FileName = GetTempFolder() & Doc.SYSREQ_EDOC_NAME & '.' & Doc.Info.Editor.Extension
FileNamesig = FileName & '.sig'
VerNumber = _LastVersionDoc(Doc)
Doc.Export(VerNumber; FileName; FALSE; FALSE; TRUE)
ESDDoc = CreateObject('MSXML.DOMDocument')
ESDDoc.Load(FileName)
Root = ESDDoc.DocumentElement
if Load
DeleteFile(FileName)
endif
SignaturesNode = Root.SelectSingleNode('DigitalSignatures')
SignNodeCount = SignaturesNode.ChildNodes.length
I = 0
while I < SignNodeCount
SignNode = SignaturesNode.ChildNodes.Item(I)
DataNode = SignNode.ChildNodes.Item(1)
WriteFile(FileNamesig;; DataNode.Text)
I = I + 1
Reslt = 1
endwhile
endif
Вроде так. Может кому пригодится.
А обратную ситуацию с загрузкой в СЭД пришедшей из вне пары документ-подпись исследовали? Я про импорт такой пары с образованием версии документа и подписи в системе отображаемой в информации о подписях документа из штатного диалога системы?
Андрей, нет. только практика хранения связки Документ-Подпись(sig) в паре для единицы Хранилища (Справочника). Но сама мысль интересная - добавить проверку подписи и загрузку в СЭД.
1. Тарас, насколько я понял, ваше решение по требованию пользователя экспортирует документы из системы, попутно вычисляя ЭП, и складывает все это в папку выгрузки. При этом вычисленная ЭП импортируется в систему в новый документ, связанный с подписываемым. Документы в системе при этом могут быть как уже подписанными, так и неподписанными.
Вопрос: если вы импортируете отделяемую подпись в систему, то как обеспечить контроль ее валидности в системе (а иначе зачем она там)?
Неподписанный документ в системе может быть легко изменен, и в этот самый момент импортированная вами ЭП превратится в тыкву. Для подписанных в системе документов ситуация чуть лучше, так как их изменение запрещено системой. Но создание новой версии приведет все к ситуации с неподписанным документом, и получится, что УКЭП в системе есть (в связанном документе), но какая версия была подписана ею, непонятно.
Рассматривали ли подписание документов сертификатами КриптоПро непосредственно в системе с последующим экспортом подписей в выгрузку? Это решило бы задачу контроля валидности подписи (переложило бы ее на плечи системы). Про то как экспортировать подпись документа из системы в файл, можно посмотреть в материале Андрея Шестакова.
2. Забавный кусок кода:
while I < SignNodeCount SignNode = SignaturesNode.ChildNodes.Item(I) DataNode = SignNode.ChildNodes.Item(1) WriteFile(FileNamesig;; DataNode.Text) I = I + 1 Reslt = 1 endwhile
То есть вы пробегаетесь по всем подписям и сливаете их в один и тот же файл с перезаписью? Почему бы не экспортнуть только последнюю?
Андрей, посмотрите материал Андрея Шестакова — там есть про импорт отсоединенной подписи.
Уточните, пожалуйста, а почему используется csptest, а не cryptcp? Судя по всему csptest - внутренняя утилита для тестирования определенных функций CSP.
Наталия, для периода тестирования и до ввода материала в продуктивную среду мне было достаточно утилиты тестирования, поэтому выбрал ее, но ее, как Вы заметили - легко заменить.
Алексей,
1. Данное решение работает во вспомогательном справочнике, где у нас хранятся как раз связи документа и его приложений, в том числе и его отделяемая подпись для тех случаев, когда ее необходимо выгрузить сотруднику без ЭЦП. Контроль обеспечивается через внутренний реквизит, который х-ранит информацию о версии к которой выгружен документ типа SIG и на Экспорте, будет делать Экспорт необходимой версии или не выгрузит sig вообще. Другого контроля и не нужно - процесс делается для узкого круга лиц и там все люди образованные чтобы просто подменять документы.
2. Это просто конечный продукт 2 направлений. Сначала было решение выгружать все в отдельные файлы, затем приняли решение оставить только последнюю, но код переписывать было немного лениво, поэтому просто перестали менять имя файла до поры (а то вдруг и это отменят)
Тарас, как всегда актуально и вовремя. Тоже на работе мучают подобным вопросом...и ваша статья - отличный материал на эту тему.
Маленькая поправка: в последних версиях системы Директум вместо:
надо использовать:
И еще замечание: если в переменной FileName будут плохие символы или длинное имя и т.д., то ваш вариант получения имени выдаст ошибку на экспорте документа, т.е. Doc.Export() не сработает:
Я бы наверно сделал так, используя ID документа:
Или так
Авторизуйтесь, чтобы написать комментарий