Hi, Having solved the issue we were having digitally signing our xml I thought I should post something just in case anyone else out there has similar issues. Apologies if this is quite a long post but hopefully it might help someone! We make no claims that this was the perfect solution - but it worked - eventually.
The following can be used where a Web Service call needs to be made where a Digital Certificate is required and the XML request message has to include a Digital Signature for XML Security.
Note: We have manually built our SOAP message including the Security node and its child nodes/elements and then used the os-command to make a synchronous cURL command-line call to send the XML message and receive the response.
To use a provided Digital Certificate:
We were provided with a P12 certificate that has a private key to be able communicate with the destination Web Service with a valid “handshake”. To use this in the cURL command, the following was performed:
· A PEM file was produced that contained the Client Certificate and Private Key from the P12 file by using the openssl command: “openssl pkcs12 –in [cert file].p12 –out [cert file].pem –clcerts”
· The cURL command includes a –E parameter of the PEM file (all programmatically included in the command from our software by being able to reference the PEM file and this is stored in a cLOB field on our database). The command is: “cURL -E [cert file].pem:[PEM Pass Phrase] -H "Content-Type:text/xml; charset=UTF-8" -H "SOAPAction:[service name]" -d @[request message].xml -X POST [Web Service URL] > [response message].xml”
To build the XML Security elements:
To be able to have a request message validated, as well as the digital certificate “handshake” the message had to include security elements for a digital signature value, which was calculated as follows (using pre-OASIS security standards http://schemas.xmlsoap.org/ws/2002/07/secext - OASIS security standards can also be implemented): · A single digest value of a sub-document section of the XML message was required. The sub-document was built using the SAX Writer and then this longchar was converted using the SHA1-DIGEST function and then the BASE64-ENCODE function. Note that the sub-document to be included in the digest value must have an “Id” attribute with a value that is the same as the Reference element’s “URI” attribute (e.g. and a .
· The signature value is digest of the node (which includes the digest value produced above) using the private key from the PEM file. To do this, the SHA1-DIGEST function seemed to produce an incorrect value. Therefore, the value was calculated using the openssl command (via os-command) as follows:
- openssl dgst -sha1 –sign [cert file].pem –passin pass:[PEM Pass Phrase] –out [sha1 file] [XML sub-document]
- openssl base64 –in [sha1 file] –out [base64 file]
- copy-lob from file “[base64 file]” to [longchar variable – signature value]
· The XML sub-documents used to calculate the digest value and signature value must both be canonicalised (exclusive canonicalization in this case - http://www.w3.org/2001/10/xml-exc-c14n#). There is no canonicalization (C14N) conversion functionality provided by OpenEdge, so the following was performed on the XML sub-documents before they were hashed/base64-encoded: - All empty elements must be defined with start and end tags e.g. “ instead of . Therefore, when building the XML using SAX Writer the START-ELEMENT and END-ELEMENT methods were used instead of WRITE-EMPTY-ELEMENT. Note that the XML document was re-opened using DOM (X-Document) to set a node value on some empty elements (digest value, etc), but on the SAVE method OpenEdge decided to automatically re-format the XML document so empty start and end tags were replaced with the shorted , which was a pain. We wrote a function to manipulate the XML longchar to put the start and end tags back (function included below)
- Leave the FORMATTED attribute of the SAX Writer object as FALSE, so no whitespace/line-feeds should be included.
- Ensure the namespaces and attributes of a node are supplied in alphabetical order, with all namespaces first before attributes. Therefore, ensure all DECLARE-NAMESPACE methods are applied before INSERT-ATTRIBUTE. Note that with a node without a prefix, it’s namespace declaration will be 1st in order. E,g,
- Any namespace prefix referenced within the XML message to be signed must have the xmlns declared once at the highest point where referenced (as this is a sub-document of the entire XML message it may require a namespace declaration declared in the whole XML document in a parent node).
- All element and attribute values must be html-encoded for special characters.
- Trim any line-feed characters from the XML longchar after it has been written.
Some of the data enclosed in the following XML documents has been removed for confidentiality reasons:
Example XML message (complete):
-
-
-
MIICvzCCAacCEHu...
-
-
-
-
9hlO6am...
RbEmOsSusAq7tx...
-
-
-
XML sub-document for Digest Value:
XML sub-document for Signature Value:
9hlO6am...
Function to replace empty element tags with start and end tags - note this worked for us but may well need some changes when applied elsewhere:
function fnCanonical returns longchar (input p-xml as longchar):
def var v-index as int no-undo.
def var v-start as int no-undo.
def var v-elementName as char no-undo.
def var v-char as char no-undo.
do while index(p-xml, "/>") > 0:
assign
v-elementName = ""
v-index = index(p-xml, "/>")
v-start = r-index(p-xml, "
if v-start > 0 then do:
do while true:
assign
v-start = v-start + 1
v-char = substring(p-xml, v-start, 1).
if v-char = " " or v-char = ">" then leave.
v-elementName = v-elementName + v-char.
end.
if v-elementName <> "" then
p-xml = substring(p-xml, 1, v-index - 1) + ">" +
substring(p-xml, v-index + 2).
end.
end.
p-xml = trim(p-xml, chr(10)).
return p-xml.
end function.