1 /**
2 * X.509 Distinguished Name
3 * 
4 * Copyright:
5 * (C) 1999-2010 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.asn1.x509_dn;
12 
13 import botan.constants;
14 public import botan.asn1.asn1_obj;
15 public import botan.asn1.asn1_oid;
16 public import botan.asn1.asn1_str;
17 public import botan.asn1.x509_dn;
18 import botan.asn1.der_enc;
19 import botan.asn1.ber_dec;
20 import botan.utils.parsing;
21 import botan.utils.types;
22 import memutils.dictionarylist;
23 import botan.asn1.oids;
24 import memutils.hashmap;
25 import std.array : Appender;
26 
27 version(X509):
28 
29 alias X509DN = RefCounted!X509DNImpl;
30 
31 /**
32 * Distinguished Name
33 */
34 final class X509DNImpl : ASN1Object
35 {
36 public:
37     /*
38     * DER encode a DistinguishedName
39     */
40     override void encodeInto(ref DEREncoder der) const
41     {
42         auto dn_info = getAttributes();
43         
44         der.startCons(ASN1Tag.SEQUENCE);
45         
46         if (!m_dn_bits.empty)
47             der.rawBytes(m_dn_bits);
48         else
49         {
50             doAva(der, dn_info, ASN1Tag.PRINTABLE_STRING, "X520.Country");
51             doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.State");
52             doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.Locality");
53             doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.Organization");
54             doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.OrganizationalUnit");
55             doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.CommonName");
56             doAva(der, dn_info, ASN1Tag.PRINTABLE_STRING, "X520.SerialNumber");
57         }
58         
59         der.endCons();
60     }
61 
62     /*
63     * Decode a BER encoded DistinguishedName
64     */
65     override void decodeFrom(ref BERDecoder source)
66     {
67         Vector!ubyte bits;
68         
69         source.startCons(ASN1Tag.SEQUENCE)
70             .rawBytes(bits)
71                 .endCons();
72         
73         BERDecoder sequence = BERDecoder(bits);
74         
75         while (sequence.moreItems())
76         {
77             BERDecoder rdn = sequence.startCons(ASN1Tag.SET);
78             
79             while (rdn.moreItems())
80             {
81                 OID oid;
82                 ASN1String str = ASN1String();
83                 
84                 rdn.startCons(ASN1Tag.SEQUENCE)
85                         .decode(oid)
86                         .decode(str)
87                         .verifyEnd()
88                         .endCons();
89                 
90                 addAttribute(oid, str.value());
91             }
92         }
93         
94         m_dn_bits = bits.dup;
95     }
96 
97     /*
98     * Get the attributes of this X509DN
99     */
100     DictionaryListRef!(OID, string) getAttributes() const
101     {
102         DictionaryListRef!(OID, string) retval;
103         foreach (const ref OID oid, const ref ASN1String asn1_str; m_dn_info)
104             retval.insert(oid, asn1_str.value());
105         return retval;
106     }
107 
108     /*
109     * Get a single attribute type
110     */
111     Vector!string getAttribute(in string attr) const
112     {
113         const OID oid = OIDS.lookup(derefInfoField(attr));
114         return getAttribute(oid);
115     }
116 
117     private Vector!string getAttribute(in OID oid) const 
118     {
119         auto range = m_dn_info.getValuesAt(oid);
120         
121         Vector!string values;
122         foreach (const ref ASN1String asn1_string; range[])
123             values.pushBack(asn1_string.value());
124         return values.move;
125     }
126 
127     /*
128     * Get the contents of this X.500 Name
129     */
130     DictionaryListRef!(string, string) contents() const
131     {
132         DictionaryListRef!(string, string) retval;
133         foreach (const ref OID key, const ref ASN1String value; m_dn_info)
134             retval.insert(OIDS.lookup(key), value.value());
135         return retval;
136     }
137 
138 
139     /*
140     * Add an attribute to a X509DN
141     */
142     void addAttribute(in string type, in string str)
143     {
144         logTrace("Add X509DN Attribute Type: ", type, ", Value: ", str);
145         OID oid = OIDS.lookup(type);
146         addAttribute(oid, str);
147     }
148 
149     /*
150     * Add an attribute to a X509DN
151     */
152     void addAttribute(const ref OID oid, in string str)
153     {
154         if (str == "")
155             return;
156 
157         bool exists;
158         void search_func(in ASN1String name) {
159             //logTrace(name.value());
160             if (name.value() == str) { 
161                 exists = true;
162             }
163         }
164         m_dn_info.getValuesAt(oid, &search_func);
165         if (!exists) {
166 			auto asn1_str = ASN1String(str.idup);
167             m_dn_info.insert(oid, asn1_str);
168             m_dn_bits.clear();
169         }
170     }
171 
172     /*
173     * Deref aliases in a subject/issuer info request
174     */
175     static string derefInfoField(in string info)
176     {
177         if (info == "Name" || info == "CommonName")         return "X520.CommonName";
178         if (info == "SerialNumber")                         return "X520.SerialNumber";
179         if (info == "Country")                              return "X520.Country";
180         if (info == "Organization")                         return "X520.Organization";
181         if (info == "Organizational Unit" || info == "OrgUnit")
182             return "X520.OrganizationalUnit";
183         if (info == "Locality")                             return "X520.Locality";
184         if (info == "State" || info == "Province")          return "X520.State";
185         if (info == "Email")                                return "RFC822";
186         return info;
187     }
188 
189     /*
190     * Return the BER encoded data, if any
191     */
192     ref const(Vector!ubyte) getBits() const
193     {
194         return m_dn_bits;
195     }
196 
197     /*
198     * Create an empty X509DN
199     */
200     this()
201     {
202     }
203     
204     /*
205     * Create an X509DN
206     */
207     this(in DictionaryListRef!(OID, string) args)
208     {
209         foreach (const ref OID oid, const ref string val; args)
210             addAttribute(oid, val);
211     }
212     
213     /*
214     * Create an X509DN
215     */
216     this(in DictionaryListRef!(string, string) args)
217     {
218         foreach (const ref string key, const ref string val; args) {
219 			OID oid = OIDS.lookup(key);
220             addAttribute(oid, val);
221 		}
222     }
223 
224     /*
225     * Compare two X509DNs for equality
226     */
227     bool opEquals(in X509DN dn2) const
228     {
229         size_t i;        
230 		foreach (const ref OID oid, const ref ASN1String str; m_dn_info) {
231             i++;
232             bool found;
233             size_t j;
234 			foreach (const ref OID oid2, const ref ASN1String val; dn2.m_dn_info) {
235                 j++;
236                 if (j != i) continue;
237                 if (x500NameCmp(val.value(), str.value())) 
238                     found = true;
239 
240                 break;
241             }
242             if (!found) return false;
243         }
244         return true;
245 
246     }
247 
248     /*
249     * Compare two X509DNs for inequality
250     */
251     int opCmp(const X509DN dn2) const
252     {
253         if (this == dn2)
254             return 0;
255         else if (this.isSmallerThan(dn2))
256             return -1;
257         else
258             return 1;
259     }
260 
261     /*
262     * Induce an arbitrary ordering on DNs
263     */
264     bool isSmallerThan(const X509DN dn2) const
265     {
266         const auto attr1 = getAttributes();
267         const auto attr2 = dn2.getAttributes();
268         
269         if (attr1.length < attr2.length) return true;
270         if (attr1.length > attr2.length) return false;
271 
272         foreach (const ref OID key, const ref string value; attr1) {
273             const auto value2 = attr2.get(key);
274             if (value2 == null) return false;
275             if (value > value2) return false;
276             if (value < value2) return true;
277         }
278         return false;
279     }
280 
281     override string toString() const
282     {
283         return toVector()[].idup;
284     }
285 
286     Vector!char toVector() const
287     {
288         Vector!char output;
289         DictionaryListRef!(string, string) contents = contents();
290         
291         foreach(const ref string key, const ref string val; contents)
292         {
293             output ~= toShortForm(key);
294             output ~= "=";
295             output ~= val;
296             output ~= ' ';
297         }
298         return output.move();
299     }
300     @property X509DN dup() const {
301         return X509DN(getAttributes());
302     }
303 
304 private:
305     DictionaryList!(OID, ASN1String) m_dn_info;
306     Vector!ubyte m_dn_bits;
307 }
308 
309 /*
310 * DER encode a RelativeDistinguishedName
311 */
312 void doAva(ref DEREncoder encoder,
313            in DictionaryListRef!(OID, string) dn_info,
314            ASN1Tag string_type, in string oid_str,
315            bool must_exist = false)
316 {
317     const OID oid = OIDS.lookup(oid_str);
318     const bool exists = (dn_info.get(oid) != null);
319 
320     if (!exists && must_exist)
321         throw new EncodingError("X509DN: No entry for " ~ oid_str);
322     if (!exists) return;
323 
324     dn_info.getValuesAt(oid, (in string val) {
325                  encoder.startCons(ASN1Tag.SET)
326                 .startCons(ASN1Tag.SEQUENCE)
327                 .encode(oid)
328                 .encode(ASN1String(val, string_type))
329                 .endCons()
330                 .endCons();
331 
332     });
333 }
334 
335 string toShortForm(in string long_id)
336 {
337     if (long_id == "X520.CommonName")
338         return "CN";
339     
340     if (long_id == "X520.Organization")
341         return "O";
342     
343     if (long_id == "X520.OrganizationalUnit")
344         return "OU";
345     
346     return long_id;
347 }