手动验证 X.509 证书,与 XML 中的文档捆绑在一起
Manual validation of a X.509 certificate, bundled with a document in an XML
我有一份政府签发的文件,格式如下(由于其中包含一些个人信息,所以很多内容都被删节了),其中包含一个 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>
发送给我此文件的人希望我下载并安装能够打开文件并验证签名的 "special program"。
由于这个"mysterious format"是一个简单的XML,我想将其中找到的信息转换成其他格式,无需他们的专用软件即可打开或验证。理想情况下,输出将是以下之一:
- 单独的文档和证书文件。
- 带有嵌入式证书的签名文档,例如可以在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>
但我不知道从这里开始做什么。
我的问题:
- 根据上面给出的 XML 的内容,使用知名工具手动验证文档需要采取哪些步骤?如果这很重要,我将不胜感激适用于 Windows 的步骤。欢迎口头和伪代码解决方案!
- 是否可以将此信息重新assemble 为有效的、已签名的 MS Word 文档?如果是 - 如何?
P.S。
如果这个问题更适合 Information Security,请发表评论,我会将其标记为迁移。
这是对问题 #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:
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
}
}
}
我有一份政府签发的文件,格式如下(由于其中包含一些个人信息,所以很多内容都被删节了),其中包含一个 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>
发送给我此文件的人希望我下载并安装能够打开文件并验证签名的 "special program"。
由于这个"mysterious format"是一个简单的XML,我想将其中找到的信息转换成其他格式,无需他们的专用软件即可打开或验证。理想情况下,输出将是以下之一:
- 单独的文档和证书文件。
- 带有嵌入式证书的签名文档,例如可以在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>
但我不知道从这里开始做什么。
我的问题:
- 根据上面给出的 XML 的内容,使用知名工具手动验证文档需要采取哪些步骤?如果这很重要,我将不胜感激适用于 Windows 的步骤。欢迎口头和伪代码解决方案!
- 是否可以将此信息重新assemble 为有效的、已签名的 MS Word 文档?如果是 - 如何?
P.S。
如果这个问题更适合 Information Security,请发表评论,我会将其标记为迁移。
这是对问题 #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:
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
}
}
}