2.4.5. Цифровая подпись XML сертификатом ГОСТ3411

Подпись XML предлагается генерировать с помощью CryptoAPI и LibXml2.

Шаблон подписи XAdES-BES
<ds:Signature  xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="xmldsig"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"/><ds:Reference URI="#foo"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/><ds:DigestValue/></ds:Reference><ds:Reference URI="#signed-props" Type="http://uri.etsi.org/01903#SignedProperties"><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/><ds:KeyInfo><ds:X509Data><ds:X509Certificate/></ds:X509Data></ds:KeyInfo><ds:Object><xades:QualifyingProperties Target="#xmldsig" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"><xades:SignedProperties Id="signed-props"><xades:SignedSignatureProperties><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/><ds:DigestValue/></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName/><ds:X509SerialNumber/></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature>
Подписание XML-документа, подпись XAdES-BES
<PASCAL AXml, AId, ASignPattern>
uses
  XML;

function GetDigest(AData, ACert, AToSign): string;
begin
  hash := Cryptor.CreateHashByCert(ACert);
  hash.HashData(AData);
  if AToSign then
  begin
    Result := hash.Sign;
    Result := BlobRevers(Result);
  end else
    Result := hash.Value

  Result := BlobToBase64(Result);
end;

procedure PutCanonicDigest(ADoc, AXPathCtx, ACertToDigest, AExclusiveCanon, ASignature, ADataToDigestXPath, ADigestValueNodeXPath);
begin
  cData := AXPathCtx.FindNodes(ADataToDigestXPath);
  cText := ADoc.C14nCanonicalize(AExclusiveCanon, cData);
  AXPath.FindNode(ADigestValueNodeXPath).Text := GetDigest(StringToBlob(cText), ACertToDigest, ASignature);
end;

SignDoc := CreateXmlDocFromString(ASignPattern);
Doc := CreateXmlDocFromString(AXml);
XPath := Doc.CreateXPathContext;
XPath.AddNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
XPath.AddNamespace('xades', 'http://uri.etsi.org/01903/v1.3.2#');

// Встроить шаблон подписи в документ
XPath.FindNode('//*[@Id="'+AId+'"]').Child.AddPrevSibling(SignDoc.Root);
XPath.FindNode('//*[@URI="#foo"]').Attributes['URI'] := '#'+AId;
SignDoc := nil;

// Заполнить поля подписи данными сертификата
Cert := Cryptor.CreateCertByDialog;
XPath.FindNode('//ds:X509Certificate').Text := BlobToBase64(Cert.RawData);
XPath.FindNode('//xades:CertDigest/ds:DigestValue').Text := GetDigest(Cert.RawData, Cert, False);
XPath.FindNode('//ds:X509IssuerName').Text := Cert.IssuerName(cnfRFC1779);
XPath.FindNode('//ds:X509SerialNumber').Text := BlobIntToStr(Cert.SerialNumber);

// рассчитать хеши подписи
PutCanonicDigest(Doc, XPath, Cert, True, False,
  '(//. | //@* | //namespace::*)[ancestor-or-self::*[@Id="'+AId+'"]][not(ancestor-or-self::ds:Signature)]',
  '//*[@URI="#'+AId+'"]/ds:DigestValue');

PutCanonicDigest(Doc, XPath, Cert, False, False,
  '(//.|//@*|//namespace::*)[ancestor-or-self::xades:SignedProperties[1]]',
  '//*[@URI=concat("#", //xades:SignedProperties[1]/@Id)]/ds:DigestValue');

PutCanonicDigest(Doc, XPath, Cert, False, True,
  '(//. | //@* | //namespace::*)[ancestor-or-self::ds:SignedInfo]',
  '//ds:Signature[1]/ds:SignatureValue');

Result := Doc.ToString;
</PASCAL>

Note

XPath-запрос для каноникализации и расчёта хешей должен возвращать не только тот узел что планируется подписать, но и все его подузлы вместе с атрибутами и объявленными namespace-ами.

Например: //ds:Body - неправильный запрос, он возвращает только сам элемент Body.

Правильно будет так: (//. | //@* | //namespace::*)[ancestor-or-self::ds:Body] - запрос возвращает Body целиком, со всем его содержимым.