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 CVC.id_ECPUBLICKEY = new ByteString("id-ecPublicKey", OID); 278 CVC.id_ANSIX9 = new ByteString("iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) prime(1)", OID); 279 CVC.id_CERTICOM = new ByteString("iso(1) identified-organization(3) certicom(132) curve(0)", OID); 280 CVC.id_BRAINPOOL = new ByteString("ellipticCurve", OID); 281 282 283 284 /** 285 * Return signature mechanism for object identifier 286 * 287 * @param {ByteString} oid the object identifer from the public key object 288 * @returns the signature mechanism as Crypto. constant or -1 if not defined 289 * @type Number 290 */ 291 CVC.getSignatureMech = function(oid) { 292 if (oid.equals(CVC.id_TA_ECDSA_SHA_1)) 293 return Crypto.ECDSA_SHA1; 294 if (oid.equals(CVC.id_TA_ECDSA_SHA_224)) 295 return Crypto.ECDSA_SHA224; 296 if (oid.equals(CVC.id_TA_ECDSA_SHA_256)) 297 return Crypto.ECDSA_SHA256; 298 if (oid.equals(CVC.id_TA_ECDSA_SHA_384)) 299 return Crypto.ECDSA_SHA384; 300 if (oid.equals(CVC.id_TA_ECDSA_SHA_512)) 301 return Crypto.ECDSA_SHA512; 302 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_1)) 303 return Crypto.RSA_SHA1; 304 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_256)) 305 return Crypto.RSA_SHA256; 306 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_512)) 307 return Crypto.RSA_SHA512; 308 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_1)) 309 return Crypto.RSA_PSS_SHA1; 310 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_256)) 311 return Crypto.RSA_PSS_SHA256; 312 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_512)) 313 return Crypto.RSA_PSS_SHA512; 314 return -1; 315 } 316 317 318 319 /** 320 * Return hash mechanism for object identifier 321 * 322 * @param {ByteString} oid the object identifer from the public key object 323 * @returns the hash mechanism as Crypto. constant or -1 if not defined 324 * @type Number 325 */ 326 CVC.getHashMech = function(oid) { 327 if (oid.equals(CVC.id_TA_ECDSA_SHA_1)) 328 return Crypto.SHA_1; 329 if (oid.equals(CVC.id_TA_ECDSA_SHA_224)) 330 return Crypto.SHA_224; 331 if (oid.equals(CVC.id_TA_ECDSA_SHA_256)) 332 return Crypto.SHA_256; 333 if (oid.equals(CVC.id_TA_ECDSA_SHA_384)) 334 return Crypto.SHA_384; 335 if (oid.equals(CVC.id_TA_ECDSA_SHA_512)) 336 return Crypto.SHA_512; 337 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_1)) 338 return Crypto.SHA_1; 339 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_256)) 340 return Crypto.SHA_256; 341 if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_512)) 342 return Crypto.SHA_512; 343 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_1)) 344 return Crypto.SHA_1; 345 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_256)) 346 return Crypto.SHA_256; 347 if (oid.equals(CVC.id_TA_RSA_PSS_SHA_512)) 348 return Crypto.SHA_512; 349 return -1; 350 } 351 352 353 354 /** 355 * Return true of the object identifier starts with id-TA-ECDSA 356 * 357 * @type boolean 358 * @return true, if ECDSA based OID 359 */ 360 CVC.isECDSA = function(oid) { 361 if (oid.startsWith(CVC.id_TA_ECDSA) == CVC.id_TA_ECDSA.length) { 362 return true; 363 } 364 if (oid.equals(CVC.id_ECPUBLICKEY)) { 365 return true; 366 } 367 if (oid.startsWith(CVC.id_BRAINPOOL) == CVC.id_BRAINPOOL.length) { 368 return true; 369 } 370 if (oid.startsWith(CVC.id_ANSIX9) == CVC.id_ANSIX9.length) { 371 return true; 372 } 373 if (oid.startsWith(CVC.id_CERTICOM) == CVC.id_CERTICOM.length) { 374 return true; 375 } 376 return false; 377 } 378 379 380 381 /** 382 * Wrap an ECDSA signature in the format r || s into a TLV encoding as defined by RFC 3279 383 * 384 * @param signature ByteString containing the concatenation of r and s as unsigned integer values 385 * @returns ASN.1 SEQUENCE objects containing two signed integer r and s 386 */ 387 CVC.wrapSignature = function(signature) { 388 var len = signature.length / 2; 389 390 // r and s are unsigned big integer. We might need to pad a zero for ASN.1 INTEGER which is signed 391 var r = signature.bytes(0, len); 392 while ((r.length > 1) && (r.byteAt(0) == 0)) { 393 r = r.bytes(1); 394 } 395 if (r.byteAt(0) >= 0x80) { 396 r = ByteString.valueOf(0, 1).concat(r); 397 } 398 399 var s = signature.bytes(len, len); 400 while ((s.length > 1) && (s.byteAt(0) == 0)) { 401 s = s.bytes(1); 402 } 403 if (s.byteAt(0) >= 0x80) { 404 s = ByteString.valueOf(0, 1).concat(s); 405 } 406 407 var t = new ASN1(ASN1.SEQUENCE); 408 t.add(new ASN1(ASN1.INTEGER, r)); 409 t.add(new ASN1(ASN1.INTEGER, s)); 410 411 return t.getBytes(); 412 } 413 414 415 416 /** 417 * Integer to octet string conversion 418 */ 419 CVC.I2O = function(value, length) { 420 if (value.length > length) { 421 value = value.right(length); 422 } 423 while (value.length < length) { 424 value = CVC.PAD.left((length - value.length - 1 & 15) + 1).concat(value); 425 } 426 return value; 427 } 428 CVC.PAD = new ByteString("00000000000000000000000000000000", HEX); 429 430 431 432 /** 433 * Unwrap a ECDSA signature from the TLV encoding according to RFC3279 into the concatenation 434 * of the unsigned integer r and s 435 * 436 * @param signature TLV encoded signature 437 * @returns concatenation of r and s 438 */ 439 CVC.unwrapSignature = function(signature, keylen) { 440 var t = new ASN1(signature); 441 if (typeof(keylen) != "undefined") { 442 var r = CVC.I2O(t.get(0).value, keylen); 443 var s = CVC.I2O(t.get(1).value, keylen); 444 } else { 445 var r = t.get(0).value; 446 if (r.byteAt(0) == 00) 447 r = r.bytes(1); 448 449 var s = t.get(1).value; 450 if (s.byteAt(0) == 00) 451 s = s.bytes(1); 452 } 453 454 return r.concat(s); 455 } 456 457 458 459 /** 460 * Rewrap an ECDSA signature that contains redundant leading zeros in integer. 461 * 462 * @param signature the signature 463 * @type ByteString 464 * @param the fixed signature 465 */ 466 CVC.rewrapSignature = function(signature) { 467 var a = new ASN1(signature); 468 var r = a.get(0).value; 469 while ((r.byteAt(0) == 0) && (r.byteAt(1) < 0x80)) { 470 r = r.bytes(1); 471 } 472 473 var s = a.get(1).value; 474 while ((s.byteAt(0) == 0) && (s.byteAt(1) < 0x80)) { 475 s = s.bytes(1); 476 } 477 return (new ASN1(0x30, new ASN1(0x02, r), new ASN1(0x02, s))).getBytes(); 478 } 479 480 481 482 /** 483 * Check a ASN sequence against a structure description 484 * 485 * @private 486 * @param {Object} s the structure description 487 * @param {ASN1} asn the asn sequence 488 * @type String 489 * @return null if valid or the validation error message 490 */ 491 CVC.checkStructure = function(s, asn) { 492 var j = 0; 493 for (var i = 0; i < s.length; i++) { 494 var d = s[i]; 495 496 // print("Checking tag " + d.tag.toString(HEX)); 497 498 if (j < asn.elements) { 499 var a = asn.get(j); 500 501 if (a.tag == d.tag) { 502 if ((typeof(d.minlen) != "undefined") && (a.value.length < d.minlen)) { 503 return "Length (" + a.value.length + ") of " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") less than " + d.minlen; 504 } 505 if ((typeof(d.maxlen) != "undefined") && (a.value.length > d.maxlen)) { 506 return "Length (" + a.value.length + ") of " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") more than " + d.maxlen; 507 } 508 if ((typeof(d.content) != "undefined")) { 509 var r = CVC.checkStructure(d.content, a); 510 if (r) { 511 return r; 512 } 513 } 514 j++; 515 } else { 516 if (!d.optional) { 517 return "Expecting " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") but found tag " + a.tag.toString(HEX); 518 } 519 } 520 } else { 521 if (!d.optional) { 522 return "Expecting " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") but sequence is exhaused"; 523 } 524 } 525 } 526 return null; 527 } 528 529 530 531 /** 532 * Validate ASN1 semantic for card verifiable certificate or certificate request 533 * 534 * @type String 535 * @return null if no error, or error message 536 */ 537 CVC.prototype.validate = function() { 538 var r = null; 539 if (this.asn.tag == CVC.TAG_AT) { 540 r = CVC.checkStructure(CVC.ATREQ_SYNTAX, this.asn); 541 } else { 542 if (this.body.find(CVC.TAG_CED)) { 543 r = CVC.checkStructure(CVC.CERT_SYNTAX, this.asn); 544 } else { 545 r = CVC.checkStructure(CVC.REQ_SYNTAX, this.asn); 546 } 547 } 548 return r; 549 } 550 551 552 553 /** 554 * Return true of the certificate contains domain parameter 555 * 556 * @type boolean 557 * @return true, if certificate contains domain parameter 558 */ 559 CVC.prototype.containsDomainParameter = function() { 560 var pdo = this.body.find(CVC.TAG_PUK); 561 if (pdo == null) { 562 return false; 563 } 564 565 var d = pdo.find(0x84); // Generator 566 return (d != null); 567 } 568 569 570 571 /** 572 * Returns the certificate profile indicator (CPI) 573 * 574 * @return the CPI or null 575 * @type Number 576 */ 577 CVC.prototype.getCPI = function() { 578 var cpido = this.body.find(CVC.TAG_CPI); 579 580 if (!cpido) { 581 return null; 582 } 583 584 return cpido.value.toUnsigned(); 585 } 586 587 588 589 /** 590 * Returns the certification authority reference (CAR). 591 * 592 * @return the CAR or null 593 * @type PublicKeyReference 594 */ 595 CVC.prototype.getCAR = function() { 596 var cardo = this.body.find(CVC.TAG_CAR); 597 598 if (!cardo) { 599 return null; 600 } 601 602 return new PublicKeyReference(cardo.value); 603 } 604 605 606 607 /** 608 * Returns the certificate holder reference (CHR). 609 * 610 * @return the CHR 611 * @type PublicKeyReference 612 */ 613 CVC.prototype.getCHR = function() { 614 var chrdo = this.body.find(CVC.TAG_CHR); 615 616 if (!chrdo) { 617 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a CHR"); 618 } 619 620 return new PublicKeyReference(chrdo.value); 621 } 622 623 624 625 /** 626 * Returns the certificate effective date (CED). 627 * 628 * @return the CED or null 629 * @type Date 630 */ 631 CVC.prototype.getCED = function() { 632 var ceddo = this.body.find(CVC.TAG_CED); 633 634 if (!ceddo) { 635 return null 636 } 637 638 var b = ceddo.value; 639 640 var d = new Date(); 641 d.setFullYear(b.byteAt(0) * 10 + b.byteAt(1) + 2000, 642 b.byteAt(2) * 10 + b.byteAt(3) - 1, 643 b.byteAt(4) * 10 + b.byteAt(5)); 644 d.setHours(12, 0, 0, 0); 645 return d; 646 } 647 648 649 650 /** 651 * Returns the certificate expiration date (CXD). 652 * 653 * @return the CXD or null 654 * @type Date 655 */ 656 CVC.prototype.getCXD = function() { 657 var cxddo = this.body.find(CVC.TAG_CXD); 658 659 if (!cxddo) { 660 return null 661 } 662 663 var b = cxddo.value; 664 665 var d = new Date(); 666 d.setFullYear(b.byteAt(0) * 10 + b.byteAt(1) + 2000, 667 b.byteAt(2) * 10 + b.byteAt(3) - 1, 668 b.byteAt(4) * 10 + b.byteAt(5)); 669 d.setHours(12, 0, 0, 0); 670 return d; 671 } 672 673 674 675 /** 676 * Returns the outer certification authority reference (CAR). 677 * 678 * @return the outer CAR or null 679 * @type PublicKeyReference 680 */ 681 CVC.prototype.getOuterCAR = function() { 682 if (!this.isAuthenticatedRequest()) { 683 return null; 684 } 685 var cardo = this.asn.get(1); 686 687 if (!cardo) { 688 return null 689 } 690 691 return new PublicKeyReference(cardo.value); 692 } 693 694 695 696 /** 697 * Returns the extension identified by the object identifier. 698 * 699 * @return the extension including the OID or null if not defined 700 * @type ASN1 701 */ 702 CVC.prototype.getExtension = function(extoid) { 703 var extdo = this.body.find(CVC.TAG_EXTN); 704 705 if (!extdo) { 706 return null; 707 } 708 709 // print(extdo); 710 711 for (var i = 0; i < extdo.elements; i++) { 712 var ext = extdo.get(i); 713 var oid = ext.get(0); 714 assert(oid.tag == ASN1.OBJECT_IDENTIFIER); 715 if (oid.value.equals(extoid)) { 716 return ext; 717 } 718 } 719 return null; 720 } 721 722 723 724 /** 725 * Returns the Certificate Holder Authorization Template. 726 * 727 * @return the chat or null if not defined 728 * @type ASN1 729 */ 730 CVC.prototype.getCHAT = function() { 731 var chat = this.body.find(CVC.TAG_CHAT); 732 733 return chat; 734 } 735 736 737 738 /** 739 * Returns the public key object identifier 740 * 741 * @returns the object identifier assigned to the public key 742 * @type ByteString 743 */ 744 CVC.prototype.getPublicKeyOID = function() { 745 var pdo = this.body.find(CVC.TAG_PUK); 746 if (pdo == null) { 747 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 748 } 749 750 var d = pdo.find(ASN1.OBJECT_IDENTIFIER); 751 if (d == null) { 752 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Public key does not contain an object identifier"); 753 } 754 return d.value; 755 } 756 757 758 759 /** 760 * Decode a public key from the TR-03110 format 761 * 762 * @param {ASN1} pdo the public key data object 763 * @param {Key} key the key object to fill 764 */ 765 CVC.decodeECPublicKey = function(pdo, key) { 766 767 var d = pdo.find(0x86); // Public point 768 if (d == null) { 769 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key value"); 770 } 771 772 var b = d.value.bytes(1); 773 key.setComponent(Key.ECC_QX, b.left(b.length >> 1)); 774 key.setComponent(Key.ECC_QY, b.right(b.length >> 1)); 775 776 var d = pdo.find(0x81); // Prime modulus 777 if (d != null) { 778 key.setComponent(Key.ECC_P, d.value); 779 } 780 781 var d = pdo.find(0x82); // First coefficient a 782 if (d != null) { 783 key.setComponent(Key.ECC_A, d.value); 784 } 785 786 var d = pdo.find(0x83); // First coefficient b 787 if (d != null) { 788 key.setComponent(Key.ECC_B, d.value); 789 } 790 791 var d = pdo.find(0x84); // Base Point G 792 if (d != null) { 793 var b = d.value.bytes(1); 794 key.setComponent(Key.ECC_GX, b.left(b.length >> 1)); 795 key.setComponent(Key.ECC_GY, b.right(b.length >> 1)); 796 } 797 798 var d = pdo.find(0x85); // Order of the base point 799 if (d != null) { 800 key.setComponent(Key.ECC_N, d.value); 801 } 802 803 var d = pdo.find(0x87); // Cofactor f 804 if (d != null) { 805 key.setComponent(Key.ECC_H, d.value); 806 } 807 } 808 809 810 811 /** 812 * Returns the EC public key contained in the certificate. 813 * 814 * @param {Key} domParam optional domain parameter if they are not contained in certificate 815 * @return the public key object 816 * @type Key 817 */ 818 CVC.prototype.getECPublicKey = function(domParam) { 819 if (typeof(domParam) != "undefined") { 820 var key = new Key(domParam); 821 } else { 822 var key = new Key(); 823 } 824 825 key.setType(Key.PUBLIC); 826 827 var pdo = this.body.find(CVC.TAG_PUK); 828 if (pdo == null) { 829 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 830 } 831 832 CVC.decodeECPublicKey(pdo, key); 833 834 return key; 835 } 836 837 838 839 /** 840 * Returns the RSA public key contained in the certificate. 841 * 842 * @return the public key object 843 * @type Key 844 */ 845 CVC.prototype.getRSAPublicKey = function() { 846 var key = new Key(); 847 848 key.setType(Key.PUBLIC); 849 850 var pdo = this.body.find(CVC.TAG_PUK); 851 if (pdo == null) { 852 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 853 } 854 855 var d = pdo.find(0x81); // modulus 856 if (d != null) { 857 key.setComponent(Key.MODULUS, d.value); 858 } 859 860 var d = pdo.find(0x82); // public exponent 861 if (d != null) { 862 key.setComponent(Key.EXPONENT, d.value); 863 } 864 865 return key; 866 } 867 868 869 870 /** 871 * Returns the public key contained in the certificate. 872 * 873 * @param {Key} domParam optional domain parameter if they are not contained in certificate 874 * @return the public key object 875 * @type Key 876 */ 877 CVC.prototype.getPublicKey = function(domParam) { 878 var pkoid = this.getPublicKeyOID(); 879 880 if (CVC.isECDSA(pkoid)) { 881 return this.getECPublicKey(domParam); 882 } 883 return this.getRSAPublicKey(); 884 } 885 886 887 888 /** 889 * Determine the SubjectKeyIdentifier as defined in X.509 890 * 891 * @return the key identifier 892 * @type ByteString 893 */ 894 CVC.prototype.determineKeyIdentifier = function() { 895 var pdo = this.body.find(CVC.TAG_PUK); 896 if (pdo == null) { 897 throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key"); 898 } 899 900 var pkoid = this.getPublicKeyOID(); 901 902 var keybin; 903 if (CVC.isECDSA(pkoid)) { 904 var d = pdo.find(0x86); // Public point 905 keybin = d.value; 906 } else { 907 var modulus = pdo.find(0x81).value; // modulus 908 if (modulus.byteAt(0) >= 0x80) { 909 modulus = ByteString.valueOf(0).concat(modulus); 910 } 911 var exponent = pdo.find(0x82).value; // public exponent 912 var rsapub = new ASN1("RSAPublicKey", ASN1.SEQUENCE, 913 new ASN1("modulus", ASN1.INTEGER, modulus), 914 new ASN1("publicKeyExponent", ASN1.INTEGER, exponent)); 915 916 keybin = rsapub.getBytes(); 917 } 918 return CVC.crypto.digest(Crypto.SHA_1, keybin); 919 } 920 921 922 923 /** 924 * Determine if this is an authenticated request 925 * 926 * @returns true, if authenticated request 927 * @type Boolean 928 */ 929 CVC.prototype.isAuthenticatedRequest = function() { 930 return (this.asn.tag == CVC.TAG_AT); 931 } 932 933 934 935 /** 936 * Determine if this is a certificate request 937 * 938 * @returns true, if certificate request 939 * @type Boolean 940 */ 941 CVC.prototype.isCertificateRequest = function() { 942 if (this.isAuthenticatedRequest()) { 943 return true; 944 } 945 946 var ced = this.getCED(); 947 return ced == null; 948 } 949 950 951 952 /** 953 * Determine if this is a countersigned authenticated request 954 * 955 * @returns true, if countersigned authenticated request 956 * @type Boolean 957 */ 958 CVC.prototype.isCountersignedRequest = function() { 959 if (!this.isAuthenticatedRequest()) { 960 return false; 961 } 962 return (this.getCHR().getHolder() != this.getOuterCAR().getHolder()); 963 } 964 965 966 967 /** 968 * Determine if this certificate is expired 969 * 970 * @returns true, if certificate is expired 971 * @type Boolean 972 */ 973 CVC.prototype.isExpired = function() { 974 var now = new Date(); 975 now.setHours(12, 0, 0, 0); 976 return (now.valueOf() > this.getCXD().valueOf()); 977 } 978 979 980 981 /** 982 * Verify certificate signature with public key 983 * 984 * @param {Key} puk the public key 985 * @param {ByteString} oid the signature algorithm 986 * @returns true if the signature is valid 987 * @type Boolean 988 */ 989 CVC.prototype.verifyWith = function(crypto, puk, oid) { 990 if (this.asn.tag == CVC.TAG_AT) { 991 var signature = this.asn.get(0).get(1); 992 } else { 993 var signature = this.asn.get(1); 994 } 995 996 if (typeof(oid) == "undefined") { 997 var oid = this.getPublicKeyOID(); 998 } 999 var mech = CVC.getSignatureMech(oid); 1000 1001 if (CVC.isECDSA(oid)) { 1002 var signatureValue = CVC.wrapSignature(signature.value); 1003 } else { 1004 var signatureValue = signature.value; 1005 } 1006 1007 return crypto.verify(puk, mech, this.body.getBytes(), signatureValue); 1008 } 1009 1010 1011 1012 /** 1013 * Verify certificate signature with public key from card verifiable certificate 1014 * 1015 * @param {CVC} cvc the card verifiable certificate used to obtain the public key 1016 * @returns true if the signature is valid 1017 * @type Boolean 1018 */ 1019 CVC.prototype.verifyWithCVC = function(crypto, cvc) { 1020 return this.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID()); 1021 } 1022 1023 1024 1025 /** 1026 * Verify outer signature of an authenticated request with public key 1027 * 1028 * @param {Key} puk the public key 1029 * @param {ByteString} oid the signature algorithm 1030 * @returns true if the signature is valid 1031 * @type Boolean 1032 */ 1033 CVC.prototype.verifyATWith = function(crypto, puk, oid) { 1034 if (!this.isAuthenticatedRequest()) { 1035 throw new GPError("CVC", GPError.INVALID_DATA, 0, "Not an authenticated request"); 1036 } 1037 1038 var signature = this.asn.get(2); 1039 var signatureInput = this.asn.get(0).getBytes().concat(this.asn.get(1).getBytes()); 1040 1041 if (typeof(oid) == "undefined") { 1042 var oid = this.getPublicKeyOID(); 1043 } 1044 var mech = CVC.getSignatureMech(oid); 1045 1046 if (CVC.isECDSA(oid)) { 1047 var signatureValue = CVC.wrapSignature(signature.value); 1048 } else { 1049 var signatureValue = signature.value; 1050 } 1051 return crypto.verify(puk, mech, signatureInput, signatureValue); 1052 } 1053 1054 1055 1056 /** 1057 * Verify outer signature of an authenticated request with public key from card verifiable certificate 1058 * 1059 * @param {CVC} cvc the card verifiable certificate used to obtain the public key 1060 * @returns true if the signature is valid 1061 * @type Boolean 1062 */ 1063 CVC.prototype.verifyATWithCVC = function(crypto, cvc) { 1064 return this.verifyATWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID()); 1065 } 1066 1067 1068 1069 /** 1070 * Returns the encoded certificate 1071 * 1072 * @return the DER encoded certificate 1073 * @type ByteString 1074 */ 1075 CVC.prototype.getBytes = function() { 1076 return this.bin; 1077 } 1078 1079 1080 1081 /** 1082 * Returns the certificate as ASN1 structure 1083 * 1084 * @return the certificate as ASN1 structure 1085 * @type ASN1 1086 */ 1087 CVC.prototype.getASN1 = function() { 1088 return this.asn; 1089 } 1090 1091 1092 1093 /** 1094 * Function to recursively walk the ASN.1 tree 1095 */ 1096 CVC.decorateTree = function(node) { 1097 var name = CVC.OBJECTNAMES[node.tag]; 1098 1099 if (name) { 1100 node.setName(name); 1101 } 1102 1103 if (node.isconstructed) { 1104 for (var i = 0; i < node.elements; i++) { 1105 CVC.decorateTree(node.get(i)); 1106 } 1107 } 1108 } 1109 1110 1111 1112 /** 1113 * Decorate the ASN.1 object with the correct name 1114 */ 1115 CVC.prototype.decorate = function() { 1116 CVC.decorateTree(this.asn); 1117 var cxddo = this.body.find(CVC.TAG_CXD); 1118 if (cxddo == null) { 1119 if (this.asn.tag == CVC.TAG_AT) { 1120 this.asn.setName("Authenticated CVC Request"); 1121 } else { 1122 this.asn.setName("CVC Request"); 1123 } 1124 } 1125 } 1126 1127 1128 1129 /** 1130 * Return list of rights granted by the certificate 1131 * 1132 * @returns the list of rights 1133 * @type String[] 1134 */ 1135 CVC.prototype.getRightsAsList = function() { 1136 var list = []; 1137 1138 var rtab; 1139 var chat = this.getCHAT(); 1140 if (chat == null) { 1141 return list; 1142 } 1143 1144 var oid = chat.get(0).value; 1145 1146 if (oid.equals(CVC.idIS)) { 1147 rtab = CVC.ISRIGHTS; 1148 } else if (oid.equals(CVC.idAT)) { 1149 rtab = CVC.ATRIGHTS; 1150 } else if (oid.equals(CVC.idST)) { 1151 rtab = CVC.STRIGHTS; 1152 } else { 1153 return null; 1154 } 1155 1156 var mask = chat.get(1).value; 1157 var c = 0; 1158 for (var i = mask.length - 1; i >= 0; i--) { 1159 var akku = mask.byteAt(i); 1160 for (var j = 0; j < (i == 0 ? 6 : 8); j++) { 1161 if (akku & 1) { 1162 list.push(rtab[c]); 1163 } 1164 c++; 1165 akku >>= 1; 1166 } 1167 } 1168 return list; 1169 } 1170 1171 1172 1173 /** 1174 * Return position of certificate in PKI hierachie 1175 * 1176 * @returns Position in PKI hierachie: 0 unknown, 1-CVCA, 2-DVCA, 3-Terminal 1177 * @type Number 1178 */ 1179 CVC.prototype.getLevel = function() { 1180 var chat = this.getCHAT(); 1181 if (!chat) { 1182 return 0; 1183 } 1184 1185 var l = chat.get(1).value.byteAt(0) & 0xC0; 1186 if (l == 0xC0) { 1187 return 1; 1188 } 1189 return l == 0x00 ? 3 : 2; 1190 } 1191 1192 1193 1194 /** 1195 * Return a string describing the certificate type 1196 * 1197 * @returns a describing string 1198 * @type String 1199 */ 1200 CVC.prototype.getType = function() { 1201 var ced = this.getCED(); 1202 var chat = this.getCHAT(); 1203 1204 // Decode certificate / request type 1205 var str = "CVC "; 1206 if (ced == null) { 1207 if (this.asn.tag == CVC.TAG_AT) { 1208 str = "AT-CVREQ "; 1209 } else { 1210 str = "CVREQ "; 1211 } 1212 } 1213 1214 // Decode CA type 1215 if (chat != null) { 1216 var oid = chat.get(0).value; 1217 1218 if (oid.equals(CVC.idSC_HSM)) { 1219 str += "id-SC-HSM "; 1220 switch(chat.get(1).value.byteAt(0) & 0xC0) { 1221 case 0xC0: str += "SRCA "; break; 1222 case 0x80: str += "DICA "; break; 1223 case 0x40: str += "MICA "; break; 1224 case 0x00: str += "Device "; break; 1225 } 1226 } else { 1227 var trustedDV = ""; 1228 var untrustedDV = ""; 1229 1230 if (oid.equals(CVC.idIS)) { 1231 str += "id-IS "; 1232 trustedDV = "(official domestic) "; 1233 untrustedDV = "(official foreign) "; 1234 } else if (oid.equals(CVC.idAT)) { 1235 str += "id-AT "; 1236 trustedDV = "(official domestic) "; 1237 untrustedDV = "(non-official / foreign) "; 1238 } else if (oid.equals(CVC.idST)) { 1239 str += "id-ST "; 1240 trustedDV = "(accreditation body) "; 1241 untrustedDV = "(certification service provider) "; 1242 } else { 1243 str += oid.toString(OID) + " "; 1244 } 1245 1246 switch(chat.get(1).value.byteAt(0) & 0xC0) { 1247 case 0xC0: str += "CVCA "; break; 1248 case 0x80: str += "DV " + trustedDV; break; 1249 case 0x40: str += "DV " + untrustedDV; break; 1250 case 0x00: str += "Terminal "; break; 1251 } 1252 } 1253 } 1254 1255 return str; 1256 } 1257 1258 1259 1260 /** 1261 * Return a textual description of the certificate 1262 * 1263 * @returns a string containing information about the certificate 1264 * @type String 1265 */ 1266 CVC.prototype.toString = function() { 1267 var car = this.getCAR(); 1268 var ced = this.getCED(); 1269 1270 var str = this.getType(); 1271 1272 if (car) { 1273 str += "CAR=" + car.toString() + " "; 1274 } 1275 1276 str += "CHR=" + this.getCHR().toString() + " "; 1277 1278 if (ced) { 1279 str += "CED=" + ced.toLocaleDateString() + " "; 1280 } 1281 1282 var cxd = this.getCXD(); 1283 if (cxd) { 1284 str += "CXD=" + cxd.toLocaleDateString() + " "; 1285 } 1286 1287 if (this.isAuthenticatedRequest()) { 1288 str += "oCAR=" + this.getOuterCAR().toString() + " "; 1289 } 1290 1291 return str; 1292 } 1293 1294 1295 1296 /** 1297 * Return object suitable for JSON encoding 1298 * 1299 * @returns a JSON encodable object 1300 * @type Object 1301 */ 1302 CVC.prototype.toJSON = function() { 1303 return { clazz: "CVC", value: this.bin }; 1304 } 1305 1306 1307 1308 /** 1309 * Recreate object from JSON encoding 1310 * 1311 * @returns the CVC object 1312 * @type Object 1313 */ 1314 CVC.fromJSON = function(obj) { 1315 assert(obj.clazz.equals("CVC"), "JSON object must contain clazz: CVC"); 1316 return new CVC(obj.value); 1317 } 1318