代码之家  ›  专栏  ›  技术社区  ›  Dev-iL

与XML文档捆绑在一起的X.509证书的手动验证

  •  0
  • Dev-iL  · 技术社区  · 6 年前

    我有一份政府签发的文件,格式如下(其中许多文件是经过编辑的,因为其中包含一些个人信息),其中包含一个doc/docx文件和一个证书,用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>
    

    无论是谁寄给我这份文件,都希望我下载并安装一个“特殊程序”,能够打开文件并验证签名。

    由于这个“神秘格式”是一个简单的XML,我想将其中的信息转换成其他格式,这些格式可以在没有他们专用软件的情况下打开或验证。理想情况下,输出将是以下之一:

    1. 单独的文档和证书文件。
    2. 一种已签名的文件,带有一个嵌入的证书,可在例如ms word中打开。

    从我目前收集到的资料来看,以下是我感兴趣的领域:

    <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>
    

    但我不知道该怎么办。

    我的问题:

    1. 根据上面给出的XML内容,我需要采取哪些步骤来使用众所周知的工具手动验证文档?如果这很重要的话,我会很感激适用于windows的步骤。欢迎使用口头和伪代码解决方案!
    2. 是否可以将此信息重新组合为有效的、已签名的MS Word文档?如果是这样,怎么办?

    附笔。
    如果这个问题更适合 Information Security ,请评论,我将标记它以便迁移。

    1 回复  |  直到 6 年前
        1
  •  0
  •   Dev-iL    6 年前

    这是对问题1的回答。

    我在apache的基础上在kotlin中创建了一个部分工作的验证器 javax.xml.crypto.dsig.samples.Validate 例子。

    不可否认,下面的代码有一个bug,其中 Digest 值与XML中出现的值不匹配(验证最终失败)。但是,这里有一些教育价值,因为所有必需的验证步骤都会显示和解释。

    在Kotlin 1.2.50和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
            }
        }
    }