Sample code for 30+ languages & platforms
Pascal (Lazarus/Delphi)

Sign SOAP XML for New Zealand Customs Service

See more XAdES Examples

Demonstrates how to create an XAdES signed SOAP XML pertaining to the New Zealand Customs Service.

Note: This example requires Chilkat v9.5.0.96 or later.

Chilkat Pascal (Lazarus/Delphi) Downloads

Pascal (Lazarus/Delphi)
program ChilkatDemo;

// Demonstrates using the Chilkat Pascal wrapper via the C bridge DLL.
// Builds as a console application under Lazarus (FPC) or Delphi.

{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}
{$APPTYPE CONSOLE}

uses
  {$IFDEF UNIX}
  cthreads,
  {$ENDIF}
  SysUtils,
  CkDllLoader,
  Chilkat.StringBuilder,
  Chilkat.XmlDSigGen,
  Chilkat.Xml,
  Chilkat.CkDateTime,
  Chilkat.Cert;

// ---------------------------------------------------------------------------

procedure RunDemo;
var
  success: Boolean;
  tsId: TStringBuilder;
  strId: TStringBuilder;
  keyInfoId: TStringBuilder;
  dt: TCkDateTime;
  sbNow: TStringBuilder;
  n: Integer;
  sbNowPlusOneHour: TStringBuilder;
  xmlToSign: TXml;
  gen: TXmlDSigGen;
  xml1: TXml;
  xml2: TXml;
  cert: TCert;
  xmlCustomKeyInfo: TXml;
  sbXml: TStringBuilder;

begin
  success := False;

  //  This example assumes the Chilkat API to have been previously unlocked.
  //  See Global Unlock Sample for sample code.

  success := True;

  //  Create the following XML to be signed:

  //  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  //      xmlns:v1="http://customs.govt.nz/jbms/msggate/reqresp/v1">
  //      <soapenv:Header>
  //          <wsse:Security soapenv:mustUnderstand="1"
  //              xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
  //              xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  //              <wsu:Timestamp wsu:Id="TS-037E78514E9B9132CB16817563559151">
  //                  <wsu:Created>2023-04-17T18:32:35.913Z</wsu:Created>
  //                  <wsu:Expires>2023-04-17T19:32:35.913Z</wsu:Expires>
  //              </wsu:Timestamp>
  //          </wsse:Security>
  //      </soapenv:Header>
  //      <soapenv:Body wsu:Id="id-8"
  //          xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  //          <v1:RequestResponse>
  //              <v1:Submitter>TEST1234</v1:Submitter>
  //              <v1:MailboxMsgId>999999</v1:MailboxMsgId>
  //          </v1:RequestResponse>
  //      </soapenv:Body>
  //  </soapenv:Envelope>

  //  Create a random ID like this: TS-037E78514E9B9132CB16817563559151
  tsId := TStringBuilder.Create;
  tsId.Append('TS-');
  tsId.AppendRandom(16,'hex');

  //  STR-037E78514E9B9132CB16817563559614
  strId := TStringBuilder.Create;
  strId.Append('STR-');
  strId.AppendRandom(16,'hex');

  //  KI-037E78514E9B9132CB16817563559583
  keyInfoId := TStringBuilder.Create;
  keyInfoId.Append('KI-');
  keyInfoId.AppendRandom(16,'hex');

  //  Create a date/time for the current time with this format:  2023-04-17T18:32:35.913Z
  dt := TCkDateTime.Create;
  dt.SetFromCurrentSystemTime();

  sbNow := TStringBuilder.Create;
  sbNow.Append(dt.GetAsTimestamp(False));
  //  If we really need the milliseconds, we can replace the "Z" with ".000Z"
  //  The server will also likely accept a timestamp without milliseconds, such as 2023-04-17T18:32:35Z
  n := sbNow.Replace('Z','.000Z');

  sbNowPlusOneHour := TStringBuilder.Create;
  dt.AddSeconds(3600);
  sbNowPlusOneHour.Append(dt.GetAsTimestamp(False));
  n := sbNowPlusOneHour.Replace('Z','.000Z');

  xmlToSign := TXml.Create;
  xmlToSign.Tag := 'soapenv:Envelope';
  xmlToSign.AddAttribute('xmlns:soapenv','http://schemas.xmlsoap.org/soap/envelope/');
  xmlToSign.AddAttribute('xmlns:v1','http://customs.govt.nz/jbms/msggate/reqresp/v1');
  xmlToSign.UpdateAttrAt('soapenv:Header|wsse:Security',True,'soapenv:mustUnderstand','1');
  xmlToSign.UpdateAttrAt('soapenv:Header|wsse:Security',True,'xmlns:wsse','http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd');
  xmlToSign.UpdateAttrAt('soapenv:Header|wsse:Security',True,'xmlns:wsu','http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd');
  xmlToSign.UpdateAttrAt('soapenv:Header|wsse:Security|wsu:Timestamp',True,'wsu:Id',tsId.GetAsString());
  xmlToSign.UpdateChildContent('soapenv:Header|wsse:Security|wsu:Timestamp|wsu:Created',sbNow.GetAsString());
  xmlToSign.UpdateChildContent('soapenv:Header|wsse:Security|wsu:Timestamp|wsu:Expires',sbNowPlusOneHour.GetAsString());
  xmlToSign.UpdateAttrAt('soapenv:Body',True,'wsu:Id','id-8');
  xmlToSign.UpdateAttrAt('soapenv:Body',True,'xmlns:wsu','http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd');
  xmlToSign.UpdateChildContent('soapenv:Body|v1:RequestResponse|v1:Submitter','TEST1234');
  xmlToSign.UpdateChildContent('soapenv:Body|v1:RequestResponse|v1:MailboxMsgId','999999');

  gen := TXmlDSigGen.Create;

  gen.SigLocation := 'soapenv:Envelope|soapenv:Header|wsse:Security';
  gen.SigLocationMod := 0;
  gen.SigId := 'SIG-037E78514E9B9132CB16817563559695';
  gen.SigNamespacePrefix := 'ds';
  gen.SigNamespaceUri := 'http://www.w3.org/2000/09/xmldsig#';
  gen.SignedInfoPrefixList := 'soapenv v1';
  gen.IncNamespacePrefix := 'ec';
  gen.IncNamespaceUri := 'http://www.w3.org/2001/10/xml-exc-c14n#';
  gen.SignedInfoCanonAlg := 'EXCL_C14N';
  gen.SignedInfoDigestMethod := 'sha256';

  //  Set the KeyInfoId before adding references..
  gen.KeyInfoId := keyInfoId.GetAsString();

  //  -------- Reference 1 --------
  xml1 := TXml.Create;
  xml1.Tag := 'ds:Transforms';
  xml1.UpdateAttrAt('ds:Transform',True,'Algorithm','http://www.w3.org/2001/10/xml-exc-c14n#');
  xml1.UpdateAttrAt('ds:Transform|ec:InclusiveNamespaces',True,'PrefixList','wsse soapenv v1');
  xml1.UpdateAttrAt('ds:Transform|ec:InclusiveNamespaces',True,'xmlns:ec','http://www.w3.org/2001/10/xml-exc-c14n#');

  gen.AddSameDocRef2(tsId.GetAsString(),'sha256',xml1,'');

  //  -------- Reference 2 --------
  xml2 := TXml.Create;
  xml2.Tag := 'ds:Transforms';
  xml2.UpdateAttrAt('ds:Transform',True,'Algorithm','http://www.w3.org/2001/10/xml-exc-c14n#');
  xml2.UpdateAttrAt('ds:Transform|ec:InclusiveNamespaces',True,'PrefixList','v1');
  xml2.UpdateAttrAt('ds:Transform|ec:InclusiveNamespaces',True,'xmlns:ec','http://www.w3.org/2001/10/xml-exc-c14n#');

  gen.AddSameDocRef2('id-8','sha256',xml2,'');

  //  Provide a certificate + private key. (PFX password is test123)
  cert := TCert.Create;
  success := cert.LoadPfxFile('qa_data/pfx/cert_test123.pfx','test123');
  if (success <> True) then
    begin
      WriteLn(cert.LastErrorText);
      Exit;
    end;
  gen.SetX509Cert(cert,True);

  gen.KeyInfoType := 'Custom';

  //  Create the custom KeyInfo XML..
  xmlCustomKeyInfo := TXml.Create;
  xmlCustomKeyInfo.Tag := 'wsse:SecurityTokenReference';
  xmlCustomKeyInfo.AddAttribute('wsu:Id',strId.GetAsString());
  xmlCustomKeyInfo.UpdateAttrAt('wsse:KeyIdentifier',True,'EncodingType','http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary');
  xmlCustomKeyInfo.UpdateAttrAt('wsse:KeyIdentifier',True,'ValueType','http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3');
  //  Insert the single-line base64 of the signing certificate's DER
  cert.UncommonOptions := 'Base64CertNoCRLF';
  xmlCustomKeyInfo.UpdateChildContent('wsse:KeyIdentifier',cert.GetEncoded());

  xmlCustomKeyInfo.EmitXmlDecl := False;
  gen.CustomKeyInfoXml := xmlCustomKeyInfo.GetXml();

  //  Load XML to be signed...
  sbXml := TStringBuilder.Create;
  xmlToSign.GetXmlSb(sbXml);

  gen.Behaviors := 'IndentedSignature';

  //  Sign the XML...
  gen.VerboseLogging := True;
  success := gen.CreateXmlDSigSb(sbXml);
  if (success <> True) then
    begin
      WriteLn(gen.LastErrorText);
      Exit;
    end;

  //  Save the signed XML to a file.
  success := sbXml.WriteFile('c:/temp/qa_output/signedXml.xml','utf-8',False);

  WriteLn(sbXml.GetAsString());


  tsId.Free;
  strId.Free;
  keyInfoId.Free;
  dt.Free;
  sbNow.Free;
  sbNowPlusOneHour.Free;
  xmlToSign.Free;
  gen.Free;
  xml1.Free;
  xml2.Free;
  cert.Free;
  xmlCustomKeyInfo.Free;
  sbXml.Free;

end;

// ---------------------------------------------------------------------------

begin

  try
    RunDemo;
  except
    on E: Exception do
      WriteLn('Unhandled exception: ', E.ClassName, ': ', E.Message);
  end;

  WriteLn;
  {$IFDEF MSWINDOWS}
  WriteLn('Press Enter to exit...');
  ReadLn;
  {$ENDIF}
end.