草庐IT

java - 从字节数组反序列化后 Xml 文档签名无效

coder 2023-08-30 原文

我在签署 xml 文档并在之后验证签名并且验证通过时遇到问题,但是当我将文档序列化为字节数组然后将其反序列化回文档时,签名验证失败。

以下是用于验证和序列化/反序列化的方法:

public class DocumentSigner {
    @Override
    public byte[] transformToByteArray(Document doc) throws Exception {
        TransformerFactory transformerFactory = TransformerFactory
                .newInstance();
        Transformer transformer = transformerFactory.newTransformer();      
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        transformer.transform(new DOMSource(doc), new StreamResult(os));
        return os.toByteArray();
    }

    private Document byteArrayToXmlDoc(byte[] documentoXml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setIgnoringElementContentWhitespace(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.parse(new ByteArrayInputStream(documentoXml), "UTF-8");
    }

    @Override
    public Boolean validate(byte[] byteArrayDoc, Integer certificatePropertiesId) throws Exception {
        Document doc = byteArrayToXmlDoc(byteArrayDoc);
        return validate(doc, certificatePropertiesId);
    }

    public Boolean validate(Document doc, Integer certificatePropertiesId) throws Exception {
        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS,
                "Signature");
        if (nl.getLength() == 0) {
            throw new Exception("No signature element.");
        }

        KeyStore ks = KeyStore.getInstance("JKS");
        CertificatePropertiesDTO certProp = databaseLogic.getCertificateProperties(certificatePropertiesId);
        if (certProp == null || certProp.getCertificatePassword().isEmpty() || certProp.getCertificate() == null){
            throw new RuntimeException("No certificate.");
        }

        ks.load(new ByteArrayInputStream(certProp.getCertificate()), certProp.getCertificatePassword().toCharArray());
        KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(), new KeyStore.PasswordProtection(certProp.getCertificatePassword().toCharArray()));
        X509Certificate[] certs =  (X509Certificate[]) keyEntry.getCertificateChain();
        if (certs == null || certs.length == 0) {
            throw new RuntimeException("No certificate found.");
        }

        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
        DOMValidateContext valContext = new DOMValidateContext(keyEntry.getCertificate().getPublicKey(), nl.item(0));
        NodeList els = doc.getElementsByTagNameNS("*", "SignatureProperties");
        Element el = (Element) els.item(0);
        valContext.setIdAttributeNS(el, null, "Id");
        valContext.setDefaultNamespacePrefix("dsig");

        valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
        try {
            XMLSignature signature2 = fac
                    .unmarshalXMLSignature(new DOMStructure(nl.item(0)));
            boolean coreValidity = signature2.validate(valContext);

            // Check core validation status.
            if (coreValidity == false) {
                log.info("Signature failed core validation");
                boolean sv = signature2.getSignatureValue()
                        .validate(valContext);
                log.info("signature validation status: " + sv);
                Iterator<?> i = signature2.getSignedInfo().getReferences()
                        .iterator();
                for (int j = 0; i.hasNext(); j++) {
                    Reference ref = (Reference) i.next();
                    boolean refValid = ref.validate(valContext);
                    log.info("ref[" + j + "] validity status: " + refValid);
                }
                return false;
            } else {
                log.info("Signature passed core validation");
                return true;
            }
        } catch (Exception ex) {
            log.info("EXCEPTION during validation: " + ex.getMessage());
            return false;
        }
    }

    public void signDocument(Document doc)
    {
        ....
    }



public void writeToDisk(String path, String rac)
    {
        BufferedWriter writer = null;
        try
        {
            writer = new BufferedWriter(new FileWriter(path));
            writer.write(rac);

        }
        catch ( IOException e)
        {
        }
        finally
        {
            try
            {
                if ( writer != null)
                writer.close( );
            }
            catch ( IOException e)
            {
                try {
                    throw e;
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
        }
    }

    @Override
    public String transformToString(Document doc,
            Boolean omitXmlDeclaration) throws Exception {
        TransformerFactory transformerFactory = TransformerFactory
                .newInstance();
        //transformerFactory.setAttribute("indent-number", 4);
        Transformer transformer = transformerFactory.newTransformer();      
        if (omitXmlDeclaration)
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
                    "yes");
//      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
//      transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
//              
        StringWriter sw = new StringWriter();
        transformer.transform(new DOMSource(doc), new StreamResult(sw));
        //String output = sw.getBuffer().toString().replaceAll("\n|\r", ""); 
        return sw.toString();
    }
}

这是它通过/失败的地方:

public void SignAndValidate()
{
    ...
    Document doc = createDocument();
    documentSigner.signDocument(doc);

    validate(doc, 1); 

    // OUTPUT:
    // Signature passed core validation

    byte[] docArr = documentSigner.transformToByteArray(doc);

    validate(docArr, 1);

    // OUTPUT:
    // Signature failed core validation
    // signature validation status: false
    // ref[0] validity status: false
    // ref[1] validity status: true
}

如有必要,我会发布创建/签署文档的方法,但它很大。

签名方法如下:

private void signDocument(Document document) throws Exception {
        //Remove ApacheXMLDSig because after every SOAP message signing it's set as default provdier, but doesn't know about here() function from XPATH2
        Security.removeProvider("ApacheXMLDSig");

        XMLSignatureFactory sigFactory =  XMLSignatureFactory.getInstance("DOM", "XMLDSig");
        String id = String.format("id%s", UUID.randomUUID().toString());

        KeyStore ks = KeyStore.getInstance("JKS");
        CertificatePropertiesDTO certProp = databaseLogic.getCertificateProperties(1);
        if (certProp == null || certProp.getCertificatePassword().isEmpty() || certProp.getCertificate() == null){
            throw new RuntimeException("No certificate.");
        }

        ks.load(new ByteArrayInputStream(certProp.getCertificate()), certProp.getCertificatePassword().toCharArray());
        KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(), new KeyStore.PasswordProtection(certProp.getCertificatePassword().toCharArray()));
        X509Certificate[] certs =  (X509Certificate[]) keyEntry.getCertificateChain();
        if (certs == null || certs.length == 0) {
            throw new RuntimeException("No certificate found.");
        }


        Element propSig = XMLElement(document, "PROP_Sig", "");
        Attr propNS = XMLAtribut(document, "xmlns", "http://ns.adobe.com/pdf/2006");
        propSig.setAttributeNodeNS(propNS);
        propSig.setAttribute("type", "cabinet");
        DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssZZ");
        Element m = XMLElement(document, "M", String.format("D:%s", df.format(new Date())));
        m.setAttribute("type", "text");
        Element name = XMLElement(document, "Name", cert.getSubjectX500Principal().getName());      
        name.setAttribute("type", "text");
        propSig.appendChild(m);
        propSig.appendChild(name);
        SignatureProperty sp = sigFactory.newSignatureProperty(Collections.singletonList(new DOMStructure(propSig)), "data_signature", null);
        SignatureProperties sps = sigFactory.newSignatureProperties(Collections.singletonList(sp), id);

        CanonicalizationMethod cm = sigFactory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, (XMLStructure) null);
        SignatureMethod sm = sigFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
        DigestMethod dm1 = sigFactory.newDigestMethod(DigestMethod.SHA1, null);
        Transform tf1 = sigFactory.newTransform(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, (TransformParameterSpec) null);     
        Reference ref1 = sigFactory.newReference("#" + id, dm1, Collections.singletonList(tf1), "http://www.w3.org/2000/09/xmldsig#SignatureProperties", null); 

        DigestMethod dm2 = sigFactory.newDigestMethod(DigestMethod.SHA1, null);
        String here = "here()/ancestor::dsig:Signature[1]/../../../../../..//. | "
                + "here()/ancestor::dsig:Signature[1]/../../../../../..//@* | "
                + "here()/ancestor::dsig:Signature[1]/../../../../../..//namespace::*";
        HashMap<String, String> hm = new HashMap<String, String>();
        hm.put("dsig", "http://www.w3.org/2000/09/xmldsig#");
        XPathType xp = new XPathType(here, XPathType.Filter.INTERSECT, hm);
        TransformParameterSpec paramsXPath2= new XPathFilter2ParameterSpec(Collections.singletonList(xp));
        Transform tf2 = sigFactory.newTransform(Transform.XPATH2, paramsXPath2);
        Transform tf3 = sigFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
        Transform tf4 = sigFactory.newTransform(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, (TransformParameterSpec) null); //"http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
        List<Transform> lt2 = new ArrayList<Transform>();
        lt2.add(tf2);
        lt2.add(tf3);
        lt2.add(tf4);
        Reference ref2 = sigFactory.newReference("", dm2, lt2, null, null);

        List<Reference> lr = new ArrayList<Reference>();
        lr.add(ref1);
        lr.add(ref2);
        SignedInfo si = sigFactory.newSignedInfo(cm, sm, lr);

        KeyInfoFactory kif = KeyInfoFactory.getInstance("DOM", "XMLDSig");

        ArrayList<Object> x509Content = new ArrayList<>();
        for (int i = 0; i < certs.length; i++) {
            x509Content.add(certs[i]);
        }

        X509Data xd = kif.newX509Data(x509Content);
        KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

        DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), document.getElementsByTagName("sac:SignatureInformation").item(0));       
        XMLSignature signature = 
                sigFactory.newXMLSignature(si, ki, Collections.singletonList( sigFactory.newXMLObject(Collections.singletonList(sps), null, null, null)), "data_signature", null);

        dsc.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
        dsc.setDefaultNamespacePrefix("dsig");
        try {
            signature.sign(dsc);
        }
        catch (Exception ex) {
            log.warn(ex.getMessage());
            throw new RuntimeException("Signing failed");
        }
    }

这是已签名的示例 XML 文档的一部分:

        <ext:UBLExtension>
            <ext:ExtensionContent>
                <sig:UBLDocumentSignatures>
                    <sac:SignatureInformation>
                        <dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" Id="data_signature">
                            <dsig:SignedInfo>
                                <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#WithComments"/>
                                <dsig:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
                                <dsig:Reference Type="http://www.w3.org/2000/09/xmldsig#SignatureProperties" URI="#idfe5688f4-583f-4a98-b26c-9d651b2f8918">
                                    <dsig:Transforms>
                                        <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#WithComments"/>
                                    </dsig:Transforms>
                                    <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                                    <dsig:DigestValue>iq802IBHl5kVdIMWA5Wlb5hYEoY=</dsig:DigestValue>
                                </dsig:Reference>
                                <dsig:Reference URI="">
                                    <dsig:Transforms>
                                        <dsig:Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">
                                            <dsig:XPath Filter="intersect" xmlns:dsig="http://www.w3.org/2002/06/xmldsig-filter2">here()/ancestor::dsig:Signature[1]/../../../../../..//. | here()/ancestor::dsig:Signature[1]/../../../../../..//@* | here()/ancestor::dsig:Signature[1]/../../../../../..//namespace::*</dsig:XPath>
                                        </dsig:Transform>
                                        <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                                        <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#WithComments"/>
                                    </dsig:Transforms>
                                    <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                                    <dsig:DigestValue>2jmj7l5rSw0yVb/vlWAYkK/YBwk=</dsig:DigestValue>
                                </dsig:Reference>
                            </dsig:SignedInfo>
                            <dsig:SignatureValue>d+DRc25SXnhxwXJs10A9Hnf1g0gG2bZqqnpTbZvrzp8X3EvtOVr3dBP6Ldc1RMTJYSF+guanlWKn
liaKlu7VbdB+SiQRuAMAZt+9Cnbn0CMlIzt22nRJNzjbeLBpCm7K63jCHGOXsWCW43DI/DYeZwq+
Q2j7WESgOtWLqUO0Jn8=</dsig:SignatureValue>
                            <dsig:KeyInfo>
                                <dsig:X509Data>
                                    <dsig:X509Certificate>...</dsig:X509Certificate>
                                    <dsig:X509Certificate>...</dsig:X509Certificate>
                                </dsig:X509Data>
                            </dsig:KeyInfo>
                            <dsig:Object>
                                <dsig:SignatureProperties Id="idfe5688f4-583f-4a98-b26c-9d651b2f8918">
                                    <dsig:SignatureProperty Target="data_signature">
                                        <PROP_Sig xmlns="http://ns.adobe.com/pdf/2006" type="cabinet">
                                            <M type="text">D:20151130163741+0100</M>
                                            <Name type="text">CN=<CN>,L=<City>,O=<Organization>,C=<Country></Name>
                                        </PROP_Sig>
                                    </dsig:SignatureProperty>
                                </dsig:SignatureProperties>
                            </dsig:Object>
                        </dsig:Signature>
                    </sac:SignatureInformation>
                </sig:UBLDocumentSignatures>
            </ext:ExtensionContent>
        </ext:UBLExtension>
    </ext:UBLExtensions>

我不明白为什么验证说 reference[0] 失败(引用具有 id 的元素),但对整个文档的引用通过?

最佳答案

@formatc 我没有发表评论的权限,但是您可以尝试查看两个文件(签名和反序列化)中的十六进制值吗?我遇到了同样的问题,出于某种原因,在我构建回 xml 时,一些非可视字符被插入到文档前面。除非你使用 HexView 或类似的工具,否则你不会看到它们。

我能够以编程方式删除这些字符,一切顺利。

关于java - 从字节数组反序列化后 Xml 文档签名无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33995151/

有关java - 从字节数组反序列化后 Xml 文档签名无效的更多相关文章

  1. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  2. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  3. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  4. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  5. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  6. ruby - 检查数组是否在增加 - 2

    这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife

  7. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  8. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  9. ruby - 在 Ruby 中用键盘诅咒数组浏览 - 2

    我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作

  10. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

随机推荐