I have the following method that uses the JDom library, the Apache XML Security library, the org.w3c.dom package and the java.security package to sign a single XML file using data from a single PFX certificate:
/** This method allows to sign a single XML using data from a single Certificate and a given Namespace
@param pKey Private Key obtained from a PFX certificate
@param cert Certificate data obtained from the PFX file
@param ns Namespace to be associated to the root tag of the signed XML content
**/
public void sign(PrivateKey pKey, X509Certificate cert, Namespace ns) throws JDOMException,
InvalidKeyException, NoSuchAlgorithmException, SignatureException, XMLSecurityException,
ParserConfigurationException, IOException, SAXException, DTECedidoException, TimbreException, CodigoException, ReciboException {
Constants.setSignatureSpecNSprefix("");
String baseUri = null;
if (ns != null)
baseUri = ns.getURI();
String alg = pKey.getAlgorithm();
if (!alg.equals(cert.getPublicKey().getAlgorithm()))
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLUtil.getNiceXML(genDocument(ns), out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
org.w3c.dom.Document doc = db.parse(in);
org.w3c.dom.Element root = doc.getDocumentElement();
XMLSignature sig = null;
if (alg.equals("RSA")) {
if (!((RSAPrivateKey) pKey).getModulus().equals(((RSAPublicKey) cert.getPublicKey()).getModulus()))
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
sig = new XMLSignature(doc, baseUri, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);
} else if (alg.equals("DSA")) {
if (!(((DSAPrivateKey) pKey).getParams().getG()
.equals(((DSAPublicKey) cert.getPublicKey()).getParams().getG())
&& ((DSAPrivateKey) pKey).getParams().getP()
.equals(((DSAPublicKey) cert.getPublicKey()).getParams().getP())
&& ((DSAPrivateKey) pKey).getParams().getQ()
.equals(((DSAPublicKey) cert.getPublicKey()).getParams().getQ())))
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
sig = new XMLSignature(doc, baseUri, XMLSignature.ALGO_ID_SIGNATURE_DSA);
} else
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
root.appendChild(sig.getElement());
sig.addDocument("#" + getId());
X509Data xdata = new X509Data(doc);
xdata.addCertificate(cert);
sig.getKeyInfo().addKeyValue(cert.getPublicKey());
sig.getKeyInfo().add(xdata);
sig.sign(pKey);
ByteArrayOutputStream bous = new ByteArrayOutputStream();
DOMBuilder docIn = new DOMBuilder();
Document domdoc = docIn.build(doc);
cl.sii.dte.util.XMLUtil.getNiceXML(domdoc, bous);
this.signedDoc = bous.toByteArray();
}
This method works fine. However, I need to overload it and the new method must support data from multiple certificates (3 at maximum). So far, I have this:
/** This method allows to sign a single XML using data from multiples Certificates and a given Namespace
@param pKey List of Private Keys obtained from one PFX certificate per key
@param cert List of Certificate datas obtained from one PFX file per data.
@param ns Namespace to be associated to the root tag of the signed XML content
**/
public void sign(List<PrivateKey> pKey, List<X509Certificate> cert, Namespace ns) throws JDOMException,
InvalidKeyException, NoSuchAlgorithmException, SignatureException, XMLSecurityException,
ParserConfigurationException, IOException, SAXException, DTECedidoException, TimbreException, CodigoException, ReciboException {
/* Loads the unsigned XML ENTIRE content in order to add the signatures*/
Constants.setSignatureSpecNSprefix("");
String baseUri = null;
if (ns != null)
baseUri = ns.getURI();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLUtil.getNiceXML(genDocument(ns), out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
org.w3c.dom.Document doc = db.parse(in);
org.w3c.dom.Element root = doc.getDocumentElement();
XMLSignature sig = null;
for(int i=0; i<pKey.size(); i++){
PrivateKey pk=pKey.get(i);
X509Certificate c=cert.get(i)
String alg = pk.getAlgorithm();
if (!alg.equals(c.getPublicKey().getAlgorithm()))
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
if (alg.equals("RSA")) {
if (!((RSAPrivateKey) pk).getModulus().equals(((RSAPublicKey) c.getPublicKey()).getModulus()))
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
sig = new XMLSignature(doc, baseUri, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);
} else if (alg.equals("DSA")) {
if (!(((DSAPrivateKey) pk).getParams().getG()
.equals(((DSAPublicKey) c.getPublicKey()).getParams().getG())
&& ((DSAPrivateKey) pk).getParams().getP()
.equals(((DSAPublicKey) c.getPublicKey()).getParams().getP())
&& ((DSAPrivateKey) pk).getParams().getQ()
.equals(((DSAPublicKey) c.getPublicKey()).getParams().getQ())))
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
sig = new XMLSignature(doc, baseUri, XMLSignature.ALGO_ID_SIGNATURE_DSA);
} else
throw (new DTECedidoException(DTECedidoException.NOT_CORRESPONDING));
root.appendChild(sig.getElement());
sig.addDocument("#" + getId());
/* The following lines of code give me problems*/
X509Data xdata = new X509Data(doc);
xdata.addCertificate(c);
sig.getKeyInfo().addKeyValue(c.getPublicKey());
sig.getKeyInfo().add(xdata);
sig.sign(pk);
}
/* Generates and stores the byte content of the resulting
signed XML */
ByteArrayOutputStream bous = new ByteArrayOutputStream();
DOMBuilder docIn = new DOMBuilder();
Document domdoc = docIn.build(doc);
cl.sii.dte.util.XMLUtil.getNiceXML(domdoc, bous);
this.signedDoc = bous.toByteArray();
}
The main problem is that the following lines of code obtains the information from the entire document and allows to add only one certificate item instead of all of the list of certificates.
X509Data xdata = new X509Data(doc);
xdata.addCertificate(c); // !!!!!!!
sig.getKeyInfo().addKeyValue(c.getPublicKey());
sig.getKeyInfo().add(xdata); //!!!!!!!
sig.sign(pk);
Where can I find a good example of sign a XML file using information from multiple PFX files using Java (I tried on my own with no success)?
Thanks in advance.
EDIT I made this method in the meantime, hoping this is the correct approach
public void sign(List<PrivateKey> pKey, List<X509Certificate> cert, Namespace ns)
throws XMLSecurityException, ParserConfigurationException, SAXException, IOException, CesionException {
Constants.setSignatureSpecNSprefix("");
String baseUri = null;
if (ns != null)
baseUri = ns.getURI();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLUtil.getNiceXML(genDocument(ns), out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
org.w3c.dom.Document doc = db.parse(in);
org.w3c.dom.Element root = doc.getDocumentElement();
XMLSignature sig = null;
for(int i=0; i<pKey.size(); i++) {
PrivateKey pk=pKey.get(i);
X509Certificate c=cert.get(i);
String alg = pk.getAlgorithm();
if (!alg.equals(c.getPublicKey().getAlgorithm()))
throw (new CesionException(CesionException.NOT_CORRESPONDING));
if (alg.equals("RSA")) {
if (!((RSAPrivateKey) pk).getModulus().equals(((RSAPublicKey) c.getPublicKey()).getModulus()))
throw (new CesionException(CesionException.NOT_CORRESPONDING));
sig = new XMLSignature(doc, baseUri, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);
} else if (alg.equals("DSA")) {
if (!(((DSAPrivateKey) pk).getParams().getG()
.equals(((DSAPublicKey) c.getPublicKey()).getParams().getG())
&& ((DSAPrivateKey) pk).getParams().getP()
.equals(((DSAPublicKey) c.getPublicKey()).getParams().getP())
&& ((DSAPrivateKey) pk).getParams().getQ()
.equals(((DSAPublicKey) c.getPublicKey()).getParams().getQ())))
throw (new CesionException(CesionException.NOT_CORRESPONDING));
sig = new XMLSignature(doc, baseUri, XMLSignature.ALGO_ID_SIGNATURE_DSA);
} else
throw (new CesionException(DTECedidoException.NOT_CORRESPONDING));
root.appendChild(sig.getElement());
sig.addDocument("#" + i);
X509Data xdata = new X509Data(doc);
xdata.addCertificate(c);
sig.getKeyInfo().addKeyValue(c.getPublicKey());
sig.getKeyInfo().add(xdata);
sig.sign(pk);
}
ByteArrayOutputStream bous = new ByteArrayOutputStream();
DOMBuilder docIn = new DOMBuilder();
Document domdoc = docIn.build(doc);
cl.sii.dte.util.XMLUtil.getNiceXML(domdoc, bous);
this.signedDoc = bous.toByteArray();
}