blob: 24d88374c1c6c0573a7ff91c8da361c9bbe45fa2 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.security.provider.certpath;
27
28import java.io.*;
29import java.math.BigInteger;
30import java.security.*;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateFactory;
33import java.security.cert.CertPathValidatorException;
34import java.security.cert.CRLReason;
35import java.security.cert.X509Certificate;
36import java.security.cert.PKIXParameters;
37import javax.security.auth.x500.X500Principal;
38import java.util.Date;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42import java.util.Set;
43import java.util.Iterator;
44import sun.misc.HexDumpEncoder;
45import sun.security.x509.*;
46import sun.security.util.*;
47
48/**
49 * This class is used to process an OCSP response.
50 * The OCSP Response is defined
51 * in RFC 2560 and the ASN.1 encoding is as follows:
52 * <pre>
53 *
54 * OCSPResponse ::= SEQUENCE {
55 * responseStatus OCSPResponseStatus,
56 * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
57 *
58 * OCSPResponseStatus ::= ENUMERATED {
59 * successful (0), --Response has valid confirmations
60 * malformedRequest (1), --Illegal confirmation request
61 * internalError (2), --Internal error in issuer
62 * tryLater (3), --Try again later
63 * --(4) is not used
64 * sigRequired (5), --Must sign the request
65 * unauthorized (6) --Request unauthorized
66 * }
67 *
68 * ResponseBytes ::= SEQUENCE {
69 * responseType OBJECT IDENTIFIER,
70 * response OCTET STRING }
71 *
72 * BasicOCSPResponse ::= SEQUENCE {
73 * tbsResponseData ResponseData,
74 * signatureAlgorithm AlgorithmIdentifier,
75 * signature BIT STRING,
76 * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
77 *
78 * The value for signature SHALL be computed on the hash of the DER
79 * encoding ResponseData.
80 *
81 * ResponseData ::= SEQUENCE {
82 * version [0] EXPLICIT Version DEFAULT v1,
83 * responderID ResponderID,
84 * producedAt GeneralizedTime,
85 * responses SEQUENCE OF SingleResponse,
86 * responseExtensions [1] EXPLICIT Extensions OPTIONAL }
87 *
88 * ResponderID ::= CHOICE {
89 * byName [1] Name,
90 * byKey [2] KeyHash }
91 *
92 * KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
93 * (excluding the tag and length fields)
94 *
95 * SingleResponse ::= SEQUENCE {
96 * certID CertID,
97 * certStatus CertStatus,
98 * thisUpdate GeneralizedTime,
99 * nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
100 * singleExtensions [1] EXPLICIT Extensions OPTIONAL }
101 *
102 * CertStatus ::= CHOICE {
103 * good [0] IMPLICIT NULL,
104 * revoked [1] IMPLICIT RevokedInfo,
105 * unknown [2] IMPLICIT UnknownInfo }
106 *
107 * RevokedInfo ::= SEQUENCE {
108 * revocationTime GeneralizedTime,
109 * revocationReason [0] EXPLICIT CRLReason OPTIONAL }
110 *
111 * UnknownInfo ::= NULL -- this can be replaced with an enumeration
112 *
113 * </pre>
114 *
115 * @author Ram Marti
116 */
117
118class OCSPResponse {
119
120 // Certificate status CHOICE
121 public static final int CERT_STATUS_GOOD = 0;
122 public static final int CERT_STATUS_REVOKED = 1;
123 public static final int CERT_STATUS_UNKNOWN = 2;
124
125 private static final Debug DEBUG = Debug.getInstance("certpath");
126 private static final boolean dump = false;
127 private static final ObjectIdentifier OCSP_BASIC_RESPONSE_OID;
128 private static final ObjectIdentifier OCSP_NONCE_EXTENSION_OID;
129 static {
130 ObjectIdentifier tmp1 = null;
131 ObjectIdentifier tmp2 = null;
132 try {
133 tmp1 = new ObjectIdentifier("1.3.6.1.5.5.7.48.1.1");
134 tmp2 = new ObjectIdentifier("1.3.6.1.5.5.7.48.1.2");
135 } catch (Exception e) {
136 // should not happen; log and exit
137 }
138 OCSP_BASIC_RESPONSE_OID = tmp1;
139 OCSP_NONCE_EXTENSION_OID = tmp2;
140 }
141
142 // OCSP response status code
143 private static final int OCSP_RESPONSE_OK = 0;
144
145 // ResponderID CHOICE tags
146 private static final int NAME_TAG = 1;
147 private static final int KEY_TAG = 2;
148
149 // Object identifier for the OCSPSigning key purpose
150 private static final String KP_OCSP_SIGNING_OID = "1.3.6.1.5.5.7.3.9";
151
152 private SingleResponse singleResponse;
153
154 // an array of all of the CRLReasons (used in SingleResponse)
155 private static CRLReason[] values = CRLReason.values();
156
157 /*
158 * Create an OCSP response from its ASN.1 DER encoding.
159 */
160 // used by OCSPChecker
161 OCSPResponse(byte[] bytes, PKIXParameters params,
162 X509Certificate responderCert)
163 throws IOException, CertPathValidatorException {
164
165 try {
166 int responseStatus;
167 ObjectIdentifier responseType;
168 int version;
169 CertificateIssuerName responderName = null;
170 Date producedAtDate;
171 AlgorithmId sigAlgId;
172 byte[] ocspNonce;
173
174 // OCSPResponse
175 if (dump) {
176 HexDumpEncoder hexEnc = new HexDumpEncoder();
177 System.out.println("OCSPResponse bytes are...");
178 System.out.println(hexEnc.encode(bytes));
179 }
180 DerValue der = new DerValue(bytes);
181 if (der.tag != DerValue.tag_Sequence) {
182 throw new IOException("Bad encoding in OCSP response: " +
183 "expected ASN.1 SEQUENCE tag.");
184 }
185 DerInputStream derIn = der.getData();
186
187 // responseStatus
188 responseStatus = derIn.getEnumerated();
189 if (DEBUG != null) {
190 DEBUG.println("OCSP response: " +
191 responseToText(responseStatus));
192 }
193 if (responseStatus != OCSP_RESPONSE_OK) {
194 throw new CertPathValidatorException(
195 "OCSP Response Failure: " +
196 responseToText(responseStatus));
197 }
198
199 // responseBytes
200 der = derIn.getDerValue();
201 if (! der.isContextSpecific((byte)0)) {
202 throw new IOException("Bad encoding in responseBytes element " +
203 "of OCSP response: expected ASN.1 context specific tag 0.");
204 };
205 DerValue tmp = der.data.getDerValue();
206 if (tmp.tag != DerValue.tag_Sequence) {
207 throw new IOException("Bad encoding in responseBytes element " +
208 "of OCSP response: expected ASN.1 SEQUENCE tag.");
209 }
210
211 // responseType
212 derIn = tmp.data;
213 responseType = derIn.getOID();
214 if (responseType.equals(OCSP_BASIC_RESPONSE_OID)) {
215 if (DEBUG != null) {
216 DEBUG.println("OCSP response type: basic");
217 }
218 } else {
219 if (DEBUG != null) {
220 DEBUG.println("OCSP response type: " + responseType);
221 }
222 throw new IOException("Unsupported OCSP response type: " +
223 responseType);
224 }
225
226 // BasicOCSPResponse
227 DerInputStream basicOCSPResponse =
228 new DerInputStream(derIn.getOctetString());
229
230 DerValue[] seqTmp = basicOCSPResponse.getSequence(2);
231 DerValue responseData = seqTmp[0];
232
233 // Need the DER encoded ResponseData to verify the signature later
234 byte[] responseDataDer = seqTmp[0].toByteArray();
235
236 // tbsResponseData
237 if (responseData.tag != DerValue.tag_Sequence) {
238 throw new IOException("Bad encoding in tbsResponseData " +
239 " element of OCSP response: expected ASN.1 SEQUENCE tag.");
240 }
241 DerInputStream seqDerIn = responseData.data;
242 DerValue seq = seqDerIn.getDerValue();
243
244 // version
245 if (seq.isContextSpecific((byte)0)) {
246 // seq[0] is version
247 if (seq.isConstructed() && seq.isContextSpecific()) {
248 //System.out.println ("version is available");
249 seq = seq.data.getDerValue();
250 version = seq.getInteger();
251 if (seq.data.available() != 0) {
252 throw new IOException("Bad encoding in version " +
253 " element of OCSP response: bad format");
254 }
255 seq = seqDerIn.getDerValue();
256 }
257 }
258
259 // responderID
260 short tag = (byte)(seq.tag & 0x1f);
261 if (tag == NAME_TAG) {
262 responderName = new CertificateIssuerName(seq.getData());
263 if (DEBUG != null) {
264 DEBUG.println("OCSP Responder name: " + responderName);
265 }
266 } else if (tag == KEY_TAG) {
267 // Ignore, for now
268 } else {
269 throw new IOException("Bad encoding in responderID element " +
270 "of OCSP response: expected ASN.1 context specific tag 0 " +
271 "or 1");
272 }
273
274 // producedAt
275 seq = seqDerIn.getDerValue();
276 producedAtDate = seq.getGeneralizedTime();
277
278 // responses
279 DerValue[] singleResponseDer = seqDerIn.getSequence(1);
280 // Examine only the first response
281 singleResponse = new SingleResponse(singleResponseDer[0]);
282
283 // responseExtensions
284 if (seqDerIn.available() > 0) {
285 seq = seqDerIn.getDerValue();
286 if (seq.isContextSpecific((byte)1)) {
287 DerValue[] responseExtDer = seq.data.getSequence(3);
288 Extension[] responseExtension =
289 new Extension[responseExtDer.length];
290 for (int i = 0; i < responseExtDer.length; i++) {
291 responseExtension[i] = new Extension(responseExtDer[i]);
292 if (DEBUG != null) {
293 DEBUG.println("OCSP extension: " +
294 responseExtension[i]);
295 }
296 if ((responseExtension[i].getExtensionId()).equals(
297 OCSP_NONCE_EXTENSION_OID)) {
298 ocspNonce =
299 responseExtension[i].getExtensionValue();
300
301 } else if (responseExtension[i].isCritical()) {
302 throw new IOException(
303 "Unsupported OCSP critical extension: " +
304 responseExtension[i].getExtensionId());
305 }
306 }
307 }
308 }
309
310 // signatureAlgorithmId
311 sigAlgId = AlgorithmId.parse(seqTmp[1]);
312
313 // signature
314 byte[] signature = seqTmp[2].getBitString();
315 X509CertImpl[] x509Certs = null;
316
317 // if seq[3] is available , then it is a sequence of certificates
318 if (seqTmp.length > 3) {
319 // certs are available
320 DerValue seqCert = seqTmp[3];
321 if (! seqCert.isContextSpecific((byte)0)) {
322 throw new IOException("Bad encoding in certs element " +
323 "of OCSP response: expected ASN.1 context specific tag 0.");
324 }
325 DerValue[] certs = (seqCert.getData()).getSequence(3);
326 x509Certs = new X509CertImpl[certs.length];
327 for (int i = 0; i < certs.length; i++) {
328 x509Certs[i] = new X509CertImpl(certs[i].toByteArray());
329 }
330 }
331
332 // Check whether the cert returned by the responder is trusted
333 if (x509Certs != null && x509Certs[0] != null) {
334 X509Certificate cert = x509Certs[0];
335
336 // First check if the cert matches the responder cert which
337 // was set locally.
338 if (cert.equals(responderCert)) {
339 // cert is trusted, now verify the signed response
340
341 // Next check if the cert was issued by the responder cert
342 // which was set locally.
343 } else if (cert.getIssuerDN().equals(
344 responderCert.getSubjectDN())) {
345
346 // Check for the OCSPSigning key purpose
347 List<String> keyPurposes = cert.getExtendedKeyUsage();
348 if (keyPurposes == null ||
349 !keyPurposes.contains(KP_OCSP_SIGNING_OID)) {
350 if (DEBUG != null) {
351 DEBUG.println("Responder's certificate is not " +
352 "valid for signing OCSP responses.");
353 }
354 throw new CertPathValidatorException(
355 "Responder's certificate not valid for signing " +
356 "OCSP responses");
357 }
358
359 // verify the signature
360 try {
361 cert.verify(responderCert.getPublicKey());
362 responderCert = cert;
363 // cert is trusted, now verify the signed response
364
365 } catch (GeneralSecurityException e) {
366 responderCert = null;
367 }
368 }
369 }
370
371 // Confirm that the signed response was generated using the public
372 // key from the trusted responder cert
373 if (responderCert != null) {
374
375 if (! verifyResponse(responseDataDer, responderCert,
376 sigAlgId, signature, params)) {
377 if (DEBUG != null) {
378 DEBUG.println("Error verifying OCSP Responder's " +
379 "signature");
380 }
381 throw new CertPathValidatorException(
382 "Error verifying OCSP Responder's signature");
383 }
384 } else {
385 // Need responder's cert in order to verify the signature
386 if (DEBUG != null) {
387 DEBUG.println("Unable to verify OCSP Responder's " +
388 "signature");
389 }
390 throw new CertPathValidatorException(
391 "Unable to verify OCSP Responder's signature");
392 }
393 } catch (CertPathValidatorException cpve) {
394 throw cpve;
395 } catch (Exception e) {
396 throw new CertPathValidatorException(e);
397 }
398 }
399
400 /*
401 * Verify the signature of the OCSP response.
402 * The responder's cert is implicitly trusted.
403 */
404 private boolean verifyResponse(byte[] responseData, X509Certificate cert,
405 AlgorithmId sigAlgId, byte[] signBytes, PKIXParameters params)
406 throws SignatureException {
407
408 try {
409
410 Signature respSignature = Signature.getInstance(sigAlgId.getName());
411 respSignature.initVerify(cert);
412 respSignature.update(responseData);
413
414 if (respSignature.verify(signBytes)) {
415 if (DEBUG != null) {
416 DEBUG.println("Verified signature of OCSP Responder");
417 }
418 return true;
419
420 } else {
421 if (DEBUG != null) {
422 DEBUG.println(
423 "Error verifying signature of OCSP Responder");
424 }
425 return false;
426 }
427 } catch (InvalidKeyException ike) {
428 throw new SignatureException(ike);
429
430 } catch (NoSuchAlgorithmException nsae) {
431 throw new SignatureException(nsae);
432 }
433 }
434
435 /*
436 * Return the revocation status code for a given certificate.
437 */
438 // used by OCSPChecker
439 int getCertStatus(SerialNumber sn) {
440 // ignore serial number for now; if we support multiple
441 // requests/responses then it will be used
442 return singleResponse.getStatus();
443 }
444
445 // used by OCSPChecker
446 CertId getCertId() {
447 return singleResponse.getCertId();
448 }
449
450 Date getRevocationTime() {
451 return singleResponse.getRevocationTime();
452 }
453
454 CRLReason getRevocationReason() {
455 return singleResponse.getRevocationReason();
456 }
457
458 Map<String, java.security.cert.Extension> getSingleExtensions() {
459 return singleResponse.getSingleExtensions();
460 }
461
462 /*
463 * Map an OCSP response status code to a string.
464 */
465 static private String responseToText(int status) {
466 switch (status) {
467 case 0:
468 return "Successful";
469 case 1:
470 return "Malformed request";
471 case 2:
472 return "Internal error";
473 case 3:
474 return "Try again later";
475 case 4:
476 return "Unused status code";
477 case 5:
478 return "Request must be signed";
479 case 6:
480 return "Request is unauthorized";
481 default:
482 return ("Unknown status code: " + status);
483 }
484 }
485
486 /*
487 * Map a certificate's revocation status code to a string.
488 */
489 // used by OCSPChecker
490 static String certStatusToText(int certStatus) {
491 switch (certStatus) {
492 case 0:
493 return "Good";
494 case 1:
495 return "Revoked";
496 case 2:
497 return "Unknown";
498 default:
499 return ("Unknown certificate status code: " + certStatus);
500 }
501 }
502
503 /*
504 * A class representing a single OCSP response.
505 */
506 private class SingleResponse {
507 private CertId certId;
508 private int certStatus;
509 private Date thisUpdate;
510 private Date nextUpdate;
511 private Date revocationTime;
512 private CRLReason revocationReason = CRLReason.UNSPECIFIED;
513 private HashMap<String, java.security.cert.Extension> singleExtensions;
514
515 private SingleResponse(DerValue der) throws IOException {
516 if (der.tag != DerValue.tag_Sequence) {
517 throw new IOException("Bad ASN.1 encoding in SingleResponse");
518 }
519 DerInputStream tmp = der.data;
520
521 certId = new CertId(tmp.getDerValue().data);
522 DerValue derVal = tmp.getDerValue();
523 short tag = (byte)(derVal.tag & 0x1f);
524 if (tag == CERT_STATUS_GOOD) {
525 certStatus = CERT_STATUS_GOOD;
526 } else if (tag == CERT_STATUS_REVOKED) {
527 certStatus = CERT_STATUS_REVOKED;
528 revocationTime = derVal.data.getGeneralizedTime();
529 if (derVal.data.available() != 0) {
530 int reason = derVal.getEnumerated();
531 // if reason out-of-range just leave as UNSPECIFIED
532 if (reason >= 0 && reason < values.length) {
533 revocationReason = values[reason];
534 }
535 }
536 // RevokedInfo
537 if (DEBUG != null) {
538 DEBUG.println("Revocation time: " + revocationTime);
539 DEBUG.println("Revocation reason: " + revocationReason);
540 }
541
542 } else if (tag == CERT_STATUS_UNKNOWN) {
543 certStatus = CERT_STATUS_UNKNOWN;
544
545 } else {
546 throw new IOException("Invalid certificate status");
547 }
548
549 thisUpdate = tmp.getGeneralizedTime();
550
551 if (tmp.available() == 0) {
552 // we are done
553 } else {
554 derVal = tmp.getDerValue();
555 tag = (byte)(derVal.tag & 0x1f);
556 if (tag == 0) {
557 // next update
558 nextUpdate = derVal.data.getGeneralizedTime();
559
560 if (tmp.available() == 0) {
561 // we are done
562 } else {
563 derVal = tmp.getDerValue();
564 tag = (byte)(derVal.tag & 0x1f);
565 }
566 }
567 }
568 // singleExtensions
569 if (tmp.available() > 0) {
570 derVal = tmp.getDerValue();
571 if (derVal.isContextSpecific((byte)1)) {
572 DerValue[] singleExtDer = derVal.data.getSequence(3);
573 singleExtensions =
574 new HashMap<String, java.security.cert.Extension>
575 (singleExtDer.length);
576 for (int i = 0; i < singleExtDer.length; i++) {
577 Extension ext = new Extension(singleExtDer[i]);
578 singleExtensions.put(ext.getId(), ext);
579 if (DEBUG != null) {
580 DEBUG.println("OCSP single extension: " + ext);
581 }
582 }
583 }
584 }
585
586 Date now = new Date();
587 if (DEBUG != null) {
588 String until = "";
589 if (nextUpdate != null) {
590 until = " until " + nextUpdate;
591 }
592 DEBUG.println("Response's validity interval is from " +
593 thisUpdate + until);
594 }
595 // Check that the test date is within the validity interval
596 if ((thisUpdate != null && now.before(thisUpdate)) ||
597 (nextUpdate != null && now.after(nextUpdate))) {
598
599 if (DEBUG != null) {
600 DEBUG.println("Response is unreliable: its validity " +
601 "interval is out-of-date");
602 }
603 throw new IOException("Response is unreliable: its validity " +
604 "interval is out-of-date");
605 }
606 }
607
608 /*
609 * Return the certificate's revocation status code
610 */
611 private int getStatus() {
612 return certStatus;
613 }
614
615 private CertId getCertId() {
616 return certId;
617 }
618
619 private Date getRevocationTime() {
620 return revocationTime;
621 }
622
623 private CRLReason getRevocationReason() {
624 return revocationReason;
625 }
626
627 private Map<String, java.security.cert.Extension> getSingleExtensions() {
628 return singleExtensions;
629 }
630
631 /**
632 * Construct a string representation of a single OCSP response.
633 */
634 public String toString() {
635 StringBuilder sb = new StringBuilder();
636 sb.append("SingleResponse: \n");
637 sb.append(certId);
638 sb.append("\nCertStatus: "+ certStatusToText(getCertStatus(null)) +
639 "\n");
640 if (certStatus == CERT_STATUS_REVOKED) {
641 sb.append("revocationTime is " + revocationTime + "\n");
642 sb.append("revocationReason is " + revocationReason + "\n");
643 }
644 sb.append("thisUpdate is " + thisUpdate + "\n");
645 if (nextUpdate != null) {
646 sb.append("nextUpdate is " + nextUpdate + "\n");
647 }
648 return sb.toString();
649 }
650 }
651}