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>,&nbsp;</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 }