1/* 2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.security.testlibrary; 27 28import java.io.*; 29import java.net.*; 30import java.security.*; 31import java.security.cert.CRLReason; 32import java.security.cert.X509Certificate; 33import java.security.cert.Extension; 34import java.security.cert.CertificateException; 35import java.security.cert.CertificateEncodingException; 36import java.security.Signature; 37import java.util.*; 38import java.util.concurrent.*; 39import java.text.SimpleDateFormat; 40import java.math.BigInteger; 41 42import sun.security.x509.*; 43import sun.security.x509.PKIXExtensions; 44import sun.security.provider.certpath.ResponderId; 45import sun.security.provider.certpath.CertId; 46import sun.security.provider.certpath.OCSPResponse; 47import sun.security.provider.certpath.OCSPResponse.ResponseStatus; 48import sun.security.util.Debug; 49import sun.security.util.DerInputStream; 50import sun.security.util.DerOutputStream; 51import sun.security.util.DerValue; 52import sun.security.util.ObjectIdentifier; 53 54 55/** 56 * This is a simple OCSP server designed to listen and respond to incoming 57 * requests. 58 */ 59public class SimpleOCSPServer { 60 private final Debug debug = Debug.getInstance("oserv"); 61 private static final ObjectIdentifier OCSP_BASIC_RESPONSE_OID = 62 ObjectIdentifier.newInternal( 63 new int[] { 1, 3, 6, 1, 5, 5, 7, 48, 1, 1}); 64 private static final SimpleDateFormat utcDateFmt = 65 new SimpleDateFormat("MMM dd yyyy, HH:mm:ss z"); 66 67 static final int FREE_PORT = 0; 68 69 // CertStatus values 70 public static enum CertStatus { 71 CERT_STATUS_GOOD, 72 CERT_STATUS_REVOKED, 73 CERT_STATUS_UNKNOWN, 74 } 75 76 // Fields used for the networking portion of the responder 77 private ServerSocket servSocket; 78 private InetAddress listenAddress; 79 private int listenPort; 80 81 // Keystore information (certs, keys, etc.) 82 private KeyStore keystore; 83 private X509Certificate issuerCert; 84 private X509Certificate signerCert; 85 private PrivateKey signerKey; 86 87 // Fields used for the operational portions of the server 88 private boolean logEnabled = false; 89 private ExecutorService threadPool; 90 private volatile boolean started = false; 91 private volatile boolean serverReady = false; 92 private volatile boolean receivedShutdown = false; 93 private volatile boolean acceptConnections = true; 94 private volatile long delayMsec = 0; 95 96 // Fields used in the generation of responses 97 private long nextUpdateInterval = -1; 98 private Date nextUpdate = null; 99 private ResponderId respId; 100 private AlgorithmId sigAlgId; 101 private Map<CertId, CertStatusInfo> statusDb = 102 Collections.synchronizedMap(new HashMap<>()); 103 104 /** 105 * Construct a SimpleOCSPServer using keystore, password, and alias 106 * parameters. 107 * 108 * @param ks the keystore to be used 109 * @param password the password to access key material in the keystore 110 * @param issuerAlias the alias of the issuer certificate 111 * @param signerAlias the alias of the signer certificate and key. A 112 * value of {@code null} means that the {@code issuerAlias} will be used 113 * to look up the signer key. 114 * 115 * @throws GeneralSecurityException if there are problems accessing the 116 * keystore or finding objects within the keystore. 117 * @throws IOException if a {@code ResponderId} cannot be generated from 118 * the signer certificate. 119 */ 120 public SimpleOCSPServer(KeyStore ks, String password, String issuerAlias, 121 String signerAlias) throws GeneralSecurityException, IOException { 122 this(null, FREE_PORT, ks, password, issuerAlias, signerAlias); 123 } 124 125 /** 126 * Construct a SimpleOCSPServer using specific network parameters, 127 * keystore, password, and alias. 128 * 129 * @param addr the address to bind the server to. A value of {@code null} 130 * means the server will bind to all interfaces. 131 * @param port the port to listen on. A value of {@code 0} will mean that 132 * the server will randomly pick an open ephemeral port to bind to. 133 * @param ks the keystore to be used 134 * @param password the password to access key material in the keystore 135 * @param issuerAlias the alias of the issuer certificate 136 * @param signerAlias the alias of the signer certificate and key. A 137 * value of {@code null} means that the {@code issuerAlias} will be used 138 * to look up the signer key. 139 * 140 * @throws GeneralSecurityException if there are problems accessing the 141 * keystore or finding objects within the keystore. 142 * @throws IOException if a {@code ResponderId} cannot be generated from 143 * the signer certificate. 144 */ 145 public SimpleOCSPServer(InetAddress addr, int port, KeyStore ks, 146 String password, String issuerAlias, String signerAlias) 147 throws GeneralSecurityException, IOException { 148 Objects.requireNonNull(ks, "Null keystore provided"); 149 Objects.requireNonNull(issuerAlias, "Null issuerName provided"); 150 151 utcDateFmt.setTimeZone(TimeZone.getTimeZone("GMT")); 152 153 keystore = ks; 154 issuerCert = (X509Certificate)ks.getCertificate(issuerAlias); 155 if (issuerCert == null) { 156 throw new IllegalArgumentException("Certificate for alias " + 157 issuerAlias + " not found"); 158 } 159 160 if (signerAlias != null) { 161 signerCert = (X509Certificate)ks.getCertificate(signerAlias); 162 if (signerCert == null) { 163 throw new IllegalArgumentException("Certificate for alias " + 164 signerAlias + " not found"); 165 } 166 signerKey = (PrivateKey)ks.getKey(signerAlias, 167 password.toCharArray()); 168 if (signerKey == null) { 169 throw new IllegalArgumentException("PrivateKey for alias " + 170 signerAlias + " not found"); 171 } 172 } else { 173 signerCert = issuerCert; 174 signerKey = (PrivateKey)ks.getKey(issuerAlias, 175 password.toCharArray()); 176 if (signerKey == null) { 177 throw new IllegalArgumentException("PrivateKey for alias " + 178 issuerAlias + " not found"); 179 } 180 } 181 182 sigAlgId = AlgorithmId.get("Sha256withRSA"); 183 respId = new ResponderId(signerCert.getSubjectX500Principal()); 184 listenAddress = addr; 185 listenPort = port; 186 } 187 188 /** 189 * Start the server. The server will bind to the specified network 190 * address and begin listening for incoming connections. 191 * 192 * @throws IOException if any number of things go wonky. 193 */ 194 public synchronized void start() throws IOException { 195 // You cannot start the server twice. 196 if (started) { 197 log("Server has already been started"); 198 return; 199 } else { 200 started = true; 201 } 202 203 // Create and start the thread pool 204 threadPool = Executors.newFixedThreadPool(32, new ThreadFactory() { 205 @Override 206 public Thread newThread(Runnable r) { 207 Thread t = Executors.defaultThreadFactory().newThread(r); 208 t.setDaemon(true); 209 return t; 210 } 211 }); 212 213 threadPool.submit(new Runnable() { 214 @Override 215 public void run() { 216 try (ServerSocket sSock = new ServerSocket()) { 217 servSocket = sSock; 218 servSocket.setReuseAddress(true); 219 servSocket.setSoTimeout(500); 220 servSocket.bind(new InetSocketAddress(listenAddress, 221 listenPort), 128); 222 log("Listening on " + servSocket.getLocalSocketAddress()); 223 224 // Singal ready 225 serverReady = true; 226 227 // Update the listenPort with the new port number. If 228 // the server is restarted, it will bind to the same 229 // port rather than picking a new one. 230 listenPort = servSocket.getLocalPort(); 231 232 // Main dispatch loop 233 while (!receivedShutdown) { 234 try { 235 Socket newConnection = servSocket.accept(); 236 if (!acceptConnections) { 237 try { 238 log("Reject connection"); 239 newConnection.close(); 240 } catch (IOException e) { 241 // ignore 242 } 243 continue; 244 } 245 threadPool.submit(new OcspHandler(newConnection)); 246 } catch (SocketTimeoutException timeout) { 247 // Nothing to do here. If receivedShutdown 248 // has changed to true then the loop will 249 // exit on its own. 250 } catch (IOException ioe) { 251 // Something bad happened, log and force a shutdown 252 log("Unexpected Exception: " + ioe); 253 stop(); 254 } 255 } 256 257 log("Shutting down..."); 258 threadPool.shutdown(); 259 } catch (IOException ioe) { 260 err(ioe); 261 } finally { 262 // Reset state variables so the server can be restarted 263 receivedShutdown = false; 264 started = false; 265 serverReady = false; 266 } 267 } 268 }); 269 } 270 271 /** 272 * Make the OCSP server reject incoming connections. 273 */ 274 public synchronized void rejectConnections() { 275 log("Reject OCSP connections"); 276 acceptConnections = false; 277 } 278 279 /** 280 * Make the OCSP server accept incoming connections. 281 */ 282 public synchronized void acceptConnections() { 283 log("Accept OCSP connections"); 284 acceptConnections = true; 285 } 286 287 288 /** 289 * Stop the OCSP server. 290 */ 291 public synchronized void stop() { 292 if (started) { 293 receivedShutdown = true; 294 log("Received shutdown notification"); 295 } 296 } 297 298 /** 299 * Print {@code SimpleOCSPServer} operating parameters. 300 * 301 * @return the {@code SimpleOCSPServer} operating parameters in 302 * {@code String} form. 303 */ 304 @Override 305 public String toString() { 306 StringBuilder sb = new StringBuilder(); 307 sb.append("OCSP Server:\n"); 308 sb.append("----------------------------------------------\n"); 309 sb.append("issuer: ").append(issuerCert.getSubjectX500Principal()). 310 append("\n"); 311 sb.append("signer: ").append(signerCert.getSubjectX500Principal()). 312 append("\n"); 313 sb.append("ResponderId: ").append(respId).append("\n"); 314 sb.append("----------------------------------------------"); 315 316 return sb.toString(); 317 } 318 319 /** 320 * Helpful debug routine to hex dump byte arrays. 321 * 322 * @param data the array of bytes to dump to stdout. 323 * 324 * @return the hexdump of the byte array 325 */ 326 private static String dumpHexBytes(byte[] data) { 327 return dumpHexBytes(data, 16, "\n", " "); 328 } 329 330 /** 331 * 332 * @param data the array of bytes to dump to stdout. 333 * @param itemsPerLine the number of bytes to display per line 334 * if the {@code lineDelim} character is blank then all bytes will be 335 * printed on a single line. 336 * @param lineDelim the delimiter between lines 337 * @param itemDelim the delimiter between bytes 338 * 339 * @return The hexdump of the byte array 340 */ 341 private static String dumpHexBytes(byte[] data, int itemsPerLine, 342 String lineDelim, String itemDelim) { 343 StringBuilder sb = new StringBuilder(); 344 if (data != null) { 345 for (int i = 0; i < data.length; i++) { 346 if (i % itemsPerLine == 0 && i != 0) { 347 sb.append(lineDelim); 348 } 349 sb.append(String.format("%02X", data[i])).append(itemDelim); 350 } 351 } 352 353 return sb.toString(); 354 } 355 356 /** 357 * Enable or disable the logging feature. 358 * 359 * @param enable {@code true} to enable logging, {@code false} to 360 * disable it. The setting must be activated before the server calls 361 * its start method. Any calls after that have no effect. 362 */ 363 public void enableLog(boolean enable) { 364 if (!started) { 365 logEnabled = enable; 366 } 367 } 368 369 /** 370 * Sets the nextUpdate interval. Intervals will be calculated relative 371 * to the server startup time. When first set, the nextUpdate date is 372 * calculated based on the current time plus the interval. After that, 373 * calls to getNextUpdate() will return this date if it is still 374 * later than current time. If not, the Date will be updated to the 375 * next interval that is later than current time. This value must be set 376 * before the server has had its start method called. Calls made after 377 * the server has been started have no effect. 378 * 379 * @param interval the recurring time interval in seconds used to 380 * calculate nextUpdate times. A value less than or equal to 0 will 381 * disable the nextUpdate feature. 382 */ 383 public synchronized void setNextUpdateInterval(long interval) { 384 if (!started) { 385 if (interval <= 0) { 386 nextUpdateInterval = -1; 387 nextUpdate = null; 388 log("nexUpdate support has been disabled"); 389 } else { 390 nextUpdateInterval = interval * 1000; 391 nextUpdate = new Date(System.currentTimeMillis() + 392 nextUpdateInterval); 393 log("nextUpdate set to " + nextUpdate); 394 } 395 } 396 } 397 398 /** 399 * Return the nextUpdate {@code Date} object for this server. If the 400 * nextUpdate date has already passed, set a new nextUpdate based on 401 * the nextUpdate interval and return that date. 402 * 403 * @return a {@code Date} object set to the nextUpdate field for OCSP 404 * responses. 405 */ 406 private synchronized Date getNextUpdate() { 407 if (nextUpdate != null && nextUpdate.before(new Date())) { 408 long nuEpochTime = nextUpdate.getTime(); 409 long currentTime = System.currentTimeMillis(); 410 411 // Keep adding nextUpdate intervals until you reach a date 412 // that is later than current time. 413 while (currentTime >= nuEpochTime) { 414 nuEpochTime += nextUpdateInterval; 415 } 416 417 // Set the nextUpdate for future threads 418 nextUpdate = new Date(nuEpochTime); 419 log("nextUpdate updated to new value: " + nextUpdate); 420 } 421 return nextUpdate; 422 } 423 424 /** 425 * Add entries into the responder's status database. 426 * 427 * @param newEntries a map of {@code CertStatusInfo} objects, keyed on 428 * their serial number (as a {@code BigInteger}). All serial numbers 429 * are assumed to have come from this responder's issuer certificate. 430 * 431 * @throws IOException if a CertId cannot be generated. 432 */ 433 public void updateStatusDb(Map<BigInteger, CertStatusInfo> newEntries) 434 throws IOException { 435 if (newEntries != null) { 436 for (BigInteger serial : newEntries.keySet()) { 437 CertStatusInfo info = newEntries.get(serial); 438 if (info != null) { 439 CertId cid = new CertId(issuerCert, 440 new SerialNumber(serial)); 441 statusDb.put(cid, info); 442 log("Added entry for serial " + serial + "(" + 443 info.getType() + ")"); 444 } 445 } 446 } 447 } 448 449 /** 450 * Check the status database for revocation information one one or more 451 * certificates. 452 * 453 * @param reqList the list of {@code LocalSingleRequest} objects taken 454 * from the incoming OCSP request. 455 * 456 * @return a {@code Map} of {@code CertStatusInfo} objects keyed by their 457 * {@code CertId} values, for each single request passed in. Those 458 * CertIds not found in the statusDb will have returned List members with 459 * a status of UNKNOWN. 460 */ 461 private Map<CertId, CertStatusInfo> checkStatusDb( 462 List<LocalOcspRequest.LocalSingleRequest> reqList) { 463 // TODO figure out what, if anything to do with request extensions 464 Map<CertId, CertStatusInfo> returnMap = new HashMap<>(); 465 466 for (LocalOcspRequest.LocalSingleRequest req : reqList) { 467 CertId cid = req.getCertId(); 468 CertStatusInfo info = statusDb.get(cid); 469 if (info != null) { 470 log("Status for SN " + cid.getSerialNumber() + ": " + 471 info.getType()); 472 returnMap.put(cid, info); 473 } else { 474 log("Status for SN " + cid.getSerialNumber() + 475 " not found, using CERT_STATUS_UNKNOWN"); 476 returnMap.put(cid, 477 new CertStatusInfo(CertStatus.CERT_STATUS_UNKNOWN)); 478 } 479 } 480 481 return Collections.unmodifiableMap(returnMap); 482 } 483 484 /** 485 * Set the digital signature algorithm used to sign OCSP responses. 486 * 487 * @param algName The algorithm name 488 * 489 * @throws NoSuchAlgorithmException if the algorithm name is invalid. 490 */ 491 public void setSignatureAlgorithm(String algName) 492 throws NoSuchAlgorithmException { 493 if (!started) { 494 sigAlgId = AlgorithmId.get(algName); 495 } 496 } 497 498 /** 499 * Get the port the OCSP server is running on. 500 * 501 * @return the port that the OCSP server is running on, or -1 if the 502 * server has not yet been bound to a port. 503 */ 504 public int getPort() { 505 if (serverReady) { 506 InetSocketAddress inetSock = 507 (InetSocketAddress)servSocket.getLocalSocketAddress(); 508 return inetSock.getPort(); 509 } else { 510 return -1; 511 } 512 } 513 514 /** 515 * Use to check if OCSP server is ready to accept connection. 516 * 517 * @return true if server ready, false otherwise 518 */ 519 public boolean isServerReady() { 520 return serverReady; 521 } 522 523 /** 524 * Set a delay between the reception of the request and production of 525 * the response. 526 * 527 * @param delayMillis the number of milliseconds to wait before acting 528 * on the incoming request. 529 */ 530 public void setDelay(long delayMillis) { 531 delayMsec = delayMillis > 0 ? delayMillis : 0; 532 if (delayMsec > 0) { 533 log("OCSP latency set to " + delayMsec + " milliseconds."); 534 } else { 535 log("OCSP latency disabled"); 536 } 537 } 538 539 /** 540 * Log a message to stdout. 541 * 542 * @param message the message to log 543 */ 544 private synchronized void log(String message) { 545 if (logEnabled || debug != null) { 546 System.out.println("[" + Thread.currentThread().getName() + "]: " + 547 message); 548 } 549 } 550 551 /** 552 * Log an error message on the stderr stream. 553 * 554 * @param message the message to log 555 */ 556 private static synchronized void err(String message) { 557 System.out.println("[" + Thread.currentThread().getName() + "]: " + 558 message); 559 } 560 561 /** 562 * Log exception information on the stderr stream. 563 * 564 * @param exc the exception to dump information about 565 */ 566 private static synchronized void err(Throwable exc) { 567 System.out.print("[" + Thread.currentThread().getName() + 568 "]: Exception: "); 569 exc.printStackTrace(System.out); 570 } 571 572 /** 573 * The {@code CertStatusInfo} class defines an object used to return 574 * information from the internal status database. The data in this 575 * object may be used to construct OCSP responses. 576 */ 577 public static class CertStatusInfo { 578 private CertStatus certStatusType; 579 private CRLReason reason; 580 private Date revocationTime; 581 582 /** 583 * Create a Certificate status object by providing the status only. 584 * If the status is {@code REVOKED} then current time is assumed 585 * for the revocation time. 586 * 587 * @param statType the status for this entry. 588 */ 589 public CertStatusInfo(CertStatus statType) { 590 this(statType, null, null); 591 } 592 593 /** 594 * Create a CertStatusInfo providing both type and revocation date 595 * (if applicable). 596 * 597 * @param statType the status for this entry. 598 * @param revDate if applicable, the date that revocation took place. 599 * A value of {@code null} indicates that current time should be used. 600 * If the value of {@code statType} is not {@code CERT_STATUS_REVOKED}, 601 * then the {@code revDate} parameter is ignored. 602 */ 603 public CertStatusInfo(CertStatus statType, Date revDate) { 604 this(statType, revDate, null); 605 } 606 607 /** 608 * Create a CertStatusInfo providing type, revocation date 609 * (if applicable) and revocation reason. 610 * 611 * @param statType the status for this entry. 612 * @param revDate if applicable, the date that revocation took place. 613 * A value of {@code null} indicates that current time should be used. 614 * If the value of {@code statType} is not {@code CERT_STATUS_REVOKED}, 615 * then the {@code revDate} parameter is ignored. 616 * @param revReason the reason the certificate was revoked. A value of 617 * {@code null} means that no reason was provided. 618 */ 619 public CertStatusInfo(CertStatus statType, Date revDate, 620 CRLReason revReason) { 621 Objects.requireNonNull(statType, "Cert Status must be non-null"); 622 certStatusType = statType; 623 switch (statType) { 624 case CERT_STATUS_GOOD: 625 case CERT_STATUS_UNKNOWN: 626 revocationTime = null; 627 break; 628 case CERT_STATUS_REVOKED: 629 revocationTime = revDate != null ? (Date)revDate.clone() : 630 new Date(); 631 break; 632 default: 633 throw new IllegalArgumentException("Unknown status type: " + 634 statType); 635 } 636 } 637 638 /** 639 * Get the cert status type 640 * 641 * @return the status applied to this object (e.g. 642 * {@code CERT_STATUS_GOOD}, {@code CERT_STATUS_UNKNOWN}, etc.) 643 */ 644 public CertStatus getType() { 645 return certStatusType; 646 } 647 648 /** 649 * Get the revocation time (if applicable). 650 * 651 * @return the revocation time as a {@code Date} object, or 652 * {@code null} if not applicable (i.e. if the certificate hasn't been 653 * revoked). 654 */ 655 public Date getRevocationTime() { 656 return (revocationTime != null ? (Date)revocationTime.clone() : 657 null); 658 } 659 660 /** 661 * Get the revocation reason. 662 * 663 * @return the revocation reason, or {@code null} if one was not 664 * provided. 665 */ 666 public CRLReason getRevocationReason() { 667 return reason; 668 } 669 } 670 671 /** 672 * Runnable task that handles incoming OCSP Requests and returns 673 * responses. 674 */ 675 private class OcspHandler implements Runnable { 676 private final Socket sock; 677 InetSocketAddress peerSockAddr; 678 679 /** 680 * Construct an {@code OcspHandler}. 681 * 682 * @param incomingSocket the socket the server created on accept() 683 */ 684 private OcspHandler(Socket incomingSocket) { 685 sock = incomingSocket; 686 } 687 688 /** 689 * Run the OCSP Request parser and construct a response to be sent 690 * back to the client. 691 */ 692 @Override 693 public void run() { 694 // If we have implemented a delay to simulate network latency 695 // wait out the delay here before any other processing. 696 try { 697 if (delayMsec > 0) { 698 Thread.sleep(delayMsec); 699 } 700 } catch (InterruptedException ie) { 701 // Just log the interrupted sleep 702 log("Delay of " + delayMsec + " milliseconds was interrupted"); 703 } 704 705 try (Socket ocspSocket = sock; 706 InputStream in = ocspSocket.getInputStream(); 707 OutputStream out = ocspSocket.getOutputStream()) { 708 peerSockAddr = 709 (InetSocketAddress)ocspSocket.getRemoteSocketAddress(); 710 log("Received incoming connection from " + peerSockAddr); 711 String[] headerTokens = readLine(in).split(" "); 712 LocalOcspRequest ocspReq = null; 713 LocalOcspResponse ocspResp = null; 714 ResponseStatus respStat = ResponseStatus.INTERNAL_ERROR; 715 try { 716 if (headerTokens[0] != null) { 717 switch (headerTokens[0]) { 718 case "POST": 719 ocspReq = parseHttpOcspPost(in); 720 break; 721 case "GET": 722 // req = parseHttpOcspGet(in); 723 // TODO implement the GET parsing 724 throw new IOException("GET method unsupported"); 725 default: 726 respStat = ResponseStatus.MALFORMED_REQUEST; 727 throw new IOException("Not a GET or POST"); 728 } 729 } else { 730 respStat = ResponseStatus.MALFORMED_REQUEST; 731 throw new IOException("Unable to get HTTP method"); 732 } 733 734 if (ocspReq != null) { 735 log(ocspReq.toString()); 736 // Get responses for all CertIds in the request 737 Map<CertId, CertStatusInfo> statusMap = 738 checkStatusDb(ocspReq.getRequests()); 739 if (statusMap.isEmpty()) { 740 respStat = ResponseStatus.UNAUTHORIZED; 741 } else { 742 ocspResp = new LocalOcspResponse( 743 ResponseStatus.SUCCESSFUL, statusMap, 744 ocspReq.getExtensions()); 745 } 746 } else { 747 respStat = ResponseStatus.MALFORMED_REQUEST; 748 throw new IOException("Found null request"); 749 } 750 } catch (IOException | RuntimeException exc) { 751 err(exc); 752 } 753 if (ocspResp == null) { 754 ocspResp = new LocalOcspResponse(respStat); 755 } 756 sendResponse(out, ocspResp); 757 } catch (IOException | CertificateException exc) { 758 err(exc); 759 } 760 } 761 762 /** 763 * Send an OCSP response on an {@code OutputStream}. 764 * 765 * @param out the {@code OutputStream} on which to send the response. 766 * @param resp the OCSP response to send. 767 * 768 * @throws IOException if an encoding error occurs. 769 */ 770 public void sendResponse(OutputStream out, LocalOcspResponse resp) 771 throws IOException { 772 StringBuilder sb = new StringBuilder(); 773 774 byte[] respBytes; 775 try { 776 respBytes = resp.getBytes(); 777 } catch (RuntimeException re) { 778 err(re); 779 return; 780 } 781 782 sb.append("HTTP/1.0 200 OK\r\n"); 783 sb.append("Content-Type: application/ocsp-response\r\n"); 784 sb.append("Content-Length: ").append(respBytes.length); 785 sb.append("\r\n\r\n"); 786 787 out.write(sb.toString().getBytes("UTF-8")); 788 out.write(respBytes); 789 log(resp.toString()); 790 } 791 792 /** 793 * Parse the incoming HTTP POST of an OCSP Request. 794 * 795 * @param inStream the input stream from the socket bound to this 796 * {@code OcspHandler}. 797 * 798 * @return the OCSP Request as a {@code LocalOcspRequest} 799 * 800 * @throws IOException if there are network related issues or problems 801 * occur during parsing of the OCSP request. 802 * @throws CertificateException if one or more of the certificates in 803 * the OCSP request cannot be read/parsed. 804 */ 805 private LocalOcspRequest parseHttpOcspPost(InputStream inStream) 806 throws IOException, CertificateException { 807 boolean endOfHeader = false; 808 boolean properContentType = false; 809 int length = -1; 810 811 while (!endOfHeader) { 812 String[] lineTokens = readLine(inStream).split(" "); 813 if (lineTokens[0].isEmpty()) { 814 endOfHeader = true; 815 } else if (lineTokens[0].equalsIgnoreCase("Content-Type:")) { 816 if (lineTokens[1] == null || 817 !lineTokens[1].equals( 818 "application/ocsp-request")) { 819 log("Unknown Content-Type: " + 820 (lineTokens[1] != null ? 821 lineTokens[1] : "<NULL>")); 822 return null; 823 } else { 824 properContentType = true; 825 log("Content-Type = " + lineTokens[1]); 826 } 827 } else if (lineTokens[0].equalsIgnoreCase("Content-Length:")) { 828 if (lineTokens[1] != null) { 829 length = Integer.parseInt(lineTokens[1]); 830 log("Content-Length = " + length); 831 } 832 } 833 } 834 835 // Okay, make sure we got what we needed from the header, then 836 // read the remaining OCSP Request bytes 837 if (properContentType && length >= 0) { 838 byte[] ocspBytes = new byte[length]; 839 inStream.read(ocspBytes); 840 return new LocalOcspRequest(ocspBytes); 841 } else { 842 return null; 843 } 844 } 845 846 /** 847 * Read a line of text that is CRLF-delimited. 848 * 849 * @param is the {@code InputStream} tied to the socket 850 * for this {@code OcspHandler} 851 * 852 * @return a {@code String} consisting of the line of text 853 * read from the stream with the CRLF stripped. 854 * 855 * @throws IOException if any I/O error occurs. 856 */ 857 private String readLine(InputStream is) throws IOException { 858 PushbackInputStream pbis = new PushbackInputStream(is); 859 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 860 boolean done = false; 861 while (!done) { 862 byte b = (byte)pbis.read(); 863 if (b == '\r') { 864 byte bNext = (byte)pbis.read(); 865 if (bNext == '\n' || bNext == -1) { 866 done = true; 867 } else { 868 pbis.unread(bNext); 869 bos.write(b); 870 } 871 } else if (b == -1) { 872 done = true; 873 } else { 874 bos.write(b); 875 } 876 } 877 878 return new String(bos.toByteArray(), "UTF-8"); 879 } 880 } 881 882 883 /** 884 * Simple nested class to handle OCSP requests without making 885 * changes to sun.security.provider.certpath.OCSPRequest 886 */ 887 public class LocalOcspRequest { 888 889 private byte[] nonce; 890 private byte[] signature = null; 891 private AlgorithmId algId = null; 892 private int version = 0; 893 private GeneralName requestorName = null; 894 private Map<String, Extension> extensions = Collections.emptyMap(); 895 private final List<LocalSingleRequest> requestList = new ArrayList<>(); 896 private final List<X509Certificate> certificates = new ArrayList<>(); 897 898 /** 899 * Construct a {@code LocalOcspRequest} from its DER encoding. 900 * 901 * @param requestBytes the DER-encoded bytes 902 * 903 * @throws IOException if decoding errors occur 904 * @throws CertificateException if certificates are found in the 905 * OCSP request and they do not parse correctly. 906 */ 907 private LocalOcspRequest(byte[] requestBytes) throws IOException, 908 CertificateException { 909 Objects.requireNonNull(requestBytes, "Received null input"); 910 911 DerInputStream dis = new DerInputStream(requestBytes); 912 913 // Parse the top-level structure, it should have no more than 914 // two elements. 915 DerValue[] topStructs = dis.getSequence(2); 916 for (DerValue dv : topStructs) { 917 if (dv.tag == DerValue.tag_Sequence) { 918 parseTbsRequest(dv); 919 } else if (dv.isContextSpecific((byte)0)) { 920 parseSignature(dv); 921 } else { 922 throw new IOException("Unknown tag at top level: " + 923 dv.tag); 924 } 925 } 926 } 927 928 /** 929 * Parse the signature block from an OCSP request 930 * 931 * @param sigSequence a {@code DerValue} containing the signature 932 * block at the outer sequence datum. 933 * 934 * @throws IOException if any non-certificate-based parsing errors occur 935 * @throws CertificateException if certificates are found in the 936 * OCSP request and they do not parse correctly. 937 */ 938 private void parseSignature(DerValue sigSequence) 939 throws IOException, CertificateException { 940 DerValue[] sigItems = sigSequence.data.getSequence(3); 941 if (sigItems.length != 3) { 942 throw new IOException("Invalid number of signature items: " + 943 "expected 3, got " + sigItems.length); 944 } 945 946 algId = AlgorithmId.parse(sigItems[0]); 947 signature = sigItems[1].getBitString(); 948 949 if (sigItems[2].isContextSpecific((byte)0)) { 950 DerValue[] certDerItems = sigItems[2].data.getSequence(4); 951 int i = 0; 952 for (DerValue dv : certDerItems) { 953 X509Certificate xc = new X509CertImpl(dv); 954 certificates.add(xc); 955 } 956 } else { 957 throw new IOException("Invalid tag in signature block: " + 958 sigItems[2].tag); 959 } 960 } 961 962 /** 963 * Parse the to-be-signed request data 964 * 965 * @param tbsReqSeq a {@code DerValue} object containing the to-be- 966 * signed OCSP request at the outermost SEQUENCE tag. 967 * @throws IOException if any parsing errors occur 968 */ 969 private void parseTbsRequest(DerValue tbsReqSeq) throws IOException { 970 while (tbsReqSeq.data.available() > 0) { 971 DerValue dv = tbsReqSeq.data.getDerValue(); 972 if (dv.isContextSpecific((byte)0)) { 973 // The version was explicitly called out 974 version = dv.data.getInteger(); 975 } else if (dv.isContextSpecific((byte)1)) { 976 // A GeneralName was provided 977 requestorName = new GeneralName(dv.data.getDerValue()); 978 } else if (dv.isContextSpecific((byte)2)) { 979 // Parse the extensions 980 DerValue[] extItems = dv.data.getSequence(2); 981 extensions = parseExtensions(extItems); 982 } else if (dv.tag == DerValue.tag_Sequence) { 983 while (dv.data.available() > 0) { 984 requestList.add(new LocalSingleRequest(dv.data)); 985 } 986 } 987 } 988 } 989 990 /** 991 * Parse a SEQUENCE of extensions. This routine is used both 992 * at the overall request level and down at the singleRequest layer. 993 * 994 * @param extDerItems an array of {@code DerValue} items, each one 995 * consisting of a DER-encoded extension. 996 * 997 * @return a {@code Map} of zero or more extensions, 998 * keyed by its object identifier in {@code String} form. 999 * 1000 * @throws IOException if any parsing errors occur. 1001 */ 1002 private Map<String, Extension> parseExtensions(DerValue[] extDerItems) 1003 throws IOException { 1004 Map<String, Extension> extMap = new HashMap<>(); 1005 1006 if (extDerItems != null && extDerItems.length != 0) { 1007 for (DerValue extDerVal : extDerItems) { 1008 sun.security.x509.Extension ext = 1009 new sun.security.x509.Extension(extDerVal); 1010 extMap.put(ext.getId(), ext); 1011 } 1012 } 1013 1014 return extMap; 1015 } 1016 1017 /** 1018 * Return the list of single request objects in this OCSP request. 1019 * 1020 * @return an unmodifiable {@code List} of zero or more requests. 1021 */ 1022 private List<LocalSingleRequest> getRequests() { 1023 return Collections.unmodifiableList(requestList); 1024 } 1025 1026 /** 1027 * Return the list of X.509 Certificates in this OCSP request. 1028 * 1029 * @return an unmodifiable {@code List} of zero or more 1030 * {@cpde X509Certificate} objects. 1031 */ 1032 private List<X509Certificate> getCertificates() { 1033 return Collections.unmodifiableList(certificates); 1034 } 1035 1036 /** 1037 * Return the map of OCSP request extensions. 1038 * 1039 * @return an unmodifiable {@code Map} of zero or more 1040 * {@code Extension} objects, keyed by their object identifiers 1041 * in {@code String} form. 1042 */ 1043 private Map<String, Extension> getExtensions() { 1044 return Collections.unmodifiableMap(extensions); 1045 } 1046 1047 /** 1048 * Display the {@code LocalOcspRequest} in human readable form. 1049 * 1050 * @return a {@code String} representation of the 1051 * {@code LocalOcspRequest} 1052 */ 1053 @Override 1054 public String toString() { 1055 StringBuilder sb = new StringBuilder(); 1056 1057 sb.append(String.format("OCSP Request: Version %d (0x%X)", 1058 version + 1, version)).append("\n"); 1059 if (requestorName != null) { 1060 sb.append("Requestor Name: ").append(requestorName). 1061 append("\n"); 1062 } 1063 1064 int requestCtr = 0; 1065 for (LocalSingleRequest lsr : requestList) { 1066 sb.append("Request [").append(requestCtr++).append("]\n"); 1067 sb.append(lsr).append("\n"); 1068 } 1069 if (!extensions.isEmpty()) { 1070 sb.append("Extensions (").append(extensions.size()). 1071 append(")\n"); 1072 for (Extension ext : extensions.values()) { 1073 sb.append("\t").append(ext).append("\n"); 1074 } 1075 } 1076 if (signature != null) { 1077 sb.append("Signature: ").append(algId).append("\n"); 1078 sb.append(dumpHexBytes(signature)).append("\n"); 1079 int certCtr = 0; 1080 for (X509Certificate cert : certificates) { 1081 sb.append("Certificate [").append(certCtr++).append("]"). 1082 append("\n"); 1083 sb.append("\tSubject: "); 1084 sb.append(cert.getSubjectX500Principal()).append("\n"); 1085 sb.append("\tIssuer: "); 1086 sb.append(cert.getIssuerX500Principal()).append("\n"); 1087 sb.append("\tSerial: ").append(cert.getSerialNumber()); 1088 } 1089 } 1090 1091 return sb.toString(); 1092 } 1093 1094 /** 1095 * Inner class designed to handle the decoding/representation of 1096 * single requests within a {@code LocalOcspRequest} object. 1097 */ 1098 public class LocalSingleRequest { 1099 private final CertId cid; 1100 private Map<String, Extension> extensions = Collections.emptyMap(); 1101 1102 private LocalSingleRequest(DerInputStream dis) 1103 throws IOException { 1104 DerValue[] srItems = dis.getSequence(2); 1105 1106 // There should be 1, possibly 2 DerValue items 1107 if (srItems.length == 1 || srItems.length == 2) { 1108 // The first parsable item should be the mandatory CertId 1109 cid = new CertId(srItems[0].data); 1110 if (srItems.length == 2) { 1111 if (srItems[1].isContextSpecific((byte)0)) { 1112 DerValue[] extDerItems = srItems[1].data.getSequence(2); 1113 extensions = parseExtensions(extDerItems); 1114 } else { 1115 throw new IOException("Illegal tag in Request " + 1116 "extensions: " + srItems[1].tag); 1117 } 1118 } 1119 } else { 1120 throw new IOException("Invalid number of items in " + 1121 "Request (" + srItems.length + ")"); 1122 } 1123 } 1124 1125 /** 1126 * Get the {@code CertId} for this single request. 1127 * 1128 * @return the {@code CertId} for this single request. 1129 */ 1130 private CertId getCertId() { 1131 return cid; 1132 } 1133 1134 /** 1135 * Return the map of single request extensions. 1136 * 1137 * @return an unmodifiable {@code Map} of zero or more 1138 * {@code Extension} objects, keyed by their object identifiers 1139 * in {@code String} form. 1140 */ 1141 private Map<String, Extension> getExtensions() { 1142 return Collections.unmodifiableMap(extensions); 1143 } 1144 1145 /** 1146 * Display the {@code LocalSingleRequest} in human readable form. 1147 * 1148 * @return a {@code String} representation of the 1149 * {@code LocalSingleRequest} 1150 */ 1151 @Override 1152 public String toString() { 1153 StringBuilder sb = new StringBuilder(); 1154 sb.append("CertId, Algorithm = "); 1155 sb.append(cid.getHashAlgorithm()).append("\n"); 1156 sb.append("\tIssuer Name Hash: "); 1157 sb.append(dumpHexBytes(cid.getIssuerNameHash(), 256, "", "")); 1158 sb.append("\n"); 1159 sb.append("\tIssuer Key Hash: "); 1160 sb.append(dumpHexBytes(cid.getIssuerKeyHash(), 256, "", "")); 1161 sb.append("\n"); 1162 sb.append("\tSerial Number: ").append(cid.getSerialNumber()); 1163 if (!extensions.isEmpty()) { 1164 sb.append("Extensions (").append(extensions.size()). 1165 append(")\n"); 1166 for (Extension ext : extensions.values()) { 1167 sb.append("\t").append(ext).append("\n"); 1168 } 1169 } 1170 1171 return sb.toString(); 1172 } 1173 } 1174 } 1175 1176 /** 1177 * Simple nested class to handle OCSP requests without making 1178 * changes to sun.security.provider.certpath.OCSPResponse 1179 */ 1180 public class LocalOcspResponse { 1181 private final int version = 0; 1182 private final OCSPResponse.ResponseStatus responseStatus; 1183 private final Map<CertId, CertStatusInfo> respItemMap; 1184 private final Date producedAtDate; 1185 private final List<LocalSingleResponse> singleResponseList = 1186 new ArrayList<>(); 1187 private final Map<String, Extension> responseExtensions; 1188 private byte[] signature; 1189 private final List<X509Certificate> certificates; 1190 private final byte[] encodedResponse; 1191 1192 /** 1193 * Constructor for the generation of non-successful responses 1194 * 1195 * @param respStat the OCSP response status. 1196 * 1197 * @throws IOException if an error happens during encoding 1198 * @throws NullPointerException if {@code respStat} is {@code null} 1199 * or {@code respStat} is successful. 1200 */ 1201 public LocalOcspResponse(OCSPResponse.ResponseStatus respStat) 1202 throws IOException { 1203 this(respStat, null, null); 1204 } 1205 1206 /** 1207 * Construct a response from a list of certificate 1208 * status objects and extensions. 1209 * 1210 * @param respStat the status of the entire response 1211 * @param itemMap a {@code Map} of {@code CertId} objects and their 1212 * respective revocation statuses from the server's response DB. 1213 * @param reqExtensions a {@code Map} of request extensions 1214 * 1215 * @throws IOException if an error happens during encoding 1216 * @throws NullPointerException if {@code respStat} is {@code null} 1217 * or {@code respStat} is successful, and a {@code null} {@code itemMap} 1218 * has been provided. 1219 */ 1220 public LocalOcspResponse(OCSPResponse.ResponseStatus respStat, 1221 Map<CertId, CertStatusInfo> itemMap, 1222 Map<String, Extension> reqExtensions) throws IOException { 1223 responseStatus = Objects.requireNonNull(respStat, 1224 "Illegal null response status"); 1225 if (responseStatus == ResponseStatus.SUCCESSFUL) { 1226 respItemMap = Objects.requireNonNull(itemMap, 1227 "SUCCESSFUL responses must have a response map"); 1228 producedAtDate = new Date(); 1229 1230 // Turn the answerd from the response DB query into a list 1231 // of single responses. 1232 for (CertId id : itemMap.keySet()) { 1233 singleResponseList.add( 1234 new LocalSingleResponse(id, itemMap.get(id))); 1235 } 1236 1237 responseExtensions = setResponseExtensions(reqExtensions); 1238 certificates = new ArrayList<>(); 1239 if (signerCert != issuerCert) { 1240 certificates.add(signerCert); 1241 } 1242 certificates.add(issuerCert); 1243 } else { 1244 respItemMap = null; 1245 producedAtDate = null; 1246 responseExtensions = null; 1247 certificates = null; 1248 } 1249 encodedResponse = this.getBytes(); 1250 } 1251 1252 /** 1253 * Set the response extensions based on the request extensions 1254 * that were received. Right now, this is limited to the 1255 * OCSP nonce extension. 1256 * 1257 * @param reqExts a {@code Map} of zero or more request extensions 1258 * 1259 * @return a {@code Map} of zero or more response extensions, keyed 1260 * by the extension object identifier in {@code String} form. 1261 */ 1262 private Map<String, Extension> setResponseExtensions( 1263 Map<String, Extension> reqExts) { 1264 Map<String, Extension> respExts = new HashMap<>(); 1265 String ocspNonceStr = PKIXExtensions.OCSPNonce_Id.toString(); 1266 1267 if (reqExts != null) { 1268 for (String id : reqExts.keySet()) { 1269 if (id.equals(ocspNonceStr)) { 1270 // We found a nonce, add it into the response extensions 1271 Extension ext = reqExts.get(id); 1272 if (ext != null) { 1273 respExts.put(id, ext); 1274 log("Added OCSP Nonce to response"); 1275 } else { 1276 log("Error: Found nonce entry, but found null " + 1277 "value. Skipping"); 1278 } 1279 } 1280 } 1281 } 1282 1283 return respExts; 1284 } 1285 1286 /** 1287 * Get the DER-encoded response bytes for this response 1288 * 1289 * @return a byte array containing the DER-encoded bytes for 1290 * the response 1291 * 1292 * @throws IOException if any encoding errors occur 1293 */ 1294 private byte[] getBytes() throws IOException { 1295 DerOutputStream outerSeq = new DerOutputStream(); 1296 DerOutputStream responseStream = new DerOutputStream(); 1297 responseStream.putEnumerated(responseStatus.ordinal()); 1298 if (responseStatus == ResponseStatus.SUCCESSFUL && 1299 respItemMap != null) { 1300 encodeResponseBytes(responseStream); 1301 } 1302 1303 // Commit the outermost sequence bytes 1304 outerSeq.write(DerValue.tag_Sequence, responseStream); 1305 return outerSeq.toByteArray(); 1306 } 1307 1308 private void encodeResponseBytes(DerOutputStream responseStream) 1309 throws IOException { 1310 DerOutputStream explicitZero = new DerOutputStream(); 1311 DerOutputStream respItemStream = new DerOutputStream(); 1312 1313 respItemStream.putOID(OCSP_BASIC_RESPONSE_OID); 1314 1315 byte[] basicOcspBytes = encodeBasicOcspResponse(); 1316 respItemStream.putOctetString(basicOcspBytes); 1317 explicitZero.write(DerValue.tag_Sequence, respItemStream); 1318 responseStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, 1319 true, (byte)0), explicitZero); 1320 } 1321 1322 private byte[] encodeBasicOcspResponse() throws IOException { 1323 DerOutputStream outerSeq = new DerOutputStream(); 1324 DerOutputStream basicORItemStream = new DerOutputStream(); 1325 1326 // Encode the tbsResponse 1327 byte[] tbsResponseBytes = encodeTbsResponse(); 1328 basicORItemStream.write(tbsResponseBytes); 1329 1330 try { 1331 sigAlgId.derEncode(basicORItemStream); 1332 1333 // Create the signature 1334 Signature sig = Signature.getInstance(sigAlgId.getName()); 1335 sig.initSign(signerKey); 1336 sig.update(tbsResponseBytes); 1337 signature = sig.sign(); 1338 basicORItemStream.putBitString(signature); 1339 } catch (GeneralSecurityException exc) { 1340 err(exc); 1341 throw new IOException(exc); 1342 } 1343 1344 // Add certificates 1345 try { 1346 DerOutputStream certStream = new DerOutputStream(); 1347 ArrayList<DerValue> certList = new ArrayList<>(); 1348 if (signerCert != issuerCert) { 1349 certList.add(new DerValue(signerCert.getEncoded())); 1350 } 1351 certList.add(new DerValue(issuerCert.getEncoded())); 1352 DerValue[] dvals = new DerValue[certList.size()]; 1353 certStream.putSequence(certList.toArray(dvals)); 1354 basicORItemStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, 1355 true, (byte)0), certStream); 1356 } catch (CertificateEncodingException cex) { 1357 err(cex); 1358 throw new IOException(cex); 1359 } 1360 1361 // Commit the outermost sequence bytes 1362 outerSeq.write(DerValue.tag_Sequence, basicORItemStream); 1363 return outerSeq.toByteArray(); 1364 } 1365 1366 private byte[] encodeTbsResponse() throws IOException { 1367 DerOutputStream outerSeq = new DerOutputStream(); 1368 DerOutputStream tbsStream = new DerOutputStream(); 1369 1370 // Note: We're not going explicitly assert the version 1371 tbsStream.write(respId.getEncoded()); 1372 tbsStream.putGeneralizedTime(producedAtDate); 1373 1374 // Sequence of responses 1375 encodeSingleResponses(tbsStream); 1376 1377 // TODO: add response extension support 1378 encodeExtensions(tbsStream); 1379 1380 outerSeq.write(DerValue.tag_Sequence, tbsStream); 1381 return outerSeq.toByteArray(); 1382 } 1383 1384 private void encodeSingleResponses(DerOutputStream tbsStream) 1385 throws IOException { 1386 DerValue[] srDerVals = new DerValue[singleResponseList.size()]; 1387 int srDvCtr = 0; 1388 1389 for (LocalSingleResponse lsr : singleResponseList) { 1390 srDerVals[srDvCtr++] = new DerValue(lsr.getBytes()); 1391 } 1392 1393 tbsStream.putSequence(srDerVals); 1394 } 1395 1396 private void encodeExtensions(DerOutputStream tbsStream) 1397 throws IOException { 1398 DerOutputStream extSequence = new DerOutputStream(); 1399 DerOutputStream extItems = new DerOutputStream(); 1400 1401 for (Extension ext : responseExtensions.values()) { 1402 ext.encode(extItems); 1403 } 1404 extSequence.write(DerValue.tag_Sequence, extItems); 1405 tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, 1406 (byte)1), extSequence); 1407 } 1408 1409 @Override 1410 public String toString() { 1411 StringBuilder sb = new StringBuilder(); 1412 1413 sb.append("OCSP Response: ").append(responseStatus).append("\n"); 1414 if (responseStatus == ResponseStatus.SUCCESSFUL) { 1415 sb.append("Response Type: "). 1416 append(OCSP_BASIC_RESPONSE_OID.toString()).append("\n"); 1417 sb.append(String.format("Version: %d (0x%X)", version + 1, 1418 version)).append("\n"); 1419 sb.append("Responder Id: ").append(respId.toString()). 1420 append("\n"); 1421 sb.append("Produced At: "). 1422 append(utcDateFmt.format(producedAtDate)).append("\n"); 1423 1424 int srCtr = 0; 1425 for (LocalSingleResponse lsr : singleResponseList) { 1426 sb.append("SingleResponse [").append(srCtr++).append("]\n"); 1427 sb.append(lsr); 1428 } 1429 1430 if (!responseExtensions.isEmpty()) { 1431 sb.append("Extensions (").append(responseExtensions.size()). 1432 append(")\n"); 1433 for (Extension ext : responseExtensions.values()) { 1434 sb.append("\t").append(ext).append("\n"); 1435 } 1436 } else { 1437 sb.append("\n"); 1438 } 1439 1440 if (signature != null) { 1441 sb.append("Signature: ").append(sigAlgId).append("\n"); 1442 sb.append(dumpHexBytes(signature)).append("\n"); 1443 int certCtr = 0; 1444 for (X509Certificate cert : certificates) { 1445 sb.append("Certificate [").append(certCtr++).append("]"). 1446 append("\n"); 1447 sb.append("\tSubject: "); 1448 sb.append(cert.getSubjectX500Principal()).append("\n"); 1449 sb.append("\tIssuer: "); 1450 sb.append(cert.getIssuerX500Principal()).append("\n"); 1451 sb.append("\tSerial: ").append(cert.getSerialNumber()); 1452 sb.append("\n"); 1453 } 1454 } 1455 } 1456 1457 return sb.toString(); 1458 } 1459 1460 private class LocalSingleResponse { 1461 private final CertId certId; 1462 private final CertStatusInfo csInfo; 1463 private final Date thisUpdate; 1464 private final Date lsrNextUpdate; 1465 private final Map<String, Extension> singleExtensions; 1466 1467 public LocalSingleResponse(CertId cid, CertStatusInfo info) { 1468 certId = Objects.requireNonNull(cid, "CertId must be non-null"); 1469 csInfo = Objects.requireNonNull(info, 1470 "CertStatusInfo must be non-null"); 1471 1472 // For now, we'll keep things simple and make the thisUpdate 1473 // field the same as the producedAt date. 1474 thisUpdate = producedAtDate; 1475 lsrNextUpdate = getNextUpdate(); 1476 1477 // TODO Add extensions support 1478 singleExtensions = Collections.emptyMap(); 1479 } 1480 1481 @Override 1482 public String toString() { 1483 StringBuilder sb = new StringBuilder(); 1484 sb.append("Certificate Status: ").append(csInfo.getType()); 1485 sb.append("\n"); 1486 if (csInfo.getType() == CertStatus.CERT_STATUS_REVOKED) { 1487 sb.append("Revocation Time: "); 1488 sb.append(utcDateFmt.format(csInfo.getRevocationTime())); 1489 sb.append("\n"); 1490 if (csInfo.getRevocationReason() != null) { 1491 sb.append("Revocation Reason: "); 1492 sb.append(csInfo.getRevocationReason()).append("\n"); 1493 } 1494 } 1495 1496 sb.append("CertId, Algorithm = "); 1497 sb.append(certId.getHashAlgorithm()).append("\n"); 1498 sb.append("\tIssuer Name Hash: "); 1499 sb.append(dumpHexBytes(certId.getIssuerNameHash(), 256, "", "")); 1500 sb.append("\n"); 1501 sb.append("\tIssuer Key Hash: "); 1502 sb.append(dumpHexBytes(certId.getIssuerKeyHash(), 256, "", "")); 1503 sb.append("\n"); 1504 sb.append("\tSerial Number: ").append(certId.getSerialNumber()); 1505 sb.append("\n"); 1506 sb.append("This Update: "); 1507 sb.append(utcDateFmt.format(thisUpdate)).append("\n"); 1508 if (lsrNextUpdate != null) { 1509 sb.append("Next Update: "); 1510 sb.append(utcDateFmt.format(lsrNextUpdate)).append("\n"); 1511 } 1512 1513 if (!singleExtensions.isEmpty()) { 1514 sb.append("Extensions (").append(singleExtensions.size()). 1515 append(")\n"); 1516 for (Extension ext : singleExtensions.values()) { 1517 sb.append("\t").append(ext).append("\n"); 1518 } 1519 } 1520 1521 return sb.toString(); 1522 } 1523 1524 public byte[] getBytes() throws IOException { 1525 byte[] nullData = { }; 1526 DerOutputStream responseSeq = new DerOutputStream(); 1527 DerOutputStream srStream = new DerOutputStream(); 1528 1529 // Encode the CertId 1530 certId.encode(srStream); 1531 1532 // Next, encode the CertStatus field 1533 CertStatus csiType = csInfo.getType(); 1534 switch (csiType) { 1535 case CERT_STATUS_GOOD: 1536 srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, 1537 false, (byte)0), nullData); 1538 break; 1539 case CERT_STATUS_REVOKED: 1540 DerOutputStream revInfo = new DerOutputStream(); 1541 revInfo.putGeneralizedTime(csInfo.getRevocationTime()); 1542 CRLReason revReason = csInfo.getRevocationReason(); 1543 if (revReason != null) { 1544 byte[] revDer = new byte[3]; 1545 revDer[0] = DerValue.tag_Enumerated; 1546 revDer[1] = 1; 1547 revDer[2] = (byte)revReason.ordinal(); 1548 revInfo.write(DerValue.createTag( 1549 DerValue.TAG_CONTEXT, true, (byte)0), 1550 revDer); 1551 } 1552 srStream.write(DerValue.createTag( 1553 DerValue.TAG_CONTEXT, true, (byte)1), 1554 revInfo); 1555 break; 1556 case CERT_STATUS_UNKNOWN: 1557 srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, 1558 false, (byte)2), nullData); 1559 break; 1560 default: 1561 throw new IOException("Unknown CertStatus: " + csiType); 1562 } 1563 1564 // Add the necessary dates 1565 srStream.putGeneralizedTime(thisUpdate); 1566 if (lsrNextUpdate != null) { 1567 DerOutputStream nuStream = new DerOutputStream(); 1568 nuStream.putGeneralizedTime(lsrNextUpdate); 1569 srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, 1570 true, (byte)0), nuStream); 1571 } 1572 1573 // TODO add singleResponse Extension support 1574 1575 // Add the single response to the response output stream 1576 responseSeq.write(DerValue.tag_Sequence, srStream); 1577 return responseSeq.toByteArray(); 1578 } 1579 } 1580 } 1581} 1582