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/TSPServer.java 12    12.02.25 17:58 Dbratko $
029//
030
031package demo.cms.tsp;
032
033import iaik.asn1.DerCoder;
034import iaik.asn1.ObjectID;
035import iaik.asn1.structures.AlgorithmID;
036import iaik.tsp.PKIFailureInfo;
037import iaik.tsp.PKIFreeText;
038import iaik.tsp.PKIStatus;
039import iaik.tsp.PKIStatusInfo;
040import iaik.tsp.TSTInfo;
041import iaik.tsp.TimeStampReq;
042import iaik.tsp.TimeStampResp;
043import iaik.tsp.TimeStampToken;
044import iaik.tsp.TspSigningException;
045import iaik.utils.LineInputStream;
046import iaik.x509.X509Certificate;
047
048import java.io.BufferedInputStream;
049import java.io.BufferedOutputStream;
050import java.io.BufferedReader;
051import java.io.CharArrayReader;
052import java.io.CharArrayWriter;
053import java.io.DataOutputStream;
054import java.io.IOException;
055import java.io.InputStream;
056import java.io.OutputStream;
057import java.io.PrintWriter;
058import java.math.BigInteger;
059import java.net.ServerSocket;
060import java.net.Socket;
061import java.net.SocketException;
062import java.security.MessageDigest;
063import java.security.NoSuchAlgorithmException;
064import java.security.PrivateKey;
065import java.util.Date;
066import java.util.StringTokenizer;
067
068import demo.keystore.CMSKeyStore;
069
070/**
071 * A simple TSP server. Used by the {@link TimeStampDemo TimeStampDemo}.
072 *
073 * @see TimeStampDemo
074 *
075 * @version File Revision <!-- $$Revision: --> 12 <!-- $ -->
076 */
077public class TSPServer {
078  
079  /**
080   * Debug mode enabled?
081   */
082  private static PrintWriter debugWriter_;
083  
084  /**
085   * Carriage Return Line Feed.
086   */
087  private static final String CRLF = "\r\n";
088  
089  /**
090   * Default Port number.
091   */ 
092  private final static int DEFAULT_PORT = 3188;
093  
094  /**
095   * TSA policy id.
096   */                                                    
097  public static final ObjectID TSA_POLICY_ID = new ObjectID("1.3.6.1.4.1.2706.2.2.5.2.1.1.1", "IAIK-CMS Demo TSA");
098  
099  /**
100   * The private signing key of the tsp server.
101   */
102  private PrivateKey privateKey_;
103  
104  /**
105   * The certificate chain of the tsp server.
106   */
107  private X509Certificate[] certChain_;
108  
109  /**
110   * Algorithm to be used for signing the response.
111   */ 
112  private AlgorithmID signatureAlgorithm_;
113  
114  /**
115   * Algorithm to be used for calculating the signature hash.
116   */ 
117  private AlgorithmID hashAlgorithm_;
118  
119  /**
120   * Server socket.
121   */
122  private ServerSocket serverSocket_;
123  
124  /**
125   * Port number.
126   */
127  private int port_;
128  
129  /**
130   * Creates a TSP server for listening on time stamp
131   * requests on port 3188.
132   * Server key (for response signing) and certificate are
133   * read from the IAIK-CMS demo test keystore ("cms.keystore")
134   * which can be created by running the {@link demo.keystore.SetupCMSKeyStore
135   * SetupCMSKeyStore} program.
136   *
137   */
138  public TSPServer() {
139    this(DEFAULT_PORT, 
140         CMSKeyStore.getTspServerPrivateKey(),
141         CMSKeyStore.getTspServerCertificate(),
142         (AlgorithmID)AlgorithmID.sha256WithRSAEncryption.clone(),
143         (AlgorithmID)AlgorithmID.sha256.clone());
144    
145    
146    
147  }
148  
149  /**
150   * Creates a TSP server.
151   *
152   * @param port the port to listen on (default 3188)
153   * @param privateKey the private key of the tsp server to be used for signing the tsp response
154   * @param certChain the certificate chain of the server (to be included in the response, if requested)
155   * @param signatureAlgorithm the algorithm used for signing the response
156   * @param hashAlgorithm algorithm to be used for calculating the signature hash
157   */ 
158  public TSPServer(int port, 
159                   PrivateKey privateKey,
160                   X509Certificate[] certChain,
161                   AlgorithmID signatureAlgorithm,
162                   AlgorithmID hashAlgorithm) {
163    
164    if (privateKey == null) {
165      throw new NullPointerException("Private key must not be null!");
166    }
167    if ((certChain == null) || (certChain.length == 0)) {
168      throw new NullPointerException("Certificate chain must not be null!");
169    }
170    if (signatureAlgorithm == null) {
171      throw new NullPointerException("Signature algorithm must not be null!");
172    }
173    if (hashAlgorithm == null) {
174      throw new NullPointerException("Hash algorithm must not be null!");
175    }
176    port_ = (port < 0) ? DEFAULT_PORT : port;
177    privateKey_ = privateKey;
178    certChain_ = certChain;
179    signatureAlgorithm_ = signatureAlgorithm;
180    hashAlgorithm_ = hashAlgorithm;
181  }
182
183  /**
184   * Starts the TSP Server.
185   */
186  public void start() {
187
188    if (serverSocket_ == null) { 
189      try {
190        serverSocket_ = new ServerSocket(port_);
191      } catch( IOException e ) {
192        System.err.println("Error binding to port " + port_ + ":");
193        e.printStackTrace();
194        return;
195      }
196    }  
197    debug(-1, "Listening for TSP request over HTTP on port " + port_ + "...");
198    
199    long id = 0;
200
201    // a thread for each new Request
202    while (true) {
203      if (serverSocket_ != null) {
204        try {
205          Socket socket = serverSocket_.accept();
206          TSPServerThread tspServerThread = new TSPServerThread(socket, 
207                                                                privateKey_,
208                                                                certChain_,
209                                                                (AlgorithmID)signatureAlgorithm_.clone(),
210                                                                hashAlgorithm_,
211                                                                ++id); 
212          tspServerThread.start();
213        } catch( SocketException e ) {
214          // ignore 
215        } catch( IOException e ) {
216          debug(-1, e);
217        }
218      } else {
219        break;
220      }
221    }
222
223  }
224  
225  /**
226   * Stops the TSP Server.
227   */
228  public void stop() {
229    if (serverSocket_ != null) {
230      ServerSocket serverSocket = serverSocket_;
231      serverSocket_ = null;
232      try {
233        serverSocket.close();
234      } catch (Exception ex) {
235        // ignore
236      }
237    }  
238    serverSocket_ = null;
239  }
240  
241  /**
242   * Gets the port number the server is listening on.
243   */
244  public int getPort() {
245    return port_;
246  }
247  
248  /**
249   * Handles one client request.
250   */
251  final static class TSPServerThread extends Thread {
252    
253    /**
254     * The socket for talking with the client.
255     */
256    private Socket socket_;
257    /**
258     * The private signing key of the tsp server.
259     */
260    private PrivateKey privateKey_;
261    
262    /**
263     * The certificate chain of the tsp server.
264     */
265    private X509Certificate[] certChain_;
266    
267    /**
268     * Algorithm to be used for signing the response.
269     */ 
270    private AlgorithmID signatureAlgorithm_;
271    
272    /**
273     * Algorithm to be used for calculating the signature hash.
274     */ 
275    private AlgorithmID hashAlgorithm_;
276    
277    /**
278     * The id number of the thread.
279     */
280    private long id_;
281    
282    
283    /**
284     * Creates an TSP server thread for handling an TSPRequest.
285     *
286     * @param socket the socket from which to read the request and to which
287     *                          to send the response
288     * @param privateKey the private key of the tsp server to be used for signing the tsp response
289     * @param certChain the certificate chain of the server (to be included in the response, if requested)
290     * @param signatureAlgorithm the algorithm used for signing the response
291     * @param hashAlgorithm the algorithm used for calculating the signature hash
292     * @param id the id number of the thread
293     */
294    public TSPServerThread(Socket socket, 
295                           PrivateKey privateKey,
296                           X509Certificate[] certChain,
297                           AlgorithmID signatureAlgorithm,
298                           AlgorithmID hashAlgorithm,
299                           long id) {
300          
301       super("TSPServerThread" + id);
302       this.socket_ = socket;
303       privateKey_ = privateKey;
304       certChain_ = certChain;
305       signatureAlgorithm_ = signatureAlgorithm;
306       hashAlgorithm_ = hashAlgorithm;
307       id_ = id;
308    }
309
310    /** 
311     * Handles the client request.
312     */
313    public void run() {
314      try {
315        socket_.setSoTimeout(1000*30);          
316       
317        OutputStream os = socket_.getOutputStream();
318        InputStream is = socket_.getInputStream();
319        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(os));
320        LineInputStream in = new LineInputStream(new BufferedInputStream(is));
321        
322        String line;
323        line = in.readLine();
324        StringTokenizer token = new StringTokenizer(line, " ");
325        String method = token.nextToken();
326        debug(id_, "Received request from " + socket_.getInetAddress() + ":");
327        debug(id_, line);
328        boolean invalidRequest = false;
329        // print header lines
330        do {
331          line = in.readLine();
332          debug(id_, line);
333          line = line.toLowerCase();
334          if (line.startsWith("content-type") &&
335             (line.indexOf("application/timestamp-query") == -1)) {
336            invalidRequest = true;
337          }    
338        } while( (line != null) && (line.length() != 0) );
339
340        // we only accept POST
341        if (!method.equalsIgnoreCase("POST")) {
342          debug(id_, "\nInvalid method: " + method + ". Only POST supported. Sending ERROR");
343          out.writeBytes("HTTP/1.0 405 Method Not Allowed");
344          out.writeBytes(CRLF);
345          out.writeBytes("Content-Type: text/html");
346          out.writeBytes(CRLF);
347          out.writeBytes("Server: IAIK TSP Demoserver");
348          out.writeBytes(CRLF);
349          out.writeBytes(CRLF);
350          out.writeBytes("<HTML>");
351          out.writeBytes(CRLF);
352          out.writeBytes("<HEAD><TITLE>IAIK-CMS TSP Demo Server</TITLE></HEAD>");
353          out.writeBytes(CRLF);
354          out.writeBytes("<BODY>");
355          out.writeBytes(CRLF);
356          out.writeBytes("<H1>405 Method Not Allowed.</H1>");
357          out.writeBytes(CRLF);
358          out.writeBytes("<P>Method " + method + " not supported.");
359          out.writeBytes(CRLF);
360          out.writeBytes("<HR>Generated by <A HREF=\"https://sic.tech/\">IAIK-CMS</A>.");
361          out.writeBytes("</BODY>");
362          out.writeBytes(CRLF);
363          out.writeBytes("</HTML>");
364          out.writeBytes(CRLF);
365          out.flush();
366          out.close();
367          return;
368        }
369        if (invalidRequest) {
370          debug(id_, "Invalid request content type. Sending ERROR.");
371          out.writeBytes("HTTP/1.0 400 Invalid request");
372          out.writeBytes(CRLF);
373          out.writeBytes("Content-Type: text/html");
374          out.writeBytes(CRLF);
375          out.writeBytes("Server: IAIK TSP Demoserver");
376          out.writeBytes(CRLF);
377          out.writeBytes(CRLF);
378          out.writeBytes("<HTML>");
379          out.writeBytes(CRLF);
380          out.writeBytes("<HEAD><TITLE>IAIK-CMS TSP Demo Server</TITLE></HEAD>");
381          out.writeBytes(CRLF);
382          out.writeBytes("<BODY>");
383          out.writeBytes(CRLF);
384          out.writeBytes("<H1>400 Invalid Request.</H1>");
385          out.writeBytes(CRLF);
386          out.writeBytes("<P>Invalid Request. Expected <code>application/timestamp-query</code>");
387          out.writeBytes(CRLF);
388          out.writeBytes("<HR>Generated by <A HREF=\"https://sic.tech/\">IAIK-CMS</A>.");
389          out.writeBytes("</BODY>");
390          out.writeBytes(CRLF);
391          out.writeBytes("</HTML>");
392          out.writeBytes(CRLF);
393          out.flush();
394          out.close();
395          return;
396        }  
397        // parse the request received
398        byte[] response = null;
399        debug(id_, "Parse request...");
400        TimeStampResp tspResponse = createResponse(in, 
401                                                   privateKey_, 
402                                                   certChain_,
403                                                   signatureAlgorithm_, 
404                                                   hashAlgorithm_,
405                                                   id_); 
406        response = tspResponse.getEncoded();
407        // now create and send the response
408        debug(id_, "Sending response...");
409        out.writeBytes("HTTP/1.0 200 OK");
410        out.writeBytes(CRLF);
411        out.writeBytes("Content-Type: application/timestamp-reply");
412        out.writeBytes(CRLF);
413        out.writeBytes("Server: IAIK TSP Demoserver");
414        out.writeBytes(CRLF);
415        out.writeBytes("Content-Length: " + response.length);
416        out.writeBytes(CRLF);
417        out.writeBytes(CRLF);
418        out.write(response);
419        out.flush();
420        out.close();
421      } catch (IOException ex) {
422        debug(id_, ex);
423      } finally {
424        try {
425          socket_.close();
426        } catch (IOException e) {
427          // ignore
428        }    
429      }        
430    }
431  }
432  
433  /**
434   * Creates a TimeStampResponse from the given data
435   * 
436   * @param in the input stream from which to read the time stamp request
437   * @param privateKey the private signing key of the tsp server
438   * @param certChain the certificate chain of the tsp server
439   * @param signatureAlgorithm the algorithm to be used for signing
440   * @param hashAlgorithm the algorithm to be used for signature hashing
441   * @param id an id counter (used for serial number creation)
442   * 
443   * @return the time stamp response
444   */
445  private final static TimeStampResp createResponse(InputStream in,
446                                                    PrivateKey privateKey,
447                                                    X509Certificate[] certChain,
448                                                    AlgorithmID signatureAlgorithm,
449                                                    AlgorithmID hashAlgorithm,
450                                                    long id) {
451    
452    TimeStampResp tspResponse = null;
453    //  parse request
454    TimeStampReq tspRequest = null;
455    try {
456      tspRequest = new TimeStampReq(DerCoder.decode(in));
457      // specific TSA policy requested?
458      ObjectID reqPolicy = tspRequest.getTSAPolicyID();
459      if  ((reqPolicy != null) && (reqPolicy.equals(TSA_POLICY_ID) == false)) {
460        String msg = "Requested policy " + reqPolicy.getID() + " not accepted.";
461        PKIStatusInfo statusInfo = new PKIStatusInfo(new PKIStatus(PKIStatus.REJECTION));
462        statusInfo.setPKIFailureInfo(new PKIFailureInfo(PKIFailureInfo.UNACCEPTED_POLICY));
463        statusInfo.setPKIFreeText(new PKIFreeText(msg));
464        tspResponse = new TimeStampResp(statusInfo);
465      }
466      
467    } catch (Exception ex) {
468      String msg = "Invalid time stamp request. ";
469      debug(id, msg + ex.toString());
470      debug(id, ex);
471      PKIStatusInfo statusInfo = new PKIStatusInfo(new PKIStatus(PKIStatus.REJECTION));
472      statusInfo.setPKIFailureInfo(new PKIFailureInfo(PKIFailureInfo.BAD_DATA_FORMAT));
473      statusInfo.setPKIFreeText(new PKIFreeText(msg));
474      tspResponse = new TimeStampResp(statusInfo);
475    }
476
477    if (tspResponse == null) {
478      // request successfully parsed
479
480      //create TSTInfo
481      TSTInfo tstInfo = new TSTInfo();
482      tstInfo.setGenTime(new Date());
483      tstInfo.setMessageImprint(tspRequest.getMessageImprint());
484      if (tspRequest.getNonce() != null) {
485        tstInfo.setNonce(tspRequest.getNonce());
486      }
487      
488      // for simplicity we calculate the serial number from current date
489      // and a serial number counter. The value of a counter (id) is not kept
490      // beyound the lifetime of the server (as it might have be done practice).
491      long serialNumber = id + System.currentTimeMillis();
492      tstInfo.setSerialNumber(BigInteger.valueOf(serialNumber));
493      tstInfo.setTSAPolicyID(TSA_POLICY_ID);
494  
495      //create TimeStampToken
496      TimeStampToken token = new TimeStampToken(tstInfo);
497      if (tspRequest.getCertReq()) {
498        token.setCertificates(certChain);
499      }
500      token.setSigningCertificate(certChain[0]);
501  
502      token.setHashAlgorithm(hashAlgorithm);
503      token.setPrivateKey(privateKey);
504  
505      try {
506        token.signTimeStampToken(null, signatureAlgorithm);
507      } catch (TspSigningException ex) {
508        String msg = "Error signing time stamp response. ";
509        debug(id ,msg);
510        debug(id , ex);
511        PKIStatusInfo statusInfo = new PKIStatusInfo(new PKIStatus(PKIStatus.REJECTION));
512        statusInfo.setPKIFailureInfo(new PKIFailureInfo(PKIFailureInfo.SYSTEM_FAILURE));
513        tspResponse = new TimeStampResp(statusInfo);
514      }
515      
516      if (tspResponse == null) {
517        // successful response
518        tspResponse = new TimeStampResp();
519        tspResponse.setTimeStampToken(token);
520  
521        PKIStatus status = new PKIStatus(PKIStatus.GRANTED);
522        PKIStatusInfo info = new PKIStatusInfo(status);
523  
524        tspResponse.setPKIStatusInfo(info);
525      }  
526    }
527    
528    return tspResponse;
529  }
530  
531  /** 
532   * Writes debug information to the debug stream.
533   *
534   * @param id an id to be printed in front of the message
535   * @param msg the message to be printed
536   */
537  private final static void debug(long id, String msg) {
538    if (debugWriter_ != null) {
539      if (id < 0) {
540        debugWriter_.println("tsp_debug: " + msg);
541      } else {
542        debugWriter_.println("tsp_debug(" + id + "): " + msg);
543      }  
544    }
545  }
546
547  /** 
548   * Debugs an exception.
549   *
550   * @param id an id to be printed in front of the exception messages
551   * @param e the exception to be debugged
552   */
553  private final static void debug(long id, Throwable e) {
554    CharArrayWriter writer = new CharArrayWriter();
555    PrintWriter pwriter = new PrintWriter(writer);
556    e.printStackTrace(pwriter);
557    pwriter.flush();
558    char[] chars = writer.toCharArray();
559    BufferedReader reader = new BufferedReader(new CharArrayReader(chars));
560    try {
561      while (true) {
562        String line = reader.readLine();
563        if (line == null) {
564          break;
565        }
566        debug(id, line);
567      }
568    } catch (IOException ex) {
569      // ignore
570    }
571  }
572
573  
574
575  /**
576   * Sets a stream to which debugging information shall be printed.
577   * 
578   * @param os the stream to which to print debugging information
579   *           or <code>null</code> if no debugging information shall
580   *           be printed 
581   */
582  public static synchronized void setDebugStream(OutputStream os) {
583    debugWriter_ = (os == null) ? null : new PrintWriter(os, true);
584  }
585
586  
587  
588}