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