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.

TSPTimeStampService.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. /* ====================================================================
  16. This product contains an ASLv2 licensed version of the OOXML signer
  17. package from the eID Applet project
  18. http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
  19. Copyright (C) 2008-2014 FedICT.
  20. ================================================================= */
  21. package org.apache.poi.poifs.crypt.dsig.services;
  22. import static org.apache.logging.log4j.util.Unbox.box;
  23. import java.io.ByteArrayInputStream;
  24. import java.io.IOException;
  25. import java.math.BigInteger;
  26. import java.security.GeneralSecurityException;
  27. import java.security.MessageDigest;
  28. import java.security.SecureRandom;
  29. import java.security.cert.CertificateFactory;
  30. import java.security.cert.X509CRL;
  31. import java.security.cert.X509Certificate;
  32. import java.util.Collections;
  33. import java.util.List;
  34. import java.util.Map;
  35. import java.util.Objects;
  36. import java.util.function.Function;
  37. import java.util.stream.Collectors;
  38. import java.util.stream.Stream;
  39. import javax.security.auth.x500.X500Principal;
  40. import org.apache.logging.log4j.LogManager;
  41. import org.apache.logging.log4j.Logger;
  42. import org.apache.poi.poifs.crypt.CryptoFunctions;
  43. import org.apache.poi.poifs.crypt.HashAlgorithm;
  44. import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
  45. import org.apache.poi.poifs.crypt.dsig.SignatureConfig.CRLEntry;
  46. import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
  47. import org.apache.poi.poifs.crypt.dsig.services.TimeStampHttpClient.TimeStampHttpClientResponse;
  48. import org.bouncycastle.asn1.ASN1IA5String;
  49. import org.bouncycastle.asn1.ASN1ObjectIdentifier;
  50. import org.bouncycastle.asn1.ASN1Primitive;
  51. import org.bouncycastle.asn1.cmp.PKIFailureInfo;
  52. import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
  53. import org.bouncycastle.asn1.x500.X500Name;
  54. import org.bouncycastle.asn1.x509.CRLDistPoint;
  55. import org.bouncycastle.asn1.x509.DistributionPoint;
  56. import org.bouncycastle.asn1.x509.DistributionPointName;
  57. import org.bouncycastle.asn1.x509.Extension;
  58. import org.bouncycastle.asn1.x509.GeneralName;
  59. import org.bouncycastle.asn1.x509.GeneralNames;
  60. import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
  61. import org.bouncycastle.cert.X509CertificateHolder;
  62. import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
  63. import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
  64. import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
  65. import org.bouncycastle.cms.SignerId;
  66. import org.bouncycastle.cms.SignerInformationVerifier;
  67. import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
  68. import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
  69. import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
  70. import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
  71. import org.bouncycastle.tsp.TimeStampRequest;
  72. import org.bouncycastle.tsp.TimeStampRequestGenerator;
  73. import org.bouncycastle.tsp.TimeStampResponse;
  74. import org.bouncycastle.tsp.TimeStampToken;
  75. /**
  76. * A TSP time-stamp service implementation.
  77. */
  78. public class TSPTimeStampService implements TimeStampService {
  79. private static final Logger LOG = LogManager.getLogger(TSPTimeStampService.class);
  80. /**
  81. * Maps the digest algorithm to corresponding OID value.
  82. */
  83. public ASN1ObjectIdentifier mapDigestAlgoToOID(HashAlgorithm digestAlgo) {
  84. switch (digestAlgo) {
  85. case sha1: return X509ObjectIdentifiers.id_SHA1;
  86. case sha256: return NISTObjectIdentifiers.id_sha256;
  87. case sha384: return NISTObjectIdentifiers.id_sha384;
  88. case sha512: return NISTObjectIdentifiers.id_sha512;
  89. default:
  90. throw new IllegalArgumentException("unsupported digest algo: " + digestAlgo);
  91. }
  92. }
  93. @Override
  94. @SuppressWarnings({"squid:S2647"})
  95. public byte[] timeStamp(SignatureInfo signatureInfo, byte[] data, RevocationData revocationData) throws Exception {
  96. SignatureConfig signatureConfig = signatureInfo.getSignatureConfig();
  97. // digest the message
  98. MessageDigest messageDigest = CryptoFunctions.getMessageDigest(signatureConfig.getTspDigestAlgo());
  99. byte[] digest = messageDigest.digest(data);
  100. // generate the TSP request
  101. BigInteger nonce = new BigInteger(128, new SecureRandom());
  102. TimeStampRequestGenerator requestGenerator = new TimeStampRequestGenerator();
  103. requestGenerator.setCertReq(true);
  104. String requestPolicy = signatureConfig.getTspRequestPolicy();
  105. if (requestPolicy != null) {
  106. requestGenerator.setReqPolicy(new ASN1ObjectIdentifier(requestPolicy));
  107. }
  108. ASN1ObjectIdentifier digestAlgoOid = mapDigestAlgoToOID(signatureConfig.getTspDigestAlgo());
  109. TimeStampRequest request = requestGenerator.generate(digestAlgoOid, digest, nonce);
  110. TimeStampHttpClient httpClient = signatureConfig.getTspHttpClient();
  111. httpClient.init(signatureConfig);
  112. httpClient.setContentTypeIn(signatureConfig.isTspOldProtocol() ? "application/timestamp-request" : "application/timestamp-query");
  113. TimeStampHttpClientResponse response = httpClient.post(signatureConfig.getTspUrl(), request.getEncoded());
  114. if (!response.isOK()) {
  115. throw new IOException("Requesting timestamp data failed");
  116. }
  117. byte[] responseBytes = response.getResponseBytes();
  118. if (responseBytes.length == 0) {
  119. throw new RuntimeException("Content-Length is zero");
  120. }
  121. // TSP response parsing and validation
  122. TimeStampResponse timeStampResponse = new TimeStampResponse(responseBytes);
  123. timeStampResponse.validate(request);
  124. if (0 != timeStampResponse.getStatus()) {
  125. LOG.atDebug().log("status: {}", box(timeStampResponse.getStatus()));
  126. LOG.atDebug().log("status string: {}", timeStampResponse.getStatusString());
  127. PKIFailureInfo failInfo = timeStampResponse.getFailInfo();
  128. if (null != failInfo) {
  129. LOG.atDebug().log("fail info int value: {}", box(failInfo.intValue()));
  130. if (/*PKIFailureInfo.unacceptedPolicy*/(1 << 8) == failInfo.intValue()) {
  131. LOG.atDebug().log("unaccepted policy");
  132. }
  133. }
  134. throw new RuntimeException("timestamp response status != 0: "
  135. + timeStampResponse.getStatus());
  136. }
  137. TimeStampToken timeStampToken = timeStampResponse.getTimeStampToken();
  138. SignerId signerId = timeStampToken.getSID();
  139. BigInteger signerCertSerialNumber = signerId.getSerialNumber();
  140. X500Name signerCertIssuer = signerId.getIssuer();
  141. LOG.atDebug().log("signer cert serial number: {}", signerCertSerialNumber);
  142. LOG.atDebug().log("signer cert issuer: {}", signerCertIssuer);
  143. // TSP signer certificates retrieval
  144. Map<String, X509CertificateHolder> certificateMap =
  145. timeStampToken.getCertificates().getMatches(null).stream()
  146. .collect(Collectors.toMap(h -> h.getSubject().toString(), Function.identity()));
  147. // TSP signer cert path building
  148. X509CertificateHolder signerCert = certificateMap.values().stream()
  149. .filter(h -> signerCertIssuer.equals(h.getIssuer())
  150. && signerCertSerialNumber.equals(h.getSerialNumber()))
  151. .findFirst()
  152. .orElseThrow(() -> new RuntimeException("TSP response token has no signer certificate"));
  153. JcaX509CertificateConverter x509converter = new JcaX509CertificateConverter();
  154. x509converter.setProvider("BC");
  155. // complete certificate chain
  156. X509Certificate child = x509converter.getCertificate(signerCert);
  157. do {
  158. revocationData.addCertificate(child);
  159. X500Principal issuer = child.getIssuerX500Principal();
  160. if (child.getSubjectX500Principal().equals(issuer)) {
  161. break;
  162. }
  163. X509CertificateHolder parentHolder = certificateMap.get(issuer.getName());
  164. child = (parentHolder != null)
  165. ? x509converter.getCertificate(parentHolder)
  166. : signatureConfig.getCachedCertificateByPrinicipal(issuer.getName());
  167. if (child != null) {
  168. retrieveCRL(signatureConfig, child).forEach(revocationData::addCRL);
  169. }
  170. } while (child != null);
  171. // verify TSP signer signature
  172. BcRSASignerInfoVerifierBuilder verifierBuilder = new BcRSASignerInfoVerifierBuilder(
  173. new DefaultCMSSignatureAlgorithmNameGenerator(),
  174. new DefaultSignatureAlgorithmIdentifierFinder(),
  175. new DefaultDigestAlgorithmIdentifierFinder(),
  176. new BcDigestCalculatorProvider());
  177. SignerInformationVerifier verifier = verifierBuilder.build(signerCert);
  178. timeStampToken.validate(verifier);
  179. // verify TSP signer certificate
  180. if (signatureConfig.getTspValidator() != null) {
  181. signatureConfig.getTspValidator().validate(revocationData.getX509chain(), revocationData);
  182. }
  183. LOG.atDebug().log("time-stamp token time: {}", timeStampToken.getTimeStampInfo().getGenTime());
  184. return timeStampToken.getEncoded();
  185. }
  186. /**
  187. * Check if CRL is to be added, check cached CRLs in config and download if necessary.
  188. * Can be overriden to suppress the logic
  189. * @return empty list, if not found or suppressed, otherwise the list of CRLs as encoded bytes
  190. */
  191. protected List<byte[]> retrieveCRL(SignatureConfig signatureConfig, X509Certificate holder) throws IOException {
  192. // TODO: add config, if crls should be added
  193. final List<CRLEntry> crlEntries = signatureConfig.getCrlEntries();
  194. byte[] crlPoints = holder.getExtensionValue(Extension.cRLDistributionPoints.getId());
  195. if (crlPoints == null) {
  196. return Collections.emptyList();
  197. }
  198. // TODO: check if parse is necessary, or if crlExt.getExtnValue() can be use directly
  199. ASN1Primitive extVal = JcaX509ExtensionUtils.parseExtensionValue(crlPoints);
  200. return Stream.of(CRLDistPoint.getInstance(extVal).getDistributionPoints())
  201. .map(DistributionPoint::getDistributionPoint)
  202. .filter(Objects::nonNull)
  203. .filter(dpn -> dpn.getType() == DistributionPointName.FULL_NAME)
  204. .flatMap(dpn -> Stream.of(GeneralNames.getInstance(dpn.getName()).getNames()))
  205. .filter(genName -> genName.getTagNo() == GeneralName.uniformResourceIdentifier)
  206. .map(genName -> ASN1IA5String.getInstance(genName.getName()).getString())
  207. .flatMap(url -> {
  208. List<CRLEntry> ul = crlEntries.stream().filter(ce -> matchCRLbyUrl(ce, holder, url)).collect(Collectors.toList());
  209. Stream<CRLEntry> cl = crlEntries.stream().filter(ce -> matchCRLbyCN(ce, holder, url));
  210. if (ul.isEmpty()) {
  211. CRLEntry ce = downloadCRL(signatureConfig, url);
  212. if (ce != null) {
  213. ul.add(ce);
  214. }
  215. }
  216. return Stream.concat(ul.stream(), cl).map(CRLEntry::getCrlBytes);
  217. })
  218. .filter(Objects::nonNull)
  219. .collect(Collectors.toList());
  220. }
  221. protected boolean matchCRLbyUrl(CRLEntry other, X509Certificate holder, String url) {
  222. return url.equals(other.getCrlURL());
  223. }
  224. protected boolean matchCRLbyCN(CRLEntry other, X509Certificate holder, String url) {
  225. return holder.getSubjectX500Principal().getName().equals(other.getCertCN());
  226. }
  227. /**
  228. * Convenience method to download a crl in an unsafe way, i.e. without verifying the
  229. * https certificates.
  230. * Please provide your own method, if you have imported the TSP server CA certificates
  231. * in your local keystore
  232. *
  233. * @return the bytes of the CRL or null if unsuccessful / download is suppressed
  234. */
  235. protected CRLEntry downloadCRL(SignatureConfig signatureConfig, String url) {
  236. if (!signatureConfig.isAllowCRLDownload()) {
  237. return null;
  238. }
  239. TimeStampHttpClient httpClient = signatureConfig.getTspHttpClient();
  240. httpClient.init(signatureConfig);
  241. httpClient.setBasicAuthentication(null, null);
  242. TimeStampHttpClientResponse response;
  243. try {
  244. response = httpClient.get(url);
  245. if (!response.isOK()) {
  246. return null;
  247. }
  248. } catch (IOException e) {
  249. return null;
  250. }
  251. try {
  252. CertificateFactory certFact = CertificateFactory.getInstance("X.509");
  253. byte[] crlBytes = response.getResponseBytes();
  254. // verify the downloaded bytes, throws Exception if invalid
  255. X509CRL crl = (X509CRL)certFact.generateCRL(new ByteArrayInputStream(crlBytes));
  256. return signatureConfig.addCRL(url, crl.getIssuerX500Principal().getName(), crlBytes);
  257. } catch (GeneralSecurityException e) {
  258. LOG.atWarn().withThrowable(e).log("CRL download failed from {}", url);
  259. return null;
  260. }
  261. }
  262. }