Shukur Nuriyev
Shukur Nuriyev

Reputation: 1

How to perform XML Canonicalization in Android without org.apache.xml.security?

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

Answers (0)

Related Questions