How in Java using CryptoPro to sign a PDF document
Hello! I am an employee of Alfa Bank and am engaged in the development of software with built-in cryptographic information protection tools.
In this article I want to talk about the following things:
- the benefits of PDF as an electronic signature document;
- Java platform, itextpdf library and CryptoPro CSP CIP, as signature tools;
- about what difficulties had to be faced, about finalizing itextpdf;
- give an example of code that performs several signatures;
- talk about the appropriateness of using the PDF format as a document with a signature.
In the modern information world, the importance of electronic signatures and the basic services provided by it (data integrity, and the non-repudiation of authorship) is difficult to overestimate. State bodies, individuals and legal entities widely use electronic signatures in information exchange flows among themselves.
According to the law, an electronic signature can give documents legal force that is equal in importance to paper media signed with their own hand and sealed with the seal of an authorized person.
Benefits of PDF
As a container for storing a variety of information in a human-friendly presentation, the PDF format has rightfully gained popularity. It can be opened in any OS. A PDF document can contain not only text and tabular data, but also audio and video recordings, engineering graphics, and three-dimensional models. For additional processing of PDF documents, this format includes such a powerful tool as JavaScript support for changing content in forms and fields when an event occurs or a user action occurs.
Adobe Systems tools (PDF developer) support the use of electronic signatures. Unlike messages with an attached enhanced electronic signature of the PKCS # 7 standard and its enhancement CAdES, additional special software is not required to view a PDF document with a signature. In addition to the cryptographic provider, which is required in all cases.
Those. Adobe tools allow you to visualize an electronic signature in a document.
When signing a PDF document, you can set visualization parameters, specify the page number, the size of the block with the signature, the name of the field where to place it, add a graphic image, for example, a company logo, a scan of the director’s handwritten signature and the organization’s seal.
When viewing such documents, in addition to visualization in the document body, Acrobat and Adobe Reader display the Signatures tab with related information: an icon showing the status of the verification of the signature, information about whether the document has been changed, the result of the verification of the public key certificate, time of the last verification , page and field containing electronic signature.
Signature Tools
The Java version was used: "1.8.0_111" HotSpot (TM) 64-Bit Server VM (build 25.111-b14).
As a certified means of protecting information from a licensed developer, we use the cryptographic provider CryptoPro CSP v4.0 and CryptoPro JCP - v.2.0, with the installation of the CryptoPro Java CSP v.4.0 module.
Why CryptoPro JCP - v.2.0, with the CryptoPro Java CSP v.4.0 module ?
Because the provider CryptoPro JCP after a long non-certified period received a certificate of compliance from the regulator until 12/31/2018, and then, according to information from the developer, uncertainty may arise again with certification. The CryptoPro Java CSP v.4.0 module does not perform cryptographic transformations in itself and is essentially an API to the CryptoPro CSP provider, with the next certification of which there are no questions. Here it must be said that a valid certificate for a cryptographic information protection certificate is not required provided that the cryptographic provider is used exclusively for internal purposes.
According to the Java Cryptography Architecture (JCA) specification, in my application I specify and use the functions of the cryptographic provider: JCSP. After installing CryptoPro, this provider is displayed in the list of all available in the file ../java/jdk1.8.x_xxx/jre/lib/security/java.security, where you can configure which of them is preferable to use by default, unless explicitly indicated in the appendix: Do not forget that the export restrictions of the Java platform should be removed, which block the work of cryptographic providers with Russian encryption algorithms. To do this, in ../java/jdk1.8.x_xxx/jre/lib/security you need to replace the local_policy.jar and US_export_policy.jar files with those provided at:
#
# List of providers and their preference orders (see above):
#
security.provider.1=ru.CryptoPro.JCSP.JCSP
Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 8 Download
and containing in default_local.policy and default_US_export.policy : The use of iText itextpdf.com library for working with PDF documents in Java was dictated by the provider CryptoPro. CryptoPro JCP includes a Git patch file for the iTextpdf library version 5.1.3 - jcp-2.0.xxxxx \ Doc \ itextpdf \ itextpdf_5.1.3.gost.user.patch The patch adapts itextpdf to work with the CryptoPro provider. You need to download the source code of the library version 5.1.3, then use the Bash command line command of the Git version control system to apply the patch: git apply --stat itextpdf_5.1.3.gost.user.patch
grant {
// There is no restriction to any algorithms.
permission javax.crypto.CryptoAllPermission;
};
Next, you need to collect the resulting library from the updated source code and connect to the application.
CryptoPro JCP contains many examples in the samples-sources.jar file . In particular, there are examples of signature and verification of electronic documents in PDF documents. (\ PDF \ SignVerifyPDFExample.java).
Assembly Problems
After successfully updating the itextpdf source code, dependencies on ru.CryptoPro.JCP and ru.CryptoPro.reprov.x509 packages appear in it.
Without them, a project with the source code itextpdf_5.1.3.gost will not be assembled. You need to take the JCP.jar and JCPRevTools.jar files from CryptoPro 2.0 and put them in the JRE directory that Maven uses: Java \ jdk1.8.0_111 \ jre \ lib \ ext. Of course, they should be in the classPath application. So, the library is assembled, we connect it to the application. And here the main problem arises. iTextpdf_5.1.3 contains a dependency on Bouncy Castle version 1.46 - an open source library that implements a cryptographic provider and support for ASN.1 structures.
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile (default-compile) on project itextpdf: Compilation failure: Compilation failure:
[ERROR] \github\iTextpdf_5.1.3_patched_cryptopro_bc1.50\src\main\java\com\itextpdf\text\pdf\PdfPKCS7.java:[138,23] error: package ru.CryptoPro.JCP does not exist
[ERROR] \github\iTextpdf_5.1.3_patched_cryptopro_bc1.50\src\main\java\com\itextpdf\text\pdf\PdfPKCS7.java:[139,31] error: package ru.CryptoPro.reprov.x509 does not exist
org.bouncycastle bctsp-jdk15 1.46 jar compile true
The delivery of CryptoPro JCP 2.0, in turn, has dependencies on Bouncy Castle version 1.50 bcpkix-jdk15on-1.50 and bcprov-jdk15on-1.5, respectively, they are placed in jre / lib / ext when installing CryptoPro.
As a result, when launching our application and the method of signing a PDF, we get an error: Each Java developer has encountered such an exception in the main thread and knows that you can spend a lot of time analyzing the problem and how to fix it. The essence of the NoClassDefFoundError exception is the following - it is thrown when the Java virtual machine cannot, at runtime, find the specific class that was available at the compilation stage. What happens - the iTextpdf_5.1.3 library has a dependency on the older provider Bouncy Castle, and for new versions of iTextpdf there is no patch from CryptoPro.
Exception in thread "main" java.lang.NoClassDefFoundError: org/bouncycastle/asn1/DEREncodable
at com.itextpdf.text.pdf.PdfSigGenericPKCS.setSignInfo(PdfSigGenericPKCS.java:97)
at com.itextpdf.text.pdf.PdfSignatureAppearance.preClose(PdfSignatureAppearance.java:1003)
at com.itextpdf.text.pdf.PdfSignatureAppearance.preClose(PdfSignatureAppearance.java:904)
at com.itextpdf.text.pdf.PdfStamper.close(PdfStamper.java:194)
at ru.alfabank.ccjava.trustcore.logic.SignatureProcessor.pdfSignature(SignatureProcessor.java:965)
at ru.alfabank.ccjava.trustcore.logic.SignatureProcessor.main(SignatureProcessor.java:1363)
Caused by: java.lang.ClassNotFoundException: org.bouncycastle.asn1.DEREncodable
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 6 more
Specifically, in the delivery of CryptoPro JCP 2.0, the CAdES.jar library has dependencies on the new version of Bouncy Castle. If you remove this library from JRE or completely refuse to support the formation of CAdES signatures when installing CryptoPro JCP 2.0, then the problem will be solved.
But what if CAdES support should remain?
To get rid of the library conflict, you must take the following steps:
- we replace in the source code of the library iTextpdf_5.1.3_patched_cryptopro the dependence org.bouncycastle 1.46 with version 1.50.
org.bouncycastle bcprov-ext-jdk15on 1.50 org.bouncycastle bcprov-jdk15on 1.50 org.bouncycastle bcmail-jdk15on 1.50 - we make corrections to the project iTextpdf_5.1.3_patched_cryptopro, guided by the document Bouncy Castle "Porting from earlier BC releases to 1.47 and later"
- to fix those places iTextpdf_5.1.3_patched_cryptopro, where it wasn’t limited to the new names of the Bouncy Castle classes and methods, but the constructions in the methods were clearly changed, download the source code itextpdf 5.5.5, which has a dependency on Bouncy Castle 1.50, carefully transfer the implementation of the methods from there .
As a result, the project iTextpdf_5.1.3_patched_cryptopro_bc1.50 begins to assemble. The conflict is resolved, CryptoPro and itextpdf refer to the same version of org.bouncycastle 1.50.
The source code for iTextpdf_5.1.3_patched_cryptopro_bc1.50 is available on GitHub: iTextpdf_5.1.3_patched_cryptopro_bc1.50
Sample code, multiple PDF signatures
An example of using CryptoPro and iTextpdf_5.1.3_patched_cryptopro_bc1.50 is as follows:
/**
*
* @param aliases
* - имена контейнеров с ключами ЭП
* @param data
* - массив байтов с документом PDF
* @param pdfVersion
* - номер версии формата PDF
* @return
* @throws SignatureProcessorException
*/
public static byte[] samplePDFSignature(String[] aliases, byte[] data, char pdfVersion) throws SignatureProcessorException {
ByteArrayOutputStream bais = new ByteArrayOutputStream();
HashMap currSignAttrMap = new HashMap();
for (String alias : aliases) {
X509Certificate certificate = (X509Certificate) signAttributesMap1.get(alias)[0];
PrivateKey privateKey = (PrivateKey) signAttributesMap1.get(alias)[1];
currSignAttrMap.put(certificate, privateKey);
if (certificate == null) {
throw new SignatureProcessorException(PDF_SIGNATURE_ERROR + CERTIFICATE_NOT_FOUND_BY_ALIAS);
}
if (privateKey == null) {
throw new SignatureProcessorException(PDF_SIGNATURE_ERROR + PRIVATE_KEY_NOT_FOUND_BY_ALIAS);
}
}
try {
FileInputStream fis = new FileInputStream(new File(FILE_PATH));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n = 0;
while ((n = fis.read(buf, 0, buf.length)) != -1) {
baos.write(buf, 0, n);
}
fis.close();
byte[] im = baos.toByteArray();
X509Certificate innerCA = obtainCertFromTrustStoreJKS(false, INNER_CA);
PdfStamper stp = null;
PdfReader reader = null;
int pageNumber = 1;
for (Entry entry : currSignAttrMap.entrySet()) {
if (bais.toByteArray().length == 0) {
reader = new PdfReader(data);
} else {
reader = new PdfReader(bais.toByteArray());
bais = new ByteArrayOutputStream();
}
stp = PdfStamper.createSignature(reader, bais, pdfVersion); //'\0'
Certificate[] certPath = new Certificate[] {entry.getKey(), innerCA};
PdfSignatureAppearance sap = stp.getSignatureAppearance();
sap.setProvider("JCSP"); //JCP
sap.setCrypto(entry.getValue(), certPath, null,
PdfSignatureAppearance.CRYPTOPRO_SIGNED);
Image image = Image.getInstance(im);
sap.setImage(image);
sap.setVisibleSignature(new Rectangle(150, 150), pageNumber, null);
pageNumber++;
stp.close();
bais.close();
reader.close();
}
} catch (RuntimeException e) {
throw new SignatureProcessorException(PDF_SIGNATURE_ERROR + ExceptionUtils.getFullStackTrace(e));
} catch (IOException e) {
throw new SignatureProcessorException(PDF_SIGNATURE_ERROR + ExceptionUtils.getFullStackTrace(e));
} catch (DocumentException e) {
throw new SignatureProcessorException(PDF_SIGNATURE_ERROR + ExceptionUtils.getFullStackTrace(e));
} catch (CertificateEncodingException e) {
throw new SignatureProcessorException(PDF_SIGNATURE_ERROR + ExceptionUtils.getFullStackTrace(e));
} catch (Exception e) {
throw new SignatureProcessorException(PDF_SIGNATURE_ERROR + ExceptionUtils.getFullStackTrace(e));
}
return bais.toByteArray();
}
The method accepts a list of container names with electronic signature keys, bytes of the PDF document, and the version number of the PDF format. According to the specified names, keys and certificates are retrieved from the cache. The image is loaded for visualization of the signature.
An object with a root certificate is created to form the certification path, which is required by the setCrypto method of class com.itextpdf.text.pdf.PdfSignatureAppearance.
In the loop, the pages of the PDF document are signed with the keys whose names were passed to the method.
For the demonstration, two signatures were made - valid and invalid with an invalid certificate.
The Signatures tab is as follows:
Using Electronic Signature of PDF Documents
In my opinion, signing directly in PDF format is a special case. Such a stamp in the file itself is intended more for visualization of electronic signature with the aim of checking it with eyes. This is convenient and effective when it comes to a small number of documents where automation is not required, and the operator performs the role of the inspector.
The operator cannot cope with the intensive process of exchanging a large number of electronic documents. In this case, the information system performs automation, and the need for beautiful visualization disappears. Accordingly, it is unnecessary to waste resources of the information system and electronic channels on the transfer of bulk PDF files and processing of stamp images.
It is more practical to form an electronic signature as a separate file or to wrap a file and signature in one container of the PKCS # 7 standard (CAdES). This standard with attached or detached signature is perfect for large workflow between information systems.
The same PDF document can be signed according to the CAdES standard with a disconnected signature, as a result there will be two files - the PDF itself and the container with the signature.
Recall the problems encountered when signing up for Java. Conclusion - PDF is now poorly supported by CryptoPro in terms of the application programming interface for Java. The existing itextpdf library had to be edited on its own.
In the field of activities related to the development of commercial software with built-in cryptographic information protection tools, this approach is unacceptable, patches, updates and improvements should be performed by the licensee organization. Therefore, the method of signing a PDF document described in the post can be used to demonstrate capabilities and for internal use.