Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/java/security/testlibrary/SimpleOCSPServer.java
38811 views
1
/*
2
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package sun.security.testlibrary;
27
28
import java.io.*;
29
import java.net.*;
30
import java.security.*;
31
import java.security.cert.CRLReason;
32
import java.security.cert.X509Certificate;
33
import java.security.cert.Extension;
34
import java.security.cert.CertificateException;
35
import java.security.cert.CertificateEncodingException;
36
import java.security.Signature;
37
import java.util.*;
38
import java.util.concurrent.*;
39
import java.text.SimpleDateFormat;
40
import java.math.BigInteger;
41
42
import sun.security.x509.*;
43
import sun.security.x509.PKIXExtensions;
44
import sun.security.provider.certpath.ResponderId;
45
import sun.security.provider.certpath.CertId;
46
import sun.security.provider.certpath.OCSPResponse;
47
import sun.security.provider.certpath.OCSPResponse.ResponseStatus;
48
import sun.security.util.Debug;
49
import sun.security.util.DerInputStream;
50
import sun.security.util.DerOutputStream;
51
import sun.security.util.DerValue;
52
import sun.security.util.ObjectIdentifier;
53
54
55
/**
56
* This is a simple OCSP server designed to listen and respond to incoming
57
* requests.
58
*/
59
public class SimpleOCSPServer {
60
private final Debug debug = Debug.getInstance("oserv");
61
private static final ObjectIdentifier OCSP_BASIC_RESPONSE_OID =
62
ObjectIdentifier.newInternal(
63
new int[] { 1, 3, 6, 1, 5, 5, 7, 48, 1, 1});
64
private static final SimpleDateFormat utcDateFmt =
65
new SimpleDateFormat("MMM dd yyyy, HH:mm:ss z");
66
67
static final int FREE_PORT = 0;
68
69
// CertStatus values
70
public static enum CertStatus {
71
CERT_STATUS_GOOD,
72
CERT_STATUS_REVOKED,
73
CERT_STATUS_UNKNOWN,
74
}
75
76
// Fields used for the networking portion of the responder
77
private ServerSocket servSocket;
78
private InetAddress listenAddress;
79
private int listenPort;
80
81
// Keystore information (certs, keys, etc.)
82
private KeyStore keystore;
83
private X509Certificate issuerCert;
84
private X509Certificate signerCert;
85
private PrivateKey signerKey;
86
87
// Fields used for the operational portions of the server
88
private boolean logEnabled = false;
89
private ExecutorService threadPool;
90
private volatile boolean started = false;
91
private volatile boolean serverReady = false;
92
private volatile boolean receivedShutdown = false;
93
private volatile boolean acceptConnections = true;
94
private volatile long delayMsec = 0;
95
96
// Fields used in the generation of responses
97
private long nextUpdateInterval = -1;
98
private Date nextUpdate = null;
99
private ResponderId respId;
100
private AlgorithmId sigAlgId;
101
private Map<CertId, CertStatusInfo> statusDb =
102
Collections.synchronizedMap(new HashMap<>());
103
104
/**
105
* Construct a SimpleOCSPServer using keystore, password, and alias
106
* parameters.
107
*
108
* @param ks the keystore to be used
109
* @param password the password to access key material in the keystore
110
* @param issuerAlias the alias of the issuer certificate
111
* @param signerAlias the alias of the signer certificate and key. A
112
* value of {@code null} means that the {@code issuerAlias} will be used
113
* to look up the signer key.
114
*
115
* @throws GeneralSecurityException if there are problems accessing the
116
* keystore or finding objects within the keystore.
117
* @throws IOException if a {@code ResponderId} cannot be generated from
118
* the signer certificate.
119
*/
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
1583