CMA Small Systems AB
MX Messages Signing guide
CMA Small Systems AB
2021
© Copyright CMA Small Systems AB, 2021.
No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of CMA Small Systems AB.
Document control section
Document information
Project: |
|
||
Prepared by: |
|
Version number |
00 3 |
Name: |
|
Version date |
202 1 - 11 - 19 |
File name: |
CMA-I 3056-15 TS 025 002 PY MX signatures security guide |
Distribution list
From |
Date |
Version |
CMA Small Systems AB |
202 1 - 11 - 19 |
00 3 |
|
|
|
|
|
|
|
|
|
Version |
To |
Action [1] |
Due date |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Version history
Version |
Date |
Author |
Comments |
001 |
202 1 - 11 - 10 |
CMA Small Systems AB |
First version |
00 2 |
202 1 - 11 - 1 8 |
CMA Small Systems AB |
Review |
003 |
202 1 - 11 - 19 |
CMA Small Systems AB |
Review |
|
|
|
|
|
|
|
|
|
|
|
|
Table of Contents
1. Definitions and abbreviations
4. Digital signatures of MX messages
4.1 Digital signing and verifying signatures of MX messages
4.1.2 References in SignedInfo
4.1.4 Full example of MX document with signature
4.2.1 Example of Java code to digitally sign MX document
4.2.3 Example of Java code to verify signature of MX document
4.2.4 Troubleshooting/debugging digital signature verification errors
API |
Application Programming Interface |
BAH |
Business Application Header (part of MX message) |
CRL |
Certificate Revocation List |
VPN |
Virtual Private Network |
HTTP |
Hypertext Transfer Protocol is an application protocol for distributed, collaborative, hypermedia information systems |
HTTPS |
Hypertext Transfer Protocol over TLS/SSL is an application protocol for distributed, collaborative, hypermedia information systems with data encryption |
IETF |
Internet Engineering Task Force |
ISO |
International Standards Organization |
LDAP |
The Lightweight Directory Access Protocol is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an Internet Protocol (IP) network |
MX |
XML Message in ISO20022 format |
Participant |
Participant of IPS |
REST |
Representational State Transfer (REST) is a software architectural style that defines a set of constraints to be used for creating web services. |
SSL |
Secure Sockets Layer protocol is cryptographic protocol designed to provide communications security over a computer network |
TLS |
Transport Layer Security protocol is cryptographic protocol designed to provide communications security over a computer network |
W3C |
World Wide Web Consortium |
2. Introduction
This document describes overall digital signatures usage when exchanging ISO20022 messages with IPS.
Security of the Systems using exchange via Private Network (VPN) meets several objectives:
- Confidentiality – information is only disclosed to authorized person at authorized location.
- Integrity – information can be relied upon to be complete, accurate and valid.
Confidentiality and integrity are ensured by securing transmission, delivery, and message storage, by validating messages and by using cryptographic facilities.
All messages being transmitted between a Participant’s site and the System site are encrypted. Only authorized persons can use software that is intended for communication with the System.
All input messages are validated to ensure they conform to the System message syntax. Only those messages that conform to the syntax are accepted for delivery.
Message authentication assures the identity of the sender and the integrity of the message text.
Figure 1 . Security principles
Central Bank PKI infrastructure will be used to issue and manage certificates. This is responsibility of sending party to generate digital signature of financial block and place this digital Signature into BAH (head.001). Chapters below represent signing schemas for different interfaces when message is sent via transfer channel VPN.
In case of business content signature unsuccessful validation, the System will generate and send a reply as MX message admi.002.001.01 to the sender of the message.
Example of admi.002.001.01 :
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:admi.002.001.01">
<admi.002.001.01>
<RltdRef>
<Ref>001R201153230017</Ref>
</RltdRef>
<Rsn>
<RjctgPtyRsn>SC312</RjctgPtyRsn>
<RjctnDtTm>2019-03-21T11:40:47</RjctnDtTm>
<RsnDesc>Invalid signature of Document</RsnDesc>
<AddtlData/>
</Rsn>
</admi.002.001.01>
</Document>
Current error code that can be returned upon signature validation:
"SC312" – Invalid signature
For each registered User ID in IPS, any Participant relying on a Private Network must have a digital key pair. One key from the pair is so called secret one (private key) and should be kept on a safe storage. The other one is a public. It should be accessible by other participants to read.
The first participant digitally signs the data using its own secret key and the other participant verifies that signature using a public key of the first.
3.1 REST
Figure 2 . Signing process - REST Interface
- Core Banking system (Participant A) generates the Document block (financial information block).
- Core Banking system (Participant A) puts business signature. This is responsibility of sending party to generate digital signature of financial block and place this digital Signature into business application header AppHdr (head.001).
- The message is sent to IPS system for processing.
-
Message is received from Participant A and processed in IPS.
- When IPS system receives the message, business application header signature is verified and stored with the message in IPS system database.
In case of unsuccessful signature validation – admi.002 message is sent.
- IPS system generates the message to the Receiving participant (Participant B) in MX format (with Business signature).
- Message for Participant B is delivered by the System.
- Message in MX format (with Business signature) is delivered to Core Banking application.
-
Participant B Core Banking verifies Business signature using Public Keys and CRL (Certificate Revocation List) obtained from Central Institution LDAP by private network connection.
4.1 Digital signing and verifying signatures of MX messages
In case of MX messages, digital signature of the business layer of MX message is used to authenticate the business sender and guarantee integrity of the business payload. This Business signature should be compliant with the XAdES standard: XAdES-BES signature with certain agreements/conventions defined below.
While the IETF/W3C XML Signature standard (usually referred to as XML-DSig) is a general framework for digitally signing documents, XAdES specifies precise profiles of XML-DSig that provide certain guarantees. XAdES-BES (for “Basic Electronic Signature”) provides basic authentication and integrity protection, which are essential for advanced electronic signatures in payment systems.
In a XAdES the signature shall be applied in the usual way of XML-DSig over the document to be signed and on the whole set of signed properties (SignedProperties element). The mandatory information in the SignedProperties element is just the signing time, specifying the time at which the signer claims to have performed the signing process. Detailed description of XAdES usage rules adopted in the system is provided below.
4.1.1 XAdES usage rules
XAdES-BES signatures used in the system should comply with the following usage rules:
- Usage of block “Object” (ds:Object)
The ds:Object element should have just one value in in QualifyingProperties/ SignedProperties/ SignedSignatureProperties: the signing time, specifying the time at which the signer claims to have performed the signing process.
The Id attribute on the KeyInfo element is mandatory and the value of the ID attribute must be an underscore (” _”) followed by a universally unique identifier (UUID), that is either time-based or random.
- Usage of block “KeyInfo”
The XAdES standard allows two different methods to comply with the XAdES-BES requirement. In the system it has been decided to use the one that includes the signer certificate in the KeyInfo element:
- Element KeyInfo must be present and must include the ds:X509Data/ds:X509Certificate/ ds:X509IssuerSerial containing the issuer and the serial number of the signing certificate.
- The Id attribute on the KeyInfo element is mandatory and the value of the ID attribute must be an underscore (”_”) followed by a universally unique identifier (UUID), that is either time-based or random.
- The SignedInfo element must reference the KeyInfo element using the Id attribute. Usage of the alternative ds:Object/ QualifyingProperties/ SignedProperties/ SignedSignatureProperties/ SigningCertificate element is not allowed.
4.1.2 References in SignedInfo
There should be 3 references in SignedInfo:
- Reference to KeyInfo (which contains unambiguous reference to the signer's certificate). The reference should have attribute URI referencing Id of the KeyInfo element.
- Reference to SignedProperties under ds:Object. The reference should have attribute URI referencing Id of the SignedProperties element.
- Application-specific Reference without any URI – this Reference points to the business document – element Document. Standard XML signatures allow a special Reference: a Reference without URI attribute. It is defined by the XML signature specification that the receiving application should be able to identify which object to use in this case. In this specification and this System, the URI-less Reference refers to the Document element with its entire contents.
4.1.3 Example signature
An example of an acceptable signature that is provided below:
< ds:Signature xmlns:ds =" http://www.w3.org/2000/09/xmldsig# ">
< ds:SignedInfo >
< ds:CanonicalizationMethod Algorithm =" http://www.w3.org/2001/10/xml-exc-c14n# "/>
< ds:SignatureMethod Algorithm =" http://www.w3.org/2000/09/xmldsig#rsa-sha1 "/>
< ds:Reference URI =" #_33d232d2-4591-4b49-b28d-3cb825fbeaa4 ">
< ds:Transforms >
< ds:Transform Algorithm =" http://www.w3.org/2001/10/xml-exc-c14n# "/>
</ ds:Transforms >
< ds:DigestMethod Algorithm =" http://www.w3.org/2001/04/xmlenc#sha256 "/>
< ds:DigestValue > h9toHGSlK/x1zE7egK0yEj06W2D9wAEK/VAuiwU8+R8= </ ds:DigestValue >
</ ds:Reference >
< ds:Reference Type =" http://uri.etsi.org/01903/v1.3.2#SignedProperties " URI =" #_aba0ee84-5f37-499e-a8e8-caa7f398341c-signedprops ">
< ds:Transforms >
< ds:Transform Algorithm =" http://www.w3.org/2001/10/xml-exc-c14n# "/>
</ ds:Transforms >
< ds:DigestMethod Algorithm =" http://www.w3.org/2001/04/xmlenc#sha256 "/>
< ds:DigestValue > Ot7tqqOtgtguRadTQi0fh5FU3XL/4/mHIv7Eoy67t/s= </ ds:DigestValue >
</ ds:Reference >
< ds:Reference >
< ds:Transforms >
< ds:Transform Algorithm =" http://www.w3.org/2001/10/xml-exc-c14n# "/>
</ ds:Transforms >
< ds:DigestMethod Algorithm =" http://www.w3.org/2001/04/xmlenc#sha256 "/>
< ds:DigestValue > 1ZZln0/NzN/eB1wIrxyp/c3SOjKWnk00Lh1bKTXlTAE= </ ds:DigestValue >
</ ds:Reference >
</ ds:SignedInfo >
< ds:SignatureValue > VRn+Q7K6snvKrFPwtH302iKPjAx1k97TKIvjysdH+/I8EMyzWg20gZ1fO1gjKk245nfzXIsiuoVI
ZJtBKNSE9Tp+VXegJxyAoXx1bz8fMZIbdjjhXaYzdx2yCGh9Fllrbg+y9RZy9VvG7sLQeu91gOge
7GHNIxO6jck96yVsY8k= </ ds:SignatureValue >
< ds:KeyInfo Id =" _33d232d2-4591-4b49-b28d-3cb825fbeaa4 ">
< ds:X509Data >
< ds:X509IssuerSerial >
< ds:X509IssuerName > C=SE, O=CMA Small Systems AB, CN=Test CA </ ds:X509IssuerName >
< ds:X509SerialNumber > 12345678 </ ds:X509SerialNumber >
</ ds:X509IssuerSerial >
</ ds:X509Data >
</ ds:KeyInfo >
< ds:Object >
< xades:QualifyingProperties xmlns:xades =" http://uri.etsi.org/01903/v1.3.2# ">
< xades:SignedProperties Id =" _aba0ee84-5f37-499e-a8e8-caa7f398341c-signedprops ">
< xades:SignedSignatureProperties >
< xades:SigningTime > 2019-08-23T19:01:41+12:00 xades:SigningTime >
</ xades:SignedSignatureProperties >
</ xades:SignedProperties >
</ xades:QualifyingProperties >
</ ds:Object >
</ ds:Signature >
- Signature starts with a Signature element in the http://www.w3.org/2000/09/xmldsig# namespace.
- The SignedInfo element is the information that is actually signed. It references the signed data and specifies which algorithms are used.
- The SignatureMethod and CanonicalizationMethod elements are used by the SignatureValue element and are included in SignedInfo to protect them from tampering.
- One or more Reference elements specify the resource being signed by URI reference; and any transforms to be applied to the resource prior to signing. A transformation can be a XPath-expression that selects a defined subset of the document tree.
- DigestMethod specifies the hash algorithm before applying the hash.
- DigestValue contains the result of applying the hash algorithm to the transformed resource.
- The SignatureValue element contains the Base64 encoded signature result - the signature generated with the parameters specified in the SignatureMethod element - of the SignedInfo element after applying the algorithm specified by the CanonicalizationMethod .
- The KeyInfo element allows verifier to identify signer’s certificate that is necessary for validating the signature. KeyInfo should include just issuer name and serial number of the certificate, but not the full certificate – this is to save valuable network traffic and disk/database space. All certificates used in the system are available in central LDAP directory server.
Additional information regarding XSD File for Signature section can be found in website:
https://www.w3.org/
https://www.w3.org/TR/xmldsig-core/
4.1.4 Full example of MX document with signature
The XAdES signature described above should be stored in element Sgntr in the AppHdr element of MX document.
Full example is provided below. Digital signature block is highlighted with green colour . The signed business data (contents of element Document) is highlighted with blue . This is the data being protected by the signature.
<?xml version="1.0" encoding="UTF-8"?>
<DataPDU xmlns="urn:cma:stp:xsd:stp.1.0">
<Body>
<AppHdr xmlns="urn:iso:std:iso:20022:tech:xsd:head.001.001.01">
<Fr>
<FIId>
<FinInstnId>
<BICFI>AAAAYYZZ</BICFI>
</FinInstnId>
</FIId>
</Fr>
<To>
<FIId>
<FinInstnId>
<BICFI> SYSTEMZZ </BICFI>
</FinInstnId>
</FIId>
</To>
<BizMsgIdr> AAAAYYZZAXXX180217100120000001230 </BizMsgIdr>
<MsgDefIdr>pacs.008.001.08</MsgDefIdr>
<BizSvc>IPS</BizSvc>
<CreDt>2019-09-13T18:18:00Z</CreDt>
<Sgntr>
<ds:Signature xmlns:ds=”http://www.w3.org/2000/09/xmldsig#”>
<ds:SignedInfo>
...
</ds:SignedInfo>
<ds:SignatureValue>…</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
…
</ds:X509Data>
</ds:KeyInfo>
<ds:Object>
…
</ds:Object>
</ds:Signature>
</Sgntr>
</AppHdr>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08">
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>AAAAYYZZAXXX180217100120000001230</MsgId>
<CreDtTm>2019-09-13T18:18:00</CreDtTm>
<NbOfTxs>1</NbOfTxs>
<SttlmInf>
<SttlmMtd>CLRG</SttlmMtd>
</SttlmInf>
</GrpHdr>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>NOTPROVIDED</EndToEndId>
<TxId>AAAAYYZZAXXX180217100120000001230</TxId>
</PmtId>
<PmtTpInf>
<ClrChanl>RTNS</ClrChanl>
<LclInstrm>
<Prtry>CSCT</Prtry>
</LclInstrm>
<CtgyPurp>
<Prtry>001</Prtry>
</CtgyPurp>
</PmtTpInf>
<IntrBkSttlmAmt Ccy="USD">71.12</IntrBkSttlmAmt>
<IntrBkSttlmDt>2019-09-14</IntrBkSttlmDt>
<ChrgBr>SLEV</ChrgBr>
<InstgAgt>
<FinInstnId>
<BICFI> AAAAYYZZ </BICFI>
</FinInstnId>
</InstgAgt>
<InstdAgt>
<FinInstnId>
<BICFI> BBBBYYZZ </BICFI>
</FinInstnId>
</InstdAgt>
<Dbtr>
<Nm>John Johnson</Nm>
</Dbtr>
<DbtrAcct>
<Id>
<IBAN> 12345678998076 </IBAN>
</Id>
</DbtrAcct>
<DbtrAgt>
<FinInstnId>
<BICFI>AAAAYYZZ</BICFI>
<Othr>
<Id> 200004 </Id>
<SchmeNm>
<Prtry> 1700085041 </Prtry>
</SchmeNm>
</Othr>
</FinInstnId>
</DbtrAgt>
<DbtrAgtAcct>
<Id>
<IBAN>89980761234567</IBAN>
</Id>
</DbtrAgtAcct>
<CdtrAgt>
<FinInstnId>
<BICFI>BBBBYYZZ</BICFI>
<Othr>
<Id>210027</Id>
<SchmeNm>
<Prtry>1400108191</Prtry>
</SchmeNm>
</Othr>
</FinInstnId>
</CdtrAgt>
<CdtrAgtAcct>
<Id>
<IBAN>98765432198765</IBAN>
</Id>
</CdtrAgtAcct>
<Cdtr>
<Nm>Omega Jones</Nm>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>54637281908745</IBAN>
</Id>
</CdtrAcct>
<InstrForNxtAgt>
<InstrInf>/BNF/Details</InstrInf>
</InstrForNxtAgt>
<Purp>
<Prtry>5814</Prtry>
</Purp>
<RgltryRptg>
<Dtls>
<Inf>SOMEINFORMATIONABOUTPAYMENT-1</Inf>
<Inf>SOMEINFORMATIONABOUTPAYMENT-2</Inf>
<Inf>SOMEINFORMATIONABOUTPAYMENT-3</Inf>
</Dtls>
</RgltryRptg>
<Tax>
<Cdtr>
<TaxId>54020835021853</TaxId>
</Cdtr>
<Dbtr>
<TaxId>90203875612954</TaxId>
</Dbtr>
</Tax>
<RmtInf>
<Ustrd>INFORMATION</Ustrd>
<Ustrd>EXTRA INFO</Ustrd>
</RmtInf>
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>
</Body>
</DataPDU>
4.2
Sample code in Java
4.2.1 Example of Java code to digitally sign MX document
The code example below uses Java 7 XML Digital Signature APIs to digitally sign MX document according to the requirements provided in previous sections.
Note: exception/error handling is omitted or limited and may not be suitable for production code.
// ******************* BEGIN OF EXAMPLE *******************
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.xml.crypto.Data;
import javax.xml.crypto.NodeSetData;
import javax.xml.crypto.URIDereferencer;
import javax.xml.crypto.URIReference;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.XMLObject;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public Document sign(
org.w3c.dom.Document doc,
java.security.cert.X509Certificate signerCertificate,
java.security.PrivateKey privateKey,
boolean debugLog
)
throws Exception
{
final String xadesNS = "http://uri.etsi.org/01903/v1.3.2#" ;
final String signedpropsIdSuffix = "-signedprops" ;
XMLSignatureFactory fac = null;
try {
fac = XMLSignatureFactory.
getInstance
("DOM", "XMLDSig");
} catch (NoSuchProviderException ex) {
fac = XMLSignatureFactory.
getInstance
("DOM");
}
// 1. Prepare KeyInfo
KeyInfoFactory kif = fac .getKeyInfoFactory();
X509IssuerSerial x509is = kif .newX509IssuerSerial(
signerCertificate .getIssuerX500Principal().toString(),
signerCertificate .getSerialNumber());
X509Data x509data = kif .newX509Data(Collections. singletonList ( x509is ));
final String keyInfoId = "_" + UUID. randomUUID ().toString();
KeyInfo ki = kif .newKeyInfo(Collections. singletonList ( x509data ), keyInfoId );
// 2. Prepare references
List<Reference> refs = new ArrayList<Reference>();
Reference ref1 = fac .newReference( "#" + keyInfoId ,
fac .newDigestMethod(DigestMethod. SHA256 , null ),
Collections. singletonList ( fac .newCanonicalizationMethod(
CanonicalizationMethod. EXCLUSIVE , (XMLStructure) null )),
null , null );
refs .add( ref1 );
final String signedpropsId = "_" + UUID. randomUUID ().toString() + signedpropsIdSuffix ;
Reference ref2 = fac .newReference( "#" + signedpropsId ,
fac .newDigestMethod(DigestMethod. SHA256 , null ),
Collections. singletonList ( fac .newCanonicalizationMethod(
CanonicalizationMethod. EXCLUSIVE , (XMLStructure) null )),
"http://uri.etsi.org/01903/v1.3.2#SignedProperties" , null );
refs .add( ref2 );
Reference ref3 = fac .newReference( null ,
fac .newDigestMethod(DigestMethod. SHA256 , null ),
Collections. singletonList ( fac .newCanonicalizationMethod(
anonicalizationMethod. EXCLUSIVE , (XMLStructure) null )),
null , null );
refs .add( ref3 );
SignedInfo si = fac .newSignedInfo(
fac .newCanonicalizationMethod(CanonicalizationMethod. EXCLUSIVE , (XMLStructure) null ),
fac .newSignatureMethod(SignatureMethod. RSA_SHA1 , null ), refs );
// 3. Create element AppHdr/ Sgntr that will contain the <ds:Signature>
Node appHdr = null ;
NodeList sgntrList = doc .getElementsByTagName( "AppHdr" );
if ( sgntrList .getLength() != 0)
appHdr = sgntrList .item(0);
if ( appHdr == null )
throw new Exception( "mandatory element AppHdr is missing in the document to be signed" );
Node sgntr = appHdr .appendChild( doc .createElementNS( appHdr .getNamespaceURI(), "Sgntr" ));
DOMSignContext dsc = new DOMSignContext(privateKey, sgntr );
if (debugLog) {
dsc.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.
TRUE
);
}
dsc .putNamespacePrefix(XMLSignature. XMLNS , "ds" );
// 4. Set up <ds:Object> with <QualifiyingProperties> inside that includes SigningTime
Element QPElement = doc .createElementNS( xadesNS , "xades:QualifyingProperties" );
QPElement.setAttributeNS(" http://www.w3.org/2000/xmlns/ ", " xmlns:xades ", xadesNS );
Element SPElement = doc.createElementNS(
xadesNS
, "xades:SignedProperties");
SPElement.setAttributeNS(null, "Id", signedpropsId);
dsc.setIdAttributeNS(SPElement, null, "Id");
SPElement.setIdAttributeNS(null, "Id", true);
QPElement.appendChild(SPElement);
Element SSPElement = doc .createElementNS( xadesNS , "xades:SignedSignatureProperties" );
SPElement .appendChild( SSPElement );
final DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ" );
String signingTime = df .format( new Date());
Element STElement = doc .createElementNS( xadesNS , "xades:SigningTime" );
STElement .appendChild( doc .createTextNode( signingTime ));
SSPElement .appendChild( STElement );
DOMStructure qualifPropStruct = new DOMStructure( QPElement );
List<DOMStructure> xmlObj = new ArrayList<DOMStructure>();
xmlObj .add( qualifPropStruct );
XMLObject object = fac .newXMLObject( xmlObj , null , null , null );
List<XMLObject> objects = Collections. singletonList ( object );
// 5. Set up custom URIDereferencer to process Reference without URI.
// This Reference points to element <Document> of MX message
final NodeList docNodes = doc .getElementsByTagName( "Document" );
final Node docNode = docNodes .item(0);
ByteArrayOutputStream refOutputStream = new ByteArrayOutputStream();
Transformer xform = TransformerFactory. newInstance ().newTransformer();
xform .setOutputProperty(OutputKeys. OMIT_XML_DECLARATION , "yes" );
xform .transform( new DOMSource( docNode ), new StreamResult( refOutputStream ));
InputStream refInputStream = new ByteArrayInputStream( refOutputStream .toByteArray());
dsc .setURIDereferencer( new NoUriDereferencer( refInputStream ));
// 6. sign it!
XMLSignature signature = fac .newXMLSignature( si , ki , objects , null , null );
signature .sign( dsc );
// 7. for debug purposes for each reference
// write the digest and the transformed data
// to the console (replace with your preferred logging such as log4j)
if (debugLog) {
int i = 0;
for (Reference ref: refs) {
StringBuilder sb = new StringBuilder();
String digValStr = digestToString(ref.getDigestValue());
InputStream is = ref.getDigestInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
is.close();
i++;
System.out.println
(
("ref #" + i + " URI: [" + ref.getURI() +"], digest: " + digValStr + ", transformed data: [" + sb.toString() + "]");
}
}
}
// ******************* END OF EXAMPLE *******************
4.2.2 Custom URI dereferencer
Standard XML signatures allow a special Reference: a Reference without URI attribute. It is defined by the XML signature specification that the receiving application should be able to identify which object to use in this case. In this specification and the System, the URI-less Reference refers to the Document element (and its entire contents). Therefore, a custom dereferencer is required to know how to get all referenced data from URIs in all type of references, including a reference without a URI attribute. This customized dereferencer implements javax.xml.crypto.URIDereferencer and is called NoUriDereferencer – please see sample code of this class below.
NoUriDereferencer is constructed with the delegation pattern. During dereferencing, if the URI attribute for the URIReference object is found to be null, the document is returned as an octet stream. For all other URIs, the class delegates to the original default dereferencer provided by the implementation of standard JSR 105.
Custom Dereferencer sample code:
public class NoUriDereferencer implements URIDereferencer {
private InputStream inputStream ;
public NoUriDereferencer(InputStream inputStream ) {
this . inputStream = inputStream ;
}
@Override
public Data dereference(URIReference uriRef , XMLCryptoContext ctx )
throws URIReferenceException
{
if ( uriRef .getURI() != null )
{
URIDereferencer defaultDereferencer =
XMLSignatureFactory. getInstance ( "DOM" ).getURIDereferencer();
return defaultDereferencer .dereference( uriRef , ctx );
}
Data data = new OctetStreamData( inputStream );
return data ;
}
}
4.2.3 Example of Java code to verify signature of MX document
The code example below uses Java 7 XML Digital Signature APIs to verify signature of MX document according to the requirements provided in previous sections.
Note 1 : exception/error handling is omitted or limited and may not be suitable for production code.
Note 2 : the example uses simplified KeySelector (see variable mockKeySelector below) that just returns signer’s certificate. Real-life implementation SHOULD search for signer’s certificate using signer’s certificate issue name and serial number - this pair uniquely identifies the certificate. All the certificates are available in central directory via LDAP, so applications may need to retrieve certificates via LDAP. Applications should cache certificates locally.
// ******************* BEGIN OF EXAMPLE *******************
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.security.auth.x500.X500Principal;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.Data;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.NodeSetData;
import javax.xml.crypto.URIDereferencer;
import javax.xml.crypto.URIReference;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public SignatureInfo verify(String dataPDU, boolean debugLog) throws Exception {
XPath xpath = XPathFactory. newInstance ().newXPath();
String xpathExpression = "//*[local-name()='Signature']" ;
NodeList nodes = (NodeList) xpath .evaluate( xpathExpression , doc .getDocumentElement(), XPathConstants. NODESET );
if ( nodes == null || nodes .getLength() == 0)
throw new Exception( "Signature is missing in the document" );
Node nodeSignature = nodes .item(0);
final KeySelector mockKeySelector = new KeySelector() {
@Override
public KeySelectorResult select(KeyInfo keyInfo , Purpose purpose , AlgorithmMethod method , XMLCryptoContext context ) throws KeySelectorException {
return new KeySelectorResult() {
@Override
public Key getKey() {
return signerCertificate .getPublicKey();
}
};
}
};
XMLSignatureFactory fac = XMLSignatureFactory. getInstance ( "DOM" );
DOMValidateContext valContext = new DOMValidateContext( keySelector , nodeSignature );
// Set up custom URIDereferencer to process Reference without URI.
// This Reference points to element <Document> of MX message
final NodeList docNodes = doc .getElementsByTagName( "Document" );
final Node docNode = docNodes .item(0);
ByteArrayOutputStream refOutputStream = new ByteArrayOutputStream();
Transformer xform = TransformerFactory. newInstance ().newTransformer();
xform .setOutputProperty(OutputKeys. OMIT_XML_DECLARATION , "yes" );
xform .transform( new DOMSource( docNode ), new StreamResult( refOutputStream ));
InputStream refInputStream = new ByteArrayInputStream( refOutputStream .toByteArray());
valContext .setURIDereferencer( new NoUriDereferencer( refInputStream ));
// Java 1.7.0_25+ complicates validation of ds:Object/QualifyingProperties/SignedProperties
// See details at https://bugs.openjdk.java.net/browse/JDK-8019379
//
// One of the solutions is to register the Id attribute using the DOMValidateContext.setIdAttributeNS
// method before validating the signature
NodeList nl = doc .getElementsByTagNameNS( "http://uri.etsi.org/01903/v1.3.2#", "SignedProperties" );
if ( nl .getLength() == 0)
throw new Exception( "SignerProperties is missing in signature" );
Element elemSignedProps = (Element) nl .item(0);
valContext .setIdAttributeNS( elemSignedProps , null , "Id" );
XMLSignature signature = fac .unmarshalXMLSignature( valContext );
boolean coreValidity = signature .validate( valContext );
if ( coreValidity )
{
// signature verified
}
else
{
// signature verification failed
System. out .println( "Signature failed core validation" );
boolean sv = signature .getSignatureValue().validate( valContext );
System. out .println( "signature validation status: " + sv );
// check the validation status of each Reference
Iterator i = signature .getSignedInfo().getReferences().iterator();
for ( int j =0; i .hasNext(); j ++) {
final Reference ref = (Reference) i .next();
final String refURI = ref .getURI();
boolean refValid = ref .validate( valContext );
System. out .println( "ref[" + j + "] validity status: " + refValid + ", ref URI: [" + refURI + "]" );
if (debugLog) {
String calcDigValStr = digestToString(ref.getCalculatedDigestValue());
String expectedDigValStr = digestToString(ref.getDigestValue());
System.
out
.println
(" Calc Digest: " + calcDigValStr);
System.
out
.println
("Expected Digest: " + expectedDigValStr);
StringBuilder sb = new StringBuilder();
InputStream is = ref.getDigestInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
is.close();
System.
out
.println
("Transformed data: [" + sb.toString() + "]");
}
}
}
}
// ******************* END OF EXAMPLE *******************
4.2.4 Troubleshooting/debugging digital signature verification errors
While adding support for MX digital signatures into your application based on the examples above, you might have an issue when you successfully create digital signature, but your own (or other party’s) verifying code fails to verify (i.e. the signature is invalid).
This might happen in case if the document was modified in-between creation of the signature, and its verification, such as extra whitespaces, newline characters, or even data has been added into the document, or the document was reformatted in some way.
To troubleshoot/debug such cases both the signature creation and signature verification code in the examples provided in this document (see above) have a flag (boolean argument) ‘debugLog’. If it is set to true by the caller, the example code will write transformed data (i.e. the data passed to digest functions and to signing/verification) to standard console output (it is recommended that you replace it with your preferred logging framework, such as log4j etc.). With this flag enabled, create signature, save the output (digest and transformed data for each of the 3 references). Then pass the signed document for verification and get the debug information from verification step (also digests and transformed data for each of the 3 references). Now if the verification fails, you can compare the digests and the transformed data for each reference and see if the data was modified in process while being transferred between signing and verification.
If your signature fails verification in the central system enable debug output, sign a document, and pass it together with the debug output to support/vendor of the central system for further investigation. By comparing your ‘transformed data’ and digest values with the values during verification they might be able to find which part of the document was modified, and assist to fix the issue.
[1] Possible action types:
- Approve; Review; Inform; File; Action required; Attend meeting; Other (please specify).