Ключевое слово в защите информации
КЛЮЧЕВОЕ СЛОВО
в защите информации
Получить ГОСТ TLS-сертификат для домена (SSL-сертификат)
Добро пожаловать, Гость! Чтобы использовать все возможности Вход или Регистрация.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline Nicky711  
#1 Оставлено : 23 мая 2018 г. 11:03:54(UTC)
Nicky711

Статус: Новичок

Группы: Участники
Зарегистрирован: 15.05.2018(UTC)
Сообщений: 8
Российская Федерация

Data.xml (1kb) загружен 22 раз(а). SignedInfoNode.xml (1kb) загружен 21 раз(а). DataPacketNINode.xml (1kb) загружен 12 раз(а). DataProcessed.xml (1kb) загружен 19 раз(а).

Есть задача подписать документ (Data.xml в аттаче):
Код:
<?xml version="1.0" encoding="utf-8"?>
<DATA_PACKET_NI Data="Data...">
</DATA_PACKET_NI>

Сделать это нужно в соответствии со спецификацией: https://docs.fsrpn.ru/tech_specs.rtf

Код (Delphi 2007, CryptoAPI):
Код:
const
  CP_GR3410_2001_PROV_A: AnsiString = 'Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider';
  szOID_CP_GOST_28147 = '1.2.643.2.2.21'; // Алгоритм шифрования ГОСТ 28147-89
  PROV_GOST_2001_DH = 75;
  CALG_GR3411 = 32798;

function MakeStringFromLines(const Lines: array of AnsiString; ReturnChars: AnsiString; AddLastReturn: Boolean): AnsiString;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to High(Lines) do
  begin
    if AddLastReturn or (I <> High(Lines)) then
      Result := Result + Lines[I] + ReturnChars
    else
      Result := Result + Lines[I];
  end;
end;

function GetSignedInfo(DataPacketNIHashBase64: AnsiString): AnsiString;
begin
  Result := MakeStringFromLines([
    '<SignedInfo>',
    '<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>',
    '<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"/>',
    '<Reference URI="">',
    '<Transforms>',
    '<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>',
    '<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>',
    '</Transforms>',
    '<DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/>',
    '<DigestValue>' + DataPacketNIHashBase64 + '</DigestValue>',
    '</Reference>',
    '</SignedInfo>'
   ], #10, False);
end;

function GetSignData(const SignedInfoNode, SignatureValueBase64, X509CertificateBase64: AnsiString): AnsiString;
begin
  Result := MakeStringFromLines([
    '<Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">',
    SignedInfoNode,
    '<SignatureValue>' + SignatureValueBase64 + '</SignatureValue>',
    '<KeyInfo>',
    '<X509Data>',
    '<X509Certificate>' + X509CertificateBase64 + '</X509Certificate>',
    '</X509Data>',
    '</KeyInfo>',
    '</Signature>'
   ], #10, False);
end;

procedure SignXml(Data: AnsiString; const SignContainerName: string; const CertificateDataForEncrypt: AnsiString; out SignedXml: AnsiString; out EncryptedXml: AnsiString);

  function GetHash(CryptProv: HCRYPTPROV; const Data: AnsiString; SignIt: Boolean): AnsiString;
  var
    CryptHash: HCRYPTHASH;
    HashSize: DWORD;
    DataSize: DWORD;
    SignSize: DWORD;
    HashStr: AnsiString;
    SignStr: AnsiString;
  begin
    if not CryptCreateHash(CryptProv, CALG_GR3411, 0, 0, CryptHash) then
      CryptoError('CryptCreateHash(CALG_GR3411)');

    try
      if not CryptHashData(CryptHash, PByte(Data), Length(Data), 0) then
        CryptoError('CryptHashData');

      DataSize := SizeOf(DataSize);
      if not CryptGetHashParam(CryptHash, HP_HASHSIZE, @HashSize, DataSize, 0) then
        CryptoError('CryptGetHashParam(HP_HASHSIZE)');

      if HashSize = 0 then
        Assert(False);
      SetLength(HashStr, HashSize);

      if not CryptGetHashParam(CryptHash, HP_HASHVAL, PByte(HashStr), HashSize, 0) then
        CryptoError('CryptGetHashParam(HP_HASHVAL)');

      if SignIt then
      begin
        SignSize := 0;
        if not CryptSignHash(CryptHash, AT_KEYEXCHANGE, nil, 0, nil, SignSize) then
          CryptoError('CryptSignHash(AT_KEYEXCHANGE length)');

        if SignSize = 0 then
          Assert(False);
        SetLength(SignStr, SignSize);

        if not CryptSignHash(CryptHash, AT_KEYEXCHANGE, nil, 0, PByte(SignStr), SignSize) then
          CryptoError('CryptSignHash(AT_KEYEXCHANGE data)');

        Result := SignStr;
      end
      else
        Result := HashStr;

    finally
      if not CryptDestroyHash(CryptHash) then
        CryptoError('CryptDestroyHash');
    end;
  end;

var
  Prov: HCRYPTPROV;
  hKey: HCRYPTKEY;
  StartIdx: Integer;
  EndIdx: Integer;
  DataPacketNINode: AnsiString;
  DataPacketNINodeHash: AnsiString;
  DataPacketNINodeHashBase64: AnsiString;
  SignedInfoNode: AnsiString;
  SignedInfoNodeSign: AnsiString;
  SignedInfoNodeSignBase64: AnsiString;
  UserCert: AnsiString;
  UserCertLength: DWORD;
  UserCertBase64: AnsiString;
begin
  // заменяем #13#10 на #10
  Data := ReplaceStr(Data, #13#10, #10);
  // заменяем #10#10 на #10
  while Pos(#10#10, Data) <> 0 do
    Data := ReplaceStr(Data, #10#10, #10);

  // убираем #10 с конца
  while Data[Length(Data)] = #10 do
    SetLength(Data, Length(Data) - 1);

  // ищем начало и конец подписываемых данных в соответствии со спецификацией в rtf-документе
  StartIdx := Pos('<DATA_PACKET_NI', UpperCase(Data));
  if StartIdx = 0 then
    raise ESignError.Create('в документе не найден <DATA_PACKET_NI');

  EndIdx := Pos('</DATA_PACKET_NI>', UpperCase(Data));
  if EndIdx = 0 then
    raise ESignError.Create('в документе не найден </DATA_PACKET_NI>');

  DataPacketNINode := Copy(Data, StartIdx, (EndIdx + Length('</DATA_PACKET_NI>') - 1) - StartIdx + 1);
  // сохраняем DataPacketNINode в файл (см. DataPacketNINode.xml в аттаче)
  SaveFileData(ExtractFilePath(ParamStr(0)) + 'DataPacketNINode.xml', DataPacketNINode);

  if not CryptAcquireContext(Prov, PChar(SignContainerName), PAnsiChar(CP_GR3410_2001_PROV_A), PROV_GOST_2001_DH, 0) then
    CryptoError('CryptAcquireContext');
  try
    // AT_SIGNATURE не проходит, поэтому подписываем тем что есть
    if not CryptGetUserKey(Prov, AT_KEYEXCHANGE, hKey) then
      CryptoError('CryptGetUserKey');
    // достаём сертификат чтобы положить его в <X509Certificate>
    if not CryptGetKeyParam(hKey, KP_CERTIFICATE, nil, UserCertLength, 0) then
      CryptoError('CryptGetKeyParam(KP_CERTIFICATE length)');

    if UserCertLength = 0 then
      Assert(False);
    SetLength(UserCert, UserCertLength);

    if not CryptGetKeyParam(hKey, KP_CERTIFICATE, PByte(UserCert), UserCertLength, 0) then
      CryptoError('CryptGetKeyParam(KP_CERTIFICATE data)');

    UserCertBase64 := ReplaceStr(Base64EncodeStr(UserCert), #13#10, ''); // сертификат без лишних символов, как и положено по тех. спецификации из rtf
    
    // вычисляем только хэш (без подписи)
    DataPacketNINodeHash := GetHash(Prov, DataPacketNINode, False);
    // хеш -> base64
    DataPacketNINodeHashBase64 := ReplaceStr(Base64EncodeStr(DataPacketNINodeHash), #13#10, '');

    // генерируем <SignedInfo>
    SignedInfoNode := GetSignedInfo(DataPacketNINodeHashBase64);
    // сохраняем SignedInfoNode в файл (см. SignedInfoNode.xml в аттаче)
    SaveFileData(ExtractFilePath(ParamStr(0)) + 'SignedInfoNode.xml', SignedInfoNode);
    
    // считаем хэш от SignedInfoNode и подписывем его
    SignedInfoNodeSign := GetHash(Prov, SignedInfoNode, True);
    SignedInfoNodeSignBase64 := ReplaceStr(Base64EncodeStr(SignedInfoNodeSign), #13#10, '');
    
    // генерируем <Signature> по SignedInfoNode, SignedInfoNodeSignBase64, UserCertBase64
    // вставляем <Signature> + #10 в документ перед </DATA_PACKET_NI>
    SignedXml := Data;
    Insert(GetSignData(SignedInfoNode, SignedInfoNodeSignBase64, UserCertBase64) + #10, SignedXml, EndIdx);
    // в SignedXml получается файл DataProcessed.xml из аттача

    // это шифрование 
    EncryptedXml := EncryptData(CertificateDataForEncrypt, SignedXml);
  finally
    CryptReleaseContext(Prov, 0);
  end;
end;



Что-то не так с подписью.
Вот что получается в результате подписывания (файл DataProcessed.xml в аттаче):
Код:
<?xml version="1.0" encoding="utf-8"?>
<DATA_PACKET_NI Data="Data...">
<Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/>
<DigestValue>HSaToYRVvJJp2gvXVU5rwg7G95Q9RWKOnL8ryLN6Ja0=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>CyyX2Slyb79QdC0opTBjxnn8+5/3LNQU4PKzpcpObRb5Ex7g3gNLDFojc0XmBXr8/HhEkRU/rymfsFdDfzVKEA==</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>тут лежит сертификат</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</DATA_PACKET_NI>


Вроде всё в соответствии с тех.заданием, но что-то не так. Подпись не проверяется у оператора.
Наверняка я что-то делаю не так, но не могу понять что. Подскажите пожалуйста.
Offline Nicky711  
#2 Оставлено : 23 мая 2018 г. 11:33:36(UTC)
Nicky711

Статус: Новичок

Группы: Участники
Зарегистрирован: 15.05.2018(UTC)
Сообщений: 8
Российская Федерация

Заметил что вот этого нет в rtf-документе: <Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
Есть только <Signature>
Сделал как просят. Проверку подпись так и не проходит.
Offline not_x  
#3 Оставлено : 29 мая 2018 г. 9:30:07(UTC)
not_x

Статус: Участник

Группы: Участники
Зарегистрирован: 27.06.2017(UTC)
Сообщений: 16
Российская Федерация

<Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">

такого быть не может в XML - файле. Если пространство имён определено (xmlns:ds=), то теги должны быть ds:<имя тега> как минимум. Посмотрите XmlSign и соответственно каноникализация с14.
Offline Nicky711  
#4 Оставлено : 29 мая 2018 г. 14:03:42(UTC)
Nicky711

Статус: Новичок

Группы: Участники
Зарегистрирован: 15.05.2018(UTC)
Сообщений: 8
Российская Федерация

Автор: not_x Перейти к цитате
<Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">

такого быть не может в XML - файле. Если пространство имён определено (xmlns:ds=), то теги должны быть ds:<имя тега> как минимум. Посмотрите XmlSign и соответственно каноникализация с14.


Исправил. Теперь проблема другая: https://www.cryptopro.ru/forum2/default.aspx?g=posts&t=13984
Offline not_x  
#5 Оставлено : 30 мая 2018 г. 9:53:12(UTC)
not_x

Статус: Участник

Группы: Участники
Зарегистрирован: 27.06.2017(UTC)
Сообщений: 16
Российская Федерация

Общая схема через CriptoAPI такая:
1.Добавляем теги как написано, добавляем метку wsu:Id
2.Вычисляем хэш от каноникализированного тега, в который добавили wsu:Id, и помещаем base64(хэш) в DigestValue (см 1.9)
3.Каноникализируем тег Signature, вычисляем хэш, вычисляем ЭП по закрытому ключу, получаем ЭП и...меняем порядок байтов!!! (в КриптоПро так)
4.base64(эп) помещаем в SignatureValue.

По п.3 - описка Каноникализируем тег SignedInfo

Отредактировано пользователем 31 мая 2018 г. 4:12:18(UTC)  | Причина: описка

Offline Nicky711  
#6 Оставлено : 30 мая 2018 г. 20:42:00(UTC)
Nicky711

Статус: Новичок

Группы: Участники
Зарегистрирован: 15.05.2018(UTC)
Сообщений: 8
Российская Федерация

Автор: not_x Перейти к цитате
Общая схема через CriptoAPI такая:
1.Добавляем теги как написано, добавляем метку wsu:Id
2.Вычисляем хэш от каноникализированного тега, в который добавили wsu:Id, и помещаем base64(хэш) в DigestValue (см 1.9)
3.Каноникализируем тег Signature, вычисляем хэш, вычисляем ЭП по закрытому ключу, получаем ЭП и...меняем порядок байтов!!! (в КриптоПро так)
4.base64(эп) помещаем в SignatureValue.


Странно. У меня другая последовательность:
0. Данные xml-файла = XmlFileData.
1. Извлекаем ноду данных из XmlFileData (получается DataNode).
2. Каноникализируем DataNode (получается DataNodeCanonicalized).
3. Берём хэш от DataNodeCanonicalized (получается DataNodeCanonicalizedHash) .
4. Конвертируем DataNodeCanonicalizedHash в base64 и удаляем из результата переводы строки (#13#10) (получаем DataNodeCanonicalizedHashBase64).
5. Ложим DataNodeCanonicalizedHashBase64 в такую структуру (перевод строки в этой структуре у меня равен #10) (получается SignedInfoNode):
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/>
<DigestValue>:DataCanonicalizedHashBase64</DigestValue>
</Reference>
</SignedInfo>

6. Каноникализируем SignedInfoNode (получается SignedInfoNodeCanonicalized).
7. Подписываем SignedInfoNodeCanonicalized (получается SignedInfoNodeSign).
8. Инвертируем SignedInfoNodeSign (получается SignedInfoNodeSignInverted).
9. Конвертируем SignedInfoNodeSignInverted в base64 и удаляем из результата переводы строки (#13#10) (получается SignedInfoNodeSignInvertedBase64).
10. Создаём такую структуру (используем сертификат лежащий в X509CertificateBase64) (перевод строки в этой структуре у меня равен #10) (получается SignatureNode):
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
:SignedInfoNode
<SignatureValue>:SignedInfoNodeSignInvertedBase64</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>:X509CertificateBase64</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>

11. Вставляем SignatureNode в XmlFileData перед закрывающим тегом данных и перед символами перевода строки что перед тегом (может быть #13#10 или #10 или без перевода).

Такая подпись успешно проверяется тут: https://dss.cryptopro.ru/Verify/Verify/ или тут: https://www.justsign.me/verifyqca/
Offline not_x  
#7 Оставлено : 31 мая 2018 г. 4:10:52(UTC)
not_x

Статус: Участник

Группы: Участники
Зарегистрирован: 27.06.2017(UTC)
Сообщений: 16
Российская Федерация

Извиняюсь, описался - конечно же по п.3 каноникализируется и подписывается SignedInfo.

По п.5 если каноникализируеncz именно это XML-структура, то это неверно - нет namespace.
Соответственно должно быть <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
Обычно стараются использовать явное указание пространства имён: <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> и ниже лежащие теги соответствующий образом каноникализируются при подписании с добавлением namespace.

По стандарту XMLDSig wsu:Id можно не добавлять в тело, но тогда в <Reference URI=""> должно находиться выражение XPATH

Порядок когда добавлять теги Signature: до или после подписания - смотрите по конкретной спецификации сервиса: люди, которые их пишут не всегда дружат со стандартами.
Offline Nicky711  
#8 Оставлено : 31 мая 2018 г. 9:23:01(UTC)
Nicky711

Статус: Новичок

Группы: Участники
Зарегистрирован: 15.05.2018(UTC)
Сообщений: 8
Российская Федерация

Автор: not_x Перейти к цитате
Извиняюсь, описался - конечно же по п.3 каноникализируется и подписывается SignedInfo.

По п.5 если каноникализируеncz именно это XML-структура, то это неверно - нет namespace.
Соответственно должно быть <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">

Да, нужно добавить пункт 5.1:
Добавляем в SignedInfoNode (в <SignedInfo>) xmlns="http://www.w3.org/2000/09/xmldsig#" (получается SignedInfoNodeX).

И п.6. после этого выглядит так:
Каноникализируем SignedInfoNodeX (получается SignedInfoNodeCanonicalized).

Автор: not_x Перейти к цитате
Порядок когда добавлять теги Signature: до или после подписания - смотрите по конкретной спецификации сервиса: люди, которые их пишут не всегда дружат со стандартами.

Хочется исходить из того, что на стороне сервиса со стандартами всё хорошо :)
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.