1 /**
2 * Common ASN.1 Objects
3 * 
4 * Copyright:
5 * (C) 1999-2007 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *     2007 Yves Jerschow
8 *
9 * License:
10 * Botan is released under the Simplified BSD License (see LICENSE.md)
11 */
12 module botan.asn1.asn1_alt_name;
13 
14 import botan.constants;
15 static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO):
16 
17 import botan.asn1.asn1_obj;
18 import botan.asn1.asn1_str;
19 import botan.asn1.asn1_oid;
20 import botan.asn1.asn1_alt_name;
21 import botan.asn1.der_enc;
22 import botan.asn1.ber_dec;
23 import botan.asn1.oids;
24 import memutils.dictionarylist;
25 import botan.utils.charset;
26 import botan.utils.parsing;
27 import botan.utils.loadstor;
28 import botan.utils.types;
29 import memutils.hashmap;
30 
31 alias AlternativeName = RefCounted!AlternativeNameImpl;
32 
33 /**
34 * Alternative Name
35 */
36 final class AlternativeNameImpl : ASN1Object
37 {
38 public:
39     /*
40     * DER encode an AlternativeName extension
41     */
42     override void encodeInto(ref DEREncoder der) const
43     {
44         der.startCons(ASN1Tag.SEQUENCE);
45         
46         encodeEntries(der, m_alt_info, "RFC822", (cast(ASN1Tag)1));
47         encodeEntries(der, m_alt_info, "DNS", (cast(ASN1Tag)2));
48         encodeEntries(der, m_alt_info, "URI", (cast(ASN1Tag)6));
49         encodeEntries(der, m_alt_info, "IP", (cast(ASN1Tag)7));
50 
51         foreach(const ref OID oid, const ref ASN1String asn1_str; m_othernames)
52         {
53             der.startExplicit(0)
54                .encode(oid)
55                .startExplicit(0)
56                .encode(asn1_str)
57                .endExplicit()
58                .endExplicit();
59         }
60         
61         der.endCons();
62     }
63 
64     /*
65     * Decode a BER encoded AlternativeName
66     */
67     override void decodeFrom(ref BERDecoder source)
68     {
69         BERDecoder names = source.startCons(ASN1Tag.SEQUENCE);
70         
71         while (names.moreItems())
72         {
73             BERObject obj = names.getNextObject();
74             if ((obj.class_tag != ASN1Tag.CONTEXT_SPECIFIC) &&
75                 (obj.class_tag != (ASN1Tag.CONTEXT_SPECIFIC | ASN1Tag.CONSTRUCTED)))
76                 continue;
77             
78             const ASN1Tag tag = obj.type_tag;
79             
80             if (tag == 0)
81             {
82                 auto othername = BERDecoder(obj.value);
83                 
84                 OID oid = OID();
85                 othername.decode(oid);
86                 if (othername.moreItems())
87                 {
88                     BERObject othername_value_outer = othername.getNextObject();
89                     othername.verifyEnd();
90                     
91                     if (othername_value_outer.type_tag != (cast(ASN1Tag) 0) ||
92                         othername_value_outer.class_tag != (ASN1Tag.CONTEXT_SPECIFIC | ASN1Tag.CONSTRUCTED))
93                         throw new DecodingError("Invalid tags on otherName value");
94                     
95                     auto othername_value_inner = BERDecoder(othername_value_outer.value);
96                     
97                     BERObject value = othername_value_inner.getNextObject();
98                     othername_value_inner.verifyEnd();
99                     
100                     const ASN1Tag value_type = value.type_tag;
101                     
102                     if (isStringType(value_type) && value.class_tag == ASN1Tag.UNIVERSAL)
103                         addOthername(oid, value.toString(), value_type);
104                 }
105             }
106             else if (tag == 1 || tag == 2 || tag == 6)
107             {
108                 const string value = transcode(obj.toString(),
109                                                LATIN1_CHARSET,
110                                                LOCAL_CHARSET);
111                 
112                 if (tag == 1) addAttribute("RFC822", value);
113                 if (tag == 2) addAttribute("DNS", value);
114                 if (tag == 6) addAttribute("URI", value);
115             }
116             else if (tag == 7)
117             {
118                 if (obj.value.length == 4)
119                 {
120                     const uint ip = loadBigEndian!uint(obj.value.ptr, 0);
121                     addAttribute("IP", ipv4ToString(ip));
122                 }
123             }
124 
125         }
126     }
127 
128     /*
129     * Return all of the alternative names
130     */
131     DictionaryListRef!(string, string) contents() const
132     {
133         DictionaryListRef!(string, string) names;
134 
135         foreach(const ref string k, const ref string v; m_alt_info) {
136             names.insert(k, v);
137         }
138 
139         foreach(const ref OID oid, const ref ASN1String asn1_str; m_othernames) {
140             names.insert(OIDS.lookup(oid), asn1_str.value());
141         }
142 
143         return names;
144     }
145   
146     /*
147     * Add an attribute to an alternative name
148     */
149     void addAttribute(in string type, in string str)
150     {
151         if (type == "" || str == "")
152             return;
153 
154         bool exists;
155         void adder(in string val) { 
156             if (val == str)
157                 exists = true;
158         }
159         m_alt_info.getValuesAt(type, &adder);
160 
161         if (!exists)
162             m_alt_info.insert(type, str);
163     }
164     
165     /*
166     * Get the attributes of this alternative name
167     */
168     const(DictionaryListRef!(string, string)) getAttributes() const
169     {
170         return m_alt_info;
171     }
172 
173     /*
174     * Add an OtherName field
175     */
176     void addOthername(in OID oid, in string value, ASN1Tag type)
177     {
178         if (value == "")
179             return;
180         m_othernames.insert(oid, ASN1String(value, type));
181     }
182 
183     /*
184     * Get the otherNames
185     */
186     const(DictionaryListRef!(OID, ASN1String)) getOthernames() const
187     {
188         return m_othernames;
189     }
190 
191     /*
192     * Return if this object has anything useful
193     */
194     bool hasItems() const
195     {
196         return (m_alt_info.length > 0 || m_othernames.length > 0);
197     }
198 
199     /*
200     * Create an AlternativeName
201     */
202     this(in string email_addr = "",
203          in string uri = "",
204          in string dns = "",
205          in string ip = "")
206     {
207         addAttribute("RFC822", email_addr);
208         addAttribute("DNS", dns);
209         addAttribute("URI", uri);
210         addAttribute("IP", ip);
211     }
212 
213 private:
214     DictionaryListRef!(string, string) m_alt_info;
215     DictionaryListRef!(OID, ASN1String) m_othernames;
216 }
217 
218 
219 
220 /*
221 * Check if type is a known ASN.1 string type
222 */
223 bool isStringType(ASN1Tag tag)
224 {
225     return (tag == ASN1Tag.NUMERIC_STRING ||
226             tag == ASN1Tag.PRINTABLE_STRING ||
227             tag == ASN1Tag.VISIBLE_STRING ||
228             tag == ASN1Tag.T61_STRING ||
229             tag == ASN1Tag.IA5_STRING ||
230             tag == ASN1Tag.UTF8_STRING ||
231             tag == ASN1Tag.BMP_STRING);
232 }
233 
234 
235 /*
236 * DER encode an AlternativeName entry
237 */
238 void encodeEntries(ref DEREncoder encoder,
239                    in DictionaryListRef!(string, string) attr,
240                    string type, ASN1Tag tagging)
241 {
242     void checker(in string alt_name) {
243         
244         if (type == "RFC822" || type == "DNS" || type == "URI")
245         {
246             ASN1String asn1_string = ASN1String(alt_name, ASN1Tag.IA5_STRING);
247             encoder.addObject(tagging, ASN1Tag.CONTEXT_SPECIFIC, asn1_string.iso8859());
248         }
249         else if (type == "IP")
250         {
251             const uint ip = stringToIpv4(alt_name);
252             ubyte[4] ip_buf;
253             storeBigEndian(ip, &ip_buf);
254             encoder.addObject(tagging, ASN1Tag.CONTEXT_SPECIFIC, ip_buf.ptr, 4);
255         }
256     }
257     attr.getValuesAt(type, &checker);
258 }