How to properly Sign XML in Delphi

2019-08-31 06:26发布

问题:

I'm trying to sign XML in Delphi with certificate, but all I'm getting are some unrecognized characters. I'm using this function

var
   xmlData, signature: PByte; data: array[0..0] of PByte;
   msgCert: array[0..0] of PCCERT_CONTEXT;
   dwDataSizeArray: array[0..0] of DWORD;
   sigParams: CRYPT_SIGN_MESSAGE_PARA;
   cbSignedBlob: DWORD;
begin
   if PCertContext = nil then
    Exit;

   GetMem(xmlData, Length(AXml));
   try
      system.Move(Pointer(AXml)^, xmlData^, Length(AXml));
      ZeroMemory(@sigParams, SizeOf(CRYPT_SIGN_MESSAGE_PARA));
      sigParams.cbSize := SizeOf(CRYPT_SIGN_MESSAGE_PARA);
      sigParams.dwMsgEncodingType := (X509_ASN_ENCODING or PKCS_7_ASN_ENCODING);

      sigParams.pSigningCert := PCertContext;
      sigParams.HashAlgorithm.pszObjId := szOID_RSA_MD5;
      //SigParams.cAuthAttr := 0;
      //SigParams.dwInnerContentType := 0;
      //SigParams.cMsgCrl := 0;
      //SigParams.cUnauthAttr := 0;
      //SigParams.dwFlags := 0;
      //SigParams.pvHashAuxInfo := nil;
      //SigParams.rgAuthAttr := nil;  }
      data[0] := xmlData;
      dwDataSizeArray[0] := Length(AXml);
      cbSignedBlob := 0;

      CryptSignMessage(@sigParams, True, 1, @data[0], @dwDataSizeArray[0], nil, @cbSignedBlob);

      GetMem(signature, cbSignedBlob);
      try
         CryptSignMessage(@sigParams, True, 1, @data[0], @dwDataSizeArray[0], signature, @cbSignedBlob);
         SetLength(Result, cbSignedBlob);
         system.Move(signature^, Pointer(Result)^, cbSignedBlob);
      finally
         FreeMem(signature);
      end;
   finally
      FreeMem(xmlData);
   end;
end

but all I get is:

'舰餁आ蘪虈'#$0DF7'܁ꀂƂりƂʆāัర'#$0806'蘪虈'#$0DF7'Ԃ'#5'ରआ蘪虈'#$0DF7'܁'#$3101'ƂぢƂɞā㠰 〰
'#$0B31'र̆ѕጆ匂ㅉ『،唃'#$0A04'ԓ佐呓ㅁ】؏唃'#$0B04#$0813'佐呓牁䅃Ђ䤾Eర'#$0806'蘪虈'#$0DF7'Ԃ'#5'രआ蘪虈'#$0DF7'āԁЀƂ堀묥䮡悕㈒古虺̐昇⠹꺶힊覬왧䮽㕺⢂꺇宬䝄'#$07B3'䲠'#$D868'㶙㌱㟜'#$DAF2'#$2B83#$1C'ity-1.0.xsd" wsu:Id="Timestamp-3c7c79b4-2afa-4184-9d2c-8f5e721c8421">2014-01-13T11:26:52Z2014-01-13T11:31:52Z'#0' OTRRC.PTT'

and there is alot more data that need to be in signed XML. In C# this was solved like this

    AsymmetricAlgorithm rsa = signCertificate.PrivateKey;

    // Read provided document, find the signature element ...
    MemoryStream documentStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(document));

    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = true;
    doc.Load(new XmlTextReader(documentStream));

    XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
    nsmgr.AddNamespace("ds", dsigURI);
    XmlNode sig = doc.SelectSingleNode("//*[@Id='" + signatureID + "']", nsmgr);

    XmlNode after = sig.PreviousSibling;
    XmlNode parent = sig.ParentNode;

    SignedXml signedXml = new SignedXml(doc);
    parent.RemoveChild(sig);

    Reference r = new Reference();
    r.Uri = "";
    r.AddTransform(new XmlDsigEnvelopedSignatureTransform());
    signedXml.AddReference(r);

    X509Certificate cert1 = new X509Certificate(signCertificate.GetRawCertData());

    KeyInfo ki = new KeyInfo();
    ki.AddClause(new KeyInfoX509Data(cert1));
    signedXml.KeyInfo = ki;

    signedXml.Signature.Id = signatureID;

    signedXml.SigningKey = rsa;
    signedXml.ComputeSignature();

    XmlNode signature = doc.ImportNode(signedXml.GetXml(), true);
    parent.InsertAfter(signature, after);

    return doc.OuterXml;                

Thanks for help in advance!

回答1:

Look at this code:

SetLength(Result, cbSignedBlob);
system.Move(signature^, Pointer(Result)^, cbSignedBlob);

I'm just guessing, but Result is probably of type string. That's a UTF-16 encoded string. But you copy in just cbSignedBlob bytes, which fills only half of the buffer. I suspect that you have either ANSI or UTF-8 encoded text, but it is a little hard to tell.

If the text is UTF-8 encoded, then this is what you do:

var
  utf8: UTF8String;
....
SetLength(utf8, cbSignedBlob);
system.Move(signature^, Pointer(utf8)^, cbSignedBlob);
Result := string(utf8);

If the text is ANSI encoded then you would do this:

var
  ansi: AnsiString(1252); // or whatever the code page really is
....
SetLength(ansi, cbSignedBlob);
system.Move(signature^, Pointer(ansi)^, cbSignedBlob);
Result := string(ansi);

There's probably more going on here but given the amount of detail presented in the question, this is as much as I can see.

For a start I am very suspicious of your lack of error checking. You call these API functions but ignore their return values. I'll wager the functions fail and your code continues regardless. Next step is to add error checking.