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 }