xmlxades4j

xades4j verification without KeyInfo


Our company uses Etokens to communicate with server over https. How do I verify an enveloped XML file which comes without signing certificate in the KeyInfo?

<?xml version="1.0" encoding="UTF-8"?>
<EDoc><NextMsg ID="Edoc">2019-09-23T16:20:53</NextMsg><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="xmldsig-5d0f41cd-6e98-488d-9415-28b6329b34d1">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod>
<ds:Reference Id="xmldsig-5d0f41cd-6e98-488d-9415-28b6329b34d1-ref0" URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
<ds:DigestValue>IUdYs162QE1GdUEKUxqppoFmNvrYMLMBGnduWy6v3rc=</ds:DigestValue>
</ds:Reference>
<ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xmldsig-5d0f41cd-6e98-488d-9415-28b6329b34d1-signedprops">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
<ds:DigestValue>Uw2b3fkLSJPm+yDeYwXQhJHZhWP+vUNBEeS55LcII00=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue Id="xmldsig-5d0f41cd-6e98-488d-9415-28b6329b34d1-sigvalue">
eHOQcdUYRRhQa3DV+P5lWnXR32KXpO08n4QI/SIXvJxbjvz3roGNas53E/1hCui8MG3TkZulx4Fw&#xD;
W3N9qJ3FXciasReaqofrexHtbntyr6O/tzQh2akcJzo3TPH+j4PxozjFUxCxcaJRSqCE0hWdBtuI&#xD;
S8rn+EKpes7ohgtlsVg=
</ds:SignatureValue>
<ds:Object><xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#xmldsig-5d0f41cd-6e98-488d-9415-28b6329b34d1"><xades:SignedProperties xmlns:ns3="http://uri.etsi.org/01903/v1.4.1#" Id="xmldsig-5d0f41cd-6e98-488d-9415-28b6329b34d1-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>2019-09-23T16:20:53+03:00</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod><ds:DigestValue>woG3fsImDUeqxznickzLkpeY9R4=</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>cn=LB-LITAS-CA,ou=MSD,o=Lietuvos bankas,l=Vilnius,c=LT</ds:X509IssuerName><ds:X509SerialNumber>105704079740755226136574</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object>
</ds:Signature></EDoc>

Default verify function searches for KeyInfo

 public static void verifyBes(KeyStore ksaa, String path)
      throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException,
          java.security.NoSuchAlgorithmException, xades4j.utils.XadesProfileResolutionException,
          xades4j.XAdES4jException, java.io.IOException, java.security.NoSuchProviderException,
          java.security.cert.CertificateException, java.security.cert.CRLException,
          java.security.cert.CertStoreException, java.security.KeyStoreException {

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);

    Document doc = dbf.newDocumentBuilder().parse(new File(path));
    Element root = doc.getDocumentElement();

    Element idChild = (Element) root.getFirstChild();
    DOMHelper.useIdAsXmlId(idChild);

    String filename =
        System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
    FileInputStream is = new FileInputStream(filename);
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    String password = "changeit";
    ks.load(is, password.toCharArray());

    X509Certificate validate_cert = null;

    FileSystemDirectoryCertStore fsCertStore = new FileSystemDirectoryCertStore("../certStore");
    Collection<? extends Certificate> allCerts = fsCertStore.getStore().getCertificates(null);
    // search for specific certificate to test agains
    for (Certificate c : allCerts) {
      X509Certificate pool_cert = (X509Certificate) c;
      System.out.println(pool_cert.getSubjectDN().getName());
      if (-1 != pool_cert.getSubjectDN().getName().indexOf("TEST CERTIFICATE"))
        validate_cert = pool_cert;
    }

    CertificateValidationProvider validationProviderMySigs =
        new PKIXCertificateValidationProvider(ks, false, fsCertStore.getStore());
    XadesVerificationProfile instance = new XadesVerificationProfile(validationProviderMySigs);
    XadesVerifier verifier = instance.newVerifier();
    Element sig = (Element) doc.getElementsByTagName("ds:Signature").item(0);
    XAdESVerificationResult r = verifier.verify(sig, null);
  }

xades4j.verification.InvalidKeyInfoDataException: No X509Data to identify the leaf certificate at xades4j.verification.SignatureUtils.processKeyInfo(SignatureUtils.java:79) at xades4j.verification.XadesVerifierImpl.verify(XadesVerifierImpl.java:184) at com.mycompany.app.App.verifyBes(App.java:993) at com.mycompany.app.App.main(App.java:460)


Solution

  • TL,DR;

    Currently, there's no way to do that.

    Reasons

    Ideas

    1. The goal of identifying the signing certificate is to supply a X509CertSelector to CertificateValidationProvider.validate(). Without other changes, if an "empty" cert selector was supplied when KeyInfo is not present, it would mean that the CertificateValidationProvider would need to know how to identify the signing certificate. The builtin PKIXCertificateValidationProvider would fail, which is not cool. However, since this scenario currently fails, it means that the change wouldn't breaking existing apps.
    2. When there is no KeyInfo, try to lookup the SigningCertificate property and if it contains a single certificate reference, use the issuer/serial in there to configure the X509CertSelector. Given the current architecture, this has the downside of accessing property data prior to the core signature verification.

    I'm not a fan of any of the options. I've added a reference to this question in an existing issue also about the creation of theX509CertSelector. I'll think a bit more about this. Feel free to throw in more ideas or experiment with implementation.