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...?
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:
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.