I'm using XAdES4j 2.2.0 for signing XML documents. Previously I've used version 1.5.1, and adapted that code to use the new version.

While XADES-BES, XADES-T and XADES-C work flawlessly, there is a problem with XADES-XL (applied after XADES-C), i.e. an exception is caught: HttpTsaConfiguration must be configured in the profile in order to use an HTTP-based time-stamp token provider

My code looks like:

// Certificate for signing is stored in PKCS12 keystore in file.
KeyingDataProvider keyingDataProvider = FileSystemKeyStoreKeyingDataProvider
        .builder("PKCS12", sKeyStorePath, new SigningCertificateSelector.single())
        .storePassword(new DirectPasswordProvider(sKeyStorePassword))
        .entryPassword(new DirectPasswordProvider(sKeyStorePassword))
        .fullChain(true)
        .build();

// KeyStore and CertStore for validating signatures and timestamps.
CertificateValidationProvider validationProvider = PKIXCertificateValidationProvider
        .builder(keyStore)
        .checkRevocation(true)
        .intermediateCertStores(certStore)
        .build();

// Read document from file on disk.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(new File(sFilePath));

// Sign document with XAdES-C.
signDocumentC(keyingDataProvider, doc, validationProvider);
// Sign document with XAdES-XL afterwards.
signDocumentXL(keyingDataProvider, doc, validationProvider);

...

/** Method for signing with XAdES-C. */
private void signDocumentC(KeyingDataProvider keyingDataProvider, Document doc, CertificateValidationProvider validationProvider) throws Exception
{
    Element elemToSign = doc.getDocumentElement();
    DOMHelper.useIdAsXmlId(elemToSign);

    ValidationDataProvider vdp = new ValidationDataFromCertValidationProvider(validationProvider);
    XadesSigner signer = new XadesCSigningProfile(keyingDataProvider, vdp)
        .with(new HttpTimeStampTokenProvider(new DefaultMessageDigestProvider(), new HttpTsaConfiguration("http://freetsa.org/tsr")))
        .newSigner();

    // Skip check for countersigning, just sign.
    new Enveloped(signer).sign(elemToSign);
}

/** Method for signing with XAdES-XL. */
private void signDocumentXL(KeyingDataProvider keyingProvider, Document doc, CertificateValidationProvider validationProvider) throws Exception
{
    Element elemToSign = doc.getDocumentElement();
    DOMHelper.useIdAsXmlId(elemToSign);

    NodeList signatures = doc.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATURE);
    Element signatureNode = (Element)signatures.item(signatures.getLength() - 1);

    XadesVerificationProfile nistVerificationProfile = new XadesVerificationProfile(validationProvider);

    XadesSignatureFormatExtender formExt = new XadesFormatExtenderProfile().getFormatExtender();
    XAdESVerificationResult res = nistVerificationProfile.newVerifier().verify(signatureNode, null, formExt, XAdESForm.X_L);
}

However, there is always an exception thrown at the last line (nistVerificationProfile.newVerifier().verify(...)), that looks like:

[2023-01-23 18:22:29] INFO: [] ESignatureXML.signDocumentXL: Signing xml document XAdES-XL...
xades4j.production.PropertyDataGeneratorNotAvailableException: Property data generation failed for SigAndRefsTimeStamp: data object generator cannot be created
    at xades4j.production.PropertyDataGeneratorsMapperImpl.getGenerator(PropertyDataGeneratorsMapperImpl.java:51)
    at xades4j.production.PropertiesDataObjectsGeneratorImpl.doGenPropsData(PropertiesDataObjectsGeneratorImpl.java:87)
    at xades4j.production.PropertiesDataObjectsGeneratorImpl.genPropsData(PropertiesDataObjectsGeneratorImpl.java:73)
    at xades4j.production.PropertiesDataObjectsGeneratorImpl.generateUnsignedPropertiesData(PropertiesDataObjectsGeneratorImpl.java:64)
    at xades4j.production.XadesSignatureFormatExtenderImpl.enrichSignature(XadesSignatureFormatExtenderImpl.java:79)
    at xades4j.verification.XadesVerifierImpl.verify(XadesVerifierImpl.java:497)
    at com.my.ESignatureXML.signDocumentXL(ESignatureXML.java:709)
    at com.my.ESignatureXML.signDocument(ESignatureXML.java:482)
    at com.my.ESignatureXML.performSigning(ESignatureXML.java:236)
    at com.my.ESignatureXML.sign(ESignatureXML.java:187)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
    at java.base/java.lang.Thread.run(Thread.java:835)
Caused by: com.google.inject.ProvisionException: Unable to provision, see the following errors:

1) [Guice/ErrorInCustomProvider]: IllegalStateException: HttpTsaConfiguration must be configured in the profile in order to use an HTTP-based time-stamp token provider.
  at DefaultProductionBindingsModule.configure(DefaultProductionBindingsModule.java:80)
      \_ installed by: Modules$OverrideModule -> DefaultProductionBindingsModule
  at HttpTimeStampTokenProvider.<init>(HttpTimeStampTokenProvider.java:44)
      \_ for 2nd parameter
  while locating HttpTimeStampTokenProvider
  at DataGenSigAndRefsTimeStamp.<init>(DataGenSigAndRefsTimeStamp.java:51)
      \_ for 2nd parameter
  while locating DataGenSigAndRefsTimeStamp
  while locating PropertyDataObjectGenerator<SigAndRefsTimeStampProperty>

Learn more:
  https://github.com/google/guice/wiki/ERROR_IN_CUSTOM_PROVIDER

1 error

======================
Full classname legend:
======================
DataGenSigAndRefsTimeStamp:      "xades4j.production.DataGenSigAndRefsTimeStamp"
DefaultProductionBindingsModule: "xades4j.production.DefaultProductionBindingsModule"
HttpTimeStampTokenProvider:      "xades4j.providers.impl.HttpTimeStampTokenProvider"
Modules$OverrideModule:          "com.google.inject.util.Modules$OverrideModule"
PropertyDataObjectGenerator:     "xades4j.production.PropertyDataObjectGenerator"
SigAndRefsTimeStampProperty:     "xades4j.properties.SigAndRefsTimeStampProperty"
========================
End of classname legend:
========================

    at com.google.inject.internal.InternalProvisionException.toProvisionException(InternalProvisionException.java:251)
    at com.google.inject.internal.InjectorImpl$1.get(InjectorImpl.java:1104)
    at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1134)
    at xades4j.production.PropertyDataGeneratorsMapperImpl.getGenerator(PropertyDataGeneratorsMapperImpl.java:48)
    ... 58 more
Caused by: java.lang.IllegalStateException: HttpTsaConfiguration must be configured in the profile in order to use an HTTP-based time-stamp token provider.
    at xades4j.production.DefaultProductionBindingsModule.lambda$configure$0(DefaultProductionBindingsModule.java:81)
    at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:86)
    at com.google.inject.internal.InternalFactoryToInitializableAdapter.provision(InternalFactoryToInitializableAdapter.java:57)
    at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:60)
    at com.google.inject.internal.InternalFactoryToInitializableAdapter.get(InternalFactoryToInitializableAdapter.java:47)
    at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:40)
    at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:60)
    at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:113)
    at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:91)
    at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:300)
    at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:60)
    at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:40)
    at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:60)
    at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:113)
    at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:91)
    at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:300)
    at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:60)
    at com.google.inject.internal.InjectorImpl$1.get(InjectorImpl.java:1101)
    ... 60 more

I have detected that in class PropertiesDataObjectsGeneratorImpl, in method doGenPropsData, while traversing the properties of the signature, one property, xades4j.properties.SigAndRefsTimeStampProperty, has value time = null, and the exception is actually thrown while trying to add this property to a collection of signature properties to be used in further validation.

This happens only when signing XAdES-XL, while there is no exception in this method with other forms (i.e. XAdES-T and XAdES-C, where also timestamp details could be seen in the resulting XML file).

How to overcome this issue? Am I missing something in declaring signer, validation provider, verification profile...?

2

There are 2 answers

0
predrags On BEST ANSWER

After some more hours/days of exploring, I think I've found the reason for this behavior.

Sample code for signing XAdES-XL after XAdES-C (i.e. for enriching the signature) exists in class xades4j.verification.XadesVerifierImplTest, in the following method:

@Test
public void testVerifyCEnrichXL() throws Exception
{
    System.out.println("verifyCEnrichXL");

    Document doc = getDocument("document.signed.c.xml");
    Element signatureNode = getSigElement(doc);

    XadesSignatureFormatExtender formExt = new XadesFormatExtenderProfile().with(DEFAULT_TEST_TSA).getFormatExtender();
    XAdESVerificationResult res = nistVerificationProfile.newVerifier().verify(signatureNode, null, formExt, XAdESForm.X_L);

    assertEquals(XAdESForm.C, res.getSignatureForm());
    assertPropElementPresent(signatureNode, SigAndRefsTimeStampProperty.PROP_NAME);
    assertPropElementPresent(signatureNode, CertificateValuesProperty.PROP_NAME);
    assertPropElementPresent(signatureNode, RevocationValuesProperty.PROP_NAME);

    outputDocument(doc, "document.verified.c.xl.xml");
}

The same case would be for signing XAdES-A after XAdES-XL, as shown in class xades4j.production.XadesSignatureFormatExtenderImplTest, in the following method:

@Test
public void testEnrichSignatureWithA() throws Exception
{
    System.out.println("enrichSignatureWithA");

    Document doc = getDocument("document.verified.c.xl.xml");
    Element signatureNode = (Element)doc.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature").item(0);

    XadesSignatureFormatExtender instance = new XadesFormatExtenderProfile().with(DEFAULT_TEST_TSA).getFormatExtender();

    XMLSignature sig = new XMLSignature(signatureNode, "");
    Collection<UnsignedSignatureProperty> usp = new ArrayList<UnsignedSignatureProperty>(1);
    usp.add(new ArchiveTimeStampProperty());

    instance.enrichSignature(sig, new UnsignedProperties(usp));

    outputDocument(doc, "document.verified.c.xl.a.xml");
}

Comparing this code to my code, it looks like the problem is that there should be a TimeStampTokenProvider supplied for the XadesFormatExtenderProfile, like in:

XadesSignatureFormatExtender formExt = new XadesFormatExtenderProfile()
        .with(new HttpTimeStampTokenProvider(new DefaultMessageDigestProvider(), new HttpTsaConfiguration("http://freetsa.org/tsr")))
        .getFormatExtender();

or:

XadesSignatureFormatExtender formExt = new XadesFormatExtenderProfile()
        .withTimeStampTokenProvider(new HttpTimeStampTokenProvider(new DefaultMessageDigestProvider(), new HttpTsaConfiguration("http://freetsa.org/tsr")))
        .getFormatExtender();

In theory, this is needed because every enrichment of XAdES format requires an additional property related to timestamp:

  • XAdES-C form has property xades:SignatureTimeStamp
  • XAdES-XL form has property xades:SigAndRefsTimeStamp
  • XAdES-A form has property xades141:ArchiveTimeStampV2

I thought it would be sufficient to have timestamp applied only once (in XAdES-C), and then the other forms (XAdES-XL and XAdES-A) would afterwards just copy that info into their respective properties; however it proved that this is not the case.

2
lgoncalves On

As you mentioned in your response, extending a signature to one of those formats requires adding new timestamp properties. As such, xades4j needs to know how to obtain those timestamps.

By default, xades4j includes a TimeStampTokenProvider that knows how to get timestamps via HTTP. You only need to configure HttpTsaConfiguration in the profiles (both for production and extending). This is documented here: https://github.com/luisgoncalves/xades4j/wiki/Timestamping.

So, in your code you don't need to register HttpTimeStampTokenProvider, only HttpTsaConfiguration. Note that this is what the tests (example code) do:

new XadesFormatExtenderProfile().with(DEFAULT_TEST_TSA)...

The same applies to the configuration of XadesCSigningProfile.