SigUtils.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /*
  2. * Copyright 2017 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.izouma.awesomeAdmin.web.signature;
  17. import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerificationException;
  18. import com.izouma.awesomeAdmin.web.signature.cert.CertificateVerifier;
  19. import org.apache.commons.logging.Log;
  20. import org.apache.commons.logging.LogFactory;
  21. import org.apache.pdfbox.cos.COSArray;
  22. import org.apache.pdfbox.cos.COSBase;
  23. import org.apache.pdfbox.cos.COSDictionary;
  24. import org.apache.pdfbox.cos.COSName;
  25. import org.apache.pdfbox.pdmodel.PDDocument;
  26. import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
  27. import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
  28. import org.bouncycastle.asn1.ASN1Object;
  29. import org.bouncycastle.asn1.cms.Attribute;
  30. import org.bouncycastle.asn1.cms.AttributeTable;
  31. import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
  32. import org.bouncycastle.asn1.x509.KeyPurposeId;
  33. import org.bouncycastle.cert.X509CertificateHolder;
  34. import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
  35. import org.bouncycastle.cms.CMSException;
  36. import org.bouncycastle.cms.CMSSignedData;
  37. import org.bouncycastle.cms.SignerInformation;
  38. import org.bouncycastle.cms.SignerInformationVerifier;
  39. import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
  40. import org.bouncycastle.operator.OperatorCreationException;
  41. import org.bouncycastle.tsp.TSPException;
  42. import org.bouncycastle.tsp.TimeStampToken;
  43. import org.bouncycastle.util.Selector;
  44. import org.bouncycastle.util.Store;
  45. import java.io.ByteArrayInputStream;
  46. import java.io.IOException;
  47. import java.io.InputStream;
  48. import java.net.URL;
  49. import java.security.GeneralSecurityException;
  50. import java.security.MessageDigest;
  51. import java.security.cert.CertificateException;
  52. import java.security.cert.CertificateParsingException;
  53. import java.security.cert.X509Certificate;
  54. import java.util.*;
  55. /**
  56. * Utility class for the signature / timestamp examples.
  57. *
  58. * @author Tilman Hausherr
  59. */
  60. public class SigUtils
  61. {
  62. private static final Log LOG = LogFactory.getLog(SigUtils.class);
  63. private SigUtils()
  64. {
  65. }
  66. /**
  67. * Get the access permissions granted for this document in the DocMDP transform parameters
  68. * dictionary. Details are described in the table "Entries in the DocMDP transform parameters
  69. * dictionary" in the PDF specification.
  70. *
  71. * @param doc document.
  72. * @return the permission value. 0 means no DocMDP transform parameters dictionary exists. Other
  73. * return values are 1, 2 or 3. 2 is also returned if the DocMDP transform parameters dictionary
  74. * is found but did not contain a /P entry, or if the value is outside the valid range.
  75. */
  76. public static int getMDPPermission(PDDocument doc)
  77. {
  78. COSDictionary permsDict = doc.getDocumentCatalog().getCOSObject()
  79. .getCOSDictionary(COSName.PERMS);
  80. if (permsDict != null)
  81. {
  82. COSDictionary signatureDict = permsDict.getCOSDictionary(COSName.DOCMDP);
  83. if (signatureDict != null)
  84. {
  85. COSArray refArray = signatureDict.getCOSArray(COSName.REFERENCE);
  86. if (refArray instanceof COSArray)
  87. {
  88. for (int i = 0; i < refArray.size(); ++i)
  89. {
  90. COSBase base = refArray.getObject(i);
  91. if (base instanceof COSDictionary)
  92. {
  93. COSDictionary sigRefDict = (COSDictionary) base;
  94. if (COSName.DOCMDP.equals(sigRefDict.getDictionaryObject(COSName.TRANSFORM_METHOD)))
  95. {
  96. base = sigRefDict.getDictionaryObject(COSName.TRANSFORM_PARAMS);
  97. if (base instanceof COSDictionary)
  98. {
  99. COSDictionary transformDict = (COSDictionary) base;
  100. int accessPermissions = transformDict.getInt(COSName.P, 2);
  101. if (accessPermissions < 1 || accessPermissions > 3)
  102. {
  103. accessPermissions = 2;
  104. }
  105. return accessPermissions;
  106. }
  107. }
  108. }
  109. }
  110. }
  111. }
  112. }
  113. return 0;
  114. }
  115. /**
  116. * Set the "modification detection and prevention" permissions granted for this document in the
  117. * DocMDP transform parameters dictionary. Details are described in the table "Entries in the
  118. * DocMDP transform parameters dictionary" in the PDF specification.
  119. *
  120. * @param doc The document.
  121. * @param signature The signature object.
  122. * @param accessPermissions The permission value (1, 2 or 3).
  123. *
  124. * @throws IOException if a signature exists.
  125. */
  126. public static void setMDPPermission(PDDocument doc, PDSignature signature, int accessPermissions)
  127. throws IOException
  128. {
  129. for (PDSignature sig : doc.getSignatureDictionaries())
  130. {
  131. // "Approval signatures shall follow the certification signature if one is present"
  132. // thus we don't care about timestamp signatures
  133. if (COSName.DOC_TIME_STAMP.equals(sig.getCOSObject().getItem(COSName.TYPE)))
  134. {
  135. continue;
  136. }
  137. if (sig.getCOSObject().containsKey(COSName.CONTENTS))
  138. {
  139. throw new IOException("DocMDP transform method not allowed if an approval signature exists");
  140. }
  141. }
  142. COSDictionary sigDict = signature.getCOSObject();
  143. // DocMDP specific stuff
  144. COSDictionary transformParameters = new COSDictionary();
  145. transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
  146. transformParameters.setInt(COSName.P, accessPermissions);
  147. transformParameters.setName(COSName.V, "1.2");
  148. transformParameters.setNeedToBeUpdated(true);
  149. COSDictionary referenceDict = new COSDictionary();
  150. referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
  151. referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
  152. referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA1"));
  153. referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
  154. referenceDict.setNeedToBeUpdated(true);
  155. COSArray referenceArray = new COSArray();
  156. referenceArray.add(referenceDict);
  157. sigDict.setItem(COSName.REFERENCE, referenceArray);
  158. referenceArray.setNeedToBeUpdated(true);
  159. // Catalog
  160. COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
  161. COSDictionary permsDict = new COSDictionary();
  162. catalogDict.setItem(COSName.PERMS, permsDict);
  163. permsDict.setItem(COSName.DOCMDP, signature);
  164. catalogDict.setNeedToBeUpdated(true);
  165. permsDict.setNeedToBeUpdated(true);
  166. }
  167. /**
  168. * Log if the certificate is not valid for signature usage. Doing this
  169. * anyway results in Adobe Reader failing to validate the PDF.
  170. *
  171. * @param x509Certificate
  172. * @throws CertificateParsingException
  173. */
  174. public static void checkCertificateUsage(X509Certificate x509Certificate)
  175. throws CertificateParsingException
  176. {
  177. // Check whether signer certificate is "valid for usage"
  178. // https://stackoverflow.com/a/52765021/535646
  179. // https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/changes.html#id1
  180. boolean[] keyUsage = x509Certificate.getKeyUsage();
  181. if (keyUsage != null && !keyUsage[0] && !keyUsage[1])
  182. {
  183. // (unclear what "signTransaction" is)
  184. // https://tools.ietf.org/html/rfc5280#section-4.2.1.3
  185. LOG.error("Certificate key usage does not include " +
  186. "digitalSignature nor nonRepudiation");
  187. }
  188. List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage();
  189. if (extendedKeyUsage != null &&
  190. !extendedKeyUsage.contains(KeyPurposeId.id_kp_emailProtection.toString()) &&
  191. !extendedKeyUsage.contains(KeyPurposeId.id_kp_codeSigning.toString()) &&
  192. !extendedKeyUsage.contains(KeyPurposeId.anyExtendedKeyUsage.toString()) &&
  193. !extendedKeyUsage.contains("1.2.840.113583.1.1.5") &&
  194. // not mentioned in Adobe document, but tolerated in practice
  195. !extendedKeyUsage.contains("1.3.6.1.4.1.311.10.3.12"))
  196. {
  197. LOG.error("Certificate extended key usage does not include " +
  198. "emailProtection, nor codeSigning, nor anyExtendedKeyUsage, " +
  199. "nor 'Adobe Authentic Documents Trust'");
  200. }
  201. }
  202. /**
  203. * Log if the certificate is not valid for timestamping.
  204. *
  205. * @param x509Certificate
  206. * @throws CertificateParsingException
  207. */
  208. public static void checkTimeStampCertificateUsage(X509Certificate x509Certificate)
  209. throws CertificateParsingException
  210. {
  211. List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage();
  212. // https://tools.ietf.org/html/rfc5280#section-4.2.1.12
  213. if (extendedKeyUsage != null &&
  214. !extendedKeyUsage.contains(KeyPurposeId.id_kp_timeStamping.toString()))
  215. {
  216. LOG.error("Certificate extended key usage does not include timeStamping");
  217. }
  218. }
  219. /**
  220. * Log if the certificate is not valid for responding.
  221. *
  222. * @param x509Certificate
  223. * @throws CertificateParsingException
  224. */
  225. public static void checkResponderCertificateUsage(X509Certificate x509Certificate)
  226. throws CertificateParsingException
  227. {
  228. List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage();
  229. // https://tools.ietf.org/html/rfc5280#section-4.2.1.12
  230. if (extendedKeyUsage != null &&
  231. !extendedKeyUsage.contains(KeyPurposeId.id_kp_OCSPSigning.toString()))
  232. {
  233. LOG.error("Certificate extended key usage does not include OCSP responding");
  234. }
  235. }
  236. /**
  237. * Gets the last relevant signature in the document, i.e. the one with the highest offset.
  238. *
  239. * @param document to get its last signature
  240. * @return last signature or null when none found
  241. */
  242. public static PDSignature getLastRelevantSignature(PDDocument document)
  243. {
  244. Comparator<PDSignature> comparatorByOffset =
  245. Comparator.comparing(sig -> sig.getByteRange()[1]);
  246. // we can't use getLastSignatureDictionary() because this will fail (see PDFBOX-3978)
  247. // if a signature is assigned to a pre-defined empty signature field that isn't the last.
  248. // we get the last in time by looking at the offset in the PDF file.
  249. Optional<PDSignature> optLastSignature =
  250. document.getSignatureDictionaries().stream().
  251. sorted(comparatorByOffset.reversed()).
  252. findFirst();
  253. if (optLastSignature.isPresent())
  254. {
  255. PDSignature lastSignature = optLastSignature.get();
  256. COSBase type = lastSignature.getCOSObject().getItem(COSName.TYPE);
  257. if (type == null || COSName.SIG.equals(type) || COSName.DOC_TIME_STAMP.equals(type))
  258. {
  259. return lastSignature;
  260. }
  261. }
  262. return null;
  263. }
  264. public static TimeStampToken extractTimeStampTokenFromSignerInformation(SignerInformation signerInformation)
  265. throws CMSException, IOException, TSPException
  266. {
  267. if (signerInformation.getUnsignedAttributes() == null)
  268. {
  269. return null;
  270. }
  271. AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
  272. // https://stackoverflow.com/questions/1647759/how-to-validate-if-a-signed-jar-contains-a-timestamp
  273. Attribute attribute = unsignedAttributes.get(
  274. PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
  275. if (attribute == null)
  276. {
  277. return null;
  278. }
  279. ASN1Object obj = (ASN1Object) attribute.getAttrValues().getObjectAt(0);
  280. CMSSignedData signedTSTData = new CMSSignedData(obj.getEncoded());
  281. return new TimeStampToken(signedTSTData);
  282. }
  283. public static void validateTimestampToken(TimeStampToken timeStampToken)
  284. throws TSPException, CertificateException, OperatorCreationException, IOException
  285. {
  286. // https://stackoverflow.com/questions/42114742/
  287. @SuppressWarnings("unchecked") // TimeStampToken.getSID() is untyped
  288. Collection<X509CertificateHolder> tstMatches =
  289. timeStampToken.getCertificates().getMatches((Selector<X509CertificateHolder>) timeStampToken.getSID());
  290. X509CertificateHolder certificateHolder = tstMatches.iterator().next();
  291. SignerInformationVerifier siv =
  292. new JcaSimpleSignerInfoVerifierBuilder().setProvider(SecurityProvider.getProvider()).build(certificateHolder);
  293. timeStampToken.validate(siv);
  294. }
  295. /**
  296. * Verify the certificate chain up to the root, including OCSP or CRL. However this does not
  297. * test whether the root certificate is in a trusted list.<br><br>
  298. * Please post bad PDF files that succeed and good PDF files that fail in
  299. * <a href="https://issues.apache.org/jira/browse/PDFBOX-3017">PDFBOX-3017</a>.
  300. *
  301. * @param certificatesStore
  302. * @param certFromSignedData
  303. * @param signDate
  304. * @throws CertificateVerificationException
  305. * @throws CertificateException
  306. */
  307. public static void verifyCertificateChain(Store<X509CertificateHolder> certificatesStore,
  308. X509Certificate certFromSignedData, Date signDate)
  309. throws CertificateVerificationException, CertificateException
  310. {
  311. Collection<X509CertificateHolder> certificateHolders = certificatesStore.getMatches(null);
  312. Set<X509Certificate> additionalCerts = new HashSet<>();
  313. JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();
  314. for (X509CertificateHolder certHolder : certificateHolders)
  315. {
  316. X509Certificate certificate = certificateConverter.getCertificate(certHolder);
  317. if (!certificate.equals(certFromSignedData))
  318. {
  319. additionalCerts.add(certificate);
  320. }
  321. }
  322. CertificateVerifier.verifyCertificate(certFromSignedData, additionalCerts, true, signDate);
  323. //TODO check whether the root certificate is in our trusted list.
  324. // For the EU, get a list here:
  325. // https://ec.europa.eu/digital-single-market/en/eu-trusted-lists-trust-service-providers
  326. // ( getRootCertificates() is not helpful because these are SSL certificates)
  327. }
  328. /**
  329. * Get certificate of a TSA.
  330. *
  331. * @param tsaUrl URL
  332. * @return the X.509 certificate.
  333. *
  334. * @throws GeneralSecurityException
  335. * @throws IOException
  336. */
  337. public static X509Certificate getTsaCertificate(String tsaUrl)
  338. throws GeneralSecurityException, IOException
  339. {
  340. MessageDigest digest = MessageDigest.getInstance("SHA-256");
  341. TSAClient tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
  342. InputStream emptyStream = new ByteArrayInputStream(new byte[0]);
  343. TimeStampToken timeStampToken = tsaClient.getTimeStampToken(emptyStream);
  344. return getCertificateFromTimeStampToken(timeStampToken);
  345. }
  346. /**
  347. * Extract X.509 certificate from a timestamp
  348. * @param timeStampToken
  349. * @return the X.509 certificate.
  350. * @throws CertificateException
  351. */
  352. public static X509Certificate getCertificateFromTimeStampToken(TimeStampToken timeStampToken)
  353. throws CertificateException
  354. {
  355. @SuppressWarnings("unchecked") // TimeStampToken.getSID() is untyped
  356. Collection<X509CertificateHolder> tstMatches =
  357. timeStampToken.getCertificates().getMatches(timeStampToken.getSID());
  358. X509CertificateHolder tstCertHolder = tstMatches.iterator().next();
  359. return new JcaX509CertificateConverter().getCertificate(tstCertHolder);
  360. }
  361. }