diff --git a/itext.tests/itext.io.tests/itext/io/font/FontProgramTest.cs b/itext.tests/itext.io.tests/itext/io/font/FontProgramTest.cs index 833d76bb66..a65a92e29a 100644 --- a/itext.tests/itext.io.tests/itext/io/font/FontProgramTest.cs +++ b/itext.tests/itext.io.tests/itext/io/font/FontProgramTest.cs @@ -32,6 +32,13 @@ namespace iText.IO.Font { public class FontProgramTest : ExtendedITextTest { private const String notExistingFont = "some-font.ttf"; + [NUnit.Framework.SetUp] + public virtual void ClearFonts() { + FontProgramFactory.ClearRegisteredFonts(); + FontProgramFactory.ClearRegisteredFontFamilies(); + FontCache.ClearSavedFonts(); + } + [NUnit.Framework.Test] public virtual void ExceptionMessageTest() { Exception e = NUnit.Framework.Assert.Catch(typeof(System.IO.IOException), () => FontProgramFactory.CreateFont @@ -68,6 +75,16 @@ public virtual void RegisterDirectoryType1Test() { .CurrentContext.TestDirectory) + "/resources/itext/io/font/type1/"); FontProgram computerModern = FontProgramFactory.CreateRegisteredFont("computer modern"); FontProgram cmr10 = FontProgramFactory.CreateRegisteredFont("cmr10"); + NUnit.Framework.Assert.IsNull(computerModern); + NUnit.Framework.Assert.IsNull(cmr10); + } + + [NUnit.Framework.Test] + public virtual void RegisterDirectoryType1RecursivelyTest() { + FontProgramFactory.RegisterFontDirectoryRecursively(iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext + .CurrentContext.TestDirectory) + "/resources/itext/io/font/type1/"); + FontProgram computerModern = FontProgramFactory.CreateRegisteredFont("computer modern"); + FontProgram cmr10 = FontProgramFactory.CreateRegisteredFont("cmr10"); NUnit.Framework.Assert.IsNotNull(computerModern); NUnit.Framework.Assert.IsNotNull(cmr10); } diff --git a/itext.tests/itext.io.tests/resources/itext/io/font/type1/cmr10.afm b/itext.tests/itext.io.tests/resources/itext/io/font/type1/testPackage/cmr10.afm similarity index 100% rename from itext.tests/itext.io.tests/resources/itext/io/font/type1/cmr10.afm rename to itext.tests/itext.io.tests/resources/itext/io/font/type1/testPackage/cmr10.afm diff --git a/itext.tests/itext.io.tests/resources/itext/io/font/type1/cmr10.pfb b/itext.tests/itext.io.tests/resources/itext/io/font/type1/testPackage/cmr10.pfb similarity index 100% rename from itext.tests/itext.io.tests/resources/itext/io/font/type1/cmr10.pfb rename to itext.tests/itext.io.tests/resources/itext/io/font/type1/testPackage/cmr10.pfb diff --git a/itext.tests/itext.io.tests/resources/itext/io/font/type1/cmr10.pfm b/itext.tests/itext.io.tests/resources/itext/io/font/type1/testPackage/cmr10.pfm similarity index 100% rename from itext.tests/itext.io.tests/resources/itext/io/font/type1/cmr10.pfm rename to itext.tests/itext.io.tests/resources/itext/io/font/type1/testPackage/cmr10.pfm diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfFontTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfFontTest.cs index f059ed8119..42700399cd 100644 --- a/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfFontTest.cs +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfFontTest.cs @@ -1373,6 +1373,17 @@ public virtual void TestFontDirectoryRegister() { pdfDoc.Close(); } + [NUnit.Framework.Test] + public virtual void FontDirectoryRegisterRecursivelyTest() { + PdfFontFactory.RegisterDirectoryRecursively(sourceFolder); + foreach (String name in PdfFontFactory.GetRegisteredFonts()) { + PdfFont pdfFont = PdfFontFactory.CreateRegisteredFont(name); + if (pdfFont == null) { + NUnit.Framework.Assert.IsTrue(false, "Font {" + name + "} can't be empty"); + } + } + } + [NUnit.Framework.Test] public virtual void FontRegisterTest() { FontProgramFactory.RegisterFont(fontsFolder + "NotoSerif-Regular_v1.7.ttf", "notoSerifRegular"); diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/tagging/PdfStructElemUnitTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/tagging/PdfStructElemUnitTest.cs index db8da2c1dc..653bd3d45c 100644 --- a/itext.tests/itext.kernel.tests/itext/kernel/pdf/tagging/PdfStructElemUnitTest.cs +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/tagging/PdfStructElemUnitTest.cs @@ -21,6 +21,7 @@ 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.IO.Source; using iText.Kernel.Exceptions; using iText.Kernel.Geom; @@ -51,5 +52,46 @@ public virtual void AnnotationHasNoReferenceToPageTest() { NUnit.Framework.Assert.AreEqual(KernelExceptionMessageConstant.ANNOTATION_SHALL_HAVE_REFERENCE_TO_PAGE, exception .Message); } + + [NUnit.Framework.Test] + public virtual void AttributesAreNullTest() { + IDictionary attributesMap = new Dictionary(); + PdfDictionary dictionary = new PdfDictionary(attributesMap); + PdfStructElem pdfStructElem = new PdfStructElem(dictionary); + IList actualAttributesList = pdfStructElem.GetAttributesList(); + IList expectedAttributesList = new List(); + NUnit.Framework.Assert.AreEqual(actualAttributesList, expectedAttributesList); + } + + [NUnit.Framework.Test] + public virtual void AttributesAreDictionaryTest() { + IDictionary attributesMap = new Dictionary(); + IDictionary dictionaryMap = new Dictionary(); + dictionaryMap.Put(PdfName.A, new PdfName("value")); + attributesMap.Put(PdfName.A, new PdfDictionary(dictionaryMap)); + PdfDictionary dictionary = new PdfDictionary(attributesMap); + PdfStructElem pdfStructElem = new PdfStructElem(dictionary); + IList actualAttributesList = pdfStructElem.GetAttributesList(); + IList expectedAttributesList = new List(); + expectedAttributesList.Add(new PdfStructureAttributes(new PdfDictionary(dictionaryMap))); + NUnit.Framework.Assert.AreEqual(actualAttributesList[0].GetPdfObject().Get(PdfName.A), expectedAttributesList + [0].GetPdfObject().Get(PdfName.A)); + } + + [NUnit.Framework.Test] + public virtual void AttributesAreArrayTest() { + IDictionary attributesMap = new Dictionary(); + IDictionary dictionaryMap = new Dictionary(); + dictionaryMap.Put(PdfName.A, new PdfName("value")); + PdfDictionary pdfDictionary = new PdfDictionary(dictionaryMap); + attributesMap.Put(PdfName.A, new PdfArray(pdfDictionary)); + PdfDictionary dictionary = new PdfDictionary(attributesMap); + PdfStructElem pdfStructElem = new PdfStructElem(dictionary); + IList actualAttributesList = pdfStructElem.GetAttributesList(); + IList expectedAttributesList = new List(); + expectedAttributesList.Add(new PdfStructureAttributes(new PdfDictionary(dictionaryMap))); + NUnit.Framework.Assert.AreEqual(actualAttributesList[0].GetPdfObject().Get(PdfName.A), expectedAttributesList + [0].GetPdfObject().Get(PdfName.A)); + } } } diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/tagging/PdfStructureAttributesTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/tagging/PdfStructureAttributesTest.cs new file mode 100644 index 0000000000..b177dca901 --- /dev/null +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/tagging/PdfStructureAttributesTest.cs @@ -0,0 +1,46 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2025 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.Kernel.Pdf; +using iText.Test; + +namespace iText.Kernel.Pdf.Tagging { + [NUnit.Framework.Category("UnitTest")] + public class PdfStructureAttributesTest : ExtendedITextTest { + [NUnit.Framework.Test] + public virtual void OwnerIsNullTest() { + PdfStructureAttributes pdfStructureAttributes = new PdfStructureAttributes(new PdfDictionary()); + NUnit.Framework.Assert.IsNull(pdfStructureAttributes.GetPdfOwner()); + } + + [NUnit.Framework.Test] + public virtual void OwnerIsNotNullTest() { + IDictionary map = new Dictionary(); + map.Put(PdfName.O, new PdfName("owner")); + PdfStructureAttributes pdfStructureAttributes = new PdfStructureAttributes(new PdfDictionary(map)); + String pdfOwner = pdfStructureAttributes.GetPdfOwner(); + NUnit.Framework.Assert.AreEqual("owner", pdfOwner); + } + } +} diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/AssertValidationReport.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/AssertValidationReport.cs index 545f92b487..b5ac802a65 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/AssertValidationReport.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/AssertValidationReport.cs @@ -127,6 +127,12 @@ public AssertValidationReport.AssertValidationReportLogItem WithMessage(String m return this; } + public virtual AssertValidationReport.AssertValidationReportLogItem WithMessageContains(String expectedContent + ) { + check.WithMessageContains(expectedContent); + return this; + } + public virtual AssertValidationReport.AssertValidationReportLogItem WithStatus(ReportItem.ReportItemStatus status) { check.WithStatus(status); @@ -219,6 +225,8 @@ private class ValidationReportLogItemCheck : AssertValidationReport.CheckChain { private String message; + private String expectedMessageContent; + private ReportItem.ReportItemStatus status; private bool checkStatus = false; @@ -245,6 +253,11 @@ public virtual void WithMessage(String message, params Func[ errorMessage.Append(" message '").Append(message).Append("'"); } + public virtual void WithMessageContains(String expectedContent) { + this.expectedMessageContent = expectedContent; + errorMessage.Append(" message containing '").Append(expectedContent).Append("'"); + } + public virtual void WithStatus(ReportItem.ReportItemStatus status) { this.status = status; checkStatus = true; @@ -276,7 +289,13 @@ protected internal override void Check(ValidationReport report, AssertValidation errorMessage.Append("found ").Append(prefiltered.Count).Append(" matches after message filter\n"); } else { - prefiltered = report.GetLogs(); + if (expectedMessageContent != null) { + prefiltered = report.GetLogs().Where((i) => i.GetMessage().Contains(expectedMessageContent)).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(); diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/CertificateChainValidatorTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/CertificateChainValidatorTest.cs index 01499c989e..bdd2af3134 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/CertificateChainValidatorTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/CertificateChainValidatorTest.cs @@ -22,10 +22,10 @@ You should have received a copy of the GNU Affero General Public License */ using System; using System.IO; +using System.Text; using iText.Commons.Bouncycastle.Cert; using iText.Commons.Bouncycastle.Security; using iText.Commons.Utils; -using iText.Kernel.Crypto; using iText.Signatures; using iText.Signatures.Testutils; using iText.Signatures.Validation.Context; @@ -116,9 +116,11 @@ public virtual void InvalidNumericBasicConstraintsTest() { (2).HasNumberOfLogs(3).HasLogItem((la) => la.WithCheckName(CertificateChainValidator.CERTIFICATE_CHECK ).WithMessage("Certificate {0} is trusted, revocation data checks are not required.", (l) => rootCert. GetSubjectDN()).WithCertificate(rootCert)).HasLogItem((la) => la.WithCheckName(CertificateChainValidator - .EXTENSIONS_CHECK).WithMessage(CertificateChainValidator.EXTENSION_MISSING, (l) => "2.5.29.19").WithCertificate - (rootCert)).HasLogItem((la) => la.WithCheckName(CertificateChainValidator.EXTENSIONS_CHECK).WithMessage - (CertificateChainValidator.EXTENSION_MISSING, (l) => "2.5.29.19").WithCertificate(intermediateCert))); + .EXTENSIONS_CHECK).WithMessage(CertificateChainValidator.EXTENSION_MISSING, (l) => MessageFormatUtil.Format + (DynamicBasicConstraintsExtension.ERROR_MESSAGE, 1, 0)).WithCertificate(rootCert)).HasLogItem((la) => + la.WithCheckName(CertificateChainValidator.EXTENSIONS_CHECK).WithMessage(CertificateChainValidator.EXTENSION_MISSING + , (l) => MessageFormatUtil.Format(DynamicBasicConstraintsExtension.ERROR_MESSAGE, 0, -1)).WithCertificate + (intermediateCert))); } [NUnit.Framework.Test] @@ -127,7 +129,7 @@ public virtual void ChainWithAiaTest() { IX509Certificate[] certificateChain = PemFileHelper.ReadFirstChain(chainName); IX509Certificate signingCert = (IX509Certificate)certificateChain[0]; IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; - IssuingCertificateRetriever customRetriever = new _IssuingCertificateRetriever_182(); + IssuingCertificateRetriever customRetriever = new _IssuingCertificateRetriever_183(); validatorChainBuilder.WithIssuingCertificateRetrieverFactory(() => customRetriever); CertificateChainValidator validator = validatorChainBuilder.BuildCertificateChainValidator(); properties.SetRequiredExtensions(CertificateSources.Of(CertificateSource.CERT_ISSUER), JavaCollectionsUtil @@ -138,8 +140,8 @@ public virtual void ChainWithAiaTest() { AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID)); } - private sealed class _IssuingCertificateRetriever_182 : IssuingCertificateRetriever { - public _IssuingCertificateRetriever_182() { + private sealed class _IssuingCertificateRetriever_183 : IssuingCertificateRetriever { + public _IssuingCertificateRetriever_183() { } protected internal override Stream GetIssuerCertByURI(String uri) { @@ -155,7 +157,7 @@ public virtual void ChainWithAiaWhichPointsToRandomCertTest() { IX509Certificate signingCert = (IX509Certificate)certificateChain[0]; IX509Certificate intermediateCert = (IX509Certificate)certificateChain[1]; IX509Certificate rootCert = (IX509Certificate)certificateChain[2]; - IssuingCertificateRetriever customRetriever = new _IssuingCertificateRetriever_206(); + IssuingCertificateRetriever customRetriever = new _IssuingCertificateRetriever_207(); validatorChainBuilder.WithIssuingCertificateRetrieverFactory(() => customRetriever); CertificateChainValidator validator = validatorChainBuilder.BuildCertificateChainValidator(); properties.SetRequiredExtensions(CertificateSources.Of(CertificateSource.CERT_ISSUER), JavaCollectionsUtil @@ -168,8 +170,8 @@ public virtual void ChainWithAiaWhichPointsToRandomCertTest() { AssertValidationReport.AssertThat(report, (a) => a.HasStatus(ValidationReport.ValidationResult.VALID)); } - private sealed class _IssuingCertificateRetriever_206 : IssuingCertificateRetriever { - public _IssuingCertificateRetriever_206() { + private sealed class _IssuingCertificateRetriever_207 : IssuingCertificateRetriever { + public _IssuingCertificateRetriever_207() { } protected internal override Stream GetIssuerCertByURI(String uri) { @@ -228,13 +230,13 @@ public virtual void SeveralFailuresWithProceedAfterFailTest() { CertificateReportItem failure1 = report.GetCertificateFailures()[0]; NUnit.Framework.Assert.AreEqual(intermediateCert, failure1.GetCertificate()); NUnit.Framework.Assert.AreEqual("Required certificate extensions check.", failure1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format("Required extension {0} is missing or incorrect." - , OID.X509Extensions.KEY_USAGE), failure1.GetMessage()); + NUnit.Framework.Assert.AreEqual(BuildKeyUsageWrongMessagePart(KeyUsage.DECIPHER_ONLY, KeyUsage.KEY_CERT_SIGN + ), failure1.GetMessage()); CertificateReportItem failure2 = report.GetCertificateFailures()[1]; NUnit.Framework.Assert.AreEqual(rootCert, failure2.GetCertificate()); NUnit.Framework.Assert.AreEqual("Required certificate extensions check.", failure2.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format("Required extension {0} is missing or incorrect." - , OID.X509Extensions.KEY_USAGE), failure2.GetMessage()); + NUnit.Framework.Assert.AreEqual(BuildKeyUsageWrongMessagePart(KeyUsage.DECIPHER_ONLY, KeyUsage.KEY_CERT_SIGN + ), failure2.GetMessage()); } [NUnit.Framework.Test] @@ -260,8 +262,8 @@ public virtual void SeveralFailuresWithoutProceedAfterFailTest() { CertificateReportItem failure1 = report.GetCertificateFailures()[0]; NUnit.Framework.Assert.AreEqual(intermediateCert, failure1.GetCertificate()); NUnit.Framework.Assert.AreEqual("Required certificate extensions check.", failure1.GetCheckName()); - NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format("Required extension {0} is missing or incorrect." - , OID.X509Extensions.KEY_USAGE), failure1.GetMessage()); + NUnit.Framework.Assert.AreEqual(BuildKeyUsageWrongMessagePart(KeyUsage.DECIPHER_ONLY, KeyUsage.KEY_CERT_SIGN + ), failure1.GetMessage()); } [NUnit.Framework.Test] @@ -331,8 +333,8 @@ public virtual void ValidChainRequiredExtensionNegativeTest() { 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.EXTENSIONS_CHECK).WithMessage(CertificateChainValidator.EXTENSION_MISSING - , (l) => OID.X509Extensions.KEY_USAGE).WithCertificate(signingCert))); + .WithCheckName(CertificateChainValidator.EXTENSIONS_CHECK).WithMessageContains(BuildKeyUsageWrongMessagePart + (KeyUsage.KEY_CERT_SIGN)).WithCertificate(signingCert))); } [NUnit.Framework.Test] @@ -672,5 +674,17 @@ public virtual void TestStopOnInvalidRevocationResultTest() { NUnit.Framework.Assert.AreEqual(0, mockCertificateRetriever.getCrlIssuerCertificatesByNameCalls.Count); NUnit.Framework.Assert.AreEqual(1, mockRevocationDataValidator.calls.Count); } + + private String BuildKeyUsageWrongMessagePart(KeyUsage expectedKeyUsage, params KeyUsage[] actualKeyUsage) { + StringBuilder stringBuilder = new StringBuilder(); + String sep = ""; + foreach (KeyUsage usage in actualKeyUsage) { + stringBuilder.Append(sep).Append(usage); + sep = ", "; + } + return MessageFormatUtil.Format(CertificateChainValidator.EXTENSION_MISSING, MessageFormatUtil.Format(KeyUsageExtension + .EXPECTED_VALUE, expectedKeyUsage) + MessageFormatUtil.Format(KeyUsageExtension.ACTUAL_VALUE, stringBuilder + .ToString())); + } } } diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/OCSPValidatorIntegrationTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/OCSPValidatorIntegrationTest.cs index 1d4e4ad238..8519b8d68e 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/OCSPValidatorIntegrationTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/OCSPValidatorIntegrationTest.cs @@ -27,12 +27,12 @@ You should have received a copy of the GNU Affero General Public License using iText.Commons.Bouncycastle.Cert; using iText.Commons.Bouncycastle.Crypto; using iText.Commons.Utils; -using iText.Kernel.Crypto; using iText.Signatures; using iText.Signatures.Testutils; using iText.Signatures.Testutils.Builder; using iText.Signatures.Testutils.Client; using iText.Signatures.Validation.Context; +using iText.Signatures.Validation.Extensions; using iText.Signatures.Validation.Report; using iText.Test; @@ -157,9 +157,8 @@ public virtual void AuthorizedOcspResponderDoesNotHaveOcspSigningExtensionTest() validator.Validate(report, baseContext, checkCert, basicOCSPResp.GetResponses()[0], basicOCSPResp, TimeTestUtil .TEST_DATE_TIME, 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 - )); + (CertificateChainValidator.EXTENSIONS_CHECK).WithMessageContains(ExtendedKeyUsageExtension.OCSP_SIGNING + )).HasStatus(ValidationReport.ValidationResult.INDETERMINATE)); } [NUnit.Framework.Test] diff --git a/itext.tests/itext.sign.tests/itext/signatures/validation/extensions/KeyUsageExtensionTest.cs b/itext.tests/itext.sign.tests/itext/signatures/validation/extensions/KeyUsageExtensionTest.cs index 0f1d1ba3ff..910cd02457 100644 --- a/itext.tests/itext.sign.tests/itext/signatures/validation/extensions/KeyUsageExtensionTest.cs +++ b/itext.tests/itext.sign.tests/itext/signatures/validation/extensions/KeyUsageExtensionTest.cs @@ -23,6 +23,7 @@ You should have received a copy of the GNU Affero General Public License using System; using iText.Commons.Bouncycastle.Cert; using iText.Commons.Utils; +using iText.Signatures; using iText.Signatures.Testutils; using iText.Test; @@ -158,5 +159,38 @@ public virtual void KeyUsageSeveralKeys2PartiallyNotExpectedTest() { , KeyUsage.DIGITAL_SIGNATURE)); NUnit.Framework.Assert.IsFalse(extension.ExistsInCertificate(certificate)); } + + [NUnit.Framework.Test] + public virtual void KeyUsageTranslationTest() { + String certName = certsSrc + "keyUsageDecipherOnlyCert.pem"; + KeyUsageExtension extension = new KeyUsageExtension(KeyUsage.DECIPHER_ONLY); + IX509Certificate certificate = (IX509Certificate)PemFileHelper.ReadFirstChain(certName)[0]; + NUnit.Framework.Assert.AreEqual(CertificateUtil.GetExtensionValue(certificate, extension.GetExtensionOid() + ), extension.GetExtensionValue()); + certName = certsSrc + "keyUsageDigitalSignatureCert.pem"; + extension = new KeyUsageExtension(KeyUsage.DIGITAL_SIGNATURE); + certificate = (IX509Certificate)PemFileHelper.ReadFirstChain(certName)[0]; + NUnit.Framework.Assert.AreEqual(CertificateUtil.GetExtensionValue(certificate, extension.GetExtensionOid() + ), extension.GetExtensionValue()); + certName = certsSrc + "keyUsageKeyCertSignCert.pem"; + extension = new KeyUsageExtension(KeyUsage.KEY_CERT_SIGN); + certificate = (IX509Certificate)PemFileHelper.ReadFirstChain(certName)[0]; + NUnit.Framework.Assert.AreEqual(CertificateUtil.GetExtensionValue(certificate, extension.GetExtensionOid() + ), extension.GetExtensionValue()); + certName = certsSrc + "keyUsageSeveralKeys1Cert.pem"; + //Non-Repudiation, Key Encipherment, Off-line CRL Signing, CRL Signing + extension = new KeyUsageExtension(JavaUtil.ArraysAsList(KeyUsage.NON_REPUDIATION, KeyUsage.KEY_ENCIPHERMENT + , KeyUsage.CRL_SIGN)); + certificate = (IX509Certificate)PemFileHelper.ReadFirstChain(certName)[0]; + NUnit.Framework.Assert.AreEqual(CertificateUtil.GetExtensionValue(certificate, extension.GetExtensionOid() + ), extension.GetExtensionValue()); + certName = certsSrc + "keyUsageSeveralKeys2Cert.pem"; + //Digital Signature, Key Agreement, Decipher Only + extension = new KeyUsageExtension(JavaUtil.ArraysAsList(KeyUsage.DIGITAL_SIGNATURE, KeyUsage.KEY_AGREEMENT + , KeyUsage.DECIPHER_ONLY)); + certificate = (IX509Certificate)PemFileHelper.ReadFirstChain(certName)[0]; + NUnit.Framework.Assert.AreEqual(CertificateUtil.GetExtensionValue(certificate, extension.GetExtensionOid() + ), extension.GetExtensionValue()); + } } } diff --git a/itext/itext.io/itext/io/font/FontProgramFactory.cs b/itext/itext.io/itext/io/font/FontProgramFactory.cs index b75caaa39e..af6ebafd42 100644 --- a/itext/itext.io/itext/io/font/FontProgramFactory.cs +++ b/itext/itext.io/itext/io/font/FontProgramFactory.cs @@ -495,6 +495,13 @@ public static int RegisterFontDirectory(String dir) { return fontRegisterProvider.RegisterFontDirectory(dir); } + /// Register all the fonts in a directory recursively. + /// the directory + /// the number of fonts registered + public static int RegisterFontDirectoryRecursively(String dir) { + return fontRegisterProvider.RegisterFontDirectory(dir, true); + } + /// Register fonts in some probable directories. /// /// Register fonts in some probable directories. It usually works in Windows, diff --git a/itext/itext.kernel/itext/kernel/font/PdfFontFactory.cs b/itext/itext.kernel/itext/kernel/font/PdfFontFactory.cs index a059ad5fd7..d67e6cf7cb 100644 --- a/itext/itext.kernel/itext/kernel/font/PdfFontFactory.cs +++ b/itext/itext.kernel/itext/kernel/font/PdfFontFactory.cs @@ -659,6 +659,7 @@ public static PdfType3Font CreateType3Font(PdfDocument document, String fontName /// /// /// + /// /// /// /// @@ -705,6 +706,7 @@ public static PdfFont CreateRegisteredFont(String fontName, String encoding, Pdf /// /// /// + /// /// /// /// @@ -749,6 +751,7 @@ public static PdfFont CreateRegisteredFont(String fontName, String encoding, Pdf /// /// /// + /// /// /// /// @@ -797,6 +800,7 @@ public static PdfFont CreateRegisteredFont(String fontName, String encoding, Pdf /// /// /// + /// /// /// /// @@ -835,6 +839,7 @@ public static PdfFont CreateRegisteredFont(String fontName, String encoding, Pdf /// /// /// + /// /// /// /// @@ -868,6 +873,7 @@ public static PdfFont CreateRegisteredFont(String fontName, String encoding) { /// /// /// + /// /// /// /// @@ -908,6 +914,13 @@ public static int RegisterDirectory(String dirPath) { return FontProgramFactory.RegisterFontDirectory(dirPath); } + /// Registers all the fonts in a directory recursively. + /// the directory path to be registered as a font directory path + /// the number of fonts registered + public static int RegisterDirectoryRecursively(String dirPath) { + return FontProgramFactory.RegisterFontDirectoryRecursively(dirPath); + } + /// Register fonts in some probable directories. /// /// Register fonts in some probable directories. It usually works in Windows, diff --git a/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructElem.cs b/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructElem.cs index 25bdfd1fdc..da6598bd8b 100644 --- a/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructElem.cs +++ b/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructElem.cs @@ -109,6 +109,29 @@ public virtual PdfObject GetAttributes(bool createNewIfNull) { return attributes; } + /// Gets a list of PDF attribute objects. + /// list of PDF attribute objects. + public virtual IList GetAttributesList() { + IList attributesList = new List(); + PdfObject elemAttributesObj = GetAttributes(false); + if (elemAttributesObj != null) { + if (elemAttributesObj.IsDictionary()) { + attributesList.Add(new PdfStructureAttributes((PdfDictionary)elemAttributesObj)); + } + else { + if (elemAttributesObj.IsArray()) { + PdfArray attributesArray = (PdfArray)elemAttributesObj; + foreach (PdfObject attributeObj in attributesArray) { + if (attributeObj.IsDictionary()) { + attributesList.Add(new PdfStructureAttributes((PdfDictionary)attributeObj)); + } + } + } + } + } + return attributesList; + } + public virtual void SetAttributes(PdfObject attributes) { Put(PdfName.A, attributes); } diff --git a/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructureAttributes.cs b/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructureAttributes.cs index ce53283956..73c50e21c1 100644 --- a/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructureAttributes.cs +++ b/itext/itext.kernel/itext/kernel/pdf/tagging/PdfStructureAttributes.cs @@ -35,6 +35,13 @@ public PdfStructureAttributes(String owner) GetPdfObject().Put(PdfName.O, PdfStructTreeRoot.ConvertRoleToPdfName(owner)); } + /// Method to get owner of current pdf object. + /// Pdf owner + public virtual String GetPdfOwner() { + PdfName pdfName = (PdfName)GetPdfObject().Get(PdfName.O); + return pdfName == null ? null : pdfName.GetValue(); + } + public PdfStructureAttributes(PdfNamespace @namespace) : base(new PdfDictionary()) { GetPdfObject().Put(PdfName.O, PdfName.NSO); diff --git a/itext/itext.sign/itext/signatures/validation/CertificateChainValidator.cs b/itext/itext.sign/itext/signatures/validation/CertificateChainValidator.cs index ce52c1323d..963085ae86 100644 --- a/itext/itext.sign/itext/signatures/validation/CertificateChainValidator.cs +++ b/itext/itext.sign/itext/signatures/validation/CertificateChainValidator.cs @@ -55,7 +55,7 @@ public class CertificateChainValidator { //\endcond //\cond DO_NOT_DOCUMENT - internal const String EXTENSION_MISSING = "Required extension {0} is missing or incorrect."; + internal const String EXTENSION_MISSING = "Required extension validation failed: {0}"; //\endcond //\cond DO_NOT_DOCUMENT @@ -290,7 +290,7 @@ private void ValidateRequiredExtensions(ValidationReport result, ValidationConte } if (!requiredExtension.ExistsInCertificate(certificate)) { result.AddReportItem(new CertificateReportItem(certificate, EXTENSIONS_CHECK, MessageFormatUtil.Format(EXTENSION_MISSING - , requiredExtension.GetExtensionOid()), ReportItem.ReportItemStatus.INVALID)); + , requiredExtension.GetMessage()), ReportItem.ReportItemStatus.INVALID)); } } } diff --git a/itext/itext.sign/itext/signatures/validation/extensions/CertificateExtension.cs b/itext/itext.sign/itext/signatures/validation/extensions/CertificateExtension.cs index 39fa7e8e0e..d66f1ba758 100644 --- a/itext/itext.sign/itext/signatures/validation/extensions/CertificateExtension.cs +++ b/itext/itext.sign/itext/signatures/validation/extensions/CertificateExtension.cs @@ -29,10 +29,20 @@ You should have received a copy of the GNU Affero General Public License namespace iText.Signatures.Validation.Extensions { /// Class representing certificate extension with all the information required for validation. public class CertificateExtension { + public const String EXCEPTION_OCCURRED = " but an exception occurred {0}:{1}."; + + public const String EXTENSION_NOT_FOUND = " but no extension with that id was found."; + + public const String FOUND_VALUE = " but found value "; + + public const String EXPECTED_EXTENSION_ID_AND_VALUE = "Expected extension with id {0} and value {1}" + " {1} {2}"; + private readonly String extensionOid; private readonly IAsn1Object extensionValue; + private String errorMessage = ""; + /// /// Create new instance of /// @@ -73,6 +83,13 @@ public virtual String GetExtensionOid() { return extensionOid; } + /// Returns a message with extra information about the check. + /// a message with extra information about the check. + public virtual String GetMessage() { + return MessageFormatUtil.Format(EXPECTED_EXTENSION_ID_AND_VALUE, GetExtensionOid(), GetExtensionValue().ToString + (), errorMessage); + } + /// Check if this extension is present in the provided certificate. /// /// Check if this extension is present in the provided certificate. @@ -97,13 +114,26 @@ public virtual bool ExistsInCertificate(IX509Certificate certificate) { try { providedExtensionValue = CertificateUtil.GetExtensionValue(certificate, extensionOid); } - catch (System.IO.IOException) { + catch (System.IO.IOException e) { + errorMessage = MessageFormatUtil.Format(EXCEPTION_OCCURRED, e.GetType().FullName, e.Message); + return false; + } + catch (Exception e) { + errorMessage = MessageFormatUtil.Format(EXCEPTION_OCCURRED, e.GetType().FullName, e.Message); return false; } - catch (Exception) { + if (providedExtensionValue == null) { + if (extensionValue == null) { + return true; + } + errorMessage = EXTENSION_NOT_FOUND; return false; } - return Object.Equals(providedExtensionValue, extensionValue); + if (Object.Equals(providedExtensionValue, extensionValue)) { + return true; + } + errorMessage = FOUND_VALUE + MessageFormatUtil.Format(" but found value {0}.", extensionValue.ToString()); + return false; } public override bool Equals(Object o) { diff --git a/itext/itext.sign/itext/signatures/validation/extensions/DynamicBasicConstraintsExtension.cs b/itext/itext.sign/itext/signatures/validation/extensions/DynamicBasicConstraintsExtension.cs index d6b2957465..342fa86060 100644 --- a/itext/itext.sign/itext/signatures/validation/extensions/DynamicBasicConstraintsExtension.cs +++ b/itext/itext.sign/itext/signatures/validation/extensions/DynamicBasicConstraintsExtension.cs @@ -20,9 +20,11 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 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.Utils; using iText.Kernel.Crypto; namespace iText.Signatures.Validation.Extensions { @@ -33,6 +35,10 @@ namespace iText.Signatures.Validation.Extensions { public class DynamicBasicConstraintsExtension : DynamicCertificateExtension { private static readonly IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.GetFactory(); + public const String ERROR_MESSAGE = "Expected extension 2.5.29.19 to have a value of at least {0} but found {1}"; + + private String errorMessage; + /// /// Create new instance of /// . @@ -62,7 +68,16 @@ public DynamicBasicConstraintsExtension() /// otherwise /// public override bool ExistsInCertificate(IX509Certificate certificate) { - return certificate.GetBasicConstraints() >= GetCertificateChainSize() - 1; + if (certificate.GetBasicConstraints() >= GetCertificateChainSize() - 1) { + return true; + } + errorMessage = MessageFormatUtil.Format(ERROR_MESSAGE, GetCertificateChainSize() - 1, certificate.GetBasicConstraints + ()); + return false; + } + + public override String GetMessage() { + return errorMessage; } } } diff --git a/itext/itext.sign/itext/signatures/validation/extensions/ExtendedKeyUsageExtension.cs b/itext/itext.sign/itext/signatures/validation/extensions/ExtendedKeyUsageExtension.cs index cd377408bf..d243e77be0 100644 --- a/itext/itext.sign/itext/signatures/validation/extensions/ExtendedKeyUsageExtension.cs +++ b/itext/itext.sign/itext/signatures/validation/extensions/ExtendedKeyUsageExtension.cs @@ -22,12 +22,14 @@ You should have received a copy of the GNU Affero General Public License */ using System; using System.Collections.Generic; +using System.Text; using iText.Bouncycastleconnector; using iText.Commons.Bouncycastle; using iText.Commons.Bouncycastle.Asn1; using iText.Commons.Bouncycastle.Cert; using iText.Commons.Bouncycastle.Security; using iText.Kernel.Crypto; +using Microsoft.Extensions.Primitives; namespace iText.Signatures.Validation.Extensions { /// Class representing "Extended Key Usage" extension. @@ -44,8 +46,16 @@ public class ExtendedKeyUsageExtension : CertificateExtension { private static readonly IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.GetFactory(); + public const String EXPECTED_KEY_USAGES = "Expected extended key usages:"; + public const String ACTUAL = "But found :"; + public const String NO_EXTENDED_KEY_USAGES_WERE_FOUND = " But no extended key usages were found."; + public const String ERROR_OCCURRED_DURING_RETRIEVAL = " But error occurred during retrieval "; + + private readonly IList extendedKeyUsageOids; + private String errorMessage = ""; + /// /// Create new /// @@ -87,6 +97,7 @@ public override bool ExistsInCertificate(IX509Certificate certificate) { { if (certificate.GetExtendedKeyUsage() == null) { + errorMessage = NO_EXTENDED_KEY_USAGES_WERE_FOUND; return false; } @@ -95,14 +106,24 @@ public override bool ExistsInCertificate(IX509Certificate certificate) { providedExtendedKeyUsage.Add(singleExtendedKeyUsage); } } - catch (AbstractCertificateParsingException) { + catch (Exception e) { + errorMessage = ERROR_OCCURRED_DURING_RETRIEVAL + e.GetType().Name + " " + e.Message; return false; } - catch (Exception) { - return false; + if (providedExtendedKeyUsage.Contains(ANY_EXTENDED_KEY_USAGE_OID) || new HashSet(providedExtendedKeyUsage + ).ContainsAll(extendedKeyUsageOids)) { + return true; + } + StringBuilder sb = new StringBuilder(ACTUAL); + char sep = '('; + foreach (String usage in providedExtendedKeyUsage) + { + sb.Append(sep).Append(usage); + sep = ','; } - return providedExtendedKeyUsage.Contains(ANY_EXTENDED_KEY_USAGE_OID) || new HashSet(providedExtendedKeyUsage - ).ContainsAll(extendedKeyUsageOids); + sb.Append(')'); + errorMessage = sb.ToString(); + return false; } private static IDerObjectIdentifier[] CreateKeyPurposeIds(IList extendedKeyUsageOids) { @@ -112,5 +133,20 @@ private static IDerObjectIdentifier[] CreateKeyPurposeIds(IList extended } return keyPurposeIds; } + + + public override String GetMessage() + { + StringBuilder sb = new StringBuilder(EXPECTED_KEY_USAGES); + char sep = '('; + foreach (String usage in extendedKeyUsageOids) + { + sb.Append(sep).Append(usage); + sep = ','; + } + sb.Append(')'); + sb.Append(errorMessage); + return sb.ToString(); + } } } diff --git a/itext/itext.sign/itext/signatures/validation/extensions/KeyUsageExtension.cs b/itext/itext.sign/itext/signatures/validation/extensions/KeyUsageExtension.cs index 5d395b0346..28fcb8487a 100644 --- a/itext/itext.sign/itext/signatures/validation/extensions/KeyUsageExtension.cs +++ b/itext/itext.sign/itext/signatures/validation/extensions/KeyUsageExtension.cs @@ -20,11 +20,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 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.Text; using iText.Bouncycastleconnector; using iText.Commons.Bouncycastle; using iText.Commons.Bouncycastle.Cert; using iText.Commons.Utils; +using iText.IO.Util; using iText.Kernel.Crypto; namespace iText.Signatures.Validation.Extensions { @@ -32,10 +35,20 @@ namespace iText.Signatures.Validation.Extensions { public class KeyUsageExtension : CertificateExtension { private static readonly IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.GetFactory(); + public const String EXPECTED_VALUE = "Key usage expected: ({0})"; + + public const String ACTUAL_VALUE = "\nbut found {0}"; + + public const String MISSING_VALUE = "\nbut nothing found."; + private readonly int keyUsage; private readonly bool resultOnMissingExtension; + private String messagePreAmble; + + private String message; + /// /// Create new /// @@ -47,6 +60,10 @@ public class KeyUsageExtension : CertificateExtension { /// /// int /// flag which represents bit values for key usage value + /// bit strings are stored with the big-endian byte order and padding on the end, + /// the big endian notation causes a shift in actual integer values for + /// bits 1-8 becoming 0-7 and bit 1 + /// the 7 bits padding makes for bit 0 to become bit 7 of the first byte /// public KeyUsageExtension(int keyUsage) : this(keyUsage, false) { @@ -63,6 +80,10 @@ public KeyUsageExtension(int keyUsage) /// /// int /// flag which represents bit values for key usage value + /// bit strings are stored with the big-endian byte order and padding on the end, + /// the big endian notation causes a shift in actual integer values for bits 1-8 + /// becoming 0-7 and bit 1 + /// the 7 bits padding makes for bit 0 to become bit 7 of the first byte /// /// /// parameter which represents return value for @@ -73,6 +94,8 @@ public KeyUsageExtension(int keyUsage, bool resultOnMissingExtension) : base(OID.X509Extensions.KEY_USAGE, FACTORY.CreateKeyUsage(keyUsage).ToASN1Primitive()) { this.keyUsage = keyUsage; this.resultOnMissingExtension = resultOnMissingExtension; + messagePreAmble = MessageFormatUtil.Format(EXPECTED_VALUE, ConvertKeyUsageMaskToString(keyUsage)); + message = messagePreAmble; } /// @@ -164,37 +187,62 @@ public override bool ExistsInCertificate(IX509Certificate certificate) { bool[] providedKeyUsageFlags = certificate.GetKeyUsage(); if (providedKeyUsageFlags == null) { // By default, we want to return true if extension is not specified. Configurable. + message = messagePreAmble + MISSING_VALUE; return resultOnMissingExtension; } - for (int i = 0; i < providedKeyUsageFlags.Length; ++i) { - int power = providedKeyUsageFlags.Length - i - 2; - if (power < 0) { - // Bits are encoded backwards, for the last bit power is -1 and in this case we need to go over byte - power = 16 + power; - } - if ((keyUsage & (1 << power)) != 0 && !providedKeyUsageFlags[i]) { - return false; + int bitmap = 0; + // bit strings are stored with the big-endian byte order and padding on the end, + // the big endian notation causes a shift in actual integer values for bits 1-8 becoming 0-7 and bit 1 + // the 7 bits padding makes for bit 0 to become bit 7 of the first byte + for (int i = 0; i < providedKeyUsageFlags.Length - 1; ++i) { + if (providedKeyUsageFlags[i]) { + bitmap += 1 << (8 - i - 1); } } + if (providedKeyUsageFlags[8]) { + bitmap += 0x8000; + } + if ((bitmap & keyUsage) != keyUsage) { + message = new StringBuilder(messagePreAmble).Append(MessageFormatUtil.Format(ACTUAL_VALUE, ConvertKeyUsageMaskToString + (bitmap))).ToString(); + return false; + } return true; } - private static int ConvertKeyUsageSetToInt(IList keyUsages) { - KeyUsage[] possibleKeyUsage = new KeyUsage[] { KeyUsage.DIGITAL_SIGNATURE, KeyUsage.NON_REPUDIATION, KeyUsage - .KEY_ENCIPHERMENT, KeyUsage.DATA_ENCIPHERMENT, KeyUsage.KEY_AGREEMENT, KeyUsage.KEY_CERT_SIGN, KeyUsage - .CRL_SIGN, KeyUsage.ENCIPHER_ONLY, KeyUsage.DECIPHER_ONLY }; - int result = 0; - for (int i = 0; i < possibleKeyUsage.Length; ++i) { - if (keyUsages.Contains(possibleKeyUsage[i])) { - int power = possibleKeyUsage.Length - i - 2; - if (power < 0) { - // Bits are encoded backwards, for the last bit power is -1 and in this case we need to go over byte - power = 16 + power; - } - result |= (1 << power); + public override String GetMessage() { + return message; + } + + private static String ConvertKeyUsageMaskToString(int keyUsageMask) { + StringBuilder result = new StringBuilder(); + String separator = ""; + // bit strings are stored with the big-endian byte order and padding on the end, + // the big endian notation causes a shift in actual integer values for bits 1-8 becoming 0-7 and bit 1 + // the 7 bits padding makes for bit 0 to become bit 7 of the first byte + foreach (KeyUsage usage in EnumUtil.GetAllValuesOfEnum()) { + if (((1 << (8 - (int)(usage) - 1)) & keyUsageMask) > 0 || (usage == KeyUsage.DECIPHER_ONLY && (keyUsageMask + & 0x8000) == 0x8000)) { + result.Append(separator).Append(usage); + separator = ", "; + } + } + return result.ToString(); + } + + private static int ConvertKeyUsageSetToInt(IEnumerable keyUsages) { + int keyUsageMask = 0; + // bit strings are stored with the big-endian byte order and padding on the end, + // the big endian notation causes a shift in actual integer values for bits 1-8 becoming 0-7 and bit 1 + // the 7 bits padding makes for bit 0 to become bit 7 of the first byte + foreach (KeyUsage usage in keyUsages) { + if (usage == KeyUsage.DECIPHER_ONLY) { + keyUsageMask += 0x8000; + continue; } + keyUsageMask += 1 << (8 - (int)(usage) - 1); } - return result; + return keyUsageMask; } } } diff --git a/port-hash b/port-hash index c0b8b7a280..1807f083b9 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -36ee4b7f5a3987a868387cefea6cf91710492010 +b4fc4fb9e7cbd1c4d6129c35bab7907db3622616