Search code examples
xmllanguage-agnosticx509digital-certificatexml-dsig

Manual validation of a X.509 certificate, bundled with a document in an XML


I have a government-issued document with the following format (much was redacted as it contained some personal information), which contains a doc/docx file and a certificate, encoded in base64:

<?xml version="1.0" encoding="UTF-8"?>
<gov.il:SignedRoot xmlns:gov.il="http://www.gov.il/xmldigsig/v_1_0_0" version="1.0.0">
   <gov.il:SigningAppInfo>
      <gov.il:ApplicationName>Sign and Verify</gov.il:ApplicationName>
      <gov.il:ApplicationVersion>2.0.0</gov.il:ApplicationVersion>
   </gov.il:SigningAppInfo>
   <gov.il:SignedObject Id="il-ae******-****-****-****-***********" MimeType="multipart/form-data">
      <gov.il:SignedInfo Id="il-ea******-****-****-****-***********">
         <gov.il:Data MimeType="multipart/form-data" DataEncodingType="base64">UkVEQUNURUQgV09SRCBET0NVTUVOVA==</gov.il:Data>
         <gov.il:OptionalDataParams>
            <gov.il:FileName>*****.DOCX</gov.il:FileName>
            <gov.il:ContentCreationTime>2018-06-**T**:**:**Z</gov.il:ContentCreationTime>
         </gov.il:OptionalDataParams>
      </gov.il:SignedInfo>
   </gov.il:SignedObject>
   <gov.il:Signature xmlns:gov.il="http://www.w3.org/2000/09/xmldsig#" Id="il-********-****-****-****-************">
      <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
         <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
         <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
         <Reference URI="#il-********-****-****-****-************">
            <Transforms>
               <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
            </Transforms>
            <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
            <DigestValue>/DJC0pAZUaSAQGe1Pl1eDlap75E=</DigestValue>
         </Reference>
      </SignedInfo>
      <SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">UkVEQUNURUQ=</SignatureValue>
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
         <X509Data>
            <X509SubjectName>CN=REDACTED, OU=REDACTED, O=Gov, C=IL</X509SubjectName>
            <X509Certificate>UkVEQUNURUQ=</X509Certificate>
         </X509Data>
      </KeyInfo>
   </gov.il:Signature>
</gov.il:SignedRoot>

Whoever sent me this document is expecting me to download and install a "special program" that is able to open the file and validate the signature.

Since this "mysterious format" is a simple XML, I would like to convert the information found in it into some other format, which can be opened or validated without their dedicated software. Ideally the output would be one of these:

  1. Separate document and certificate files.
  2. A signed document with an embedded certificate openable in e.g. MS Word.

From what I gathered so far, these are the fields of interest:

<gov.il:Data MimeType="multipart/form-data" DataEncodingType="base64">...</gov.il:Data>

<DigestValue>/DJC0pAZUaSAQGe1Pl1eDlap75E=</DigestValue>

<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">...</SignatureValue>

<X509Data>
   ...
</X509Data>

but I don't know what to do from here.

My questions:

  1. What are the steps I need to take to validate the document manually, using well-known tools, based on the contents of the XML given above? If it matters, I'd appreciate steps that are applicable to Windows. Verbal and pseudocode solutions are welcome!
  2. Is it possible to re-assemble this information into a valid, signed, MS Word document? If so - how?

P.S.
If this question is a better fit for Information Security, please comment, and I'll flag it for migration.


Solution

  • This is an answer to question #1.

    I have managed to create a partially-working validator in Kotlin on the basis of Apache's javax.xml.crypto.dsig.samples.Validate example.

    Admittedly, the code below has a bug where the computed Digest value doesn't match the one appearing in the XML (and the validation ultimately fails). However, there is some educational value here since all required validation steps are shown and explained.

    This was tested on Kotlin 1.2.50 and JDK 9.0.1.

    import org.w3c.dom.Element
    import javax.xml.crypto.*
    import javax.xml.crypto.dsig.*
    import javax.xml.crypto.dsig.dom.DOMValidateContext
    import javax.xml.crypto.dsig.keyinfo.*
    import java.io.FileInputStream
    import java.security.*
    import java.security.cert.X509Certificate
    import javax.management.modelmbean.XMLParseException
    import javax.xml.parsers.DocumentBuilderFactory
    
    /**
     * This is a simple example of validating an XML
     * Signature using the JSR 105 API. It assumes the key needed to
     * validate the signature is contained in a KeyValue KeyInfo.
     */
    object Validate {
    
        //
        // Synopsis: java Validate [document]
        //
        //    where "document" is the name of a file containing the XML document
        //    to be validated.
        //
        @JvmStatic
        fun main(args: Array<String>) {
            // Instantiate the document to be validated
            val dbf = DocumentBuilderFactory.newInstance()
            dbf.isNamespaceAware = true
    
            val doc = dbf.newDocumentBuilder().parse(FileInputStream(args[0]))
    
            // Find Signature element
            val nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature")
            if (nl.length == 0) {
                throw XMLParseException("Cannot find any Signature elements")
            }
    
            // Find SignedInfo elements that have an "Id" property and explicitly set them to be 
            // of type "ID". Inspired by: https://stackoverflow.com/a/7466809/3372061
            val nd = doc.getElementsByTagNameNS("*", "SignedInfo")
            (0 until nd.length)
                .map { nd.item(it) }
                .filter { it -> it.attributes.getNamedItem("Id") != null }
                .forEach { it -> (it as Element).setIdAttribute("Id", true) }
    
            // Create a DOM XMLSignatureFactory that will be used to unmarshal the
            // document containing the XMLSignature
            val fac = XMLSignatureFactory.getInstance("DOM")
    
            // Create a DOMValidateContext and specify a KeyValue KeySelector
            // and document context
            val valContext = DOMValidateContext(KeyValueKeySelector(), nl.item(0))
    
            // Unmarshal the XMLSignature
            val signature = fac.unmarshalXMLSignature(valContext)
    
            // Validate the XMLSignature (generated above)
            val coreValidity = signature.validate(valContext)
    
            // Check core validation status
            if (!coreValidity) {
                System.err.println("Signature failed core validation")
                val sv = signature.signatureValue.validate(valContext)
                println("signature validation status: " + sv)
                // check the validation status of each Reference
                val i = signature.signedInfo.references.iterator()
                var j = 0
                while (i.hasNext()) {
                    val refValid = i.next().validate(valContext)
                    println("ref[$j] validity status: $refValid")
                    j++
                }
            } else {
                println("Signature passed core validation")
            }
        }
    
        /**
         * KeySelector which retrieves the public key out of the
         * KeyValue element and returns it.
         * NOTE: If the key algorithm doesn't match signature algorithm,
         * then the public key will be ignored.
         */
        private class KeyValueKeySelector : KeySelector() {
            @Throws(KeySelectorException::class)
            override fun select(keyInfo: KeyInfo?,
                                purpose: KeySelector.Purpose,
                                method: AlgorithmMethod,
                                context: XMLCryptoContext): KeySelectorResult {
                if (keyInfo == null) {
                    throw KeySelectorException("Null KeyInfo object!")
                }
                val sm = method as SignatureMethod
                val list = keyInfo.content
                var pk: PublicKey? = null
    
                for (item in list) {
                    val xmlStructure = item as XMLStructure
                    if (xmlStructure is KeyValue) {
                        try {
                            pk = xmlStructure.publicKey
                        } catch (ke: KeyException) {
                            throw KeySelectorException(ke)
                        }
                    } else if (xmlStructure is X509Data) {
                        for (data in xmlStructure.content) {
                            if (data is X509Certificate) {
                                pk = data.publicKey
                                break
                            }
                        }
                    }
                    // make sure algorithm is compatible with method
                    if (algEquals(sm.algorithm, pk!!.algorithm)) {
                        return SimpleKeySelectorResult(pk)
                    }
                }
                throw KeySelectorException("No KeyValue element found!")
            }
    
            companion object {
    
                //@@@FIXME: this should also work for key types other than DSA/RSA
                internal fun algEquals(algURI: String, algName: String): Boolean {
                    return (algName.equals("DSA", ignoreCase = true) &&
                             algURI.equals(SignatureMethod.DSA_SHA1, ignoreCase = true)) ||
                           (algName.equals("RSA", ignoreCase = true) &&
                             algURI.equals(SignatureMethod.RSA_SHA1, ignoreCase = true))
                }
            }
        }
    
        private class SimpleKeySelectorResult
        internal constructor(private val pk: PublicKey) : KeySelectorResult {
    
            override fun getKey(): Key {
                return pk
            }
        }
    }