1 /**
2 * SCAN Name Abstraction
3 * 
4 * Copyright:
5 * (C) 2008-2009 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.algo_base.scan_token;
12 
13 import botan.utils.parsing;
14 import botan.utils.exceptn;
15 import std.exception;
16 import std.array : Appender;
17 import botan.utils.types;
18 import std.conv : to;
19 import core.sync.mutex;
20 import memutils.hashmap;
21 import botan.constants;
22 
23 private HashMap!(string, SCANToken) m_cache;
24 
25 /**
26 * A class encapsulating a SCAN name (similar to JCE conventions)
27 * http://www.users.zetnet.co.uk/hopwood/crypto/scan/
28 */
29 struct SCANToken
30 {
31 public:
32     /**
33     * Params:
34     *  algo_spec = A SCAN-format name
35     */
36     this(string algo_spec)
37     {
38 		if (auto tok = algo_spec in m_cache) {
39 			m_args = tok.m_args;
40 			m_mode_info = tok.m_mode_info;
41 			m_alg_name = tok.m_alg_name;
42 			m_orig_algo_spec = tok.m_orig_algo_spec;
43 			return;
44 		}
45 		scope(exit) m_cache[algo_spec] = this;
46         m_args = Array!string();
47         m_mode_info = Array!string();
48         m_orig_algo_spec = algo_spec;
49         
50         Vector!( Pair!(size_t, string)  ) names;
51         size_t level = 0;
52         Array!ubyte def_buf;
53         def_buf.reserve(8);
54         auto accum = makePair(level, def_buf);
55         
56         string decoding_error = "Bad SCAN name '" ~ algo_spec ~ "': ";
57         
58         algo_spec = derefAlias(algo_spec);
59         
60         foreach (immutable(char) c; algo_spec)
61         {
62 
63             if (c == '/' || c == ',' || c == '(' || c == ')')
64             {
65                 if (c == '(')
66                     ++level;
67                 else if (c == ')')
68                 {
69                     if (level == 0)
70                         throw new DecodingError(decoding_error ~ "Mismatched parens");
71                     --level;
72                 }
73                 
74                 if (c == '/' && level > 0) {
75                     accum.second ~= c;
76 
77                 }
78                 else
79                 {
80                     if (accum.second.length > 0) {
81                         names ~= makePair(accum.first, derefAlias(cast(string)accum.second[]));
82                     }
83                     Array!ubyte str;
84                     str.reserve(8);
85                     accum = makePair(level, str);
86                 }
87             }
88             else {
89                 accum.second ~= c;
90             }
91         }
92         
93         if (accum.second.length > 0) {
94             names ~= makePair(accum.first, derefAlias(cast(string)accum.second[]));
95         }
96         if (level != 0)
97             throw new DecodingError(decoding_error ~ "Missing close paren");
98         
99         if (names.length == 0)
100             throw new DecodingError(decoding_error ~ "Empty name");
101         
102         m_alg_name = names[0].second[].idup;
103 
104         if (names.length == 1)
105             return;
106 
107         bool in_modes;
108         size_t i = 1;
109         foreach (name; names[1 .. $])
110         {
111             if (name.first == 0)
112             {
113                 string val = makeArg(names, i);
114                 m_mode_info.pushBack(val);
115                 in_modes = true;
116             }
117             else if (name.first == 1 && !in_modes) { 
118                 string val = makeArg(names, i);
119                 m_args.pushBack(val);
120             }
121             i++;
122         }
123     }
124     
125     /**
126     * Returns: original input string
127     */
128     string toString() const { return m_orig_algo_spec; }
129     
130     /**
131     * Returns: algorithm name
132     */
133     @property string algoName() const { return m_alg_name; }
134     
135     /**
136     * Returns: algorithm name plus any arguments
137     */
138     string algoNameAndArgs() const
139     {
140         Appender!string output;
141         
142         output ~= algoName;
143         
144         if (argCount())
145         {
146             output ~= '(';
147             foreach (size_t i; 0 .. argCount())
148             {
149                 output ~= arg(i);
150                 if (i != argCount() - 1)
151                     output ~= ',';
152             }
153             output ~= ')';
154             
155         }
156         
157         return output.data;
158     }
159     
160     /**
161     * Returns: number of arguments
162     */
163     size_t argCount() const { return m_args.length; }
164     
165     /**
166     * Params:
167     *  lower = is the lower bound
168     *  upper = is the upper bound
169     * 
170     * Returns: true if the number of arguments is between lower and upper
171     */
172     bool argCountBetween(size_t lower, size_t upper) const
173     { return ((argCount() >= lower) && (argCount() <= upper)); }
174     
175     /**
176     * Params:
177     *  i = which argument
178     * 
179     * Returns: ith argument
180     */
181     string arg(size_t i) const
182     {
183         if (i >= argCount())
184             throw new RangeError("SCANToken.argument - i out of range");
185         return m_args[i];
186     }
187     
188     /**
189     * Params:
190     *  i = which argument
191     *  def_value = the default value
192     * 
193     * Returns: ith argument or the default value
194     */
195     string arg(size_t i, in string def_value) const
196     {
197         if (i >= argCount())
198             return def_value;
199         return m_args[i];
200     }
201     
202     /**
203     * Params:
204     *  i = which argument
205     *  def_value = the default value
206     * 
207     * Returns: ith argument as an integer, or the default value
208     */
209     size_t argAsInteger(size_t i, size_t def_value) const
210     {
211         if (i >= argCount())
212             return def_value;
213         return to!uint(m_args[i]);
214     }
215     
216     /**
217     * Returns: cipher mode (if any)
218     */
219     string cipherMode() const
220     { return (m_mode_info.length >= 1) ? m_mode_info[0] : ""; }
221     
222     /**
223     * Returns: cipher mode padding (if any)
224     */
225     string cipherModePad() const
226     { return (m_mode_info.length >= 2) ? m_mode_info[1] : ""; }
227     
228     static void addAlias(string _alias, in string basename)
229     {
230         if (!s_alias_map.get(_alias, null))
231             s_alias_map[_alias] = basename;
232     }
233 
234     
235     static string derefAlias(string input, bool keep = false)
236     {
237         auto name = s_alias_map.get(input, null);
238         if (name)
239             return name;
240         else if (keep)
241 			return input;
242 		else
243             return input.idup;
244     }
245 
246     static void setDefaultAliases()
247     {
248         // common variations worth supporting
249         addAlias("EME-PKCS1-v1_5",  "PKCS1v15");
250         addAlias("3DES",            "TripleDES");
251         addAlias("DES-EDE",         "TripleDES");
252         addAlias("CAST5",           "CAST-128");
253         addAlias("SHA1",            "SHA-160");
254         addAlias("SHA-1",           "SHA-160");
255         addAlias("MARK-4",          "RC4(256)");
256         addAlias("ARC4",            "RC4");
257         addAlias("OMAC",            "CMAC");
258             
259         addAlias("EMSA-PSS",        "PSSR");
260         addAlias("PSS-MGF1",        "PSSR");
261         addAlias("EME-OAEP",        "OAEP");
262             
263         addAlias("EMSA2",           "EMSA_X931");
264         addAlias("EMSA3",           "EMSA_PKCS1");
265         addAlias("EMSA-PKCS1-v1_5", "EMSA_PKCS1");
266             
267             // should be renamed in sources
268         addAlias("X9.31",           "EMSA2");
269             
270             // kept for compatability with old library versions
271         addAlias("EMSA4",           "PSSR");
272         addAlias("EME1",            "OAEP");
273             
274             // probably can be removed
275         addAlias("GOST",            "GOST-28147-89");
276         addAlias("GOST-34.11",      "GOST-R-34.11-94");
277     }
278     
279 
280 private:
281     static HashMapRef!(string, string) s_alias_map;
282     
283     string m_orig_algo_spec;
284     string m_alg_name;
285     Array!string m_args;
286     Array!string m_mode_info;
287 }
288 
289 private:
290 
291 string makeArg(ref Vector!(Pair!(size_t, string)) names, size_t start)
292 {
293     Appender!string output;
294     output ~= names[start].second;
295     size_t level = names[start].first;
296 
297     size_t paren_depth = 0;
298 
299     foreach (name; names[(start + 1) .. $])
300     {
301         if (name.first <= names[start].first)
302             break;
303 
304         if (name.first > level)
305         {
306             output ~= '(' ~ name.second;
307             ++paren_depth;
308         }
309         else if (name.first < level)
310         {
311             output ~= ")," ~ name.second;
312             --paren_depth;
313         }
314         else
315         {
316             if (output.data[output.data.length - 1] != '(')
317                 output ~= ",";
318             output ~= name.second;
319         }
320 
321         level = name.first;
322     }
323 
324     foreach (size_t i; 0 .. paren_depth)
325         output ~= ')';
326 
327     return output.data;
328 }