Pascal (Lazarus/Delphi)
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
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.