blob: 37c05c982173d6a8f6805361e6d315b6eee1574a [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-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.x509;
27
28import java.io.InputStream;
29import java.io.IOException;
30import java.security.cert.CRLException;
31import java.security.cert.CRLReason;
32import java.security.cert.CertificateException;
33import java.security.cert.X509CRLEntry;
34import java.math.BigInteger;
35import java.util.Collection;
36import java.util.Date;
37import java.util.Enumeration;
38import java.util.HashMap;
39import java.util.Map;
40import java.util.Set;
41import java.util.HashSet;
42
43import javax.security.auth.x500.X500Principal;
44
45import sun.security.util.*;
46import sun.misc.HexDumpEncoder;
47
48/**
49 * <p>Abstract class for a revoked certificate in a CRL.
50 * This class is for each entry in the <code>revokedCertificates</code>,
51 * so it deals with the inner <em>SEQUENCE</em>.
52 * The ASN.1 definition for this is:
53 * <pre>
54 * revokedCertificates SEQUENCE OF SEQUENCE {
55 * userCertificate CertificateSerialNumber,
56 * revocationDate ChoiceOfTime,
57 * crlEntryExtensions Extensions OPTIONAL
58 * -- if present, must be v2
59 * } OPTIONAL
60 *
61 * CertificateSerialNumber ::= INTEGER
62 *
63 * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
64 *
65 * Extension ::= SEQUENCE {
66 * extnId OBJECT IDENTIFIER,
67 * critical BOOLEAN DEFAULT FALSE,
68 * extnValue OCTET STRING
69 * -- contains a DER encoding of a value
70 * -- of the type registered for use with
71 * -- the extnId object identifier value
72 * }
73 * </pre>
74 *
75 * @author Hemma Prafullchandra
76 */
77
78public class X509CRLEntryImpl extends X509CRLEntry {
79
80 private SerialNumber serialNumber = null;
81 private Date revocationDate = null;
82 private CRLExtensions extensions = null;
83 private byte[] revokedCert = null;
84 private X500Principal certIssuer;
85
86 private final static boolean isExplicit = false;
87 private static final long YR_2050 = 2524636800000L;
88
89 /**
90 * Constructs a revoked certificate entry using the given
91 * serial number and revocation date.
92 *
93 * @param num the serial number of the revoked certificate.
94 * @param date the Date on which revocation took place.
95 */
96 public X509CRLEntryImpl(BigInteger num, Date date) {
97 this.serialNumber = new SerialNumber(num);
98 this.revocationDate = date;
99 }
100
101 /**
102 * Constructs a revoked certificate entry using the given
103 * serial number, revocation date and the entry
104 * extensions.
105 *
106 * @param num the serial number of the revoked certificate.
107 * @param date the Date on which revocation took place.
108 * @param crlEntryExts the extensions for this entry.
109 */
110 public X509CRLEntryImpl(BigInteger num, Date date,
111 CRLExtensions crlEntryExts) {
112 this.serialNumber = new SerialNumber(num);
113 this.revocationDate = date;
114 this.extensions = crlEntryExts;
115 }
116
117 /**
118 * Unmarshals a revoked certificate from its encoded form.
119 *
120 * @param revokedCert the encoded bytes.
121 * @exception CRLException on parsing errors.
122 */
123 public X509CRLEntryImpl(byte[] revokedCert) throws CRLException {
124 try {
125 parse(new DerValue(revokedCert));
126 } catch (IOException e) {
127 this.revokedCert = null;
128 throw new CRLException("Parsing error: " + e.toString());
129 }
130 }
131
132 /**
133 * Unmarshals a revoked certificate from its encoded form.
134 *
135 * @param derVal the DER value containing the revoked certificate.
136 * @exception CRLException on parsing errors.
137 */
138 public X509CRLEntryImpl(DerValue derValue) throws CRLException {
139 try {
140 parse(derValue);
141 } catch (IOException e) {
142 revokedCert = null;
143 throw new CRLException("Parsing error: " + e.toString());
144 }
145 }
146
147 /**
148 * Returns true if this revoked certificate entry has
149 * extensions, otherwise false.
150 *
151 * @return true if this CRL entry has extensions, otherwise
152 * false.
153 */
154 public boolean hasExtensions() {
155 return (extensions != null);
156 }
157
158 /**
159 * Encodes the revoked certificate to an output stream.
160 *
161 * @param outStrm an output stream to which the encoded revoked
162 * certificate is written.
163 * @exception CRLException on encoding errors.
164 */
165 public void encode(DerOutputStream outStrm) throws CRLException {
166 try {
167 if (revokedCert == null) {
168 DerOutputStream tmp = new DerOutputStream();
169 // sequence { serialNumber, revocationDate, extensions }
170 serialNumber.encode(tmp);
171
172 if (revocationDate.getTime() < YR_2050) {
173 tmp.putUTCTime(revocationDate);
174 } else {
175 tmp.putGeneralizedTime(revocationDate);
176 }
177
178 if (extensions != null)
179 extensions.encode(tmp, isExplicit);
180
181 DerOutputStream seq = new DerOutputStream();
182 seq.write(DerValue.tag_Sequence, tmp);
183
184 revokedCert = seq.toByteArray();
185 }
186 outStrm.write(revokedCert);
187 } catch (IOException e) {
188 throw new CRLException("Encoding error: " + e.toString());
189 }
190 }
191
192 /**
193 * Returns the ASN.1 DER-encoded form of this CRL Entry,
194 * which corresponds to the inner SEQUENCE.
195 *
196 * @exception CRLException if an encoding error occurs.
197 */
198 public byte[] getEncoded() throws CRLException {
199 if (revokedCert == null)
200 this.encode(new DerOutputStream());
201 return revokedCert.clone();
202 }
203
204 @Override
205 public X500Principal getCertificateIssuer() {
206 return certIssuer;
207 }
208
209 void setCertificateIssuer(X500Principal crlIssuer, X500Principal certIssuer) {
210 if (crlIssuer.equals(certIssuer)) {
211 this.certIssuer = null;
212 } else {
213 this.certIssuer = certIssuer;
214 }
215 }
216
217 /**
218 * Gets the serial number from this X509CRLEntry,
219 * i.e. the <em>userCertificate</em>.
220 *
221 * @return the serial number.
222 */
223 public BigInteger getSerialNumber() {
224 return serialNumber.getNumber();
225 }
226
227 /**
228 * Gets the revocation date from this X509CRLEntry,
229 * the <em>revocationDate</em>.
230 *
231 * @return the revocation date.
232 */
233 public Date getRevocationDate() {
234 return new Date(revocationDate.getTime());
235 }
236
237 /**
238 * This method is the overridden implementation of the getRevocationReason
239 * method in X509CRLEntry. It is better performance-wise since it returns
240 * cached values.
241 */
242 @Override
243 public CRLReason getRevocationReason() {
244 Extension ext = getExtension(PKIXExtensions.ReasonCode_Id);
245 if (ext == null) {
246 return null;
247 }
248 CRLReasonCodeExtension rcExt = (CRLReasonCodeExtension) ext;
249 return rcExt.getReasonCode();
250 }
251
252 /**
253 * This static method is the default implementation of the
254 * getRevocationReason method in X509CRLEntry.
255 */
256 public static CRLReason getRevocationReason(X509CRLEntry crlEntry) {
257 try {
258 byte[] ext = crlEntry.getExtensionValue("2.5.29.21");
259 if (ext == null) {
260 return null;
261 }
262 DerValue val = new DerValue(ext);
263 byte[] data = val.getOctetString();
264
265 CRLReasonCodeExtension rcExt =
266 new CRLReasonCodeExtension(Boolean.FALSE, data);
267 return rcExt.getReasonCode();
268 } catch (IOException ioe) {
269 return null;
270 }
271 }
272
273 /**
274 * get Reason Code from CRL entry.
275 *
276 * @returns Integer or null, if no such extension
277 * @throws IOException on error
278 */
279 public Integer getReasonCode() throws IOException {
280 Object obj = getExtension(PKIXExtensions.ReasonCode_Id);
281 if (obj == null)
282 return null;
283 CRLReasonCodeExtension reasonCode = (CRLReasonCodeExtension)obj;
284 return (Integer)(reasonCode.get(reasonCode.REASON));
285 }
286
287 /**
288 * Returns a printable string of this revoked certificate.
289 *
290 * @return value of this revoked certificate in a printable form.
291 */
292 @Override
293 public String toString() {
294 StringBuilder sb = new StringBuilder();
295
296 sb.append(serialNumber.toString());
297 sb.append(" On: " + revocationDate.toString());
298 if (certIssuer != null) {
299 sb.append("\n Certificate issuer: " + certIssuer);
300 }
301 if (extensions != null) {
302 Collection allEntryExts = extensions.getAllExtensions();
303 Object[] objs = allEntryExts.toArray();
304
305 sb.append("\n CRL Entry Extensions: " + objs.length);
306 for (int i = 0; i < objs.length; i++) {
307 sb.append("\n [" + (i+1) + "]: ");
308 Extension ext = (Extension)objs[i];
309 try {
310 if (OIDMap.getClass(ext.getExtensionId()) == null) {
311 sb.append(ext.toString());
312 byte[] extValue = ext.getExtensionValue();
313 if (extValue != null) {
314 DerOutputStream out = new DerOutputStream();
315 out.putOctetString(extValue);
316 extValue = out.toByteArray();
317 HexDumpEncoder enc = new HexDumpEncoder();
318 sb.append("Extension unknown: "
319 + "DER encoded OCTET string =\n"
320 + enc.encodeBuffer(extValue) + "\n");
321 }
322 } else
323 sb.append(ext.toString()); //sub-class exists
324 } catch (Exception e) {
325 sb.append(", Error parsing this extension");
326 }
327 }
328 }
329 sb.append("\n");
330 return sb.toString();
331 }
332
333 /**
334 * Return true if a critical extension is found that is
335 * not supported, otherwise return false.
336 */
337 public boolean hasUnsupportedCriticalExtension() {
338 if (extensions == null)
339 return false;
340 return extensions.hasUnsupportedCriticalExtension();
341 }
342
343 /**
344 * Gets a Set of the extension(s) marked CRITICAL in this
345 * X509CRLEntry. In the returned set, each extension is
346 * represented by its OID string.
347 *
348 * @return a set of the extension oid strings in the
349 * Object that are marked critical.
350 */
351 public Set<String> getCriticalExtensionOIDs() {
352 if (extensions == null) {
353 return null;
354 }
355 Set<String> extSet = new HashSet<String>();
356 for (Extension ex : extensions.getAllExtensions()) {
357 if (ex.isCritical()) {
358 extSet.add(ex.getExtensionId().toString());
359 }
360 }
361 return extSet;
362 }
363
364 /**
365 * Gets a Set of the extension(s) marked NON-CRITICAL in this
366 * X509CRLEntry. In the returned set, each extension is
367 * represented by its OID string.
368 *
369 * @return a set of the extension oid strings in the
370 * Object that are marked critical.
371 */
372 public Set<String> getNonCriticalExtensionOIDs() {
373 if (extensions == null) {
374 return null;
375 }
376 Set<String> extSet = new HashSet<String>();
377 for (Extension ex : extensions.getAllExtensions()) {
378 if (!ex.isCritical()) {
379 extSet.add(ex.getExtensionId().toString());
380 }
381 }
382 return extSet;
383 }
384
385 /**
386 * Gets the DER encoded OCTET string for the extension value
387 * (<em>extnValue</em>) identified by the passed in oid String.
388 * The <code>oid</code> string is
389 * represented by a set of positive whole number separated
390 * by ".", that means,<br>
391 * &lt;positive whole number&gt;.&lt;positive whole number&gt;.&lt;positive
392 * whole number&gt;.&lt;...&gt;
393 *
394 * @param oid the Object Identifier value for the extension.
395 * @return the DER encoded octet string of the extension value.
396 */
397 public byte[] getExtensionValue(String oid) {
398 if (extensions == null)
399 return null;
400 try {
401 String extAlias = OIDMap.getName(new ObjectIdentifier(oid));
402 Extension crlExt = null;
403
404 if (extAlias == null) { // may be unknown
405 ObjectIdentifier findOID = new ObjectIdentifier(oid);
406 Extension ex = null;
407 ObjectIdentifier inCertOID;
408 for (Enumeration<Extension> e = extensions.getElements();
409 e.hasMoreElements();) {
410 ex = e.nextElement();
411 inCertOID = ex.getExtensionId();
412 if (inCertOID.equals(findOID)) {
413 crlExt = ex;
414 break;
415 }
416 }
417 } else
418 crlExt = extensions.get(extAlias);
419 if (crlExt == null)
420 return null;
421 byte[] extData = crlExt.getExtensionValue();
422 if (extData == null)
423 return null;
424
425 DerOutputStream out = new DerOutputStream();
426 out.putOctetString(extData);
427 return out.toByteArray();
428 } catch (Exception e) {
429 return null;
430 }
431 }
432
433 /**
434 * get an extension
435 *
436 * @param oid ObjectIdentifier of extension desired
437 * @returns Extension of type <extension> or null, if not found
438 */
439 public Extension getExtension(ObjectIdentifier oid) {
440 if (extensions == null)
441 return null;
442
443 // following returns null if no such OID in map
444 //XXX consider cloning this
445 return extensions.get(OIDMap.getName(oid));
446 }
447
448 private void parse(DerValue derVal)
449 throws CRLException, IOException {
450
451 if (derVal.tag != DerValue.tag_Sequence) {
452 throw new CRLException("Invalid encoded RevokedCertificate, " +
453 "starting sequence tag missing.");
454 }
455 if (derVal.data.available() == 0)
456 throw new CRLException("No data encoded for RevokedCertificates");
457
458 revokedCert = derVal.toByteArray();
459 // serial number
460 DerInputStream in = derVal.toDerInputStream();
461 DerValue val = in.getDerValue();
462 this.serialNumber = new SerialNumber(val);
463
464 // revocationDate
465 int nextByte = derVal.data.peekByte();
466 if ((byte)nextByte == DerValue.tag_UtcTime) {
467 this.revocationDate = derVal.data.getUTCTime();
468 } else if ((byte)nextByte == DerValue.tag_GeneralizedTime) {
469 this.revocationDate = derVal.data.getGeneralizedTime();
470 } else
471 throw new CRLException("Invalid encoding for revocation date");
472
473 if (derVal.data.available() == 0)
474 return; // no extensions
475
476 // crlEntryExtensions
477 this.extensions = new CRLExtensions(derVal.toDerInputStream());
478 }
479
480 /**
481 * Utility method to convert an arbitrary instance of X509CRLEntry
482 * to a X509CRLEntryImpl. Does a cast if possible, otherwise reparses
483 * the encoding.
484 */
485 public static X509CRLEntryImpl toImpl(X509CRLEntry entry)
486 throws CRLException {
487 if (entry instanceof X509CRLEntryImpl) {
488 return (X509CRLEntryImpl)entry;
489 } else {
490 return new X509CRLEntryImpl(entry.getEncoded());
491 }
492 }
493
494 /**
495 * Returns the CertificateIssuerExtension
496 *
497 * @return the CertificateIssuerExtension, or null if it does not exist
498 */
499 CertificateIssuerExtension getCertificateIssuerExtension() {
500 return (CertificateIssuerExtension)
501 getExtension(PKIXExtensions.CertificateIssuer_Id);
502 }
503
504 public Map<String, java.security.cert.Extension> getExtensions() {
505 Collection<Extension> exts = extensions.getAllExtensions();
506 HashMap<String, java.security.cert.Extension> map =
507 new HashMap<String, java.security.cert.Extension>(exts.size());
508 for (Extension ext : exts) {
509 map.put(ext.getId(), ext);
510 }
511 return map;
512 }
513}