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 }