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