From 950b6f176fed61eba7d3d87202bab0c2cbba2be0 Mon Sep 17 00:00:00 2001 From: Glenn Volckaert Date: Wed, 8 May 2024 11:41:39 +0000 Subject: [PATCH 1/3] Adds mocking classes and converts validation tests to mocked out unit tests DEVSIX-8284 Autoported commit. Original commit hash: [c781db543] Manual files: commons/src/main/java/com/itextpdf/commons/utils/DateTimeUtil.java commons/src/test/java/com/itextpdf/commons/utils/DateTimeUtilTest.java sign/src/test/java/com/itextpdf/signatures/testutils/builder/TestOcspResponseBuilder.java --- .../testutils/client/TestCrlClientWrapper.cs | 51 +++ .../testutils/client/TestOcspClientWrapper.cs | 58 +++ .../validation/v1/AssertValidationReport.cs | 220 ++++++++-- .../validation/v1/CRLValidatorTest.cs | 118 +++--- .../v1/CertificateChainValidatorTest.cs | 246 ++++++------ .../validation/v1/MockChainValidator.cs | 30 +- .../validation/v1/MockCrlValidator.cs | 61 +++ .../v1/MockIssuingCertificateRetriever.cs | 98 +++++ .../validation/v1/MockOCSPValidator.cs | 66 +++ .../v1/MockRevocationDataValidator.cs | 60 +++ .../v1/MockSignatureValidationProperties.cs | 105 +++++ .../v1/OCSPValidatorIntegrationTest.cs | 267 +++++++++++++ .../validation/v1/OCSPValidatorTest.cs | 352 ++++++---------- .../RevocationDataValidatorIntegrationTest.cs | 89 +++++ .../v1/RevocationDataValidatorTest.cs | 377 +++++++++--------- .../v1/SignatureValidationPropertiesTest.cs | 4 +- .../v1/SignatureValidatorIntegrationTest.cs | 201 ++++++++++ .../validation/v1/SignatureValidatorTest.cs | 302 +++----------- .../v1/report/ValidationReportTest.cs | 127 ++++++ .../trustedOcspResponderCert.pem | 51 +++ .../signatures/validation/v1/OCSPValidator.cs | 4 +- .../validation/v1/report/ValidationReport.cs | 9 +- port-hash | 2 +- 23 files changed, 2006 insertions(+), 892 deletions(-) create mode 100644 itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorIntegrationTest.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorIntegrationTest.cs create mode 100644 itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/RevocationDataValidatorTest/trustedOcspResponderCert.pem diff --git a/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs new file mode 100644 index 0000000000..1e17d32785 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using iText.Commons.Bouncycastle.Cert; +using iText.Signatures; + +namespace iText.Signatures.Testutils.Client { + public class TestCrlClientWrapper : ICrlClient { + private readonly ICrlClient wrappedClient; + + private readonly IList calls = new List(); + + public TestCrlClientWrapper(ICrlClient wrappedClient) { + this.wrappedClient = wrappedClient; + } + + public virtual ICollection GetEncoded(IX509Certificate checkCert, String url) { + ICollection crlBytesCollection = wrappedClient.GetEncoded(checkCert, url); + IList crlResponses = new List(); + foreach (byte[] crlBytes in crlBytesCollection) { + try { + crlResponses.Add((IX509Crl)CertificateUtil.ParseCrlFromStream(new MemoryStream(crlBytes))); + } + catch (Exception e) { + throw new Exception("Deserializing CRL response failed", e); + } + } + calls.Add(new TestCrlClientWrapper.CrlClientCall(checkCert, url, crlResponses)); + return crlBytesCollection; + } + + public virtual IList GetCalls() { + return calls; + } + + public class CrlClientCall { + public readonly IX509Certificate checkCert; + + public readonly String url; + + public readonly IList responses; + + public CrlClientCall(IX509Certificate checkCert, String url, IList responses) { + this.checkCert = checkCert; + this.url = url; + this.responses = responses; + } + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs new file mode 100644 index 0000000000..0f5856cc95 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using iText.Bouncycastleconnector; +using iText.Commons.Bouncycastle; +using iText.Commons.Bouncycastle.Asn1.Ocsp; +using iText.Commons.Bouncycastle.Cert; +using iText.Signatures; + +namespace iText.Signatures.Testutils.Client { + public class TestOcspClientWrapper : IOcspClient { + private static readonly IBouncyCastleFactory BOUNCY_CASTLE_FACTORY = BouncyCastleFactoryCreator.GetFactory + (); + + private readonly IList calls = new List(); + + private readonly IOcspClient wrappedClient; + + public TestOcspClientWrapper(IOcspClient wrappedClient) { + this.wrappedClient = wrappedClient; + } + + public virtual byte[] GetEncoded(IX509Certificate checkCert, IX509Certificate issuerCert, String url) { + byte[] response = wrappedClient.GetEncoded(checkCert, issuerCert, url); + try { + IBasicOcspResponse basicOCSPResp = BOUNCY_CASTLE_FACTORY.CreateBasicOCSPResponse(BOUNCY_CASTLE_FACTORY.CreateASN1Primitive + (response)); + calls.Add(new TestOcspClientWrapper.OcspClientCall(checkCert, issuerCert, url, basicOCSPResp)); + } + catch (System.IO.IOException e) { + throw new Exception("deserializing ocsp response failed", e); + } + return response; + } + + public virtual IList GetCalls() { + return calls; + } + + public class OcspClientCall { + public readonly IX509Certificate checkCert; + + public readonly IX509Certificate issuerCert; + + public readonly String url; + + public readonly IBasicOcspResponse response; + + public OcspClientCall(IX509Certificate checkCert, IX509Certificate issuerCert, String url, IBasicOcspResponse + response) { + this.checkCert = checkCert; + this.issuerCert = issuerCert; + this.url = url; + this.response = response; + } + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/AssertValidationReport.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/AssertValidationReport.cs index 39a58e845d..108eb81d8c 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/AssertValidationReport.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/AssertValidationReport.cs @@ -21,26 +21,40 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; +using iText.Commons.Bouncycastle.Cert; +using iText.Commons.Utils; using iText.Signatures.Validation.V1.Report; namespace iText.Signatures.Validation.V1 { - public class AssertValidationReport { + public class AssertValidationReport : IDisposable { private readonly ValidationReport report; private readonly AssertValidationReport.CheckChain chain = new AssertValidationReport.StartOfChain(); - public AssertValidationReport(ValidationReport report) { + private bool asserted = false; + + private AssertValidationReport(ValidationReport report) { this.report = report; } - public virtual void DoAssert() { + public static void AssertThat(ValidationReport report, Action c) { + iText.Signatures.Validation.V1.AssertValidationReport assertion = new iText.Signatures.Validation.V1.AssertValidationReport + (report); + c(assertion); + assertion.DoAssert(); + } + + private void DoAssert() { + asserted = true; AssertValidationReport.CheckResult result = new AssertValidationReport.CheckResult(); chain.Run(report, result); if (!result.success) { - result.messageBuilder.Append("\n For item: ").Append(report); + result.messageBuilder.Append("\n For report: ").Append(report); throw new AssertionException(result.messageBuilder.ToString()); } } @@ -55,15 +69,26 @@ public virtual iText.Signatures.Validation.V1.AssertValidationReport HasNumberOf return this; } - public virtual iText.Signatures.Validation.V1.AssertValidationReport HasLogItem(Func check - , String itemDescription) { - chain.SetNext(new AssertValidationReport.ItemCheck(check, 1, itemDescription)); + public virtual iText.Signatures.Validation.V1.AssertValidationReport HasLogItem(ReportItem logItem) { + chain.SetNext(new AssertValidationReport.LogItemCheck(logItem)); return this; } - public virtual iText.Signatures.Validation.V1.AssertValidationReport HasLogItems(Func check - , int count, String itemDescription) { - chain.SetNext(new AssertValidationReport.ItemCheck(check, count, itemDescription)); + public virtual iText.Signatures.Validation.V1.AssertValidationReport HasLogItem(Action c) { + AssertValidationReport.AssertValidationReportLogItem asserter = new AssertValidationReport.AssertValidationReportLogItem + (1, 1); + c(asserter); + asserter.AddToChain(this); + return this; + } + + public virtual iText.Signatures.Validation.V1.AssertValidationReport HasLogItems(int minCount, int maxCount + , Action c) { + AssertValidationReport.AssertValidationReportLogItem asserter = new AssertValidationReport.AssertValidationReportLogItem + (minCount, maxCount); + c(asserter); + asserter.AddToChain(this); return this; } @@ -73,6 +98,53 @@ public virtual iText.Signatures.Validation.V1.AssertValidationReport HasStatus(V return this; } + public virtual void Close() { + if (!asserted) { + throw new InvalidOperationException("AssertValidationReport not asserted!"); + } + } + + public class AssertValidationReportLogItem { + private readonly AssertValidationReport.ValidationReportLogItemCheck check; + + public AssertValidationReportLogItem(int minCount, int maxCount) { + this.check = new AssertValidationReport.ValidationReportLogItemCheck(minCount, maxCount); + } + + public virtual AssertValidationReport.AssertValidationReportLogItem WithCheckName(String checkName) { + check.WithCheckName(checkName); + return this; + } + + public AssertValidationReport.AssertValidationReportLogItem WithMessage(String message, params Func[] @params) { + check.WithMessage(message, @params); + return this; + } + + public virtual AssertValidationReport.AssertValidationReportLogItem WithStatus(ReportItem.ReportItemStatus + status) { + check.WithStatus(status); + return this; + } + + public virtual AssertValidationReport.AssertValidationReportLogItem WithCertificate(IX509Certificate certificate + ) { + check.WithCertificate(certificate); + return this; + } + + public virtual AssertValidationReport.AssertValidationReportLogItem WithExceptionCauseType(Type exceptionType + ) { + check.WithExceptionCauseType(exceptionType); + return this; + } + + public virtual void AddToChain(AssertValidationReport asserter) { + asserter.chain.SetNext(check); + } + } + private class CheckResult { public StringBuilder messageBuilder = new StringBuilder("\n"); @@ -129,6 +201,110 @@ protected internal override void Check(ValidationReport report, AssertValidation } } + private class ValidationReportLogItemCheck : AssertValidationReport.CheckChain { + private readonly int minCount; + + private readonly int maxCount; + + private readonly IList> messageParams = new List>(); + + private readonly StringBuilder errorMessage = new StringBuilder(); + + private String checkName; + + private String message; + + private ReportItem.ReportItemStatus status; + + private bool checkStatus = false; + + private IX509Certificate certificate; + + private Type exceptionType; + + public ValidationReportLogItemCheck(int minCount, int maxCount) { + this.minCount = minCount; + this.maxCount = maxCount; + errorMessage.Append("\nExpected between ").Append(minCount).Append(" and ").Append(maxCount).Append(" message with " + ); + } + + public virtual void WithCheckName(String checkName) { + this.checkName = checkName; + errorMessage.Append(" check name '").Append(checkName).Append("'"); + } + + public virtual void WithMessage(String message, params Func[] @params) { + this.message = message; + messageParams.AddAll(@params); + errorMessage.Append(" message '").Append(message).Append("'"); + } + + public virtual void WithStatus(ReportItem.ReportItemStatus status) { + this.status = status; + checkStatus = true; + errorMessage.Append(" status '").Append(status).Append("'"); + } + + public virtual void WithCertificate(IX509Certificate certificate) { + this.certificate = certificate; + errorMessage.Append(" certificate '").Append(certificate.GetSubjectDN()).Append("'"); + } + + public virtual void WithExceptionCauseType(Type exceptionType) { + this.exceptionType = exceptionType; + errorMessage.Append(" with exception cause '").Append(exceptionType.FullName).Append("'"); + } + + protected internal override void Check(ValidationReport report, AssertValidationReport.CheckResult result) { + errorMessage.Append("\n"); + IList prefiltered; + if (message != null) { + prefiltered = report.GetLogs().Where((i) => { + Object[] @params = new Object[messageParams.Count]; + for (int p = 0; p < messageParams.Count; p++) { + @params[p] = messageParams[p].Invoke(i); + } + return i.GetMessage().Equals(MessageFormatUtil.Format(message, @params)); + } + ).ToList(); + errorMessage.Append("found ").Append(prefiltered.Count).Append(" matches after message filter\n"); + } + else { + prefiltered = report.GetLogs(); + } + if (checkName != null) { + prefiltered = prefiltered.Where((i) => (checkName.Equals(i.GetCheckName()))).ToList(); + errorMessage.Append("found ").Append(prefiltered.Count).Append(" matches after check name filter\n"); + } + if (checkStatus) { + prefiltered = prefiltered.Where((i) => (status.Equals(i.GetStatus()))).ToList(); + errorMessage.Append("found ").Append(prefiltered.Count).Append(" matches after status filter\n"); + } + if (certificate != null) { + prefiltered = prefiltered.Where((i) => certificate.Equals(((CertificateReportItem)i).GetCertificate())).ToList + (); + errorMessage.Append("found ").Append(prefiltered.Count).Append(" matches after certificate filter\n"); + } + if (exceptionType != null) { + prefiltered = prefiltered.Where((i) => i.GetExceptionCause() != null && exceptionType.IsAssignableFrom(i.GetExceptionCause + ().GetType())).ToList(); + errorMessage.Append("found ").Append(prefiltered.Count).Append(" matches after exception cause filter\n"); + } + long foundCount = prefiltered.Count; + if (foundCount < minCount || foundCount > maxCount) { + result.success = false; + result.messageBuilder.Append(errorMessage); + } + } + + public override String ToString() { + return "checkName='" + checkName + '\'' + ", message='" + message + '\'' + ", status=" + status + ", certificate=" + + (certificate == null ? "null" : certificate.GetSubjectDN().ToString()) + ", exceptionType=" + (exceptionType + == null ? "null" : exceptionType.FullName); + } + } + private class LogCountCheck : AssertValidationReport.CheckChain { private readonly int expected; @@ -146,26 +322,18 @@ protected internal override void Check(ValidationReport report, AssertValidation } } - private class ItemCheck : AssertValidationReport.CheckChain { - private readonly Func check; + private class LogItemCheck : AssertValidationReport.CheckChain { + private readonly ReportItem expectedItem; - private readonly String message; - - private readonly int expectedCount; - - public ItemCheck(Func check, int count, String itemDescription) + public LogItemCheck(ReportItem expectedItem) : base() { - this.check = check; - this.expectedCount = count; - this.message = itemDescription; + this.expectedItem = expectedItem; } protected internal override void Check(ValidationReport report, AssertValidationReport.CheckResult result) { - long foundCount = report.GetLogs().Where((i) => check.Invoke(i)).Count(); - if (foundCount != expectedCount) { + if (!report.GetLogs().Contains(expectedItem)) { result.success = false; - result.messageBuilder.Append("\nExpected ").Append(expectedCount).Append(" report logs like '").Append(message - ).Append("' but found ").Append(foundCount); + result.messageBuilder.Append("\nExpected report item not found:").Append(expectedItem); } } } @@ -186,5 +354,9 @@ protected internal override void Check(ValidationReport report, AssertValidation } } } + + void System.IDisposable.Dispose() { + Close(); + } } } diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CRLValidatorTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CRLValidatorTest.cs index 6fe1146891..d3f719f0f7 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CRLValidatorTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CRLValidatorTest.cs @@ -79,8 +79,7 @@ public virtual void HappyPathTest() { byte[] crl = CreateCrl(crlIssuerCert, crlIssuerKey, TimeTestUtil.TEST_DATE_TIME.AddDays(-5), TimeTestUtil. TEST_DATE_TIME.AddDays(+5)); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.IsTrue(report.GetFailures().IsEmpty()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID)); } [NUnit.Framework.Test] @@ -89,11 +88,9 @@ public virtual void NextUpdateBeforeValidationTest() { DateTime nextUpdate = TimeTestUtil.TEST_DATE_TIME.AddDays(-5); byte[] crl = CreateCrl(crlIssuerCert, crlIssuerKey, TimeTestUtil.TEST_DATE_TIME.AddDays(-15), nextUpdate); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CRLValidator.UPDATE_DATE_BEFORE_CHECK_DATE, nextUpdate - , TimeTestUtil.TEST_DATE_TIME), report.GetFailures()[0].GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((la) => la.WithMessage(CRLValidator.UPDATE_DATE_BEFORE_CHECK_DATE, (l) => nextUpdate, (l) => + TimeTestUtil.TEST_DATE_TIME))); } [NUnit.Framework.Test] @@ -103,9 +100,12 @@ public virtual void ChainValidatorUsageTest() { TEST_DATE_TIME.AddDays(+5)); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.IsTrue(report.GetFailures().IsEmpty()); NUnit.Framework.Assert.AreEqual(1, mockChainValidator.verificationCalls.Count); NUnit.Framework.Assert.AreEqual(crlIssuerCert, mockChainValidator.verificationCalls[0].certificate); + NUnit.Framework.Assert.AreEqual(CertificateSource.CRL_ISSUER, mockChainValidator.verificationCalls[0].context + .GetCertificateSource()); + NUnit.Framework.Assert.AreEqual(ValidatorContext.CRL_VALIDATOR, mockChainValidator.verificationCalls[0].context + .GetValidatorContext()); } [NUnit.Framework.Test] @@ -114,9 +114,8 @@ public virtual void IssuerCertificateIsNotFoundTest() { byte[] crl = CreateCrl(crlIssuerCert, crlIssuerKey, TimeTestUtil.TEST_DATE_TIME.AddDays(-5), TimeTestUtil. TEST_DATE_TIME.AddDays(+5)); ValidationReport report = PerformValidation("missingIssuer", TimeTestUtil.TEST_DATE_TIME, crl); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_ISSUER_NOT_FOUND, report.GetFailures()[0].GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((la) => la.WithMessage(CRLValidator.CRL_ISSUER_NOT_FOUND))); } [NUnit.Framework.Test] @@ -126,10 +125,8 @@ public virtual void CrlIssuerAndSignCertHaveNoSharedRootTest() { TEST_DATE_TIME.AddDays(+5)); ValidationReport report = PerformValidation("crlIssuerAndSignCertHaveNoSharedRoot", TimeTestUtil.TEST_DATE_TIME , crl); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_ISSUER_NO_COMMON_ROOT, report.GetFailures()[0].GetMessage - ()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((la) => la.WithMessage(CRLValidator.CRL_ISSUER_NO_COMMON_ROOT))); } [NUnit.Framework.Test] @@ -141,10 +138,9 @@ public virtual void CrlIssuerRevokedBeforeSigningDate() { TEST_DATE_TIME.AddDays(+5), signCert, revocationDate, 1); ValidationReport report = PerformValidation("crlIssuerRevokedBeforeSigningDate", TimeTestUtil.TEST_DATE_TIME , crl); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CRLValidator.CERTIFICATE_REVOKED, crlIssuerCert.GetSubjectDN - (), revocationDate), report.GetFailures()[0].GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((al) => al.WithStatus(ReportItem.ReportItemStatus + .INVALID).WithMessage(CRLValidator.CERTIFICATE_REVOKED, (i) => crlIssuerCert.GetSubjectDN(), (i) => revocationDate + ))); } [NUnit.Framework.Test] @@ -155,10 +151,9 @@ public virtual void CrlRevokedAfterSigningDate() { byte[] crl = CreateCrl(crlIssuerCert, crlIssuerKey, TimeTestUtil.TEST_DATE_TIME.AddDays(+18), TimeTestUtil .TEST_DATE_TIME.AddDays(+23), signCert, revocationDate, 1); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(2, report.GetLogs().Count); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(SignLogMessageConstant.VALID_CERTIFICATE_IS_REVOKED - , revocationDate), report.GetLogs()[1].GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((la) => la.WithMessage(SignLogMessageConstant + .VALID_CERTIFICATE_IS_REVOKED, (i) => revocationDate).WithStatus(ReportItem.ReportItemStatus.INFO).WithCertificate + (signCert))); } [NUnit.Framework.Test] @@ -168,21 +163,16 @@ public virtual void CrlSignatureMismatch() { byte[] crl = CreateCrl(crlIssuerCert, intermediateKey, TimeTestUtil.TEST_DATE_TIME.AddDays(+18), TimeTestUtil .TEST_DATE_TIME.AddDays(+23), signCert, TimeTestUtil.TEST_DATE_TIME.AddDays(+20), 1); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_INVALID, report.GetFailures()[0].GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((la) => la.WithMessage(CRLValidator.CRL_INVALID + ).WithStatus(ReportItem.ReportItemStatus.INDETERMINATE))); } [NUnit.Framework.Test] public virtual void CrlContainsOnlyCACertsTest() { String crlPath = SOURCE_FOLDER + "issuingDistributionPointTest/onlyCA.crl"; ValidationReport report = CheckCrlScope(crlPath); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(CRLValidator.CERTIFICATE_IS_NOT_IN_THE_CRL_SCOPE, report.GetFailures()[0]. - GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((la) => la.WithMessage(CRLValidator.CERTIFICATE_IS_NOT_IN_THE_CRL_SCOPE + ).WithStatus(ReportItem.ReportItemStatus.INDETERMINATE))); } [NUnit.Framework.Test] @@ -190,18 +180,14 @@ public virtual void CrlContainsOnlyUserCertsTest() { String crlPath = SOURCE_FOLDER + "issuingDistributionPointTest/onlyUser.crl"; ValidationReport report = CheckCrlScope(crlPath); NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); } [NUnit.Framework.Test] public virtual void CrlContainsOnlyAttributeCertsTest() { String crlPath = SOURCE_FOLDER + "issuingDistributionPointTest/onlyAttr.crl"; ValidationReport report = CheckCrlScope(crlPath); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(CRLValidator.ATTRIBUTE_CERTS_ASSERTED, report.GetFailures()[0].GetMessage( - )); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((la) => la.WithMessage(CRLValidator.ATTRIBUTE_CERTS_ASSERTED))); } [NUnit.Framework.Test] @@ -220,12 +206,9 @@ public virtual void OnlySomeReasonsTest() { .SIGNER_CERT, TimeBasedContext.PRESENT); validator.Validate(report, context, signCert, (IX509Crl)CertificateUtil.ParseCrlFromStream(new MemoryStream (builder.MakeCrl())), TimeTestUtil.TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - CertificateReportItem reportItem = (CertificateReportItem)report.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(signCert, reportItem.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.ONLY_SOME_REASONS_CHECKED, reportItem.GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((al) => al.WithMessage(CRLValidator.ONLY_SOME_REASONS_CHECKED).WithCertificate(signCert)) + ); } [NUnit.Framework.Test] @@ -249,11 +232,8 @@ public virtual void CheckLessReasonsTest() { // Validate CRL with onlySomeReasons. validator.Validate(report, context, signCert, (IX509Crl)CertificateUtil.ParseCrlFromStream(new MemoryStream (builder.MakeCrl())), TimeTestUtil.TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - CertificateReportItem reportItem = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(signCert, reportItem.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.SAME_REASONS_CHECK, reportItem.GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasLogItem + ((al) => al.WithMessage(CRLValidator.SAME_REASONS_CHECK))); } [NUnit.Framework.Test] @@ -272,11 +252,9 @@ public virtual void RemoveFromCrlTest() { .SIGNER_CERT, TimeBasedContext.PRESENT); validator.Validate(report, context, signCert, (IX509Crl)CertificateUtil.ParseCrlFromStream(new MemoryStream (builder.MakeCrl())), TimeTestUtil.TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - CertificateReportItem reportItem = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(signCert, reportItem.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CERTIFICATE_IS_UNREVOKED, reportItem.GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasLogItem + ((la) => la.WithCertificate(signCert).WithCheckName(CRLValidator.CRL_CHECK).WithMessage(CRLValidator.CERTIFICATE_IS_UNREVOKED + ))); } [NUnit.Framework.Test] @@ -299,13 +277,9 @@ public virtual void FullCrlButDistributionPointWithReasonsTest() { .SIGNER_CERT, TimeBasedContext.PRESENT); validator.Validate(report, context, cert, (IX509Crl)CertificateUtil.ParseCrlFromStream(new MemoryStream(builder .MakeCrl())), checkDate); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - CertificateReportItem reportItem = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INDETERMINATE, reportItem.GetStatus()); - NUnit.Framework.Assert.AreEqual(cert, reportItem.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.ONLY_SOME_REASONS_CHECKED, reportItem.GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((la) => la.WithStatus(ReportItem.ReportItemStatus.INDETERMINATE).WithCertificate(cert).WithMessage + (CRLValidator.ONLY_SOME_REASONS_CHECKED))); } [NUnit.Framework.Test] @@ -316,11 +290,10 @@ public virtual void NoExpiredCertOnCrlExtensionTest() { (401)); byte[] crl = builder.MakeCrl(); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); - new AssertValidationReport(report).HasStatus(ValidationReport.ValidationResult.INDETERMINATE).HasNumberOfFailures - (1).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals(CRLValidator.CRL_CHECK) && l.GetMessage - ().Equals(MessageFormatUtil.Format(CRLValidator.CERTIFICATE_IS_EXPIRED, signCert.GetNotAfter())) && (( - CertificateReportItem)l).GetCertificate().Equals(signCert), CRLValidator.CERTIFICATE_IS_EXPIRED).DoAssert - (); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(CRLValidator.CRL_CHECK). + WithMessage(CRLValidator.CERTIFICATE_IS_EXPIRED, (i) => signCert.GetNotAfter()).WithCertificate(signCert + ))); } [NUnit.Framework.Test] @@ -333,11 +306,10 @@ public virtual void CertExpiredBeforeDateFromExpiredCertOnCrlTest() { (TimeTestUtil.TEST_DATE_TIME.AddYears(400))); byte[] crl = builder.MakeCrl(); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); - new AssertValidationReport(report).HasStatus(ValidationReport.ValidationResult.INDETERMINATE).HasNumberOfFailures - (1).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals(CRLValidator.CRL_CHECK) && l.GetMessage - ().Equals(MessageFormatUtil.Format(CRLValidator.CERTIFICATE_IS_EXPIRED, signCert.GetNotAfter())) && (( - CertificateReportItem)l).GetCertificate().Equals(signCert), CRLValidator.CERTIFICATE_IS_EXPIRED).DoAssert - (); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(CRLValidator.CRL_CHECK). + WithMessage(CRLValidator.CERTIFICATE_IS_EXPIRED, (i) => signCert.GetNotAfter()).WithCertificate(signCert + ))); } [NUnit.Framework.Test] @@ -350,8 +322,8 @@ public virtual void CertExpiredAfterDateFromExpiredCertOnCrlExtensionTest() { (TimeTestUtil.TEST_DATE_TIME.AddYears(399))); byte[] crl = builder.MakeCrl(); ValidationReport report = PerformValidation("happyPath", TimeTestUtil.TEST_DATE_TIME, crl); - new AssertValidationReport(report).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures( - 0).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0)); } private ValidationReport CheckCrlScope(String crlPath) { diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CertificateChainValidatorTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CertificateChainValidatorTest.cs index 5d1beb6c5a..24a7081d4d 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CertificateChainValidatorTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/CertificateChainValidatorTest.cs @@ -46,14 +46,15 @@ public class CertificateChainValidatorTest : ExtendedITextTest { private readonly ValidationContext baseContext = new ValidationContext(ValidatorContext.CERTIFICATE_CHAIN_VALIDATOR , CertificateSource.SIGNER_CERT, TimeBasedContext.PRESENT); + private MockRevocationDataValidator mockRevocationDataValidator; + [NUnit.Framework.SetUp] public virtual void Setup() { + mockRevocationDataValidator = new MockRevocationDataValidator(); properties = new SignatureValidationProperties(); certificateRetriever = new IssuingCertificateRetriever(); validatorChainBuilder = new ValidatorChainBuilder().WithIssuingCertificateRetriever(certificateRetriever). - WithSignatureValidationProperties(properties); - validatorChainBuilder.WithRevocationDataValidator(new CertificateChainValidatorTest.MockRevocationDataValidator - (validatorChainBuilder)); + WithSignatureValidationProperties(properties).WithRevocationDataValidator(mockRevocationDataValidator); } [NUnit.Framework.Test] @@ -69,11 +70,38 @@ public virtual void ValidChainTest() { certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); ValidationReport report = validator.ValidateCertificate(baseContext, signingCert, TimeTestUtil.TEST_DATE_TIME ); - new AssertValidationReport(report).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures( - 0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage( - ).Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), CertificateChainValidator.CERTIFICATE_TRUSTED - ).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((la) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK + ).WithMessage("Certificate {0} is trusted, revocation data checks are not required.", (l) => rootCert. + GetSubjectDN()).WithCertificate(rootCert))); + } + + [NUnit.Framework.Test] + public virtual void RevocationValidationCallTest() { + String chainName = CERTS_SRC + "chain.pem"; + IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); + IX509Certificate signingCert = (IX509Certificate)certificateChain[0]; + IX509Certificate intermediateCert = (IX509Certificate)certificateChain[1]; + IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + CertificateChainValidator validator = validatorChainBuilder.BuildCertificateChainValidator(); + certificateRetriever.AddKnownCertificates(JavaCollectionsUtil.SingletonList(intermediateCert + )); + certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); + ValidationReport report = validator.ValidateCertificate(baseContext, signingCert, TimeTestUtil.TEST_DATE_TIME + ); + NUnit.Framework.Assert.AreEqual(2, mockRevocationDataValidator.calls.Count); + MockRevocationDataValidator.RevocationDataValidatorCall call1 = mockRevocationDataValidator.calls[0]; + NUnit.Framework.Assert.AreEqual(signingCert, call1.certificate); + NUnit.Framework.Assert.AreEqual(CertificateSource.SIGNER_CERT, call1.context.GetCertificateSource()); + NUnit.Framework.Assert.AreEqual(ValidatorContext.CERTIFICATE_CHAIN_VALIDATOR, call1.context.GetValidatorContext + ()); + NUnit.Framework.Assert.AreEqual(TimeTestUtil.TEST_DATE_TIME, call1.validationDate); + MockRevocationDataValidator.RevocationDataValidatorCall call2 = mockRevocationDataValidator.calls[1]; + NUnit.Framework.Assert.AreEqual(intermediateCert, call2.certificate); + NUnit.Framework.Assert.AreEqual(CertificateSource.CERT_ISSUER, call2.context.GetCertificateSource()); + NUnit.Framework.Assert.AreEqual(ValidatorContext.CERTIFICATE_CHAIN_VALIDATOR, call2.context.GetValidatorContext + ()); + NUnit.Framework.Assert.AreEqual(TimeTestUtil.TEST_DATE_TIME, call2.validationDate); } [NUnit.Framework.Test] @@ -152,10 +180,9 @@ public virtual void IntermediateCertTrustedTest() { certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(intermediateCert)); ValidationReport report = validator.ValidateCertificate(baseContext, signingCert, DateTimeUtil.GetCurrentUtcTime ()); - new AssertValidationReport(report).HasNumberOfFailures(0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName - ().Equals("Certificate check.") && l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator - .CERTIFICATE_TRUSTED, intermediateCert.GetSubjectDN())), CertificateChainValidator.CERTIFICATE_TRUSTED - ).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(0).HasNumberOfLogs(1).HasLogItem((la + ) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (l) => intermediateCert.GetSubjectDN()))); } [NUnit.Framework.Test] @@ -170,11 +197,10 @@ public virtual void ValidChainRequiredExtensionPositiveTest() { certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); ValidationReport report = validator.ValidateCertificate(baseContext, signingCert, DateTimeUtil.GetCurrentUtcTime ()); - new AssertValidationReport(report).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures( - 0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage( - ).Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), CertificateChainValidator.CERTIFICATE_TRUSTED - ).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((la) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK + ).WithMessage(CertificateChainValidator.CERTIFICATE_TRUSTED, (l) => rootCert.GetSubjectDN()).WithCertificate + (rootCert))); } [NUnit.Framework.Test] @@ -189,16 +215,13 @@ public virtual void ValidChainRequiredExtensionNegativeTest() { certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); ValidationReport report = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .CERT_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); - new AssertValidationReport(report).HasNumberOfFailures(2).HasNumberOfLogs(3).HasLogItem((l) => l.GetCheckName - ().Equals("Certificate check.") && l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator - .CERTIFICATE_TRUSTED, rootCert.GetSubjectDN())) && ((CertificateReportItem)l).GetCertificate().Equals( - rootCert), CertificateChainValidator.CERTIFICATE_TRUSTED).HasLogItem((l) => l.GetCheckName().Equals("Required certificate extensions check." - ) && l.GetMessage().Equals(MessageFormatUtil.Format("Required extension {0} is missing or incorrect.", - OID.X509Extensions.KEY_USAGE)) && ((CertificateReportItem)l).GetCertificate().Equals(signingCert), "Required extension {0} is missing or incorrect." - ).HasLogItem((l) => l.GetCheckName().Equals("Required certificate extensions check.") && l.GetMessage( - ).Equals(MessageFormatUtil.Format("Required extension {0} is missing or incorrect.", OID.X509Extensions - .BASIC_CONSTRAINTS)) && ((CertificateReportItem)l).GetCertificate().Equals(signingCert), "Required extension {0} is missing or incorrect." - ).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(2).HasNumberOfLogs(3).HasLogItem((la + ) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (l) => rootCert.GetSubjectDN()).WithCertificate(rootCert)).HasLogItem((la) => la + .WithCheckName(CertificateChainValidator.EXTENSIONS_CHECK).WithMessage(CertificateChainValidator.EXTENSION_MISSING + , (l) => OID.X509Extensions.KEY_USAGE).WithCertificate(signingCert)).HasLogItem((la) => la.WithCheckName + (CertificateChainValidator.EXTENSIONS_CHECK).WithMessage(CertificateChainValidator.EXTENSION_MISSING, + (l) => OID.X509Extensions.BASIC_CONSTRAINTS).WithCertificate(signingCert))); } [NUnit.Framework.Test] @@ -211,11 +234,10 @@ public virtual void ValidChainTrustedRootIsnSetTest() { certificateRetriever.AddKnownCertificates(JavaCollectionsUtil.SingletonList(intermediateCert)); ValidationReport report = validator.ValidateCertificate(baseContext, signingCert, DateTimeUtil.GetCurrentUtcTime ()); - new AssertValidationReport(report).HasStatus(ValidationReport.ValidationResult.INDETERMINATE).HasNumberOfFailures - (1).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.ISSUER_MISSING, intermediateCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(intermediateCert), CertificateChainValidator - .ISSUER_MISSING).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((la) => la.WithCheckName(CertificateChainValidator + .CERTIFICATE_CHECK).WithMessage(CertificateChainValidator.ISSUER_MISSING, (l) => intermediateCert.GetSubjectDN + ()).WithCertificate(intermediateCert))); } [NUnit.Framework.Test] @@ -232,13 +254,12 @@ public virtual void IntermediateCertIsNotYetValidTest() { certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); ValidationReport report = validator.ValidateCertificate(baseContext, signingCert, TimeTestUtil.TEST_DATE_TIME ); - new AssertValidationReport(report).HasNumberOfFailures(1).HasNumberOfLogs(2).HasLogItem((l) => l.GetCheckName - ().Equals("Certificate check.") && l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator - .CERTIFICATE_TRUSTED, rootCert.GetSubjectDN())) && ((CertificateReportItem)l).GetCertificate().Equals( - rootCert), CertificateChainValidator.CERTIFICATE_TRUSTED).HasLogItem((l) => l.GetCheckName().Equals("Certificate validity period check." - ) && l.GetMessage().Equals(MessageFormatUtil.Format("Certificate {0} is not yet valid.", intermediateCert - .GetSubjectDN())) && ((CertificateReportItem)l).GetCertificate().Equals(intermediateCert) && l.GetExceptionCause - () is AbstractCertificateNotYetValidException, "Certificate {0} is not yet valid.").DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(1).HasNumberOfLogs(2).HasLogItem((la + ) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (l) => rootCert.GetSubjectDN()).WithCertificate(rootCert)).HasLogItem((la) => la + .WithCheckName(CertificateChainValidator.VALIDITY_CHECK).WithMessage(CertificateChainValidator.NOT_YET_VALID_CERTIFICATE + , (l) => intermediateCert.GetSubjectDN()).WithCertificate(intermediateCert).WithExceptionCauseType(typeof( + AbstractCertificateNotYetValidException)))); } [NUnit.Framework.Test] @@ -255,14 +276,12 @@ public virtual void IntermediateCertIsExpiredTest() { certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); ValidationReport report = validator.ValidateCertificate(baseContext, signingCert, DateTimeUtil.GetCurrentUtcTime ()); - new AssertValidationReport(report).HasStatus(ValidationReport.ValidationResult.INVALID).HasNumberOfFailures - (1).HasNumberOfLogs(2).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), CertificateChainValidator.ISSUER_MISSING - ).HasLogItem((l) => l.GetCheckName().Equals("Certificate validity period check.") && l.GetMessage().Equals - (MessageFormatUtil.Format("Certificate {0} is expired.", intermediateCert.GetSubjectDN())) && ((CertificateReportItem - )l).GetCertificate().Equals(intermediateCert) && l.GetExceptionCause() is AbstractCertificateExpiredException - , CertificateChainValidator.ISSUER_MISSING).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID).HasNumberOfFailures + (1).HasNumberOfLogs(2).HasLogItem((la) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK + ).WithMessage(CertificateChainValidator.CERTIFICATE_TRUSTED, (l) => rootCert.GetSubjectDN()).WithCertificate + (rootCert)).HasLogItem((la) => la.WithCheckName(CertificateChainValidator.VALIDITY_CHECK).WithMessage( + CertificateChainValidator.EXPIRED_CERTIFICATE, (l) => intermediateCert.GetSubjectDN()).WithCertificate + (intermediateCert).WithExceptionCauseType(typeof(AbstractCertificateExpiredException)))); } [NUnit.Framework.Test] @@ -281,25 +300,19 @@ public virtual void CertificateGenerallyTrustedTest() { >()); ValidationReport report1 = validator.ValidateCertificate(baseContext, signingCert, DateTimeUtil.GetCurrentUtcTime ()); - new AssertValidationReport(report1).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report1, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); ValidationReport report2 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .OCSP_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); - new AssertValidationReport(report2).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report2, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); ValidationReport report3 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .TIMESTAMP), signingCert, DateTimeUtil.GetCurrentUtcTime()); - new AssertValidationReport(report3).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report3, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); } [NUnit.Framework.Test] @@ -318,25 +331,19 @@ public virtual void RootCertificateTrustedForCATest() { >()); ValidationReport report1 = validator.ValidateCertificate(baseContext, signingCert, DateTimeUtil.GetCurrentUtcTime ()); - new AssertValidationReport(report1).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report1, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); ValidationReport report2 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .OCSP_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); - new AssertValidationReport(report2).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report2, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); ValidationReport report3 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .TIMESTAMP), signingCert, DateTimeUtil.GetCurrentUtcTime()); - new AssertValidationReport(report3).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report3, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); } [NUnit.Framework.Test] @@ -355,20 +362,17 @@ public virtual void FirstCertificateTrustedForCATest() { ValidationReport report1 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .CERT_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This works fine because certificate in question has CertificateSource.CERT_ISSUER context. - new AssertValidationReport(report1).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, signingCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(signingCert), "Certificate {0} is trusted." - ).DoAssert(); + AssertValidationReport.AssertThat(report1, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => signingCert.GetSubjectDN()).WithCertificate(signingCert))); ValidationReport report2 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .TIMESTAMP), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This doesn't work because certificate in question has CertificateSource.TIMESTAMP context. - new AssertValidationReport(report2).HasStatus(ValidationReport.ValidationResult.INDETERMINATE).HasNumberOfFailures - (1).HasNumberOfLogs(2).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, signingCert.GetSubjectDN(), "certificates generation")), CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil. - Format(CertificateChainValidator.ISSUER_MISSING, intermediateCert.GetSubjectDN())), CertificateChainValidator - .ISSUER_MISSING).DoAssert(); + AssertValidationReport.AssertThat(report2, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasNumberOfLogs(2).HasLogItem((al) => al.WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, (i) => signingCert.GetSubjectDN(), (i) => "certificates generation" + )).HasLogItem((al) => al.WithMessage(CertificateChainValidator.ISSUER_MISSING, (i) => intermediateCert + .GetSubjectDN()))); } [NUnit.Framework.Test] @@ -389,21 +393,18 @@ public virtual void RootCertificateTrustedForOCSPTest() { .OCSP_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This works fine because even though root certificate has CertificateSource.CERT_ISSUER context, // the chain contains initial certificate with CertificateSource.OCSP_ISSUER context. - new AssertValidationReport(report1).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report1, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); ValidationReport report2 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .TIMESTAMP), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This doesn't work because root certificate has CertificateSource.CERT_ISSUER context and // the chain doesn't contain any certificate with CertificateSource.OCSP_ISSUER context. - new AssertValidationReport(report2).HasStatus(ValidationReport.ValidationResult.INDETERMINATE).HasNumberOfFailures - (1).HasNumberOfLogs(2).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, rootCert.GetSubjectDN(), "OCSP response generation")), CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil. - Format(CertificateChainValidator.ISSUER_MISSING, rootCert.GetSubjectDN())), CertificateChainValidator. - ISSUER_MISSING).DoAssert(); + AssertValidationReport.AssertThat(report2, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasNumberOfLogs(2).HasLogItem((l) => l.WithMessage(CertificateChainValidator. + CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, (i) => rootCert.GetSubjectDN(), (i) => "OCSP response generation" + )).HasLogItem((l) => l.WithMessage(CertificateChainValidator.ISSUER_MISSING, (i) => rootCert.GetSubjectDN + ()))); } [NUnit.Framework.Test] @@ -424,21 +425,17 @@ public virtual void RootCertificateTrustedForCRLTest() { .CRL_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This works fine because even though root certificate has CertificateSource.CERT_ISSUER context, // the chain contains initial certificate with CertificateSource.CRL_ISSUER context. - new AssertValidationReport(report1).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report1, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); ValidationReport report2 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .OCSP_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This doesn't work because root certificate has CertificateSource.CERT_ISSUER context and // the chain doesn't contain any certificate with CertificateSource.CRL_ISSUER context. - new AssertValidationReport(report2).HasStatus(ValidationReport.ValidationResult.INDETERMINATE).HasNumberOfFailures - (1).HasNumberOfLogs(2).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, rootCert.GetSubjectDN(), "CRL generation")), CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil. - Format(CertificateChainValidator.ISSUER_MISSING, rootCert.GetSubjectDN())), CertificateChainValidator. - ISSUER_MISSING).DoAssert(); + AssertValidationReport.AssertThat(report2, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasNumberOfLogs(2).HasLogItem((l) => l.WithMessage(CertificateChainValidator. + CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, (i) => rootCert.GetSubjectDN(), (i) => "CRL generation")).HasLogItem + ((l) => l.WithMessage(CertificateChainValidator.ISSUER_MISSING, (i) => rootCert.GetSubjectDN()))); } [NUnit.Framework.Test] @@ -459,31 +456,18 @@ public virtual void RootCertificateTrustedForTimestampTest() { .TIMESTAMP), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This works fine because even though root certificate has CertificateSource.CERT_ISSUER context, // the chain contains initial certificate with CertificateSource.TIMESTAMP context. - new AssertValidationReport(report1).HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures - (0).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName().Equals("Certificate check.") && l.GetMessage - ().Equals(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN - ())) && ((CertificateReportItem)l).GetCertificate().Equals(rootCert), "Certificate {0} is trusted.").DoAssert - (); + AssertValidationReport.AssertThat(report1, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfFailures + (0).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName("Certificate check.").WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); ValidationReport report2 = validator.ValidateCertificate(baseContext.SetCertificateSource(CertificateSource .CRL_ISSUER), signingCert, DateTimeUtil.GetCurrentUtcTime()); // This doesn't work because root certificate has CertificateSource.CERT_ISSUER context and // the chain doesn't contain any certificate with CertificateSource.TIMESTAMP context. - new AssertValidationReport(report2).HasStatus(ValidationReport.ValidationResult.INDETERMINATE).HasNumberOfFailures - (1).HasNumberOfLogs(2).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, rootCert.GetSubjectDN(), "timestamp generation")), CertificateChainValidator - .CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT).HasLogItem((l) => l.GetMessage().Equals(MessageFormatUtil. - Format(CertificateChainValidator.ISSUER_MISSING, rootCert.GetSubjectDN())), CertificateChainValidator. - ISSUER_MISSING).DoAssert(); - } - - private class MockRevocationDataValidator : RevocationDataValidator { - public MockRevocationDataValidator(ValidatorChainBuilder builder) - : base(builder) { - } - - public override void Validate(ValidationReport report, ValidationContext context, IX509Certificate certificate - , DateTime validationDate) { - } + AssertValidationReport.AssertThat(report2, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasNumberOfLogs(2).HasLogItem((l) => l.WithMessage(CertificateChainValidator. + CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT, (i) => rootCert.GetSubjectDN(), (i) => "timestamp generation" + )).HasLogItem((l) => l.WithMessage(CertificateChainValidator.ISSUER_MISSING, (i) => rootCert.GetSubjectDN + ()))); } } } diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockChainValidator.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockChainValidator.cs index 0a96a11eb8..9e2d7c6efb 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockChainValidator.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockChainValidator.cs @@ -27,27 +27,45 @@ You should have received a copy of the GNU Affero General Public License using iText.Signatures.Validation.V1.Report; namespace iText.Signatures.Validation.V1 { - internal class MockChainValidator : CertificateChainValidator { + public class MockChainValidator : CertificateChainValidator { public IList verificationCalls = new List(); + private Action onCallHandler; + internal MockChainValidator() : base(new ValidatorChainBuilder()) { } public override ValidationReport Validate(ValidationReport result, ValidationContext context, IX509Certificate certificate, DateTime verificationDate) { - verificationCalls.Add(new MockChainValidator.ValidationCallBack(certificate, verificationDate)); + MockChainValidator.ValidationCallBack call = new MockChainValidator.ValidationCallBack(certificate, context + , result, verificationDate); + if (onCallHandler != null) { + onCallHandler(call); + } + verificationCalls.Add(call); return result; } - public class ValidationCallBack { - public IX509Certificate certificate; + public virtual void OnCallDo(Action c) { + onCallHandler = c; + } + + public sealed class ValidationCallBack { + public readonly IX509Certificate certificate; + + public readonly ValidationContext context; + + public readonly ValidationReport report; - public DateTime checkDate; + public readonly DateTime checkDate; - public ValidationCallBack(IX509Certificate certificate, DateTime checkDate) { + public ValidationCallBack(IX509Certificate certificate, ValidationContext context, ValidationReport report + , DateTime checkDate) { this.certificate = certificate; + this.context = context; + this.report = report; this.checkDate = checkDate; } } diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs new file mode 100644 index 0000000000..5808b65866 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using iText.Commons.Bouncycastle.Cert; +using iText.Commons.Utils; +using iText.Signatures.Validation.V1.Context; +using iText.Signatures.Validation.V1.Report; + +namespace iText.Signatures.Validation.V1 { + public class MockCrlValidator : CRLValidator { + public readonly IList calls = new List + (); + + private Action onCallHandler; + + /// + /// Creates new + /// + /// instance. + /// + public MockCrlValidator() + : base(new ValidatorChainBuilder()) { + } + + public override void Validate(ValidationReport report, ValidationContext context, IX509Certificate certificate + , IX509Crl crl, DateTime validationDate) { + MockCrlValidator.CRLValidateCall call = new MockCrlValidator.CRLValidateCall(report, context, certificate, + crl, validationDate); + calls.Add(call); + if (onCallHandler != null) { + onCallHandler(calls[calls.Count - 1]); + } + } + + public virtual void OnCallDo(Action c) { + onCallHandler = c; + } + + public sealed class CRLValidateCall { + public readonly DateTime timeStamp = DateTimeUtil.GetCurrentUtcTime(); + + public readonly ValidationReport report; + + public readonly ValidationContext context; + + public readonly IX509Certificate certificate; + + public readonly IX509Crl crl; + + public readonly DateTime validationDate; + + public CRLValidateCall(ValidationReport report, ValidationContext context, IX509Certificate certificate, IX509Crl + crl, DateTime validationDate) { + this.report = report; + this.context = context; + this.certificate = certificate; + this.crl = crl; + this.validationDate = validationDate; + } + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs new file mode 100644 index 0000000000..8eaaedf492 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using iText.Commons.Bouncycastle.Cert; +using iText.Signatures; + +namespace iText.Signatures.Validation.V1 { + public class MockIssuingCertificateRetriever : IssuingCertificateRetriever { + public IList retrieveMissingCertificatesCalls = new List(); + + public IList getCrlIssuerCertificatesCalls = new List(); + + public IList> setTrustedCertificatesCalls = new List>(); + + public IList> addKnownCertificatesCalls = new List>(); + + public IList isCertificateTrustedDoCalls = new List(); + + private Func retrieveMissingCertificatesHandler; + + private Func getCrlIssuerCertificatesHandler; + + private Action> setTrustedCertificatesHandler; + + private Action> addKnownCertificatesHandler; + + private Func isCertificateTrustedDoHandler; + + public override IX509Certificate[] RetrieveMissingCertificates(IX509Certificate[] chain) { + retrieveMissingCertificatesCalls.Add(chain); + if (retrieveMissingCertificatesHandler != null) { + return retrieveMissingCertificatesHandler.Invoke(chain); + } + return new IX509Certificate[0]; + } + + public override IX509Certificate[] GetCrlIssuerCertificates(IX509Crl crl) { + getCrlIssuerCertificatesCalls.Add(crl); + if (getCrlIssuerCertificatesHandler != null) { + return getCrlIssuerCertificatesHandler.Invoke(crl); + } + return new IX509Certificate[0]; + } + + public override void SetTrustedCertificates(ICollection certificates) { + setTrustedCertificatesCalls.Add(certificates); + if (setTrustedCertificatesHandler != null) { + setTrustedCertificatesHandler(certificates); + } + } + + public override void AddKnownCertificates(ICollection certificates) { + addKnownCertificatesCalls.Add(certificates); + if (addKnownCertificatesHandler != null) { + addKnownCertificatesHandler(certificates); + } + } + + public override bool IsCertificateTrusted(IX509Certificate certificate) { + isCertificateTrustedDoCalls.Add(certificate); + if (isCertificateTrustedDoHandler != null) { + return isCertificateTrustedDoHandler.Invoke(certificate); + } + return true; + } + + public virtual MockIssuingCertificateRetriever OnRetrieveMissingCertificatesDo(Func callback) { + retrieveMissingCertificatesHandler = callback; + return this; + } + + public virtual MockIssuingCertificateRetriever OngetCrlIssuerCertificatesDo(Func callback) { + getCrlIssuerCertificatesHandler = callback; + return this; + } + + public virtual MockIssuingCertificateRetriever OnSetTrustedCertificatesDo(Action> callback) { + setTrustedCertificatesHandler = callback; + return this; + } + + public virtual MockIssuingCertificateRetriever OnAddKnownCertificatesDo(Action> callback) { + addKnownCertificatesHandler = callback; + return this; + } + + public virtual MockIssuingCertificateRetriever OnIsCertificateTrustedDo(Func callback + ) { + isCertificateTrustedDoHandler = callback; + return this; + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs new file mode 100644 index 0000000000..a2b2b52f35 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using iText.Commons.Bouncycastle.Asn1.Ocsp; +using iText.Commons.Bouncycastle.Cert; +using iText.Commons.Bouncycastle.Cert.Ocsp; +using iText.Commons.Utils; +using iText.Signatures.Validation.V1.Context; +using iText.Signatures.Validation.V1.Report; + +namespace iText.Signatures.Validation.V1 { + public class MockOCSPValidator : OCSPValidator { + public readonly IList calls = new List(); + + private Action onCallHandler; + + /// + /// Creates new + /// + /// instance. + /// + public MockOCSPValidator() + : base(new ValidatorChainBuilder()) { + } + + public override void Validate(ValidationReport report, ValidationContext context, IX509Certificate certificate + , ISingleResponse singleResp, IBasicOcspResponse ocspResp, DateTime validationDate) { + MockOCSPValidator.OCSPValidatorCall call = new MockOCSPValidator.OCSPValidatorCall(report, context, certificate + , singleResp, ocspResp, validationDate); + calls.Add(call); + if (onCallHandler != null) { + onCallHandler(call); + } + } + + public virtual void OnCallDo(Action c) { + onCallHandler = c; + } + + public sealed class OCSPValidatorCall { + public readonly DateTime timeStamp = DateTimeUtil.GetCurrentUtcTime(); + + public readonly ValidationReport report; + + public readonly ValidationContext context; + + public readonly IX509Certificate certificate; + + public readonly ISingleResponse singleResp; + + public readonly IBasicOcspResponse ocspResp; + + public readonly DateTime validationDate; + + public OCSPValidatorCall(ValidationReport report, ValidationContext context, IX509Certificate certificate, + ISingleResponse singleResp, IBasicOcspResponse ocspResp, DateTime validationDate) { + this.report = report; + this.context = context; + this.certificate = certificate; + this.singleResp = singleResp; + this.ocspResp = ocspResp; + this.validationDate = validationDate; + } + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs new file mode 100644 index 0000000000..38f8e1790d --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using iText.Commons.Bouncycastle.Cert; +using iText.Signatures; +using iText.Signatures.Validation.V1.Context; +using iText.Signatures.Validation.V1.Report; + +namespace iText.Signatures.Validation.V1 { + public class MockRevocationDataValidator : RevocationDataValidator { + public IList crlClientsAdded = new List(); + + public IList ocspClientsAdded = new List(); + + public IList calls = new List(); + + /// + /// Creates new + /// + /// instance to validate certificate revocation data. + /// + internal MockRevocationDataValidator() + : base(new ValidatorChainBuilder()) { + } + + public override RevocationDataValidator AddCrlClient(ICrlClient crlClient) { + crlClientsAdded.Add(crlClient); + return this; + } + + public override RevocationDataValidator AddOcspClient(IOcspClient ocspClient) { + ocspClientsAdded.Add(ocspClient); + return this; + } + + public override void Validate(ValidationReport report, ValidationContext context, IX509Certificate certificate + , DateTime validationDate) { + calls.Add(new MockRevocationDataValidator.RevocationDataValidatorCall(report, context, certificate, validationDate + )); + } + + public sealed class RevocationDataValidatorCall { + public readonly ValidationReport report; + + public readonly ValidationContext context; + + public readonly IX509Certificate certificate; + + public readonly DateTime validationDate; + + public RevocationDataValidatorCall(ValidationReport report, ValidationContext context, IX509Certificate certificate + , DateTime validationDate) { + this.report = report; + this.context = context; + this.certificate = certificate; + this.validationDate = validationDate; + } + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs new file mode 100644 index 0000000000..1e19933be7 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using iText.Signatures.Validation.V1.Context; +using iText.Signatures.Validation.V1.Extensions; + +namespace iText.Signatures.Validation.V1 { + /// This mock class wrapper a real SignatureValidationProperties instance. + /// + /// This mock class wrapper a real SignatureValidationProperties instance. + /// It will track the calls made to it. + /// You can override a response by adding it with the add{someproperty}Response methods. + /// These will be served first, when there are no more responses left, the wrapped properties + /// will be returned. + /// + public class MockSignatureValidationProperties : SignatureValidationProperties { + private readonly SignatureValidationProperties wrappedProperties; + + public IList continueAfterFailureCalls = new List(); + + public IList freshnessCalls = new List(); + + public IList requiredExtensionsCalls = new List(); + + public IList revocationOnlineFetchingCalls = new List(); + + private readonly IList continueAfterFailureResponses = new List(); + + private int continueAfterFailureResponsesIndex = 0; + + private readonly IList freshnessResponses = new List(); + + private int freshnessResponsesIndex = 0; + + private readonly IList> requiredExtensionsResponses = new List>(); + + private int requiredExtensionsResponsesIndex = 0; + + private readonly IList revocationOnlineFetchingResponses = new + List(); + + private int revocationOnlineFetchingResponsesIndex = 0; + + public MockSignatureValidationProperties(SignatureValidationProperties properties) { + this.wrappedProperties = properties; + } + + public override bool GetContinueAfterFailure(ValidationContext validationContext) { + continueAfterFailureCalls.Add(validationContext); + if (continueAfterFailureResponsesIndex < continueAfterFailureResponses.Count) { + return continueAfterFailureResponses[continueAfterFailureResponsesIndex++]; + } + return wrappedProperties.GetContinueAfterFailure(validationContext); + } + + public override TimeSpan GetFreshness(ValidationContext validationContext) { + freshnessCalls.Add(validationContext); + if (freshnessResponsesIndex < freshnessResponses.Count) { + return freshnessResponses[freshnessResponsesIndex++]; + } + return wrappedProperties.GetFreshness(validationContext); + } + + public override IList GetRequiredExtensions(ValidationContext validationContext) { + requiredExtensionsCalls.Add(validationContext); + if (requiredExtensionsResponsesIndex < requiredExtensionsResponses.Count) { + return requiredExtensionsResponses[requiredExtensionsResponsesIndex++]; + } + return wrappedProperties.GetRequiredExtensions(validationContext); + } + + public override SignatureValidationProperties.OnlineFetching GetRevocationOnlineFetching(ValidationContext + validationContext) { + revocationOnlineFetchingCalls.Add(validationContext); + if (revocationOnlineFetchingResponsesIndex < revocationOnlineFetchingResponses.Count) { + return revocationOnlineFetchingResponses[revocationOnlineFetchingResponsesIndex++]; + } + return wrappedProperties.GetRevocationOnlineFetching(validationContext); + } + + public virtual iText.Signatures.Validation.V1.MockSignatureValidationProperties AddContinueAfterFailureResponse + (bool value) { + continueAfterFailureResponses.Add(value); + return this; + } + + public virtual iText.Signatures.Validation.V1.MockSignatureValidationProperties AddFreshnessResponse(TimeSpan + freshness) { + freshnessResponses.Add(freshness); + return this; + } + + public virtual iText.Signatures.Validation.V1.MockSignatureValidationProperties AddRequiredExtensionsResponses + (IList requiredExtensions) { + requiredExtensionsResponses.Add(requiredExtensions); + return this; + } + + public virtual iText.Signatures.Validation.V1.MockSignatureValidationProperties AddRevocationOnlineFetchingResponse + (SignatureValidationProperties.OnlineFetching value) { + revocationOnlineFetchingResponses.Add(value); + return this; + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorIntegrationTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorIntegrationTest.cs new file mode 100644 index 0000000000..be6f527a24 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorIntegrationTest.cs @@ -0,0 +1,267 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +using System; +using iText.Bouncycastleconnector; +using iText.Commons.Bouncycastle; +using iText.Commons.Bouncycastle.Asn1.Ocsp; +using iText.Commons.Bouncycastle.Cert; +using iText.Commons.Bouncycastle.Crypto; +using iText.Commons.Utils; +using iText.Signatures; +using iText.Signatures.Testutils; +using iText.Signatures.Testutils.Builder; +using iText.Signatures.Testutils.Client; +using iText.Signatures.Validation.V1.Context; +using iText.Signatures.Validation.V1.Report; +using iText.Test; + +namespace iText.Signatures.Validation.V1 { + [NUnit.Framework.Category("BouncyCastleUnitTest")] + public class OCSPValidatorIntegrationTest : ExtendedITextTest { + private static readonly String SOURCE_FOLDER = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext + .CurrentContext.TestDirectory) + "/resources/itext/signatures/validation/v1/OCSPValidatorTest/"; + + private static readonly IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.GetFactory(); + + private static readonly char[] PASSWORD = "testpassphrase".ToCharArray(); + + private static IX509Certificate caCert; + + private static IPrivateKey caPrivateKey; + + private static IX509Certificate checkCert; + + private static IX509Certificate responderCert; + + private static IPrivateKey ocspRespPrivateKey; + + private IssuingCertificateRetriever certificateRetriever; + + private SignatureValidationProperties parameters; + + private readonly ValidationContext baseContext = new ValidationContext(ValidatorContext.REVOCATION_DATA_VALIDATOR + , CertificateSource.SIGNER_CERT, TimeBasedContext.PRESENT); + + private ValidatorChainBuilder validatorChainBuilder; + + [NUnit.Framework.OneTimeSetUp] + public static void Before() { + String rootCertFileName = SOURCE_FOLDER + "rootCert.pem"; + String checkCertFileName = SOURCE_FOLDER + "signCert.pem"; + String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCert.pem"; + caCert = (IX509Certificate)PemFileHelper.ReadFirstChain(rootCertFileName)[0]; + caPrivateKey = PemFileHelper.ReadFirstKey(rootCertFileName, PASSWORD); + checkCert = (IX509Certificate)PemFileHelper.ReadFirstChain(checkCertFileName)[0]; + responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName)[0]; + ocspRespPrivateKey = PemFileHelper.ReadFirstKey(ocspResponderCertFileName, PASSWORD); + } + + [NUnit.Framework.SetUp] + public virtual void SetUp() { + certificateRetriever = new IssuingCertificateRetriever(); + parameters = new SignatureValidationProperties(); + validatorChainBuilder = new ValidatorChainBuilder().WithSignatureValidationProperties(parameters).WithIssuingCertificateRetriever + (certificateRetriever); + } + + [NUnit.Framework.Test] + public virtual void ValidateResponderOcspNoCheckTest() { + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + ValidationReport report = ValidateTest(checkDate); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(0).HasNumberOfLogs(2).HasLogItem((al + ) => al.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .TRUSTED_OCSP_RESPONDER)).HasLogItem((al) => al.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK + ).WithMessage(CertificateChainValidator.CERTIFICATE_TRUSTED, (l) => ((CertificateReportItem)l).GetCertificate + ().GetSubjectDN())).HasStatus(ValidationReport.ValidationResult.VALID)); + } + + [NUnit.Framework.Test] + public virtual void ValidateAuthorizedOCSPResponderWithOcspTest() { + ValidationReport report = VerifyResponderWithOcsp(false); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(0).HasNumberOfLogs(2).HasLogItems(2 + , 2, (al) => al.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (l) => ((CertificateReportItem)l).GetCertificate().GetSubjectDN())).HasStatus(ValidationReport.ValidationResult + .VALID)); + } + + [NUnit.Framework.Test] + public virtual void ValidateAuthorizedOCSPResponderWithOcspRevokedTest() { + String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCertForOcspTest.pem"; + IX509Certificate responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName) + [0]; + certificateRetriever.AddKnownCertificates(JavaCollectionsUtil.Singleton(responderCert)); + ValidationReport report = VerifyResponderWithOcsp(true); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((al + ) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator.CERT_IS_REVOKED).WithStatus( + ReportItem.ReportItemStatus.INDETERMINATE))); + } + + [NUnit.Framework.Test] + public virtual void ValidateAuthorizedOCSPResponderFromTheTrustedStoreTest() { + ValidationReport report = ValidateOcspWithoutCertsTest(true); + NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); + NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); + } + + [NUnit.Framework.Test] + public virtual void TrustedOcspResponderDoesNotHaveOcspSigningExtensionTest() { + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(caCert, caPrivateKey); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse caBasicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient + .GetEncoded(checkCert, caCert, null))); + ValidationReport report = new ValidationReport(); + // Configure OCSP signing authority for the certificate in question + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); + validator.Validate(report, baseContext, checkCert, caBasicOCSPResp.GetResponses()[0], caBasicOCSPResp, TimeTestUtil + .TEST_DATE_TIME); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(0).HasStatus(ValidationReport.ValidationResult + .VALID)); + } + + [NUnit.Framework.Test] + public virtual void AuthorizedOcspResponderDoesNotHaveOcspSigningExtensionTest() { + String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCertWithoutOcspSigning.pem"; + IX509Certificate responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName) + [0]; + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder.SetThisUpdate(DateTimeUtil.GetCalendar(TimeTestUtil.TEST_DATE_TIME.AddDays(1))); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. + GetEncoded(checkCert, caCert, null))); + ValidationReport report = new ValidationReport(); + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil + .TEST_DATE_TIME); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(1).HasLogItem((al) => al.WithCheckName + (CertificateChainValidator.EXTENSIONS_CHECK).WithMessage(CertificateChainValidator.EXTENSION_MISSING, + (l) => OID.X509Extensions.EXTENDED_KEY_USAGE)).HasStatus(ValidationReport.ValidationResult.INDETERMINATE + )); + } + + private ValidationReport ValidateTest(DateTime checkDate) { + return ValidateTest(checkDate, checkDate.AddDays(1), 0); + } + + private ValidationReport ValidateTest(DateTime checkDate, DateTime thisUpdate, long freshness) { + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder.SetThisUpdate(DateTimeUtil.GetCalendar(thisUpdate)); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. + GetEncoded(checkCert, caCert, null))); + ValidationReport report = new ValidationReport(); + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); + parameters.SetFreshness(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays + (freshness)); + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, checkDate + ); + return report; + } + + private ValidationReport ValidateRevokedTest(DateTime checkDate, DateTime revocationDate) { + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder.SetCertificateStatus(FACTORY.CreateRevokedStatus(revocationDate, FACTORY.CreateCRLReason().GetKeyCompromise + ())); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. + GetEncoded(checkCert, caCert, null))); + ValidationReport report = new ValidationReport(); + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, checkDate + ); + return report; + } + + private ValidationReport ValidateOcspWithoutCertsTest(bool addResponderToTrusted) { + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder.SetOcspCertsChain(new IX509Certificate[0]); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. + GetEncoded(checkCert, caCert, null))); + ValidationReport report = new ValidationReport(); + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + if (addResponderToTrusted) { + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(responderCert)); + } + OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil + .TEST_DATE_TIME); + return report; + } + + private ValidationReport VerifyResponderWithOcsp(bool revokedOcsp) { + String rootCertFileName = SOURCE_FOLDER + "rootCertForOcspTest.pem"; + String checkCertFileName = SOURCE_FOLDER + "signCertForOcspTest.pem"; + String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCertForOcspTest.pem"; + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + IX509Certificate caCert = (IX509Certificate)PemFileHelper.ReadFirstChain(rootCertFileName)[0]; + IPrivateKey caPrivateKey = PemFileHelper.ReadFirstKey(rootCertFileName, PASSWORD); + IX509Certificate checkCert = (IX509Certificate)PemFileHelper.ReadFirstChain(checkCertFileName)[0]; + IX509Certificate responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName) + [0]; + IPrivateKey ocspRespPrivateKey = PemFileHelper.ReadFirstKey(ocspResponderCertFileName, PASSWORD); + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(-5))); + builder.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(5))); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. + GetEncoded(checkCert, caCert, null))); + ValidationReport report = new ValidationReport(); + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + TestOcspResponseBuilder builder2 = revokedOcsp ? new TestOcspResponseBuilder(caCert, caPrivateKey, FACTORY + .CreateRevokedStatus(TimeTestUtil.TEST_DATE_TIME.AddDays(-5), FACTORY.CreateCRLReason().GetKeyCompromise + ())) : new TestOcspResponseBuilder(caCert, caPrivateKey); + builder2.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(20))); + builder2.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(30))); + TestOcspClient ocspClient2 = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder2); + parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts + .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() + , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(5)); + if (revokedOcsp) { + parameters.SetContinueAfterFailure(ValidatorContexts.All(), CertificateSources.All(), false); + } + validatorChainBuilder.GetRevocationDataValidator().AddOcspClient(ocspClient); + validatorChainBuilder.GetRevocationDataValidator().AddOcspClient(ocspClient2); + OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, checkDate + ); + return report; + } + + private class TestIssuingCertificateRetriever : IssuingCertificateRetriever { + internal IX509Certificate issuerCertificate; + + public TestIssuingCertificateRetriever(String issuerPath) + : base() { + this.issuerCertificate = PemFileHelper.ReadFirstChain(issuerPath)[0]; + } + + public override IX509Certificate RetrieveIssuerCertificate(IX509Certificate certificate) { + return issuerCertificate; + } + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorTest.cs index a08fd2a861..c3326764db 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/OCSPValidatorTest.cs @@ -56,15 +56,17 @@ public class OCSPValidatorTest : ExtendedITextTest { private static IPrivateKey ocspRespPrivateKey; + private readonly ValidationContext baseContext = new ValidationContext(ValidatorContext.REVOCATION_DATA_VALIDATOR + , CertificateSource.SIGNER_CERT, TimeBasedContext.PRESENT); + private IssuingCertificateRetriever certificateRetriever; private SignatureValidationProperties parameters; - private readonly ValidationContext baseContext = new ValidationContext(ValidatorContext.REVOCATION_DATA_VALIDATOR - , CertificateSource.SIGNER_CERT, TimeBasedContext.PRESENT); - private ValidatorChainBuilder validatorChainBuilder; + private MockChainValidator mockCertificateChainValidator; + [NUnit.Framework.OneTimeSetUp] public static void Before() { String rootCertFileName = SOURCE_FOLDER + "rootCert.pem"; @@ -81,79 +83,49 @@ public static void Before() { public virtual void SetUp() { certificateRetriever = new IssuingCertificateRetriever(); parameters = new SignatureValidationProperties(); + mockCertificateChainValidator = new MockChainValidator(); validatorChainBuilder = new ValidatorChainBuilder().WithSignatureValidationProperties(parameters).WithIssuingCertificateRetriever - (certificateRetriever); + (certificateRetriever).WithCertificateChainValidator(mockCertificateChainValidator); } [NUnit.Framework.Test] - public virtual void ValidateResponderOcspNoCheckTest() { + public virtual void HappyPathTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; ValidationReport report = ValidateTest(checkDate); - new AssertValidationReport(report).HasNumberOfFailures(0).HasNumberOfLogs(2).HasLogItem((l) => l.GetCheckName - ().Equals(RevocationDataValidator.REVOCATION_DATA_CHECK) && l.GetMessage().Equals(RevocationDataValidator - .TRUSTED_OCSP_RESPONDER), "Revocation data check with trusted responder").HasLogItem((l) => l.GetCheckName - ().Equals(CertificateChainValidator.CERTIFICATE_CHECK) && l.GetMessage().Equals(MessageFormatUtil.Format - (CertificateChainValidator.CERTIFICATE_TRUSTED, ((CertificateReportItem)l).GetCertificate().GetSubjectDN - ())), "ChainValidator certificate trusted").HasStatus(ValidationReport.ValidationResult.VALID).DoAssert - (); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID)); } [NUnit.Framework.Test] - public virtual void ValidateAuthorizedOCSPResponderWithOcspTest() { - ValidationReport report = VerifyResponderWithOcsp(false); - new AssertValidationReport(report).HasNumberOfFailures(0).HasNumberOfLogs(2).HasLogItems((l) => l.GetCheckName - ().Equals(CertificateChainValidator.CERTIFICATE_CHECK) && l.GetMessage().Equals(MessageFormatUtil.Format - (CertificateChainValidator.CERTIFICATE_TRUSTED, ((CertificateReportItem)l).GetCertificate().GetSubjectDN - ())), 2, "Certificate check with trusted certificate").HasStatus(ValidationReport.ValidationResult.VALID - ).DoAssert(); - } - - [NUnit.Framework.Test] - public virtual void ValidateAuthorizedOCSPResponderWithOcspRevokedTest() { - String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCertForOcspTest.pem"; - IX509Certificate responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName) - [0]; - certificateRetriever.AddKnownCertificates(JavaCollectionsUtil.Singleton(responderCert)); - ValidationReport report = VerifyResponderWithOcsp(true); - new AssertValidationReport(report).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName - ().Equals(OCSPValidator.OCSP_CHECK) && l.GetMessage().Equals(OCSPValidator.CERT_IS_REVOKED) && l.GetStatus - ().Equals(ReportItem.ReportItemStatus.INDETERMINATE), "Certificate revoked").DoAssert(); - } - - [NUnit.Framework.Test] - public virtual void ValidateAuthorizedOCSPResponderFromTheTrustedStoreTest() { - ValidationReport report = ValidateOcspWithoutCertsTest(true); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); + public virtual void OcpsIssuerChainValidationsUsesCorrectParametersTest() { + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + ValidationReport report = ValidateTest(checkDate); + NUnit.Framework.Assert.AreEqual(1, mockCertificateChainValidator.verificationCalls.Count); + NUnit.Framework.Assert.AreEqual(responderCert, mockCertificateChainValidator.verificationCalls[0].certificate + ); + NUnit.Framework.Assert.AreEqual(ValidatorContext.OCSP_VALIDATOR, mockCertificateChainValidator.verificationCalls + [0].context.GetValidatorContext()); + NUnit.Framework.Assert.AreEqual(CertificateSource.OCSP_ISSUER, mockCertificateChainValidator.verificationCalls + [0].context.GetCertificateSource()); + NUnit.Framework.Assert.AreEqual(checkDate, mockCertificateChainValidator.verificationCalls[0].checkDate); + NUnit.Framework.Assert.AreEqual(checkDate.AddDays(0), mockCertificateChainValidator.verificationCalls[0].checkDate + ); } [NUnit.Framework.Test] - public virtual void NoResponderFoundInCertsTest() { + public virtual void OcspForSelfSignedCertShouldNotValdateFurtherTest() { TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); - builder.SetOcspCertsChain(new IX509Certificate[] { caCert }); TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); - IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. - GetEncoded(checkCert, caCert, null))); + IBasicOcspResponse caBasicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient + .GetEncoded(caCert, caCert, null))); ValidationReport report = new ValidationReport(); certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); - validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil + validator.Validate(report, baseContext, caCert, caBasicOCSPResp.GetResponses()[0], caBasicOCSPResp, TimeTestUtil .TEST_DATE_TIME); - new AssertValidationReport(report).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName - ().Equals(OCSPValidator.OCSP_CHECK) && l.GetMessage().Equals(OCSPValidator.OCSP_COULD_NOT_BE_VERIFIED) - , "OCSP responder not found").HasStatus(ValidationReport.ValidationResult.INDETERMINATE).DoAssert(); - } - - [NUnit.Framework.Test] - public virtual void NoResponderFoundTest() { - ValidationReport report = ValidateOcspWithoutCertsTest(false); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_COULD_NOT_BE_VERIFIED, item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasNumberOfLogs + (1).HasLogItem((al) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(RevocationDataValidator. + SELF_SIGNED_CERTIFICATE).WithCertificate(caCert))); + NUnit.Framework.Assert.AreEqual(0, mockCertificateChainValidator.verificationCalls.Count); } [NUnit.Framework.Test] @@ -162,124 +134,117 @@ public virtual void ValidationDateAfterNextUpdateTest() { DateTime nextUpdate = TimeTestUtil.TEST_DATE_TIME.AddDays(30); DateTime checkDate = TimeTestUtil.TEST_DATE_TIME.AddDays(45); ValidationReport report = ValidateTest(checkDate, TimeTestUtil.TEST_DATE_TIME, 50); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(OCSPValidator.OCSP_IS_NO_LONGER_VALID, checkDate, - nextUpdate), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - } - - [NUnit.Framework.Test] - public virtual void CertificateWasRevokedAfterCheckDateTest() { - DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - DateTime revocationDate = TimeTestUtil.TEST_DATE_TIME.AddDays(10); - ValidationReport report = ValidateRevokedTest(checkDate, revocationDate); - new AssertValidationReport(report).HasNumberOfFailures(0).HasNumberOfLogs(3).HasLogItem((l) => l.GetCheckName - ().Equals(OCSPValidator.OCSP_CHECK) && l.GetMessage().Equals(MessageFormatUtil.Format(SignLogMessageConstant - .VALID_CERTIFICATE_IS_REVOKED, revocationDate)), "valid certificate is revoked").HasStatus(ValidationReport.ValidationResult - .VALID).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((al) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator.OCSP_IS_NO_LONGER_VALID + , (l) => checkDate, (l) => nextUpdate))); } [NUnit.Framework.Test] - public virtual void CertificateWasRevokedBeforeCheckDateTest() { + public virtual void SerialNumbersDoNotMatchTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - DateTime revocationDate = TimeTestUtil.TEST_DATE_TIME.AddDays(-1); - ValidationReport report = ValidateRevokedTest(checkDate, revocationDate); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem ocspCheckItem = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, ocspCheckItem.GetCheckName()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.CERT_IS_REVOKED, ocspCheckItem.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(1))); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse caBasicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient + .GetEncoded(caCert, caCert, null))); + ValidationReport report = new ValidationReport(); + certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); + validator.Validate(report, baseContext, checkCert, caBasicOCSPResp.GetResponses()[0], caBasicOCSPResp, checkDate + ); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfLogs(1).HasStatus(ValidationReport.ValidationResult + .INDETERMINATE).HasLogItem((al) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator + .SERIAL_NUMBERS_DO_NOT_MATCH).WithCertificate(checkCert))); + NUnit.Framework.Assert.AreEqual(0, mockCertificateChainValidator.verificationCalls.Count); } [NUnit.Framework.Test] - public virtual void CertificateStatusIsUnknownTest() { - DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + public virtual void IssuersDoNotMatchTest() { + String wrongRootCertFileName = SOURCE_FOLDER + "rootCertForOcspTest.pem"; TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); - builder.SetCertificateStatus(FACTORY.CreateUnknownStatus()); TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. GetEncoded(checkCert, caCert, null))); ValidationReport report = new ValidationReport(); - certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + validatorChainBuilder.WithIssuingCertificateRetriever(new OCSPValidatorTest.TestIssuingCertificateRetriever + (wrongRootCertFileName)); OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); - validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, checkDate - ); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem ocspCheckItem = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, ocspCheckItem.GetCheckName()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.CERT_STATUS_IS_UNKNOWN, ocspCheckItem.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil + .TEST_DATE_TIME); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((la + ) => la.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator.ISSUERS_DO_NOT_MATCH).WithStatus + (ReportItem.ReportItemStatus.INDETERMINATE))); } [NUnit.Framework.Test] - public virtual void SerialNumbersDoesNotMatchTest() { + public virtual void PositiveFreshnessNegativeTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); - builder.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(1))); - TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); - IBasicOcspResponse caBasicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient - .GetEncoded(caCert, caCert, null))); + DateTime thisUpdate = checkDate.AddDays(-3); + ValidationReport report = ValidateTest(checkDate, thisUpdate, 2); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasLogItem((al) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator + .FRESHNESS_CHECK, (l) => thisUpdate, (l) => checkDate, (l) => TimeSpan.FromDays(2)))); + } + + [NUnit.Framework.Test] + public virtual void NextUpdateNotSetResultsInValidStatusTest() { + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + TestOcspResponseBuilder builder = new TestOcspResponseBuilder(caCert, caPrivateKey); + builder.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(-20))); + builder.SetNextUpdate(DateTimeUtil.GetCalendar((DateTime)TimestampConstants.UNDEFINED_TIMESTAMP_DATE)); + builder.SetProducedAt(TimeTestUtil.TEST_DATE_TIME.AddDays(-20)); + TestOcspClient client = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(client.GetEncoded + (checkCert, caCert, ""))); + certificateRetriever.AddKnownCertificates(JavaCollectionsUtil.Singleton(caCert)); ValidationReport report = new ValidationReport(); - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); - validator.Validate(report, baseContext, checkCert, caBasicOCSPResp.GetResponses()[0], caBasicOCSPResp, checkDate + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, checkDate ); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.SERIAL_NUMBERS_DO_NOT_MATCH, item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID)); } [NUnit.Framework.Test] - public virtual void OcspForSelfSignedCertTest() { - TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); - TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); - IBasicOcspResponse caBasicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient - .GetEncoded(caCert, caCert, null))); - ValidationReport report = new ValidationReport(); - certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); - OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); - validator.Validate(report, baseContext, caCert, caBasicOCSPResp.GetResponses()[0], caBasicOCSPResp, TimeTestUtil - .TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.SELF_SIGNED_CERTIFICATE, item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); + public virtual void CertificateWasRevokedBeforeCheckDateShouldFailTest() { + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + DateTime revocationDate = TimeTestUtil.TEST_DATE_TIME.AddDays(-1); + ValidationReport report = ValidateRevokedTestMocked(checkDate, revocationDate); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID).HasLogItem + ((al) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator.CERT_IS_REVOKED).WithCertificate + (checkCert))); } [NUnit.Framework.Test] - public virtual void IssuersDoesNotMatchTest() { - String wrongRootCertFileName = SOURCE_FOLDER + "rootCertForOcspTest.pem"; + public virtual void CertificateWasRevokedAfterCheckDateShouldSucceedTest() { + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + DateTime revocationDate = TimeTestUtil.TEST_DATE_TIME.AddDays(10); + ValidationReport report = ValidateRevokedTestMocked(checkDate, revocationDate); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((la) => la.WithCheckName(OCSPValidator.OCSP_CHECK + ).WithMessage(SignLogMessageConstant.VALID_CERTIFICATE_IS_REVOKED, (l) => revocationDate)).HasStatus(ValidationReport.ValidationResult + .VALID)); + } + + [NUnit.Framework.Test] + public virtual void CertificateStatusIsUnknownTest() { + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder.SetCertificateStatus(FACTORY.CreateUnknownStatus()); TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. GetEncoded(checkCert, caCert, null))); ValidationReport report = new ValidationReport(); - validatorChainBuilder.WithIssuingCertificateRetriever(new OCSPValidatorTest.TestIssuingCertificateRetriever - (wrongRootCertFileName)); + certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); - validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil - .TEST_DATE_TIME); - new AssertValidationReport(report).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.GetCheckName - ().Equals(OCSPValidator.OCSP_CHECK) && l.GetMessage().Equals(OCSPValidator.ISSUERS_DO_NOT_MATCH) && l. - GetStatus().Equals(ReportItem.ReportItemStatus.INDETERMINATE), OCSPValidator.ISSUERS_DO_NOT_MATCH).DoAssert - (); + validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, checkDate + ); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((al) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator.CERT_STATUS_IS_UNKNOWN + ).WithCertificate(checkCert))); + NUnit.Framework.Assert.AreEqual(0, mockCertificateChainValidator.verificationCalls.Count); } [NUnit.Framework.Test] - public virtual void CertificateDoesNotVerifyWithSuppliedKeyTest() { + public virtual void OcspIssuerCertificateDoesNotVerifyWithCaPKTest() { String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCertForOcspTest.pem"; IX509Certificate responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName) [0]; @@ -293,37 +258,19 @@ public virtual void CertificateDoesNotVerifyWithSuppliedKeyTest() { OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil .TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem ocspCheckItem = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, ocspCheckItem.GetCheckName()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.INVALID_OCSP, ocspCheckItem.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(1).HasStatus(ValidationReport.ValidationResult + .INVALID).HasLogItem((al) => al.WithCheckName(OCSPValidator.OCSP_CHECK).WithMessage(OCSPValidator.INVALID_OCSP + ) + // This should be the checked certificate, not the ocsp responder + + //.withCertificate(checkCert) + .WithCertificate(responderCert))); } [NUnit.Framework.Test] - public virtual void TrustedOcspResponderDoesNotHaveOcspSigningExtensionTest() { - TestOcspResponseBuilder builder = new TestOcspResponseBuilder(caCert, caPrivateKey); - TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); - IBasicOcspResponse caBasicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient - .GetEncoded(checkCert, caCert, null))); - ValidationReport report = new ValidationReport(); - // Configure OCSP signing authority for the certificate in question - certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); - OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); - validator.Validate(report, baseContext, checkCert, caBasicOCSPResp.GetResponses()[0], caBasicOCSPResp, TimeTestUtil - .TEST_DATE_TIME); - new AssertValidationReport(report).HasNumberOfFailures(0).HasStatus(ValidationReport.ValidationResult.VALID - ).DoAssert(); - } - - [NUnit.Framework.Test] - public virtual void AuthorizedOcspResponderDoesNotHaveOcspSigningExtensionTest() { - String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCertWithoutOcspSigning.pem"; - IX509Certificate responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName) - [0]; + public virtual void NoResponderFoundInCertsTest() { TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); - builder.SetThisUpdate(DateTimeUtil.GetCalendar(TimeTestUtil.TEST_DATE_TIME.AddDays(1))); + builder.SetOcspCertsChain(new IX509Certificate[] { caCert }); TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); IBasicOcspResponse basicOCSPResp = FACTORY.CreateBasicOCSPResponse(FACTORY.CreateASN1Primitive(ocspClient. GetEncoded(checkCert, caCert, null))); @@ -332,56 +279,25 @@ public virtual void AuthorizedOcspResponderDoesNotHaveOcspSigningExtensionTest() OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil .TEST_DATE_TIME); - new AssertValidationReport(report).HasNumberOfFailures(1).HasLogItem((l) => l.GetCheckName().Equals(CertificateChainValidator - .EXTENSIONS_CHECK) && l.GetMessage().Equals(MessageFormatUtil.Format(CertificateChainValidator.EXTENSION_MISSING - , OID.X509Extensions.EXTENDED_KEY_USAGE)), "OCSP_SIGNING extended key usage is missing").HasStatus(ValidationReport.ValidationResult - .INDETERMINATE).DoAssert(); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((la) => la.WithCheckName(OCSPValidator.OCSP_CHECK + ).WithMessage(OCSPValidator.OCSP_COULD_NOT_BE_VERIFIED)).HasStatus(ValidationReport.ValidationResult.INDETERMINATE + )); } [NUnit.Framework.Test] - public virtual void PositiveFreshnessPositiveTest() { + public virtual void ChainValidatorReportWrappingTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - ValidationReport report = ValidateTest(checkDate, checkDate.AddDays(-3), 5); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - } - - [NUnit.Framework.Test] - public virtual void PositiveFreshnessNegativeTest() { - DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - DateTime thisUpdate = checkDate.AddDays(-3); - ValidationReport report = ValidateTest(checkDate, thisUpdate, 2); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(OCSPValidator.FRESHNESS_CHECK, thisUpdate, checkDate - , TimeSpan.FromDays(2)), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - } - - [NUnit.Framework.Test] - public virtual void NegativeFreshnessPositiveTest() { - DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - ValidationReport report = ValidateTest(checkDate, checkDate.AddDays(5), -3); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - } - - [NUnit.Framework.Test] - public virtual void NegativeFreshnessNegativeTest() { - DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - DateTime thisUpdate = checkDate.AddDays(2); - ValidationReport report = ValidateTest(checkDate, thisUpdate, -3); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(OCSPValidator.FRESHNESS_CHECK, thisUpdate, checkDate - , TimeSpan.FromDays(-3)), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); + mockCertificateChainValidator.OnCallDo((c) => { + c.report.AddReportItem(new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO)); + c.report.AddReportItem(new ReportItem("test2", "test2", ReportItem.ReportItemStatus.INDETERMINATE)); + c.report.AddReportItem(new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INVALID)); + } + ); + ValidationReport report = ValidateTest(checkDate); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItems(0, 0, (la) => la.WithStatus(ReportItem.ReportItemStatus.INVALID)).HasLogItems(2, 2, (la) => + la.WithStatus(ReportItem.ReportItemStatus.INDETERMINATE)).HasLogItem((la) => la.WithStatus(ReportItem.ReportItemStatus + .INFO))); } private ValidationReport ValidateTest(DateTime checkDate) { @@ -404,7 +320,7 @@ private ValidationReport ValidateTest(DateTime checkDate, DateTime thisUpdate, l return report; } - private ValidationReport ValidateRevokedTest(DateTime checkDate, DateTime revocationDate) { + private ValidationReport ValidateRevokedTestMocked(DateTime checkDate, DateTime revocationDate) { TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); builder.SetCertificateStatus(FACTORY.CreateRevokedStatus(revocationDate, FACTORY.CreateCRLReason().GetKeyCompromise ())); @@ -419,7 +335,7 @@ private ValidationReport ValidateRevokedTest(DateTime checkDate, DateTime revoca return report; } - private ValidationReport ValidateOcspWithoutCertsTest(bool addResponderToTrusted) { + private ValidationReport ValidateOcspWithoutCertsTestMocked(bool addResponderToTrusted) { TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); builder.SetOcspCertsChain(new IX509Certificate[0]); TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); @@ -436,7 +352,7 @@ private ValidationReport ValidateOcspWithoutCertsTest(bool addResponderToTrusted return report; } - private ValidationReport VerifyResponderWithOcsp(bool revokedOcsp) { + private ValidationReport VerifyResponderWithOcspMocked(bool revokedOcsp) { String rootCertFileName = SOURCE_FOLDER + "rootCertForOcspTest.pem"; String checkCertFileName = SOURCE_FOLDER + "signCertForOcspTest.pem"; String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCertForOcspTest.pem"; @@ -467,8 +383,6 @@ private ValidationReport VerifyResponderWithOcsp(bool revokedOcsp) { if (revokedOcsp) { parameters.SetContinueAfterFailure(ValidatorContexts.All(), CertificateSources.All(), false); } - validatorChainBuilder.GetRevocationDataValidator().AddOcspClient(ocspClient); - validatorChainBuilder.GetRevocationDataValidator().AddOcspClient(ocspClient2); OCSPValidator validator = validatorChainBuilder.BuildOCSPValidator(); validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, checkDate ); diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs new file mode 100644 index 0000000000..5964ffc248 --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs @@ -0,0 +1,89 @@ +using System; +using iText.Bouncycastleconnector; +using iText.Commons.Bouncycastle; +using iText.Commons.Bouncycastle.Cert; +using iText.Commons.Bouncycastle.Crypto; +using iText.Commons.Utils; +using iText.Signatures; +using iText.Signatures.Testutils; +using iText.Signatures.Testutils.Builder; +using iText.Signatures.Testutils.Client; +using iText.Signatures.Validation.V1.Context; +using iText.Signatures.Validation.V1.Report; +using iText.Test; + +namespace iText.Signatures.Validation.V1 { + [NUnit.Framework.Category("BouncyCastleUnitTest")] + public class RevocationDataValidatorIntegrationTest : ExtendedITextTest { + private static readonly IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.GetFactory(); + + private static readonly String SOURCE_FOLDER = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext + .CurrentContext.TestDirectory) + "/resources/itext/signatures/validation/v1/RevocationDataValidatorTest/"; + + private static readonly char[] PASSWORD = "testpassphrase".ToCharArray(); + + private static IX509Certificate caCert; + + private static IPrivateKey caPrivateKey; + + private static IX509Certificate checkCert; + + private static IX509Certificate responderCert; + + private static IPrivateKey ocspRespPrivateKey; + + private IssuingCertificateRetriever certificateRetriever; + + private SignatureValidationProperties parameters; + + private ValidatorChainBuilder validatorChainBuilder; + + private ValidationContext baseContext = new ValidationContext(ValidatorContext.SIGNATURE_VALIDATOR, CertificateSource + .SIGNER_CERT, TimeBasedContext.PRESENT); + + [NUnit.Framework.OneTimeSetUp] + public static void Before() { + String rootCertFileName = SOURCE_FOLDER + "rootCert.pem"; + String checkCertFileName = SOURCE_FOLDER + "signCert.pem"; + String ocspResponderCertFileName = SOURCE_FOLDER + "ocspResponderCert.pem"; + caCert = (IX509Certificate)PemFileHelper.ReadFirstChain(rootCertFileName)[0]; + caPrivateKey = PemFileHelper.ReadFirstKey(rootCertFileName, PASSWORD); + checkCert = (IX509Certificate)PemFileHelper.ReadFirstChain(checkCertFileName)[0]; + responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName)[0]; + ocspRespPrivateKey = PemFileHelper.ReadFirstKey(ocspResponderCertFileName, PASSWORD); + } + + [NUnit.Framework.SetUp] + public virtual void SetUp() { + certificateRetriever = new IssuingCertificateRetriever(); + parameters = new SignatureValidationProperties(); + validatorChainBuilder = new ValidatorChainBuilder().WithIssuingCertificateRetriever(certificateRetriever). + WithSignatureValidationProperties(parameters); + } + + [NUnit.Framework.Test] + public virtual void CrlWithOnlySomeReasonsTest() { + TestCrlBuilder builder1 = new TestCrlBuilder(caCert, caPrivateKey); + builder1.AddExtension(FACTORY.CreateExtensions().GetIssuingDistributionPoint(), true, FACTORY.CreateIssuingDistributionPoint + (null, false, false, FACTORY.CreateReasonFlags(CRLValidator.ALL_REASONS - 31), false, false)); + TestCrlBuilder builder2 = new TestCrlBuilder(caCert, caPrivateKey); + builder2.AddExtension(FACTORY.CreateExtensions().GetIssuingDistributionPoint(), true, FACTORY.CreateIssuingDistributionPoint + (null, false, false, FACTORY.CreateReasonFlags(31), false, false)); + TestCrlClient crlClient = new TestCrlClient().AddBuilderForCertIssuer(builder1).AddBuilderForCertIssuer(builder2 + ); + TestOcspResponseBuilder ocspBuilder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + ocspBuilder.SetProducedAt(TimeTestUtil.TEST_DATE_TIME.AddDays(-100)); + certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); + parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts + .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH); + ValidationReport report = new ValidationReport(); + RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); + validator.AddOcspClient(new TestOcspClient().AddBuilderForCertIssuer(caCert, ocspBuilder)).AddCrlClient(crlClient + ); + validator.Validate(report, baseContext, checkCert, TimeTestUtil.TEST_DATE_TIME); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(0).HasLogItem((la) => la.WithCertificate + (checkCert).WithStatus(ReportItem.ReportItemStatus.INFO).WithMessage(CRLValidator.ONLY_SOME_REASONS_CHECKED + ))); + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorTest.cs index 098ae8ff74..68f70ea2a6 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorTest.cs @@ -22,6 +22,7 @@ You should have received a copy of the GNU Affero General Public License */ using System; using System.Collections.Generic; +using System.Threading; using iText.Bouncycastleconnector; using iText.Commons.Bouncycastle; using iText.Commons.Bouncycastle.Cert; @@ -45,8 +46,6 @@ public class RevocationDataValidatorTest : ExtendedITextTest { private static readonly char[] PASSWORD = "testpassphrase".ToCharArray(); - private const long MILLISECONDS_PER_DAY = 86_400_000L; - private static IX509Certificate caCert; private static IPrivateKey caPrivateKey; @@ -57,6 +56,8 @@ public class RevocationDataValidatorTest : ExtendedITextTest { private static IPrivateKey ocspRespPrivateKey; + private static IX509Certificate trustedOcspResponderCert; + private IssuingCertificateRetriever certificateRetriever; private SignatureValidationProperties parameters; @@ -66,6 +67,12 @@ public class RevocationDataValidatorTest : ExtendedITextTest { private ValidatorChainBuilder validatorChainBuilder; + private MockCrlValidator mockCrlValidator; + + private MockOCSPValidator mockOCSPValidator; + + private MockSignatureValidationProperties mockParameters; + [NUnit.Framework.OneTimeSetUp] public static void Before() { String rootCertFileName = SOURCE_FOLDER + "rootCert.pem"; @@ -76,161 +83,154 @@ public static void Before() { checkCert = (IX509Certificate)PemFileHelper.ReadFirstChain(checkCertFileName)[0]; responderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName)[0]; ocspRespPrivateKey = PemFileHelper.ReadFirstKey(ocspResponderCertFileName, PASSWORD); + trustedOcspResponderCert = (IX509Certificate)PemFileHelper.ReadFirstChain(ocspResponderCertFileName)[0]; } [NUnit.Framework.SetUp] public virtual void SetUp() { certificateRetriever = new IssuingCertificateRetriever(); parameters = new SignatureValidationProperties(); + mockCrlValidator = new MockCrlValidator(); + mockOCSPValidator = new MockOCSPValidator(); + mockParameters = new MockSignatureValidationProperties(parameters); validatorChainBuilder = new ValidatorChainBuilder().WithIssuingCertificateRetriever(certificateRetriever). - WithSignatureValidationProperties(parameters); + WithSignatureValidationProperties(mockParameters).WithCRLValidator(mockCrlValidator).WithOCSPValidator + (mockOCSPValidator); } [NUnit.Framework.Test] - public virtual void BasicValidationWithOcspClientTest() { + public virtual void BasicOCSPValidatorUsageTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; TestOcspResponseBuilder builder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); builder.SetProducedAt(checkDate.AddDays(5)); builder.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(5))); builder.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(10))); - TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder); + TestOcspClientWrapper ocspClient = new TestOcspClientWrapper(new TestOcspClient().AddBuilderForCertIssuer( + caCert, builder)); ValidationReport report = new ValidationReport(); certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() - , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); + mockParameters.AddRevocationOnlineFetchingResponse(SignatureValidationProperties.OnlineFetching.NEVER_FETCH + ); + mockParameters.AddRevocationOnlineFetchingResponse(SignatureValidationProperties.OnlineFetching.NEVER_FETCH + ); + mockParameters.AddFreshnessResponse(TimeSpan.FromDays(-2)); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); validator.AddOcspClient(ocspClient); + ReportItem reportItem = new ReportItem("validator", "message", ReportItem.ReportItemStatus.INFO); + mockOCSPValidator.OnCallDo((c) => c.report.AddReportItem(reportItem)); validator.Validate(report, baseContext, checkCert, checkDate); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(2, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.TRUSTED_OCSP_RESPONDER, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, item - .GetCertificate().GetSubjectDN()), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID) + // the logitem from the OCSP valdiation should be copied to the final report + .HasNumberOfLogs(1).HasLogItem(reportItem)); + // there should be one call per ocspClient + NUnit.Framework.Assert.AreEqual(1, ocspClient.GetCalls().Count); + // There was only one ocsp response so we expect 1 call to the ocsp validator + NUnit.Framework.Assert.AreEqual(1, mockOCSPValidator.calls.Count); + // the validationDate should be passed as is + NUnit.Framework.Assert.AreEqual(checkDate, mockOCSPValidator.calls[0].validationDate); + // the response should be passed as is + NUnit.Framework.Assert.AreEqual(ocspClient.GetCalls()[0].response, mockOCSPValidator.calls[0].ocspResp); + // There should be a new report generated and any logs must be copied the actual report. + NUnit.Framework.Assert.AreNotEqual(report, mockOCSPValidator.calls[0].report); } [NUnit.Framework.Test] - public virtual void BasicValidationWithCrlClientTest() { - // TODO what is being tested here? + public virtual void BasicCrlValidatorUsageTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; DateTime revocationDate = checkDate.AddDays(-1); TestCrlBuilder builder = new TestCrlBuilder(caCert, caPrivateKey, checkDate); builder.SetNextUpdate(checkDate.AddDays(10)); builder.AddCrlEntry(checkCert, revocationDate, FACTORY.CreateCRLReason().GetKeyCompromise()); - TestCrlClient crlClient = new TestCrlClient().AddBuilderForCertIssuer(builder); + TestCrlClientWrapper crlClient = new TestCrlClientWrapper(new TestCrlClient().AddBuilderForCertIssuer(builder + )); ValidationReport report = new ValidationReport(); certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() - , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); - parameters.SetFreshness(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays - (0)); + mockParameters.AddRevocationOnlineFetchingResponse(SignatureValidationProperties.OnlineFetching.NEVER_FETCH + ); + mockParameters.AddRevocationOnlineFetchingResponse(SignatureValidationProperties.OnlineFetching.NEVER_FETCH + ); + mockParameters.AddFreshnessResponse(TimeSpan.FromDays(0)); + ReportItem reportItem = new ReportItem("validator", "message", ReportItem.ReportItemStatus.INFO); + mockCrlValidator.OnCallDo((c) => c.report.AddReportItem(reportItem)); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator().AddCrlClient(crlClient ); validator.Validate(report, baseContext, checkCert, checkDate); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(3, report.GetLogs().Count); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[0], report.GetLogs()[2]); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(caCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(CRLValidator.NEXT_UPDATE_VALIDATION, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(caCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, item - .GetCertificate().GetSubjectDN()), item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[2]; - NUnit.Framework.Assert.AreEqual(checkCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CRLValidator.CERTIFICATE_REVOKED, caCert.GetSubjectDN - (), revocationDate), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(0) + // the logitem from the CRL valdiation should be copied to the final report + .HasNumberOfLogs(1).HasLogItem(reportItem)); + // there should be one call per CrlClient + NUnit.Framework.Assert.AreEqual(1, crlClient.GetCalls().Count); + // since there was one response there should be one validator call + NUnit.Framework.Assert.AreEqual(1, mockCrlValidator.calls.Count); + NUnit.Framework.Assert.AreEqual(checkCert, mockCrlValidator.calls[0].certificate); + NUnit.Framework.Assert.AreEqual(checkDate, mockCrlValidator.calls[0].validationDate); + // There should be a new report generated and any logs must be copied the actual report. + NUnit.Framework.Assert.AreNotEqual(report, mockCrlValidator.calls[0].report); + NUnit.Framework.Assert.AreEqual(crlClient.GetCalls()[0].responses[0], mockCrlValidator.calls[0].crl); } [NUnit.Framework.Test] - public virtual void UseFreshCrlResponseTest() { - // Add client with indeterminate CRL, then with CRL which contains revoked checkCert. + public virtual void CrlResponseOrderingTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; - DateTime revocationDate = checkDate.AddDays(-1); - TestCrlBuilder builder1 = new TestCrlBuilder(caCert, caPrivateKey, checkDate); - builder1.SetNextUpdate(checkDate.AddDays(2)); - builder1.AddCrlEntry(checkCert, revocationDate, FACTORY.CreateCRLReason().GetKeyCompromise()); - TestCrlClient crlClient1 = new TestCrlClient().AddBuilderForCertIssuer(builder1); - DateTime thisUpdate2 = checkDate.AddDays(-2); + DateTime thisUpdate1 = checkDate.AddDays(-2); + TestCrlBuilder builder1 = new TestCrlBuilder(caCert, caPrivateKey, thisUpdate1); + builder1.SetNextUpdate(checkDate.AddDays(-2)); + TestCrlClientWrapper crlClient1 = new TestCrlClientWrapper(new TestCrlClient().AddBuilderForCertIssuer(builder1 + )); + DateTime thisUpdate2 = checkDate; TestCrlBuilder builder2 = new TestCrlBuilder(caCert, caPrivateKey, thisUpdate2); builder2.SetNextUpdate(checkDate); - TestCrlClient crlClient2 = new TestCrlClient().AddBuilderForCertIssuer(builder2); - ValidationReport report = new ValidationReport(); - certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() - , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); - parameters.SetFreshness(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays - (0)); + TestCrlClientWrapper crlClient2 = new TestCrlClientWrapper(new TestCrlClient().AddBuilderForCertIssuer(builder2 + )); + DateTime thisUpdate3 = checkDate.AddDays(+2); + TestCrlBuilder builder3 = new TestCrlBuilder(caCert, caPrivateKey, thisUpdate3); + builder3.SetNextUpdate(checkDate.AddDays(-2)); + TestCrlClientWrapper crlClient3 = new TestCrlClientWrapper(new TestCrlClient().AddBuilderForCertIssuer(builder3 + )); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator().AddCrlClient(crlClient1 - ).AddCrlClient(crlClient2); + ).AddCrlClient(crlClient2).AddCrlClient(crlClient3); + mockCrlValidator.OnCallDo((c) => c.report.AddReportItem(new ReportItem("test", "test", ReportItem.ReportItemStatus + .INDETERMINATE))); + ValidationReport report = new ValidationReport(); validator.Validate(report, baseContext, checkCert, checkDate); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(3, report.GetLogs().Count); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[0], report.GetLogs()[2]); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(caCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(CRLValidator.NEXT_UPDATE_VALIDATION, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(caCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, item - .GetCertificate().GetSubjectDN()), item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[2]; - NUnit.Framework.Assert.AreEqual(checkCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CRLValidator.CERTIFICATE_REVOKED, caCert.GetSubjectDN - (), revocationDate), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); + NUnit.Framework.Assert.AreEqual(crlClient3.GetCalls()[0].responses[0], mockCrlValidator.calls[0].crl); + NUnit.Framework.Assert.AreEqual(crlClient2.GetCalls()[0].responses[0], mockCrlValidator.calls[1].crl); + NUnit.Framework.Assert.AreEqual(crlClient1.GetCalls()[0].responses[0], mockCrlValidator.calls[2].crl); } [NUnit.Framework.Test] - public virtual void UseFreshOcspResponseTest() { - // Add client with indeterminate OCSP, then with valid OCSP. + public virtual void OcspResponseOrderingTest() { DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; TestOcspResponseBuilder builder1 = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); builder1.SetProducedAt(checkDate); builder1.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate)); builder1.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(5))); - builder1.SetCertificateStatus(FACTORY.CreateUnknownStatus()); - TestOcspClient ocspClient1 = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder1); + TestOcspClientWrapper ocspClient1 = new TestOcspClientWrapper(new TestOcspClient().AddBuilderForCertIssuer + (caCert, builder1)); TestOcspResponseBuilder builder2 = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); builder2.SetProducedAt(checkDate.AddDays(5)); builder2.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(5))); builder2.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(10))); - TestOcspClient ocspClient2 = new TestOcspClient().AddBuilderForCertIssuer(caCert, builder2); + TestOcspClientWrapper ocspClient2 = new TestOcspClientWrapper(new TestOcspClient().AddBuilderForCertIssuer + (caCert, builder2)); + TestOcspResponseBuilder builder3 = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); + builder3.SetProducedAt(checkDate.AddDays(2)); + builder3.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(2))); + builder3.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(8))); + TestOcspClientWrapper ocspClient3 = new TestOcspClientWrapper(new TestOcspClient().AddBuilderForCertIssuer + (caCert, builder3)); + mockOCSPValidator.OnCallDo((c) => c.report.AddReportItem(new ReportItem("", "", ReportItem.ReportItemStatus + .INDETERMINATE))); ValidationReport report = new ValidationReport(); certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() - , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); - parameters.SetFreshness(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays - (-2)); + mockParameters.AddRevocationOnlineFetchingResponse(SignatureValidationProperties.OnlineFetching.NEVER_FETCH + ).AddFreshnessResponse(TimeSpan.FromDays(-2)); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator().AddOcspClient(ocspClient1 - ).AddOcspClient(ocspClient2); + ).AddOcspClient(ocspClient2).AddOcspClient(ocspClient3); validator.Validate(report, baseContext, checkCert, checkDate); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(2, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.TRUSTED_OCSP_RESPONDER, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, item - .GetCertificate().GetSubjectDN()), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); + NUnit.Framework.Assert.AreEqual(ocspClient2.GetCalls()[0].response, mockOCSPValidator.calls[0].ocspResp); + NUnit.Framework.Assert.AreEqual(ocspClient3.GetCalls()[0].response, mockOCSPValidator.calls[1].ocspResp); + NUnit.Framework.Assert.AreEqual(ocspClient1.GetCalls()[0].response, mockOCSPValidator.calls[2].ocspResp); } [NUnit.Framework.Test] @@ -241,13 +241,33 @@ public virtual void ValidityAssuredTest() { ValidationReport report = new ValidationReport(); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); validator.Validate(report, baseContext, certificate, checkDate); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(certificate, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.VALIDITY_ASSURED, item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasLogItem + ((la) => la.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .VALIDITY_ASSURED).WithCertificate(certificate))); + } + + [NUnit.Framework.Test] + public virtual void SelfSignedCertificateIsNotValidatedTest() { + DateTime checkDate = TimeTestUtil.TEST_DATE_TIME; + ValidationReport report = new ValidationReport(); + RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); + validator.Validate(report, baseContext, caCert, checkDate); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasLogItem + ((la) => la.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .SELF_SIGNED_CERTIFICATE).WithCertificate(caCert))); + } + + [NUnit.Framework.Test] + public virtual void NocheckExtensionShouldNotFurtherValdiateTest() { + ValidationReport report = new ValidationReport(); + parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts + .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH); + RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); + validator.Validate(report, baseContext.SetCertificateSource(CertificateSource.OCSP_ISSUER), trustedOcspResponderCert + , TimeTestUtil.TEST_DATE_TIME); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((la) => la.WithStatus(ReportItem.ReportItemStatus + .INFO).WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .TRUSTED_OCSP_RESPONDER))); } [NUnit.Framework.Test] @@ -258,30 +278,22 @@ public virtual void NoRevocationDataTest() { , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); validator.Validate(report, baseContext, checkCert, TimeTestUtil.TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.NO_REVOCATION_DATA, item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); + AssertValidationReport.AssertThat(report, (a) => a.HasLogItem((la) => la.WithStatus(ReportItem.ReportItemStatus + .INDETERMINATE).WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .NO_REVOCATION_DATA))); } [NUnit.Framework.Test] public virtual void TryFetchRevocationDataOnlineTest() { ValidationReport report = new ValidationReport(); parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() - , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); + .All(), SignatureValidationProperties.OnlineFetching.FETCH_IF_NO_OTHER_DATA_AVAILABLE).SetFreshness(ValidatorContexts + .All(), CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); validator.Validate(report, baseContext, checkCert, TimeTestUtil.TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(1, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.NO_REVOCATION_DATA, item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((la) => la.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .NO_REVOCATION_DATA))); } [NUnit.Framework.Test] @@ -295,20 +307,16 @@ public virtual void CrlEncodingErrorTest() { parameters.SetFreshness(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays (2)); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); - validator.AddCrlClient(new _ICrlClient_355(crl)).Validate(report, baseContext, checkCert, TimeTestUtil.TEST_DATE_TIME + validator.AddCrlClient(new _ICrlClient_398(crl)).Validate(report, baseContext, checkCert, TimeTestUtil.TEST_DATE_TIME ); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.CRL_PARSING_ERROR, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.NO_REVOCATION_DATA, item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((la) => la.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .CRL_PARSING_ERROR)).HasLogItem((la) => la.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK + ).WithMessage(RevocationDataValidator.NO_REVOCATION_DATA))); } - private sealed class _ICrlClient_355 : ICrlClient { - public _ICrlClient_355(byte[] crl) { + private sealed class _ICrlClient_398 : ICrlClient { + public _ICrlClient_398(byte[] crl) { this.crl = crl; } @@ -327,25 +335,28 @@ public virtual void SortResponsesTest() { ocspBuilder1.SetProducedAt(checkDate); ocspBuilder1.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate)); ocspBuilder1.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(3))); - TestOcspClient ocspClient1 = new TestOcspClient().AddBuilderForCertIssuer(caCert, ocspBuilder1); + TestOcspClientWrapper ocspClient1 = new TestOcspClientWrapper(new TestOcspClient().AddBuilderForCertIssuer + (caCert, ocspBuilder1)); TestOcspResponseBuilder ocspBuilder2 = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); ocspBuilder2.SetProducedAt(checkDate.AddDays(3)); ocspBuilder2.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(3))); ocspBuilder2.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(5))); ocspBuilder2.SetCertificateStatus(FACTORY.CreateUnknownStatus()); - TestOcspClient ocspClient2 = new TestOcspClient().AddBuilderForCertIssuer(caCert, ocspBuilder2); + TestOcspClientWrapper ocspClient2 = new TestOcspClientWrapper(new TestOcspClient().AddBuilderForCertIssuer + (caCert, ocspBuilder2)); TestOcspResponseBuilder ocspBuilder3 = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); ocspBuilder3.SetProducedAt(checkDate.AddDays(5)); ocspBuilder3.SetThisUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(5))); ocspBuilder3.SetNextUpdate(DateTimeUtil.GetCalendar(checkDate.AddDays(10))); ocspBuilder3.SetCertificateStatus(FACTORY.CreateUnknownStatus()); - TestOcspClient ocspClient3 = new TestOcspClient().AddBuilderForCertIssuer(caCert, ocspBuilder3); + TestOcspClientWrapper ocspClient3 = new TestOcspClientWrapper(new TestOcspClient().AddBuilderForCertIssuer + (caCert, ocspBuilder3)); TestCrlBuilder crlBuilder1 = new TestCrlBuilder(caCert, caPrivateKey, checkDate); crlBuilder1.SetNextUpdate(checkDate.AddDays(2)); TestCrlBuilder crlBuilder2 = new TestCrlBuilder(caCert, caPrivateKey, checkDate.AddDays(2)); crlBuilder2.SetNextUpdate(checkDate.AddDays(5)); - TestCrlClient crlClient = new TestCrlClient().AddBuilderForCertIssuer(crlBuilder1).AddBuilderForCertIssuer - (crlBuilder2); + TestCrlClientWrapper crlClient = new TestCrlClientWrapper(new TestCrlClient().AddBuilderForCertIssuer(crlBuilder1 + ).AddBuilderForCertIssuer(crlBuilder2)); ValidationReport report = new ValidationReport(); certificateRetriever.AddTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts @@ -353,64 +364,38 @@ public virtual void SortResponsesTest() { .CRL_VALIDATOR), CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-5)); RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator().AddCrlClient(crlClient ).AddOcspClient(ocspClient1).AddOcspClient(ocspClient2).AddOcspClient(ocspClient3); + mockCrlValidator.OnCallDo((c) => { + c.report.AddReportItem(new ReportItem("1", "2", ReportItem.ReportItemStatus.INDETERMINATE)); + try { + Thread.Sleep(10); + } + catch (ThreadInterruptedException) { + } + } + ); + mockOCSPValidator.OnCallDo((c) => { + c.report.AddReportItem(new ReportItem("1", "2", ReportItem.ReportItemStatus.INDETERMINATE)); + try { + Thread.Sleep(10); + } + catch (ThreadInterruptedException) { + } + } + ); validator.Validate(report, baseContext, checkCert, checkDate); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(6, report.GetLogs().Count); - CertificateReportItem item = (CertificateReportItem)report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(checkCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.CERT_STATUS_IS_UNKNOWN, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[1]; - NUnit.Framework.Assert.AreEqual(checkCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.OCSP_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(OCSPValidator.CERT_STATUS_IS_UNKNOWN, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[2]; - NUnit.Framework.Assert.AreEqual(checkCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CRLValidator.FRESHNESS_CHECK, checkDate.AddDays(2 - ), checkDate, TimeSpan.FromDays(-5)), item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[3]; - NUnit.Framework.Assert.AreEqual(checkCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.CRL_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CRLValidator.FRESHNESS_CHECK, checkDate, checkDate - , TimeSpan.FromDays(-5)), item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[4]; - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.TRUSTED_OCSP_RESPONDER, item.GetMessage()); - item = (CertificateReportItem)report.GetLogs()[5]; - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, item - .GetCertificate().GetSubjectDN()), item.GetMessage()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - } - - [NUnit.Framework.Test] - public virtual void CrlWithOnlySomeReasonsTest() { - TestCrlBuilder builder1 = new TestCrlBuilder(caCert, caPrivateKey); - builder1.AddExtension(FACTORY.CreateExtensions().GetIssuingDistributionPoint(), true, FACTORY.CreateIssuingDistributionPoint - (null, false, false, FACTORY.CreateReasonFlags(CRLValidator.ALL_REASONS - 31), false, false)); - TestCrlBuilder builder2 = new TestCrlBuilder(caCert, caPrivateKey); - builder2.AddExtension(FACTORY.CreateExtensions().GetIssuingDistributionPoint(), true, FACTORY.CreateIssuingDistributionPoint - (null, false, false, FACTORY.CreateReasonFlags(31), false, false)); - TestCrlClient crlClient = new TestCrlClient().AddBuilderForCertIssuer(builder1).AddBuilderForCertIssuer(builder2 - ); - TestOcspResponseBuilder ocspBuilder = new TestOcspResponseBuilder(responderCert, ocspRespPrivateKey); - ocspBuilder.SetProducedAt(TimeTestUtil.TEST_DATE_TIME.AddDays(-100)); - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(caCert)); - ValidationReport report = new ValidationReport(); - RevocationDataValidator validator = validatorChainBuilder.BuildRevocationDataValidator(); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH); - validator.AddOcspClient(new TestOcspClient().AddBuilderForCertIssuer(caCert, ocspBuilder)).AddCrlClient(crlClient - ); - validator.Validate(report, baseContext, checkCert, TimeTestUtil.TEST_DATE_TIME); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(0, report.GetFailures().Count); - CertificateReportItem reportItem = (CertificateReportItem)report.GetLogs()[2]; - NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INFO, reportItem.GetStatus()); - NUnit.Framework.Assert.AreEqual(checkCert, reportItem.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CRLValidator.ONLY_SOME_REASONS_CHECKED, reportItem.GetMessage()); + NUnit.Framework.Assert.IsTrue(mockOCSPValidator.calls[0].timeStamp.Before(mockOCSPValidator.calls[1].timeStamp + )); + NUnit.Framework.Assert.IsTrue(mockOCSPValidator.calls[1].timeStamp.Before(mockCrlValidator.calls[0].timeStamp + )); + NUnit.Framework.Assert.IsTrue(mockCrlValidator.calls[0].timeStamp.Before(mockCrlValidator.calls[1].timeStamp + )); + NUnit.Framework.Assert.IsTrue(mockCrlValidator.calls[1].timeStamp.Before(mockOCSPValidator.calls[2].timeStamp + )); + NUnit.Framework.Assert.AreEqual(ocspClient1.GetCalls()[0].response, mockOCSPValidator.calls[2].ocspResp); + NUnit.Framework.Assert.AreEqual(ocspClient2.GetCalls()[0].response, mockOCSPValidator.calls[1].ocspResp); + NUnit.Framework.Assert.AreEqual(ocspClient3.GetCalls()[0].response, mockOCSPValidator.calls[0].ocspResp); + NUnit.Framework.Assert.AreEqual(crlClient.GetCalls()[0].responses[0], mockCrlValidator.calls[1].crl); + NUnit.Framework.Assert.AreEqual(crlClient.GetCalls()[0].responses[1], mockCrlValidator.calls[0].crl); } } } diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidationPropertiesTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidationPropertiesTest.cs index 0a85e313cb..fa5b34ff91 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidationPropertiesTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidationPropertiesTest.cs @@ -144,10 +144,10 @@ public virtual void SetRequiredExtensionsTest() { } private class IncrementalFreshnessValueSetter { - private int value; - private readonly int increment; + private int value; + public IncrementalFreshnessValueSetter(int initialValue, int increment) { this.value = initialValue; this.increment = increment; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorIntegrationTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorIntegrationTest.cs new file mode 100644 index 0000000000..b58b8861bf --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorIntegrationTest.cs @@ -0,0 +1,201 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +using System; +using iText.Bouncycastleconnector; +using iText.Commons.Bouncycastle; +using iText.Commons.Bouncycastle.Cert; +using iText.Commons.Bouncycastle.Crypto; +using iText.Commons.Utils; +using iText.Kernel.Pdf; +using iText.Signatures; +using iText.Signatures.Testutils; +using iText.Signatures.Testutils.Builder; +using iText.Signatures.Testutils.Client; +using iText.Signatures.Validation.V1.Context; +using iText.Signatures.Validation.V1.Report; +using iText.Test; + +namespace iText.Signatures.Validation.V1 { + [NUnit.Framework.Category("BouncyCastleIntegrationTest")] + public class SignatureValidatorIntegrationTest : ExtendedITextTest { + private static readonly String CERTS_SRC = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext + .CurrentContext.TestDirectory) + "/resources/itext/signatures/validation/v1/SignatureValidatorTest/certs/"; + + private static readonly String SOURCE_FOLDER = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext + .CurrentContext.TestDirectory) + "/resources/itext/signatures/validation/v1/SignatureValidatorTest/"; + + private static readonly IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.GetFactory(); + + private static readonly char[] PASSWORD = "testpassphrase".ToCharArray(); + + private SignatureValidationProperties parameters; + + private IssuingCertificateRetriever certificateRetriever; + + private ValidatorChainBuilder builder; + + [NUnit.Framework.OneTimeSetUp] + public static void Before() { + } + + [NUnit.Framework.SetUp] + public virtual void SetUp() { + parameters = new SignatureValidationProperties(); + certificateRetriever = new IssuingCertificateRetriever(); + builder = new ValidatorChainBuilder().WithIssuingCertificateRetriever(certificateRetriever).WithSignatureValidationProperties + (parameters); + } + + [NUnit.Framework.Test] + public virtual void ValidLatestSignatureTest() { + String chainName = CERTS_SRC + "validCertsChain.pem"; + IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); + IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + ValidationReport report; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDoc.pdf"))) { + certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); + AddRevDataClients(); + SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); + report = signatureValidator.ValidateLatestSignature(); + } + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasLogItems + (3, 3, (al) => al.WithCertificate(rootCert).WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK) + .WithMessage(CertificateChainValidator.CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()))); + } + + [NUnit.Framework.Test] + public virtual void LatestSignatureIsTimestampTest() { + String chainName = CERTS_SRC + "validCertsChain.pem"; + String privateKeyName = CERTS_SRC + "rootCertKey.pem"; + IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); + IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + IPrivateKey rootPrivateKey = PemFileHelper.ReadFirstKey(privateKeyName, PASSWORD); + ValidationReport report; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "timestampSignatureDoc.pdf"))) { + certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); + TestOcspResponseBuilder ocspBuilder = new TestOcspResponseBuilder(rootCert, rootPrivateKey); + DateTime currentDate = DateTimeUtil.GetCurrentUtcTime(); + ocspBuilder.SetProducedAt(currentDate); + ocspBuilder.SetThisUpdate(DateTimeUtil.GetCalendar(currentDate.AddDays(3))); + ocspBuilder.SetNextUpdate(DateTimeUtil.GetCalendar(currentDate.AddDays(30))); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(rootCert, ocspBuilder); + builder.GetRevocationDataValidator().AddOcspClient(ocspClient); + parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts + .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() + , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); + SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); + report = signatureValidator.ValidateLatestSignature(); + } + AssertValidationReport.AssertThat(report, (a) => a.HasNumberOfFailures(0).HasNumberOfLogs(2).HasLogItems(2 + , 2, (la) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (l) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); + } + + [NUnit.Framework.Test] + public virtual void CertificatesNotInLatestSignatureTest() { + String chainName = CERTS_SRC + "validCertsChain.pem"; + IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); + IX509Certificate signingCert = (IX509Certificate)certificateChain[0]; + IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + ValidationReport report; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDocWithoutChain.pdf"))) { + SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); + certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); + parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts + .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() + , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); + report = signatureValidator.ValidateLatestSignature(); + } + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasLogItem((al) => al.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .NO_REVOCATION_DATA).WithCertificate(signingCert).WithStatus(ReportItem.ReportItemStatus.INDETERMINATE + )).HasLogItem((al) => al.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator + .ISSUER_MISSING, (i) => signingCert.GetSubjectDN()).WithStatus(ReportItem.ReportItemStatus.INDETERMINATE + ).WithCertificate(signingCert))); + } + + [NUnit.Framework.Test] + public virtual void CertificatesNotInLatestSignatureButSetAsKnownTest() { + String chainName = CERTS_SRC + "validCertsChain.pem"; + IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); + IX509Certificate intermediateCert = (IX509Certificate)certificateChain[1]; + IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + ValidationReport report; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDocWithoutChain.pdf"))) { + certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); + certificateRetriever.AddKnownCertificates(JavaCollectionsUtil.SingletonList(intermediateCert)); + AddRevDataClients(); + SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); + report = signatureValidator.ValidateLatestSignature(); + } + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasLogItems + (3, 3, (al) => al.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator + .CERTIFICATE_TRUSTED, (i) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); + } + + [NUnit.Framework.Test] + public virtual void RootIsNotTrustedInLatestSignatureTest() { + String chainName = CERTS_SRC + "validCertsChain.pem"; + IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); + IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + ValidationReport report; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDoc.pdf"))) { + SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); + parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts + .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() + , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); + report = signatureValidator.ValidateLatestSignature(); + } + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(3).HasLogItem((al) => al.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK + ).WithMessage(RevocationDataValidator.NO_REVOCATION_DATA).WithCertificate((IX509Certificate)certificateChain + [0])).HasLogItem((al) => al.WithCheckName(RevocationDataValidator.REVOCATION_DATA_CHECK).WithMessage(RevocationDataValidator + .NO_REVOCATION_DATA).WithCertificate((IX509Certificate)certificateChain[1])).HasLogItem((al) => al.WithCheckName + (CertificateChainValidator.CERTIFICATE_CHECK).WithMessage(CertificateChainValidator.ISSUER_MISSING, (i + ) => rootCert.GetSubjectDN()).WithCertificate(rootCert))); + } + + private void AddRevDataClients() { + String chainName = CERTS_SRC + "validCertsChain.pem"; + String privateKeyName = CERTS_SRC + "rootCertKey.pem"; + IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); + IX509Certificate intermediateCert = (IX509Certificate)certificateChain[1]; + IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + IPrivateKey rootPrivateKey = PemFileHelper.ReadFirstKey(privateKeyName, PASSWORD); + DateTime currentDate = DateTimeUtil.GetCurrentUtcTime(); + TestOcspResponseBuilder builder1 = new TestOcspResponseBuilder(rootCert, rootPrivateKey); + builder1.SetProducedAt(currentDate); + builder1.SetThisUpdate(DateTimeUtil.GetCalendar(currentDate)); + builder1.SetNextUpdate(DateTimeUtil.GetCalendar(currentDate.AddDays(30))); + TestOcspResponseBuilder builder2 = new TestOcspResponseBuilder(rootCert, rootPrivateKey); + builder2.SetProducedAt(currentDate); + builder2.SetThisUpdate(DateTimeUtil.GetCalendar(currentDate)); + builder2.SetNextUpdate(DateTimeUtil.GetCalendar(currentDate.AddDays(30))); + TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(rootCert, builder1).AddBuilderForCertIssuer + (intermediateCert, builder2); + builder.GetRevocationDataValidator().AddOcspClient(ocspClient); + parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts + .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH); + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorTest.cs index ff77711bf8..0abc1ce0a1 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/SignatureValidatorTest.cs @@ -21,6 +21,8 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ using System; +using System.Collections.Generic; +using System.Linq; using iText.Bouncycastleconnector; using iText.Commons.Bouncycastle; using iText.Commons.Bouncycastle.Cert; @@ -28,7 +30,6 @@ You should have received a copy of the GNU Affero General Public License using iText.Commons.Bouncycastle.Security; using iText.Commons.Utils; using iText.Kernel.Pdf; -using iText.Signatures; using iText.Signatures.Testutils; using iText.Signatures.Testutils.Builder; using iText.Signatures.Testutils.Client; @@ -37,7 +38,7 @@ You should have received a copy of the GNU Affero General Public License using iText.Test; namespace iText.Signatures.Validation.V1 { - [NUnit.Framework.Category("BouncyCastleIntegrationTest")] + [NUnit.Framework.Category("BouncyCastleUnitTest")] public class SignatureValidatorTest : ExtendedITextTest { private static readonly String CERTS_SRC = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext .CurrentContext.TestDirectory) + "/resources/itext/signatures/validation/v1/SignatureValidatorTest/certs/"; @@ -51,43 +52,24 @@ public class SignatureValidatorTest : ExtendedITextTest { private SignatureValidationProperties parameters; - private IssuingCertificateRetriever certificateRetriever; + private MockIssuingCertificateRetriever mockCertificateRetriever; private ValidatorChainBuilder builder; + private MockChainValidator mockCertificateChainValidator; + [NUnit.Framework.OneTimeSetUp] public static void Before() { } [NUnit.Framework.SetUp] public virtual void SetUp() { + mockCertificateChainValidator = new MockChainValidator(); parameters = new SignatureValidationProperties(); - certificateRetriever = new IssuingCertificateRetriever(); - builder = new ValidatorChainBuilder().WithIssuingCertificateRetriever(certificateRetriever).WithSignatureValidationProperties - (parameters); - } - - [NUnit.Framework.Test] - public virtual void ValidLatestSignatureTest() { - String chainName = CERTS_SRC + "validCertsChain.pem"; - IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); - IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; - ValidationReport report; - using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDoc.pdf"))) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - AddRevDataClients(); - SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); - report = signatureValidator.ValidateLatestSignature(); - } - for (int i = 0; i < 3; ++i) { - CertificateReportItem item = report.GetCertificateLogs()[i]; - NUnit.Framework.Assert.AreEqual(rootCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert - .GetSubjectDN()), item.GetMessage()); - } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(3, report.GetLogs().Count); + mockCertificateRetriever = new MockIssuingCertificateRetriever(); + builder = new ValidatorChainBuilder().WithIssuingCertificateRetriever(mockCertificateRetriever).WithSignatureValidationProperties + (parameters).WithCertificateChainValidator(mockCertificateChainValidator).WithRevocationDataValidator( + new MockRevocationDataValidator()); } [NUnit.Framework.Test] @@ -97,9 +79,11 @@ public virtual void LatestSignatureIsTimestampTest() { IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; IPrivateKey rootPrivateKey = PemFileHelper.ReadFirstKey(privateKeyName, PASSWORD); + IX509Certificate timeStampCert = (IX509Certificate)PemFileHelper.ReadFirstChain(CERTS_SRC + "timestamp.pem" + )[0]; ValidationReport report; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "timestampSignatureDoc.pdf"))) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); + mockCertificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); TestOcspResponseBuilder ocspBuilder = new TestOcspResponseBuilder(rootCert, rootPrivateKey); DateTime currentDate = DateTimeUtil.GetCurrentUtcTime(); ocspBuilder.SetProducedAt(currentDate); @@ -113,33 +97,11 @@ public virtual void LatestSignatureIsTimestampTest() { SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); report = signatureValidator.ValidateLatestSignature(); } - new AssertValidationReport(report).HasNumberOfFailures(0).HasNumberOfLogs(2).HasLogItems((l) => l.GetCheckName - ().Equals(CertificateChainValidator.CERTIFICATE_CHECK) && l.GetMessage().Equals(MessageFormatUtil.Format - (CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert.GetSubjectDN())) && ((CertificateReportItem)l - ).GetCertificate().Equals(rootCert), 2, CertificateChainValidator.CERTIFICATE_TRUSTED).DoAssert(); - } - - [NUnit.Framework.Test] - public virtual void ValidLatestSignatureWithTimestampTest() { - String chainName = CERTS_SRC + "validCertsChain.pem"; - IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); - IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; - ValidationReport report; - using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDocWithTimestamp.pdf"))) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - AddRevDataClients(); - SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); - report = signatureValidator.ValidateLatestSignature(); - } - for (int i = 0; i < 5; ++i) { - CertificateReportItem item1 = report.GetCertificateLogs()[i]; - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert - .GetSubjectDN()), item1.GetMessage()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(rootCert, item1.GetCertificate()); - } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(5, report.GetLogs().Count); + NUnit.Framework.Assert.AreEqual(1, mockCertificateChainValidator.verificationCalls.Count); + MockChainValidator.ValidationCallBack call = mockCertificateChainValidator.verificationCalls[0]; + NUnit.Framework.Assert.AreEqual(CertificateSource.TIMESTAMP, call.context.GetCertificateSource()); + NUnit.Framework.Assert.AreEqual(ValidatorContext.SIGNATURE_VALIDATOR, call.context.GetValidatorContext()); + NUnit.Framework.Assert.AreEqual(timeStampCert.GetSubjectDN(), call.certificate.GetSubjectDN()); } [NUnit.Framework.Test] @@ -150,25 +112,13 @@ public virtual void LatestSignatureWithBrokenTimestampTest() { ValidationReport report; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "docWithBrokenTimestamp.pdf")) ) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - AddRevDataClients(); + mockCertificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); report = signatureValidator.ValidateLatestSignature(); } - NUnit.Framework.Assert.AreEqual(report.GetFailures()[0], report.GetLogs()[0]); - ReportItem failure = report.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(SignatureValidator.CANNOT_VERIFY_TIMESTAMP, failure.GetMessage()); - NUnit.Framework.Assert.AreEqual(SignatureValidator.TIMESTAMP_VERIFICATION, failure.GetCheckName()); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(1, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(6, report.GetLogs().Count); - for (int i = 0; i < 5; ++i) { - CertificateReportItem item1 = report.GetCertificateLogs()[i]; - NUnit.Framework.Assert.AreEqual(rootCert, item1.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert - .GetSubjectDN()), item1.GetMessage()); - } + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID).HasLogItem + ((al) => al.WithCheckName(SignatureValidator.TIMESTAMP_VERIFICATION).WithMessage(SignatureValidator.CANNOT_VERIFY_TIMESTAMP + ).WithStatus(ReportItem.ReportItemStatus.INVALID))); } [NUnit.Framework.Test] @@ -178,31 +128,14 @@ public virtual void DocumentModifiedLatestSignatureTest() { IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; ValidationReport report; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "modifiedDoc.pdf"))) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - AddRevDataClients(); + mockCertificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); report = signatureValidator.ValidateLatestSignature(); } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(2, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(5, report.GetLogs().Count); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[0], report.GetLogs()[0]); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[1], report.GetLogs()[1]); - ReportItem item1 = report.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(SignatureValidator.SIGNATURE_VERIFICATION, item1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(SignatureValidator.DOCUMENT_IS_NOT_COVERED, "Signature1" - ), item1.GetMessage()); - ReportItem item2 = report.GetFailures()[1]; - NUnit.Framework.Assert.AreEqual(SignatureValidator.SIGNATURE_VERIFICATION, item2.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(SignatureValidator.CANNOT_VERIFY_SIGNATURE, "Signature1" - ), item2.GetMessage()); - for (int i = 0; i < 3; ++i) { - CertificateReportItem item = report.GetCertificateLogs()[i]; - NUnit.Framework.Assert.AreEqual(rootCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert - .GetSubjectDN()), item.GetMessage()); - } + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID).HasLogItem + ((al) => al.WithCheckName(SignatureValidator.SIGNATURE_VERIFICATION).WithMessage(SignatureValidator.DOCUMENT_IS_NOT_COVERED + , (i) => "Signature1")).HasLogItem((al) => al.WithCheckName(SignatureValidator.SIGNATURE_VERIFICATION) + .WithMessage(SignatureValidator.CANNOT_VERIFY_SIGNATURE, (i) => "Signature1"))); } [NUnit.Framework.Test] @@ -214,184 +147,79 @@ public virtual void LatestSignatureInvalidStopValidationTest() { parameters.SetContinueAfterFailure(ValidatorContexts.All(), CertificateSources.All(), false); using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "modifiedDoc.pdf"))) { SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); + mockCertificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); report = signatureValidator.ValidateLatestSignature(); } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(2, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(2, report.GetLogs().Count); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[0], report.GetLogs()[0]); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[1], report.GetLogs()[1]); - ReportItem item1 = report.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(SignatureValidator.SIGNATURE_VERIFICATION, item1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(SignatureValidator.DOCUMENT_IS_NOT_COVERED, "Signature1" - ), item1.GetMessage()); - ReportItem item2 = report.GetFailures()[1]; - NUnit.Framework.Assert.AreEqual(SignatureValidator.SIGNATURE_VERIFICATION, item2.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(SignatureValidator.CANNOT_VERIFY_SIGNATURE, "Signature1" - ), item2.GetMessage()); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID).HasLogItem + ((al) => al.WithCheckName(SignatureValidator.SIGNATURE_VERIFICATION).WithMessage(SignatureValidator.DOCUMENT_IS_NOT_COVERED + , (i) => "Signature1").WithStatus(ReportItem.ReportItemStatus.INVALID)).HasLogItem((al) => al.WithCheckName + (SignatureValidator.SIGNATURE_VERIFICATION).WithMessage(SignatureValidator.CANNOT_VERIFY_SIGNATURE, (i + ) => "Signature1").WithStatus(ReportItem.ReportItemStatus.INVALID))); + // check that no requests are made after failure + NUnit.Framework.Assert.AreEqual(0, mockCertificateChainValidator.verificationCalls.Count); } [NUnit.Framework.Test] - public virtual void CertificatesNotInLatestSignatureTest() { + public virtual void CertificatesNotInLatestSignatureButTakenFromDSSTest() { String chainName = CERTS_SRC + "validCertsChain.pem"; IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); - IX509Certificate signingCert = (IX509Certificate)certificateChain[0]; IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; - ValidationReport report; - using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDocWithoutChain.pdf"))) { - SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() - , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); - report = signatureValidator.ValidateLatestSignature(); - } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(2, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(2, report.GetLogs().Count); - CertificateReportItem item = report.GetCertificateFailures()[0]; - NUnit.Framework.Assert.AreEqual(signingCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.NO_REVOCATION_DATA, item.GetMessage()); - item = report.GetCertificateFailures()[1]; - NUnit.Framework.Assert.AreEqual(signingCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.ISSUER_MISSING, signingCert - .GetSubjectDN()), item.GetMessage()); - } - - [NUnit.Framework.Test] - public virtual void CertificatesNotInLatestSignatureButSetAsKnownTest() { - String chainName = CERTS_SRC + "validCertsChain.pem"; - IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); IX509Certificate intermediateCert = (IX509Certificate)certificateChain[1]; - IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; - ValidationReport report; - using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDocWithoutChain.pdf"))) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - certificateRetriever.AddKnownCertificates(JavaCollectionsUtil.SingletonList(intermediateCert)); - AddRevDataClients(); + IX509Certificate signCert = (IX509Certificate)certificateChain[0]; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "docWithDss.pdf"))) { + mockCertificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); - report = signatureValidator.ValidateLatestSignature(); + signatureValidator.ValidateLatestSignature(); } - for (int i = 0; i < 3; ++i) { - CertificateReportItem item = report.GetCertificateLogs()[i]; - NUnit.Framework.Assert.AreEqual(rootCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert - .GetSubjectDN()), item.GetMessage()); - } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(3, report.GetLogs().Count); + NUnit.Framework.Assert.AreEqual(2, mockCertificateRetriever.addKnownCertificatesCalls.Count); + ICollection dssCall = mockCertificateRetriever.addKnownCertificatesCalls[0]; + NUnit.Framework.Assert.AreEqual(3, dssCall.Count); + NUnit.Framework.Assert.AreEqual(1, dssCall.Where((c) => ((IX509Certificate)c).Equals(rootCert)).Count()); + NUnit.Framework.Assert.AreEqual(1, dssCall.Where((c) => ((IX509Certificate)c).Equals(intermediateCert)).Count + ()); + NUnit.Framework.Assert.AreEqual(1, dssCall.Where((c) => ((IX509Certificate)c).Equals(signCert)).Count()); } [NUnit.Framework.Test] - public virtual void CertificatesNotInLatestSignatureButTakenFromDSSTest() { + public virtual void CertificatesNotInLatestSignatureButTakenFromDSSOneCertIsBrokenTest() { String chainName = CERTS_SRC + "validCertsChain.pem"; IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; ValidationReport report; - using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "docWithDss.pdf"))) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - AddRevDataClients(); + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "docWithBrokenDss.pdf"))) { + mockCertificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); report = signatureValidator.ValidateLatestSignature(); } - for (int i = 0; i < 3; ++i) { - CertificateReportItem item = report.GetCertificateLogs()[i]; - NUnit.Framework.Assert.AreEqual(rootCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert - .GetSubjectDN()), item.GetMessage()); - } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(3, report.GetLogs().Count); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID).HasLogItem + ((al) => al.WithCheckName(SignatureValidator.CERTS_FROM_DSS).WithExceptionCauseType(typeof(AbstractGeneralSecurityException + )))); } [NUnit.Framework.Test] - public virtual void CertificatesNotInLatestSignatureButTakenFromDSSOneCertIsBrokenTest() { - String chainName = CERTS_SRC + "validCertsChain.pem"; - IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); - IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + public virtual void IndeterminateChainValidationLeadsToIndeterminateResultTest() { + mockCertificateChainValidator.OnCallDo((c) => c.report.AddReportItem(new ReportItem("test", "test", ReportItem.ReportItemStatus + .INDETERMINATE))); ValidationReport report; - using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "docWithBrokenDss.pdf"))) { - certificateRetriever.SetTrustedCertificates(JavaCollectionsUtil.SingletonList(rootCert)); - AddRevDataClients(); + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDoc.pdf"))) { SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); report = signatureValidator.ValidateLatestSignature(); } - ReportItem reportItem = report.GetLogs()[0]; - NUnit.Framework.Assert.AreEqual(SignatureValidator.CERTS_FROM_DSS, reportItem.GetCheckName()); - NUnit.Framework.Assert.IsTrue(reportItem.GetExceptionCause() is AbstractGeneralSecurityException); - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, report.GetValidationResult()); - NUnit.Framework.Assert.AreEqual(4, report.GetLogs().Count); - for (int i = 0; i < 3; ++i) { - CertificateReportItem item = report.GetCertificateLogs()[i]; - NUnit.Framework.Assert.AreEqual(rootCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.CERTIFICATE_TRUSTED, rootCert - .GetSubjectDN()), item.GetMessage()); - } + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INDETERMINATE + ).HasNumberOfFailures(1).HasLogItem((al) => al.WithCheckName("test").WithMessage("test"))); } [NUnit.Framework.Test] - public virtual void RootIsNotTrustedInLatestSignatureTest() { - String chainName = CERTS_SRC + "validCertsChain.pem"; - IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); - IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; + public virtual void InvalidChainValidationLeadsToInvalidResultTest() { + mockCertificateChainValidator.OnCallDo((c) => c.report.AddReportItem(new ReportItem("test", "test", ReportItem.ReportItemStatus + .INVALID))); ValidationReport report; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDoc.pdf"))) { SignatureValidator signatureValidator = builder.BuildSignatureValidator(document); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH).SetFreshness(ValidatorContexts.All() - , CertificateSources.All(), TimeBasedContexts.All(), TimeSpan.FromDays(-2)); report = signatureValidator.ValidateLatestSignature(); } - NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, report.GetValidationResult - ()); - NUnit.Framework.Assert.AreEqual(3, report.GetFailures().Count); - NUnit.Framework.Assert.AreEqual(4, report.GetLogs().Count); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[0], report.GetLogs()[0]); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[1], report.GetLogs()[1]); - NUnit.Framework.Assert.AreEqual(report.GetFailures()[2], report.GetLogs()[3]); - CertificateReportItem item = report.GetCertificateFailures()[0]; - NUnit.Framework.Assert.AreEqual(certificateChain[0], item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.NO_REVOCATION_DATA, item.GetMessage()); - item = report.GetCertificateFailures()[1]; - NUnit.Framework.Assert.AreEqual(certificateChain[1], item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.REVOCATION_DATA_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(RevocationDataValidator.NO_REVOCATION_DATA, item.GetMessage()); - item = report.GetCertificateFailures()[2]; - NUnit.Framework.Assert.AreEqual(rootCert, item.GetCertificate()); - NUnit.Framework.Assert.AreEqual(CertificateChainValidator.CERTIFICATE_CHECK, item.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(CertificateChainValidator.ISSUER_MISSING, rootCert - .GetSubjectDN()), item.GetMessage()); - } - - private void AddRevDataClients() { - String chainName = CERTS_SRC + "validCertsChain.pem"; - String privateKeyName = CERTS_SRC + "rootCertKey.pem"; - IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); - IX509Certificate intermediateCert = (IX509Certificate)certificateChain[1]; - IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; - IPrivateKey rootPrivateKey = PemFileHelper.ReadFirstKey(privateKeyName, PASSWORD); - DateTime currentDate = DateTimeUtil.GetCurrentUtcTime(); - TestOcspResponseBuilder builder1 = new TestOcspResponseBuilder(rootCert, rootPrivateKey); - builder1.SetProducedAt(currentDate); - builder1.SetThisUpdate(DateTimeUtil.GetCalendar(currentDate)); - builder1.SetNextUpdate(DateTimeUtil.GetCalendar(currentDate.AddDays(30))); - TestOcspResponseBuilder builder2 = new TestOcspResponseBuilder(rootCert, rootPrivateKey); - builder2.SetProducedAt(currentDate); - builder2.SetThisUpdate(DateTimeUtil.GetCalendar(currentDate)); - builder2.SetNextUpdate(DateTimeUtil.GetCalendar(currentDate.AddDays(30))); - TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(rootCert, builder1).AddBuilderForCertIssuer - (intermediateCert, builder2); - builder.GetRevocationDataValidator().AddOcspClient(ocspClient); - parameters.SetRevocationOnlineFetching(ValidatorContexts.All(), CertificateSources.All(), TimeBasedContexts - .All(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH); + AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID).HasNumberOfFailures + (1).HasLogItem((al) => al.WithCheckName("test").WithMessage("test"))); } } } diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs new file mode 100644 index 0000000000..b3e5bde0dd --- /dev/null +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs @@ -0,0 +1,127 @@ +using System; +using iText.Commons.Bouncycastle.Cert; +using iText.Signatures.Testutils; +using iText.Test; + +namespace iText.Signatures.Validation.V1.Report { + [NUnit.Framework.Category("BouncyCastleUnitTest")] + public class ValidationReportTest : ExtendedITextTest { + private static readonly String CERTS_SRC = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext + .CurrentContext.TestDirectory) + "/resources/itext/signatures/certs/"; + + [NUnit.Framework.Test] + public virtual void GetValidationResultWithNoLogsShouldBeValid() { + ValidationReport sut = new ValidationReport(); + NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, sut.GetValidationResult()); + } + + [NUnit.Framework.Test] + public virtual void GetValidationResultWithOnlyValidLogsShouldBeValid() { + ValidationReport sut = new ValidationReport(); + sut.AddReportItem(new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO)); + sut.AddReportItem(new ReportItem("test2", "test2", ReportItem.ReportItemStatus.INFO)); + sut.AddReportItem(new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INFO)); + NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.VALID, sut.GetValidationResult()); + } + + [NUnit.Framework.Test] + public virtual void GetValidationResultWithValidAndIndeterminateLogsShouldBeIndeterminate() { + ValidationReport sut = new ValidationReport(); + sut.AddReportItem(new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO)); + sut.AddReportItem(new ReportItem("test2", "test2", ReportItem.ReportItemStatus.INDETERMINATE)); + sut.AddReportItem(new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INFO)); + NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INDETERMINATE, sut.GetValidationResult() + ); + } + + [NUnit.Framework.Test] + public virtual void GetValidationResultWithInvalidLogsShouldBeInvalid() { + ValidationReport sut = new ValidationReport(); + sut.AddReportItem(new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO)); + sut.AddReportItem(new ReportItem("test2", "test2", ReportItem.ReportItemStatus.INVALID)); + sut.AddReportItem(new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INDETERMINATE)); + NUnit.Framework.Assert.AreEqual(ValidationReport.ValidationResult.INVALID, sut.GetValidationResult()); + } + + [NUnit.Framework.Test] + public virtual void TestGetFailures() { + ValidationReport sut = new ValidationReport(); + sut.AddReportItem(new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO)); + ReportItem failure1 = new ReportItem("test2", "test2", ReportItem.ReportItemStatus.INVALID); + sut.AddReportItem(failure1); + ReportItem failure2 = new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INDETERMINATE); + sut.AddReportItem(failure2); + NUnit.Framework.Assert.IsTrue(sut.GetFailures().Contains(failure1)); + NUnit.Framework.Assert.IsTrue(sut.GetFailures().Contains(failure2)); + NUnit.Framework.Assert.AreEqual(2, sut.GetFailures().Count); + } + + [NUnit.Framework.Test] + public virtual void GetCertificateFailuresTest() { + ValidationReport sut = new ValidationReport(); + sut.AddReportItem(new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO)); + IX509Certificate cert = (IX509Certificate)PemFileHelper.ReadFirstChain(CERTS_SRC + "adobeExtensionCert.pem" + )[0]; + CertificateReportItem failure1 = new CertificateReportItem(cert, "test2", "test2", ReportItem.ReportItemStatus + .INVALID); + sut.AddReportItem(failure1); + ReportItem failure2 = new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INDETERMINATE); + sut.AddReportItem(failure2); + NUnit.Framework.Assert.IsTrue(sut.GetCertificateFailures().Contains(failure1)); + NUnit.Framework.Assert.AreEqual(1, sut.GetCertificateFailures().Count); + } + + [NUnit.Framework.Test] + public virtual void GetLogsTest() { + ValidationReport sut = new ValidationReport(); + ReportItem item1 = new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO); + sut.AddReportItem(item1); + IX509Certificate cert = (IX509Certificate)PemFileHelper.ReadFirstChain(CERTS_SRC + "adobeExtensionCert.pem" + )[0]; + CertificateReportItem failure1 = new CertificateReportItem(cert, "test2", "test2", ReportItem.ReportItemStatus + .INVALID); + sut.AddReportItem(failure1); + ReportItem failure2 = new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INDETERMINATE); + sut.AddReportItem(failure2); + NUnit.Framework.Assert.AreEqual(item1, sut.GetLogs()[0]); + NUnit.Framework.Assert.AreEqual(failure1, sut.GetLogs()[1]); + NUnit.Framework.Assert.AreEqual(failure2, sut.GetLogs()[2]); + NUnit.Framework.Assert.AreEqual(3, sut.GetLogs().Count); + } + + [NUnit.Framework.Test] + public virtual void GetCertificateLogsTest() { + ValidationReport sut = new ValidationReport(); + sut.AddReportItem(new ReportItem("test1", "test1", ReportItem.ReportItemStatus.INFO)); + IX509Certificate cert = (IX509Certificate)PemFileHelper.ReadFirstChain(CERTS_SRC + "adobeExtensionCert.pem" + )[0]; + CertificateReportItem failure1 = new CertificateReportItem(cert, "test2", "test2", ReportItem.ReportItemStatus + .INVALID); + sut.AddReportItem(failure1); + ReportItem failure2 = new ReportItem("test3", "test3", ReportItem.ReportItemStatus.INDETERMINATE); + sut.AddReportItem(failure2); + NUnit.Framework.Assert.IsTrue(sut.GetCertificateLogs().Contains(failure1)); + NUnit.Framework.Assert.AreEqual(1, sut.GetCertificateLogs().Count); + } + + [NUnit.Framework.Test] + public virtual void ToStringTest() { + ValidationReport sut = new ValidationReport(); + sut.AddReportItem(new ReportItem("test1check", "test1message", ReportItem.ReportItemStatus.INFO)); + IX509Certificate cert = (IX509Certificate)PemFileHelper.ReadFirstChain(CERTS_SRC + "adobeExtensionCert.pem" + )[0]; + CertificateReportItem failure1 = new CertificateReportItem(cert, "test2check", "test2message", ReportItem.ReportItemStatus + .INVALID); + sut.AddReportItem(failure1); + ReportItem failure2 = new ReportItem("test3check", "test3message", ReportItem.ReportItemStatus.INDETERMINATE + ); + sut.AddReportItem(failure2); + NUnit.Framework.Assert.IsTrue(sut.ToString().Contains("INVALID")); + NUnit.Framework.Assert.IsTrue(sut.ToString().Contains("test1check")); + NUnit.Framework.Assert.IsTrue(sut.ToString().Contains("test1message")); + NUnit.Framework.Assert.IsTrue(sut.ToString().Contains("test2check")); + NUnit.Framework.Assert.IsTrue(sut.ToString().Contains("test2message")); + NUnit.Framework.Assert.IsTrue(sut.ToString().Contains("test3check")); + } + } +} diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/RevocationDataValidatorTest/trustedOcspResponderCert.pem b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/RevocationDataValidatorTest/trustedOcspResponderCert.pem new file mode 100644 index 0000000000..87bd404436 --- /dev/null +++ b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/RevocationDataValidatorTest/trustedOcspResponderCert.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIDbzCCAlegAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwODELMAkGA1UEBhMCQlkx +DjAMBgNVBAoMBWlUZXh0MRkwFwYDVQQDDBBpVGV4dFRlc3RSb290UnNhMCAXDTAw +MDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjA+MQswCQYDVQQGEwJCWTEOMAwG +A1UECgwFaVRleHQxHzAdBgNVBAMMFmlUZXh0VGVzdE9jc3BSZXNwb25kZXIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTpG5XNA+s7WqXukyPwb5UCmhl +JdJuY/5Qa/48cY5qLoOAT5EYAofxmaZU3BmLWupkvIAm3HkkBNzrvOR830uuomzv +1kxoPyycBm9jlvap3PRzFfOeqBqjoRVcw9EXbnCZQWFDdRaPB5zyG/Vwi0ycwhXb +Zs98JXZeD8uYIdRjpd8ygLAb4+vpV+kyIOrSuQHsa/HdCGBTmu1uQSU9J/vjHmQy +INgCHVhue1t7VHu5rAtyOXFoE8ydyoE0VgrO50ibfp83CZNYjE8oBfkM/B92kcMI +9O5Qg+C/1U6/r/i4YG49/6Muvd6y9++TJU1QVsYIpiWcoWR/apHhOh7flSg7AgMB +AAGjezB5MB0GA1UdDgQWBBQ/1FBILdb8EtHROA6izhhyDJ7rLjAfBgNVHSMEGDAW +gBQpg7aJjDLCulYvlE5pwqzcCjSFQTAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/ +BAwwCgYIKwYBBQUHAwkwDwYJKwYBBQUHMAEFBAIFADANBgkqhkiG9w0BAQsFAAOC +AQEALBJSsM3YgGzkHsdNSUDO5EFWRlXu9oDFPltywa/9f6rzI+bY+udAETCNPCoQ +xWr66Of2f3MwgUqOrhLNpB1OQekHWDCfdcFdMYw08TjwURSrMT7LejiujQLBI4Sz +HZXUSuCDbyTaCqhAxEknSkyp3ujZ1zvKz40/EWSgwdx+bJP02AfEzhRigMoMB2P9 +yzaoJrIFuqrZv9LDqh2DCZnybMjXlMA4VshTuEIMNwIViG1t/gNIMeRvLKB8Mnx4 +5ep0gRorY7+BTI0hLp8TgNmDiKuA/rzQlaIsWihT/KrWOfdfLeZ6BgtZB5J/B3IH +r7yOzAb6zvBJRWCfKFgCYHBUaA== +-----END CERTIFICATE----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQcdIPPqgvPd5xSXiw +hD4NKwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEKX2ZZrPTL+wipvQ +1dczNlwEggTQg5i86SShEScxyx6AEtBhbjqfBiOC25jYXIaL6WyucwgUsEC/vu0r +sNJ2ik9BozSOLg073hyBhrGIf5ZYOuj5cRnvebxW7sj1lpWq/92hax/+t5jFpbOh +ekEZFTdI3crwFtj8AWyRIty9vx/X6NhfGh7cjH5XsthgkxdNUja+C/+Kop3vn49V +d4JIuqRLVzlnIzEVn+3sDjduG25hV3cHW5T7PBQkCzsZXPPK45pIryzt5UQ3Ylfu +QiOGAQ6+vIw1ws+Fp9YRiROFgQgLgvJzo1W77S21AGZ41pWtdQfBIKDu2i4lC8Wp +xHa+/KxmhKkE4hH7FgJkBzwjX4BRjhh5OtXGLVsbu0NC6LWqMqyuxyx89EST+J/W +PxMUDWO3cU9MzwgcK/un0D3OiISuIR72JEEG4da/5rIj0zoP8DZmBPHQarsNDF2y +rHxzC20WwtMO7691AsYPSzeM2DNxoyCdd4pmzM/zZNUxJkb2k0d83cbff7osGuQ1 +RJZlAGxpS89Ki5n72UVBec2hSaAIvxeyJAIsVcwU3C+md+OwQNid1h8avPtvXRzB +J/olvIheSKbRZ4RFtnatkGtAGqWmawC76kmqmCOGjq/8q3itgNhrDT2F8r85e7mz +ZPRv+uHn9+mLaDy07r6FLlAnKIQZktSZ2luVbW1EpBShcYNahqFzMkSgb078WttX +VHfhQToYqc2mUAo2+FwPbGh82WqtDDgdcYKOJ7JwWnnz4bQhqRXS3w2p81xX/dMN +rGkrM9SHHa7VpH45ZtgrUK6rwXx0376S4C6uoZm6ENdWesStAIiU7PEC4TrKwJKY +PxpBkdV1zCHii5gvbx1K9PIU8DFPGqRDHNNrW8VCjn6/8dnyKwQxUrVM3Na6wfcU +y3Cw4q25DyJXSqzowIi+pAN0KVgMMZnzLz380eS94wRkL+fOb34XATvbBW5btwMA +ctLOPhDEI6KipF7suXU0d7GkR12sTcKWYGKGaJtfjjuGM3Qi5gvCen/zV6HtEnxf +hZrPNhrlR7t53iqxX8Vib3I1lLihnS8/L/EDc1YG6Ew4bV3WllJj+7bwctY0hMgN +f2/JzZwrdoY6dUTGtQN2MoCBAJlEpFjrvysAuNkNrbpSHx/Z4LHaqrlBhnbVl5qp +FGP6Li7GE19sWH1rDmz4JFdLJ8z3WBvjWfSjl6xGk3Xiinkv72PoZGHjhIsVuW2r ++iUXiAcm0NnvTcM7u1lr+oaXhScUVTmlH8drtLhEDJGXxM7aEUYR0+zIl15+r3YV +sOr9c8uH/ctrYyr6SMFyHI6V7rxj/HtVI/UssCC7SqqEKdzea2Tr1ojPHakT0VVc +05FXAhyOrdhJegwn7RuEtl2TcKok+zlJK27bLLYjhKwlBd/Q+LKMYwfQQ7qTZEfR +IOCo5jOI4xq3Nhq1t2L0yWMMB5y6x+xx9A5AQGvbF4HLf9Cw+q0nk9EBY0THwFhS +9ilOznX+oI6DmK5uhN8ghxdiv8IfZR7IQunGhKD7bdSLsncFAP/MOkUIZkCOhxFD +fI2E0uxQ8wyh0aewPETFTioG2iva9poT9v7bIVBaCG6W3YK+jU/4fs2HZQe/6jr1 +IXimv/KBWTSsv5LdIyhLRIDY0cPgyOZkgeFwkawAY4bPwh/CUcdqUEE= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/itext/itext.sign/itext/signatures/validation/v1/OCSPValidator.cs b/itext/itext.sign/itext/signatures/validation/v1/OCSPValidator.cs index 4358877144..8ece9e99b9 100644 --- a/itext/itext.sign/itext/signatures/validation/v1/OCSPValidator.cs +++ b/itext/itext.sign/itext/signatures/validation/v1/OCSPValidator.cs @@ -113,9 +113,9 @@ public virtual void Validate(ValidationReport report, ValidationContext context, return; } } - catch (Exception) { + catch (Exception e) { report.AddReportItem(new CertificateReportItem(certificate, OCSP_CHECK, UNABLE_TO_CHECK_IF_ISSUERS_MATCH, - ReportItem.ReportItemStatus.INDETERMINATE)); + e, ReportItem.ReportItemStatus.INDETERMINATE)); return; } // So, since the issuer name and serial number identify a unique certificate, we found the single response diff --git a/itext/itext.sign/itext/signatures/validation/v1/report/ValidationReport.cs b/itext/itext.sign/itext/signatures/validation/v1/report/ValidationReport.cs index 02f21cdda2..7d64b1de4e 100644 --- a/itext/itext.sign/itext/signatures/validation/v1/report/ValidationReport.cs +++ b/itext/itext.sign/itext/signatures/validation/v1/report/ValidationReport.cs @@ -23,6 +23,7 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Generic; using System.Linq; +using System.Text; using iText.Commons.Utils; namespace iText.Signatures.Validation.V1.Report { @@ -111,7 +112,13 @@ public virtual void AddReportItem(ReportItem item) { } public override String ToString() { - return "ValidationReport{" + "reportItems=" + reportItems + '}'; + StringBuilder sb = new StringBuilder("ValidationReport{validationResult="); + sb.Append(GetValidationResult()).Append("\nreportItems="); + foreach (ReportItem i in reportItems) { + sb.Append(i).Append(", "); + } + sb.Append("}"); + return sb.ToString(); } /// Enum representing possible validation results. diff --git a/port-hash b/port-hash index 59cf833bc3..877c0ead64 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -0a2ca4821d4c6e36a237a41ca08cefa72b277dbd +0b1301016918024ab739b2571607f5ee37488c1a From c7e75689d9f4fbd0d1a61cb9f24c005c4fcb3403 Mon Sep 17 00:00:00 2001 From: iText Software Date: Wed, 8 May 2024 18:00:03 +0000 Subject: [PATCH 2/3] Add missing copyright headers Autoported commit. Original commit hash: [4269d2c18] --- .../testutils/client/TestCrlClientWrapper.cs | 22 +++++++++++++++++++ .../testutils/client/TestOcspClientWrapper.cs | 22 +++++++++++++++++++ .../validation/v1/MockCrlValidator.cs | 22 +++++++++++++++++++ .../v1/MockIssuingCertificateRetriever.cs | 22 +++++++++++++++++++ .../validation/v1/MockOCSPValidator.cs | 22 +++++++++++++++++++ .../v1/MockRevocationDataValidator.cs | 22 +++++++++++++++++++ .../v1/MockSignatureValidationProperties.cs | 22 +++++++++++++++++++ .../RevocationDataValidatorIntegrationTest.cs | 22 +++++++++++++++++++ .../v1/report/ValidationReportTest.cs | 22 +++++++++++++++++++ port-hash | 2 +- 10 files changed, 199 insertions(+), 1 deletion(-) diff --git a/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs index 1e17d32785..04a56a3ead 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestCrlClientWrapper.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using System.IO; diff --git a/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs index 0f5856cc95..687103e839 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/testutils/client/TestOcspClientWrapper.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using iText.Bouncycastleconnector; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs index 5808b65866..595bccf057 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockCrlValidator.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using iText.Commons.Bouncycastle.Cert; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs index 8eaaedf492..9e3159f55d 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockIssuingCertificateRetriever.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using iText.Commons.Bouncycastle.Cert; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs index a2b2b52f35..854ea95230 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockOCSPValidator.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using iText.Commons.Bouncycastle.Asn1.Ocsp; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs index 38f8e1790d..c1fc20578f 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockRevocationDataValidator.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using iText.Commons.Bouncycastle.Cert; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs index 1e19933be7..5ef9d6ba52 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/MockSignatureValidationProperties.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using System.Collections.Generic; using iText.Signatures.Validation.V1.Context; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs index 5964ffc248..24edc017d2 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/RevocationDataValidatorIntegrationTest.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using iText.Bouncycastleconnector; using iText.Commons.Bouncycastle; diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs index b3e5bde0dd..edd75cfede 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/report/ValidationReportTest.cs @@ -1,3 +1,25 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ using System; using iText.Commons.Bouncycastle.Cert; using iText.Signatures.Testutils; diff --git a/port-hash b/port-hash index 877c0ead64..58b5c1e96f 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -0b1301016918024ab739b2571607f5ee37488c1a +4269d2c18c89982acf03d5e4fbc10abb9229642d From 630575a077d5f36ab57a62c6b1dd5d6ff42547fc Mon Sep 17 00:00:00 2001 From: Angelina Pavlovets Date: Thu, 9 May 2024 19:12:41 +0000 Subject: [PATCH 3/3] Support docMDP level 2 checks DEVSIX-8237 Autoported commit. Original commit hash: [6ef2c8cfc] --- .../v1/DocumentRevisionsValidatorTest.cs | 347 +++++++- .../addTextField.pdf | Bin 0 -> 82754 bytes .../addUnsignedSignatureField.pdf | Bin 0 -> 82526 bytes .../brokenSignatureFieldDictionary.pdf | Bin 0 -> 82945 bytes ...gnatureLevelBTest1.pdf => fillInField.pdf} | Bin 19072 -> 25358 bytes ...rect.pdf => makeFontDirectAndIndirect.pdf} | Bin 20834 -> 21987 bytes .../modifyPageAnnots.pdf | Bin 0 -> 82237 bytes .../multipleRevisionsDocument2.pdf | Bin 0 -> 77683 bytes .../removeAcroform.pdf | Bin 0 -> 82160 bytes .../removeDSS.pdf | Bin 0 -> 82165 bytes .../removeField.pdf | Bin 0 -> 82350 bytes .../removePermissions.pdf | Bin 0 -> 82152 bytes .../renameField.pdf | Bin 0 -> 82159 bytes .../forms/fields/PdfFormAnnotationUtil.cs | 1 + .../v1/DocumentRevisionsValidator.cs | 801 ++++++++++++++++-- port-hash | 2 +- 16 files changed, 1035 insertions(+), 116 deletions(-) create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/addTextField.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/addUnsignedSignatureField.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/brokenSignatureFieldDictionary.pdf rename itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/{padesSignatureLevelBTest1.pdf => fillInField.pdf} (64%) rename itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/{makePagesDirect.pdf => makeFontDirectAndIndirect.pdf} (77%) create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/modifyPageAnnots.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/multipleRevisionsDocument2.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeAcroform.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeDSS.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeField.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removePermissions.pdf create mode 100644 itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/renameField.pdf diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/DocumentRevisionsValidatorTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/DocumentRevisionsValidatorTest.cs index 86c9a806ea..8d86d2e9d9 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/v1/DocumentRevisionsValidatorTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/v1/DocumentRevisionsValidatorTest.cs @@ -43,8 +43,9 @@ public static void Before() { } [NUnit.Framework.Test] - public virtual void MultipleRevisionsDocument() { + public virtual void MultipleRevisionsDocumentLevel1Test() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "multipleRevisionsDocument.pdf" ))) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); @@ -56,12 +57,13 @@ public virtual void MultipleRevisionsDocument() { validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[1]); } } - // Between these two revisions DSS is added, which is allowed, but also timestamp is added, which is not yet allowed. + // Between these two revisions DSS and timestamp are added, it is allowed. + // But PDF is generated with extra annotation (it was a bug). NUnit.Framework.Assert.AreEqual(1, validationReport.GetFailures().Count); ReportItem reportItem1 = validationReport.GetFailures()[0]; NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_CATALOG_CHANGES, reportItem1.GetMessage - ()); + NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(DocumentRevisionsValidator.UNEXPECTED_ENTRY_IN_XREF + , 27), reportItem1.GetMessage()); NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem1.GetStatus()); using (Stream inputStream_1 = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions [1])) { @@ -77,6 +79,7 @@ public virtual void MultipleRevisionsDocument() { [NUnit.Framework.Test] public virtual void HugeDocumentTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "hugeDocument.pdf"))) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); IList documentRevisions = revisionsReader.GetAllRevisions(); @@ -94,6 +97,7 @@ public virtual void HugeDocumentTest() { [NUnit.Framework.Test] public virtual void ExtensionsModificationsTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "extensionsModifications.pdf") )) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); @@ -159,6 +163,7 @@ public virtual void ExtensionsModificationsTest() { [NUnit.Framework.Test] public virtual void CompletelyInvalidDocumentTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "completelyInvalidDocument.pdf" ))) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); @@ -173,16 +178,17 @@ public virtual void CompletelyInvalidDocumentTest() { NUnit.Framework.Assert.AreEqual(1, validationReport.GetFailures().Count); ReportItem reportItem = validationReport.GetFailures()[0]; NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem.GetCheckName()); - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_CATALOG_CHANGES, reportItem.GetMessage - ()); + NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.PAGES_MODIFIED, reportItem.GetMessage()); NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem.GetStatus()); } } [NUnit.Framework.Test] - public virtual void MakePagesEntryDirectAndIndirectTest() { + public virtual void MakeFontDirectAndIndirectTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); - using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "makePagesDirect.pdf"))) { + validator.docMDP = 1; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "makeFontDirectAndIndirect.pdf" + ))) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); IList documentRevisions = revisionsReader.GetAllRevisions(); ValidationReport validationReport; @@ -193,12 +199,17 @@ public virtual void MakePagesEntryDirectAndIndirectTest() { } } // Adobe Acrobat doesn't complain about such change. We consider this incorrect. - NUnit.Framework.Assert.AreEqual(1, validationReport.GetFailures().Count); + NUnit.Framework.Assert.AreEqual(2, validationReport.GetFailures().Count); ReportItem reportItem1 = validationReport.GetFailures()[0]; NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_CATALOG_CHANGES, reportItem1.GetMessage - ()); + NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(DocumentRevisionsValidator.FIELD_REMOVED, "Signature1" + ), reportItem1.GetMessage()); NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem1.GetStatus()); + ReportItem reportItem2 = validationReport.GetFailures()[1]; + NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem2.GetCheckName()); + NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_ACROFORM_CHANGES, reportItem2.GetMessage + ()); + NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem2.GetStatus()); using (Stream inputStream_1 = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions [1])) { using (PdfDocument previousDocument_1 = new PdfDocument(new PdfReader(inputStream_1))) { @@ -206,10 +217,15 @@ public virtual void MakePagesEntryDirectAndIndirectTest() { } } // Adobe Acrobat doesn't complain about such change. We consider this incorrect. - NUnit.Framework.Assert.AreEqual(1, validationReport.GetFailures().Count); - ReportItem reportItem2 = validationReport.GetFailures()[0]; + NUnit.Framework.Assert.AreEqual(2, validationReport.GetFailures().Count); + reportItem1 = validationReport.GetFailures()[0]; + NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem1.GetCheckName()); + NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(DocumentRevisionsValidator.FIELD_REMOVED, "Signature1" + ), reportItem1.GetMessage()); + NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem1.GetStatus()); + reportItem2 = validationReport.GetFailures()[1]; NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem2.GetCheckName()); - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_CATALOG_CHANGES, reportItem2.GetMessage + NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_ACROFORM_CHANGES, reportItem2.GetMessage ()); NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem2.GetStatus()); } @@ -218,6 +234,7 @@ public virtual void MakePagesEntryDirectAndIndirectTest() { [NUnit.Framework.Test] public virtual void RandomEntryAddedTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "randomEntryAdded.pdf"))) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); IList documentRevisions = revisionsReader.GetAllRevisions(); @@ -241,6 +258,7 @@ public virtual void RandomEntryAddedTest() { [NUnit.Framework.Test] public virtual void RandomEntryWithoutUsageTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "randomEntryWithoutUsage.pdf") )) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); @@ -265,6 +283,7 @@ public virtual void RandomEntryWithoutUsageTest() { [NUnit.Framework.Test] public virtual void ChangeExistingFontTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "changeExistingFont.pdf"))) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); IList documentRevisions = revisionsReader.GetAllRevisions(); @@ -275,18 +294,17 @@ public virtual void ChangeExistingFontTest() { validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[1]); } } - NUnit.Framework.Assert.AreEqual(1, validationReport.GetFailures().Count); - ReportItem reportItem1 = validationReport.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_CATALOG_CHANGES, reportItem1.GetMessage - ()); - NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem1.GetStatus()); + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.PAGE_MODIFIED).WithStatus(ReportItem.ReportItemStatus + .INVALID))); } } [NUnit.Framework.Test] public virtual void ChangeExistingFontAndAddAsDssTest() { DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "changeExistingFontAndAddAsDss.pdf" ))) { PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); @@ -299,12 +317,289 @@ public virtual void ChangeExistingFontAndAddAsDssTest() { } } // Adobe Acrobat doesn't complain about such change. We consider this incorrect. - NUnit.Framework.Assert.AreEqual(1, validationReport.GetFailures().Count); - ReportItem reportItem1 = validationReport.GetFailures()[0]; - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.DOC_MDP_CHECK, reportItem1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(DocumentRevisionsValidator.NOT_ALLOWED_CATALOG_CHANGES, reportItem1.GetMessage - ()); - NUnit.Framework.Assert.AreEqual(ReportItem.ReportItemStatus.INVALID, reportItem1.GetStatus()); + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.PAGE_MODIFIED).WithStatus(ReportItem.ReportItemStatus + .INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void FillInFieldAtLevel1Test() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 1; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "fillInField.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [0])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[1]); + } + } + // Between these two revisions forms were filled in, it is not allowed at docMDP level 1. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(2).HasNumberOfLogs(2).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.FIELD_REMOVED, (i) => "input").WithStatus(ReportItem.ReportItemStatus + .INVALID)).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator.DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator + .NOT_ALLOWED_ACROFORM_CHANGES).WithStatus(ReportItem.ReportItemStatus.INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void MultipleRevisionsDocumentLevel2Test() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "multipleRevisionsDocument2.pdf" + ))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [0])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[1]); + } + } + // Between these two revisions forms were filled in, it is allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID + ).HasNumberOfFailures(0).HasNumberOfLogs(0)); + using (Stream inputStream_1 = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [1])) { + using (PdfDocument previousDocument_1 = new PdfDocument(new PdfReader(inputStream_1))) { + validationReport = validator.ValidateRevision(document, previousDocument_1, documentRevisions[2]); + } + } + // Between these two revisions existing signature field was signed, it is allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID + ).HasNumberOfFailures(0).HasNumberOfLogs(0)); + using (Stream inputStream_2 = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [2])) { + using (PdfDocument previousDocument_2 = new PdfDocument(new PdfReader(inputStream_2))) { + validationReport = validator.ValidateRevision(document, previousDocument_2, documentRevisions[3]); + } + } + // Between these two revisions newly added signature field was signed, it is allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID + ).HasNumberOfFailures(0).HasNumberOfLogs(0)); + } + } + + [NUnit.Framework.Test] + public virtual void RemovePermissionsTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "removePermissions.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions /Perms key was removed, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.PERMISSIONS_REMOVED).WithStatus(ReportItem.ReportItemStatus + .INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void RemoveDSSTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "removeDSS.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions /DSS key was removed, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.DSS_REMOVED).WithStatus(ReportItem.ReportItemStatus + .INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void RemoveAcroformTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "removeAcroform.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions /Acroform key was removed, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.ACROFORM_REMOVED).WithStatus(ReportItem.ReportItemStatus + .INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void RemoveFieldTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "removeField.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions field was removed, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.NOT_ALLOWED_ACROFORM_CHANGES).WithStatus(ReportItem.ReportItemStatus + .INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void RenameFieldTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "renameField.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions field was renamed, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(2).HasNumberOfLogs(2).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.FIELD_REMOVED, (i) => "input").WithStatus(ReportItem.ReportItemStatus + .INVALID)).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator.DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator + .NOT_ALLOWED_ACROFORM_CHANGES).WithStatus(ReportItem.ReportItemStatus.INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void AddTextFieldTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "addTextField.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions new field was added, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(2).HasNumberOfLogs(2).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.UNEXPECTED_FORM_FIELD, (i) => "text").WithStatus + (ReportItem.ReportItemStatus.INVALID)).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator.DOC_MDP_CHECK + ).WithMessage(DocumentRevisionsValidator.NOT_ALLOWED_ACROFORM_CHANGES).WithStatus(ReportItem.ReportItemStatus + .INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void AddUnsignedSignatureFieldTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "addUnsignedSignatureField.pdf" + ))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions new unsigned signature field was added, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(2).HasNumberOfLogs(2).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.UNEXPECTED_FORM_FIELD, (i) => "signature").WithStatus + (ReportItem.ReportItemStatus.INVALID)).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator.DOC_MDP_CHECK + ).WithMessage(DocumentRevisionsValidator.NOT_ALLOWED_ACROFORM_CHANGES).WithStatus(ReportItem.ReportItemStatus + .INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void BrokenSignatureFieldDictionaryTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "brokenSignatureFieldDictionary.pdf" + ))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions signature value was replaced by text, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(3).HasNumberOfLogs(3).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.SIGNATURE_MODIFIED, (i) => "Signature1").WithStatus + (ReportItem.ReportItemStatus.INVALID)).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator.DOC_MDP_CHECK + ).WithMessage(DocumentRevisionsValidator.FIELD_REMOVED, (i) => "Signature1").WithStatus(ReportItem.ReportItemStatus + .INVALID)).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator.DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator + .NOT_ALLOWED_ACROFORM_CHANGES).WithStatus(ReportItem.ReportItemStatus.INVALID))); + } + } + + [NUnit.Framework.Test] + public virtual void ModifyPageAnnotsTest() { + DocumentRevisionsValidator validator = new DocumentRevisionsValidator(); + validator.docMDP = 2; + using (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "modifyPageAnnots.pdf"))) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(document.GetReader()); + IList documentRevisions = revisionsReader.GetAllRevisions(); + ValidationReport validationReport; + using (Stream inputStream = DocumentRevisionsValidator.CreateInputStreamFromRevision(document, documentRevisions + [documentRevisions.Count - 2])) { + using (PdfDocument previousDocument = new PdfDocument(new PdfReader(inputStream))) { + validationReport = validator.ValidateRevision(document, previousDocument, documentRevisions[documentRevisions + .Count - 1]); + } + } + // Between these two revisions circle annotation was added to the first page, it is not allowed. + AssertValidationReport.AssertThat(validationReport, (a) => a.HasStatus(ValidationReport.ValidationResult.INVALID + ).HasNumberOfFailures(1).HasNumberOfLogs(1).HasLogItem((l) => l.WithCheckName(DocumentRevisionsValidator + .DOC_MDP_CHECK).WithMessage(DocumentRevisionsValidator.PAGE_ANNOTATIONS_MODIFIED).WithStatus(ReportItem.ReportItemStatus + .INVALID))); } } } diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/addTextField.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/addTextField.pdf new file mode 100644 index 0000000000000000000000000000000000000000..81eaeb6b4e07607533cae8588f4c1fa2afb6e79a GIT binary patch literal 82754 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 O>vo>4Dvp`k&ij9dF}`E~ literal 0 HcmV?d00001 diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/addUnsignedSignatureField.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/addUnsignedSignatureField.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c45881fb5fe20d5313049d59d5eebee8703d0904 GIT binary patch literal 82526 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 Z>4Kan+D+`V8)+)t-Hl|cQ_hhS{XawMCQ|?a literal 0 HcmV?d00001 diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/padesSignatureLevelBTest1.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/fillInField.pdf similarity index 64% rename from itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/padesSignatureLevelBTest1.pdf rename to itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/fillInField.pdf index 286726cfc26d1126ff3693bb81ce1970b289b7e2..6df17c07af63825ed2d5785ab44148ccaeb4d084 100644 GIT binary patch delta 6512 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 delta 1753 zcmZWp+iz4w7^h8%bp=8NL^0@Tpa~6W&)hGjZn~wHT_RW;6sAA@8^d#i!5Kk+GBt%qf5dab%5GSYeKY7XUfu z8Bx+8fs7HDXauN*gh(Ka_Xc1}Br;Lxjj@tQ9S`cYj_H^Y09uM2g#ck6&*_mggfb@~ zfs!ypl*U}usWDOkBfPYdV`{unmII`y)6ye_n2Ht27%tPa;cSvQ? zjU!Y?7e_tSLIT8q)zAq-1p!=p81o55z0!_*$RJgMLT2m8A_^(hfZ&*!DTuW&N;0c7 zT}=X;sHoS}Yh50*lyC#v;r>YL5W-!YkkeX21YuqC|2+nARR>gf-SY7OuRH z@oa<&p{QmVG6qu18RSr8&|=6$Y-ARI2@

5IKUew?I1G#30gF7uJ5aHJt2jUDKZK z*wXtF0$FVU$Id|khNUwGBL+ zC~jXr`u2=Vx2c+%s_f6_b8gyZSivw>`Y06k>|Mhjq1Qj@rtIvP4EgOF!nr-`Lf_D) z@bT!zMo7Yy?L*lI4Y6>3c;i!O5yfegKpItPIJpZnzyOCtBNCxJ_-x!{;>$ai*AXa# zraPaHUYfbob*ZQGtJR(F@NJ*{c;VQ&p5lfxb05rKym0YYPce7qxG5Bd-hEY{>M0#K zbix;(Idi=6!&(IL=WlM_?!JDz>D#LUznotA;`=STK50Jq>CrD=-|t_FU<7 z?p*8qy0)V;6Yq~NJ3HO+cUEcEyA`o{4~j@^g|g%aET1Ch(q=~vSV_oFXQrmz8hjUDPQ8u83W;k@y$()aTNX;+f;{~;3Z}P z}lX2#?zdz~TfvkkzVca>`Y+(JA#NV_g&QBqA@3E5982!=45MBv9Z44;A()|&0hN$Qsz|xU z5^AY-kzkk*P%!2YA(6Q}S56aWG?EBm$(_W&DeR!uoNJVyNK4bck}S@6I$WvLl8A1c m>5<8!E`j0CiM6fd;rQfcYpPm1QguF;F(1gawe{`n&;1MDq3&b= diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/makePagesDirect.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/makeFontDirectAndIndirect.pdf similarity index 77% rename from itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/makePagesDirect.pdf rename to itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/makeFontDirectAndIndirect.pdf index c5fc4fc4339edd612496d45bbc61922a3df07838..9d180f3c53610c380a6c810198adbb0353a25380 100644 GIT binary patch delta 2459 zcmZ`*&u?8<5!MSTQePt)S8mmY2ySUDpeFH|Idf*tNS5VL+jUYYjoJtnD52-4R3u_Z zqpD@|wrq-I?mvLoAr`303bA5`#G**75DP@QVaJL+bD#6Wv75Je@7_6c&dm8{zHi?B z-_E}H{p{S@^_{(oa7pHC_rLi3?{heO{`}Wq01hYvD(i6b@UlO=K0mzs%`<}I=PNI+ zU~&;%V(*u~p&$O~wVD6yU-zG%hrG05knykQ{`s7D!8!AW3gc%hyrAybDW=F2BX@Dt zfm25w2py5@xn+nbf|aW4T3jpCLhh*Ai~3TW<5r|%N!}+Y(~@NHz}>cyWUWD?mJHgk zR!^}+d+#Aq^KH#xrfNPV7EoywHB^Y+q1Rvmyk*ZczWu_57ecVy?ScWkqwbx{QOCFjvvcshU|rYYHU9J~K1*;6lwDpmyb&Qg^jfjB0nO345ZF z+0u+i9i8~_KvS?rE+$u zd>ykRH`83yGaoLPFP^MJQwk(|xnW2kQ(c zbx{S`e5w^gyLj%{tbi8FPQcolM|CA@X{m?{I$=w!g~5tS6|2FYnmQk2%?X-l;;JP| zs8V_emKrnw14?6#p(iih4)E1N<&tm_Jah0j6<3*eRK?kb0|(&(;xa=ryajEu|DVR}og{YFjYdp@N|| zun^V+u2$mIAXx(#oTy%#5>{7J73XtOM5?7=HEZjYny?kxdNmqyK%NbmccA1uGacnhQ@ZTU+yXyG{!hXnps+Gl(PXZH#}|{nqrjzRTmYdl$~4 zo18ap}tSja?c4viJ3w+PfQ9-}==X(}52{EVKC0cb1X9J8W)lZQVQ;$t}Y}f)FCDStwzh}N^Qn)Rj!mUQX%w`2Pv}!JMJvW{V+vyf zjy4j+*c@km{NB}Xyo6-!vi8A^_VL||Zywxf7vm*&DU9!5ed#MgG=6e*|A-1`d5ov2 z@R$(KJ?hj~##eXt$N%j8adi97eA^H3pwX1{SOejs1-n1M5)v$6G!GKcB+EvfDVAx6 z$x11#Ca@fGj4j!mG7a(!KBCFAsan&NO|vPPSfNce>Lr;RioqL94|R#%ghZi8*0Y8y zFh=4SinExej-10bZQb0sa=RZ4)<;Id>v!5m1K26F@$SLF-N9;Z*7rX6MH`+9+v+pu zJn3WLY--9=f%`J}&QDxDV*01kBf#7JOVC|{bsF99l%`xgJL$EXudVIAvp4@gD_nzs delta 2089 zcmbtVO>3P+6eaaba;)yHdo3OBEDcy3rQFrTqtu+1S3x zrP=u@oOXlBTepAyb+EPn*{5KLVo8Q%jSMer&6usF-COUzFiy7ra^T5}w*Wc1k%U2U?i zL5scy$=LzgU^x|uSc0#Jq$@NIi;b9KvDsE`!~s1T4$@y9dwvs)CXN7(2?B{;UZbPXvWYf&i(1D*7OhQMIXzi{0-+{BN;f|^_~haK$-z(iwPS~OYi?RH z)dPElB@seqt}u5QgWDT&5w~zJKA3aeb{Vyr!G?FF^l$@nc=xn0Nh;lNZNP z`pl_`O-4I}(QkNpIM`mGvxGZrAR7DOeG+#1iVHh^t=r-2L|_!Je|~znSLFVN{`TpK z6=!&dhi7(=-oQE7d3*2p(S07o^y_C|*=!@Kq+F`cR3f&R6I0}tlxaz*wP>`jRAffn z65CQt+Gs#3LD?$nqYo6KwplL57MrS)O1z4eNF`&Gt`kbE~TxL&Zy%%a`7-s-;Jd4kjU@3uVWgJU>wClPr2h|S zDk5P7+OF*$R9)(8XMR-7&OeCTu5H}37X6#qi81L%=IF%3;E^`{BQw~KZ zgpL=bTJ=77eJYmBQFM5em~AvVKdRROk{w29SZXkD+Q*?fLS-ka?!Qt|lk+^7oSZv5 GKlle$qAjHW diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/modifyPageAnnots.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/modifyPageAnnots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c7f4a173c9c988fb1199b732466a3002d0164b0d GIT binary patch literal 82237 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 literal 0 HcmV?d00001 diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/multipleRevisionsDocument2.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/multipleRevisionsDocument2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cafcfb94c3d6456a3a08427b0eae7815a3e9da0e GIT binary patch literal 77683 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 AhyVZp literal 0 HcmV?d00001 diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeAcroform.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeAcroform.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4c1e90b7044d62b566746be493377df206121055 GIT binary patch literal 82160 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 literal 0 HcmV?d00001 diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeDSS.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeDSS.pdf new file mode 100644 index 0000000000000000000000000000000000000000..63f8ad0a415106b205970859672477a1f2c0e739 GIT binary patch literal 82165 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 literal 0 HcmV?d00001 diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeField.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/removeField.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7ffac83690a105a0820b150670e04ed20270591a GIT binary patch literal 82350 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 literal 0 HcmV?d00001 diff --git a/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/renameField.pdf b/itext.tests/itext.sign.tests/resources/itext/signatures/validation/v1/DocumentRevisionsValidatorTest/renameField.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5292794a093919f2434cd6efff1a530790ded12e GIT binary patch literal 82159 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 literal 0 HcmV?d00001 diff --git a/itext/itext.forms/itext/forms/fields/PdfFormAnnotationUtil.cs b/itext/itext.forms/itext/forms/fields/PdfFormAnnotationUtil.cs index 49c8a0229a..59db7dd841 100644 --- a/itext/itext.forms/itext/forms/fields/PdfFormAnnotationUtil.cs +++ b/itext/itext.forms/itext/forms/fields/PdfFormAnnotationUtil.cs @@ -102,6 +102,7 @@ public static void MergeWidgetWithParentField(PdfFormField field) { kidDict.Remove(PdfName.Parent); field.GetPdfObject().MergeDifferent(kidDict); field.RemoveChildren(); + kidDict.GetIndirectReference().SetFree(); field.SetChildField(PdfFormAnnotation.MakeFormAnnotation(field.GetPdfObject(), field.GetDocument())); ReplaceAnnotationOnPage(kidDict, field.GetPdfObject()); } diff --git a/itext/itext.sign/itext/signatures/validation/v1/DocumentRevisionsValidator.cs b/itext/itext.sign/itext/signatures/validation/v1/DocumentRevisionsValidator.cs index 3ab770af81..e82dbc7215 100644 --- a/itext/itext.sign/itext/signatures/validation/v1/DocumentRevisionsValidator.cs +++ b/itext/itext.sign/itext/signatures/validation/v1/DocumentRevisionsValidator.cs @@ -26,6 +26,8 @@ You should have received a copy of the GNU Affero General Public License using System.Linq; using iText.Commons.Actions.Contexts; using iText.Commons.Utils; +using iText.Forms; +using iText.Forms.Fields; using iText.IO.Source; using iText.Kernel.Pdf; using iText.Signatures.Validation.V1.Report; @@ -35,20 +37,56 @@ namespace iText.Signatures.Validation.V1 { internal class DocumentRevisionsValidator { internal const String DOC_MDP_CHECK = "DocMDP check."; - internal const String NOT_ALLOWED_CATALOG_CHANGES = "PDF document catalog contains changes other than DSS dictionary addition, which is not allowed."; + internal const String ACROFORM_REMOVED = "AcroForm dictionary was removed from catalog."; + + internal const String ANNOTATIONS_MODIFIED = "Field annotations were removed, added or unexpectedly modified."; + + internal const String DEVELOPER_EXTENSION_REMOVED = "Developer extension \"{0}\" dictionary was removed or unexpectedly modified."; + + internal const String DIRECT_OBJECT = "{0} must be an indirect reference."; internal const String DSS_REMOVED = "DSS dictionary was removed from catalog."; - internal const String EXTENSIONS_REMOVED = "Extensions dictionary was removed from catalog."; + internal const String EXTENSIONS_REMOVED = "Extensions dictionary was removed from the catalog."; - internal const String DEVELOPER_EXTENSION_REMOVED = "Developer extension \"{0}\" dictionary was removed or unexpectedly modified."; + internal const String EXTENSIONS_TYPE = "Developer extensions must be a dictionary."; internal const String EXTENSION_LEVEL_DECREASED = "Extension level number in developer extension \"{0}\" dictionary was decreased."; + internal const String FIELD_REMOVED = "Form field {0} was removed or unexpectedly modified."; + + internal const String NOT_ALLOWED_ACROFORM_CHANGES = "PDF document AcroForm contains changes other than " + + "document timestamp (docMDP level >= 1), form fill-in and digital signatures (docMDP level >= 2), " + + "adding or editing annotations (docMDP level 3), which are not allowed."; + + internal const String NOT_ALLOWED_CATALOG_CHANGES = "PDF document catalog contains changes other than " + + "DSS dictionary and DTS addition (docMDP level >= 1), " + "form fill-in and digital signatures (docMDP level >= 2), " + + "adding or editing annotations (docMDP level 3), which are not allowed."; + internal const String OBJECT_REMOVED = "Object \"{0}\", which is not allowed to be removed, was removed from the document through XREF table."; + internal const String PAGES_MODIFIED = "Pages structure was unexpectedly modified."; + + internal const String PAGE_ANNOTATIONS_MODIFIED = "Page annotations were unexpectedly modified."; + + internal const String PAGE_MODIFIED = "Page was unexpectedly modified."; + + internal const String PERMISSIONS_REMOVED = "Permissions dictionary was removed from the catalog."; + + internal const String PERMISSIONS_TYPE = "Permissions must be a dictionary."; + + internal const String PERMISSION_REMOVED = "Permission \"{0}\" dictionary was removed or unexpectedly modified."; + + internal const String REFERENCE_REMOVED = "Signature reference dictionary was removed or unexpectedly modified."; + + internal const String SIGNATURE_MODIFIED = "Signature {0} was unexpectedly modified."; + internal const String UNEXPECTED_ENTRY_IN_XREF = "New PDF document revision contains unexpected entry \"{0}\" in XREF table."; + internal const String UNEXPECTED_FORM_FIELD = "New PDF document revision contains unexpected form field \"{0}\"."; + + internal int docMDP = 2; + private IMetaInfo metaInfo; internal DocumentRevisionsValidator() { @@ -67,6 +105,14 @@ public virtual void SetEventCountingMetaInfo(IMetaInfo metaInfo) { this.metaInfo = metaInfo; } + internal static Stream CreateInputStreamFromRevision(PdfDocument originalDocument, DocumentRevision revision + ) { + RandomAccessFileOrArray raf = originalDocument.GetReader().GetSafeFile(); + WindowRandomAccessSource source = new WindowRandomAccessSource(raf.CreateSourceView(), 0, revision.GetEofOffset + ()); + return new RASInputStream(source); + } + internal virtual ValidationReport ValidateRevision(PdfDocument originalDocument, PdfDocument documentWithoutRevision , DocumentRevision revision) { ValidationReport validationReport = new ValidationReport(); @@ -112,15 +158,23 @@ internal virtual ValidationReport ValidateRevision(PdfDocument originalDocument, return validationReport; } - private bool CheckAllowedReferences(IList allowedReferences, PdfIndirectReference - indirectReference, PdfDocument documentWithoutRevision) { - foreach (DocumentRevisionsValidator.ReferencesPair allowedReference in allowedReferences) { - if (IsSameReference(allowedReference.GetCurrentReference(), indirectReference)) { - return documentWithoutRevision.GetPdfObject(indirectReference.GetObjNumber()) == null || allowedReferences - .Any((reference) => IsSameReference(reference.GetPreviousReference(), indirectReference)); - } + private bool CompareCatalogs(PdfDocument documentWithoutRevision, PdfDocument documentWithRevision, ValidationReport + report) { + PdfDictionary previousCatalog = documentWithoutRevision.GetCatalog().GetPdfObject(); + PdfDictionary currentCatalog = documentWithRevision.GetCatalog().GetPdfObject(); + PdfDictionary previousCatalogCopy = CopyCatalogEntriesToCompare(previousCatalog); + PdfDictionary currentCatalogCopy = CopyCatalogEntriesToCompare(currentCatalog); + if (!ComparePdfObjects(previousCatalogCopy, currentCatalogCopy)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, NOT_ALLOWED_CATALOG_CHANGES, ReportItem.ReportItemStatus + .INVALID)); + return false; } - return false; + return CompareExtensions(previousCatalog.Get(PdfName.Extensions), currentCatalog.Get(PdfName.Extensions), + report) && ComparePermissions(previousCatalog.Get(PdfName.Perms), currentCatalog.Get(PdfName.Perms), report + ) && CompareDss(previousCatalog.Get(PdfName.DSS), currentCatalog.Get(PdfName.DSS), report) && ComparePages + (previousCatalog.GetAsDictionary(PdfName.Pages), currentCatalog.GetAsDictionary(PdfName.Pages), report + ) && CompareAcroForms(previousCatalog.GetAsDictionary(PdfName.AcroForm), currentCatalog.GetAsDictionary + (PdfName.AcroForm), report); } private IList CreateAllowedReferences(PdfDocument documentWithRevision @@ -148,17 +202,505 @@ private bool CheckAllowedReferences(IList previousDssDictionary.GetIndirectReference()))); + allowedReferences.AddAll(CreateAllowedDssEntries(documentWithRevision, documentWithoutRevision)); + } + PdfDictionary currentAcroForm = documentWithRevision.GetCatalog().GetPdfObject().GetAsDictionary(PdfName.AcroForm + ); + if (currentAcroForm != null) { + PdfDictionary previousAcroForm = documentWithoutRevision.GetCatalog().GetPdfObject().GetAsDictionary(PdfName + .AcroForm); + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentAcroForm.GetIndirectReference() + , GetIndirectReferenceOrNull(() => previousAcroForm.GetIndirectReference()))); + allowedReferences.AddAll(CreateAllowedAcroFormEntries(documentWithRevision, documentWithoutRevision)); + } + PdfDictionary currentPagesDictionary = documentWithRevision.GetCatalog().GetPdfObject().GetAsDictionary(PdfName + .Pages); + if (currentPagesDictionary != null) { + PdfDictionary previousPagesDictionary = documentWithoutRevision.GetCatalog().GetPdfObject().GetAsDictionary + (PdfName.Pages); + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentPagesDictionary.GetIndirectReference + (), GetIndirectReferenceOrNull(() => previousPagesDictionary.GetIndirectReference()))); + allowedReferences.AddAll(CreateAllowedPagesEntries(currentPagesDictionary, previousPagesDictionary)); } - allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentDssDictionary.GetIndirectReference - (), GetIndirectReferenceOrNull(() => previousDssDictionary.GetIndirectReference()))); - allowedReferences.AddAll(CreateAllowedDssEntries(documentWithRevision, documentWithoutRevision)); return allowedReferences; } + private bool CheckAllowedReferences(IList allowedReferences, PdfIndirectReference + indirectReference, PdfDocument documentWithoutRevision) { + foreach (DocumentRevisionsValidator.ReferencesPair allowedReference in allowedReferences) { + if (IsSameReference(allowedReference.GetCurrentReference(), indirectReference)) { + return documentWithoutRevision.GetPdfObject(indirectReference.GetObjNumber()) == null || allowedReferences + .Any((reference) => IsSameReference(reference.GetPreviousReference(), indirectReference)); + } + } + return false; + } + + // Compare catalogs nested methods section: + private bool CompareExtensions(PdfObject previousExtensions, PdfObject currentExtensions, ValidationReport + report) { + if (previousExtensions == null || ComparePdfObjects(previousExtensions, currentExtensions)) { + return true; + } + if (currentExtensions == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, EXTENSIONS_REMOVED, ReportItem.ReportItemStatus.INVALID + )); + return false; + } + if (!(previousExtensions is PdfDictionary) || !(currentExtensions is PdfDictionary)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, EXTENSIONS_TYPE, ReportItem.ReportItemStatus.INVALID)); + return false; + } + PdfDictionary previousExtensionsDictionary = (PdfDictionary)previousExtensions; + PdfDictionary currentExtensionsDictionary = (PdfDictionary)currentExtensions; + foreach (KeyValuePair previousExtension in previousExtensionsDictionary.EntrySet()) { + PdfDictionary currentExtension = currentExtensionsDictionary.GetAsDictionary(previousExtension.Key); + if (currentExtension == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(DEVELOPER_EXTENSION_REMOVED, previousExtension + .Key), ReportItem.ReportItemStatus.INVALID)); + return false; + } + else { + PdfDictionary currentExtensionCopy = new PdfDictionary(currentExtension); + currentExtensionCopy.Remove(PdfName.ExtensionLevel); + PdfDictionary previousExtensionCopy = new PdfDictionary((PdfDictionary)previousExtension.Value); + previousExtensionCopy.Remove(PdfName.ExtensionLevel); + // Apart from extension level dictionaries are expected to be equal. + if (!ComparePdfObjects(previousExtensionCopy, currentExtensionCopy)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(DEVELOPER_EXTENSION_REMOVED, previousExtension + .Key), ReportItem.ReportItemStatus.INVALID)); + return false; + } + PdfNumber previousExtensionLevel = ((PdfDictionary)previousExtension.Value).GetAsNumber(PdfName.ExtensionLevel + ); + PdfNumber currentExtensionLevel = currentExtension.GetAsNumber(PdfName.ExtensionLevel); + if (previousExtensionLevel != null) { + if (currentExtensionLevel == null || previousExtensionLevel.IntValue() > currentExtensionLevel.IntValue()) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(EXTENSION_LEVEL_DECREASED, previousExtension + .Key), ReportItem.ReportItemStatus.INVALID)); + return false; + } + } + } + } + return true; + } + + private bool ComparePermissions(PdfObject previousPerms, PdfObject currentPerms, ValidationReport report) { + if (previousPerms == null || ComparePdfObjects(previousPerms, currentPerms)) { + return true; + } + if (currentPerms == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, PERMISSIONS_REMOVED, ReportItem.ReportItemStatus.INVALID + )); + return false; + } + if (!(previousPerms is PdfDictionary) || !(currentPerms is PdfDictionary)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, PERMISSIONS_TYPE, ReportItem.ReportItemStatus.INVALID)); + return false; + } + PdfDictionary previousPermsDictionary = (PdfDictionary)previousPerms; + PdfDictionary currentPermsDictionary = (PdfDictionary)currentPerms; + foreach (KeyValuePair previousPermission in previousPermsDictionary.EntrySet()) { + PdfDictionary currentPermission = currentPermsDictionary.GetAsDictionary(previousPermission.Key); + if (currentPermission == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(PERMISSION_REMOVED, previousPermission + .Key), ReportItem.ReportItemStatus.INVALID)); + return false; + } + else { + // Perms dictionary is the signature dictionary. + if (!CompareSignatureDictionaries(previousPermission.Value, currentPermission, report)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(PERMISSION_REMOVED, previousPermission + .Key), ReportItem.ReportItemStatus.INVALID)); + return false; + } + } + } + return true; + } + + private bool CompareDss(PdfObject previousDss, PdfObject currentDss, ValidationReport report) { + if (previousDss == null) { + return true; + } + if (currentDss == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, DSS_REMOVED, ReportItem.ReportItemStatus.INVALID)); + return false; + } + return true; + } + + private bool ComparePages(PdfDictionary prevPages, PdfDictionary currPages, ValidationReport report) { + if (prevPages == null ^ currPages == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, PAGES_MODIFIED, ReportItem.ReportItemStatus.INVALID)); + return false; + } + if (prevPages == null) { + return true; + } + PdfDictionary previousPagesCopy = new PdfDictionary(prevPages); + previousPagesCopy.Remove(PdfName.Kids); + previousPagesCopy.Remove(PdfName.Parent); + PdfDictionary currentPagesCopy = new PdfDictionary(currPages); + currentPagesCopy.Remove(PdfName.Kids); + currentPagesCopy.Remove(PdfName.Parent); + if (!ComparePdfObjects(previousPagesCopy, currentPagesCopy) || !CompareIndirectReferencesObjNums(prevPages + .Get(PdfName.Parent), currPages.Get(PdfName.Parent), report, "Page tree node parent")) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, PAGES_MODIFIED, ReportItem.ReportItemStatus.INVALID)); + return false; + } + PdfArray prevKids = prevPages.GetAsArray(PdfName.Kids); + PdfArray currKids = currPages.GetAsArray(PdfName.Kids); + if (prevKids.Size() != currKids.Size()) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, PAGES_MODIFIED, ReportItem.ReportItemStatus.INVALID)); + return false; + } + for (int i = 0; i < currKids.Size(); ++i) { + PdfDictionary previousKid = prevKids.GetAsDictionary(i); + PdfDictionary currentKid = currKids.GetAsDictionary(i); + if (PdfName.Pages.Equals(previousKid.GetAsName(PdfName.Type))) { + // Compare page tree nodes. + if (!ComparePages(previousKid, currentKid, report)) { + return false; + } + } + else { + // Compare page objects (leaf node in the page tree). + PdfDictionary previousPageCopy = new PdfDictionary(previousKid); + previousPageCopy.Remove(PdfName.Annots); + previousPageCopy.Remove(PdfName.Parent); + PdfDictionary currentPageCopy = new PdfDictionary(currentKid); + currentPageCopy.Remove(PdfName.Annots); + currentPageCopy.Remove(PdfName.Parent); + if (!ComparePdfObjects(previousPageCopy, currentPageCopy) || !CompareIndirectReferencesObjNums(previousKid + .Get(PdfName.Parent), currentKid.Get(PdfName.Parent), report, "Page parent")) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, PAGE_MODIFIED, ReportItem.ReportItemStatus.INVALID)); + return false; + } + PdfArray prevAnnots = GetAnnotsNotAllowedToBeModified(previousKid); + PdfArray currAnnots = GetAnnotsNotAllowedToBeModified(currentKid); + if (!ComparePdfObjects(prevAnnots, currAnnots)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, PAGE_ANNOTATIONS_MODIFIED, ReportItem.ReportItemStatus. + INVALID)); + return false; + } + } + } + return true; + } + + private bool CompareAcroForms(PdfDictionary prevAcroForm, PdfDictionary currAcroForm, ValidationReport report + ) { + if (prevAcroForm == null) { + if (currAcroForm == null) { + return true; + } + PdfArray fields = currAcroForm.GetAsArray(PdfName.Fields); + foreach (PdfObject field in fields) { + PdfDictionary fieldDict = (PdfDictionary)field; + if (!IsAllowedSignatureField(fieldDict, report)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, NOT_ALLOWED_ACROFORM_CHANGES, ReportItem.ReportItemStatus + .INVALID)); + return false; + } + } + return true; + } + if (currAcroForm == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, ACROFORM_REMOVED, ReportItem.ReportItemStatus.INVALID)); + return false; + } + PdfDictionary previousAcroFormCopy = CopyAcroformDictionary(prevAcroForm); + PdfDictionary currentAcroFormCopy = CopyAcroformDictionary(currAcroForm); + PdfArray prevFields = prevAcroForm.GetAsArray(PdfName.Fields); + PdfArray currFields = currAcroForm.GetAsArray(PdfName.Fields); + if (!ComparePdfObjects(previousAcroFormCopy, currentAcroFormCopy) || (prevFields.Size() > currFields.Size( + )) || !CompareFormFields(prevFields, currFields, report)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, NOT_ALLOWED_ACROFORM_CHANGES, ReportItem.ReportItemStatus + .INVALID)); + return false; + } + return true; + } + + private bool CompareFormFields(PdfArray prevFields, PdfArray currFields, ValidationReport report) { + IDictionary prevFieldsMap = PopulateFormFieldsMap(prevFields); + IDictionary currFieldsMap = PopulateFormFieldsMap(currFields); + foreach (KeyValuePair fieldEntry in prevFieldsMap) { + PdfDictionary previousField = fieldEntry.Value; + PdfDictionary currentField = currFieldsMap.Get(fieldEntry.Key); + if (currentField == null || !CompareFields(previousField, currentField, report)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(FIELD_REMOVED, fieldEntry.Key) + , ReportItem.ReportItemStatus.INVALID)); + return false; + } + currFieldsMap.JRemove(fieldEntry.Key); + } + foreach (KeyValuePair fieldEntry in currFieldsMap) { + if (!IsAllowedSignatureField(fieldEntry.Value, report)) { + return false; + } + } + return CompareAnnotations(prevFields, currFields, report); + } + + ///

DocMDP level >= 2 allows setting values of the fields and accordingly update the widget appearances of them. + /// + /// + /// DocMDP level >= 2 allows setting values of the fields and accordingly update the widget appearances of them. But + /// you cannot change the form structure, so it is not allowed to add, remove or rename fields, change most of their + /// properties. + /// + /// field from the previous revision to check + /// field from the current revision to check + /// validation report + /// + /// + /// + /// if the changes of the field are allowed, + /// + /// otherwise. + /// + private bool CompareFields(PdfDictionary previousField, PdfDictionary currentField, ValidationReport report + ) { + PdfDictionary prevFormDict = CopyFieldDictionary(previousField); + PdfDictionary currFormDict = CopyFieldDictionary(currentField); + if (!ComparePdfObjects(prevFormDict, currFormDict) || !CompareIndirectReferencesObjNums(prevFormDict.Get(PdfName + .Parent), currFormDict.Get(PdfName.Parent), report, "Form field parent") || !CompareIndirectReferencesObjNums + (prevFormDict.Get(PdfName.P), currFormDict.Get(PdfName.P), report, "Page object with which field annotation is associated" + )) { + return false; + } + PdfObject prevValue = previousField.Get(PdfName.V); + PdfObject currValue = currentField.Get(PdfName.V); + if (prevValue == null && currValue == null && PdfName.Ch.Equals(currentField.GetAsName(PdfName.FT))) { + // Choice field: if the items in the I entry differ from those in the V entry, the V entry shall be used. + prevValue = previousField.Get(PdfName.I); + currValue = currentField.Get(PdfName.I); + } + if (PdfName.Sig.Equals(currentField.GetAsName(PdfName.FT))) { + if (!CompareSignatureDictionaries(prevValue, currValue, report)) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(SIGNATURE_MODIFIED, currentField + .GetAsString(PdfName.T).GetValue()), ReportItem.ReportItemStatus.INVALID)); + return false; + } + } + else { + if (docMDP == 1 && !ComparePdfObjects(prevValue, currValue)) { + return false; + } + } + return CompareFormFields(previousField.GetAsArray(PdfName.Kids), currentField.GetAsArray(PdfName.Kids), report + ); + } + + private bool CompareAnnotations(PdfArray prevFields, PdfArray currFields, ValidationReport report) { + IList prevAnnots = PopulateAnnotations(prevFields); + IList currAnnots = PopulateAnnotations(currFields); + if (prevAnnots.Count != currAnnots.Count) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, ANNOTATIONS_MODIFIED, ReportItem.ReportItemStatus.INVALID + )); + return false; + } + for (int i = 0; i < prevAnnots.Count; i++) { + PdfDictionary prevAnnot = new PdfDictionary(prevAnnots[i]); + RemoveAppearanceRelatedProperties(prevAnnot); + PdfDictionary currAnnot = new PdfDictionary(currAnnots[i]); + RemoveAppearanceRelatedProperties(currAnnot); + if (!ComparePdfObjects(prevAnnot, currAnnot) || !CompareIndirectReferencesObjNums(prevAnnots[i].Get(PdfName + .P), currAnnots[i].Get(PdfName.P), report, "Page object with which annotation is associated")) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, ANNOTATIONS_MODIFIED, ReportItem.ReportItemStatus.INVALID + )); + return false; + } + } + return true; + } + + private bool CompareSignatureDictionaries(PdfObject prevSigDict, PdfObject curSigDict, ValidationReport report + ) { + if (prevSigDict == null) { + return true; + } + if (curSigDict == null) { + return false; + } + if (!(prevSigDict is PdfDictionary) || !(curSigDict is PdfDictionary)) { + return false; + } + PdfDictionary currentSigDictCopy = new PdfDictionary((PdfDictionary)curSigDict); + currentSigDictCopy.Remove(PdfName.Reference); + PdfDictionary previousSigDictCopy = new PdfDictionary((PdfDictionary)prevSigDict); + previousSigDictCopy.Remove(PdfName.Reference); + // Apart from the reference, dictionaries are expected to be equal. + if (!ComparePdfObjects(previousSigDictCopy, currentSigDictCopy)) { + return false; + } + PdfArray previousReference = ((PdfDictionary)prevSigDict).GetAsArray(PdfName.Reference); + PdfArray currentReference = ((PdfDictionary)curSigDict).GetAsArray(PdfName.Reference); + return CompareSignatureReferenceDictionaries(previousReference, currentReference, report); + } + + private bool CompareSignatureReferenceDictionaries(PdfArray previousReferences, PdfArray currentReferences + , ValidationReport report) { + if (previousReferences == null || ComparePdfObjects(previousReferences, currentReferences)) { + return true; + } + if (currentReferences == null || previousReferences.Size() != currentReferences.Size()) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, REFERENCE_REMOVED, ReportItem.ReportItemStatus.INVALID) + ); + return false; + } + else { + for (int i = 0; i < previousReferences.Size(); ++i) { + PdfDictionary currentReferenceCopy = new PdfDictionary(currentReferences.GetAsDictionary(i)); + currentReferenceCopy.Remove(PdfName.Data); + PdfDictionary previousReferenceCopy = new PdfDictionary(previousReferences.GetAsDictionary(i)); + previousReferenceCopy.Remove(PdfName.Data); + // Apart from the data, dictionaries are expected to be equal. Data is an indirect reference + // to the object in the document upon which the object modification analysis should be performed. + if (!ComparePdfObjects(previousReferenceCopy, currentReferenceCopy) || !CompareIndirectReferencesObjNums(previousReferences + .GetAsDictionary(i).Get(PdfName.Data), currentReferences.GetAsDictionary(i).Get(PdfName.Data), report, + "Data entry in the signature reference dictionary")) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, REFERENCE_REMOVED, ReportItem.ReportItemStatus.INVALID) + ); + return false; + } + } + } + return true; + } + + private bool CompareIndirectReferencesObjNums(PdfObject prevObj, PdfObject currObj, ValidationReport report + , String type) { + if (prevObj == null ^ currObj == null) { + return false; + } + if (prevObj == null) { + return true; + } + PdfIndirectReference prevObjRef = prevObj.GetIndirectReference(); + PdfIndirectReference currObjRef = currObj.GetIndirectReference(); + if (prevObjRef == null || currObjRef == null) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(DIRECT_OBJECT, type), ReportItem.ReportItemStatus + .INVALID)); + return false; + } + return IsSameReference(prevObjRef, currObjRef); + } + + /// + /// DocMDP level <=2 allows adding new fields in the following cases: + /// docMDP level 1: allows adding only DocTimeStamp signature fields; + /// docMDP level 2: same as level 1 and also adding and then signing signature fields, + /// so signature dictionary shouldn't be null. + /// + /// newly added field entry + /// validation report + /// true if newly added field is allowed to be added, false otherwise. + private bool IsAllowedSignatureField(PdfDictionary field, ValidationReport report) { + PdfDictionary value = field.GetAsDictionary(PdfName.V); + if (!PdfName.Sig.Equals(field.GetAsName(PdfName.FT)) || value == null || (docMDP == 1 && !PdfName.DocTimeStamp + .Equals(value.GetAsName(PdfName.Type)))) { + report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(UNEXPECTED_FORM_FIELD, field.GetAsString + (PdfName.T).GetValue()), ReportItem.ReportItemStatus.INVALID)); + return false; + } + return true; + } + + private IDictionary PopulateFormFieldsMap(PdfArray fieldsArray) { + IDictionary fields = new Dictionary(); + if (fieldsArray != null) { + for (int i = 0; i < fieldsArray.Size(); ++i) { + PdfDictionary fieldDict = (PdfDictionary)fieldsArray.Get(i); + if (PdfFormField.IsFormField(fieldDict)) { + String fieldName = fieldDict.GetAsString(PdfName.T).GetValue(); + fields.Put(fieldName, fieldDict); + } + } + } + return fields; + } + + private IList PopulateAnnotations(PdfArray fieldsArray) { + IList annotations = new List(); + if (fieldsArray != null) { + for (int i = 0; i < fieldsArray.Size(); ++i) { + PdfDictionary annotDict = (PdfDictionary)fieldsArray.Get(i); + if (PdfFormAnnotationUtil.IsPureWidget(annotDict)) { + annotations.Add(annotDict); + } + } + } + return annotations; + } + + private PdfArray GetAnnotsNotAllowedToBeModified(PdfDictionary page) { + PdfArray annots = page.GetAsArray(PdfName.Annots); + if (annots == null) { + return null; + } + PdfArray annotsCopy = new PdfArray(annots); + foreach (PdfObject annot in annots) { + PdfDictionary annotDict = (PdfDictionary)annot; + if (PdfFormAnnotationUtil.IsPureWidgetOrMergedField(annotDict)) { + // Ideally we should also distinguish between docMDP level 1 (DTS) or 2 allowed annotations + // (we check them only on the acroform level, but they could be added to the page) + annotsCopy.Remove(annot); + } + } + return annotsCopy; + } + + private PdfDictionary CopyCatalogEntriesToCompare(PdfDictionary catalog) { + PdfDictionary catalogCopy = new PdfDictionary(catalog); + catalogCopy.Remove(PdfName.Metadata); + catalogCopy.Remove(PdfName.Extensions); + catalogCopy.Remove(PdfName.Perms); + catalogCopy.Remove(PdfName.DSS); + catalogCopy.Remove(PdfName.AcroForm); + catalogCopy.Remove(PdfName.Pages); + return catalogCopy; + } + + private PdfDictionary CopyAcroformDictionary(PdfDictionary acroForm) { + PdfDictionary acroFormCopy = new PdfDictionary(acroForm); + acroFormCopy.Remove(PdfName.Fields); + acroFormCopy.Remove(PdfName.DR); + acroFormCopy.Remove(PdfName.DA); + return acroFormCopy; + } + + private PdfDictionary CopyFieldDictionary(PdfDictionary field) { + PdfDictionary formDict = new PdfDictionary(field); + formDict.Remove(PdfName.V); + // Value for the choice fields could be specified by the /I key. + formDict.Remove(PdfName.I); + formDict.Remove(PdfName.Parent); + formDict.Remove(PdfName.Kids); + // Remove also annotation related properties (e.g. in case of the merged field). + RemoveAppearanceRelatedProperties(formDict); + return formDict; + } + + private void RemoveAppearanceRelatedProperties(PdfDictionary annotDict) { + annotDict.Remove(PdfName.P); + if (docMDP > 1) { + annotDict.Remove(PdfName.AP); + annotDict.Remove(PdfName.AS); + annotDict.Remove(PdfName.M); + annotDict.Remove(PdfName.F); + } + } + + // Allowed references creation nested methods section: private IList CreateAllowedDssEntries(PdfDocument documentWithRevision , PdfDocument documentWithoutRevision) { IList allowedReferences = new List CreateAllowedPagesEntries(PdfDictionary currentPagesDictionary + , PdfDictionary previousPagesDictionary) { + IList allowedReferences = new List(); + PdfArray currentKids = currentPagesDictionary.GetAsArray(PdfName.Kids); + if (currentKids != null) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentKids.GetIndirectReference(), GetIndirectReferenceOrNull + (() => previousPagesDictionary.Get(PdfName.Kids).GetIndirectReference()))); + for (int i = 0; i < currentKids.Size(); ++i) { + int finalI = i; + PdfDictionary currentPageNode = currentKids.GetAsDictionary(i); + PdfDictionary previousPageNode = null; + try { + previousPageNode = previousPagesDictionary.GetAsArray(PdfName.Kids).GetAsDictionary(i); + } + catch (NullReferenceException) { + } + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentKids.Get(i).GetIndirectReference + (), GetIndirectReferenceOrNull(() => previousPagesDictionary.GetAsArray(PdfName.Kids).Get(finalI).GetIndirectReference + ()))); + if (currentPageNode != null) { + if (PdfName.Pages.Equals(currentPageNode.GetAsName(PdfName.Type))) { + allowedReferences.AddAll(CreateAllowedPagesEntries(currentPageNode, previousPageNode)); + } + else { + PdfObject currentAnnots = currentPageNode.Get(PdfName.Annots); + if (currentAnnots != null) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentAnnots.GetIndirectReference(), + GetIndirectReferenceOrNull(() => previousPagesDictionary.GetAsArray(PdfName.Kids).GetAsDictionary(finalI + ).Get(PdfName.Annots).GetIndirectReference()))); + } + } + } + } } - return CompareExtensions(previousCatalog.Get(PdfName.Extensions), currentCatalog.Get(PdfName.Extensions), - report) && CompareDss(previousCatalog.Get(PdfName.DSS), currentCatalog.Get(PdfName.DSS), report); + // We don't need to add annotations because all the allowed ones are already added during acroform processing. + return allowedReferences; } - private bool CompareExtensions(PdfObject previousExtensions, PdfObject currentExtensions, ValidationReport - report) { - if (previousExtensions == null || ComparePdfObjects(previousExtensions, currentExtensions)) { - return true; + private ICollection CreateAllowedAcroFormEntries(PdfDocument documentWithRevision + , PdfDocument documentWithoutRevision) { + IList allowedReferences = new List(); + PdfAcroForm prevAcroForm = PdfFormCreator.GetAcroForm(documentWithoutRevision, false); + PdfAcroForm currAcroForm = PdfFormCreator.GetAcroForm(documentWithRevision, false); + IDictionary prevFields = prevAcroForm == null ? new Dictionary + () : prevAcroForm.GetAllFormFields(); + foreach (KeyValuePair fieldEntry in currAcroForm.GetAllFormFields()) { + PdfFormField previousField = prevFields.Get(fieldEntry.Key); + PdfFormField currentField = fieldEntry.Value; + PdfObject value = currentField.GetValue(); + if (docMDP >= 2 || (value is PdfDictionary && PdfName.DocTimeStamp.Equals(((PdfDictionary)value).GetAsName + (PdfName.Type)))) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentField.GetPdfObject().GetIndirectReference + (), GetIndirectReferenceOrNull(() => previousField.GetPdfObject().GetIndirectReference()))); + if (previousField == null) { + // For newly generated form field all references are allowed to be added. + AddAllNestedDictionaryEntries(allowedReferences, currentField.GetPdfObject(), null); + } + else { + // For already existing form field only several entries are allowed to be updated. + allowedReferences.AddAll(CreateAllowedExistingFormFieldEntries(currentField, previousField)); + } + } } - if (currentExtensions == null) { - report.AddReportItem(new ReportItem(DOC_MDP_CHECK, EXTENSIONS_REMOVED, ReportItem.ReportItemStatus.INVALID - )); - return false; + PdfDictionary currentResources = currAcroForm.GetDefaultResources(); + if (currentResources != null) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentResources.GetIndirectReference( + ), GetIndirectReferenceOrNull(() => prevAcroForm.GetDefaultResources().GetIndirectReference()))); + AddAllNestedDictionaryEntries(allowedReferences, currentResources, prevAcroForm == null ? null : prevAcroForm + .GetDefaultResources()); } - if (!(previousExtensions is PdfDictionary) || !(currentExtensions is PdfDictionary)) { - return false; + return allowedReferences; + } + + private ICollection CreateAllowedExistingFormFieldEntries(PdfFormField + currentField, PdfFormField previousField) { + IList allowedReferences = new List(); + PdfObject currentValue = currentField.GetValue(); + if (currentValue != null) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentValue.GetIndirectReference(), GetIndirectReferenceOrNull + (() => previousField.GetValue().GetIndirectReference()))); } - PdfDictionary previousExtensionsDictionary = (PdfDictionary)previousExtensions; - PdfDictionary currentExtensionsDictionary = (PdfDictionary)currentExtensions; - foreach (KeyValuePair previousExtension in previousExtensionsDictionary.EntrySet()) { - PdfDictionary currentExtension = currentExtensionsDictionary.GetAsDictionary(previousExtension.Key); - if (currentExtension == null) { - report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(DEVELOPER_EXTENSION_REMOVED, previousExtension - .Key), ReportItem.ReportItemStatus.INVALID)); - return false; - } - else { - PdfDictionary currentExtensionCopy = new PdfDictionary(currentExtension); - currentExtensionCopy.Remove(PdfName.ExtensionLevel); - PdfDictionary previousExtensionCopy = new PdfDictionary((PdfDictionary)previousExtension.Value); - previousExtensionCopy.Remove(PdfName.ExtensionLevel); - // Apart from extension level dictionaries are expected to be equal. - if (!ComparePdfObjects(previousExtensionCopy, currentExtensionCopy)) { - report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(DEVELOPER_EXTENSION_REMOVED, previousExtension - .Key), ReportItem.ReportItemStatus.INVALID)); - return false; - } - PdfNumber previousExtensionLevel = ((PdfDictionary)previousExtension.Value).GetAsNumber(PdfName.ExtensionLevel - ); - PdfNumber currentExtensionLevel = currentExtension.GetAsNumber(PdfName.ExtensionLevel); - if (previousExtensionLevel != null) { - if (currentExtensionLevel == null || previousExtensionLevel.IntValue() > currentExtensionLevel.IntValue()) { - report.AddReportItem(new ReportItem(DOC_MDP_CHECK, MessageFormatUtil.Format(EXTENSION_LEVEL_DECREASED, previousExtension - .Key), ReportItem.ReportItemStatus.INVALID)); - return false; + IList currAnnots = currentField.GetChildFormAnnotations(); + if (!currAnnots.IsEmpty()) { + IList prevAnnots = previousField == null ? null : previousField.GetChildFormAnnotations + (); + for (int i = 0; i < currAnnots.Count; i++) { + int finalI = i; + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currAnnots[i].GetPdfObject().GetIndirectReference + (), GetIndirectReferenceOrNull(() => prevAnnots[finalI].GetPdfObject().GetIndirectReference()))); + PdfObject currentAppearance = currAnnots[i].GetPdfObject().Get(PdfName.AP); + if (currentAppearance != null) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentAppearance.GetIndirectReference + (), GetIndirectReferenceOrNull(() => prevAnnots[finalI].GetPdfObject().Get(PdfName.AP).GetIndirectReference + ()))); + if (currentAppearance is PdfDictionary) { + PdfObject previousAppearance; + try { + previousAppearance = prevAnnots[finalI].GetPdfObject().Get(PdfName.AP); + } + catch (NullReferenceException) { + previousAppearance = null; + } + AddAllNestedDictionaryEntries(allowedReferences, (PdfDictionary)currentAppearance, previousAppearance); } } + PdfObject currentAppearanceState = currAnnots[i].GetPdfObject().Get(PdfName.AS); + if (currentAppearanceState != null) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentAppearanceState.GetIndirectReference + (), GetIndirectReferenceOrNull(() => prevAnnots[finalI].GetPdfObject().Get(PdfName.AS).GetIndirectReference + ()))); + } + PdfObject currentTimeStamp = currAnnots[i].GetPdfObject().Get(PdfName.M); + if (currentTimeStamp != null) { + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentTimeStamp.GetIndirectReference( + ), GetIndirectReferenceOrNull(() => prevAnnots[finalI].GetPdfObject().Get(PdfName.M).GetIndirectReference + ()))); + } } } - return true; + return allowedReferences; } - private bool CompareDss(PdfObject previousDss, PdfObject currentDss, ValidationReport report) { - if (previousDss == null) { - return true; - } - if (currentDss == null) { - report.AddReportItem(new ReportItem(DOC_MDP_CHECK, DSS_REMOVED, ReportItem.ReportItemStatus.INVALID)); - return false; + private void AddAllNestedDictionaryEntries(IList allowedReferences + , PdfDictionary currentDictionary, PdfObject previousDictionary) { + foreach (KeyValuePair entry in currentDictionary.EntrySet()) { + PdfObject currValue = entry.Value; + if (currValue.GetIndirectReference() != null && allowedReferences.Any((pair) => IsSameReference(pair.GetCurrentReference + (), currValue.GetIndirectReference()))) { + // Required to not end up in an infinite loop. + continue; + } + PdfObject prevValue = previousDictionary is PdfDictionary ? ((PdfDictionary)previousDictionary).Get(entry. + Key) : null; + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currValue.GetIndirectReference(), GetIndirectReferenceOrNull + (() => prevValue.GetIndirectReference()))); + if (currValue is PdfDictionary) { + AddAllNestedDictionaryEntries(allowedReferences, (PdfDictionary)currValue, prevValue); + } + if (currValue is PdfArray) { + AddAllNestedArrayEntries(allowedReferences, (PdfArray)currValue, prevValue); + } } - return true; } - internal static Stream CreateInputStreamFromRevision(PdfDocument originalDocument, DocumentRevision revision - ) { - RandomAccessFileOrArray raf = originalDocument.GetReader().GetSafeFile(); - WindowRandomAccessSource source = new WindowRandomAccessSource(raf.CreateSourceView(), 0, revision.GetEofOffset - ()); - return new RASInputStream(source); + private void AddAllNestedArrayEntries(IList allowedReferences, + PdfArray currentArray, PdfObject previousArray) { + for (int i = 0; i < currentArray.Size(); ++i) { + PdfObject currentArrayEntry = currentArray.Get(i); + if (currentArrayEntry.GetIndirectReference() != null && allowedReferences.Any((pair) => IsSameReference(pair + .GetCurrentReference(), currentArrayEntry.GetIndirectReference()))) { + // Required to not end up in an infinite loop. + continue; + } + PdfObject previousArrayEntry = previousArray is PdfArray ? ((PdfArray)previousArray).Get(i) : null; + allowedReferences.Add(new DocumentRevisionsValidator.ReferencesPair(currentArrayEntry.GetIndirectReference + (), GetIndirectReferenceOrNull(() => previousArrayEntry.GetIndirectReference()))); + if (currentArrayEntry is PdfDictionary) { + AddAllNestedDictionaryEntries(allowedReferences, currentArray.GetAsDictionary(i), previousArrayEntry); + } + if (currentArrayEntry is PdfArray) { + AddAllNestedArrayEntries(allowedReferences, currentArray.GetAsArray(i), previousArrayEntry); + } + } } + // Compare PDF objects util section: private static bool ComparePdfObjects(PdfObject pdfObject1, PdfObject pdfObject2) { return ComparePdfObjects(pdfObject1, pdfObject2, new HashSet()); } diff --git a/port-hash b/port-hash index 58b5c1e96f..96f345e6ee 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -4269d2c18c89982acf03d5e4fbc10abb9229642d +6ef2c8cfcaa16536cd70d0a324f580464783c7bf