You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PDFSignature.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.pdf;
  19. import java.io.BufferedInputStream;
  20. import java.io.BufferedOutputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.OutputStream;
  24. import java.net.URI;
  25. import java.net.URISyntaxException;
  26. import java.security.GeneralSecurityException;
  27. import java.security.KeyStore;
  28. import java.security.PrivateKey;
  29. import java.security.cert.Certificate;
  30. import java.security.cert.X509Certificate;
  31. import java.util.Arrays;
  32. import java.util.Date;
  33. import java.util.Enumeration;
  34. import org.bouncycastle.cert.jcajce.JcaCertStore;
  35. import org.bouncycastle.cms.CMSException;
  36. import org.bouncycastle.cms.CMSSignedData;
  37. import org.bouncycastle.cms.CMSSignedDataGenerator;
  38. import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
  39. import org.bouncycastle.operator.ContentSigner;
  40. import org.bouncycastle.operator.OperatorException;
  41. import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
  42. import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
  43. import org.apache.commons.io.IOUtils;
  44. import org.apache.commons.io.output.CountingOutputStream;
  45. import org.apache.xmlgraphics.io.TempResourceURIGenerator;
  46. import org.apache.fop.apps.FOUserAgent;
  47. public class PDFSignature {
  48. private static final int SIZE_OF_CONTENTS = 18944;
  49. private static final TempResourceURIGenerator TEMP_URI_GENERATOR = new TempResourceURIGenerator("pdfsign2");
  50. private Perms perms;
  51. private PDFRoot root;
  52. private PrivateKey privateKey;
  53. private long startOfDocMDP;
  54. private long startOfContents;
  55. private FOUserAgent userAgent;
  56. private URI tempURI;
  57. private PDFSignParams signParams;
  58. static class TransformParams extends PDFDictionary {
  59. TransformParams() {
  60. put("Type", new PDFName("TransformParams"));
  61. put("P", 2);
  62. put("V", new PDFName("1.2"));
  63. }
  64. }
  65. static class SigRef extends PDFDictionary {
  66. SigRef() {
  67. put("Type", new PDFName("SigRef"));
  68. put("TransformMethod", new PDFName("DocMDP"));
  69. put("DigestMethod", new PDFName("SHA1"));
  70. put("TransformParams", new TransformParams());
  71. }
  72. }
  73. class Contents extends PDFObject {
  74. protected String toPDFString() {
  75. return PDFText.toHex(new byte[SIZE_OF_CONTENTS / 2]);
  76. }
  77. public int output(OutputStream stream) throws IOException {
  78. CountingOutputStream countingOutputStream = (CountingOutputStream) stream;
  79. startOfContents = startOfDocMDP + countingOutputStream.getByteCount();
  80. return super.output(stream);
  81. }
  82. }
  83. class DocMDP extends PDFDictionary {
  84. DocMDP() {
  85. put("Type", new PDFName("Sig"));
  86. put("Filter", new PDFName("Adobe.PPKLite"));
  87. put("SubFilter", new PDFName("adbe.pkcs7.detached"));
  88. if (signParams.getName() != null) {
  89. put("Name", signParams.getName());
  90. }
  91. if (signParams.getLocation() != null) {
  92. put("Location", signParams.getLocation());
  93. }
  94. if (signParams.getReason() != null) {
  95. put("Reason", signParams.getReason());
  96. }
  97. put("M", PDFInfo.formatDateTime(new Date()));
  98. PDFArray array = new PDFArray();
  99. array.add(new SigRef());
  100. put("Reference", array);
  101. put("Contents", new Contents());
  102. put("ByteRange", new PDFArray(0, 1000000000, 1000000000, 1000000000));
  103. }
  104. public int output(OutputStream stream) throws IOException {
  105. if (stream instanceof CountingOutputStream) {
  106. CountingOutputStream countingOutputStream = (CountingOutputStream) stream;
  107. startOfDocMDP = countingOutputStream.getByteCount();
  108. return super.output(stream);
  109. }
  110. throw new IOException("Disable pdf linearization");
  111. }
  112. }
  113. static class Perms extends PDFDictionary {
  114. DocMDP docMDP;
  115. Perms(PDFRoot root, DocMDP docMDP) {
  116. this.docMDP = docMDP;
  117. root.getDocument().registerObject(docMDP);
  118. put("DocMDP", docMDP);
  119. }
  120. }
  121. static class SigField extends PDFDictionary {
  122. SigField(Perms perms, PDFPage page, PDFRoot root) {
  123. root.getDocument().registerObject(this);
  124. put("FT", new PDFName("Sig"));
  125. put("Type", new PDFName("Annot"));
  126. put("Subtype", new PDFName("Widget"));
  127. put("F", 132);
  128. put("T", "Signature1");
  129. put("Rect", new PDFRectangle(0, 0, 0, 0));
  130. put("V", perms.docMDP);
  131. put("P", new PDFReference(page));
  132. put("AP", new AP(root));
  133. }
  134. }
  135. static class AP extends PDFDictionary {
  136. AP(PDFRoot root) {
  137. put("N", new FormXObject(root));
  138. }
  139. }
  140. static class FormXObject extends PDFStream {
  141. FormXObject(PDFRoot root) {
  142. root.getDocument().registerObject(this);
  143. put("Length", 0);
  144. put("Type", new PDFName("XObject"));
  145. put("Subtype", new PDFName("Form"));
  146. put("BBox", new PDFRectangle(0, 0, 0, 0));
  147. }
  148. }
  149. static class AcroForm extends PDFDictionary {
  150. AcroForm(SigField sigField) {
  151. PDFArray fields = new PDFArray();
  152. fields.add(sigField);
  153. put("Fields", fields);
  154. put("SigFlags", 3);
  155. }
  156. }
  157. public PDFSignature(PDFRoot root, FOUserAgent userAgent, PDFSignParams signParams) {
  158. this.root = root;
  159. this.userAgent = userAgent;
  160. this.signParams = signParams;
  161. perms = new Perms(root, new DocMDP());
  162. root.put("Perms", perms);
  163. tempURI = TEMP_URI_GENERATOR.generate();
  164. }
  165. public void add(PDFPage page) {
  166. SigField sigField = new SigField(perms, page, root);
  167. root.put("AcroForm", new AcroForm(sigField));
  168. page.addAnnotation(sigField);
  169. }
  170. public void signPDF(URI uri, OutputStream os) throws IOException {
  171. try (InputStream pdfIS = getTempIS(uri)) {
  172. pdfIS.mark(Integer.MAX_VALUE);
  173. String byteRangeValues = "0 1000000000 1000000000 1000000000";
  174. String byteRange = "\n /ByteRange [" + byteRangeValues + "]";
  175. int pdfLength = pdfIS.available();
  176. long offsetToPDFEnd = startOfContents + SIZE_OF_CONTENTS + 2 + byteRange.length();
  177. long endOfPDFSize = pdfLength - offsetToPDFEnd;
  178. String byteRangeValues2 = String.format("0 %s %s %s", startOfContents,
  179. startOfContents + SIZE_OF_CONTENTS + 2, byteRange.length() + endOfPDFSize);
  180. byteRange = "\n /ByteRange [" + byteRangeValues2 + "]";
  181. String byteRangePadding = new String(new char[byteRangeValues.length() - byteRangeValues2.length()])
  182. .replace("\0", " ");
  183. try (OutputStream editedPDF = getTempOS()) {
  184. IOUtils.copyLarge(pdfIS, editedPDF, 0, startOfContents);
  185. editedPDF.write(byteRange.getBytes("UTF-8"));
  186. editedPDF.write(byteRangePadding.getBytes("UTF-8"));
  187. IOUtils.copyLarge(pdfIS, editedPDF, offsetToPDFEnd - startOfContents, Long.MAX_VALUE);
  188. }
  189. pdfIS.reset();
  190. IOUtils.copyLarge(pdfIS, os, 0, startOfContents);
  191. try (InputStream is = getTempIS(tempURI)) {
  192. byte[] signed = readPKCS(is);
  193. String signedHexPadding = new String(new char[SIZE_OF_CONTENTS - (signed.length * 2)])
  194. .replace("\0", "0");
  195. String signedHex = "<" + PDFText.toHex(signed, false) + signedHexPadding + ">";
  196. os.write(signedHex.getBytes("UTF-8"));
  197. }
  198. os.write(byteRange.getBytes("UTF-8"));
  199. os.write(byteRangePadding.getBytes("UTF-8"));
  200. IOUtils.copyLarge(pdfIS, os, offsetToPDFEnd - startOfContents, Long.MAX_VALUE);
  201. }
  202. }
  203. private OutputStream getTempOS() throws IOException {
  204. return new BufferedOutputStream(userAgent.getResourceResolver().getOutputStream(tempURI));
  205. }
  206. private InputStream getTempIS(URI uri) throws IOException {
  207. return new BufferedInputStream(userAgent.getResourceResolver().getResource(uri));
  208. }
  209. private byte[] readPKCS(InputStream pdf) throws IOException {
  210. try {
  211. char[] password = signParams.getPassword().toCharArray();
  212. KeyStore keystore = KeyStore.getInstance("PKCS12");
  213. try (InputStream is = userAgent.getResourceResolver().getResource(signParams.getPkcs12())) {
  214. keystore.load(is, password);
  215. }
  216. Certificate[] certificates = readKeystore(keystore, password);
  217. return sign(pdf, certificates);
  218. } catch (GeneralSecurityException | URISyntaxException | OperatorException | CMSException e) {
  219. throw new RuntimeException(e);
  220. }
  221. }
  222. private Certificate[] readKeystore(KeyStore keystore, char[] password)
  223. throws GeneralSecurityException, IOException {
  224. Enumeration<String> aliases = keystore.aliases();
  225. while (aliases.hasMoreElements()) {
  226. String alias = aliases.nextElement();
  227. privateKey = (PrivateKey) keystore.getKey(alias, password);
  228. Certificate[] certChain = keystore.getCertificateChain(alias);
  229. if (certChain != null) {
  230. Certificate cert = certChain[0];
  231. if (cert instanceof X509Certificate) {
  232. ((X509Certificate) cert).checkValidity();
  233. }
  234. return certChain;
  235. }
  236. }
  237. throw new IOException("Could not find certificate");
  238. }
  239. private byte[] sign(InputStream content, Certificate[] certChain)
  240. throws GeneralSecurityException, OperatorException, CMSException, IOException {
  241. CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
  242. X509Certificate cert = (X509Certificate) certChain[0];
  243. ContentSigner sha2Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
  244. gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
  245. new JcaDigestCalculatorProviderBuilder().build()).build(sha2Signer, cert));
  246. gen.addCertificates(new JcaCertStore(Arrays.asList(certChain)));
  247. CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
  248. CMSSignedData signedData = gen.generate(msg, false);
  249. return signedData.getEncoded();
  250. }
  251. }