001 // Copyright (C) 2002 IAIK
002 // https://jce.iaik.tugraz.at
003 //
004 // Copyright (C) 2003 - 2025 Stiftung Secure Information and
005 // Communication Technologies SIC
006 // https://sic.tech
007 //
008 // All rights reserved.
009 //
010 // Redistribution and use in source and binary forms, with or without
011 // modification, are permitted provided that the following conditions
012 // are met:
013 // 1. Redistributions of source code must retain the above copyright
014 // notice, this list of conditions and the following disclaimer.
015 // 2. Redistributions in binary form must reproduce the above copyright
016 // notice, this list of conditions and the following disclaimer in the
017 // documentation and/or other materials provided with the distribution.
018 //
019 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
020 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
021 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
023 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
024 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
025 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
027 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
028 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
029 // SUCH DAMAGE.
030
031 // Copyright (C) 2002 IAIK
032 // https://sic.tech/
033 //
034 // Copyright (C) 2003 - 2025 Stiftung Secure Information and
035 // Communication Technologies SIC
036 // https://sic.tech/
037 //
038 // All rights reserved.
039 //
040 // This source is provided for inspection purposes and recompilation only,
041 // unless specified differently in a contract with IAIK. This source has to
042 // be kept in strict confidence and must not be disclosed to any third party
043 // under any circumstances. Redistribution in source and binary forms, with
044 // or without modification, are <not> permitted in any case!
045 //
046 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
047 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
048 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
049 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
050 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
051 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
052 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
053 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
054 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
055 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
056 // SUCH DAMAGE.
057 //
058 // $Header: /IAIK-CMS/current/src/demo/cms/tsp/TSPDemoUtils.java 9 12.02.25 17:58 Dbratko $
059 // $Revision: 9 $
060 //
061
062 package demo.cms.tsp;
063
064 import iaik.asn1.CodingException;
065 import iaik.asn1.ObjectID;
066 import iaik.asn1.structures.AlgorithmID;
067 import iaik.asn1.structures.Attribute;
068 import iaik.cms.CMSException;
069 import iaik.cms.SignerInfo;
070 import iaik.smime.attributes.SignatureTimeStampToken;
071 import iaik.tsp.MessageImprint;
072 import iaik.tsp.PKIFailureInfo;
073 import iaik.tsp.PKIStatus;
074 import iaik.tsp.PKIStatusInfo;
075 import iaik.tsp.TSTInfo;
076 import iaik.tsp.TimeStampReq;
077 import iaik.tsp.TimeStampResp;
078 import iaik.tsp.TimeStampToken;
079 import iaik.tsp.TspException;
080 import iaik.tsp.transport.http.TspHttpClient;
081 import iaik.tsp.transport.http.TspHttpResponse;
082 import iaik.utils.CryptoUtils;
083 import iaik.x509.X509Certificate;
084 import iaik.x509.X509ExtensionInitException;
085 import iaik.x509.extensions.ExtendedKeyUsage;
086
087 import java.io.IOException;
088 import java.math.BigInteger;
089 import java.net.URL;
090 import java.security.MessageDigest;
091 import java.security.NoSuchAlgorithmException;
092 import java.security.cert.CertificateException;
093 import java.util.Random;
094
095 /**
096 * Some utils for creating and sending time stamp requests,
097 * validating responses and adding time stamp token attributes.
098 * Used by the TSP demo.
099 *
100 *
101 * @see TimeStampDemo
102 * @see TimeStampListener
103 */
104 public class TSPDemoUtils {
105
106 /**
107 * Creates a TimeStampRequest for the given SignerInfo.
108 *
109 * @param signerInfo the SignerInfo to be time stamped
110 * @param reqPolicy the policy of the TSA from which to get a response,
111 * maybe <code>null</code> if we accept any TSA
112 *
113 * @return the time stamp request just created
114 *
115 * @throws TspException if some error occurs during time stamp creation
116 */
117 public static TimeStampReq createRequest(SignerInfo signerInfo,
118 ObjectID reqPolicy)
119
120 throws TspException {
121
122 // we have to time stamp the signature value
123 byte[] signatureValue = signerInfo.getSignatureValue();
124 // calculate the MessageImprint
125 AlgorithmID hashAlg = (AlgorithmID)AlgorithmID.sha256.clone();
126 MessageDigest md = null;
127 try {
128 md = hashAlg.getMessageDigestInstance();
129 } catch (NoSuchAlgorithmException ex) {
130 throw new TspException("Cannot calculate MessageImprint! Algorithm SHA-256 not supported!");
131 }
132 byte[] toBeTimeStamped = md.digest(signatureValue);
133 MessageImprint imprint = new MessageImprint(hashAlg, toBeTimeStamped);
134 //Create a new TimeStampReq
135 TimeStampReq request = new TimeStampReq();
136 //set the imprint
137 request.setMessageImprint(imprint);
138 //request the TSA to include its certificate chain into the response
139 request.setCertReq(true);
140 if (reqPolicy != null) {
141 // request some particular TSA policy?
142 request.setTSAPolicyID(reqPolicy);
143 }
144 // set a Nonce
145 BigInteger nonce = new BigInteger(64, new Random());
146 request.setNonce(nonce);
147 return request;
148 }
149
150 /**
151 * Sends the given time stamp request to the given TSA.
152 *
153 * @param request the time stamp request to be sent to the TSA
154 * @param tsaUrl the URL of the time stamp authority from which to
155 * get the time stamp
156 *
157 * @throws TspException if an error occurs during sending the request to the TSA (e.g.
158 * connecting to the TSA fails, ...)
159 * @throws NullPointerException if <code>request</code> or <code>tsaUrl</code>
160 * are <code>null</code>
161 */
162 public static TimeStampResp sendRequest(TimeStampReq request,
163 String tsaUrl)
164
165 throws TspException {
166
167 if (request == null) {
168 throw new NullPointerException("Time stamp request must not be null!");
169 }
170 if (tsaUrl == null) {
171 throw new NullPointerException("TSA url must not be null!");
172 }
173
174 try {
175 // send the request to the TSA
176 TspHttpClient tspHttpClient = new TspHttpClient(new URL(tsaUrl));
177 TspHttpResponse tspHttpResponse = tspHttpClient.sendRequest(request);
178 if (tspHttpResponse.isErrorResponse()) {
179 throw new TspException("Error connecting to TSA: " + tspHttpResponse.getErrorMsg());
180 }
181 TimeStampResp response = tspHttpResponse.getTimeStampResp();
182 return response;
183 } catch (IOException ex) {
184 throw new TspException("Error connecting to TSA: " + ex.toString());
185 } catch (CodingException ex) {
186 throw new TspException("Error encoding tsp request: " + ex.getMessage());
187 }
188 }
189
190 /**
191 * Adds a SignatureTimeStampToken attribute to the given SignerInfo.
192 *
193 * @param tspToken the time stamp token to be added as attribute
194 * @param signerInfo the SignerInfo to be time stamped
195 *
196 * @throws TspException if some error occurs when adding the attribute
197 */
198 public static void timeStamp(TimeStampToken tspToken,
199 SignerInfo signerInfo)
200
201 throws TspException {
202
203 if (tspToken == null) {
204 throw new NullPointerException("tspToken must not be null!");
205 }
206 if (signerInfo == null) {
207 throw new NullPointerException("signerInfo must not be null!");
208 }
209
210 try {
211 // include TimeStampToken as unsigned attribute
212 SignatureTimeStampToken stst = new SignatureTimeStampToken(tspToken.toASN1Object());
213 signerInfo.addUnSignedAttribute(new Attribute(stst));
214 } catch (CodingException ex) {
215 throw new TspException("Error encoding TimeStampToken attribute: " + ex.getMessage());
216 } catch (CMSException ex) {
217 throw new TspException("Error adding SignatureTimeStampToken attribute: " + ex.getMessage());
218 }
219
220 }
221
222 /**
223 * Validates the response received from the TSA.
224 *
225 * @param response the time stamp response to be validated
226 * @param request the time stamp request that has been sent
227 *
228 * @throws TspException if the response is invalid (wrong MessageImprint, missing certificate,...)
229 */
230 public static void validateResponse(TimeStampResp response,
231 TimeStampReq request)
232
233 throws TspException {
234
235 // get the status info
236 PKIStatusInfo statusInfo = response.getPKIStatusInfo();
237 // status?
238 PKIStatus status = statusInfo.getPKIStatus();
239 int statusCode = status.getStatus();
240 if ((statusCode != PKIStatus.GRANTED) && (statusCode != PKIStatus.GRANTED_WITH_MODS)) {
241 PKIFailureInfo failureInfo = statusInfo.getPKIFailureInfo();
242 throw new TspException("TSA reported failure:\n" + status + ((failureInfo == null) ? "" : ("\n("+failureInfo+")")));
243 }
244 // we got a TimeStampToken
245 TimeStampToken token = response.getTimeStampToken();
246 if (token == null) {
247 throw new TspException("Got invalid response from TSA: TimeStampToken is missing");
248 }
249 // verify the signature of the token
250 X509Certificate tsaCert = (X509Certificate)token.getSigningCertificate();
251 if (tsaCert == null) {
252 throw new TspException("Invalid response: does not contain the requested TSA certificate!");
253 }
254 token.verifyTimeStampToken(tsaCert);
255 try {
256 if (token.isSigningCertificate(tsaCert) == false) {
257 throw new TspException("Certificate identified by SigningCertificate is not TSA cert!");
258 }
259 } catch (CertificateException e) {
260 throw new TspException("Error checking SigningCertificate attribute: " + e.toString());
261 }
262
263 // here we should validate the TSA certificate (omitted in this demo)
264
265 // get the TSTInfo
266 TSTInfo tstInfo = token.getTSTInfo();
267 // validate the MessageImprint
268 MessageImprint mi = tstInfo.getMessageImprint();
269 if (mi.equals(request.getMessageImprint()) == false) {
270 throw new TspException("Response MessageImprint does not match to request imprint!");
271 }
272 // nonce included?
273 BigInteger requestNonce = request.getNonce();
274 if (requestNonce != null) {
275 BigInteger responseNonce = tstInfo.getNonce();
276 if (responseNonce == null) {
277 throw new TspException("Invalid Response! Does not contain nonce!");
278 }
279 if (requestNonce.equals(responseNonce) == false) {
280 throw new TspException("Response nonce does not match to request nonce!");
281 }
282 }
283 // did we request a TSA policy
284 ObjectID requestPolicy = request.getTSAPolicyID();
285 if (requestPolicy != null) {
286 if (requestPolicy.equals(tstInfo.getTSAPolicyID()) == false) {
287 throw new TspException("TSA policy not trusted!");
288 }
289 }
290 }
291
292 /**
293 * Validates an unsigned SignatureTimeStampToken contained in the given
294 * SignerInfo.
295 *
296 * @param signerInfo the SignerInfo containing the SignatureTimeStampToken attribute
297 *
298 * @throws TspException if the time stamp token validation fails
299 * @throws NullPointerException if the SignerInfo does not contain a
300 * SignatureTimeStampToken as expected
301 *
302 */
303 public static void validateSignatureTimeStampToken(SignerInfo signerInfo)
304 throws TspException {
305
306 SignatureTimeStampToken signatureTimeStampToken;
307 TimeStampToken token;
308 try {
309 signatureTimeStampToken =
310 (SignatureTimeStampToken)signerInfo.getUnsignedAttributeValue(SignatureTimeStampToken.oid);
311 if (signatureTimeStampToken == null) {
312 throw new NullPointerException("Missing SignatureTimeStampToken in SignerInfo!");
313 }
314 token = new TimeStampToken(signatureTimeStampToken.toASN1Object());
315 } catch (CMSException ex) {
316 throw new TspException("Error parsing time stamp token: " + ex.toString());
317 } catch (CodingException ex) {
318 throw new TspException("Error parsing time stamp token: " + ex.toString());
319 }
320 // verify the signature of the token (we assume that the TSA certificate is included)
321 X509Certificate tsaCert = (X509Certificate)token.getSigningCertificate();
322 if (tsaCert == null) {
323 throw new TspException("Cannot verify TimeStampToken: TSA certificate not included!");
324 }
325 token.verifyTimeStampToken(tsaCert);
326
327 // here we should validate the TSA certificate (omitted in this demo)
328
329 // get the TSTInfo
330 TSTInfo tstInfo = token.getTSTInfo();
331
332 // we may check the MessageImprint to see if actually the signature value has been time stamped
333 MessageImprint imprint = tstInfo.getMessageImprint();
334 AlgorithmID hashAlg = imprint.getHashAlgorithm();
335 MessageDigest md = null;
336 try {
337 md = hashAlg.getMessageDigestInstance();
338 } catch (NoSuchAlgorithmException ex) {
339 throw new TspException("Cannot calculate MessageImprint! Hash Algorithm not supported: " + ex.getMessage());
340 }
341
342 // calculate a hash from the signature value
343 byte[] toBeTimeStamped = md.digest(signerInfo.getSignatureValue());
344 // and compare it against the MessageImprint value
345 if (CryptoUtils.equalsBlock(toBeTimeStamped, imprint.getHashedMessage()) == false) {
346 throw new TspException("Invalid timestamp token: wrong MessageImprint value!");
347 }
348
349 System.out.println("Signature has been time stamped from " + tsaCert.getSubjectDN() + " at: " + tstInfo.getGenTime());
350 }
351
352
353
354
355 }