Search code examples
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

    • xadesj4j is based on the XAdES 1.4.1 standard. On that standard, the SigningCertificate property requires that the signing certificate is identified within the property, but doesn't state which item corresponds to the signing certificate if there is more than one certificate reference in the property. xades4j tries to use the data on KeyInfo to figure it out.
    • xades4j starts by doing the core XML-DSIG/cryptographic validation (which requires the singing certificate) and only after checks each qualifying property. This means that the SigningCertificate property isn't considered when trying to identify the certificate. Maybe this could be different, specially if there's only one certificate reference in the property (the problem mentioned in the previous item wouldn't be applicable).

    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.