1 module hunt.security.util.DerValue;
2 
3 import hunt.security.util.DerInputBuffer;
4 import hunt.security.util.DerOutputStream;
5 // import hunt.security.util.DerInputStream;
6 
7 import hunt.stream.Common;
8 import hunt.Exceptions;
9 
10 import std.conv;
11 import std.bigint;
12 import std.bitmanip;
13 import std.datetime;
14 
15 
16 /**
17  * Represents a single DER-encoded value.  DER encoding rules are a subset
18  * of the "Basic" Encoding Rules (BER), but they only support a single way
19  * ("Definite" encoding) to encode any given value.
20  *
21  * <P>All DER-encoded data are triples <em>{type, length, data}</em>.  This
22  * class represents such tagged values as they have been read (or constructed),
23  * and provides structured access to the encoded data.
24  *
25  * <P>At this time, this class supports only a subset of the types of DER
26  * data encodings which are defined.  That subset is sufficient for parsing
27  * most X.509 certificates, and working with selected additional formats
28  * (such as PKCS #10 certificate requests, and some kinds of PKCS #7 data).
29  *
30  * A note with respect to T61/Teletex strings: From RFC 1617, section 4.1.3
31  * and RFC 3280, section 4.1.2.4., we assume that this kind of string will
32  * contain ISO-8859-1 characters only.
33  *
34  *
35  * @author David Brownell
36  * @author Amit Kapoor
37  * @author Hemma Prafullchandra
38  */
39 class DerValue {
40     /** The tag class types */
41     enum byte TAG_UNIVERSAL = cast(byte)0x000;
42     enum byte TAG_APPLICATION = cast(byte)0x040;
43     enum byte TAG_CONTEXT = cast(byte)0x080;
44     enum byte TAG_PRIVATE = cast(byte)0x0c0;
45 
46     /** The DER tag of the value; one of the tag_ constants. */
47     byte                 tag;
48 
49     protected DerInputBuffer    buffer;
50 
51     /**
52      * The DER-encoded data of the value, never null
53      */
54     // DerInputStream data;
55 
56     private int                 _length;
57 
58     /*
59      * The type starts at the first byte of the encoding, and
60      * is one of these tag_* values.  That may be all the type
61      * data that is needed.
62      */
63 
64     /*
65      * These tags are the "universal" tags ... they mean the same
66      * in all contexts.  (Mask with 0x1f -- five bits.)
67      */
68 
69     /** Tag value indicating an ASN.1 "BOOLEAN" value. */
70     enum byte    tag_Boolean = 0x01;
71 
72     /** Tag value indicating an ASN.1 "INTEGER" value. */
73     enum byte    tag_Integer = 0x02;
74 
75     /** Tag value indicating an ASN.1 "BIT STRING" value. */
76     enum byte    tag_BitString = 0x03;
77 
78     /** Tag value indicating an ASN.1 "OCTET STRING" value. */
79     enum byte    tag_OctetString = 0x04;
80 
81     /** Tag value indicating an ASN.1 "NULL" value. */
82     enum byte    tag_Null = 0x05;
83 
84     /** Tag value indicating an ASN.1 "OBJECT IDENTIFIER" value. */
85     enum byte    tag_ObjectId = 0x06;
86 
87     /** Tag value including an ASN.1 "ENUMERATED" value */
88     enum byte    tag_Enumerated = 0x0A;
89 
90     /** Tag value indicating an ASN.1 "UTF8String" value. */
91     enum byte    tag_UTF8String = 0x0C;
92 
93     /** Tag value including a "printable" string */
94     enum byte    tag_PrintableString = 0x13;
95 
96     /** Tag value including a "teletype" string */
97     enum byte    tag_T61String = 0x14;
98 
99     /** Tag value including an ASCII string */
100     enum byte    tag_IA5String = 0x16;
101 
102     /** Tag value indicating an ASN.1 "UTCTime" value. */
103     enum byte    tag_UtcTime = 0x17;
104 
105     /** Tag value indicating an ASN.1 "GeneralizedTime" value. */
106     enum byte    tag_GeneralizedTime = 0x18;
107 
108     /** Tag value indicating an ASN.1 "GenerallString" value. */
109     enum byte    tag_GeneralString = 0x1B;
110 
111     /** Tag value indicating an ASN.1 "UniversalString" value. */
112     enum byte    tag_UniversalString = 0x1C;
113 
114     /** Tag value indicating an ASN.1 "BMPString" value. */
115     enum byte    tag_BMPString = 0x1E;
116 
117     // CONSTRUCTED seq/set
118 
119     /**
120      * Tag value indicating an ASN.1
121      * "SEQUENCE" (zero to N elements, order is significant).
122      */
123     enum byte    tag_Sequence = 0x30;
124 
125     /**
126      * Tag value indicating an ASN.1
127      * "SEQUENCE OF" (one to N elements, order is significant).
128      */
129     enum byte    tag_SequenceOf = 0x30;
130 
131     /**
132      * Tag value indicating an ASN.1
133      * "SET" (zero to N members, order does not matter).
134      */
135     enum byte    tag_Set = 0x31;
136 
137     /**
138      * Tag value indicating an ASN.1
139      * "SET OF" (one to N members, order does not matter).
140      */
141     enum byte    tag_SetOf = 0x31;
142 
143     /*
144      * These values are the high order bits for the other kinds of tags.
145      */
146 
147     /**
148      * Returns true if the tag class is UNIVERSAL.
149      */
150     bool isUniversal()      { return ((tag & 0x0c0) == 0x000); }
151 
152     /**
153      * Returns true if the tag class is APPLICATION.
154      */
155     bool isApplication()    { return ((tag & 0x0c0) == 0x040); }
156 
157     /**
158      * Returns true iff the CONTEXT SPECIFIC bit is set in the type tag.
159      * This is associated with the ASN.1 "DEFINED BY" syntax.
160      */
161     bool isContextSpecific() { return ((tag & 0x0c0) == 0x080); }
162 
163     /**
164      * Returns true iff the CONTEXT SPECIFIC TAG matches the passed tag.
165      */
166     bool isContextSpecific(byte cntxtTag) {
167         if (!isContextSpecific()) {
168             return false;
169         }
170         return ((tag & 0x01f) == cntxtTag);
171     }
172 
173     bool isPrivate()        { return ((tag & 0x0c0) == 0x0c0); }
174 
175     /** Returns true iff the CONSTRUCTED bit is set in the type tag. */
176     bool isConstructed()    { return ((tag & 0x020) == 0x020); }
177 
178     /**
179      * Returns true iff the CONSTRUCTED TAG matches the passed tag.
180      */
181     bool isConstructed(byte constructedTag) {
182         if (!isConstructed()) {
183             return false;
184         }
185         return ((tag & 0x01f) == constructedTag);
186     }
187 
188     /**
189      * Creates a PrintableString or UTF8string DER value from a string
190      */
191     this(string value) {
192         bool isPrintableString = true;
193         for (size_t i = 0; i < value.length; i++) {
194             if (!isPrintableStringChar(value[i])) {
195                 isPrintableString = false;
196                 break;
197             }
198         }
199 
200         // data = init(isPrintableString ? tag_PrintableString : tag_UTF8String, value);
201     }
202 
203     /**
204      * Creates a string type DER value from a string object
205      * @param stringTag the tag for the DER value to create
206      * @param value the string object to use for the DER value
207      */
208     this(byte stringTag, string value) {
209         // data = init(stringTag, value);
210     }
211 
212     // Creates a DerValue from a tag and some DER-encoded data w/ additional
213     // arg to control whether DER checks are enforced.
214     this(byte tag, byte[] data, bool allowBER) {
215         this.tag = tag;
216         // buffer = new DerInputBuffer(data.clone(), allowBER);
217         _length = cast(int)data.length;
218         // this.data = new DerInputStream(buffer);
219         // this.data.mark(int.max);
220     }
221 
222     /**
223      * Creates a DerValue from a tag and some DER-encoded data.
224      *
225      * @param tag the DER type tag
226      * @param data the DER-encoded data
227      */
228     this(byte tag, byte[] data) {
229         this(tag, data, true);
230     }
231 
232     /*
233      * package private
234      */
235     // this(DerInputBuffer ib) {
236 
237     //     // XXX must also parse BER-encoded constructed
238     //     // values such as sequences, sets...
239     //     tag = cast(byte)ib.read();
240     //     byte lenByte = cast(byte)ib.read();
241     //     length = DerInputStream.getLength(lenByte, ib);
242     //     if (length == -1) {  // indefinite length encoding found
243     //         DerInputBuffer inbuf = ib.dup();
244     //         int readLen = inbuf.available();
245     //         int offset = 2;     // for tag and length bytes
246     //         byte[] indefData = new byte[readLen + offset];
247     //         indefData[0] = tag;
248     //         indefData[1] = lenByte;
249     //         DataInputStream dis = new DataInputStream(inbuf);
250     //         dis.readFully(indefData, offset, readLen);
251     //         dis.close();
252     //         DerIndefLenConverter derIn = new DerIndefLenConverter();
253     //         inbuf = new DerInputBuffer(derIn.convert(indefData), ib.allowBER);
254     //         if (tag != inbuf.read())
255     //             throw new IOException
256     //                     ("Indefinite length encoding not supported");
257     //         length = DerInputStream.getLength(inbuf);
258     //         buffer = inbuf.dup();
259     //         buffer.truncate(length);
260     //         data = new DerInputStream(buffer);
261     //         // indefinite form is encoded by sending a length field with a
262     //         // length of 0. - i.e. [1000|0000].
263     //         // the object is ended by sending two zero bytes.
264     //         ib.skip(length + offset);
265     //     } else {
266 
267     //         buffer = ib.dup();
268     //         buffer.truncate(length);
269     //         data = new DerInputStream(buffer);
270 
271     //         ib.skip(length);
272     //     }
273     // }
274 
275     // Get an ASN.1/DER encoded datum from a buffer w/ additional
276     // arg to control whether DER checks are enforced.
277     this(byte[] buf, bool allowBER) {
278         implementationMissing(false);
279         // data = init(true, new ByteArrayInputStream(buf), allowBER);
280     }
281 
282     /**
283      * Get an ASN.1/DER encoded datum from a buffer.  The
284      * entire buffer must hold exactly one datum, including
285      * its tag and length.
286      *
287      * @param buf buffer holding a single DER-encoded datum.
288      */
289     this(byte[] buf) {
290         this(buf, true);
291     }
292 
293     // Get an ASN.1/DER encoded datum from part of a buffer w/ additional
294     // arg to control whether DER checks are enforced.
295     this(byte[] buf, int offset, int len, bool allowBER)
296         {
297 
298         implementationMissing();
299         // data = init(true, new ByteArrayInputStream(buf, offset, len), allowBER);
300     }
301 
302     /**
303      * Get an ASN.1/DER encoded datum from part of a buffer.
304      * That part of the buffer must hold exactly one datum, including
305      * its tag and length.
306      *
307      * @param buf the buffer
308      * @param offset start point of the single DER-encoded dataum
309      * @param length how many bytes are in the encoded datum
310      */
311     this(byte[] buf, int offset, int len) {
312         this(buf, offset, len, true);
313     }
314 
315     // Get an ASN1/DER encoded datum from an input stream w/ additional
316     // arg to control whether DER checks are enforced.
317     this(InputStream inputStream, bool allowBER) {
318         // data = init(false, inputStream, allowBER);
319         implementationMissing();
320     }
321 
322     /**
323      * Get an ASN1/DER encoded datum from an input stream.  The
324      * stream may have additional data following the encoded datum.
325      * In case of indefinite length encoded datum, the input stream
326      * must hold only one datum.
327      *
328      * @param in the input stream holding a single DER datum,
329      *  which may be followed by additional data
330      */
331     this(InputStream inputStream) {
332         this(inputStream, true);
333     }
334 
335     // private DerInputStream init(byte stringTag, string value)
336     //     {
337     //     string enc = null;
338 
339     //     tag = stringTag;
340 
341     //     switch (stringTag) {
342     //     case tag_PrintableString:
343     //     case tag_IA5String:
344     //     case tag_GeneralString:
345     //         enc = "ASCII";
346     //         break;
347     //     case tag_T61String:
348     //         enc = "ISO-8859-1";
349     //         break;
350     //     case tag_BMPString:
351     //         enc = "UnicodeBigUnmarked";
352     //         break;
353     //     case tag_UTF8String:
354     //         enc = "UTF8";
355     //         break;
356     //         // TBD: Need encoder for UniversalString before it can
357     //         // be handled.
358     //     default:
359     //         throw new IllegalArgumentException("Unsupported DER string type");
360     //     }
361 
362     //     byte[] buf = value.getBytes(enc);
363     //     length = buf.length;
364     //     buffer = new DerInputBuffer(buf, true);
365     //     DerInputStream result = new DerInputStream(buffer);
366     //     result.mark(int.max);
367     //     return result;
368     // }
369 
370     // /*
371     //  * helper routine
372     //  */
373     // private DerInputStream init(bool fullyBuffered, InputStream inputStream,
374     //     bool allowBER) {
375 
376     //     tag = cast(byte)inputStream.read();
377     //     byte lenByte = cast(byte)inputStream.read();
378     //     length = DerInputStream.getLength(lenByte, inputStream);
379     //     if (length == -1) { // indefinite length encoding found
380     //         int readLen = inputStream.available();
381     //         int offset = 2;     // for tag and length bytes
382     //         byte[] indefData = new byte[readLen + offset];
383     //         indefData[0] = tag;
384     //         indefData[1] = lenByte;
385     //         DataInputStream dis = new DataInputStream(inputStream);
386     //         dis.readFully(indefData, offset, readLen);
387     //         dis.close();
388     //         DerIndefLenConverter derIn = new DerIndefLenConverter();
389     //         inputStream = new ByteArrayInputStream(derIn.convert(indefData));
390     //         if (tag != inputStream.read())
391     //             throw new IOException
392     //                     ("Indefinite length encoding not supported");
393     //         length = DerInputStream.getLength(inputStream);
394     //     }
395 
396     //     if (fullyBuffered && inputStream.available() != length)
397     //         throw new IOException("extra data given to DerValue constructor");
398 
399     //     byte[] bytes = IOUtils.readFully(inputStream, length, true);
400 
401     //     buffer = new DerInputBuffer(bytes, allowBER);
402     //     return new DerInputStream(buffer);
403     // }
404 
405     // /**
406     //  * Encode an ASN1/DER encoded datum onto a DER output stream.
407     //  */
408     // void encode(DerOutputStream ot)
409     // {
410     //     ot.write(tag);
411     //     ot.putLength(length);
412     //     // XXX yeech, excess copies ... DerInputBuffer.write(OutStream)
413     //     if (length > 0) {
414     //         byte[] value = new byte[length];
415     //         // always synchronized on data
416     //         synchronized (data) {
417     //             buffer.reset();
418     //             if (buffer.read(value) != length) {
419     //                 throw new IOException("short DER value read (encode)");
420     //             }
421     //             ot.write(value);
422     //         }
423     //     }
424     // }
425 
426     // final DerInputStream getData() {
427     //     return data;
428     // }
429 
430     final byte getTag() {
431         return tag;
432     }
433 
434     /**
435      * Returns an ASN.1 BOOLEAN
436      *
437      * @return the bool held in this DER value
438      */
439     bool getBoolean() {
440         if (tag != tag_Boolean) {
441             throw new IOException("DerValue.getBoolean, not a BOOLEAN " ~ tag);
442         }
443         if (length != 1) {
444             throw new IOException("DerValue.getBoolean, invalid length "
445                                         ~ length.to!string());
446         }
447         // if (buffer.read() != 0) {
448         //     return true;
449         // }
450         implementationMissing();
451         return false;
452     }
453 
454     /**
455      * Returns an ASN.1 OBJECT IDENTIFIER.
456      *
457      * @return the OID held in this DER value
458      */
459     // ObjectIdentifier getOID() {
460     //     if (tag != tag_ObjectId)
461     //         throw new IOException("DerValue.getOID, not an OID " ~ tag);
462     //     return new ObjectIdentifier(buffer);
463     // }
464 
465     private byte[] append(byte[] a, byte[] b) {
466         if (a is null)
467             return b;
468 
469         // byte[] ret = new byte[a.length + b.length];
470         // ret = a ~ b;
471         // System.arraycopy(a, 0, ret, 0, a.length);
472         // System.arraycopy(b, 0, ret, a.length, b.length);
473 
474         return a ~ b;
475     }
476 
477     /**
478      * Returns an ASN.1 OCTET STRING
479      *
480      * @return the octet string held in this DER value
481      */
482     byte[] getOctetString() {
483         byte[] bytes;
484 
485         if (tag != tag_OctetString && !isConstructed(tag_OctetString)) {
486             throw new IOException(
487                 "DerValue.getOctetString, not an Octet string: " ~ tag);
488         }
489         bytes = new byte[length];
490         // Note: do not tempt to call buffer.read(bytes) at all. There's a
491         // known bug that it returns -1 instead of 0.
492         if (length == 0) {
493             return bytes;
494         }
495         // if (buffer.read(bytes) != length)
496         //     throw new IOException("short read on DerValue buffer");
497         // if (isConstructed()) {
498         //     DerInputStream inputStream = new DerInputStream(bytes, 0, bytes.length,
499         //         buffer.allowBER);
500         //     bytes = null;
501         //     while (inputStream.available() != 0) {
502         //         bytes = append(bytes, inputStream.getOctetString());
503         //     }
504         // }
505         implementationMissing();
506         return bytes;
507     }
508 
509     /**
510      * Returns an ASN.1 INTEGER value as an integer.
511      *
512      * @return the integer held in this DER value.
513      */
514     int getInteger() {
515         if (tag != tag_Integer) {
516             throw new IOException("DerValue.getInteger, not an int " ~ tag);
517         }
518         implementationMissing();
519         return 0 ; //buffer.getInteger(data.available());
520     }
521 
522     /**
523      * Returns an ASN.1 INTEGER value as a BigInt.
524      *
525      * @return the integer held in this DER value as a BigInt.
526      */
527     BigInt getBigInteger() {
528         if (tag != tag_Integer)
529             throw new IOException("DerValue.getBigInteger, not an int " ~ tag);
530         // return buffer.getBigInteger(data.available(), false);
531         implementationMissing();
532         return BigInt(0);
533     }
534 
535     /**
536      * Returns an ASN.1 INTEGER value as a positive BigInt.
537      * This is just to deal with implementations that incorrectly encode
538      * some values as negative.
539      *
540      * @return the integer held in this DER value as a BigInt.
541      */
542     BigInt getPositiveBigInteger() {
543         if (tag != tag_Integer)
544             throw new IOException("DerValue.getBigInteger, not an int " ~ tag);
545         // return buffer.getBigInteger(data.available(), true);
546         implementationMissing();
547         return BigInt(0);
548     }
549 
550     /**
551      * Returns an ASN.1 ENUMERATED value.
552      *
553      * @return the integer held in this DER value.
554      */
555     // int getEnumerated() {
556     //     if (tag != tag_Enumerated) {
557     //         throw new IOException("DerValue.getEnumerated, incorrect tag: "
558     //                               + tag);
559     //     }
560     //     return buffer.getInteger(data.available());
561     // }
562 
563     /**
564      * Returns an ASN.1 BIT STRING value.  The bit string must be byte-aligned.
565      *
566      * @return the bit string held in this value
567      */
568     // byte[] getBitString() {
569     //     if (tag != tag_BitString)
570     //         throw new IOException(
571     //             "DerValue.getBitString, not a bit string " ~ tag);
572 
573     //     return buffer.getBitString();
574     // }
575 
576     /**
577      * Returns an ASN.1 BIT STRING value that need not be byte-aligned.
578      *
579      * @return a BitArray representing the bit string held in this value
580      */
581     BitArray getUnalignedBitString() {
582         // if (tag != tag_BitString)
583         //     throw new IOException(
584         //         "DerValue.getBitString, not a bit string " ~ tag);
585 
586         // return buffer.getUnalignedBitString();
587         implementationMissing();
588         return BitArray.init;
589     }
590 
591     /**
592      * Returns the name component as a Java string, regardless of its
593      * encoding restrictions (ASCII, T61, Printable, IA5, BMP, UTF8).
594      */
595     // TBD: Need encoder for UniversalString before it can be handled.
596     string getAsString() {
597         if (tag == tag_UTF8String)
598             return getUTF8String();
599         else if (tag == tag_PrintableString)
600             return getPrintableString();
601         else if (tag == tag_T61String)
602             return getT61String();
603         else if (tag == tag_IA5String)
604             return getIA5String();
605         /*
606           else if (tag == tag_UniversalString)
607           return getUniversalString();
608         */
609         else if (tag == tag_BMPString)
610             return getBMPString();
611         else if (tag == tag_GeneralString)
612             return getGeneralString();
613         else
614             return null;
615     }
616 
617     /**
618      * Returns an ASN.1 BIT STRING value, with the tag assumed implicit
619      * based on the parameter.  The bit string must be byte-aligned.
620      *
621      * @params tagImplicit if true, the tag is assumed implicit.
622      * @return the bit string held in this value
623      */
624     // byte[] getBitString(bool tagImplicit) {
625     //     if (!tagImplicit) {
626     //         if (tag != tag_BitString)
627     //             throw new IOException("DerValue.getBitString, not a bit string "
628     //                                    + tag);
629     //         }
630     //     return buffer.getBitString();
631     // }
632 
633     /**
634      * Returns an ASN.1 BIT STRING value, with the tag assumed implicit
635      * based on the parameter.  The bit string need not be byte-aligned.
636      *
637      * @params tagImplicit if true, the tag is assumed implicit.
638      * @return the bit string held in this value
639      */
640     BitArray getUnalignedBitString(bool tagImplicit)
641     {
642         if (!tagImplicit) {
643             if (tag != tag_BitString)
644                 throw new IOException("DerValue.getBitString, not a bit string "
645                                        ~ tag.to!string());
646             }
647         return buffer.getUnalignedBitString();
648     }
649 
650     /**
651      * Helper routine to return all the bytes contained in the
652      * DerInputStream associated with this object.
653      */
654     byte[] getDataBytes() {
655         byte[] retVal = new byte[length];
656         // synchronized (data) {
657         //     data.reset();
658         //     data.getBytes(retVal);
659         // }
660         implementationMissing();
661         return retVal;
662     }
663 
664     /**
665      * Returns an ASN.1 STRING value
666      *
667      * @return the printable string held in this value
668      */
669     string getPrintableString()
670     {
671         if (tag != tag_PrintableString)
672             throw new IOException(
673                 "DerValue.getPrintableString, not a string " ~ tag);
674 
675         return cast(string)getDataBytes();
676     }
677 
678     /**
679      * Returns an ASN.1 T61 (Teletype) STRING value
680      *
681      * @return the teletype string held in this value
682      */
683     string getT61String() {
684         if (tag != tag_T61String)
685             throw new IOException(
686                 "DerValue.getT61String, not T61 " ~ tag);
687 
688         return cast(string)getDataBytes();
689     }
690 
691     /**
692      * Returns an ASN.1 IA5 (ASCII) STRING value
693      *
694      * @return the ASCII string held in this value
695      */
696     string getIA5String() {
697         if (tag != tag_IA5String)
698             throw new IOException(
699                 "DerValue.getIA5String, not IA5 " ~ tag);
700 
701         return cast(string)getDataBytes();
702     }
703 
704     /**
705      * Returns the ASN.1 BMP (Unicode) STRING value as a Java string.
706      *
707      * @return a string corresponding to the encoded BMPString held in
708      * this value
709      */
710     string getBMPString() {
711         if (tag != tag_BMPString)
712             throw new IOException(
713                 "DerValue.getBMPString, not BMP " ~ tag);
714 
715         // BMPString is the same as Unicode in big endian, unmarked
716         // format.
717         return cast(string)getDataBytes();
718     }
719 
720     /**
721      * Returns the ASN.1 UTF-8 STRING value as a Java string.
722      *
723      * @return a string corresponding to the encoded UTF8String held in
724      * this value
725      */
726     string getUTF8String() {
727         if (tag != tag_UTF8String)
728             throw new IOException(
729                 "DerValue.getUTF8String, not UTF-8 " ~ tag);
730 
731         return cast(string)getDataBytes();
732     }
733 
734     /**
735      * Returns the ASN.1 GENERAL STRING value as a Java string.
736      *
737      * @return a string corresponding to the encoded GeneralString held in
738      * this value
739      */
740     string getGeneralString() {
741         if (tag != tag_GeneralString)
742             throw new IOException(
743                 "DerValue.getGeneralString, not GeneralString " ~ tag);
744 
745         return cast(string)getDataBytes();
746     }
747 
748     /**
749      * Returns a Date if the DerValue is UtcTime.
750      *
751      * @return the Date held in this DER value
752      */
753     // Date getUTCTime() {
754     //     if (tag != tag_UtcTime) {
755     //         throw new IOException("DerValue.getUTCTime, not a UtcTime: " ~ tag);
756     //     }
757     //     return buffer.getUTCTime(data.available());
758     // }
759 
760     /**
761      * Returns a Date if the DerValue is GeneralizedTime.
762      *
763      * @return the Date held in this DER value
764      */
765     // Date getGeneralizedTime() {
766     //     if (tag != tag_GeneralizedTime) {
767     //         throw new IOException(
768     //             "DerValue.getGeneralizedTime, not a GeneralizedTime: " ~ tag);
769     //     }
770     //     return buffer.getGeneralizedTime(data.available());
771     // }
772 
773     /**
774      * Returns true iff the other object is a DER value which
775      * is bitwise equal to this one.
776      *
777      * @param other the object being compared with this one
778      */
779     override
780     bool opEquals(Object other) {
781         if (typeid(other) == typeid(DerValue))
782             return opEquals(cast(DerValue)other);
783         else
784             return false;
785     }
786 
787     /**
788      * Bitwise equality comparison.  DER encoded values have a single
789      * encoding, so that bitwise equality of the encoded values is an
790      * efficient way to establish equivalence of the unencoded values.
791      *
792      * @param other the object being compared with this one
793      */
794     bool opEquals(DerValue other) {
795         if (this is other) {
796             return true;
797         }
798         if (tag != other.tag) {
799             return false;
800         }
801         implementationMissing();
802         return false;
803         // if (data == other.data) {
804         //     return true;
805         // }
806 
807         // // make sure the order of lock is always consistent to avoid a deadlock
808         // return hashOf(this.data)
809         //         > hashOf(other.data) ?
810         //         doEquals(this, other):
811         //         doEquals(other, this);
812     }
813 
814     /**
815      * Helper for method equals()
816      */
817     private static bool doEquals(DerValue d1, DerValue d2) {
818         // synchronized (d1.data) {
819         //     synchronized (d2.data) {
820         //         d1.data.reset();
821         //         d2.data.reset();
822         //         return d1.buffer.equals(d2.buffer);
823         //     }
824         // }
825         implementationMissing();
826         return false;
827     }
828 
829     /**
830      * Returns a printable representation of the value.
831      *
832      * @return printable representation of the value
833      */
834     override string toString() {
835         try {
836 
837             string str = getAsString();
838             if (str !is null)
839                 return "\"" ~ str ~ "\"";
840             if (tag == tag_Null)
841                 return "[DerValue, null]";
842             // if (tag == tag_ObjectId)
843             //     return "OID." ~ getOID();
844 
845             // integers
846             else
847                 return "[DerValue, tag = " ~ tag
848                         ~ ", length = " ~ _length.to!string() ~ "]";
849         } catch (IOException e) {
850             throw new IllegalArgumentException("misformatted DER value");
851         }
852     }
853 
854     /**
855      * Returns a DER-encoded value, such that if it's passed to the
856      * DerValue constructor, a value equivalent to "this" is returned.
857      *
858      * @return DER-encoded value, including tag and length.
859      */
860     byte[] toByteArray() {
861         DerOutputStream ot = new DerOutputStream();
862         implementationMissing();
863         // encode(ot);
864         // data.reset();
865         return ot.toByteArray();
866     }
867 
868     /**
869      * For "set" and "sequence" types, this function may be used
870      * to return a DER stream of the members of the set or sequence.
871      * This operation is not supported for primitive types such as
872      * integers or bit strings.
873      */
874     // DerInputStream toDerInputStream() {
875     //     if (tag == tag_Sequence || tag == tag_Set)
876     //         return new DerInputStream(buffer);
877     //     throw new IOException("toDerInputStream rejects tag type " ~ tag);
878     // }
879 
880     /**
881      * Get the length of the encoded value.
882      */
883     int length() {
884         return _length;
885     }
886 
887     /**
888      * Determine if a character is one of the permissible characters for
889      * PrintableString:
890      * A-Z, a-z, 0-9, space, apostrophe (39), left and right parentheses,
891      * plus sign, comma, hyphen, period, slash, colon, equals sign,
892      * and question mark.
893      *
894      * Characters that are *not* allowed in PrintableString include
895      * exclamation point, quotation mark, number sign, dollar sign,
896      * percent sign, ampersand, asterisk, semicolon, less than sign,
897      * greater than sign, at sign, left and right square brackets,
898      * backslash, circumflex (94), underscore, back quote (96),
899      * left and right curly brackets, vertical line, tilde,
900      * and the control codes (0-31 and 127).
901      *
902      * This list is based on X.680 (the ASN.1 spec).
903      */
904     static bool isPrintableStringChar(char ch) {
905         if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
906             (ch >= '0' && ch <= '9')) {
907             return true;
908         } else {
909             switch (ch) {
910                 case ' ':       /* space */
911                 case '\'':      /* apostrophe */
912                 case '(':       /* left paren */
913                 case ')':       /* right paren */
914                 case '+':       /* plus */
915                 case ',':       /* comma */
916                 case '-':       /* hyphen */
917                 case '.':       /* period */
918                 case '/':       /* slash */
919                 case ':':       /* colon */
920                 case '=':       /* equals */
921                 case '?':       /* question mark */
922                     return true;
923                 default:
924                     return false;
925             }
926         }
927     }
928 
929     /**
930      * Create the tag of the attribute.
931      *
932      * @params class the tag class type, one of UNIVERSAL, CONTEXT,
933      *               APPLICATION or PRIVATE
934      * @params form if true, the value is constructed, otherwise it
935      * is primitive.
936      * @params val the tag value
937      */
938     static byte createTag(byte tagClass, bool form, byte val) {
939         byte tag = cast(byte)(tagClass | val);
940         if (form) {
941             tag |= cast(byte)0x20;
942         }
943         return (tag);
944     }
945 
946     /**
947      * Set the tag of the attribute. Commonly used to reset the
948      * tag value used for IMPLICIT encodings.
949      *
950      * @params tag the tag value
951      */
952     void resetTag(byte tag) {
953         this.tag = tag;
954     }
955 
956     /**
957      * Returns a hashcode for this DerValue.
958      *
959      * @return a hashcode for this DerValue.
960      */
961     override size_t toHash() @trusted nothrow {
962         try
963         {
964             string s = toString(); 
965             return hashOf(toString());
966         }
967         catch(Exception)
968         {
969             return super.toHash();
970         }
971     }
972 }