1 /** 2 * --------- 3 * |.##> <##.| Open Smart Card Development Platform (www.openscdp.org) 4 * |# #| 5 * |# #| Copyright (c) 1999-2009 CardContact Software & System Consulting 6 * |'##> <##'| Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de) 7 * --------- 8 * 9 * This file is part of OpenSCDP. 10 * 11 * OpenSCDP is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License version 2 as 13 * published by the Free Software Foundation. 14 * 15 * OpenSCDP is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with OpenSCDP; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 * 24 * @fileoverview Support for card verifiable certificates and certificate requests according to EAC 1.1/2.0 25 */ 26 27 28 // Imports 29 var PublicKeyReference = require('scsh/eac/PublicKeyReference').PublicKeyReference; 30 31 32 33 /** 34 * Create a CVC object from a DER encoded ByteString. 35 * 36 * @class Class implementing a decoder for card verifiable certificates or requests according to 37 * Extended Access Control (EAC) as defined in BSI TR-03110 1.11 and 2.02. 38 * @constructor 39 * @param {ByteString} param the DER encoded certificate 40 * @return 41 */ 42 function CVC() { 43 if (arguments.length > 0) { 44 var arg = arguments[0]; 45 if (arg instanceof ASN1) { 46 this.asn = arg; 47 this.bin = this.asn.getBytes(); 48 } else if (arg instanceof ByteString) { 49 this.bin = arg; 50 this.asn = new ASN1(this.bin); 51 } else { 52 throw new GPError("CVC", GPError.INVALID_DATA, 0, "Argument must be of type ByteString or ASN1"); 53 } 54 if (this.asn.tag == CVC.TAG_AT) { 55 this.body = this.asn.get(0).get(0); 56 } else if (this.asn.tag == CVC.TAG_CVC) { 57 this.body = this.asn.get(0); 58 } else { 59 throw new GPError("CVC", GPError.INVALID_DATA, 0, "Argument is neither a CVC or CVC request"); 60 } 61 } 62 } 63 64 exports.CVC = CVC; 65 66 CVC.crypto = new Crypto(); 67 68 69 /** Authentication Template */ 70 CVC.TAG_AT = 0x67; 71 /** CV Certificate */ 72 CVC.TAG_CVC = 0x7F21; 73 /** Certificate Body */ 74 CVC.TAG_BODY = 0x7F4E; 75 /** Certificate Profile Identifier */ 76 CVC.TAG_CPI = 0x5F29; 77 /** Certification Authority Reference */ 78 CVC.TAG_CAR = 0x42; 79 /** Public Key */ 80 CVC.TAG_PUK = 0x7F49; 81 /** Prime Modulus */ 82 CVC.TAG_ECC_P = 0x81; 83 /** First coefficient a */ 84 CVC.TAG_ECC_A = 0x82; 85 /** Second coefficient b */ 86 CVC.TAG_ECC_B = 0x83; 87 /** Base Point G */ 88 CVC.TAG_ECC_G = 0x84; 89 /** Order of the base point */ 90 CVC.TAG_ECC_N = 0x85; 91 /** Public Point y */ 92 CVC.TAG_ECC_Q = 0x86; 93 /** Cofactor f */ 94 CVC.TAG_ECC_H = 0x87; 95 /** Certificate Holder Reference */ 96 CVC.TAG_CHR = 0x5F20; 97 /** Certificate Holder Authorisation Template */ 98 CVC.TAG_CHAT = 0x7F4C; 99 /** Relative Authorization */ 100 CVC.TAG_AUT = 0x53; 101 /** Certificate Extension */ 102 CVC.TAG_EXTN = 0x65; 103 /** Certificate Effective Date */ 104 CVC.TAG_CED = 0x5F25; 105 /** Certificate Expiration Date */ 106 CVC.TAG_CXD = 0x5F24; 107 /** Signature */ 108 CVC.TAG_SIG = 0x5F37; 109 110 111 /** Table of tag names */ 112 CVC.OBJECTNAMES = [] 113 CVC.OBJECTNAMES[CVC.TAG_AT] = "Authentication Template"; 114 CVC.OBJECTNAMES[CVC.TAG_CVC] = "CV Certificate"; 115 CVC.OBJECTNAMES[CVC.TAG_BODY] = "Certificate Body"; 116 CVC.OBJECTNAMES[CVC.TAG_CPI] = "Certificate Profile Indicator"; 117 CVC.OBJECTNAMES[CVC.TAG_CAR] = "Certification Authority Reference"; 118 CVC.OBJECTNAMES[CVC.TAG_PUK] = "Public Key"; 119 CVC.OBJECTNAMES[CVC.TAG_ECC_P] = "Prime/Modulus"; 120 CVC.OBJECTNAMES[CVC.TAG_ECC_A] = "First coefficient a/Exponent"; 121 CVC.OBJECTNAMES[CVC.TAG_ECC_B] = "Second coefficient b"; 122 CVC.OBJECTNAMES[CVC.TAG_ECC_G] = "Base Point G"; 123 CVC.OBJECTNAMES[CVC.TAG_ECC_N] = "Order of the base point"; 124 CVC.OBJECTNAMES[CVC.TAG_ECC_Q] = "Public Point y"; 125 CVC.OBJECTNAMES[CVC.TAG_ECC_H] = "Cofactor f"; 126 CVC.OBJECTNAMES[CVC.TAG_CHR] = "Certificate Holder Reference"; 127 CVC.OBJECTNAMES[CVC.TAG_CHAT] = "Certificate Holder Authentication Template"; 128 CVC.OBJECTNAMES[CVC.TAG_AUT] = "Relative Authorization"; 129 CVC.OBJECTNAMES[CVC.TAG_EXTN] = "Extension"; 130 CVC.OBJECTNAMES[CVC.TAG_CED] = "Certificate Effective Date"; 131 CVC.OBJECTNAMES[CVC.TAG_CXD] = "Certificate Expiration Date"; 132 CVC.OBJECTNAMES[CVC.TAG_SIG] = "Signature"; 133 134 CVC.PUK_SYNTAX = [ 135 { tag: 0x06, minlen: 2, maxlen: 20 }, 136 { tag: CVC.TAG_ECC_P, minlen: 20, maxlen: 512, optional: true }, 137 { tag: CVC.TAG_ECC_A, minlen: 1, maxlen: 512, optional: true }, 138 { tag: CVC.TAG_ECC_B, minlen: 20, maxlen: 66, optional: true }, 139 { tag: CVC.TAG_ECC_G, minlen: 41, maxlen: 133, optional: true }, 140 { tag: CVC.TAG_ECC_N, minlen: 20, maxlen: 66, optional: true }, 141 { tag: CVC.TAG_ECC_Q, minlen: 41, maxlen: 133, optional: true }, 142 { tag: CVC.TAG_ECC_H, minlen: 1, maxlen: 1, optional: true } 143 ]; 144 145 CVC.REQ_BODY_SYNTAX = [ 146 { tag: CVC.TAG_CPI, minlen: 1, maxlen: 1 }, 147 { tag: CVC.TAG_CAR, minlen: 8, maxlen: 16, optional: true }, 148 { tag: CVC.TAG_PUK, content: CVC.PUK_SYNTAX }, 149 { tag: CVC.TAG_CHR, minlen: 8, maxlen: 16 }, 150 { tag: CVC.TAG_EXTN, optional: true } 151 ]; 152 153 CVC.REQ_SYNTAX = [ 154 { tag: CVC.TAG_BODY, content: CVC.REQ_BODY_SYNTAX }, 155 { tag: CVC.TAG_SIG, minlen: 40, maxlen: 550 } 156 ]; 157 158 CVC.ATREQ_SYNTAX = [ 159 { tag: CVC.TAG_CVC, content: CVC.REQ_SYNTAX }, 160 { tag: CVC.TAG_CAR, minlen: 8, maxlen: 16 }, 161 { tag: CVC.TAG_SIG, minlen: 40, maxlen: 550 } 162 ]; 163 164 CVC.CHAT_SYNTAX = [ 165 { tag: 0x06, minlen: 2, maxlen: 20 }, 166 { tag: CVC.TAG_AUT, minlen: 1, maxlen: 5 } 167 ]; 168 169 CVC.CERT_BODY_SYNTAX = [ 170 { tag: CVC.TAG_CPI, minlen: 1, maxlen: 1 }, 171 { tag: CVC.TAG_CAR, minlen: 8, maxlen: 16 }, 172 { tag: CVC.TAG_PUK, content: CVC.PUK_SYNTAX }, 173 { tag: CVC.TAG_CHR, minlen: 8, maxlen: 16 }, 174 { tag: CVC.TAG_CHAT, content: CVC.CHAT_SYNTAX }, 175 { tag: CVC.TAG_CED, minlen: 6, maxlen: 6 }, 176 { tag: CVC.TAG_CXD, minlen: 6, maxlen: 6 }, 177 { tag: CVC.TAG_EXTN, optional: true } 178 ]; 179 180 CVC.CERT_SYNTAX = [ 181 { tag: CVC.TAG_BODY, content: CVC.CERT_BODY_SYNTAX }, 182 { tag: CVC.TAG_SIG, minlen: 24, maxlen: 550 } 183 ]; 184 185 186 187 /** Table of rights description for id-IS */ 188 CVC.ISRIGHTS = [ 189 "Read access to ePassport application: DG 3 (Fingerprint)", 190 "Read access to ePassport application: DG 4 (Iris)", 191 "RFU (Bit 3)", 192 "RFU (Bit 4)", 193 "RFU (Bit 5)", 194 "Read access to eID application" 195 ]; 196 CVC.idIS = new ByteString("id-IS", OID); 197 198 199 /** Table of rights description for id-AT */ 200 CVC.ATRIGHTS = [ 201 "Age Verification", 202 "Community ID Verification", 203 "Restricted Identification", 204 "Privileged Terminal", 205 "CAN allowed", 206 "PIN Management", 207 "Install Certificate", 208 "Install Qualified Certificate", 209 210 "Read Access DG 1 (Document Type)", 211 "Read Access DG 2 (Issuing State)", 212 "Read Access DG 3 (Date of Expiration)", 213 "Read Access DG 4 (Given Name)", 214 "Read Access DG 5 (Surname)", 215 "Read Access DG 6 (Pseudonym)", 216 "Read Access DG 7 (Academic Grade)", 217 "Read Access DG 8 (Date of Birth)", 218 219 "Read Access DG 9 (Place of Birth)", 220 "Read Access DG 10", 221 "Read Access DG 11", 222 "Read Access DG 12", 223 "Read Access DG 13", 224 "Read Access DG 14", 225 "Read Access DG 15", 226 "Read Access DG 16", 227 228 "Read Access DG 17 (Place of Residence)", 229 "Read Access DG 18 (Community ID)", 230 "Read Access DG 19 (Conditions I-eAT)", 231 "Read Access DG 20 (Conditions II-eAT)", 232 "Read Access DG 21", 233 "RFU (Bit 29)", 234 "RFU (Bit 30)", 235 "RFU (Bit 31)", 236 237 "RFU (Bit 32)", 238 "Write Access DG 21", 239 "Write Access DG 20 (Conditions II-eAT)", 240 "Write Access DG 19 (Conditions I-eAT)", 241 "Write Access DG 18 (Community ID)", 242 "Write Access DG 17 (Place of Residence)" 243 ]; 244 CVC.idAT = new ByteString("id-AT", OID); 245 246 247 248 /** Table of rights description for id-ST */ 249 CVC.STRIGHTS = [ 250 "Generate electronic signature", 251 "Generate qualified electronic signature", 252 "RFU (Bit 2)", 253 "RFU (Bit 3)", 254 "RFU (Bit 4)", 255 "RFU (Bit 5)" 256 ]; 257 CVC.idST = new ByteString("id-ST", OID); 258 259 CVC.idSC_HSM = new ByteString("2B0601040181C31F030101", HEX); 260 261 262 263 /** TA constants */ 264 CVC.id_TA_ECDSA = new ByteString("id-TA-ECDSA", OID); 265 CVC.id_TA_ECDSA_SHA_1 = new ByteString("id-TA-ECDSA-SHA-1", OID); 266 CVC.id_TA_ECDSA_SHA_224 = new ByteString("id-TA-ECDSA-SHA-224", OID); 267 CVC.id_TA_ECDSA_SHA_256 = new ByteString("id-TA-ECDSA-SHA-256", OID); 268 CVC.id_TA_ECDSA_SHA_384 = new ByteString("id-TA-ECDSA-SHA-384", OID); 269 CVC.id_TA_ECDSA_SHA_512 = new ByteString("id-TA-ECDSA-SHA-512", OID); 270 CVC.id_TA_RSA_v1_5_SHA_1 = new ByteString("id-TA-RSA-v1-5-SHA-1", OID); 271 CVC.id_TA_RSA_v1_5_SHA_256 = new ByteString("id-TA-RSA-v1-5-SHA-256", OID); 272 CVC.id_TA_RSA_v1_5_SHA_512 = new ByteString("id-TA-RSA-v1-5-SHA-512", OID); 273 CVC.id_TA_RSA_PSS_SHA_1 = new ByteString("id-TA-RSA-PSS-SHA-1", OID); 274 CVC.id_TA_RSA_PSS_SHA_256 = new ByteString("id-TA-RSA-PSS-SHA-256", OID); 275 CVC.id_TA_RSA_PSS_SHA_512 = new ByteString("id-TA-RSA-PSS-SHA-512", OID); 276 277 278 279 /** 280 * Return signature mechanism for object identifier 281 * 282 * @param {ByteString} oid the object identifer from the public key object 283 * @returns the signature mechanism as Crypto. constant or -1 if not defined 284 * @type Number 285 */ 286 CVC.getSignatureMech = function(oid) { 287 if (oid.equals(CVC.id_TA_ECDSA_SHA_1)) 288 return Crypto.ECDSA_SHA1; 289 if (oid.equals(CVC.id_TA_ECDSA_SHA_224)) 290 return Crypto.ECDSA_SHA224; 291 if (oid.equals(CVC.id_TA_ECDSA_SHA_256)) 292 return Crypto.ECDSA_SHA256; 293 if (oid.equals(CVC.id_TA_ECDSA_SHA_384)) 294 return Crypto.ECDSA_SHA384; 295 if (oid.equals(CVC.id_TA_ECDSA_SHA_512)) 296 return Crypto.ECDSA_SHA512; 297 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_1)) 298 return Crypto.RSA_SHA1; 299 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_256)) 300 return Crypto.RSA_SHA256; 301 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_512)) 302 return Crypto.RSA_SHA512; 303 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_1)) 304 return Crypto.RSA_PSS_SHA1; 305 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_256)) 306 return Crypto.RSA_PSS_SHA256; 307 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_512)) 308 return Crypto.RSA_PSS_SHA512; 309 return -1; 310 } 311 312 313 314 /** 315 * Return hash mechanism for object identifier 316 * 317 * @param {ByteString} oid the object identifer from the public key object 318 * @returns the hash mechanism as Crypto. constant or -1 if not defined 319 * @type Number 320 */ 321 CVC.getHashMech = function(oid) { 322 if (oid.equals(CVC.id_TA_ECDSA_SHA_1)) 323 return Crypto.SHA_1; 324 if (oid.equals(CVC.id_TA_ECDSA_SHA_224)) 325 return Crypto.SHA_224; 326 if (oid.equals(CVC.id_TA_ECDSA_SHA_256)) 327 return Crypto.SHA_256; 328 if (oid.equals(CVC.id_TA_ECDSA_SHA_384)) 329 return Crypto.SHA_384; 330 if (oid.equals(CVC.id_TA_ECDSA_SHA_512)) 331 return Crypto.SHA_512; 332 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_1)) 333 return Crypto.SHA_1; 334 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_256)) 335 return Crypto.SHA_256; 336 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_512)) 337 return Crypto.SHA_512; 338 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_1)) 339 return Crypto.SHA_1; 340 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_256)) 341 return Crypto.SHA_256; 342 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_512)) 343 return Crypto.SHA_512; 344 return -1; 345 } 346 347 348 349 /** 350 * Return true of the object identifier starts with id-TA-ECDSA 351 * 352 * @type boolean 353 * @return true, if ECDSA based OID 354 */ 355 CVC.isECDSA = function(oid) { 356 return oid.startsWith(CVC.id_TA_ECDSA) == CVC.id_TA_ECDSA.length; 357 } 358 359 360 361 /** 362 * Wrap an ECDSA signature in the format r || s into a TLV encoding as defined by RFC 3279 363 * 364 * @param signature ByteString containing the concatenation of r and s as unsigned integer values 365 * @returns ASN.1 SEQUENCE objects containing two signed integer r and s 366 */ 367 CVC.wrapSignature = function(signature) { 368 var len = signature.length / 2; 369 370 // r and s are unsigned big integer. We might need to pad a zero for ASN.1 INTEGER which is signed 371 var r = signature.bytes(0, len); 372 while ((r.length > 1) && (r.byteAt(0) == 0)) { 373 r = r.bytes(1); 374 } 375 if (r.byteAt(0) >= 0x80) { 376 r = ByteString.valueOf(0, 1).concat(r); 377 } 378 379 var s = signature.bytes(len, len); 380 while ((s.length > 1) && (s.byteAt(0) == 0)) { 381 s = s.bytes(1); 382 } 383 if (s.byteAt(0) >= 0x80) { 384 s = ByteString.valueOf(0, 1).concat(s); 385 } 386 387 var t = new ASN1(ASN1.SEQUENCE); 388 t.add(new ASN1(ASN1.INTEGER, r)); 389 t.add(new ASN1(ASN1.INTEGER, s)); 390 391 return t.getBytes(); 392 } 393 394 395 396 /** 397 * Integer to octet string conversion 398 */ 399 CVC.I2O = function(value, length) { 400 if (value.length > length) { 401 value = value.right(length); 402 } 403 while (value.length < length) { 404 value = CVC.PAD.left((length - value.length - 1 & 15) + 1).concat(value); 405 } 406 return value; 407 } 408 CVC.PAD = new ByteString("00000000000000000000000000000000", HEX); 409 410 411 412 /** 413 * Unwrap a ECDSA signature from the TLV encoding according to RFC3279 into the concatenation 414 * of the unsigned integer r and s 415 * 416 * @param signature TLV encoded signature 417 * @returns concatenation of r and s 418 */ 419 CVC.unwrapSignature = function(signature, keylen) { 420 var t = new ASN1(signature); 421 if (typeof(keylen) != "undefined") { 422 var r = CVC.I2O(t.get(0).value, keylen); 423 var s = CVC.I2O(t.get(1).value, keylen); 424 } else { 425 var r = t.get(0).value; 426 if (r.byteAt(0) == 00) 427 r = r.bytes(1); 428 429 var s = t.get(1).value; 430 if (s.byteAt(0) == 00) 431 s = s.bytes(1); 432 } 433 434 return r.concat(s); 435 } 436 437 438 439 /** 440 * Rewrap an ECDSA signature that contains redundant leading zeros in integer. 441 * 442 * @param signature the signature 443 * @type ByteString 444 * @param the fixed signature 445 */ 446 CVC.rewrapSignature = function(signature) { 447 var a = new ASN1(signature); 448 var r = a.get(0).value; 449 while ((r.byteAt(0) == 0) && (r.byteAt(1) < 0x80)) { 450 r = r.bytes(1); 451 } 452 453 var s = a.get(1).value; 454 while ((s.byteAt(0) == 0) && (s.byteAt(1) < 0x80)) { 455 s = s.bytes(1); 456 } 457 return (new ASN1(0x30, new ASN1(0x02, r), new ASN1(0x02, s))).getBytes(); 458 } 459 460 461 462 /** 463 * Check a ASN sequence against a structure description 464 * 465 * @private 466 * @param {Object} s the structure description 467 * @param {ASN1} asn the asn sequence 468 * @type String 469 * @return null if valid or the validation error message 470 */ 471 CVC.checkStructure = function(s, asn) { 472 var j = 0; 473 for (var i = 0; i < s.length; i++) { 474 var d = s[i]; 475 476 // print("Checking tag " + d.tag.toString(HEX)); 477 478 if (j < asn.elements) { 479 var a = asn.get(j); 480 481 if (a.tag == d.tag) { 482 if ((typeof(d.minlen) != "undefined") && (a.value.length < d.minlen)) { 483 return "Length (" + a.value.length + ") of " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") less than " + d.minlen; 484 } 485 if ((typeof(d.maxlen) != "undefined") && (a.value.length > d.maxlen)) { 486 return "Length (" + a.value.length + ") of " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") more than " + d.maxlen; 487 } 488 if ((typeof(d.content) != "undefined")) { 489 var r = CVC.checkStructure(d.content, a); 490 if (r) { 491 return r; 492 } 493 } 494 j++; 495 } else { 496 if (!d.optional) { 497 return "Expecting " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") but found tag " + a.tag.toString(HEX); 498 } 499 } 500 } else { 501 if (!d.optional) { 502 return "Expecting " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") but sequence is exhaused"; 503 } 504 } 505 } 506 return null; 507 } 508 509 510 511 /** 512 * Validate ASN1 semantic for card verifiable certificate or certificate request 513 * 514 * @type String 515 * @return null if no error, or error message 516 */ 517 CVC.prototype.validate = function() { 518 var r = null; 519 if (this.asn.tag == CVC.TAG_AT) { 520 r = CVC.checkStructure(CVC.ATREQ_SYNTAX, this.asn); 521 } else { 522 if (this.body.find(CVC.TAG_CED)) { 523 r = CVC.checkStructure(CVC.CERT_SYNTAX, this.asn); 524 } else { 525 r = CVC.checkStructure(CVC.REQ_SYNTAX, this.asn); 526 } 527 } 528 return r; 529 } 530 531 532 533 /** 534 * Return true of the certificate contains domain parameter 535 * 536 * @type boolean 537 * @return true, if certificate contains domain parameter 538 */ 539 CVC.prototype.containsDomainParameter = function() { 540 var pdo = this.body.find(CVC.TAG_PUK); 541 if (pdo == null) { 542 return false; 543 } 544 545 var d = pdo.find(0x84); // Generator 546 return (d != null); 547 } 548 549 550 551 /** 552 * Returns the certificate profile indicator (CPI) 553 * 554 * @return the CPI or null 555 * @type Number 556 */ 557 CVC.prototype.getCPI = function() { 558 var cpido = this.body.find(CVC.TAG_CPI); 559 560 if (!cpido) { 561 return null; 562 } 563 564 return cpido.value.toUnsigned(); 565 } 566 567 568 569 /** 570 * Returns the certification authority reference (CAR). 571 * 572 * @return the CAR or null 573 * @type PublicKeyReference 574 */ 575 CVC.prototype.getCAR = function() { 576 var cardo = this.body.find(CVC.TAG_CAR); 577 578 if (!cardo) { 579 return null; 580 } 581 582 return new PublicKeyReference(cardo.value); 583 } 584 585 586 587 /** 588 * Returns the certificate holder reference (CHR). 589 * 590 * @return the CHR 591 * @type PublicKeyReference 592 */ 593 CVC.prototype.getCHR = function() { 594 var chrdo = this.body.find(CVC.TAG_CHR); 595 596 if (!chrdo) { 597 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a CHR"); 598 } 599 600 return new PublicKeyReference(chrdo.value); 601 } 602 603 604 605 /** 606 * Returns the certificate effective date (CED). 607 * 608 * @return the CED or null 609 * @type Date 610 */ 611 CVC.prototype.getCED = function() { 612 var ceddo = this.body.find(CVC.TAG_CED); 613 614 if (!ceddo) { 615 return null 616 } 617 618 var b = ceddo.value; 619 620 var d = new Date(); 621 d.setFullYear(b.byteAt(0) * 10 + b.byteAt(1) + 2000, 622 b.byteAt(2) * 10 + b.byteAt(3) - 1, 623 b.byteAt(4) * 10 + b.byteAt(5)); 624 d.setHours(12, 0, 0, 0); 625 return d; 626 } 627 628 629 630 /** 631 * Returns the certificate expiration date (CXD). 632 * 633 * @return the CXD or null 634 * @type Date 635 */ 636 CVC.prototype.getCXD = function() { 637 var cxddo = this.body.find(CVC.TAG_CXD); 638 639 if (!cxddo) { 640 return null 641 } 642 643 var b = cxddo.value; 644 645 var d = new Date(); 646 d.setFullYear(b.byteAt(0) * 10 + b.byteAt(1) + 2000, 647 b.byteAt(2) * 10 + b.byteAt(3) - 1, 648 b.byteAt(4) * 10 + b.byteAt(5)); 649 d.setHours(12, 0, 0, 0); 650 return d; 651 } 652 653 654 655 /** 656 * Returns the outer certification authority reference (CAR). 657 * 658 * @return the outer CAR or null 659 * @type PublicKeyReference 660 */ 661 CVC.prototype.getOuterCAR = function() { 662 if (!this.isAuthenticatedRequest()) { 663 return null; 664 } 665 var cardo = this.asn.get(1); 666 667 if (!cardo) { 668 return null 669 } 670 671 return new PublicKeyReference(cardo.value); 672 } 673 674 675 676 /** 677 * Returns the extension identified by the object identifier. 678 * 679 * @return the extension including the OID or null if not defined 680 * @type ASN1 681 */ 682 CVC.prototype.getExtension = function(extoid) { 683 var extdo = this.body.find(CVC.TAG_EXTN); 684 685 if (!extdo) { 686 return null; 687 } 688 689 // print(extdo); 690 691 for (var i = 0; i < extdo.elements; i++) { 692 var ext = extdo.get(i); 693 var oid = ext.get(0); 694 assert(oid.tag == ASN1.OBJECT_IDENTIFIER); 695 if (oid.value.equals(extoid)) { 696 return ext; 697 } 698 } 699 return null; 700 } 701 702 703 704 /** 705 * Returns the Certificate Holder Authorization Template. 706 * 707 * @return the chat or null if not defined 708 * @type ASN1 709 */ 710 CVC.prototype.getCHAT = function() { 711 var chat = this.body.find(CVC.TAG_CHAT); 712 713 return chat; 714 } 715 716 717 718 /** 719 * Returns the public key object identifier 720 * 721 * @returns the object identifier assigned to the public key 722 * @type ByteString 723 */ 724 CVC.prototype.getPublicKeyOID = function() { 725 var pdo = this.body.find(CVC.TAG_PUK); 726 if (pdo == null) { 727 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 728 } 729 730 var d = pdo.find(ASN1.OBJECT_IDENTIFIER); 731 if (d == null) { 732 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Public key does not contain an object identifier"); 733 } 734 return d.value; 735 } 736 737 738 739 /** 740 * Decode a public key from the TR-03110 format 741 * 742 * @param {ASN1} pdo the public key data object 743 * @param {Key} key the key object to fill 744 */ 745 CVC.decodeECPublicKey = function(pdo, key) { 746 747 var d = pdo.find(0x86); // Public point 748 if (d == null) { 749 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key value"); 750 } 751 752 var b = d.value.bytes(1); 753 key.setComponent(Key.ECC_QX, b.left(b.length >> 1)); 754 key.setComponent(Key.ECC_QY, b.right(b.length >> 1)); 755 756 var d = pdo.find(0x81); // Prime modulus 757 if (d != null) { 758 key.setComponent(Key.ECC_P, d.value); 759 } 760 761 var d = pdo.find(0x82); // First coefficient a 762 if (d != null) { 763 key.setComponent(Key.ECC_A, d.value); 764 } 765 766 var d = pdo.find(0x83); // First coefficient b 767 if (d != null) { 768 key.setComponent(Key.ECC_B, d.value); 769 } 770 771 var d = pdo.find(0x84); // Base Point G 772 if (d != null) { 773 var b = d.value.bytes(1); 774 key.setComponent(Key.ECC_GX, b.left(b.length >> 1)); 775 key.setComponent(Key.ECC_GY, b.right(b.length >> 1)); 776 } 777 778 var d = pdo.find(0x85); // Order of the base point 779 if (d != null) { 780 key.setComponent(Key.ECC_N, d.value); 781 } 782 783 var d = pdo.find(0x87); // Cofactor f 784 if (d != null) { 785 key.setComponent(Key.ECC_H, d.value); 786 } 787 } 788 789 790 791 /** 792 * Returns the EC public key contained in the certificate. 793 * 794 * @param {Key} domParam optional domain parameter if they are not contained in certificate 795 * @return the public key object 796 * @type Key 797 */ 798 CVC.prototype.getECPublicKey = function(domParam) { 799 if (typeof(domParam) != "undefined") { 800 var key = new Key(domParam); 801 } else { 802 var key = new Key(); 803 } 804 805 key.setType(Key.PUBLIC); 806 807 var pdo = this.body.find(CVC.TAG_PUK); 808 if (pdo == null) { 809 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 810 } 811 812 CVC.decodeECPublicKey(pdo, key); 813 814 return key; 815 } 816 817 818 819 /** 820 * Returns the RSA public key contained in the certificate. 821 * 822 * @return the public key object 823 * @type Key 824 */ 825 CVC.prototype.getRSAPublicKey = function() { 826 var key = new Key(); 827 828 key.setType(Key.PUBLIC); 829 830 var pdo = this.body.find(CVC.TAG_PUK); 831 if (pdo == null) { 832 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 833 } 834 835 var d = pdo.find(0x81); // modulus 836 if (d != null) { 837 key.setComponent(Key.MODULUS, d.value); 838 } 839 840 var d = pdo.find(0x82); // public exponent 841 if (d != null) { 842 key.setComponent(Key.EXPONENT, d.value); 843 } 844 845 return key; 846 } 847 848 849 850 /** 851 * Returns the public key contained in the certificate. 852 * 853 * @param {Key} domParam optional domain parameter if they are not contained in certificate 854 * @return the public key object 855 * @type Key 856 */ 857 CVC.prototype.getPublicKey = function(domParam) { 858 var pkoid = this.getPublicKeyOID(); 859 860 if (CVC.isECDSA(pkoid)) { 861 return this.getECPublicKey(domParam); 862 } 863 return this.getRSAPublicKey(); 864 } 865 866 867 868 /** 869 * Determine the SubjectKeyIdentifier as defined in X.509 870 * 871 * @return the key identifier 872 * @type ByteString 873 */ 874 CVC.prototype.determineKeyIdentifier = function() { 875 var pdo = this.body.find(CVC.TAG_PUK); 876 if (pdo == null) { 877 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 878 } 879 880 var pkoid = this.getPublicKeyOID(); 881 882 var keybin; 883 if (CVC.isECDSA(pkoid)) { 884 var d = pdo.find(0x86); // Public point 885 keybin = d.value; 886 } else { 887 var modulus = pdo.find(0x81).value; // modulus 888 if (modulus.byteAt(0) >= 0x80) { 889 modulus = ByteString.valueOf(0).concat(modulus); 890 } 891 var exponent = pdo.find(0x82).value; // public exponent 892 var rsapub = new ASN1("RSAPublicKey", ASN1.SEQUENCE, 893 new ASN1("modulus", ASN1.INTEGER, modulus), 894 new ASN1("publicKeyExponent", ASN1.INTEGER, exponent)); 895 896 keybin = rsapub.getBytes(); 897 } 898 return CVC.crypto.digest(Crypto.SHA_1, keybin); 899 } 900 901 902 903 /** 904 * Determine if this is an authenticated request 905 * 906 * @returns true, if authenticated request 907 * @type Boolean 908 */ 909 CVC.prototype.isAuthenticatedRequest = function() { 910 return (this.asn.tag == CVC.TAG_AT); 911 } 912 913 914 915 /** 916 * Determine if this is a certificate request 917 * 918 * @returns true, if certificate request 919 * @type Boolean 920 */ 921 CVC.prototype.isCertificateRequest = function() { 922 if (this.isAuthenticatedRequest()) { 923 return true; 924 } 925 926 var ced = this.getCED(); 927 return ced == null; 928 } 929 930 931 932 /** 933 * Determine if this is a countersigned authenticated request 934 * 935 * @returns true, if countersigned authenticated request 936 * @type Boolean 937 */ 938 CVC.prototype.isCountersignedRequest = function() { 939 if (!this.isAuthenticatedRequest()) { 940 return false; 941 } 942 return (this.getCHR().getHolder() != this.getOuterCAR().getHolder()); 943 } 944 945 946 947 /** 948 * Determine if this certificate is expired 949 * 950 * @returns true, if certificate is expired 951 * @type Boolean 952 */ 953 CVC.prototype.isExpired = function() { 954 var now = new Date(); 955 now.setHours(12, 0, 0, 0); 956 return (now.valueOf() > this.getCXD().valueOf()); 957 } 958 959 960 961 /** 962 * Verify certificate signature with public key 963 * 964 * @param {Key} puk the public key 965 * @param {ByteString} oid the signature algorithm 966 * @returns true if the signature is valid 967 * @type Boolean 968 */ 969 CVC.prototype.verifyWith = function(crypto, puk, oid) { 970 if (this.asn.tag == CVC.TAG_AT) { 971 var signature = this.asn.get(0).get(1); 972 } else { 973 var signature = this.asn.get(1); 974 } 975 976 if (typeof(oid) == "undefined") { 977 var oid = this.getPublicKeyOID(); 978 } 979 var mech = CVC.getSignatureMech(oid); 980 981 if (CVC.isECDSA(oid)) { 982 var signatureValue = CVC.wrapSignature(signature.value); 983 } else { 984 var signatureValue = signature.value; 985 } 986 987 return crypto.verify(puk, mech, this.body.getBytes(), signatureValue); 988 } 989 990 991 992 /** 993 * Verify certificate signature with public key from card verifiable certificate 994 * 995 * @param {CVC} cvc the card verifiable certificate used to obtain the public key 996 * @returns true if the signature is valid 997 * @type Boolean 998 */ 999 CVC.prototype.verifyWithCVC = function(crypto, cvc) { 1000 return this.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID()); 1001 } 1002 1003 1004 1005 /** 1006 * Verify outer signature of an authenticated request with public key 1007 * 1008 * @param {Key} puk the public key 1009 * @param {ByteString} oid the signature algorithm 1010 * @returns true if the signature is valid 1011 * @type Boolean 1012 */ 1013 CVC.prototype.verifyATWith = function(crypto, puk, oid) { 1014 if (!this.isAuthenticatedRequest()) { 1015 throw new GPError("CVC", GPError.INVALID_DATA, 0, "Not an authenticated request"); 1016 } 1017 1018 var signature = this.asn.get(2); 1019 var signatureInput = this.asn.get(0).getBytes().concat(this.asn.get(1).getBytes()); 1020 1021 if (typeof(oid) == "undefined") { 1022 var oid = this.getPublicKeyOID(); 1023 } 1024 var mech = CVC.getSignatureMech(oid); 1025 1026 if (CVC.isECDSA(oid)) { 1027 var signatureValue = CVC.wrapSignature(signature.value); 1028 } else { 1029 var signatureValue = signature.value; 1030 } 1031 return crypto.verify(puk, mech, signatureInput, signatureValue); 1032 } 1033 1034 1035 1036 /** 1037 * Verify outer signature of an authenticated request with public key from card verifiable certificate 1038 * 1039 * @param {CVC} cvc the card verifiable certificate used to obtain the public key 1040 * @returns true if the signature is valid 1041 * @type Boolean 1042 */ 1043 CVC.prototype.verifyATWithCVC = function(crypto, cvc) { 1044 return this.verifyATWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID()); 1045 } 1046 1047 1048 1049 /** 1050 * Returns the encoded certificate 1051 * 1052 * @return the DER encoded certificate 1053 * @type ByteString 1054 */ 1055 CVC.prototype.getBytes = function() { 1056 return this.bin; 1057 } 1058 1059 1060 1061 /** 1062 * Returns the certificate as ASN1 structure 1063 * 1064 * @return the certificate as ASN1 structure 1065 * @type ASN1 1066 */ 1067 CVC.prototype.getASN1 = function() { 1068 return this.asn; 1069 } 1070 1071 1072 1073 /** 1074 * Function to recursively walk the ASN.1 tree 1075 */ 1076 CVC.decorateTree = function(node) { 1077 var name = CVC.OBJECTNAMES[node.tag]; 1078 1079 if (name) { 1080 node.setName(name); 1081 } 1082 1083 if (node.isconstructed) { 1084 for (var i = 0; i < node.elements; i++) { 1085 CVC.decorateTree(node.get(i)); 1086 } 1087 } 1088 } 1089 1090 1091 1092 /** 1093 * Decorate the ASN.1 object with the correct name 1094 */ 1095 CVC.prototype.decorate = function() { 1096 CVC.decorateTree(this.asn); 1097 var cxddo = this.body.find(CVC.TAG_CXD); 1098 if (cxddo == null) { 1099 if (this.asn.tag == CVC.TAG_AT) { 1100 this.asn.setName("Authenticated CVC Request"); 1101 } else { 1102 this.asn.setName("CVC Request"); 1103 } 1104 } 1105 } 1106 1107 1108 1109 /** 1110 * Return list of rights granted by the certificate 1111 * 1112 * @returns the list of rights 1113 * @type String[] 1114 */ 1115 CVC.prototype.getRightsAsList = function() { 1116 var list = []; 1117 1118 var rtab; 1119 var chat = this.getCHAT(); 1120 if (chat == null) { 1121 return list; 1122 } 1123 1124 var oid = chat.get(0).value; 1125 1126 if (oid.equals(CVC.idIS)) { 1127 rtab = CVC.ISRIGHTS; 1128 } else if (oid.equals(CVC.idAT)) { 1129 rtab = CVC.ATRIGHTS; 1130 } else if (oid.equals(CVC.idST)) { 1131 rtab = CVC.STRIGHTS; 1132 } else { 1133 return null; 1134 } 1135 1136 var mask = chat.get(1).value; 1137 var c = 0; 1138 for (var i = mask.length - 1; i >= 0; i--) { 1139 var akku = mask.byteAt(i); 1140 for (var j = 0; j < (i == 0 ? 6 : 8); j++) { 1141 if (akku & 1) { 1142 list.push(rtab[c]); 1143 } 1144 c++; 1145 akku >>= 1; 1146 } 1147 } 1148 return list; 1149 } 1150 1151 1152 1153 /** 1154 * Return position of certificate in PKI hierachie 1155 * 1156 * @returns Position in PKI hierachie: 0 unknown, 1-CVCA, 2-DVCA, 3-Terminal 1157 * @type Number 1158 */ 1159 CVC.prototype.getLevel = function() { 1160 var chat = this.getCHAT(); 1161 if (!chat) { 1162 return 0; 1163 } 1164 1165 var l = chat.get(1).value.byteAt(0) & 0xC0; 1166 if (l == 0xC0) { 1167 return 1; 1168 } 1169 return l == 0x00 ? 3 : 2; 1170 } 1171 1172 1173 1174 /** 1175 * Return a string describing the certificate type 1176 * 1177 * @returns a describing string 1178 * @type String 1179 */ 1180 CVC.prototype.getType = function() { 1181 var ced = this.getCED(); 1182 var chat = this.getCHAT(); 1183 1184 // Decode certificate / request type 1185 var str = "CVC "; 1186 if (ced == null) { 1187 if (this.asn.tag == CVC.TAG_AT) { 1188 str = "AT-CVREQ "; 1189 } else { 1190 str = "CVREQ "; 1191 } 1192 } 1193 1194 // Decode CA type 1195 if (chat != null) { 1196 var oid = chat.get(0).value; 1197 1198 if (oid.equals(CVC.idSC_HSM)) { 1199 str += "id-SC-HSM "; 1200 switch(chat.get(1).value.byteAt(0) & 0xC0) { 1201 case 0xC0: str += "SRCA "; break; 1202 case 0x80: str += "DICA "; break; 1203 case 0x40: str += "DICA "; break; 1204 case 0x00: str += "Device "; break; 1205 } 1206 } else { 1207 var trustedDV = ""; 1208 var untrustedDV = ""; 1209 1210 if (oid.equals(CVC.idIS)) { 1211 str += "id-IS "; 1212 trustedDV = "(official domestic) "; 1213 untrustedDV = "(official foreign) "; 1214 } else if (oid.equals(CVC.idAT)) { 1215 str += "id-AT "; 1216 trustedDV = "(official domestic) "; 1217 untrustedDV = "(non-official / foreign) "; 1218 } else if (oid.equals(CVC.idST)) { 1219 str += "id-ST "; 1220 trustedDV = "(accreditation body) "; 1221 untrustedDV = "(certification service provider) "; 1222 } else { 1223 str += oid.toString(OID) + " "; 1224 } 1225 1226 switch(chat.get(1).value.byteAt(0) & 0xC0) { 1227 case 0xC0: str += "CVCA "; break; 1228 case 0x80: str += "DV " + trustedDV; break; 1229 case 0x40: str += "DV " + untrustedDV; break; 1230 case 0x00: str += "Terminal "; break; 1231 } 1232 } 1233 } 1234 1235 return str; 1236 } 1237 1238 1239 1240 /** 1241 * Return a textual description of the certificate 1242 * 1243 * @returns a string containing information about the certificate 1244 * @type String 1245 */ 1246 CVC.prototype.toString = function() { 1247 var car = this.getCAR(); 1248 var ced = this.getCED(); 1249 1250 var str = this.getType(); 1251 1252 if (car) { 1253 str += "CAR=" + car.toString() + " "; 1254 } 1255 1256 str += "CHR=" + this.getCHR().toString() + " "; 1257 1258 if (ced) { 1259 str += "CED=" + ced.toLocaleDateString() + " "; 1260 } 1261 1262 var cxd = this.getCXD(); 1263 if (cxd) { 1264 str += "CXD=" + cxd.toLocaleDateString() + " "; 1265 } 1266 1267 if (this.isAuthenticatedRequest()) { 1268 str += "oCAR=" + this.getOuterCAR().toString() + " "; 1269 } 1270 1271 return str; 1272 } 1273 1274 1275 1276 /** 1277 * Return object suitable for JSON encoding 1278 * 1279 * @returns a JSON encodable object 1280 * @type Object 1281 */ 1282 CVC.prototype.toJSON = function() { 1283 return { clazz: "CVC", value: this.bin }; 1284 } 1285 1286 1287 1288 /** 1289 * Recreate object from JSON encoding 1290 * 1291 * @returns the CVC object 1292 * @type Object 1293 */ 1294 CVC.fromJSON = function(obj) { 1295 assert(obj.clazz.equals("CVC"), "JSON object must contain clazz: CVC"); 1296 return new CVC(obj.value); 1297 } 1298