Digitally sign XML (XAdES ) with timestamping by streaming the XML - java

I have created an method that digitally signs an xml (XAdES with Timestamping) using xades4j library https://github.com/luisgoncalves/xades4j
Unfortunately the xml's are quite big sometimes (1,8 GB) and I was wondering if there is a way to do that by streaming the XML instead of creating a DOM and loading the whole document in memory. Is there a way? Can I do that with xades4j?
Below is the current code that signs the document using a DOM representation of the xml. The initial method that is called first is the signXml().
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStoreException;
import javax.annotation.PostConstruct;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.safe.AbstractManager;
import com.safe.model.DirectPasswordProvider;
import xades4j.algorithms.ExclusiveCanonicalXMLWithoutComments;
import xades4j.production.Enveloped;
import xades4j.production.SignatureAlgorithms;
import xades4j.production.XadesSigner;
import xades4j.production.XadesTSigningProfile;
import xades4j.providers.KeyingDataProvider;
import xades4j.providers.impl.FileSystemKeyStoreKeyingDataProvider;
import xades4j.providers.impl.HttpTsaConfiguration;
import xades4j.providers.impl.KeyStoreKeyingDataProvider;
import xades4j.utils.DOMHelper;
#Component
public class FileOperationsManager extends AbstractManager {
#Value("${certificates.digital-signature.filepath}")
private String certPath;
#Value("${certificates.digital-signature.password}")
private String certPass;
#Value("${certificates.digital-signature.type}")
private String certType;
private DocumentBuilder db;
private TransformerFactory tf;
#PostConstruct
public void init() throws Exception {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
this.db = dbf.newDocumentBuilder();
this.tf = TransformerFactory.newInstance();
}
public Path signXml(final Path xmlFile, final Path targetDir) {
final String baseName = FilenameUtils.getBaseName(xmlFile.getFileName().toString())
.concat("_Signed")
.concat(".")
.concat(FilenameUtils.getExtension(xmlFile.getFileName().toString()));
final Path target = Paths.get(targetDir.toString(), baseName);
try (final FileInputStream fis = new FileInputStream(String.valueOf(xmlFile))) {
final Document doc = this.parseDocument(fis);
final Element elementToSign = doc.getDocumentElement();
final SignatureAlgorithms algorithms = new SignatureAlgorithms()
.withCanonicalizationAlgorithmForTimeStampProperties(new ExclusiveCanonicalXMLWithoutComments("ds", "xades"))
.withCanonicalizationAlgorithmForSignature(new ExclusiveCanonicalXMLWithoutComments());
final KeyingDataProvider kdp = this.createFileSystemKeyingDataProvider(certType, certPath, certPass, true);
final XadesSigner signer = new XadesTSigningProfile(kdp)
.withSignatureAlgorithms(algorithms)
.with(new HttpTsaConfiguration("http://timestamp.digicert.com"))
.newSigner();
new Enveloped(signer).sign(elementToSign);
this.exportDocument(doc, target);
} catch (final FileNotFoundException e) {
throw new RuntimeException();
} catch (final Exception e) {
throw new RuntimeException();
}
return target;
}
private FileSystemKeyStoreKeyingDataProvider createFileSystemKeyingDataProvider(
final String keyStoreType,
final String keyStorePath,
final String keyStorePwd,
final boolean returnFullChain) throws KeyStoreException {
return FileSystemKeyStoreKeyingDataProvider
.builder(keyStoreType, keyStorePath, KeyStoreKeyingDataProvider.SigningCertificateSelector.single())
.storePassword(new DirectPasswordProvider(keyStorePwd))
.entryPassword(new DirectPasswordProvider(keyStorePwd))
.fullChain(returnFullChain)
.build();
}
public Document parseDocument(final InputStream is) {
try {
final Document doc = this.db.parse(is);
final Element elem = doc.getDocumentElement();
DOMHelper.useIdAsXmlId(elem);
return doc;
} catch (final Exception e) {
throw new RuntimeException();
}
}
public void exportDocument(final Document doc, final Path target) {
try (final FileOutputStream out = new FileOutputStream(target.toFile())) {
this.tf.newTransformer().transform(
new DOMSource(doc),
new StreamResult(out));
} catch (final Exception e) {
throw new RuntimeException();
}
}

Unfortunately xades4j doesn't support streaming on the XML document to which the signature will be appended. I don't know if there are other alternative libraries that do.
A possible workaround using xades4j is to use a detached signature instead of an enveloped signature. The signature can be added to an empty XML document and the large XML file is explicitly added as a Reference to that signature.
xades4j delegates the core XML-DSIG handling to Apache Santuario, so if Santuario uses streaming for Reference resolution, this should avoid your issue. I'm not sure, though, but it may be worth testing.
https://github.com/luisgoncalves/xades4j/wiki/DefiningSignedResources
You may need to use a file URI and/or base URIs.

Related

Verify SOAP request using the X509 certificate

I have a SOAP server. The SOAP request that is receiving at the server has ws security headers. Following are the main nodes of the request XML.
BinarySecurityToken (X509PKIPathv1 certificate)
DigestMethod
DigestValue
SignatureValue
SecurityTokenReference
Data (data that is send by
client in SOAP body)
I have to verify the request using the certificate (.cer files) which is provided by the client (sender of the request).
What are the steps to validate the requests? Please explain the concept. No libraries are available for doing this. After a long research I am able to match BinarySecurityToken with base64_encode($certFile) $certFile is the requester's certifiate. Now I am researching how to matchDigestValue with what.
WS-Security headers can be verified by the following. I have written an utility for that. Have a look at it.
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.security.KeyStore;
import java.security.Provider;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
public class WSUtil {
public static void main(String[] args) throws Exception {
String req = "SOAPMESSAGE";
Document p = createXMLDocument(req);
InputStream inStream = new FileInputStream("certificate.p12"); //Provide your certificate file
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inStream, "pass".toCharArray()); //Certificate password - pass
String alias = ks.aliases().nextElement();
X509Certificate certificate = (X509Certificate) ks.getCertificate(alias);
validateSignature(p.getElementsByTagName("ds:Signature").item(0),p.getElementsByTagName("soapenv:Body").item(0),certificate.getPublicKey());//True if the message is valid
}
public static Document createXMLDocument(String xmlString) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder;
Document document = null;
try {
builder = factory.newDocumentBuilder();
document = builder.parse(new InputSource(
new StringReader(xmlString)));
} catch (Exception e) {
throw e;
}
return document;
}
private static boolean validateSignature(Node signatureNode, Node bodyTag, PublicKey publicKey) {
boolean signatureIsValid = false;
try {
// Create a DOM XMLSignatureFactory that will be used to unmarshal the
// document containing the XMLSignature
String providerName = System.getProperty
("jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
// Create a DOMValidateContext and specify a KeyValue KeySelector
// and document context
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(publicKey), signatureNode);
valContext.setIdAttributeNS((Element) bodyTag, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id");
// Unmarshal the XMLSignature.
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate the XMLSignature.
signatureIsValid = signature.validate(valContext);
} catch (Exception ex) {
ex.printStackTrace();
}
return signatureIsValid;
}
}
Note
You have to provide the SOAP message as it is. You shouldn't do any XML format or any empty space somewhere. The security added SOAP message is very sensitive. Even a space at the end will make the SOAP message as invalid.
X509KeySelector constructor is not correct type

JAXB marshlling is not ignoring namespace

I have spent some time to investigate what is the problem but I couldn't solve it. When I unmarshal below XML and marshal back I see different XML.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<one>test</one>
<three>\MySG\test.jsp</three>
<two>
<st>
<seeta>
<Source>
<problemtag xmlns="uuid:B89290D2-36FB-4EBC-A581-69B16D59EB92">
<p>deploy_test_page_renderingMetadata</p>
</problemtag>
</Source>
</seeta>
<Template id="tcm:1-63-32" title="Smart Compound Component Template"/>
<Publication id="tcm:0-1-1" title="Publication"/>
</st>
</two>
</root>
In the above xml only one tag (first one) expected remaining all (including namespace) are unexpected elements. Another application sends the above XML.
My Mapping are like this
package com.seeta.xml;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="root")
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlElement(name="one")
private String one;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
#XmlElement(name="three")
private String three;
#XmlAnyElement
private List<Object> remaining = new ArrayList<Object>();
public String getThree() {
return three;
}
public void setThree(String three) {
this.three = three;
}
public List<Object> getRemaining() {
return remaining;
}
public void setRemaining(List<Object> remaining) {
this.remaining = remaining;
}
public String toString() {
return String.format("One [%s]-> Number of remaing elements [%d]-> three [%s]", one, remaining.size(), three);
}
}
Here is my simple code
package com.seeta.xml;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLDecoder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class JaxbSample {
public Document getDOMDocument(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
if (inputStream != null) {
return documentBuilder.parse(new InputSource(inputStream));
} else {
return documentBuilder.newDocument();
}
}
public Root unmarshall(Document document) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Root root = (Root) unmarshaller.unmarshal(document);
return root;
}
public Document marshall(Root root) throws JAXBException, ParserConfigurationException, SAXException, IOException {
JAXBContext context = JAXBContext.newInstance(Root.class);
Marshaller marshaller = context.createMarshaller();
Document document = getDOMDocument(null);
marshaller.marshal(root, document);
return document;
}
private String transform(Document document) throws TransformerException {
StringWriter sw = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new DOMSource(document), new StreamResult(sw));
return sw.toString();
}
public void testUnmarshallMarshallUsingDocument() throws ParserConfigurationException, SAXException, IOException, JAXBException, TransformerException {
InputStream inputStream = this.getClass().getResourceAsStream("jaxb.xml");
Document document = getDOMDocument(inputStream);
Root root = unmarshall(document);
Document documentAfterMarshal = marshall(root);
String output = transform(documentAfterMarshal);
System.out.println(output);
}
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, JAXBException, TransformerException {
JaxbSample jaxbTest = new JaxbSample();
jaxbTest.testUnmarshallMarshallUsingDocument();
}
}
output is
<root>
<one>test</one>
<three>\MySG\test.jsp</three>
<two>
<st>
<seeta>
<Source>
<problemtag:problemtag xmlns="uuid:B89290D2-36FB-4EBC-A581-69B16D59EB92" xmlns:problemtag="uuid:B89290D2-36FB-4EBC-A581-69B16D59EB92">
<p>deploy_test_page_renderingMetadata</p>
</problemtag:problemtag>
</Source>
</seeta>
<Template id="tcm:1-63-32" title="Smart Compound Component Template"/>
<Publication id="tcm:0-1-1" title="Publication"/>
</st>
</two>
</root>
And also I tried following
I tried with NamespacePrefixMapper. I can able to give different namespace but not empty(""). I don't want any namespace at all.
new NamespacePrefixMapper() {
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
return "";
}
};
We don't have any xsd ( at least I don't know) in our project for trying unqualified thing
I really didn't understand QName thing
If all you want is to preserve the unused elements and marshall them back, I think you ought to be able to do something like this:
#XmlRootElement(name="Root")
#XmlAccessorType(XmlAccessType.FIELD)
class Root {
#XmlElement(name="One")
private String one;
#XmlAnyElement
private List<Any> otherElements;
}
class AnyAdapter extends XmlAdapter<Element,Any> {
#Override
public Any unmarshal(Element element) throws Exception {
return new Any(element);
}
#Override
public Element marshal(Any any) throws Exception {
return any.element;
}
}
#XmlJavaTypeAdapter(AnyAdapter.class)
class Any {
Element element;
Any(Element element) {
this.element = element;
}
}
I don't want any namespace at all.
You won't be able to accomplish this with JAXB alone. The #XmlAnyElement tells the unmarshaller to dump the elements that it can't process into your list. Those elements have namespaces attached. When you then marshal those elements, they'll be written with their namespaces.
One option is for you to parse the incoming XML with a namespace-unaware DOM parser, then unmarshall it using the DOM tree. There's an example of this in the Unmarshaller JavaDoc (which uses a namespace-aware parser; it should be obvious what to change to make it namespace-unaware).
I really didn't understand QName thing
Do you mean that you don't understand why the output is a qualified name, or why it picked the particular prefix? Or what QNames mean?
It's a qualified name because that's the most unambiguous way to represent the element.
I can't tell you why it picked this particular prefix; the JAXP serializer picks short names like "ns1", "ns2", and so on.

XPath error-->[Fatal Error] :1:1: Content is not allowed in prolog

i wrote the below program, but when it goes ot the XPath classes it gives [Fatal Error] :1:1: Content is not allowed in prolog. i tried to figure it out but couldnt make it.is there any clue to fix this problem?
package xpath;
import com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathVariableResolver;
import org.ccil.cowan.tagsoup.Parser;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
public class XPath {
private static int seg;
private static void check(Node node) throws XPathExpressionException {
if (node == null || node.getNodeName() == null)
return;
TFIDF( node.getNodeValue(),"java");
check(node.getFirstChild());
if(node.getFirstChild()==null &&node.getNextSibling()==null)
seg++;
System.out.println(node.getNodeValue() != null && node.getNodeValue().trim().length() == 0 ? "" : node);
check(node.getNextSibling());
}
public static void main(String[] args) throws MalformedURLException, SAXNotRecognizedException, SAXNotSupportedException, ParserConfigurationException, IOException, SAXException, XPathExpressionException {
Parser p = new Parser();
SAX2DOM sax2dom = null;
org.w3c.dom.Node doc = null;
URL url = new URL("http://stackoverflow.com/questions");
p.setFeature(Parser.namespacesFeature, false);
p.setFeature(Parser.namespacePrefixesFeature, false);
sax2dom = new SAX2DOM();
p.setContentHandler(sax2dom);
p.parse(new InputSource(new InputStreamReader(url.openStream())));
doc = sax2dom.getDOM();
Node html=doc.getFirstChild();
check(html);
}
private static void TFIDF(String segment, String keyword) throws XPathExpressionException {
if (segment!=null)
{
InputSource src = new InputSource(new StringReader(segment));
final String term = keyword;
String expression = "//*[contains(text(),$term)]";
final QName termVariableName = new QName("term");
class TermResolver implements XPathVariableResolver {
#Override
public Object resolveVariable(QName variableName) {
return termVariableName.equals(variableName) ? term : null;
}
}
javax.xml.xpath.XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setXPathVariableResolver(new TermResolver());
Node node = (Node) xpath.evaluate(expression, src, XPathConstants.NODE);
}
}
}
The "Content is not allowed in prolog" error usually means that you have something before the first XML element in your document, often whitespace. Since you are grabbing http://stackoverflow.com/questions, I'd guess that it's the newline character after the <!doctype> that is causing the problem. According to the XML spec whitespace should be allowed in the prolog both before and after the doctype, but many tools do not handle this correctly.
Try manually removing the whitespace and see if that helps. If not, try removing the doctype declaration altogether.

Problem with conversion of org.dom4j.Document to org.w3c.dom.Document and XML Signature

I have some classes that already use DOM4J to read XML files and provide
getter methods to the data. Now, I need to add the possibility of checking XML digital
signatures.
Using org.w3c.dom and following http://java.sun.com/developer/technicalArticles/xml/dig_signature_api/
everything works correctly.
So, I try to use DOMWriter to convert from org.dom4j.Document to
org.w3c.dom.Document, but after this the signature validation doesn't work. I think it
happens because DOMWiter is changing the XML tree (as doc4.asXML() seems to show).
I try to find something to set in order to mantain the integrity of the document, but
DOMWriter don't have such methods.
Below is the code demonstrating the asymmetric conversion.
The file used for tests is http://www.robertodiasduarte.com.br/files/nfe/131090007910044_v1.10-procNFe.xml
Does someone know reasons/workarounds to this?
Thanks (and sorry my poor english).
package testevalidanfe;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import javax.swing.JOptionPane;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.dom4j.io.XMLWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class Testevalidanfe {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.parse("exemplo-nfe.xml");
Node no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), no);
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
JOptionPane.showMessageDialog(null, "Validation using org.w3c.dom: " + signature.validate(valContext));
org.dom4j.io.DOMReader domreader = new org.dom4j.io.DOMReader();
org.dom4j.Document doc4 = domreader.read(d);
org.dom4j.io.DOMWriter domwriter = new org.dom4j.io.DOMWriter();
d = domwriter.write(doc4);
String after = doc4.asXML();
PrintWriter writer = new PrintWriter(new File("after-convertion.xml"));
writer.print(after);
writer.close();
no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
valContext = new DOMValidateContext(new X509KeySelector(), no);
fac = XMLSignatureFactory.getInstance("DOM");
signature = fac.unmarshalXMLSignature(valContext);
JOptionPane.showMessageDialog(null, "Validation after convert: " + signature.validate(valContext));
}
}
package testevalidanfe;
import java.security.Key;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.X509Data;
public class X509KeySelector extends KeySelector {
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context)
throws KeySelectorException {
Iterator ki = keyInfo.getContent().iterator();
while (ki.hasNext()) {
XMLStructure info = (XMLStructure) ki.next();
if (!(info instanceof X509Data))
continue;
X509Data x509Data = (X509Data) info;
Iterator xi = x509Data.getContent().iterator();
while (xi.hasNext()) {
Object o = xi.next();
if (!(o instanceof X509Certificate))
continue;
final PublicKey key = ((X509Certificate)o).getPublicKey();
if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
return new KeySelectorResult() {
public Key getKey() { return key; }
};
}
}
}
throw new KeySelectorException("No key found!");
}
static boolean algEquals(String algURI, String algName) {
if ((algName.equalsIgnoreCase("DSA") &&
algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
(algName.equalsIgnoreCase("RSA") &&
algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
return true;
} else {
return false;
}
}
}
For example, if the original XML starts with:
<nfeProc versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
<infNFe Id="NFe31090807301671000131550010001000216008030809" versao="1.10" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
doc4.asXML() return this:
<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.10">
<NFe>
<infNFe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="NFe31090807301671000131550010001000216008030809" versao="1.10">
...
I had a closer look at this, and it turns out that DOM4J DOMWriter is doing something odd w.r.t. namespaces that obviously confuses the canonicalization process. I haven't pin pointed the exact reason, but I think it has to do with DOMWriter inserting extra xmlns attributes in the DOM elements. You can see the effect if you turn on logging for the XML digital signature API (as described in the article you refer to), the canonicalized <SignedInfo> element lacks namespace declaration in the DOM document produced by DOM4J.
However, instead of using DOMWriter, you can produce a DOM document by transformation, using a DOM4J DocumentSource and a DOMResult.
/**
* Create a DOM document from a DOM4J document
*/
static Document copy(org.dom4j.Document orig) {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
DOMResult result = new DOMResult();
t.transform(new DocumentSource(orig), result);
return (Document) result.getNode();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Using the resulting DOM document, the validation works.

issues with writing test case in java [duplicate]

This question already has an answer here:
Closed 12 years ago.
Possible Duplicate:
how to write test case in java
hi
I created one class in which I have one constructor as follows:
public class ABC {
private static String host;
private static String port;
private static String browser;
private static String url;
private static String fullurl;
public ABC(){
try {
File file = new File("Element.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(file);
doc.getDocumentElement().normalize();
please tell me test case for constructor:
First of all, doc is not the output. It is a local variable inside the constructor and can't be tested/validated in a unit test. But on the other hand, you can rely on the (tested) Parser. It will produce the correct DOM for the given input file.
You may want to test, if the values from the input file are stored to the fields as specified.
So create an input file with legal values, create an instance and assert if the fields contain the correct values:
#Test
public void testABCWithLegalValues() {
ABC abc = new ABC("correct.xml"); // NOTE! I invented a new constructor
// to allow passing test config files!!
assertEquals("www.google.com", abc.getHost());
assertEquals(80, abc.getPort());
// ...
}
This is an example test method based on jUnit 4.
Other tests would feed the constructor with malformed xml files or files with illegal data (like a port Address > 65535) and verify that the class reacts as specified.
I have no idea what kinds of test case you need.
However, your can verify the final result in this way: feeding the xml and asserting the values about host, port, browser, url, fullurl.
Maybe you need refactor it to make xml text or file set by test-case.
Your class is performing two distinct tasks:
Reading a file and parsing it as a Document
Processing the Document to determine host, port, browser, url and fullurl
Since this currently all occurs within the constructor and the file name is hardcoded, this class is pretty hard to unit test.
If you can modify the class, then here are my suggestions to make this class testable:
Don't hardcode the name of the file to be parsed. I would pass it as a constructor argument here because you don't need the fileName later on so no need to keep it as a private field.
Separate the tasks, let the constructor read the file and create a separate method to process the document.
Since you wanted the codez, here is the modified class:
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class ABC {
private static final Logger LOG = Logger.getLogger(ABC.class);
private static final String DEFAULT_FILENAME = "Element.xml";
private String host;
private String port;
private String browser;
private String url;
private String fullurl;
public class AbcFileAccessException extends Exception {
private static final long serialVersionUID = 1L;
public AbcFileAccessException(Exception e) {
super(e);
}
}
public ABC() throws AbcFileAccessException {
this(DEFAULT_FILENAME);
}
public ABC(String fileName) throws AbcFileAccessException {
File file = new File(fileName);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
process(db.parse(file));
} catch (ParserConfigurationException e) {
throw new AbcFileAccessException(e);
} catch (SAXException e) {
throw new AbcFileAccessException(e);
} catch (IOException e) {
throw new AbcFileAccessException(e);
}
}
public ABC(Document document) {
process(document);
}
public void process(Document document) {
if (document == null) {
throw new IllegalArgumentException("Document may not be null");
}
document.getDocumentElement().normalize();
LOG.info("Root element " + document.getDocumentElement().getNodeName());
NodeList nodeLst = document.getElementsByTagName("selenium");
for (int s = 0; s < nodeLst.getLength(); s++) {
Node fstNode = nodeLst.item(s);
if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
Element fstElmnt = (Element) fstNode;
NodeList fstNmElmntLst = fstElmnt.getElementsByTagName("name");
Element fstNmElmnt = (Element) fstNmElmntLst.item(0);
NodeList fstNm = fstNmElmnt.getChildNodes();
String name = ((Node) fstNm.item(0)).getNodeValue();
NodeList lstNmElmntLst = fstElmnt.getElementsByTagName("value");
Element lstNmElmnt = (Element) lstNmElmntLst.item(0);
NodeList lstNm = lstNmElmnt.getChildNodes();
String value = ((Node) lstNm.item(0)).getNodeValue();
if (name.equals("host")) {
host = value;
}
if (name.equals("port")) {
port = value;
}
if (name.equals("browser")) {
browser = value;
}
if (name.equals("url")) {
url = value;
}
if (name.equals("fullurl")) {
fullurl = value;
}
}
}
}
public String getHost() {
return host;
}
public String getPort() {
return port;
}
public String getBrowser() {
return browser;
}
public String getUrl() {
return url;
}
public String getFullurl() {
return fullurl;
}
}
Other improvements I made :
Avoid static fields for runtime data like this. If they are private (as in your example) then they can just be instance field, seeing you are already creating a (non-singleton) instance of the class. If you intended them to be accessed by other classes it is even worse, because those other classes could access the fields like ABC.host which makes them hard to test and locked in to you implementation class. Let's not go into that now (-:
NEVER call setters from a contructor (see http://www.javapractices.com/topic/TopicAction.do?Id=215 for an explanation).
Scope try-catch blocks as narrowly as possible (or practical). This makes your code more readable because it is clear where the exceptions are being thrown.
Catch each exception type separately. Bundeling them together makes the code less readable. I agree this is a pain for some parts of the API (try using reflection), but it is good practice. Assume a developer should be able to read and understand your code from a printout (so without hovering and code navigation features of your IDE).
Don't handle exceptions by calling printStacktrace, logging an error or throwing a RuntimeException if you can avoid it. If you do, at least document these error conditions thoroughly. It is ok to create your own exception types for error conditions, this makes for a very understandable API (so other developers don't have to delve into your code, but can use the class after reading the JavaDoc).
Don't use System.out.println for logging, use a logging framework like Log4j.
This class can now be tested as follows:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import xcon.pilot.ABC.AbcFileAccessException;
public class ABCTest {
private Document document;
#Before
public void setUp() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.newDocument();
}
#Test
public void testProcess() throws ParserConfigurationException,
AbcFileAccessException, TransformerFactoryConfigurationError,
TransformerException {
Element root = (Element) document.createElement("root");
document.appendChild(root);
String host = "myhost";
String port = "myport";
String browser = "mybrowser";
String url = "myurl";
String fullurl = "myfullurl";
root.appendChild(createElement("host", host));
root.appendChild(createElement("port", port));
root.appendChild(createElement("browser", browser));
root.appendChild(createElement("url", url));
root.appendChild(createElement("fullurl", fullurl));
// for your convenience
printXml();
ABC instance = new ABC(document);
Assert.assertEquals(host, instance.getHost());
Assert.assertEquals(port, instance.getPort());
Assert.assertEquals(browser, instance.getBrowser());
Assert.assertEquals(url, instance.getUrl());
Assert.assertEquals(fullurl, instance.getFullurl());
}
private Element createElement(String name, String value) {
Element result = (Element) document.createElement("selenium");
Element nameElement = document.createElement("name");
nameElement.setTextContent(name);
result.appendChild(nameElement);
Element valueElement = document.createElement("value");
valueElement.setTextContent(value);
result.appendChild(valueElement);
return result;
}
private void printXml() throws TransformerConfigurationException,
TransformerFactoryConfigurationError, TransformerException {
Transformer transformer = TransformerFactory.newInstance()
.newTransformer();
Source source = new DOMSource(document);
Result output = new StreamResult(System.out);
transformer.transform(source, output);
}
}
This tests your Document processing logic. Testing the reading and parsing of files is notably tricky and can't really be seen as unit testing because you are always dependent on the operating system and its filesystem. I usually leave that as part of integration testing and build/deployment support. It helps to build good sanity checks and error handling in your code so missing/incorrect files are reported clearly and early.
Hope this helped you.

Categories