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