I have been trying to get XML signing to work, using the JDK's javax.xml.crypto.dsig package.
It works when i don't have any namespaces, but as soon as i add the namespaces, then the signing and validation works, on the in-memory DOM object. As soon as the object is transformed and then parsed again, the validation fails.
I have created an example where this can easily be seen. In the main-method, right after creating the Document, i have to transform and then parse the document, in which case signing and validation works. This seems to be just really stupid =))
So my question: Does anyone have an idea how i can fix the code so i don't need to first transform the Document to bytes then parse them again, so that the signature validation works?
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
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.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
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.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
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.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("Creating doc...");
Document doc = createDoc();
// AFTER creating the document, i must immediately
// transform and parse the document, to have xml
// signing and validation work:
doc = parse(transform(doc));
System.out.println();
System.out.println("Initial unsigned document:");
System.out.println();
dumpDocument(doc);
System.out.println();
System.out.println("Signing...");
sign(doc);
validate(doc);
System.out.println();
System.out.println("Initial signed and validated document:");
System.out.println();
dumpDocument(doc);
System.out.println();
// transform
System.out.println("Transforming...");
byte[] bytes = transform(doc);
System.out.println("Transformed document:");
System.out.println();
System.out.println(new String(bytes, "UTF-8"));
System.out.println();
// now parse
System.out.println("Parsing...");
doc = parse(bytes);
System.out.println();
System.out.println("After parsing:");
System.out.println();
//dumpDocument(doc);
System.out.println("Validating...");
validate(doc);
}
private static void dumpDocument(Node root) throws TransformerException {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(new DOMSource(root), new StreamResult(System.out));
}
public static byte[] transform(Document doc) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(out));
return out.toByteArray();
}
public static Document parse(byte[] bytes) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(bytes));
return doc;
}
public static Document createDoc() throws Exception {
String issuer = "test";
String destination = "test";
String assertionConsumerServiceUrl = "test";
Calendar issueInstant = Calendar.getInstance();
// create dates
SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
simpleDf.setTimeZone(TimeZone.getTimeZone("UTC"));
String issueInstantS = simpleDf.format(issueInstant.getTime());
String notBeforeS = issueInstantS;
issueInstant.add(Calendar.SECOND, 10);
String notOnOrAfterS = simpleDf.format(issueInstant.getTime());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
Document doc = docBuilder.newDocument();
Element authnReqE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest");
//Element authnReqE = doc.createElement("AuthnRequest");
authnReqE.setPrefix("samlp");
authnReqE.setAttribute("Version", "2.0");
authnReqE.setAttribute("IssueInstant", issueInstantS);
authnReqE.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
authnReqE.setAttribute("AssertionConsumerServiceURL", assertionConsumerServiceUrl);
authnReqE.setAttribute("Destination", destination);
doc.appendChild(authnReqE);
Element issuerE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer");
//Element issuerE = doc.createElement("Issuer");
issuerE.setPrefix("saml");
issuerE.setTextContent(issuer);
authnReqE.appendChild(issuerE);
Element conditionsE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Conditions");
//Element conditionsE = doc.createElement("Conditions");
conditionsE.setPrefix("saml");
conditionsE.setAttribute("NotBefore", notBeforeS);
conditionsE.setAttribute("NotOnOrAfter", notOnOrAfterS);
authnReqE.appendChild(conditionsE);
return doc;
}
public static void sign(Document doc) throws Exception {
String id = "Signed_" + UUID.randomUUID().toString();
Element rootElement = doc.getDocumentElement();
rootElement.setAttribute("ID", id);
// Create a DOM XMLSignatureFactory that will be used to
// generate the enveloped signature.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create a Reference to the enveloped document (in this case,
// you are signing the whole document, so a URI of "" signifies
// that, and also specify the SHA1 digest algorithm and
// the ENVELOPED Transform.
List<Transform> transforms = new ArrayList<>();
transforms.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));
transforms.add(fac.newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null));
DigestMethod digestMethod = fac.newDigestMethod(DigestMethod.SHA1, null);
Reference ref = fac.newReference("#" + id, digestMethod, transforms, null, null);
//Reference ref = fac.newReference("", digestMethod, transforms, null, null);
// Create the SignedInfo.
SignedInfo signedInfo = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null), //
fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), //
Collections.singletonList(ref));
// Load the KeyStore and get the signing key and certificate.
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("KeyStore_client.jks"), "changeit".toCharArray());
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(),
new KeyStore.PasswordProtection("changeit".toCharArray()));
X509Certificate cert = (X509Certificate) keyEntry.getCertificate();
// Create the KeyInfo containing the X509Data.
KeyInfoFactory kif = fac.getKeyInfoFactory();
List<Object> x509Content = new ArrayList<>();
x509Content.add(cert.getSubjectX500Principal().getName());
x509Content.add(cert);
X509Data xd = kif.newX509Data(x509Content);
KeyInfo keyInfo = kif.newKeyInfo(Collections.singletonList(xd));
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element.
DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), rootElement);
//dsc.setDefaultNamespacePrefix("samlp");
dsc.putNamespacePrefix(XMLSignature.XMLNS, "ds");
dsc.setIdAttributeNS(rootElement, null, "ID");
// Create the XMLSignature, but don't sign it yet.
XMLSignature signature = fac.newXMLSignature(signedInfo, keyInfo);
// Marshal, generate, and sign the enveloped signature.
signature.sign(dsc);
}
public static void validate(Document doc) throws Exception {
// Create a DOM XMLSignatureFactory that will be used to
// generate the enveloped signature.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Find Signature element.
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element!");
} else if (nl.getLength() > 1) {
throw new Exception("Found multiple Signature elements!");
}
// Load the KeyStore and get the signing key and certificate.
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("KeyStore_client.jks"), "changeit".toCharArray());
KeyStore.PrivateKeyEntry entry = (PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(),
new KeyStore.PasswordProtection("changeit".toCharArray()));
PublicKey publicKey = entry.getCertificate().getPublicKey();
// Create a DOMValidateContext and specify a KeySelector
// and document context.
Node signatureNode = nl.item(0);
DOMValidateContext valContext = new DOMValidateContext(publicKey, signatureNode);
valContext.setDefaultNamespacePrefix("samlp");
valContext.putNamespacePrefix(XMLSignature.XMLNS, "ds");
valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:protocol", "samlp");
valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:assertion", "saml");
valContext.setIdAttributeNS(doc.getDocumentElement(), null, "ID");
// Unmarshal the XMLSignature.
valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate the XMLSignature.
boolean coreValidity = signature.validate(valContext);
// Check core validation status.
if (coreValidity) {
System.out.println("Signature passed core validation");
} else {
System.err.println("Signature failed core validation");
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("signature validation status: " + sv);
if (!sv) {
// Check the validation status of each Reference.
Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
for (int j = 0; i.hasNext(); j++) {
boolean refValid = ((Reference) i.next()).validate(valContext);
System.out.println("ref[" + j + "] validity status: " + refValid);
}
}
throw new RuntimeException("Uh-oh, failed!");
}
}
}
Related
I have an xml file:
<pickingOrderBeginEventMessage xmlns="http://www.xmlns.walmartstores.com/SuppyChain/FulfillmentManagement/GlobalIntegeratedFulfillment/">
<MessageBody>
<RoutingInfo>
<SourceNode>
<location>
<countryCode>US</countryCode>
</location>
</SourceNode>
</RoutingInfo>
<fulfillmentOrders>
<fulfillmentOrder>
<orderNbr>784</orderNbr>
</fulfillmentOrder>
</fulfillmentOrders>
</MessageBody>
</pickingOrderBeginEventMessage>
I want to change <orderNbr>784</orderNbr> to <orderNbr>784778474484747</orderNbr>
This is my method:
(Note that I am using dom4j.)
public String replaceXML(String attribute,String oldValue, String newValue) throws SAXException, DocumentException, IOException, TransformerException {
SAXReader xmlReader = new SAXReader();
Document input = xmlReader.read("src/test/resources/xml/pick_begin.xml");
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
String expr = String.format("//*[contains(#%s, '%s')]", attribute, oldValue);
XPath xpath = DocumentHelper.createXPath(expr);
List<Node> nodes = xpath.selectNodes(input);
for (int i = 0; i < nodes.size(); i++) {
Element element = (Element) nodes.get(i);
element.addAttribute(attribute, newValue);
}
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Writer output = new StringWriter();
xformer.transform(new DocumentSource(input), new StreamResult(output));
return output.toString();
}
}
Where String attribute is orderNbr, oldValue is 784 and newValue is 78455556767.
But with this method, the new value is not getting replaced. Where am I going wrong?
According to the XML file in your question, orderNbr is an element and not an attribute and its text value is 784. So you want to replace the text value with 78455556767.
Your code does not change the original XML because your XPath query string does not find anything.
Therefore you need to change two things in your code.
The XPath query string.
The method you call to change the XML.
The below code contains the two changes. The changed lines are indicated with the following comment at the end of the line.
// CHANGE HERE
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.XPath;
import org.dom4j.io.DocumentSource;
import org.dom4j.io.SAXReader;
import org.xml.sax.SAXException;
public class ChngAttr {
public static String replaceXML(String attribute,
String oldValue,
String newValue) throws DocumentException,
IOException,
SAXException,
TransformerException {
SAXReader xmlReader = new SAXReader();
Document input = xmlReader.read("pick_begin.xml");
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
String expr = String.format("//%s[text() = '%s']", attribute, oldValue); // CHANGE HERE
XPath xpath = DocumentHelper.createXPath(expr);
List<Node> nodes = xpath.selectNodes(input);
for (int i = 0; i < nodes.size(); i++) {
Element element = (Element) nodes.get(i);
element.setText(newValue); // CHANGE HERE
}
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Writer output = new StringWriter();
xformer.transform(new DocumentSource(input), new StreamResult(output));
return output.toString();
}
public static void main(String[] args) throws Exception {
String result = replaceXML("orderNbr", "784", "78455556767");
System.out.println(result);
}
}
I just need to encrypt some XML elements with a public key. I successfully can use this code with a simetric algorithm (XMLCipher.AES_128), but failed when try use XMLCipher.RSA_v1dot5.
That is the code:
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.xml.security.encryption.XMLCipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class XmlEncryption {
private static final String MENSAGEM_ENTIDADE_1 = "mensagem1";
private static final String MENSAGEM_ENTIDADE_2 = "mensagem2";
public static void main(String[] args) throws Exception {
/*
* Encrypt a file with 2 diferent assimetric keys
*/
// parse file into document
// Document document = EncryptToolExtended.parseFile("teste.xml");
Document document = EncryptToolExtended.parseFile("teste.xml");
// generate the keys
KeyPair entity1KeyPair = EncryptToolExtended.GenerateAssymmetricKey();
//KeyPair entity2KeyPair = EncryptToolExtended.GenerateAssymmetricKey();
// specify the element to encrypt
Element rootElement = document.getDocumentElement();
Element mensagem1ToEncrypt = rootElement;
Element mensagem2ToEncrypt = rootElement;
mensagem1ToEncrypt = (Element) rootElement.getElementsByTagName(
MENSAGEM_ENTIDADE_1).item(0);
mensagem2ToEncrypt = (Element) rootElement.getElementsByTagName(
MENSAGEM_ENTIDADE_2).item(0);
if (mensagem1ToEncrypt == null || mensagem2ToEncrypt == null) {
System.err.println("Unable to find element: " + MENSAGEM_ENTIDADE_1
+ " or " + MENSAGEM_ENTIDADE_2);
System.exit(1);
}
// initialize cipher
Security.addProvider(new BouncyCastleProvider());
XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.RSA_v1dot5);
xmlCipher.init(XMLCipher.ENCRYPT_MODE, entity1KeyPair.getPublic());
// add key info to encrypted data element
// EncryptedData encryptedDataElement = xmlCipher.getEncryptedData();
// KeyInfo keyInfo = new KeyInfo(document);
// keyInfo.add(entity1KeyPair.getPublic());
// encryptedDataElement.setKeyInfo(keyInfo);
// do the actual encryption
boolean encryptContentsOnly = true;
xmlCipher.doFinal(document, mensagem1ToEncrypt);
xmlCipher.doFinal(document, mensagem1ToEncrypt, encryptContentsOnly);
// write the results to a file
// EncryptToolExtended.writeEncryptedDocToFile(document, "encc.xml");
}
public static Document parseFile(String fileName) throws Exception {
javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory
.newInstance();
dbf.setNamespaceAware(true);
javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(fileName);
return document;
}
public static KeyPair GenerateAssymmetricKey() throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
return keyGen.genKeyPair();
}
}
Exception:
java.lang.IllegalArgumentException: unknown parameter type.
at org.bouncycastle.jce.provider.JCERSACipher.engineInit(Unknown Source)
at javax.crypto.Cipher.init(Cipher.java:1346)
at javax.crypto.Cipher.init(Cipher.java:1282)
at org.apache.xml.security.encryption.XMLCipher.encryptData(XMLCipher.java:1184)
at org.apache.xml.security.encryption.XMLCipher.encryptData(XMLCipher.java:1136)
at org.apache.xml.security.encryption.XMLCipher.encryptElement(XMLCipher.java:869)
at org.apache.xml.security.encryption.XMLCipher.doFinal(XMLCipher.java:987)
at XmlEncryption.main(XmlEncryption.java:84)
Any tips?
Thanks
From the given XML String, i have to update End Date value .
Even though I'm updating the xml in updateNodeValue() method, my final output xml is same as the input xml.
Can someone tell me what is the mistake in this code
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
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 class MyClass{
static String strXml = "<INFO><BeginDate>2013-12-02</BeginDate><EndDate>2014-01-31</EndDate></INFO>";
public static void main(String[] args) throws Exception {
System.out.println(strXml);
Document doc = StringToDocument(strXml);
updateNodeValue(doc);
String newxml = DocumentToString(doc);
System.out.println(newxml);
}
public static void updateNodeValue(Document doc) {
Node rootNode = doc.getFirstChild();
NodeList list = rootNode.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Element element = (Element) list.item(i);
Node node = list.item(i);
if ("EndDate".equals(node.getNodeName())) {
element.setNodeValue("2013-12-12");
return;
}
}
}
public static String DocumentToString(Document doc) throws Exception {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
String output = writer.getBuffer().toString();
return output;
}
public static Document StringToDocument(String strXml) throws Exception {
Document doc = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
StringReader strReader = new StringReader(strXml);
InputSource is = new InputSource(strReader);
doc = (Document) builder.parse(is);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return doc;
}
}
Useelement.setTextContent(...) in your updateNodeValue method.
The method you should use is not setNodeValue() but setTextContent()
See http://docs.oracle.com/javase/1.5.0/docs/api/org/w3c/dom/Node.html#setNodeValue(java.lang.String)
I am using bcmail-jdk16-1.46.jar and bcprov-jdk16-1.46.jar (Bouncycastle libraries) to sign a string and then verify the signature.
This is my code to sign a string:
package my.package;
import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
import sun.misc.BASE64Encoder;
public class SignMessage {
static final String KEYSTORE_FILE = "keys/certificates.p12";
static final String KEYSTORE_INSTANCE = "PKCS12";
static final String KEYSTORE_PWD = "test";
static final String KEYSTORE_ALIAS = "Key1";
public static void main(String[] args) throws Exception {
String text = "This is a message";
Security.addProvider(new BouncyCastleProvider());
KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());
//Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(text.getBytes());
//Build CMS
X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
List certList = new ArrayList();
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
certList.add(cert);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, cert));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, false);
BASE64Encoder encoder = new BASE64Encoder();
String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
System.out.println("Signed content: " + signedContent + "\n");
String envelopedData = encoder.encode(sigData.getEncoded());
System.out.println("Enveloped data: " + envelopedData);
}
}
Now, the EnvelopedData output will be used in the process to verify the signature:
package my.package;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;
public class VerifySignature {
public static void main(String[] args) throws Exception {
String envelopedData = "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIAwggLQMIIC" +
"OQIEQ479uzANBgkqhkiG9w0BAQUFADCBrjEmMCQGCSqGSIb3DQEJARYXcm9zZXR0YW5ldEBtZW5k" +
"ZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEi" +
"MCAGA1UEChMZbWVuZGVsc29uLWUtY29tbWVyY2UgR21iSDEiMCAGA1UECxMZbWVuZGVsc29uLWUt" +
"Y29tbWVyY2UgR21iSDENMAsGA1UEAxMEbWVuZDAeFw0wNTEyMDExMzQyMTlaFw0xOTA4MTAxMzQy" +
"MTlaMIGuMSYwJAYJKoZIhvcNAQkBFhdyb3NldHRhbmV0QG1lbmRlbHNvbi5kZTELMAkGA1UEBhMC" +
"REUxDzANBgNVBAgTBkJlcmxpbjEPMA0GA1UEBxMGQmVybGluMSIwIAYDVQQKExltZW5kZWxzb24t" +
"ZS1jb21tZXJjZSBHbWJIMSIwIAYDVQQLExltZW5kZWxzb24tZS1jb21tZXJjZSBHbWJIMQ0wCwYD" +
"VQQDEwRtZW5kMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+X1g6JvbdwJI6mQMNT41GcycH" +
"UbwCFWKJ4qHDaHffz3n4h+uQJJoQvc8yLTCfnl109GB0yL2Y5YQtTohOS9IwyyMWBhh77WJtCN8r" +
"dOfD2DW17877te+NlpugRvg6eOH6np9Vn3RZODVxxTyyJ8pI8VMnn13YeyMMw7VVaEO5hQIDAQAB" +
"MA0GCSqGSIb3DQEBBQUAA4GBALwOIc/rWMAANdEh/GgO/DSkVMwxM5UBr3TkYbLU/5jg0Lwj3Y++" +
"KhumYSrxnYewSLqK+JXA4Os9NJ+b3eZRZnnYQ9eKeUZgdE/QP9XE04y8WL6ZHLB4sDnmsgVaTU+p" +
"0lFyH0Te9NyPBG0J88109CXKdXCTSN5gq0S1CfYn0staAAAxggG9MIIBuQIBATCBtzCBrjEmMCQG" +
"CSqGSIb3DQEJARYXcm9zZXR0YW5ldEBtZW5kZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI" +
"EwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEiMCAGA1UEChMZbWVuZGVsc29uLWUtY29tbWVyY2Ug" +
"R21iSDEiMCAGA1UECxMZbWVuZGVsc29uLWUtY29tbWVyY2UgR21iSDENMAsGA1UEAxMEbWVuZAIE" +
"Q479uzAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx" +
"DxcNMTMwNTIxMDE1MDUzWjAjBgkqhkiG9w0BCQQxFgQU8mE6gw6iudxLUc9379lWK0lUSWcwDQYJ" +
"KoZIhvcNAQEBBQAEgYB5mVhqJu1iX9nUqfqk7hTYJb1lR/hQiCaxruEuInkuVTglYuyzivZjAR54" +
"zx7Cfm5lkcRyyxQ35ztqoq/V5JzBa+dYkisKcHGptJX3CbmmDIa1s65mEye4eLS4MTBvXCNCUTb9" +
"STYSWvr4VPenN80mbpqSS6JpVxjM0gF3QTAhHwAAAAAAAA==";
Security.addProvider(new BouncyCastleProvider());
CMSSignedData cms = new CMSSignedData(Base64.decode(envelopedData.getBytes()));
Store store = cms.getCertificates();
SignerInformationStore signers = cms.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
Collection certCollection = store.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
System.out.println("verified");
}
}
}
}
Everything works fine until signer.verify(..) due to the following Exception:
Exception in thread "main" org.bouncycastle.cms.CMSSignerDigestMismatchException: message-digest attribute value does not match calculated value
at org.bouncycastle.cms.SignerInformation.doVerify(Unknown Source)
at org.bouncycastle.cms.SignerInformation.verify(Unknown Source)
at my.package.VerifySignature.main(VerifySignature.java:64)
Can someone please give me a hint of what could be happening?
PS. If someone wants to test above code you will need the test certificate file that I am using to replicate this, just download it from here:
https://www.dropbox.com/s/zs4jo1a86v8qamw/certificates.p12?dl=0
The
gen.generate(msg, false)
means the signed data is not encapsulated in the signature. This is fine if you want to create a detached signature, but it does mean that when you go to verify the SignedData you have to use the CMSSignedData constructor that takes a copy of the data as well - in this case the code is using the single argument constructor which has to assume the signed data was encapsulated (so for this case will be empty), with the result that the attempt at verification is failing.
There are two kinds of CMSSignedData object generated using CMSSignedDataGenerator They are generated by the following way:
The one below generates a CMSSignedData object carrying a detached CMS signature
gen.generate(cmsdata);
The code below creates a CMSSignedData carrying a detached CMS signature, having the data encapsulated
gen.generate(cmsdata, true);
So verifying them requires 2 approaches
Approach No.1 to verify detached signature with encapsulated data
//sig is the Signature object
CMSSignedData signedData = new CMSSignedData(Base64.decode(sig.getBytes()));
Approach No.2 to verify detached signature without encapsulated data, just the detached signature
//Create a CMSProcessable object, specify any encoding, I have used mine
CMSProcessable signedContent = new CMSProcessableByteArray(content.getBytes("ISO-8859-1"));
//Create a InputStream object
InputStream is = new ByteArrayInputStream(Base64.decode(sig.getBytes()));
//Pass them both to CMSSignedData constructor
CMSSignedData signedData = new CMSSignedData(signedContent, is);
Rest of the code for verification remains the same
Store store = signedData.getCertificates();
SignerInformationStore signers = signedData.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation)it.next();
Collection certCollection = store.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder certHolder = (X509CertificateHolder)certIt.next();
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
ret = true;
}
}
If we use signature.sign() as in the OP's answer, we won't be able to retrieve the original message, because its only the signature.
You should just input the original text bytes rather than the signed content. Basically, ignore this part:
//Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(text.getBytes());
and just input as:
CMSTypedData msg = new CMSProcessableByteArray(text.getBytes());
You can find answer at this link here You need to just add some headers to message or just add one blank line before message then sign the message then it will work fine.
Got it working for detached signature :D
package signature;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;
public class VerifySignature {
static final String DIGEST_SHA1 = "SHA1withRSA";
static final String BC_PROVIDER = "BC";
public static void main(String[] args) throws Exception {
String envelopedData = "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIAwggLQMIIC" +
"OQIEQ479uzANBgkqhkiG9w0BAQUFADCBrjEmMCQGCSqGSIb3DQEJARYXcm9zZXR0YW5ldEBtZW5k" +
"ZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEi" +
"MCAGA1UEChMZbWVuZGVsc29uLWUtY29tbWVyY2UgR21iSDEiMCAGA1UECxMZbWVuZGVsc29uLWUt" +
"Y29tbWVyY2UgR21iSDENMAsGA1UEAxMEbWVuZDAeFw0wNTEyMDExMzQyMTlaFw0xOTA4MTAxMzQy" +
"MTlaMIGuMSYwJAYJKoZIhvcNAQkBFhdyb3NldHRhbmV0QG1lbmRlbHNvbi5kZTELMAkGA1UEBhMC" +
"REUxDzANBgNVBAgTBkJlcmxpbjEPMA0GA1UEBxMGQmVybGluMSIwIAYDVQQKExltZW5kZWxzb24t" +
"ZS1jb21tZXJjZSBHbWJIMSIwIAYDVQQLExltZW5kZWxzb24tZS1jb21tZXJjZSBHbWJIMQ0wCwYD" +
"VQQDEwRtZW5kMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+X1g6JvbdwJI6mQMNT41GcycH" +
"UbwCFWKJ4qHDaHffz3n4h+uQJJoQvc8yLTCfnl109GB0yL2Y5YQtTohOS9IwyyMWBhh77WJtCN8r" +
"dOfD2DW17877te+NlpugRvg6eOH6np9Vn3RZODVxxTyyJ8pI8VMnn13YeyMMw7VVaEO5hQIDAQAB" +
"MA0GCSqGSIb3DQEBBQUAA4GBALwOIc/rWMAANdEh/GgO/DSkVMwxM5UBr3TkYbLU/5jg0Lwj3Y++" +
"KhumYSrxnYewSLqK+JXA4Os9NJ+b3eZRZnnYQ9eKeUZgdE/QP9XE04y8WL6ZHLB4sDnmsgVaTU+p" +
"0lFyH0Te9NyPBG0J88109CXKdXCTSN5gq0S1CfYn0staAAAxggG9MIIBuQIBATCBtzCBrjEmMCQG" +
"CSqGSIb3DQEJARYXcm9zZXR0YW5ldEBtZW5kZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI" +
"EwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEiMCAGA1UEChMZbWVuZGVsc29uLWUtY29tbWVyY2Ug" +
"R21iSDEiMCAGA1UECxMZbWVuZGVsc29uLWUtY29tbWVyY2UgR21iSDENMAsGA1UEAxMEbWVuZAIE" +
"Q479uzAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx" +
"DxcNMTMwNTIxMDE1MDUzWjAjBgkqhkiG9w0BCQQxFgQU8mE6gw6iudxLUc9379lWK0lUSWcwDQYJ" +
"KoZIhvcNAQEBBQAEgYB5mVhqJu1iX9nUqfqk7hTYJb1lR/hQiCaxruEuInkuVTglYuyzivZjAR54" +
"zx7Cfm5lkcRyyxQ35ztqoq/V5JzBa+dYkisKcHGptJX3CbmmDIa1s65mEye4eLS4MTBvXCNCUTb9" +
"STYSWvr4VPenN80mbpqSS6JpVxjM0gF3QTAhHwAAAAAAAA==";
String Sig_Bytes ="YduK22AlMLSXV3ajX5r/pX5OQ0xjj58uhGT9I9MvOrz912xNHo+9OiOKeMOD+Ys2/LUW3XaN6T+/"+
"tuRM5bi4RK7yjaqaJCZWtr/O4I968BQGgt0cyNvK8u0Jagbr9MYk6G7nnejbRXYHyAOaunqD05lW"+
"U/+g92i18dl0OMc50m4=";
Provider provider = new BouncyCastleProvider();
Security.addProvider(provider);
CMSSignedData signedData = new CMSSignedData(Base64.decode(envelopedData.getBytes()));
CMSProcessable cmsProcesableContent = new CMSProcessableByteArray(Base64.decode(Sig_Bytes.getBytes()));
signedData = new CMSSignedData(cmsProcesableContent, Base64.decode(envelopedData.getBytes()));
// Verify signature
Store store = signedData.getCertificates();
SignerInformationStore signers = signedData.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
Collection certCollection = store.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
X509Certificate certFromSignedData = new JcaX509CertificateConverter().setProvider(BC_PROVIDER).getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC_PROVIDER).build(certFromSignedData))) {
System.out.println("Signature verified");
} else {
System.out.println("Signature verification failed");
}
}
}
}
I have the following simplified XML:
<?xml version="1.0" encoding="UTF-8"?>
<ExportData>
<Rows>
<R>
<companyCodestringtrue>101</companyCodestringtrue>
<transactionQualifierstring>Sales</transactionQualifierstring>
<menuItemNumberlong>4302150</menuItemNumberlong>
<productQuantityinttrue>14</productQuantityinttrue>
<productValueInclVATdecimaltrue>1.90</productValueInclVATdecimaltrue>
<productValueExclVATdecimaltrue>1.775701</productValueExclVATdecimaltrue>
</R>
<R>
<companyCodestringtrue>101</companyCodestringtrue>
<transactionQualifierstring>Sales</transactionQualifierstring>
<menuItemNumberlong>333555</menuItemNumberlong>
<productQuantityinttrue>0</productQuantityinttrue>
<productValueInclVATdecimaltrue>3.90</productValueInclVATdecimaltrue>
<productValueExclVATdecimaltrue>3.775701</productValueExclVATdecimaltrue>
</R>
<R>
<companyCodestringtrue>101</companyCodestringtrue>
<transactionQualifierstring>Sales</transactionQualifierstring>
<menuItemNumberlong>1235665</menuItemNumberlong>
<productQuantityinttrue>5</productQuantityinttrue>
<productValueInclVATdecimaltrue>4.90</productValueInclVATdecimaltrue>
<productValueExclVATdecimaltrue>4.775701</productValueExclVATdecimaltrue>
</R>
</Rows>
</ExportData>
I need to delete each complete <R> element if the <productQuantityinttrue> element equals "0".
I came up with the following Java code:
package filterPositions;
import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
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.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class FilterPositions {
public static String result = "";
public static void main(String[] args) throws Exception {
try {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
File filePath = new File("C:/LSA_SALES_EXPORT_1507_test_zero_qu.xml");
Document doc = docBuilder.parse(filePath);
Node rootNode = doc.getDocumentElement();
final Element element = doc.getDocumentElement();
// output new XML Document
DocumentBuilder parser = docFactory.newDocumentBuilder();
Document newdoc = parser.newDocument();
newdoc.adoptNode(traversingXML(element));
writeXmlFile(newdoc, "LSA_SALES_EXPORT_1507_test_zero_qu_OUT.xml");
System.out.println("Done...");
System.out.println("Exiting...");
} catch (Exception e) {
e.printStackTrace();
}
}
public static Element traversingXML(Element element) {
NodeList positionen = element.getElementsByTagName("R");
Element e = null;
for (int i = 0; i < positionen.getLength(); i++) {
e = (Element) positionen.item(i);
for (Node child = e.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof Element && "productQuantityinttrue".equals(child.getNodeName())&& "0".equals(child.getTextContent())) {
e.getParentNode().removeChild(e);
}
}
}
System.out.println(e);
return e;
}
public static void writeXmlFile(Document doc, String filename) {
try {
// Prepare the DOM document for writing
Source source = new DOMSource();
// Prepare the output file
File file = new File(filename);
Result result = new StreamResult(file);
// Write the DOM document to the file
Transformer xformer = TransformerFactory.newInstance()
.newTransformer();
xformer.transform(source, result);
} catch (TransformerConfigurationException e) {
} catch (TransformerException e) {
}
}
}
I am not sure if my method "traversingXML" is working properly. My problem right now is that the adapted XML structure (one deleted) is not written to newdoc.
You don't copy the original document to newdoc; instead you create a new, empty XML document.
Instead, try this code:
...
final Element element = doc.getDocumentElement(); // original code up to here
traversingXML(element); // delete the node
writeXmlFile(doc, "LSA_SALES_EXPORT_1507_test_zero_qu_OUT.xml"); // save modified document