1 /**
2 * EAC ASN.1 Objects
3 * 
4 * Copyright:
5 * (C) 2007-2008 FlexSecure GmbH
6 *      2008-2010 Jack Lloyd
7 * (C) 2014-2015 Etienne Cimon
8 *
9 * License:
10 * Botan is released under the Simplified BSD License (see LICENSE.md)
11 */
12 module botan.cert.cvc.eac_asn_obj;
13 
14 import botan.constants;
15 static if (BOTAN_HAS_CARD_VERIFIABLE_CERTIFICATES):
16 
17 import botan.asn1.asn1_obj;
18 import botan.asn1.der_enc;
19 import botan.asn1.ber_dec;
20 import botan.utils.rounding;
21 import botan.utils.charset;
22 import botan.utils.parsing;
23 import std.datetime;
24 import botan.utils.types;
25 import std.array : Appender;
26 
27 alias ASN1Car = RefCounted!ASN1CarImpl;
28 alias ASN1Chr = RefCounted!ASN1ChrImpl;
29 alias ASN1Cex = RefCounted!ASN1CexImpl;
30 alias ASN1Ced = RefCounted!ASN1CedImpl;
31 alias EACTime = RefCounted!EACTimeImpl;
32 
33 /**
34 * This class represents CVC EAC Time objects.
35 * It only models year, month and day. Only limited sanity checks of
36 * the inputted date value are performed.
37 */
38 class EACTimeImpl : ASN1Object
39 {
40 public:
41 
42     /*
43     * DER encode a EACTime
44     */
45     override void encodeInto(ref DEREncoder der) const
46     {
47         der.addObject(m_tag, ASN1Tag.APPLICATION, encodedEacTime());
48     }
49 
50     /*
51     * Decode a BER encoded EACTime
52     */
53     override void decodeFrom(ref BERDecoder source)
54     {
55         BERObject obj = source.getNextObject();
56         
57         if (obj.type_tag != this.m_tag)
58             throw new BERDecodingError("Tag mismatch when decoding");
59         if (obj.value.length != 6)
60         {
61             throw new DecodingError("EACTime decoding failed: decoded length was not 6");
62         }
63         
64         try
65         {
66             uint tmp_year = decTwoDigit(obj.value[0], obj.value[1]);
67             uint tmp_mon = decTwoDigit(obj.value[2], obj.value[3]);
68             uint tmp_day = decTwoDigit(obj.value[4], obj.value[5]);
69             year = tmp_year + 2000;
70             month = tmp_mon;
71             day = tmp_day;
72         }
73         catch (InvalidArgument e)
74         {
75             throw new DecodingError("EACTime decoding failed (" ~ e.msg ~ ")");
76         }
77         
78     }
79 
80     /**
81     * Return a string representation of the time
82     * Returns: date string
83     */
84     override string toString() const
85     {
86         if (timeIsSet() == false)
87             throw new InvalidState("toString: No time set");
88         
89         return to!string(year * 10000 + month * 100 + day);
90     }
91 
92 
93     /**
94     * Get a this objects value as a readable formatted string.
95     * Returns: date string
96     */
97     string readableString() const
98     {
99         if (timeIsSet() == false)
100             throw new InvalidState("readableString: No time set");
101         
102         import std.string : format;
103         return format("%04d/%02d/%02d", year, month, day);
104     }
105 
106     /**
107     * Find out whether this object's values have been set.
108     * Returns: true if this object's internal values are set
109     */
110     bool timeIsSet() const
111     {
112         return (year != 0);
113     }
114 
115     /**
116     * Compare this to another EACTime object.
117     * Returns: -1 if this object's date is earlier than
118     * other, +1 in the opposite case, and 0 if both dates are
119     * equal.
120     */
121     int cmp(in EACTimeImpl other) const
122     {
123         if (timeIsSet() == false)
124             throw new InvalidState("cmp: No time set");
125         
126         const int EARLIER = -1, LATER = 1, SAME_TIME = 0;
127         
128         if (year < other.year)      return EARLIER;
129         if (year > other.year)      return LATER;
130         if (month < other.month)    return EARLIER;
131         if (month > other.month)    return LATER;
132         if (day < other.day)        return EARLIER;
133         if (day > other.day)        return LATER;
134         
135         return SAME_TIME;
136     }
137 
138 
139     /**
140     * Set this' value by a human readable string
141     * Params:
142     *  time_str = a string in the format "yyyy mm dd",
143     * e.g. "2007 08 01"
144     */
145     void setTo(in string time_str = "")
146     {
147         if (time_str == "")
148         {
149             year = month = day = 0;
150             return;
151         }
152         
153         Vector!string params;
154         Vector!ubyte current;
155         current.reserve(time_str.length);
156         
157         for (uint j = 0; j != time_str.length; ++j)
158         {
159             if (isDigit(time_str[j]))
160                 current ~= time_str[j];
161             else
162             {
163                 if (current.length > 0)
164                     params.pushBack(current[].idup); // fixme: GC allocations
165                 current.clear();
166             }
167         }
168         if (current.length > 0)
169             params.pushBack(current[].idup);
170         
171         if (params.length != 3)
172             throw new InvalidArgument("Invalid time specification " ~ time_str);
173         
174         year    = to!uint(params[0]);
175         month   = to!uint(params[1]);
176         day     = to!uint(params[2]);
177         
178         if (!passesSanityCheck())
179             throw new InvalidArgument("Invalid time specification " ~ time_str ~ " => " ~ year.to!string ~ "-" ~ month.to!string ~ "-" ~ day.to!string);
180     }
181 
182     /**
183     * Add the specified number of years to this.
184     *
185     * Params:
186     *  years = the number of years to add
187     */
188     void addYears(uint years)
189     {
190         year += years;
191     }
192 
193 
194     /**
195     * Add the specified number of months to this.
196     *
197     * Params:
198     *  months = the number of months to add
199     */
200     void addMonths(uint months)
201     {
202         year += months/12;
203         month += months % 12;
204         if (month > 12)
205         {
206             year += 1;
207             month -= 12;
208         }
209     }
210 
211     /**
212     * Get the year value of this objects.
213     * Returns: year value
214     */
215     uint getYear() const { return year; }
216 
217     /**
218     * Get the month value of this objects.
219     * Returns: month value
220     */
221     uint getMonth() const { return month; }
222 
223     /**
224     * Get the day value of this objects.
225     * Returns: day value
226     */
227     uint getDay() const { return day; }
228 
229     /*
230     * Create an EACTime
231     */
232     this(in SysTime time, ASN1Tag t = (cast(ASN1Tag) 0))
233     {
234         m_tag = t;
235         
236         year = time.year;
237         month = time.month;
238         day    = time.day;
239     }
240 
241     /*
242     * Create an EACTime
243     */
244     this(in string t_spec = "", ASN1Tag t = (cast(ASN1Tag) 0))
245     {
246         m_tag = t;
247         setTo(t_spec);
248     }
249 
250     /*
251     * Create an EACTime
252     */
253     this(uint y, uint m, uint d, ASN1Tag t = (cast(ASN1Tag) 0))
254     {
255         year = y;
256         month = m;
257         day = d;
258         m_tag = t;
259     }
260 
261     /*
262     * Compare two EACTimes for in various ways
263     */
264     bool opEquals(in EACTimeImpl t2) const
265     {
266         return (cmp(t2) == 0);
267     }
268     
269     int opCmp(in EACTimeImpl t2) const
270     {
271         return cmp(t2);
272     }
273 
274     ~this() {}
275 private:
276     /*
277     * make the value an octet string for encoding
278     */
279     Vector!ubyte encodedEacTime() const
280     {
281         Vector!ubyte result;
282         result.reserve(6);
283         result ~= encTwoDigitArr(year).ptr[0..2];
284         result ~= encTwoDigitArr(month).ptr[0..2];
285         result ~= encTwoDigitArr(day).ptr[0..2];
286         return result;
287     }
288 
289     /*
290     * Do a general sanity check on the time
291     */
292     bool passesSanityCheck() const
293     {
294         if (year < 2000 || year > 2099)
295             return false;
296         if (month == 0 || month > 12)
297             return false;
298         if (day == 0 || day > 31)
299             return false;
300         
301         return true;
302     }
303 
304     uint year, month, day;
305     ASN1Tag m_tag;
306 }
307 
308 /**
309 * This class represents CVC CEDs. Only limited sanity checks of
310 * the inputted date value are performed.
311 */
312 final class ASN1CedImpl : EACTimeImpl
313 {
314 public:
315     /**
316     * Construct a CED from a string value.
317     *
318     * Params:
319     *  str = a string in the format "yyyy mm dd",
320     * e.g. "2007 08 01"
321     */
322     this(in string str = "") {
323         super(str, (cast(ASN1Tag)37));
324     }
325 
326     /**
327     * Construct a CED from a time point
328     */
329     this(in SysTime time) {
330         super(time, (cast(ASN1Tag)37));
331     }
332 
333     /**
334     * Copy constructor (for general EACTime objects).
335     *
336     * Params:
337     *  other = the object to copy from
338     */
339     this(in EACTime other)
340     {
341         super(other.getYear(), other.getMonth(), other.getDay(), (cast(ASN1Tag)37));
342     }
343 
344     this(const ref EACTime other)
345     {
346         super(other.getYear(), other.getMonth(), other.getDay(), (cast(ASN1Tag)37));
347     }
348 
349     this(const ref ASN1Ced other) {
350         super(other.getYear(), other.getMonth(), other.getDay(), (cast(ASN1Tag)37));
351     }
352 }
353 
354 /**
355 * This class represents CVC CEXs. Only limited sanity checks of
356 * the inputted date value are performed.
357 */
358 final class ASN1CexImpl : EACTimeImpl
359 {
360 public:
361     /**
362     * Construct a CEX from a string value.
363     *
364     * Params:
365     *  str = a string in the format "yyyy mm dd",
366     * e.g. "2007 08 01"
367     */
368     this(in string str = "") 
369     {
370         super(str, (cast(ASN1Tag)36));
371     }
372 
373     this(in SysTime time)
374     {
375         super(time, (cast(ASN1Tag)36));
376     }
377 
378     this(const ref EACTime other)
379     {
380         super(other.getYear(), other.getMonth(), other.getDay(), (cast(ASN1Tag)36));
381     }
382 
383     this(in EACTimeImpl other)
384     {
385         super(other.getYear(), other.getMonth(), other.getDay(), (cast(ASN1Tag)36));
386     }
387     
388     this(const ref ASN1Cex other) {
389         super(other.getYear(), other.getMonth(), other.getDay(), (cast(ASN1Tag)36));
390     }
391 }
392 
393 /**
394 * Base class for car/chr of cv certificates.
395 */
396 class ASN1EACString : ASN1Object
397 {
398 public:
399     /*
400     * DER encode an ASN1EACString
401     */
402     override void encodeInto(ref DEREncoder encoder) const
403     {
404         string value = iso8859();
405         encoder.addObject(tagging(), ASN1Tag.APPLICATION, value);
406     }
407     
408     /*
409     * Decode a BER encoded ASN1EACString
410     */
411     override void decodeFrom(ref BERDecoder source)
412     {
413         BERObject obj = source.getNextObject();
414         
415         if (obj.type_tag != this.m_tag)
416         {
417             Appender!string ss;
418             ss ~= "ASN1EACString tag mismatch, tag was " ~ obj.type_tag.to!string ~ " expected " ~ this.m_tag.to!string;
419             
420             throw new DecodingError(ss.data);
421         }
422 
423         CharacterSet charset_is = LATIN1_CHARSET;
424         
425         try
426         {
427             m_iso_8859_str = transcode(obj.toString(), charset_is, LOCAL_CHARSET);
428             m_tag = obj.type_tag;
429         }
430         catch(InvalidArgument inv_arg)
431         {
432             throw new DecodingError("ASN1EACString decoding failed: " ~ inv_arg.msg);
433         }
434     }
435     
436 
437     /**
438     * Get this objects string value.
439     * Returns: string value
440     */
441     string value() const
442     {
443         return transcode(m_iso_8859_str, LATIN1_CHARSET, LOCAL_CHARSET);
444     }
445 
446     /**
447     * Get this objects string value.
448     * Returns: string value in iso8859 encoding
449     */
450     string iso8859() const
451     {
452         return m_iso_8859_str;
453     }
454 
455     /*
456     * Return the type of this string object
457     */
458     ASN1Tag tagging() const
459     {
460         return m_tag;
461     }
462 
463     /*
464     * Create an ASN1EACString
465     */
466     this(in string str, ASN1Tag t)
467     {
468         m_tag = t;
469         m_iso_8859_str = transcode(str, LOCAL_CHARSET, LATIN1_CHARSET);
470         
471         if (!sanityCheck())
472             throw new InvalidArgument("ASN1EACString contains illegal characters");
473     }
474 
475     bool opEquals(in ASN1EACString rhs) const
476     {
477         return (iso8859() == rhs.iso8859());
478     }
479 
480     bool opCmp(string op)(in ASN1EACString rhs)
481         if (op == "!=")
482     {
483         return !(lhs == rhs);
484     }
485 
486     ~this() {}
487 protected:
488     // checks for compliance to the alphabet defined in TR-03110 v1.10, 2007-08-20
489     // p. 43
490     bool sanityCheck() const
491     {
492         const(ubyte)* rep = cast(const(ubyte)*) m_iso_8859_str.ptr;
493         const size_t rep_len = m_iso_8859_str.length;
494         
495         foreach (size_t i; 0 .. rep_len)
496         {
497             if ((rep[i] < 0x20) || ((rep[i] >= 0x7F) && (rep[i] < 0xA0)))
498                 return false;
499         }
500         
501         return true;
502     }
503 
504     string m_iso_8859_str;
505     ASN1Tag m_tag;
506 }
507 
508 /**
509 * This class represents CARs of CVCs. (String tagged with 2)
510 */
511 final class ASN1CarImpl : ASN1EACString
512 {
513 public:
514     /**
515     * Create a CAR with the specified content.
516     *
517     * Params:
518     *  str = the CAR value
519     */
520     this(in string str = "")
521     {
522         super(str, (cast(ASN1Tag)2));
523 
524     }
525 
526     this(const ref ASN1Car other) {
527         super(m_iso_8859_str, m_tag);
528     }
529         
530 }
531 
532 /**
533 * This class represents CHRs of CVCs (tag 32)
534 */
535 final class ASN1ChrImpl : ASN1EACString
536 {
537 public:
538     /**
539     * Create a CHR with the specified content.
540     *
541     * Params:
542     *  str = the CHR value
543     */
544     this(in string str = "")
545     {
546         super(str, (cast(ASN1Tag)32));
547     }
548 
549     this(const ref ASN1Chr other) {
550         super(m_iso_8859_str, m_tag);
551     }
552 
553 }
554 
555 
556 Vector!ubyte encTwoDigit(uint input)
557 {
558     ubyte[2] res = encTwoDigitArr(input);
559     return Vector!ubyte(res.ptr[0 .. 2]);
560 }
561 
562 ubyte[2] encTwoDigitArr(uint input)
563 {
564     ubyte[2] result;
565     input %= 100;
566     if (input < 10)
567         result[0] = 0x00;
568     else
569     {
570         uint y_first_pos = roundDown!uint(input, 10) / 10;
571         result[0] = cast(ubyte) y_first_pos;
572     }
573     
574     uint y_sec_pos = input % 10;
575     result[1] = cast(ubyte) y_sec_pos;
576     return result;
577 }
578 
579 uint decTwoDigit(ubyte b1, ubyte b2)
580 {
581     uint upper = b1;
582     uint lower = b2;
583     
584     if (upper > 9 || lower > 9)
585         throw new InvalidArgument("CVC decTwoDigit value too large");
586     
587     return upper*10 + lower;
588 }