格式化带有 SignatureValue 的 XML 后签名验证失败

问题描述 投票:0回答:2

来自 apache santuario xmlsignature 测试, 我做了一个测试:向 XML 文件添加签名,然后将其保存为文件,然后从该文件中读取它,并验证签名。 请参考代码中的注释。

纯 xml 文件是:

<?xml version="1.0" encoding="UTF-8"?>
<PurchaseOrder xmlns="urn:example:po">
  <Items>
    <Item Code="001-001-001" Quantity="1">
      spade
    </Item>
    <Item Code="001-001-002" Quantity="1">
      shovel
    </Item>
  </Items>
  <ShippingAddress>
    Dig PLC, 1 First Ave, Dublin 1, Ireland
  </ShippingAddress>
  <PaymentInfo>
    <BillingAddress>
      Dig PLC, 1 First Ave, Dublin 1, Ireland
    </BillingAddress>
    <CreditCard Type="Amex">
      <Name>Foo B Baz</Name>
      <Number>1234 567890 12345</Number>
      <Expires Month="1" Year="2005" />
    </CreditCard>
  </PaymentInfo>
</PurchaseOrder>

我有一些问题:

  1. 使用 XML 中的证书验证签名。如果有效,XML 文件是自我解释的,为什么我们还需要从密钥库加载证书?
  2. 我无法格式化生成的包含签名的 XML。如果我更改XML文件的某些部分,就会导致验证失败。我应该如何格式化 XML 文件?还是就放在那里?
      <ds:SignedInfo><!--If I add a lot of blank spaces at the start of this line, the verifying is ok-->
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><!--If I add a blank space at the start of this line, the verifying is not working-->

主要Java代码:

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.xml.security.signature.XMLSignature;
import org.junit.Assert;
import org.w3c.dom.Document;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Element;

/**
 * This tests using the DOM API of Apache Santuario - XML Security for Java for XML Signature.
 */
public class SignatureDOMTest extends org.junit.Assert {

    // Sign + Verify an XML Document using the DOM API
    @org.junit.Test
    public void testSignatureUsingDOMAPI() throws Exception {

        // Read in plaintext document
        InputStream sourceDocument =
                new FileInputStream(new File("plaintext.xml"));
        Document document = XMLUtils.read(sourceDocument, true);

        // Set up the Key
        KeyStore keyStore = KeyStore.getInstance("jks");
        keyStore.load(
            this.getClass().getClassLoader().getResource("clientstore.jks").openStream(),
            "cspass".toCharArray()
        );
        Key key = keyStore.getKey("myclientkey", "ckpass".toCharArray());
        X509Certificate cert = (X509Certificate)keyStore.getCertificate("myclientkey");

        // Sign using DOM
        List<QName> namesToSign = new ArrayList<QName>();
        namesToSign.add(new QName("urn:example:po", "PaymentInfo"));

        SignatureUtils.signUsingDOM(
            document, namesToSign, "http://www.w3.org/2000/09/xmldsig#rsa-sha1", key, cert
        );
        // Verify using DOM, here it use the cert load from Keystore.
        SignatureUtils.verifyUsingDOM(document, namesToSign, cert);

        // Those code are my test:
        // I write the signed xml to a file, then read from the file.
        XMLUtils.outputDOM(document, new FileOutputStream(new File("plaintext_signed.xml")));
        
        // read from the signed file.
        Document signedDocument = XMLUtils.read(new FileInputStream(new File("plaintext_signed.xml")), true);

        // Verify using DOM
        List<QName> namesToSign1 = new ArrayList<QName>();
        namesToSign1.add(new QName("urn:example:po", "PaymentInfo"));

        Element sigElement = SignatureUtils.getSignatureElement(signedDocument);
        Assert.assertNotNull(sigElement);

        SignatureUtils.findElementsToVerify(signedDocument, namesToSign1);
        
        // try to get cert from the node in the xml.
        XMLSignature signature = new XMLSignature(sigElement, "");

        X509Certificate cert_in_xml = signature.getKeyInfo().getX509Certificate();
        
        // verify the signature by using the cert in the xml.
        // If it works, why do we need a cert loaded from the keystore?
        SignatureUtils.verifyUsingDOM(signedDocument, namesToSign1, cert_in_xml);
    }
}

为了完整性,将函数包含在

SignatureUtils.java
中:

    
import org.apache.xml.security.Init;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.junit.Assert;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.namespace.QName;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;

/**
 * Some utility methods for signing/verifying documents
 */
public final class SignatureUtils {

    static {
        Init.init();
    }

    private SignatureUtils() {
        // complete
    }

    /**
     * Sign the document using the DOM API of Apache Santuario - XML Security for Java.
     * It signs a list of QNames that it finds in the Document via XPath.
     */
    public static void signUsingDOM(
        Document document,
        List<QName> namesToSign,
        String algorithm,
        Key signingKey,
        X509Certificate signingCert
    ) throws Exception {
        XMLSignature sig =
            new XMLSignature(document, "", algorithm, "http://www.w3.org/2001/10/xml-exc-c14n#");
        Element root = document.getDocumentElement();
        root.appendChild(sig.getElement());

        XPathFactory xpf = XPathFactory.newInstance();
        XPath xpath = xpf.newXPath();
        xpath.setNamespaceContext(new DSNamespaceContext());

        for (QName nameToSign : namesToSign) {
            String expression = "//*[local-name()='" + nameToSign.getLocalPart() + "']";
            NodeList elementsToSign =
                    (NodeList) xpath.evaluate(expression, document, XPathConstants.NODESET);
            for (int i = 0; i < elementsToSign.getLength(); i++) {
                Element elementToSign = (Element)elementsToSign.item(i);
                Assert.assertNotNull(elementToSign);
                String id = UUID.randomUUID().toString();
                elementToSign.setAttributeNS(null, "Id", id);
                elementToSign.setIdAttributeNS(null, "Id", true);

                Transforms transforms = new Transforms(document);
                transforms.addTransform("http://www.w3.org/2001/10/xml-exc-c14n#");
                sig.addDocument("#" + id, transforms, "http://www.w3.org/2000/09/xmldsig#sha1");
            }
        }

        sig.sign(signingKey);

        // Find the Signature Element
        Element sigElement = getSignatureElement(document);
        Assert.assertNotNull(sigElement);

        if (signingCert != null) {
            sig.addKeyInfo(signingCert);
        }
    }

    /**
     * Verify the document using the DOM API of Apache Santuario - XML Security for Java.
     * It finds a list of QNames via XPath and uses the DOM API to mark them as having an
     * "Id".
     */
    public static void verifyUsingDOM(
        Document document,
        List<QName> namesToSign,
        X509Certificate cert
    ) throws Exception {
        // Find the Signature Element
        Element sigElement = getSignatureElement(document);
        Assert.assertNotNull(sigElement);

        findElementsToVerify(document, namesToSign);

        XMLSignature signature = new XMLSignature(sigElement, "");

        // Check we have a KeyInfo
        KeyInfo ki = signature.getKeyInfo();
        Assert.assertNotNull(ki);

        // Check the Signature value
        Assert.assertTrue(signature.checkSignatureValue(cert));
    }




    public static void findElementsToVerify(Document document, List<QName> namesToSign) throws XPathExpressionException {
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xpath = xpf.newXPath();
        xpath.setNamespaceContext(new DSNamespaceContext());

        for (QName nameToSign : namesToSign) {
            String expression = "//*[local-name()='" + nameToSign.getLocalPart() + "']";
            Element signedElement =
                (Element) xpath.evaluate(expression, document, XPathConstants.NODE);
            Assert.assertNotNull(signedElement);
            if (signedElement.hasAttributeNS(null, "Id")) {
                signedElement.setIdAttributeNS(null, "Id", true);
            }
        }
    }

    public static Element getSignatureElement(Document document) throws XPathExpressionException {
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xpath = xpf.newXPath();
        xpath.setNamespaceContext(new DSNamespaceContext());

        // Find the Signature Element
        String expression = "//dsig:Signature[1]";
        Element sigElement =
                (Element) xpath.evaluate(expression, document, XPathConstants.NODE);
        return sigElement;
    }
}

生成的带有签名的xml文件,格式不好,我想格式化它,但结果导致验证失败。

<PurchaseOrder xmlns="urn:example:po">
  <Items>
    <Item Code="001-001-001" Quantity="1">
      spade
    </Item>
    <Item Code="001-001-002" Quantity="1">
      shovel
    </Item>
  </Items>
  <ShippingAddress>
    Dig PLC, 1 First Ave, Dublin 1, Ireland
  </ShippingAddress>
  <PaymentInfo Id="27a5af39-7896-4002-ad65-a4ec9015bf23">
    <BillingAddress>
      Dig PLC, 1 First Ave, Dublin 1, Ireland
    </BillingAddress>
    <CreditCard Type="Amex">
      <Name>Foo B Baz</Name>
      <Number>1234 567890 12345</Number>
      <Expires Month="1" Year="2005"></Expires>
    </CreditCard>
  </PaymentInfo>
 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo><!--If I add a lot of blank spaces at the start of this line, the verification is ok-->
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><!--If I add a blank space at the start of this line, the verification is not working-->
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
<ds:Reference URI="#27a5af39-7896-4002-ad65-a4ec9015bf23">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
<ds:DigestValue>ANOpAn94IZWzHAFvQSXI6rK3Hsg=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
Tlbljw+A69WQ4oClxMgv64RLunmxX0k92UJdqGkQUo7L35wpVgET1EjtZf7TF6ekaxuICDEISPmq&#xD;
R7vwrRsmf3/u1v9bANaa0rOtO7nuJgxUs9kVndnMnAPWcIm9Njtl7GUEpt6BLMKwAVkbDjOq5Q0S&#xD;
R3P9d31ppuYdOt7nnbGvgtYDDVIsQRA50rlZqotWI3odfh27HlJMtIys9YJ/0BuQKI4LaNhdymKQ&#xD;
haRFe6jaAv3v0MVG9Hg53uaJf8vwKL0tYG73ZO2Wh2Tr0j0EPEJY72akyJdNcAHjHiSzZEw1HwRt&#xD;
Y9Ns2cnbMLpVEYQzW8oO/TO8uf9UYXfUttFyLQ==
</ds:SignatureValue>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>
MIIDqjCCAxOgAwIBAgIBHzANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJERTEPMA0GA1UECBMG&#xD;
QmF5ZXJuMQ8wDQYDVQQHEwZNdW5pY2gxDTALBgNVBAoTBEhvbWUxFTATBgNVBAsTDEFwYWNoZSBX&#xD;
U1M0SjEPMA0GA1UEAxMGV2VybmVyMB4XDTE1MDkxMDEzMjEyMloXDTI1MDkwNzEzMjEyMlowUzEL&#xD;
MAkGA1UEBhMCSUUxETAPBgNVBAgTCExlaW5zdGVyMQ8wDQYDVQQHEwZEdWJsaW4xDzANBgNVBAoT&#xD;
BkFwYWNoZTEPMA0GA1UEAxMGQ2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA&#xD;
jeP2hF/KOv8Pw/++n9bAa0DxKFzyLvlcAciBgv1E01cCyUwn5X0v8nT9gIdjXBi6Y6PvSCs2GqcA&#xD;
zo82yzIjlcDmocXt/2vBkezwh6Ow2xoXItzXD+I6KlhpH8gl4xXd35N0z+88m4SEclncm//l3sQH&#xD;
P6CMG9O4H91A+0DXzTkQHzs2PEg13ONMOVbg5P85ceZ+VTBjd3BkqC0mZDB6Ovo6Xt06tVS/S4rF&#xD;
R0L9mtyxZ+v/Psd8TYAIk3wlz1e1wm+dobp1YkOPgvv08r8ROVnWC0pRZquCPQUou6r50LseRZWE&#xD;
o9MGN8kt4sYcLCaJbF3fsTvKYi6InWyczv2JSQIDAQABo4H2MIHzMAkGA1UdEwQCMAAwLAYJYIZI&#xD;
AYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQsRXQjSEq1&#xD;
PetSTIekkXnMBtntGTCBmAYDVR0jBIGQMIGNgBRWF+/2a4tZ/iMZaN54wOFNZ33QZqFqpGgwZjEL&#xD;
MAkGA1UEBhMCREUxDzANBgNVBAgTBkJheWVybjEPMA0GA1UEBxMGTXVuaWNoMQ0wCwYDVQQKEwRI&#xD;
b21lMRUwEwYDVQQLEwxBcGFjaGUgV1NTNEoxDzANBgNVBAMTBldlcm5lcoIJAI3hLAppEXfSMA0G&#xD;
CSqGSIb3DQEBCwUAA4GBAKZXwqAplMzutyC8jIUzFN/DfQOAuZ8ExLBIUxW51KSzxxL+kfydq4HF&#xD;
otRC7PZ3bYEkXYN7deUYoq6yFO0AAWvagBbxdzRYDPRCwpKe+4jn8VLGtjkEyLSsMiiaNiOuMx8q&#xD;
WxvV1sA9KioHvZDFVP+0QqSc1ZytEPOKasdViUU9
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature></PurchaseOrder>

更新1:

  1. 原始 xml 格式良好。目前,只有
    ds:Signature
    节点格式不正确。
  2. 当使用apache库处理标志时,我们如何知道XML文件已经格式化了?因为 XML 文件现在表示为 Java 对象。
  3. 通常,
    >
    <
    之间的内容,像这块
    <Item>this is content</Item>
    this is content
    就是payload,签名过程是否只对payload进行签名?或者它会包含
    <Item>
    <\Item>
    ,甚至还会在
    <Item>
    之前包含一些空格,例如
      <Item>
    \n
    末尾的一些
    <\Item>
java xml xml-signature
2个回答
0
投票

有相当多的问题。

  1. 密钥库中的证书验证 XML 是否由预期密钥签名。您不能隐式信任 XML 文件中的签名。这就像我为你提供一个带照片的身份证件,它由一面镜子组成,上面写着“我”。它可能是正确的,但并不真正可靠。

  2. 格式化 XML 必须在签名之前完成。 XML 的规范化旨在确保所签名的 XML 是等效的。它使用规则处理 XML,以确保等效的 XML 具有相同的签名,但签名后的一些格式化和整理会破坏它。虽然我确实喜欢格式良好的 XML,但它应该由计算机处理,所以不用太担心...

更新1:

  1. 原始 xml 格式良好。目前,只有 ds:Signature 节点的格式不正确。

一般来说,没什么可做的。对 XML 进行签名需要库创建一个 xml 片段,其中包含 XML 规范版本的摘要。然后对该片段进行签名,结果是另一个 XML 片段。一般来说,它们是独立的,只要您不更改等效规范 xml 的组成部分,但有足够的规则使库在组合时很难使其变得漂亮。您需要以漂亮的格式组合原始 XML 和签名,然后使用更改的部分更新 Signature/SignedInfo。例如,将 SignedInfo 的新签名值插入到您知道格式良好的完整文档中。

  1. 当使用apache库处理标志时,我们如何知道XML文件已经格式化了?因为 XML 文件现在表示为 作为 Java 对象。

大多数(许多?一些?)XML 库都有一个可以将 XML 格式化为“漂亮”(或格式良好)的函数。然而,签名 XML 的性质意味着这样做可能会影响签名。

  1. 一般是>和<, like in this piece of this is content, this is content is the payload, Does the signing process only sign the payload? or it will contain the , <\Item>之间的内容,甚至在前面还会有一些空格 喜欢和一些 最后<\Item>?

规则因规范化方法的不同而有所不同。例如,请参阅 https://www.w3.org/TR/xml-c14n/#Terminology。但我希望这样

All whitespace in character content is retained (excluding characters removed during line feed normalization)

0
投票

如果您想删除回车符( )在

<ds:SignatureValue>
<ds:X509Certificate>
内每行的末尾,您可以尝试将这些选项添加(或者)到 JVM(或 Servlet 容器):

  • -Dcom.sun.org.apache.xml.internal.security.ignoreLineBreaks=true

  • -Dorg.apache.xml.security.ignoreLineBreaks=true
© www.soinside.com 2019 - 2024. All rights reserved.