1 /* 2 * Copyright (c) 1997, 2009, 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.x509; 27 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.lang.reflect.Constructor; 31 import java.lang.reflect.Field; 32 import java.lang.reflect.InvocationTargetException; 33 import java.security.cert.CertificateException; 34 import java.util.*; 35 36 import sun.misc.HexDumpEncoder; 37 38 import sun.security.util.*; 39 40 /** 41 * This class defines the Extensions attribute for the Certificate. 42 * 43 * @author Amit Kapoor 44 * @author Hemma Prafullchandra 45 * @see CertAttrSet 46 */ 47 public class CertificateExtensions implements CertAttrSet<Extension> { 48 /** 49 * Identifier for this attribute, to be used with the 50 * get, set, delete methods of Certificate, x509 type. 51 */ 52 public static final String IDENT = "x509.info.extensions"; 53 /** 54 * name 55 */ 56 public static final String NAME = "extensions"; 57 58 private static final Debug debug = Debug.getInstance("x509"); 59 60 private Map<String,Extension> map = Collections.synchronizedMap( 61 new TreeMap<String,Extension>()); 62 private boolean unsupportedCritExt = false; 63 64 private Map<String,Extension> unparseableExtensions; 65 66 /** 67 * Default constructor. 68 */ 69 public CertificateExtensions() { } 70 71 /** 72 * Create the object, decoding the values from the passed DER stream. 73 * 74 * @param in the DerInputStream to read the Extension from. 75 * @exception IOException on decoding errors. 76 */ 77 public CertificateExtensions(DerInputStream in) throws IOException { 78 init(in); 79 } 80 81 // helper routine 82 private void init(DerInputStream in) throws IOException { 83 84 DerValue[] exts = in.getSequence(5); 85 86 for (int i = 0; i < exts.length; i++) { 87 Extension ext = new Extension(exts[i]); 88 parseExtension(ext); 89 } 90 } 91 92 private static Class[] PARAMS = {Boolean.class, Object.class}; 93 94 // Parse the encoded extension 95 private void parseExtension(Extension ext) throws IOException { 96 try { 97 Class extClass = OIDMap.getClass(ext.getExtensionId()); 98 if (extClass == null) { // Unsupported extension 99 if (ext.isCritical()) { 100 unsupportedCritExt = true; 101 } 102 if (map.put(ext.getExtensionId().toString(), ext) == null) { 103 return; 104 } else { 105 throw new IOException("Duplicate extensions not allowed"); 106 } 107 } 108 Constructor cons = ((Class<?>)extClass).getConstructor(PARAMS); 109 110 Object[] passed = new Object[] {Boolean.valueOf(ext.isCritical()), 111 ext.getExtensionValue()}; 112 CertAttrSet certExt = (CertAttrSet)cons.newInstance(passed); 113 if (map.put(certExt.getName(), (Extension)certExt) != null) { 114 throw new IOException("Duplicate extensions not allowed"); 115 } 116 } catch (InvocationTargetException invk) { 117 Throwable e = invk.getTargetException(); 118 if (ext.isCritical() == false) { 119 // ignore errors parsing non-critical extensions 120 if (unparseableExtensions == null) { 121 unparseableExtensions = new TreeMap<String,Extension>(); 122 } 123 unparseableExtensions.put(ext.getExtensionId().toString(), 124 new UnparseableExtension(ext, e)); 125 if (debug != null) { 126 debug.println("Error parsing extension: " + ext); 127 e.printStackTrace(); 128 HexDumpEncoder h = new HexDumpEncoder(); 129 System.err.println(h.encodeBuffer(ext.getExtensionValue())); 130 } 131 return; 132 } 133 if (e instanceof IOException) { 134 throw (IOException)e; 135 } else { 136 throw (IOException)new IOException(e.toString()).initCause(e); 137 } 138 } catch (IOException e) { 139 throw e; 140 } catch (Exception e) { 141 throw (IOException)new IOException(e.toString()).initCause(e); 142 } 143 } 144 145 /** 146 * Encode the extensions in DER form to the stream, setting 147 * the context specific tag as needed in the X.509 v3 certificate. 148 * 149 * @param out the DerOutputStream to marshal the contents to. 150 * @exception CertificateException on encoding errors. 151 * @exception IOException on errors. 152 */ 153 public void encode(OutputStream out) 154 throws CertificateException, IOException { 155 encode(out, false); 156 } 157 158 /** 159 * Encode the extensions in DER form to the stream. 160 * 161 * @param out the DerOutputStream to marshal the contents to. 162 * @param isCertReq if true then no context specific tag is added. 163 * @exception CertificateException on encoding errors. 164 * @exception IOException on errors. 165 */ 166 public void encode(OutputStream out, boolean isCertReq) 167 throws CertificateException, IOException { 168 DerOutputStream extOut = new DerOutputStream(); 169 Collection<Extension> allExts = map.values(); 170 Object[] objs = allExts.toArray(); 171 172 for (int i = 0; i < objs.length; i++) { 173 if (objs[i] instanceof CertAttrSet) 174 ((CertAttrSet)objs[i]).encode(extOut); 175 else if (objs[i] instanceof Extension) 176 ((Extension)objs[i]).encode(extOut); 177 else 178 throw new CertificateException("Illegal extension object"); 179 } 180 181 DerOutputStream seq = new DerOutputStream(); 182 seq.write(DerValue.tag_Sequence, extOut); 183 184 DerOutputStream tmp; 185 if (!isCertReq) { // certificate 186 tmp = new DerOutputStream(); 187 tmp.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)3), 188 seq); 189 } else 190 tmp = seq; // pkcs#10 certificateRequest 191 192 out.write(tmp.toByteArray()); 193 } 194 195 /** 196 * Set the attribute value. 197 * @param name the extension name used in the cache. 198 * @param obj the object to set. 199 * @exception IOException if the object could not be cached. 200 */ 201 public void set(String name, Object obj) throws IOException { 202 if (obj instanceof Extension) { 203 map.put(name, (Extension)obj); 204 } else { 205 throw new IOException("Unknown extension type."); 206 } 207 } 208 209 /** 210 * Get the attribute value. 211 * @param name the extension name used in the lookup. 212 * @exception IOException if named extension is not found. 213 */ 214 public Object get(String name) throws IOException { 215 Object obj = map.get(name); 216 if (obj == null) { 217 throw new IOException("No extension found with name " + name); 218 } 219 return (obj); 220 } 221 222 // Similar to get(String), but throw no exception, might return null. 223 // Used in X509CertImpl::getExtension(OID). 224 Extension getExtension(String name) { 225 return map.get(name); 226 } 227 228 /** 229 * Delete the attribute value. 230 * @param name the extension name used in the lookup. 231 * @exception IOException if named extension is not found. 232 */ 233 public void delete(String name) throws IOException { 234 Object obj = map.get(name); 235 if (obj == null) { 236 throw new IOException("No extension found with name " + name); 237 } 238 map.remove(name); 239 } 240 241 public String getNameByOid(ObjectIdentifier oid) throws IOException { 242 for (String name: map.keySet()) { 243 if (map.get(name).getExtensionId().equals(oid)) { 244 return name; 245 } 246 } 247 return null; 248 } 249 250 /** 251 * Return an enumeration of names of attributes existing within this 252 * attribute. 253 */ 254 public Enumeration<Extension> getElements() { 255 return Collections.enumeration(map.values()); 256 } 257 258 /** 259 * Return a collection view of the extensions. 260 * @return a collection view of the extensions in this Certificate. 261 */ 262 public Collection<Extension> getAllExtensions() { 263 return map.values(); 264 } 265 266 public Map<String,Extension> getUnparseableExtensions() { 267 if (unparseableExtensions == null) { 268 return Collections.emptyMap(); 269 } else { 270 return unparseableExtensions; 271 } 272 } 273 274 /** 275 * Return the name of this attribute. 276 */ 277 public String getName() { 278 return NAME; 279 } 280 281 /** 282 * Return true if a critical extension is found that is 283 * not supported, otherwise return false. 284 */ 285 public boolean hasUnsupportedCriticalExtension() { 286 return unsupportedCritExt; 287 } 288 289 /** 290 * Compares this CertificateExtensions for equality with the specified 291 * object. If the <code>other</code> object is an 292 * <code>instanceof</code> <code>CertificateExtensions</code>, then 293 * all the entries are compared with the entries from this. 294 * 295 * @param other the object to test for equality with this 296 * CertificateExtensions. 297 * @return true iff all the entries match that of the Other, 298 * false otherwise. 299 */ 300 public boolean equals(Object other) { 301 if (this == other) 302 return true; 303 if (!(other instanceof CertificateExtensions)) 304 return false; 305 Collection<Extension> otherC = 306 ((CertificateExtensions)other).getAllExtensions(); 307 Object[] objs = otherC.toArray(); 308 309 int len = objs.length; 310 if (len != map.size()) 311 return false; 312 313 Extension otherExt, thisExt; 314 String key = null; 315 for (int i = 0; i < len; i++) { 316 if (objs[i] instanceof CertAttrSet) 317 key = ((CertAttrSet)objs[i]).getName(); 318 otherExt = (Extension)objs[i]; 319 if (key == null) 320 key = otherExt.getExtensionId().toString(); 321 thisExt = map.get(key); 322 if (thisExt == null) 323 return false; 324 if (! thisExt.equals(otherExt)) 325 return false; 326 } 327 return this.getUnparseableExtensions().equals( 328 ((CertificateExtensions)other).getUnparseableExtensions()); 329 } 330 331 /** 332 * Returns a hashcode value for this CertificateExtensions. 333 * 334 * @return the hashcode value. 335 */ 336 public int hashCode() { 337 return map.hashCode() + getUnparseableExtensions().hashCode(); 338 } 339 340 /** 341 * Returns a string representation of this <tt>CertificateExtensions</tt> 342 * object in the form of a set of entries, enclosed in braces and separated 343 * by the ASCII characters "<tt>, </tt>" (comma and space). 344 * <p>Overrides to <tt>toString</tt> method of <tt>Object</tt>. 345 * 346 * @return a string representation of this CertificateExtensions. 347 */ 348 public String toString() { 349 return map.toString(); 350 } 351 352 } 353 354 class UnparseableExtension extends Extension { 355 private String name; 356 private Throwable why; 357 358 public UnparseableExtension(Extension ext, Throwable why) { 359 super(ext); 360 361 name = ""; 362 try { 363 Class extClass = OIDMap.getClass(ext.getExtensionId()); 364 if (extClass != null) { 365 Field field = extClass.getDeclaredField("NAME"); 366 name = (String)(field.get(null)) + " "; 367 } 368 } catch (Exception e) { 369 // If we cannot find the name, just ignore it 370 } 371 372 this.why = why; 373 } 374 375 @Override public String toString() { 376 return super.toString() + 377 "Unparseable " + name + "extension due to\n" + why + "\n\n" + 378 new sun.misc.HexDumpEncoder().encodeBuffer(getExtensionValue()); 379 } 380 } 381