Reputation: 1
I'm working on an Android app where I need to send a signed XML request to a government agency. The requirement is to canonicalize the XML document before signing it. I cannot use the org.apache.xml.security library because it's incompatible with Android due to issues with System.getLogger(). I'm aware of alternatives like SpongyCastle, but I must adhere strictly to the Canonicalizer standard. The org.apache.xml.security library is not an option due to incompatibility with Android (System.getLogger() issues). Does anyone know of a suitable solution or workaround to achieve XML Canonicalization in Android?
I tried SpongyCastle and other libraries, but none offer direct support for Canonicalizer as required. Libraries like SpongyCastle results in an error from the tax agency (Agenzia delle Entrate in Italy), which returns a response indicating that the XML signature is not valid (Error 406).
(Links inside of the code were removed to avoid spam flagging)!
CODE BY USING CANONICALIZER CAUSING PROBLEMS WITH System.getLogger() of org.apache.xml.security
public static String generateCertificateRequestXml(KeyStore ks, String csrString, String taxCode, String vatNumber) throws Exception {
// Set up the XML document
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true); // Enable namespace support
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
// Create the root element as per the XSD schema
Element rootElement = doc.createElementNS("", "ns3:CertificateRequestDevice");
rootElement.setAttributeNS("", "xmlns:ns3", "");
rootElement.setAttribute("version", "1.0");
doc.appendChild(rootElement);
// Add CSR
Element csrElement = doc.createElement("Csr");
csrElement.appendChild(doc.createTextNode(csrString));
rootElement.appendChild(csrElement);
// Add Device
Element device = doc.createElement("Device");
Element type = doc.createElement("Type");
type.appendChild(doc.createTextNode("DM"));
device.appendChild(type);
rootElement.appendChild(device);
// Add VerifyingTechnician
Element verifyingTechnician = doc.createElement("VerifyingTechnician");
Element taxCodeElem = doc.createElement("TaxCode");
taxCodeElem.appendChild(doc.createTextNode(taxCode));
verifyingTechnician.appendChild(taxCodeElem);
// Add VatCompany
Element vatCompany = doc.createElement("VatCompany");
Element countryId = doc.createElement("CountryId");
countryId.appendChild(doc.createTextNode("IT"));
Element codeId = doc.createElement("CodeId");
codeId.appendChild(doc.createTextNode(vatNumber));
vatCompany.appendChild(countryId);
vatCompany.appendChild(codeId);
verifyingTechnician.appendChild(vatCompany);
rootElement.appendChild(verifyingTechnician);
// Convert the XML document into a string without spaces
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
String xmlString = writer.getBuffer().toString();
// Calculate the XML document digest
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] digestValue = digest.digest(xmlString.getBytes(StandardCharsets.UTF_8));
String digestValueBase64 = android.util.Base64.encodeToString(digestValue, android.util.Base64.DEFAULT);
// Sign the document using the certificate's private key
String alias = "cda.elkey.prod";
if (ks.containsAlias(alias)) {
Key key = ks.getKey(alias, passwordCertDispositivo.toCharArray());
if (key instanceof PrivateKey) {
PrivateKey privateKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(xmlString.getBytes(StandardCharsets.UTF_8));
// Create the signature in Base64
String signedXml = android.util.Base64.encodeToString(signature.sign(), android.util.Base64.DEFAULT);
// Create the Signature element in the XML
Element signatureElement = doc.createElementNS("", "Signature");
// Add SignedInfo
Element signedInfoElement = doc.createElement("SignedInfo");
Element canonicalizationMethod = doc.createElement("CanonicalizationMethod");
canonicalizationMethod.setAttribute("Algorithm", "");
signedInfoElement.appendChild(canonicalizationMethod);
Element signatureMethod = doc.createElement("SignatureMethod");
signatureMethod.setAttribute("Algorithm", "");
signedInfoElement.appendChild(signatureMethod);
// Add Reference and Transforms
Element referenceElement = doc.createElement("Reference");
referenceElement.setAttribute("URI", "");
Element transforms = doc.createElement("Transforms");
Element transform1 = doc.createElement("Transform");
transform1.setAttribute("Algorithm", "");
transforms.appendChild(transform1);
Element transform2 = doc.createElement("Transform");
transform2.setAttribute("Algorithm", "");
transforms.appendChild(transform2);
referenceElement.appendChild(transforms);
// Add DigestMethod and DigestValue
Element digestMethod = doc.createElement("DigestMethod");
digestMethod.setAttribute("Algorithm", "");
referenceElement.appendChild(digestMethod);
Element digestValueElement = doc.createElement("DigestValue");
digestValueElement.appendChild(doc.createTextNode(digestValueBase64));
referenceElement.appendChild(digestValueElement);
signedInfoElement.appendChild(referenceElement);
signatureElement.appendChild(signedInfoElement);
// Add SignatureValue
Element signatureValueElement = doc.createElement("SignatureValue");
signatureValueElement.appendChild(doc.createTextNode(signedXml));
signatureElement.appendChild(signatureValueElement);
// Add the Signature element to the root
rootElement.appendChild(signatureElement);
// Save the final signed XML document
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
transformer.transform(new DOMSource(doc), new StreamResult(outputStream));
String signedXmlOutput = outputStream.toString("UTF-8");
System.out.println("GENERATED XML!");
System.out.println(signedXmlOutput);
// Return the signed XML
return signedXmlOutput;
} else {
throw new Exception("Alias does not correspond to a private key.");
}
} else {
Log.e("CertificateRequest", "Error: alias not found in KeyStore.");
throw new Exception("Alias not found in KeyStore.");
}
}
CODE BY USING SPONGYCASTLE CAUSING XML WITH NOT VALID SIGNATURE
public static String generateCertificateRequestXml(KeyStore ks, String csrString, String taxCode, String vatNumber) throws Exception {
// Set up the XML document
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true); // Enable namespace support
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
// Create the root element as per the XSD schema
Element rootElement = doc.createElementNS("", "ns3:CertificateRequestDevice");
rootElement.setAttributeNS("", "xmlns:ns3", "");
rootElement.setAttribute("version", "1.0");
doc.appendChild(rootElement);
// Add CSR
Element csrElement = doc.createElement("Csr");
csrElement.appendChild(doc.createTextNode(csrString));
rootElement.appendChild(csrElement);
// Add Device
Element device = doc.createElement("Device");
Element type = doc.createElement("Type");
type.appendChild(doc.createTextNode("DM"));
device.appendChild(type);
rootElement.appendChild(device);
// Add VerifyingTechnician
Element verifyingTechnician = doc.createElement("VerifyingTechnician");
Element taxCodeElem = doc.createElement("TaxCode");
taxCodeElem.appendChild(doc.createTextNode(taxCode));
verifyingTechnician.appendChild(taxCodeElem);
// Add VatCompany
Element vatCompany = doc.createElement("VatCompany");
Element countryId = doc.createElement("CountryId");
countryId.appendChild(doc.createTextNode("IT"));
Element codeId = doc.createElement("CodeId");
codeId.appendChild(doc.createTextNode(vatNumber));
vatCompany.appendChild(countryId);
vatCompany.appendChild(codeId);
verifyingTechnician.appendChild(vatCompany);
rootElement.appendChild(verifyingTechnician);
// Convert the XML document into a string without spaces
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
String xmlString = writer.getBuffer().toString();
// Calculate the XML document digest
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] digestValue = digest.digest(xmlString.getBytes(StandardCharsets.UTF_8));
String digestValueBase64 = android.util.Base64.encodeToString(digestValue, android.util.Base64.DEFAULT);
// Sign the document using the certificate's private key
String alias = "cda.elkey.prod";
if (ks.containsAlias(alias)) {
Key key = ks.getKey(alias, passwordCertDispositivo.toCharArray());
if (key instanceof PrivateKey) {
PrivateKey privateKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(xmlString.getBytes(StandardCharsets.UTF_8));
// Create the signature in Base64
String signedXml = android.util.Base64.encodeToString(signature.sign(), android.util.Base64.DEFAULT);
// Create the Signature element in the XML
Element signatureElement = doc.createElementNS("", "Signature");
// Add SignedInfo
Element signedInfoElement = doc.createElement("SignedInfo");
Element canonicalizationMethod = doc.createElement("CanonicalizationMethod");
canonicalizationMethod.setAttribute("Algorithm", "");
signedInfoElement.appendChild(canonicalizationMethod);
Element signatureMethod = doc.createElement("SignatureMethod");
signatureMethod.setAttribute("Algorithm", "");
signedInfoElement.appendChild(signatureMethod);
// Add Reference and Transforms
Element referenceElement = doc.createElement("Reference");
referenceElement.setAttribute("URI", "");
Element transforms = doc.createElement("Transforms");
Element transform1 = doc.createElement("Transform");
transform1.setAttribute("Algorithm", "");
transforms.appendChild(transform1);
Element transform2 = doc.createElement("Transform");
transform2.setAttribute("Algorithm", "");
transforms.appendChild(transform2);
referenceElement.appendChild(transforms);
// Add DigestMethod and DigestValue
Element digestMethod = doc.createElement("DigestMethod");
digestMethod.setAttribute("Algorithm", "");
referenceElement.appendChild(digestMethod);
Element digestValueElement = doc.createElement("DigestValue");
digestValueElement.appendChild(doc.createTextNode(digestValueBase64));
referenceElement.appendChild(digestValueElement);
signedInfoElement.appendChild(referenceElement);
signatureElement.appendChild(signedInfoElement);
// Add SignatureValue
Element signatureValueElement = doc.createElement("SignatureValue");
signatureValueElement.appendChild(doc.createTextNode(signedXml));
signatureElement.appendChild(signatureValueElement);
// Add the Signature element to the root
rootElement.appendChild(signatureElement);
// Save the final signed XML document
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
transformer.transform(new DOMSource(doc), new StreamResult(outputStream));
String signedXmlOutput = outputStream.toString("UTF-8");
System.out.println("GENERATED XML!");
System.out.println(signedXmlOutput);
// Return the signed XML
return signedXmlOutput;
} else {
throw new Exception("Alias does not correspond to a private key.");
}
} else {
Log.e("CertificateRequest", "Error: alias not found in KeyStore.");
throw new Exception("Alias not found in KeyStore.");
}
}
Upvotes: 0
Views: 34