EldoS | Feel safer!

Software components for data protection, secure storage and transfer

Delphi, XML -- TElXMLDOMDocument alteres document

Also by EldoS: MsgConnect
Cross-platform protocol-independent communication framework for building peer-to-peer and client-server applications and middleware components.
#28737
Posted: 03/08/2014 03:19:20
by Jon Lennart Aasenden (Basic support level)
Joined: 03/08/2014
Posts: 1

My task: Create a simple delphi class that loads a government defined xml document, signs this using a smartcard, save the exact results to a stream, convert to BASE64 - and send to a soap server.

I have pieced together a signing class from the examples and online documentation. The signing process works fine. The certificate and hash are decoded properly by the reciever -- but for some reason the TElXMLDOMDocument alters the XML data.

It seems to generate the signaure based on the original xml, but when i save the xml from TElXMLDOMDocument with the signature, the xml that comes out is not the same that came in.

1. It replaces unix LF with CR + LF (one byte extra per line)
2. It changes uppercase encoding="UTF-8" into lowercase "utf-8"

So when i send the XML, the document never validates, because the signature doesnt match the output XML, only the input XML.

Is there some way of telling the TElXMLDOMDocument object to not modify the XML in any way - except the insertion of the signature tag?

As it stands now we first have to load the XML into TElXMLDOMDocument, save it out, then load it back in again for the source-xml and signed-xml to match.

Code
procedure TNextXMLSign.LoadFromStream(aXMLStream: TStream);

  function CheckBOM(const aData:TStream):Boolean;
  var
    mChar:  Byte;
  Begin
    result:=( (aData.Read(mChar,1)=1) and (mChar=$EF))
    and     ( (aData.Read(mChar,1)=1) and (mChar=$BB))
    and     ( (aData.Read(mChar,1)=1) and (mChar=$BF));
  end;

var
  mCache: TStream;
begin
  Clear;

  FXML:=TElXMLDOMDocument.Create;
  try
    mCache:=TMemoryStream.Create;
    try
      (* Does BOm exist? Skip 3 bytes to strip it *)
      if CheckBOM(aXMLStream) then
      Begin
        aXMLStream.Position:=3;
        mCache.CopyFrom(aXMLStream,aXMLStream.Size-aXMLStream.Position);
        mCache.Position:=0;
      end else
      Begin
        (* No BOM, copy entire stream *)
        aXMLStream.Position:=0;
        mCache.CopyFrom(aXMLStream,aXMLStream.Size);
        mCache.Position:=0;
      end;

      (* Load into XML *)
      FXML.LoadFromStream(mCache);

      (* Save out again, to make sure CR+LF+case matches later.
         The XML document alters things like uppercase/ CRLF etc.
         By doing it this way, the signed document will always match
         the data we send *)
      mCache.Size:=0;
      FXML.SaveToStream(mCache,xcmNone,FCodec);
      mCache.Position:=0;

      //TMemoryStream(mCache).SaveToFile('c:\xml_input02.xml');

      (* And re-load the now formated XML, so that the signature
         match what we except to find *)
      FreeAndNIL(FXML);
      FXML:=TElXMLDOMDocument.Create;
      FXMl.LoadFromStream(mCache);

    finally
      mCache.Free;
    end;
  except
    on e: exception do
    begin
      FreeAndNIL(FXML);
      Raise Exception.CreateFmt
      ('Failed to load XML, TElXMLDOMDocument threw exception: %s',
      [e.Message]);
    end;
  end;
end;


The signing routine:

Code

function TNextXMLSign.Sign(aProvider:String;var aTargetStream:TStream):Boolean;
var
  mSigner:  TElXMLSigner;
  mRefList: TELXMLReferenceList;
  mRef: TELXmlReference;
  mData:  TElXMLKeyInfoX509Data;
  mDummy: TELXmlDOMNode;
begin
  result:=False;
  aTargetStream:=NIL;

  (* Release current certificate if already used *)
  if assigned(FCert) then
  freeAndNIL(FCert);

  aProvider:=trim(aprovider);
  if length(aProvider)>0 then
  begin

    (* Check that XML is loaded *)
    if assigned(FXML) then
    begin
      if FindValidCert(aProvider, FCert) then
      begin
        (* Setup signer *)
        mSigner:=TElXMLSigner.Create(NIL);
        try
          (* Initialize signing control *)
          mSigner.SignatureType:=TElXmlSignatureType.xstEnveloped;
          mSigner.SignatureMethodType:=TELXMLSigMethodType.xmtSig;
          mSigner.KeyName:=BinaryToString(FCert.SerialNumber);
          mSigner.IncludeKey:=true;
          mSigner.SignatureMethod:=TELXMLSignatureMethod.xsmRSA_SHA1;
          mSigner.SignatureCompliance:=TELXMLSignatureCompliance.xscDSIG;

          (* Create reference list
             NOTE: The signer-control releases this on destruction!
                   DO NOT FREE *)
          mRefList:=TELXMLReferenceList.Create;
          mRef:=TELXmlReference.Create;
          mRef.DigestMethod:=TELXMLDigestMethod.xdmSHA1;
          mRef.URINode := FXml.DocumentElement;
          mRef.URI := '';

          (* Add XML transform for signature *)
          mRef.TransformChain.Add(TElXMLEnvelopedSignatureTransform.Create);
          mRef.TransformChain.Add(TElXMLC14NTransform.Create);
          mRefList.Add(mRef);

          (* Get the certificate key data for export
             NOTE: The signer-control releases this on destruction!
                   DO NOT FREE *)
          mData := TElXMLKeyInfoX509Data.Create(False);
          mData.Certificate := FCert;
          mSigner.KeyData := mData;

          (* Hook up references *)
          mSigner.References:=mRefList;

          (* Update the digest paths *)
          mSigner.UpdateReferencesDigest;

          (* Generate signature and sign *)
          mSigner.GenerateSignature;
          mSigner.Sign;

          (* This triggers the certificate and causes a buypass dialog *)
          mDummy:=FXml.DocumentElement.ChildNodes[0].ParentNode;
          mSigner.Save(mDummy);

          aTargetStream:=TMemoryStream.Create;

          FXML.SaveToStream(aTargetStream,xcmNone,FCodec);
          aTargetStream.Position:=0;

          result:=true;
        finally
          mSigner.Free;
        end;
      end else
      Raise Exception.Create('Sign failed, a valid certificate could not be found');
    end else
    Raise Exception.Create('Sign failed, no XML loaded error');
  end else
  raise Exception.Create('Sign failed, provider string was empty');
end;
#28738
Posted: 03/08/2014 17:16:07
by Dmytro Bogatskyy (EldoS Corp.)

Thank you for contacting us.

Quote
1. It replaces unix LF with CR + LF (one byte extra per line)

TElXMLDOMDocument doesn't do such replacement. Maybe you meant that it replace in opposite direction (CR LF -> LF)?
Then, yes, TElXMLDOMDocument do perform new-line normalization on document loading (the normalization options is controlled by a third parameter in the TElXMLDOMDocument.LoadFromStream method).
See: http://www.eldos.com/documentation/sb...tream.html
If you set it to true (default value) a CRLF and CR characters will be replaced by LF character.
Quote
2. It changes uppercase encoding="UTF-8" into lowercase "utf-8"

The simplest way to change it, at the moment, is to you override GetCharsetName method of TElXMLUTF8Codec class.
Please see the sample here:
https://www.eldos.com/forum/read.php?F...ssage18429

By the way, you don't need to remove byte order mark (BOM), the LoadFromStream method should correctly understand it.

Reply

Statistics

Topic viewed 2345 times

Number of guests: 1, registered members: 0, in total hidden: 0




|

Back to top

As of July 15, 2016 EldoS Corporation will operate as a division of /n software inc. For more information, please read the announcement.

Got it!