Экспорт и импорт отсоединенной электронной подписи: опыт применения

10 3

Проблема

При использовании реализованного в статье решения, файл отсоединенной электронной подписи не проходил проверку на валидность на сайте «Госуслуги» и в установленной у заказчика программе «КриптоПро».

Постановка задачи

Разработать функцию, осуществляющую экспорт документов с открепленной электронной подписью. В результате ее выполнения из Directum должны выгружаться два файла: файл документа и файл отсоединенной электронной подписи формата .sig. Оба файла должны пройти проверку на валидность в специализированном ПО (например, КриптоАРМ).

Также необходимо разработать функцию, осуществляющую импорт документов с открепленной электронной подписью. В результате ее выполнения в Directum должен создаваться подписанный электронный документ, содержимое которого получено из файла документа, а подпись – из файла отсоединенной подписи формата ‘.sig’.

Функция экспорта

При использовании реализованной в статье функции ExportSignaturesFromEDocVer файл отсоединенной электронной подписи не проходил проверку на сайте «Госуслуги» и в «КриптоПро». Выяснилось, что в нашем случае функция неверно получала тело подписи из базы данных, вследствие чего было решено реализовать второй вариант экспорта отсоединенной подписи, описанный в статье: через чтение ESD-файла.

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

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

В результате экспорт был реализован следующим образом:

if DocVersion.Signed and DocVersion.SignatureType = stApproving               
  DocVersion.Export(DocPath)
  if PDFVersion.Note == "со штампом"
    PDFVersion.Export(PDFPath)
  endif
  TempPath = Format("%s\%s"; ArrayOf(GetTempFolder(); "ExportSign.esd")) 
  Doc.Export(i+1; TempPath; ; ; True; )
        
  XMLDoc = CreateObject("MSXml.DomDocument")
  XMLDoc.Load(TempPath)                                                          //Считывание ESD-документа
  Root = XMLDoc.selectSingleNode("StructuredElectronicObject")                   //Получение корневого тега 
  SignaturesRoot = Root.selectSingleNode("DigitalSignatures")                    //Получение корневого тега раздела с подписями
  FirstSignature = SignaturesRoot.selectSingleNode("DigitalSignature")           //Получение первой подписи из набора
  FirstSignatureBody = FirstSignature.selectSingleNode("Data")                   //Получение тела подписи
  FirstSignatureValue = FirstSignatureBody.text
        
  SignPath = Format("%s%s.doc.1.sig"; ArrayOf(Path; DocName))
  WriteFile(SignPath; ; FirstSignatureValue)
  DeleteFile(TempPath)
else
  ShowMessage("Не найдена версия документа, подписанная утверждающей подписью")
endif

Функция импорта

Предложенное в упомянутой выше статье решение производило импорт файла формата ‘.p7s’, что не подходило по требованиям заказчика, но при дальнейшем изучении форматов ‘.p7s’ и ‘.sig’ было выяснено, что подпись можно выгружать в файл формата ‘.sig’.

При использовании функции CreateEDocWithSigFromFile в чистом виде для импорта документа в формате ‘.doc’ и файла отсоединенной подписи формата ‘.p7s’ возникли другие трудности. В результате выполнения функции создавался документ с корректным содержимым, но недействительной подписью:

Причиной такого поведения стали комментарии «-----BEGIN CMS-----» и «-----END CMS-----», добавляемые некоторыми программами при экспорте отсоединенной электронной подписи в текст подписи. Программный поиск и удаление этих комментариев решило проблему с некорректным чтением электронной подписи системой.

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

После всех изменений функция CreateEDocWithSigFromFile приняла следующий вид:

  Result = nil
  if Assigned(DocPath)      
    if FileExists(DocPath)
      if FileSize(DocPath) > 0
        // Определить Приложение-редактор
        Extension = ExtractFileDriveDirNameExt(DocPath; 'E')   
        Editor = GetEditorCodeByExtension(UpperCase(Extension))         

        ESDFilePath = GetTempFolder() & '\ImportedESD.esd'
        
        ESDTemplate = 
                    '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
                      <StructuredElectronicObject Version="2.0" MetadataID="" MetadataVersion="">
                        <Header Type="Document" Name="" Extension="%0:s" Modified="" Organization="" Number="" Date=""/>
                        <ExtAttributes>
                          <ID Name="ID" Type="Integer" IsNull="false" Value=""/>
                          <VersionNumber Name="VersionNumber" Type="Integer" IsNull="false" Value="1"/>
                        </ExtAttributes>
                        <AccessRights/>
                        <Links/>
                        <Contents>%1:s</Contents>
                        <DigitalSignatures>
                          %2:s                           
                        </DigitalSignatures>
                      </StructuredElectronicObject>'        
        
        DigitalSignatureTemplate = 
                         '<DigitalSignature Signed="" CertificateIssuedTo="" CryptoProvider="Capicom Encryption" Version="2.0" SignatureType="Approving">
                            <Attributes>
                              <Comment Name="Comment" Type="String" IsNull="false" Value=""/>
                              <SignedByUserName Name="SignedByUserName" Type="String" IsNull="false" Value=""/>
                              <InTheNameOfUserName Name="InTheNameOfUserName" Type="String" IsNull="false" Value=""/>
                            </Attributes>
                            <Data>%0:s</Data>
                          </DigitalSignature>'
  
        // Добавить информацию об ЭП
        ESDXML = CreateObject("MSXML.DOMDocument")

        
        // CAPICOM кодирует ЭП в base64 2 раза, поэтому нужно проверить, в каком формате пришла ЭП. Если в base64, то закодировать 1 раз, иначе 2
        RegExp = CreateObject("VBScript.RegExp")
        RegExp.IgnoreCase = TRUE
        RegExp.Global = TRUE         
        RegExp.Pattern = '^([A-Za-z0-9+/\n\r=])+$'
        
        DigitalSignaturesString = ''
        
        foreach SignaturePath in CArrayElement(Signatures)
          if FileExists(SignaturePath)
            // Проверить размер тела ЭП
            if FileSize(SignaturePath) > 0              
                  
              StringSign = ReadFile(SignaturePath)
              if RegExp.Test(StringSign)
                BinarySignBase64 = MimeEncodeString(StringSign)                
              else
                BinarySign = LoadFile(SignaturePath)
                BinarySignBase64 = MimeEncodeString(MimeEncodeBinary(BinarySign))                 
              endif 
                        
              DigitalSignature = ESDXML.CreateCDataSection(BinarySignBase64)
              NewDigitalSignatureNode = Format(DigitalSignatureTemplate; DigitalSignature.xml)        
              DigitalSignaturesString = AddSubString(NewDigitalSignatureNode; DigitalSignaturesString; CR)
            else
              Raise(CreateException(''; 'ЭП нулевого размера: ' & SignaturePath; ecException))
            endif
          else
            Raise(CreateException(''; 'Не найден файл ЭП при импорте документа: ' & SignaturePath; ecException))
          endif
        endforeach        
        
        RegExp = nil
        
        BinaryFileBody = LoadFile(DocPath)
        BinaryFileBodyCData = ESDXML.CreateCDataSection(MimeEncodeBinary(BinaryFileBody))
        ESDFullXML = Format(ESDTemplate; ArrayOf(Extension; BinaryFileBodyCData.xml; DigitalSignaturesString))                
        ESDXML.LoadXML(ESDFullXML)          
        ESDXML.Save(ESDFilePath)         
                                    
        EDoc = EDocuments.CreateNewFromFile(DocType; DocKind; Editor; ESDFilePath; TRUE)     
        DeleteFile(ESDFilePath)
        
        if Assigned(RCC) 
          RCC:IReference 
          EDoc.Дата4 = RCC.Дата2
          EDoc.Дополнение  = RCC.Дополнение2 
          EDoc.Организация = RCC.Организация
          EDoc.Дополнение3 = RCC.Содержание
        endif
          
        try
          EDoc.Save()      
        except
          EDoc.Form.ShowModal()
        endexcept
  
        Result = EDoc 
      else
        Raise(CreateException(''; 'Файл документа нулевого размера: ' & DocPath; ecException))
      endif
    else                 
      Raise(CreateException(''; 'Не найден файл: ' & DocPath; ecException))   
    endif
  endif

 

Алексей Семакин

Вопрос к фразе "Вопрос выгрузки нескольких отсоединенных подписей в один файл остается открытым." Не уверен, что понимаю, о чем она. Не решена какая-то техническая задача? Хотите объединить несколько ЭП в один файл? А это вообще корректно? Как потом с ними работать?

Елена Питомцева: обновлено 30.04.2021 в 11:21

Алексей, изначально было пожелание сделать экспорт нескольких подписей в один файл. В  качестве примера заказчик приводил аналогичный функционал в КриптоПро, и предложенный пример из этой программы корректно распознавался на Госуслугах. Но на текущий момент эта задача отложена, так как вероятность того, что документ, для которого разработан экспорт, будет иметь несколько подписей, мала. Кроме того нам пока действительно непонятно, насколько это корректно и как осуществить такое объединение в Directum.

Дмитрий Зайцев

День добрый!  А полный текст функции экспорта после Ваших изменений как выглядит? И если я правильно понимаю, то функция формирует файл с прикрепленной цифровой подписью?

Авторизуйтесь, чтобы написать комментарий