Skip to content

Commit f33d3c9

Browse files
author
Eugene Bochilo
committed
Add support for transition period and check for incompatible OJ certificates
DEVSIX-9285
1 parent 1a8f838 commit f33d3c9

File tree

10 files changed

+195
-23
lines changed

10 files changed

+195
-23
lines changed

sign/src/main/java/com/itextpdf/signatures/exceptions/SignExceptionMessageConstant.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ public final class SignExceptionMessageConstant {
142142
public static final String CACHE_ALREADY_INITIALIZED = "Global LOTL service has already been initialized. " +
143143
"You cannot initialize it again. If you want to use a different configuration, please create a new " +
144144
"instance of LotlService with the desired properties and use it in ValidatorChainBuilder.";
145+
public static final String OFFICIAL_JOURNAL_CERTIFICATES_OUTDATED =
146+
"Trusted certificates from Official Journal of European Union are outdated. " +
147+
"LOTL file cannot be validated. " +
148+
"Please, provide OJ certificates, which match the ones used to sign European Union List of Trusted Lists.";
145149

146150
private SignExceptionMessageConstant() {
147151
// Private constructor will prevent the instantiation of this class directly

sign/src/main/java/com/itextpdf/signatures/logs/SignLogMessageConstant.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public final class SignLogMessageConstant {
4747
public static final String NO_COUNTRY_SPECIFIC_LOTL_FETCHED = "Zero country specific Lotl files were fetched." ;
4848
public static final String FAILED_TO_FETCH_EU_JOURNAL_CERTIFICATES = "Problem occurred while fetching " +
4949
"EU Journal certificates.\n{0}";
50+
public static final String OJ_TRANSITION_PERIOD =
51+
"Main LOTL file contains two Official Journal of European Union links. " +
52+
"This usually indicates that transition period for Official Journal has started. " +
53+
"Newest version of Official Journal should be used from now on " +
54+
"to retrieve trusted certificates and LOTL location.";
5055

5156
private SignLogMessageConstant() {
5257
// Private constructor will prevent the instantiation of this class directly

sign/src/main/java/com/itextpdf/signatures/validation/lotl/EuropeanResourceFetcher.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public Result getEUJournalCertificates() {
5757
Result result = new Result();
5858
EuropeanTrustedListConfigurationFactory factory =
5959
EuropeanTrustedListConfigurationFactory.getFactory().get();
60+
result.setCurrentlySupportedPublication(factory.getCurrentlySupportedPublication());
6061

6162
SafeCalling.onExceptionLog(
6263
() -> result.setCertificates(factory.getCertificates()),
@@ -72,6 +73,7 @@ public Result getEUJournalCertificates() {
7273
public static class Result {
7374
private final ValidationReport localReport;
7475
private List<Certificate> certificates;
76+
private String currentlySupportedPublication;
7577

7678
/**
7779
* Create a new Instance of {@link Result}.
@@ -99,6 +101,15 @@ public List<Certificate> getCertificates() {
99101
return certificates;
100102
}
101103

104+
/**
105+
* Gets string constant representing currently used Official Journal publication.
106+
*
107+
* @return {@link String} constant representing currently used Official Journal publication
108+
*/
109+
public String getCurrentlySupportedPublication() {
110+
return currentlySupportedPublication;
111+
}
112+
102113
/**
103114
* Sets the list of certificates.
104115
*
@@ -107,6 +118,16 @@ public List<Certificate> getCertificates() {
107118
public void setCertificates(List<Certificate> certificates) {
108119
this.certificates = certificates;
109120
}
121+
122+
/**
123+
* Sets string constant representing currently used Official Journal publication.
124+
*
125+
* @param currentlySuppostedPublication {@link String}
126+
* constant representing currently used Official Journal publication
127+
*/
128+
public void setCurrentlySupportedPublication(String currentlySuppostedPublication) {
129+
this.currentlySupportedPublication = currentlySuppostedPublication;
130+
}
110131
}
111132

112133
}

sign/src/main/java/com/itextpdf/signatures/validation/lotl/LotlFetchingProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public LotlFetchingProperties setCacheStalenessInMilliseconds(long stalenessInMi
148148
* Gets the calculation function for the cache refresh interval.
149149
* <p>
150150
* This function will be used to determine the refresh interval based on the staleness time.
151-
* By default, it takes 70% of the staleness time as the refresh interval.
151+
* By default, it takes 23% of the staleness time as the refresh interval.
152152
*
153153
* @return a function that takes the staleness time in milliseconds and returns the refresh interval in
154154
* milliseconds.

sign/src/main/java/com/itextpdf/signatures/validation/lotl/LotlService.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,14 @@ public void initializeCache() {
160160
setupTimer();
161161
EuropeanLotlFetcher.Result mainLotlResult = lotlByteFetcher.fetch();
162162
if (!mainLotlResult.getLocalReport().getFailures().isEmpty()) {
163-
//We throw on main Lotl fetch failure, so we don't proceed to pivot and country specific LOTL fetches
163+
// We throw on main LOTL fetch failure, so we don't proceed to pivot and country specific LOTL fetches
164164
final ReportItem reportItem = mainLotlResult.getLocalReport().getFailures().get(0);
165165
throw new PdfException(reportItem.getMessage(), reportItem.getExceptionCause());
166166
}
167167

168168
EuropeanResourceFetcher.Result europeanResourceFetcherEUJournalCertificates =
169169
europeanResourceFetcher.getEUJournalCertificates();
170+
pivotFetcher.setCurrentJournalUri(europeanResourceFetcherEUJournalCertificates.getCurrentlySupportedPublication());
170171
PivotFetcher.Result pivotsResult = pivotFetcher.downloadAndValidatePivotFiles(mainLotlResult.getLotlXml(),
171172
europeanResourceFetcherEUJournalCertificates.getCertificates());
172173

@@ -320,9 +321,11 @@ protected void tryAndRefreshCache() {
320321
boolean mainLotlFetchSuccessful = false;
321322
Exception mainLotlFetchException = null;
322323

324+
String currentJournalUri;
323325
try {
324326
EuropeanResourceFetcher.Result europeanResourceFetcherEUJournalCertificates =
325327
europeanResourceFetcher.getEUJournalCertificates();
328+
currentJournalUri = europeanResourceFetcherEUJournalCertificates.getCurrentlySupportedPublication();
326329
if (europeanResourceFetcherEUJournalCertificates.getLocalReport().getValidationResult()
327330
!= ValidationResult.VALID) {
328331
throw new PdfException(
@@ -352,6 +355,7 @@ protected void tryAndRefreshCache() {
352355
if (mainLotlFetchSuccessful) {
353356
//Only if the main Lotl was fetched successfully, we proceed to re-fetch the new pivot files.
354357
try {
358+
pivotFetcher.setCurrentJournalUri(currentJournalUri);
355359
pivotResult = pivotFetcher.downloadAndValidatePivotFiles(
356360
mainLotlResult.getLotlXml(),
357361
europeanResourceFetcher.getEUJournalCertificates().getCertificates());
@@ -405,11 +409,13 @@ protected void tryAndRefreshCache() {
405409
}
406410
}
407411

408-
PivotFetcher.Result getAndValidatePivotFiles(byte[] lotlXml, List<Certificate> certificates) {
412+
PivotFetcher.Result getAndValidatePivotFiles(byte[] lotlXml, List<Certificate> certificates,
413+
String currentJournalUri) {
409414
PivotFetcher.Result result = cache.getPivotResult();
410415
if (result != null) {
411416
return result;
412417
}
418+
pivotFetcher.setCurrentJournalUri(currentJournalUri);
413419
PivotFetcher.Result newResult = pivotFetcher.downloadAndValidatePivotFiles(lotlXml, certificates);
414420
cache.setPivotResult(newResult);
415421
return newResult;

sign/src/main/java/com/itextpdf/signatures/validation/lotl/LotlValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public ValidationReport validate() {
7575
report.merge(europeanResult.getLocalReport());
7676

7777
PivotFetcher.Result result = service.getAndValidatePivotFiles(lotl.getLotlXml(),
78-
europeanResult.getCertificates());
78+
europeanResult.getCertificates(), europeanResult.getCurrentlySupportedPublication());
7979

8080
report.merge(result.getLocalReport());
8181
if (result.getLocalReport().getValidationResult() != ValidationResult.VALID) {

sign/src/main/java/com/itextpdf/signatures/validation/lotl/PivotFetcher.java

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,24 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.commons.utils.MessageFormatUtil;
2626
import com.itextpdf.kernel.exceptions.PdfException;
27+
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;
28+
import com.itextpdf.signatures.logs.SignLogMessageConstant;
2729
import com.itextpdf.signatures.validation.SafeCalling;
2830
import com.itextpdf.signatures.validation.TrustedCertificatesStore;
2931
import com.itextpdf.signatures.validation.lotl.xml.XmlSaxProcessor;
3032
import com.itextpdf.signatures.validation.report.ReportItem;
3133
import com.itextpdf.signatures.validation.report.ValidationReport;
3234
import com.itextpdf.signatures.validation.report.ValidationReport.ValidationResult;
35+
import org.slf4j.Logger;
36+
import org.slf4j.LoggerFactory;
3337

3438
import java.io.ByteArrayInputStream;
3539
import java.net.URL;
3640
import java.security.cert.Certificate;
3741
import java.util.ArrayList;
3842
import java.util.Collections;
3943
import java.util.List;
44+
import java.util.stream.Collectors;
4045

4146
import static com.itextpdf.signatures.validation.lotl.LotlValidator.LOTL_VALIDATION;
4247
import static com.itextpdf.signatures.validation.lotl.LotlValidator.LOTL_VALIDATION_UNSUCCESSFUL;
@@ -46,8 +51,10 @@ This file is part of the iText (R) project.
4651
* This class fetches and validates pivot files from a List of Trusted Lists (Lotl) XML.
4752
*/
4853
public class PivotFetcher {
54+
private static final Logger LOGGER = LoggerFactory.getLogger(PivotFetcher.class);
4955

5056
private final LotlService service;
57+
private String currentJournalUri;
5158

5259
/**
5360
* Constructs a PivotFetcher with the specified LotlService and ValidatorChainBuilder.
@@ -58,6 +65,15 @@ public PivotFetcher(LotlService service) {
5865
this.service = service;
5966
}
6067

68+
/**
69+
* Sets {@link String} constant representing currently used Official Journal publication.
70+
*
71+
* @param currentJournalUri {@link String} constant representing currently used Official Journal publication
72+
*/
73+
public void setCurrentJournalUri(String currentJournalUri) {
74+
this.currentJournalUri = currentJournalUri;
75+
}
76+
6177
/**
6278
* Fetches and validates pivot files from the provided Lotl XML.
6379
*
@@ -70,24 +86,36 @@ public Result downloadAndValidatePivotFiles(byte[] lotlXml, List<Certificate> ce
7086
if (lotlXml == null) {
7187
throw new PdfException(LotlValidator.UNABLE_TO_RETRIEVE_LOTL);
7288
}
73-
XmlPivotsHandler pivotsHandler = new XmlPivotsHandler();
74-
new XmlSaxProcessor().process(new ByteArrayInputStream(lotlXml), pivotsHandler);
7589
Result result = new Result();
7690

77-
List<String> pivotsUrlList = pivotsHandler.getPivots();
91+
List<String> pivotsUrlList = getPivotsUrlList(lotlXml);
92+
List<String> ojUris = pivotsUrlList.stream()
93+
.filter(url -> XmlPivotsHandler.isOfficialJournal(url)).collect(Collectors.toList());
94+
if (ojUris.size() > 1) {
95+
LOGGER.warn(SignLogMessageConstant.OJ_TRANSITION_PERIOD);
96+
}
7897
result.setPivotUrls(pivotsUrlList);
7998
List<byte[]> pivotFiles = new ArrayList<>();
8099

100+
// If we weren't able to find any OJ links, or current OJ uri is null, we process all the pivots.
101+
boolean startProcessing = ojUris.isEmpty() || currentJournalUri == null;
81102
// We need to process pivots backwards.
82103
for (int i = pivotsUrlList.size() - 1; i >= 0; i--) {
83104
String pivotUrl = pivotsUrlList.get(i);
84-
SafeCalling.onExceptionLog(
85-
() -> pivotFiles.add(service.getResourceRetriever().getByteArrayByUrl(new URL(pivotUrl))),
86-
result.getLocalReport(),
87-
e -> new ReportItem(LOTL_VALIDATION, MessageFormatUtil.format(
88-
UNABLE_TO_RETRIEVE_PIVOT, pivotUrl), e, ReportItem.ReportItemStatus.INVALID));
89-
if (result.getLocalReport().getValidationResult() != ValidationResult.VALID) {
90-
return result;
105+
if (pivotUrl.equals(currentJournalUri)) {
106+
// We only need to process pivots which, were created after OJ entry was added.
107+
startProcessing = true;
108+
continue;
109+
}
110+
if (startProcessing && !XmlPivotsHandler.isOfficialJournal(pivotUrl)) {
111+
SafeCalling.onExceptionLog(
112+
() -> pivotFiles.add(service.getResourceRetriever().getByteArrayByUrl(new URL(pivotUrl))),
113+
result.getLocalReport(),
114+
e -> new ReportItem(LOTL_VALIDATION, MessageFormatUtil.format(
115+
UNABLE_TO_RETRIEVE_PIVOT, pivotUrl), e, ReportItem.ReportItemStatus.INVALID));
116+
if (result.getLocalReport().getValidationResult() != ValidationResult.VALID) {
117+
return result;
118+
}
91119
}
92120
}
93121

@@ -107,6 +135,9 @@ public Result downloadAndValidatePivotFiles(byte[] lotlXml, List<Certificate> ce
107135
result.getLocalReport().addReportItem(new ReportItem(LOTL_VALIDATION, LOTL_VALIDATION_UNSUCCESSFUL,
108136
ReportItem.ReportItemStatus.INVALID));
109137
result.getLocalReport().merge(localReport);
138+
if (!ojUris.stream().anyMatch(ojUri -> ojUri.equals(currentJournalUri))) {
139+
throw new PdfException(SignExceptionMessageConstant.OFFICIAL_JOURNAL_CERTIFICATES_OUTDATED);
140+
}
110141
return result;
111142
}
112143
XmlCertificateRetriever certificateRetriever = new XmlCertificateRetriever(
@@ -116,6 +147,19 @@ public Result downloadAndValidatePivotFiles(byte[] lotlXml, List<Certificate> ce
116147
return result;
117148
}
118149

150+
/**
151+
* Gets list of pivots xml files, including OJ entries.
152+
*
153+
* @param lotlXml {@code byte} array representing main LOTL file
154+
*
155+
* @return list of pivots xml files, including OJ entries
156+
*/
157+
protected List<String> getPivotsUrlList(byte[] lotlXml) {
158+
XmlPivotsHandler pivotsHandler = new XmlPivotsHandler();
159+
new XmlSaxProcessor().process(new ByteArrayInputStream(lotlXml), pivotsHandler);
160+
return pivotsHandler.getPivots();
161+
}
162+
119163
/**
120164
* Result class encapsulates the result of the pivot fetching and validation process.
121165
*/

sign/src/main/java/com/itextpdf/signatures/validation/lotl/XmlPivotsHandler.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ public void startElement(String uri, String localName, String qName, HashMap<Str
5050
public void endElement(String uri, String localName, String qName) {
5151
if (XmlTagConstants.SCHEME_INFORMATION_URI.equals(localName)) {
5252
schemeInformationContext = false;
53-
} else if (XmlTagConstants.URI.equals(localName) && isPivot(uriLink.toString())) {
54-
pivots.add(uriLink.toString());
53+
} else if (XmlTagConstants.URI.equals(localName)) {
54+
String uriLinkString = uriLink.toString();
55+
if (isPivot(uriLinkString) || isOfficialJournal(uriLinkString)) {
56+
pivots.add(uriLinkString);
57+
}
5558
}
5659
}
5760

@@ -66,6 +69,10 @@ public List<String> getPivots() {
6669
return new ArrayList<>(pivots);
6770
}
6871

72+
static boolean isOfficialJournal(String uriLink) {
73+
return uriLink.contains("eur-lex.europa.eu");
74+
}
75+
6976
private static boolean isPivot(String uriLink) {
7077
return uriLink.contains("eu-lotl-pivot");
7178
}

sign/src/test/java/com/itextpdf/signatures/validation/lotl/LotlServiceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public Result downloadAndValidatePivotFiles(byte[] lotlXml, List<Certificate> ce
133133
}
134134
);
135135
f = lotlService.getAndValidatePivotFiles("abc".getBytes(StandardCharsets.UTF_8),
136-
Collections.<Certificate>emptyList());
136+
Collections.<Certificate>emptyList(), null);
137137
}
138138
Assertions.assertNotNull(f);
139139
assertEquals(1, f.getPivotUrls().size());
@@ -215,7 +215,7 @@ protected void tryAndRefreshCache() {
215215
}
216216
}) {
217217
lotlService.setupTimer();
218-
Thread.sleep(1000);
218+
Thread.sleep(2000);
219219
}
220220
Assertions.assertTrue(refreshCounter.get() >= 8,
221221
"Refresh counter should be greater than 8, but was: " + refreshCounter.get());

0 commit comments

Comments
 (0)