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    }