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