Breaking changes for iText 8
Sign module
Also see Bouncy-castle related changelog.
Breaking change |
---|
IExternalSignature interface:
|
PrivateKeySignature class implements IExternalSignature. So the changes are similar:
|
PdfPKCS7 class:
|
EncryptionAlgorithms class is renamed to SignatureMechanisms |
Commons module
Breaking change | Upgrade path |
---|---|
ZipFileReader class:
| Update usages in tests only. |
DateTimeUtil class
| Replaced by DateTimeUtil#getCurrentTimeZoneOffset(Date) |
IO module
Breaking change | Upgrade path |
---|---|
IoLogMessageConstant class, the following unused constants are removed:
| |
UrlUtil class: Method getFinalURL(URL initialUrl) is removed | Use UrlUtil.getInputStreamOfFinalConnection(URL) instead. |
CMapCidByte class is renamed to CMapCidToCodepoint FontCache class:
CMapEncoding class:
| Update usages. |
Styled-xml-parser module
Breaking change | Upgrade path |
---|---|
DataUtil class:
| Update usages in tests only. |
Forms module
Kernel module
Breaking change | Upgrade path |
---|---|
PdfNameTree is extended from GenericNameTree:
| Update usages. |
PdfDestination, PdfExplicitDestination, PdfExplicitRemoteGoToDestination, PdfNamedDestination, PdfStringDestination and PdfStructureDestination classes:
| Update usages. |
Switch to new IPdfFunction hierarchy instead of old PdfFunction:
| Update usages. |
Two constants are removed from KernelExceptionMessageConstant:
| |
Method getUniMapFromOrdering(String ordering) is removed from PdfType0Font. | Use method getUniMapFromOrdering(String, boolean) instead |
Method storeLinkAnnotation is removed from PdfDocument. | Internal usage, use method storeDestinationToReaddress |
Class PdfFunction is removed. | Use classes PdfType0Function, PdfType2Function, PdfType3Function or PdfType4Function instead. |
SVG module
Breaking change |
---|
SvgExceptionMessageConstant class, the following constants are removed:
|
Forms module changes
Overview
In the scope of the 8.0.0 release we've introduced several major breaking changes in the forms
module of the itextcore
repository.
List of breaking changes:
Form fields with the same fully qualified names are now merged with each other.
If these fields have a different field type, value or default value, a log will be displayed (in the reading mode or when reading the document) or an exception will be thrown (in cases when the user tries to add a field with the same name as an already existing one, but different
/FT
,/V
or/DV
values). Merge occurs when an acroform is created, when children are added to a field, when field is renamed, or when fields are added to an acroform.PdfAcroForm.addField(PdfFormField, PdfPage, boolean)
can be used to not throw an exception while adding a new field.setValue
method sets the value not only to the fieldsetValue
is called on, but also to its siblings (children of the parent field) if they have the same fully qualified name. Such fields may present when it was not possible to merge them, or, for example, during acroform reading.If there are 2 fields in the hierarchy of fields – a parent without /T entry and a child, and the child has all the values in its dictionary (except for
/Parent
and/Kids
) that match the parent values, and child/Kids
contain only annotations (no form fields), then this child will be merged with the parent, that is, the/Kids
of the child will become the/Kids
of the parent. This makes the document structure cleaner (field annotations become pure widgets). Parent should not have /T entry because we only want to perform such a merge if the field doesn't contain any name (even an empty one).
For radio groups, all of its child form fields are merged with it, since radio group should contain only annotation children according to the specification. This means that we force radio buttons to be pure widgets in order to match the PDF specification.
For fields with multiple widgets, all annotations are added to the page (previously only fields with one widget were supported).
PdfFormField.setValue
doesn't set the value to the child form fields any longer.PdfAcroForm.replaceField
– fields now get replaced in their parents, where previously they just would have been added to the main form dictionary.Fields (including merged fields) that do not have
/T
key are considered fields named""
(empty string).Changed
PdfFormField
logic to be able to add fields without partial name. In general we leave the field without changes and treat it's name as an empty string. This name is handled as any other name, since an empty string is still a string (e.g. fields merging, search, remove, etc...). Full name for field's children looks likerootFieldName.parentFieldName.childName
In this particular case parentFieldName will be an empty string. This behavior violates the PDF 2.0 specification, but it is valid for PDF 1.7.Moved radio button, push button, checkbox, text field drawing logic from forms to renderers.
This led to some changes in the fields appearance, and now flattened and interactive fields are drawn in almost the same way.
Also fixed a bug related to fields stream data: it was written to the content stream of the page before, but now it is in the field normal appearance which is used for drawing the annotation.
Creating fields using model element became much more convenient. Currently we have two main ways to create form field in the acroform structure:
First one is the old one: through the usage of
PdfAcroForm
class.Second one is much less complicated and easy for users. One needs to simply create a layout element related to form fields and add it to the
Document
object like we usually do for other layout elements (such asParagraph
,Table
,Div
, etc...). After that, the form field will be automatically placed on the page and related elements will be added to the acroform if iterative mode is enabled. This is not yet supported for choice form fields.
Text form field drawing logic was moved to
TextAreaRenderer
andInputFieldRenderer
.Text drawing was moved to the related layout elements.
Text approximation is improved.
Border drawing bug with border being 2 times smaller was fixed.
Text no longer overlaps the border.
Some new classes were added for checkboxes drawing:
/form/renderer/checkboximpl/HtmlCheckBoxRenderingStrategy.java
/form/renderer/checkboximpl/ICheckBoxRenderingStrategy.java
/form/renderer/checkboximpl/PdfACheckBoxRenderingStrategy.java
/form/renderer/checkboximpl/PdfCheckBoxRenderingStrategy.java
Model element can be passed to
PdfFormAnnotation
by usingpublic PdfFormAnnotation setFormFieldElement(IFormField element)
. Note that annotation properties will take precedence, so such properties cannot be overridden by using this method (e.g. background, text color, etc.), but it can be useful to take into account when drawing those properties that the annotation does not have (e.g. paddings). Also note that the model element won't be used for annotations for choice form fields.Each model element is responsible for one annotation. We can add them to the document using
Document.add
method. Elements with the same id will be responsible for the annotations of the field with the name equal to id. Note that fields with the same fully qualified field name shall have the same field type, value, and default value to merge it without problems.
Re-adding the same model element two or more times can lead to an undefined behavior since model element properties is changed inPdfFormAnnotation
.
API changes:
Breaking change | Upgrade path |
---|---|
Created builder classes for every different
| Use builders instead of old creator methods and set appropriate properties, e.g. instead of
JAVA
use
JAVA
|
| Use |
A few convenience getters were added into
| First use |
Generating appearance was moved from
| If you used to override some drawing methods in For example, |
Model element classes and corresponding renderers were moved from
| |
Changed radio buttons creation logic to make them pure widgets instead of form fields: radio buttons changed from a | For radio buttons use
JAVA
|
More user friendly enums are introduced/used for check box types and fields justification.
| Use |
Moved radio button, push button, checkbox, text field drawing logic from forms to renderers (
| |
| Use |
Drawing logic related to comb property and choice fields was moved to the new | |
|
Bouncy Castle changes
Overview
In the scope of the 8.0.0 release, we've introduced several major breaking changes in the way we handle bouncy-castle dependencies. Now we no longer use org.bouncycastle
dependencies directly in our kernel
and sign
modules. Instead, all the necessary bouncy-castle related classes are grouped into two new modules: bouncy-castle-adapter
and bouncy-castle-fips-adapter
.
One of these modules is required to be in a classpath for the correct usage of cryptographic and signatures-related logic of our kernel
and sign
modules. However, by default they are not added as a dependency in order to provide the ability to choose one of them from the customer's side.
It is important not to add them both as a dependency since they are not compatible
Basically, this means that in order to use any other iText product (except several products which will be mentioned later) together with cryptographic or signatures-related logic either the bouncy-castle-adapter
or bouncy-castle-fips-adapter
module shall be added as a dependency. Otherwise, a special log message will be generated, such as the following:
Either com.itextpdf:bouncy-castle-adapter or com.itextpdf:bouncy-castle-fips-adapter dependency must be added in order to use BouncyCastleFactoryCreator
Android version limitations
Android artifacts directly depend on vanilla BouncyCastle, and don't support switching to BouncyCastle FIPS.
The whole idea behind adding the possibility to depend on BouncyCastle-FIPS in iText Core is that this build of Bouncy-Castle is FIPS-certified, i.e. it adheres to the FIPS standard as confirmed by a certification process.
Status of known vulnerability CVE-2022-45146 for FIPS Java API before 1.0.2.4
The whole idea behind adding the possibility to depend on BouncyCastle-FIPS in iText Core is that this build of Bouncy-Castle is FIPS-certified, i.e. it adheres to the FIPS standard as confirmed by certification process.
So, there are two reasons why iText Core keeps 1.0.2.3 dependency as the default one:
1) The only available version with the fix, 1.0.2.4, is not FIPS-certified (see https://www.bouncycastle.org/latest_releases.html#1.0.2.4-NONCERT)
2) BC-FIPS 1.0.2.3 is specifically certified against JRE 1.7, JRE 1.8, and JRE 1.11 (see https://www.bouncycastle.org/download/bouncy-castle-java-fips/roadmap/).
So, the expectation is that a user would want to opt-in to BC-FIPS dependency only with BC-FIPS 1.0.2.3 version and only for Java 8 or Java 11, while CVE-2022-45146 is only relevant to Java 13+.
It's also possible to switch to 1.0.2.4 by explicitly specifying this version in the Maven pom file.
The inside of bouncy-castle adapters
Java
com.itextpdf:bouncy-castle-adapter
Maven module encapsulates the bouncy-castle related classes for these two Maven dependencies:
org.bouncycastle:bcpkix-jdk15on
org.bouncycastle:bcprov-jdk15on
com.itextpdf:bouncy-castle-fips-adapter
Maven module encapsulates the bouncy-castle related classes for these two Maven dependencies:
org.bouncycastle:bcpkix-fips
org.bouncycastle:bc-fips
.NET
itext7.bouncy-castle-adapter
NuGet package encapsulates the bouncy-castle related classes for this NuGet dependency:
BouncyCastle.Cryptography
The itext.bouncy-castle-adapter
previously relied upon the Portable.BouncyCastle
dependency. However, as of the release of iText version 8.0.2, we transitioned to use the newer BouncyCastle.Cryptography
package officially maintained by the Legion of the Bouncy Castle Inc.
itext7.bouncy-castle-fips-adapter
NuGet package encapsulates the bouncy-castle related classes for these two assemblies (dlls taken from https://www.bouncycastle.org/download/bouncy-castle-c-fips/):
bc-fips-1.0.1.1
bcpkix-fips-1.0.1
Since those two dll files are not signed, we can't use them with .NET Framework and therefore the only supported target framework for bouncy-castle-fips-adapter
project is netstandard2.0
.
Ways to specify adapter
The idea of using adapters relies on the fact that only one adapter will be used in order to generate bouncy-castle related wrappers. The choice of adapter happens in the separate bouncy-castle-connector
module. If for some reason both bouncy-castle-adapter
and bouncy-castle-fips-adapter
dependencies are added, there are still ways to specify which one need to be used.
First of all, the connector module searches for the special system or environment variable called ITEXT_BOUNCY_CASTLE_FACTORY_NAME
. There are three possible values here:
"bouncy-castle" - forces connector to first try to use
bouncy-castle-adapter
module"bouncy-castle-fips" - forces connector to first try to use
bouncy-castle-fips-adapter
moduleanything else - ignored
However, the related dependency should still be added in order to successfully use the related module.
Bouncy-castle-fips-adapter module operating modes
Bouncy-castle-fips-adapter
module can be operated in two modes:
General mode
Approved mode
Note that only approved mode provides full FIPS compliance
General mode is enabled by default. There are several ways to enable approved mode.
Java:
Call
CryptoServicesRegistrar#setApprovedOnlyMode(true)
methodPass "
org.bouncycastle.fips.approved_only=true
" VM parameter to your build configuration
.NET:
Call
CryptoServicesRegistrar#SetApprovedOnlyMode(true)
methodPass environment or system variable
ITEXT_DOTNET_BOUNCY_CASTLE_FIPS_MODE
with "approved_mode
" value
Ones approved mode is enabled, you can't go back to the general mode. Also this operating mode is thread local, i.e. it needs to be explicitly set per each thread.
Several algorithms are not available in the approved mode (such as MD5
for example). You will receive a corresponding log message if an algorithm is not FIPS compliant and therefore cannot be used in approved mode.
API usage
In the scope of these changes several public API methods definitions also changed. Right now we use special interfaces instead of org.bouncycastle
classes in our API as a parameter or return values. In cases when you need to work with such interfaces to either create them from actual org.bouncycastle
values or retrieve actual values, you should access classes from either bouncy-castle-adapter
or bouncy-castle-fips-adapter
directly, and either call the constructor to create an instance and pass it as a parameter, or call the corresponding getter method to retrieve the actual value. Let's take a more precise look at one of the examples.
Let's say you need to create IX509Certificate
interface in order to pass it as a parameter to PdfSigner#SignDetached
method, and assuming you already have X509Certificate
object. Here there are two cases:
If you are using the
bouncy-castle-adapter
dependency, then you should createX509CertificateBC
class (i.e. a class with the same name withBC
postfix) passing your originalX509Certificate
as a parameter to the constructor, and then use this created wrapper as a parameter forPdfSigner#SignDetached
method.If you are using the
bouncy-castle-fips-adapter
dependency, then you should createX509CertificateBCFips
class passing your actualX509Certificate
as a parameter, and then use this created wrapper as a parameter forPdfSigner#SignDetached
method.
Correspondingly, if you for example need to retrieve an actual X509Certificate
object from either X509CertificateBC
or X509CertificateBCFips
, you should call GetCertificate
method.
This workflow is valid for each wrapper from bouncy-castle-adapter
and bouncy-castle-fips-adapter
modules, with the respective names.
Let's take a look at the code.
.NET
before:
ICipherParameters pk = pk12.GetKey(alias).Key;
X509CertificateEntry[] ce = pk12.GetCertificateChain(alias);
X509Certificate[] chain = new X509Certificate[ce.Length];
for (int k = 0; k < ce.Length; ++k)
{
chain[k] = ce[k].Certificate;
}
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileStream(dest, FileMode.Create),
new StampingProperties().UseAppendMode());
// Set signer options
signer.SetFieldName(name);
signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING);
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
// Sign the document using the detached mode, CMS or CAdES equivalent.
signer.SignDetached(pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
after:
ICipherParameters pk = pk12.GetKey(alias).Key;
X509CertificateEntry[] ce = pk12.GetCertificateChain(alias);
IX509Certificate[] chain = new IX509Certificate[ce.Length];
for (int k = 0; k < ce.Length; ++k)
{
chain[k] = new X509CertificateBC(ce[k].Certificate);
}
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileStream(dest, FileMode.Create),
new StampingProperties().UseAppendMode());
// Set signer options
signer.SetFieldName(name);
signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING);
IExternalSignature pks = new PrivateKeySignature(new PrivateKeyBC(pk), DigestAlgorithms.SHA256);
// Sign the document using the detached mode, CMS or CAdES equivalent.
signer.SignDetached(pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
Java
before:
if (TimestampConstants.UNDEFINED_TIMESTAMP_DATE != pkcs7.getTimeStampDate()) {
System.out.println("TimeStamp: " + date_format.format(pkcs7.getTimeStampDate().getTime()));
TSTInfo ts = pkcs7.getTimeStampToken();
System.out.println("TimeStamp service: " + ts.getTimeStampInfo().getTsa());
System.out.println("Timestamp verified? " + pkcs7.verifyTimestampImprint());
}
after:
if (TimestampConstants.UNDEFINED_TIMESTAMP_DATE != pkcs7.getTimeStampDate()) {
System.out.println("TimeStamp: " + date_format.format(pkcs7.getTimeStampDate().getTime()));
TSTInfo ts = ((TSTInfoBC) pkcs7.getTimeStampTokenInfo()).getTstInfo();
System.out.println("TimeStamp service: " + ts.getTsa());
System.out.println("Timestamp verified? " + pkcs7.verifyTimestampImprint());
}