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