1 module hunt.security.x509.Extension;
2 
3 import hunt.security.cert.Extension;
4 
5 import hunt.security.util.DerValue;
6 import hunt.security.util.DerInputStream;
7 import hunt.security.util.DerOutputStream;
8 import hunt.security.util.ObjectIdentifier;
9 
10 import hunt.stream.Common;
11 import hunt.Exceptions;
12 
13 /**
14  * Represent a X509 Extension Attribute.
15  *
16  * <p>Extensions are additional attributes which can be inserted in a X509
17  * v3 certificate. For example a "Driving License Certificate" could have
18  * the driving license number as a extension.
19  *
20  * <p>Extensions are represented as a sequence of the extension identifier
21  * (Object Identifier), a bool flag stating whether the extension is to
22  * be treated as being critical and the extension value itself (this is again
23  * a DER encoding of the extension value).
24  * <pre>
25  * ASN.1 definition of Extension:
26  * Extension ::= SEQUENCE {
27  *      ExtensionId     OBJECT IDENTIFIER,
28  *      critical        BOOLEAN DEFAULT FALSE,
29  *      extensionValue  OCTET STRING
30  * }
31  * </pre>
32  * All subclasses need to implement a constructor of the form
33  * <pre>
34  *     <subclass> (Boolean, Object)
35  * </pre>
36  * where the Object is typically an array of DER encoded bytes.
37  * <p>
38  * @author Amit Kapoor
39  * @author Hemma Prafullchandra
40  */
41 class Extension : CertExtension {
42 
43     protected ObjectIdentifier  extensionId = null;
44     protected bool           critical = false;
45     protected byte[]            extensionValue = null;
46 
47     /**
48      * Default constructor.  Used only by sub-classes.
49      */
50     this() { }
51 
52     /**
53      * Constructs an extension from a DER encoded array of bytes.
54      */
55     this(DerValue derVal) {
56 
57         implementationMissing(false);
58         DerInputStream stream; // = derVal.toDerInputStream();
59 
60         // Object identifier
61         extensionId = new ObjectIdentifier(stream); // stream.getOID();
62 
63         // If the criticality flag was false, it will not have been encoded.
64         // DerValue val = stream.getDerValue();
65         // if (val.tag == DerValue.tag_Boolean) {
66         //     critical = val.getBoolean();
67 
68         //     // Extension value (DER encoded)
69         //     val = stream.getDerValue();
70         //     extensionValue = val.getOctetString();
71         // } else {
72         //     critical = false;
73         //     extensionValue = val.getOctetString();
74         // }
75     }
76 
77     /**
78      * Constructs an Extension from individual components of ObjectIdentifier,
79      * criticality and the DER encoded OctetString.
80      *
81      * @param extensionId the ObjectIdentifier of the extension
82      * @param critical the bool indicating if the extension is critical
83      * @param extensionValue the DER encoded octet string of the value.
84      */
85     this(ObjectIdentifier extensionId, bool critical,
86                      byte[] extensionValue) {
87         this.extensionId = extensionId;
88         this.critical = critical;
89         // passed in a DER encoded octet string, strip off the tag
90         // and length
91         DerValue inDerVal = new DerValue(extensionValue);
92         this.extensionValue = inDerVal.getOctetString();
93     }
94 
95     /**
96      * Constructs an Extension from another extension. To be used for
97      * creating decoded subclasses.
98      *
99      * @param ext the extension to create from.
100      */
101     this(Extension ext) {
102         this.extensionId = ext.extensionId;
103         this.critical = ext.critical;
104         this.extensionValue = ext.extensionValue;
105     }
106 
107     /**
108      * Constructs an Extension from individual components of ObjectIdentifier,
109      * criticality and the raw encoded extension value.
110      *
111      * @param extensionId the ObjectIdentifier of the extension
112      * @param critical the bool indicating if the extension is critical
113      * @param rawExtensionValue the raw DER-encoded extension value (this
114      * is not the encoded OctetString).
115      */
116     static Extension newExtension(ObjectIdentifier extensionId,
117         bool critical, byte[] rawExtensionValue) {
118         Extension ext = new Extension();
119         ext.extensionId = extensionId;
120         ext.critical = critical;
121         ext.extensionValue = rawExtensionValue;
122         return ext;
123     }
124 
125     void encode(OutputStream stream) {
126         if (stream is null) {
127             throw new NullPointerException();
128         }
129 
130         DerOutputStream dos1 = new DerOutputStream();
131         DerOutputStream dos2 = new DerOutputStream();
132 
133         // dos1.putOID(extensionId);
134         extensionId.encode(dos1);
135         if (critical) {
136             dos1.putBoolean(critical);
137         }
138         dos1.putOctetString(extensionValue);
139 
140         dos2.write(DerValue.tag_Sequence, dos1);
141         stream.write(dos2.toByteArray());
142     }
143 
144     /**
145      * Write the extension to the DerOutputStream.
146      *
147      * @param stream the DerOutputStream to write the extension to.
148      * @exception IOException on encoding errors
149      */
150     void encode(DerOutputStream stream) {
151 
152         if (extensionId is null)
153             throw new IOException("Null OID to encode for the extension!");
154         if (extensionValue is null)
155             throw new IOException("No value to encode for the extension!");
156 
157         DerOutputStream dos = new DerOutputStream();
158 
159         // dos.putOID(extensionId);
160         extensionId.encode(dos);
161         if (critical)
162             dos.putBoolean(critical);
163         dos.putOctetString(extensionValue);
164 
165         stream.write(DerValue.tag_Sequence, dos);
166     }
167 
168     /**
169      * Returns true if extension is critical.
170      */
171     bool isCritical() {
172         return critical;
173     }
174 
175     /**
176      * Returns the ObjectIdentifier of the extension.
177      */
178     ObjectIdentifier getExtensionId() {
179         return extensionId;
180     }
181 
182     byte[] getValue() {
183         return extensionValue.dup;
184     }
185 
186     /**
187      * Returns the extension value as an byte array for further processing.
188      * Note, this is the raw DER value of the extension, not the DER
189      * encoded octet string which is in the certificate.
190      * This method does not return a clone; it is the responsibility of the
191      * caller to clone the array if necessary.
192      */
193     byte[] getExtensionValue() {
194         return extensionValue;
195     }
196 
197     string getId() {
198         return extensionId.toString();
199     }
200 
201     /**
202      * Returns the Extension in user readable form.
203      */
204     override string toString() {
205         string s = "ObjectId: " ~ extensionId.toString();
206         if (critical) {
207             s ~= " Criticality=true\n";
208         } else {
209             s ~= " Criticality=false\n";
210         }
211         return (s);
212     }
213 
214     // Value to mix up the hash
215     private enum int hashMagic = 31;
216 
217     /**
218      * Returns a hashcode value for this Extension.
219      *
220      * @return the hashcode value.
221      */
222     override size_t toHash() @trusted nothrow {
223         size_t h = 0;
224         if (extensionValue !is null) {
225             byte[] val = extensionValue;
226             size_t len = val.length;
227             while (len > 0)
228                 h += len * val[--len];
229         }
230         h = h * hashMagic + extensionId.toHash();
231         h = h * hashMagic + (critical?1231:1237);
232         return h;
233     }
234 
235     /**
236      * Compares this Extension for equality with the specified
237      * object. If the <code>other</code> object is an
238      * <code>instanceof</code> <code>Extension</code>, then
239      * its encoded form is retrieved and compared with the
240      * encoded form of this Extension.
241      *
242      * @param other the object to test for equality with this Extension.
243      * @return true iff the other object is of type Extension, and the
244      * criticality flag, object identifier and encoded extension value of
245      * the two Extensions match, false otherwise.
246      */
247     override bool opEquals(Object other) {
248         if (this is other)
249             return true;
250         Extension otherExt = cast(Extension) other;
251         if(otherExt is null)
252             return false;
253         if (critical != otherExt.critical)
254             return false;
255         if (!extensionId.opEquals(otherExt.extensionId))
256             return false;
257         return extensionValue == otherExt.extensionValue;
258     }
259 }
260 
261 alias X509Extension = Extension;