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