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