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 }