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 
26 package sun.security.testlibrary;
27 
28 import java.io.*;
29 import java.net.*;
30 import java.security.*;
31 import java.security.cert.CRLReason;
32 import java.security.cert.X509Certificate;
33 import java.security.cert.Extension;
34 import java.security.cert.CertificateException;
35 import java.security.cert.CertificateEncodingException;
36 import java.security.Signature;
37 import java.util.*;
38 import java.util.concurrent.*;
39 import java.text.SimpleDateFormat;
40 import java.math.BigInteger;
41 
42 import sun.security.x509.*;
43 import sun.security.x509.PKIXExtensions;
44 import sun.security.provider.certpath.ResponderId;
45 import sun.security.provider.certpath.CertId;
46 import sun.security.provider.certpath.OCSPResponse;
47 import sun.security.provider.certpath.OCSPResponse.ResponseStatus;
48 import sun.security.util.Debug;
49 import sun.security.util.DerInputStream;
50 import sun.security.util.DerOutputStream;
51 import sun.security.util.DerValue;
52 import sun.security.util.ObjectIdentifier;
53 
54 
55 /**
56  * This is a simple OCSP server designed to listen and respond to incoming
57  * requests.
58  */
59 public 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      */
SimpleOCSPServer(KeyStore ks, String password, String issuerAlias, String signerAlias)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      */
SimpleOCSPServer(InetAddress addr, int port, KeyStore ks, String password, String issuerAlias, String signerAlias)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      */
start()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      */
rejectConnections()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      */
acceptConnections()282     public synchronized void acceptConnections() {
283         log("Accept OCSP connections");
284         acceptConnections = true;
285     }
286 
287 
288     /**
289      * Stop the OCSP server.
290      */
stop()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
toString()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      */
dumpHexBytes(byte[] data)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      */
dumpHexBytes(byte[] data, int itemsPerLine, String lineDelim, String itemDelim)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      */
enableLog(boolean enable)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      */
setNextUpdateInterval(long interval)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      */
getNextUpdate()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      */
updateStatusDb(Map<BigInteger, CertStatusInfo> newEntries)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      */
checkStatusDb( List<LocalOcspRequest.LocalSingleRequest> reqList)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      */
setSignatureAlgorithm(String algName)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      */
getPort()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      */
isServerReady()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      */
setDelay(long delayMillis)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      */
log(String message)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      */
err(String message)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      */
err(Throwable exc)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          */
CertStatusInfo(CertStatus statType)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          */
CertStatusInfo(CertStatus statType, Date revDate)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          */
CertStatusInfo(CertStatus statType, Date revDate, CRLReason revReason)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          */
getType()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          */
getRevocationTime()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          */
getRevocationReason()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          */
OcspHandler(Socket incomingSocket)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
run()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          */
sendResponse(OutputStream out, LocalOcspResponse resp)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          */
parseHttpOcspPost(InputStream inStream)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          */
readLine(InputStream is)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          */
LocalOcspRequest(byte[] requestBytes)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          */
parseSignature(DerValue sigSequence)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          */
parseTbsRequest(DerValue tbsReqSeq)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          */
parseExtensions(DerValue[] extDerItems)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          */
getRequests()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          */
getCertificates()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          */
getExtensions()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
toString()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 
LocalSingleRequest(DerInputStream dis)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              */
getCertId()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              */
getExtensions()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
toString()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          */
LocalOcspResponse(OCSPResponse.ResponseStatus respStat)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          */
LocalOcspResponse(OCSPResponse.ResponseStatus respStat, Map<CertId, CertStatusInfo> itemMap, Map<String, Extension> reqExtensions)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          */
setResponseExtensions( Map<String, Extension> reqExts)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          */
getBytes()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 
encodeResponseBytes(DerOutputStream responseStream)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 
encodeBasicOcspResponse()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 
encodeTbsResponse()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 
encodeSingleResponses(DerOutputStream tbsStream)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 
encodeExtensions(DerOutputStream tbsStream)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
toString()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 
LocalSingleResponse(CertId cid, CertStatusInfo info)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
toString()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 
getBytes()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