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}