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 }