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