Статус: Новичок
Группы: Участники
Зарегистрирован: 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>
Вроде всё в соответствии с тех.заданием, но что-то не так. Подпись не проверяется у оператора. Наверняка я что-то делаю не так, но не могу понять что. Подскажите пожалуйста.
|