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

时间:2018-06-14 13:05:41

标签: xml language-agnostic x509 digital-certificate xml-dsig

我有一份政府发布的文件,其格式如下(很多是因为它包含一些个人信息而被编辑),其中包含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。
  3. 从我到目前为止收集的内容来看,这些是感兴趣的领域:

    <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文档中?如果是这样 - 怎么样?
    3. P.S。
      如果这个问题更适合Information Security,请发表评论,我会将其标记为迁移。

1 个答案:

答案 0 :(得分:0)

这是问题#1的答案。

我已经设法在Apache的javax.xml.crypto.dsig.samples.Validate示例的基础上在Kotlin中创建了一个部分工作的验证器。

不可否认,下面的代码有一个错误,其中计算的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
        }
    }
}