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 Global Platform SCP03 protocol 25 */ 26 27 var APDU = require('scsh/cardsim/APDU').APDU; 28 29 30 31 /** 32 * Class implementing support for Global Platform SCP03 secure messaging protocol 33 * 34 * @constructor 35 * @param {Crypto} crypto the crypto provider 36 * @param {Key} kenc the static secure channel encryption key 37 * @param {Key} kmac the static secure channel message authentication code key 38 * @param {Key} kdek the static data encryption key 39 * @param {ByteString) aid the AID to use for challenge calculation 40 */ 41 function GPSCP03(crypto, kenc, kmac, kdek, aid) { 42 this.crypto = crypto; 43 this.kenc = kenc; 44 this.kmac = kmac; 45 this.kdek = kdek; 46 this.chaining = ByteString.valueOf(0, 16); 47 this.enccnt = 1; 48 this.seclevel = 0; 49 50 if (aid != undefined) { 51 assert(aid instanceof ByteString, "Argument aid must be ByteString"); 52 this.aid = aid; 53 } else { 54 this.aid = new ByteString("A000000151000000", HEX); 55 } 56 } 57 58 exports.GPSCP03 = GPSCP03; 59 60 61 62 /** 63 * Strip the ISO padding from dechipered cryptogram 64 * 65 * @param {ByteString} the plain text with padding 66 * @type ByteString 67 * @return the plain text without padding 68 */ 69 GPSCP03.stripPadding = function(plain) { 70 var i = plain.length - 1; 71 while ((i > 0) && (plain.byteAt(i) == 0)) { 72 i--; 73 } 74 if (plain.byteAt(i) != 0x80) { 75 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Padding check failed"); 76 } 77 return plain.left(i); 78 } 79 80 81 82 /** 83 * Reset the internal state 84 **/ 85 GPSCP03.prototype.reset = function() { 86 this.chaining = ByteString.valueOf(0, 16); 87 this.enccnt = 1; 88 this.seclevel = 0; 89 } 90 91 92 93 /** 94 * Called by the secure messaging wrapper to wrap the command APDU 95 */ 96 GPSCP03.prototype.wrap = function(apduToWrap, usageQualifier) { 97 98 if (this.seclevel & 0x01) { 99 var apdu = new APDU(apduToWrap); 100 101 if (apdu.hasCData()) { 102 if (this.seclevel & 0x02) { 103 var ivinp = ByteString.valueOf(this.enccnt, 16); 104 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 105 var encinp = apdu.getCData().pad(Crypto.ISO9797_METHOD_2_16); 106 107 apdu.setCData(this.crypto.encrypt(this.skenc, Crypto.AES_CBC, encinp, iv)); 108 } 109 } else { 110 apdu.setCData(new ByteString("", HEX)); 111 } 112 113 var bb = new ByteBuffer(); 114 bb.append(this.chaining); 115 116 var cla = apdu.getCLA(); 117 if (cla & 0x40) { 118 // GP Format 119 cla = cla & 0xC0 | 0x40; 120 apdu.cla |= 0x40; 121 } else { 122 // ISO Format 123 cla = cla & 0x80 | 0x04; 124 apdu.cla |= 0x04; 125 } 126 bb.append(cla); 127 bb.append(apdu.getINS()); 128 bb.append(apdu.getP1()); 129 bb.append(apdu.getP2()); 130 131 var lc = apdu.getCData().length + 8; 132 if (apdu.isExtendedLength()) { 133 bb.append(0); 134 bb.append(ByteString.valueOf(lc, 2)); 135 } else { 136 bb.append(ByteString.valueOf(lc, 1)); 137 } 138 139 bb.append(apdu.getCData()); 140 var mac = this.crypto.sign(this.skmac, Crypto.AES_CMAC, bb.toByteString()); 141 142 this.chaining = mac; 143 144 apdu.setCData(apdu.getCData().concat(mac.left(8))); 145 146 if (this.clatransformation) { 147 apdu.setCLA(this.clatransformation(apdu.getCLA())); 148 } 149 150 apduToWrap = apdu.getCommandAPDU(); 151 } 152 return apduToWrap; 153 } 154 155 156 157 /** 158 * Called by the secure messaging wrapper to unwrap the response APDU 159 */ 160 GPSCP03.prototype.unwrap = function(apduToUnwrap, usageQualifier) { 161 if ((this.seclevel & 0x10) && (apduToUnwrap.length > 2)) { 162 var bb = new ByteBuffer(); 163 bb.append(this.chaining); 164 bb.append(apduToUnwrap.left(apduToUnwrap.length - 10)); 165 bb.append(apduToUnwrap.right(2)); 166 167 var mac = this.crypto.sign(this.skrmac, Crypto.AES_CMAC, bb.toByteString()).left(8); 168 169 if (!mac.equals(apduToUnwrap.bytes(apduToUnwrap.length - 10, 8))) { 170 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "MAC on R-Data failed verification"); 171 } 172 173 if ((this.seclevel & 0x20) && (apduToUnwrap.length > 10)) { 174 var ivinp = ByteString.valueOf(0x80).concat(ByteString.valueOf(this.enccnt, 15)); 175 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 176 177 var plain = this.crypto.decrypt(this.skenc, Crypto.AES_CBC, apduToUnwrap.left(apduToUnwrap.length - 10), iv); 178 apduToUnwrap = GPSCP03.stripPadding(plain).concat(apduToUnwrap.right(2)); 179 } else { 180 apduToUnwrap = apduToUnwrap.left(apduToUnwrap.length - 10).concat(apduToUnwrap.right(2)); 181 } 182 } 183 184 this.enccnt++; 185 186 return apduToUnwrap; 187 } 188 189 190 191 192 /** 193 * Counterpart to wrap and meant to be used in a simulator 194 */ 195 GPSCP03.prototype.unwrapCommandAPDU = function(apdu) { 196 if (this.seclevel == 0) { 197 throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_CONDOFUSENOTSAT, "No secure messaging active"); 198 } 199 200 if (this.seclevel & 0x01) { 201 if (!apdu.hasCData() || apdu.getCData().length < 8) { 202 throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_SMOBJMISSING, "MAC on C-Data missing"); 203 } 204 var bb = new ByteBuffer(); 205 bb.append(this.chaining); 206 207 var cla = apdu.getCLA(); 208 if (cla & 0x40) { 209 // GP Format 210 cla = cla & 0xC0 | 0x40; 211 } else { 212 // ISO Format 213 cla = cla & 0x80 | 0x04; 214 } 215 216 bb.append(cla); 217 bb.append(apdu.getINS()); 218 bb.append(apdu.getP1()); 219 bb.append(apdu.getP2()); 220 221 var lc = apdu.getCData().length; 222 if (apdu.isExtendedLength()) { 223 bb.append(0); 224 bb.append(ByteString.valueOf(lc, 2)); 225 } else { 226 bb.append(ByteString.valueOf(lc, 1)); 227 } 228 229 bb.append(apdu.getCData().left(lc - 8)); 230 231 var mac = this.crypto.sign(this.skmac, Crypto.AES_CMAC, bb.toByteString()); 232 233 if (!mac.left(8).equals(apdu.getCData().right(8))) { 234 throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_SECSTATNOTSAT, "MAC on C-Data failed verification"); 235 } 236 237 this.chaining = mac; 238 239 apdu.setCData(apdu.getCData().left(lc - 8)); 240 241 if ((this.seclevel & 0x02) && (lc > 8)) { 242 var ivinp = ByteString.valueOf(this.enccnt, 16); 243 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 244 245 var plain = this.crypto.decrypt(this.skenc, Crypto.AES_CBC, apdu.getCData(), iv); 246 apdu.setCData(GPSCP03.stripPadding(plain)); 247 } 248 249 } 250 } 251 252 253 254 /** 255 * Counterpart to unwrap and meant to be used in a simulator 256 */ 257 GPSCP03.prototype.wrapResponseAPDU = function(apdu) { 258 if (this.seclevel & 0x10) { 259 if ((this.seclevel & 0x20) && apdu.hasRData()) { 260 var ivinp = ByteString.valueOf(0x80).concat(ByteString.valueOf(this.enccnt, 15)); 261 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 262 var plain = apdu.getRData().pad(Crypto.ISO9797_METHOD_2_16); 263 264 var cryptogram = this.crypto.encrypt(this.skenc, Crypto.AES_CBC, plain, iv); 265 apdu.setRData(cryptogram); 266 } 267 268 var bb = new ByteBuffer(); 269 bb.append(this.chaining); 270 271 bb.append(apdu.getResponseAPDU()); 272 273 var mac = this.crypto.sign(this.skrmac, Crypto.AES_CMAC, bb.toByteString()).left(8); 274 275 apdu.setRData(apdu.hasRData() ? apdu.getRData().concat(mac) : mac); 276 } 277 278 this.enccnt++; 279 } 280 281 282 283 GPSCP03.prototype.setCLATransformation = function(clatransformation) { 284 assert(typeof(clatransformation) == "function", "Argument clatransformation must be a function"); 285 this.clatransformation = clatransformation; 286 } 287 288 289 290 /** 291 * Issue INITIALIZE UPDATE command to card and parse response 292 * 293 * @param {Card} card the card to use 294 * @param {Number} keyVersion the version of the key to use (0 for default) 295 * @param {Number} keyId the key id to use (usually 0) 296 */ 297 GPSCP03.prototype.initializeUpdate = function(card, keyVersion, keyId) { 298 this.hostChallenge = this.crypto.generateRandom(8); 299 300 var cla = 0x80; 301 if (this.clatransformation) { 302 cla = this.clatransformation(cla); 303 } 304 305 var rsp = card.sendApdu(cla, 0x50, keyVersion, keyId, this.hostChallenge, 0, [0x9000]); 306 307 this.diversificationData = rsp.bytes(0, 10); 308 this.keyInformation = rsp.bytes(10, 3); 309 this.cardChallenge = rsp.bytes(13, 8); 310 this.cardCryptogram = rsp.bytes(21, 8); 311 if (rsp.length > 29) { 312 this.sequenceCounter = rsp.bytes(29,3); 313 var context = this.sequenceCounter.concat(this.aid); 314 var ref = this.deriveKey(this.kenc, 2, 8, context); 315 assert(ref.equals(this.cardChallenge), "Pseudo-random card challenge does not match Kenc"); 316 } 317 } 318 319 320 321 /** 322 * Set the sequence number for pseudo-random card challenges 323 * 324 * @param {ByteString} cnt the last used counter value 325 */ 326 GPSCP03.prototype.setSequenceCounter = function(cnt) { 327 assert(cnt instanceof ByteString, "Argument cnt must be ByteString"); 328 assert(cnt.length == 3, "Argument cnt must be 3 bytes long"); 329 this.sequenceCounter = cnt; 330 } 331 332 333 334 /** 335 * Set the key version and protocol parameter 336 * 337 * @param {Number} version the key version indicated in INITIALIZE_UPDATE 338 * @param {Number} parami the i parameter for the SCP03 protocol (Default '00') 339 */ 340 GPSCP03.prototype.setKeyInfo = function(version, parami) { 341 assert(typeof(version) == "number", "Argument version must be of type number"); 342 if (typeof(parami) == "undefined") { 343 parami = 0; 344 } 345 this.keyInformation = ByteString.valueOf(version).concat(ByteString.valueOf(0x03)).concat(ByteString.valueOf(parami)); 346 } 347 348 349 350 /** 351 * Determine random or pseudo-random card challenge 352 * 353 */ 354 GPSCP03.prototype.determineCardChallenge = function() { 355 if (typeof(this.sequenceCounter) == "undefined") { 356 this.cardChallenge = this.crypto.generateRandom(8); 357 } else { 358 var cnt = this.sequenceCounter.toUnsigned() + 1; 359 this.sequenceCounter = ByteString.valueOf(cnt, 3); 360 var context = this.sequenceCounter.concat(this.aid); 361 this.cardChallenge = this.deriveKey(this.kenc, 2, 8, context); 362 } 363 } 364 365 366 367 /** 368 * Handle processing of INITIALIZE UPDATE command 369 * 370 * @param {Number} keyVersion the version of the key to use (0 for default) 371 * @param {Number} keyId the key id to use (usually 0) 372 * @param {ByteString} hostCryptogram the cryptogram calculated at the host 373 */ 374 GPSCP03.prototype.handleInitializeUpdate = function(keyVersion, keyId, hostChallenge) { 375 this.hostChallenge = hostChallenge; 376 this.determineCardChallenge(); 377 378 this.diversificationData = new ByteString("0102030405060708090A", HEX); 379 380 if (typeof(this.keyInformation) == "undefined") { 381 this.keyInformation = new ByteString("010300", HEX); 382 } 383 384 this.deriveSessionKeys(); 385 var context = this.hostChallenge.concat(this.cardChallenge); 386 this.cardCryptogram = this.deriveKey(this.skmac, 0, 8, context); 387 388 this.seclevel = 1; 389 this.chaining = ByteString.valueOf(0, 16); 390 391 var bb = new ByteBuffer(); 392 bb.append(this.diversificationData); 393 bb.append(this.keyInformation); 394 bb.append(this.cardChallenge); 395 bb.append(this.cardCryptogram); 396 if (this.sequenceCounter != undefined) { 397 bb.append(this.sequenceCounter); 398 } 399 return bb.toByteString(); 400 } 401 402 403 404 /** 405 * Issue EXTERNAL AUTHENTICATE command to card. 406 * 407 * @param {Card} card the card to use 408 * @param {Number} level the security level (a combination of bits B5 (R-ENC), B4 (R-MAC), B2 (C-ENC) and B1 (C-MAC)) 409 * @param {ByteString} hostCryptogram optional parameter, calculated internally if missing 410 */ 411 GPSCP03.prototype.externalAuthenticate = function(card, level, hostCryptogram) { 412 assert(card instanceof Card, "Argument card must be instance of Card"); 413 assert(typeof(level) == "number", "Argument level must be a number"); 414 assert((level & ~0x33) == 0, "Argument level must use only bits B5,B4,B2 or B1"); 415 assert((hostCryptogram == undefined) || (hostCryptogram instanceof ByteString), "hostCryptogram must be missing or ByteString"); 416 417 if (hostCryptogram == undefined) { 418 this.calculateHostCryptogram(); 419 } else { 420 this.hostCryptogram = hostCryptogram; 421 } 422 this.seclevel = 1; 423 424 card.sendSecMsgApdu(Card.ALL, 0x80, 0x82, level, 0x00, this.hostCryptogram, [0x9000]); 425 this.seclevel = level; 426 this.enccnt = 1; 427 } 428 429 430 431 /** 432 * Derive S-ENC, S-MAC and S-RMAC session keys 433 */ 434 GPSCP03.prototype.deriveSessionKeys = function() { 435 var context = this.hostChallenge.concat(this.cardChallenge); 436 437 var keysize = this.kmac.getSize() >> 3; 438 439 // Derive S-MAC 440 var ss = this.deriveKey(this.kmac, 6, keysize, context); 441 this.skmac = new Key(); 442 this.skmac.setComponent(Key.AES, ss); 443 444 // Derive S-RMAC 445 var ss = this.deriveKey(this.kmac, 7, keysize, context); 446 this.skrmac = new Key(); 447 this.skrmac.setComponent(Key.AES, ss); 448 449 // Derive S-ENC 450 var ss = this.deriveKey(this.kenc, 4, keysize, context); 451 this.skenc = new Key(); 452 this.skenc.setComponent(Key.AES, ss); 453 } 454 455 456 457 /** 458 * Verify card cryptogram 459 * 460 * @type boolean 461 * @return true if cryptogram is valid 462 */ 463 GPSCP03.prototype.verifyCardCryptogram = function() { 464 var context = this.hostChallenge.concat(this.cardChallenge); 465 466 var ss = this.deriveKey(this.skmac, 0, 8, context); 467 return ss.equals(this.cardCryptogram); 468 } 469 470 471 472 /** 473 * Calculate host cryptogram using the key derive method 474 * 475 * @type ByteString 476 * @return the 8 byte cryptogram 477 */ 478 GPSCP03.prototype.calculateHostCryptogram = function() { 479 var context = this.hostChallenge.concat(this.cardChallenge); 480 481 this.hostCryptogram = this.deriveKey(this.skmac, 1, 8, context); 482 return this.hostCryptogram; 483 } 484 485 486 487 /** 488 * Encrypt key and prepare key block for PUT KEY command 489 * 490 * @param {Key} dek the data encryption key 491 * @param {Key} key the key to wrap 492 * @type ByteString 493 * @return the key type, length, cryptogram and key check value 494 */ 495 GPSCP03.prototype.prepareKeyBlock = function(dek, key) { 496 assert(dek instanceof Key, "Argument dek must be instance of Key"); 497 assert(key instanceof Key, "Argument key must be instance of Key"); 498 499 var keysize = key.getSize() >> 3; 500 var ones = new ByteString("01010101010101010101010101010101", HEX); 501 var kcv = this.crypto.encrypt(key, Crypto.AES_ECB, ones).left(3); 502 503 var iv = ByteString.valueOf(0, 16); 504 var ck = this.crypto.encrypt(dek, Crypto.AES_CBC, key.getComponent(Key.AES), iv); 505 var ckbin = new ByteBuffer("88", HEX); 506 ckbin.append(keysize + 1); 507 ckbin.append(keysize); 508 ckbin.append(ck); 509 ckbin.append(3); 510 ckbin.append(kcv); 511 512 return ckbin.toByteString(); 513 } 514 515 516 517 /** 518 * Extract a key data field from C-Data in a PUT KEY APDU 519 * 520 * @param {ByteString} keyDataField the list of key data fields 521 * @type ByteString 522 * @return the extracted key data 523 */ 524 GPSCP03.parseKeyDataField = function(keyDataField) { 525 var i = keyDataField.byteAt(1); 526 assert(i - 1 == keyDataField.byteAt(2), "Field length does not match"); 527 i += 2; 528 i += keyDataField.byteAt(i) + 1; 529 return keyDataField.left(i); 530 } 531 532 533 534 /** 535 * Decrypt and validate key blob containing AES key wrapped with DEK 536 * 537 * @param {ByteString} keyblob the wrapped key 538 * @type Key 539 * @return the unwrapped key 540 */ 541 GPSCP03.prototype.validateKeyBlock = function(keyblob) { 542 assert(keyblob instanceof ByteString, "Argument keyblob must be instance of ByteString"); 543 544 assert(keyblob.byteAt(0) == 0x88, "AES key blob must start with '88'"); 545 var l = keyblob.byteAt(1); 546 assert(keyblob.length > l + 2, "Length exceeds key blob"); 547 assert(l - 1 == keyblob.byteAt(2), "Field length does not match"); 548 549 var ck = keyblob.bytes(3, l - 1); 550 var iv = ByteString.valueOf(0, 16); 551 var plain = this.crypto.decrypt(this.kdek, Crypto.AES_CBC, ck, iv); 552 553 var key = new Key(); 554 key.setComponent(Key.AES, plain); 555 556 var ones = new ByteString("01010101010101010101010101010101", HEX); 557 var kcv = this.crypto.encrypt(key, Crypto.AES_ECB, ones).left(3); 558 559 assert(keyblob.right(3).equals(kcv), "KCV does not match"); 560 return key; 561 } 562 563 564 565 /** 566 * Derive a session key or cryptogram 567 * 568 * @param {Key} key the master key 569 * @param {Number} ddc the data derivation constant 570 * @param {Number} size the size of byte of the resulting key or cryptogram 571 * @param {ByteString} context the context (usually hostChallenge || cardChallenge) 572 * @return the derived value 573 * @type ByteString 574 */ 575 GPSCP03.prototype.deriveKey = function(key, ddc, size, context) { 576 assert(key instanceof Key, "Argument key must be instance of Key"); 577 assert(typeof(ddc) == "number", "Argument ddc must be a number"); 578 assert(typeof(size) == "number", "Argument size must be a number"); 579 assert(context instanceof ByteString, "Argument context must be instance of ByteString"); 580 581 var dd = new ByteBuffer(); 582 var os = size; 583 var iter = 1; 584 while (os > 0) { 585 var dp = new ByteBuffer(); 586 dp.append(ByteString.valueOf(ddc, 12)); 587 dp.append(0); 588 dp.append(ByteString.valueOf(size << 3, 2)); 589 dp.append(ByteString.valueOf(iter)); 590 dp.append(context); 591 592 var mac = this.crypto.sign(key, Crypto.AES_CMAC, dp.toByteString()); 593 594 dd.append(mac.left(os > mac.length ? mac.length : os)); 595 os -= mac.length; 596 iter++; 597 } 598 return dd.toByteString(); 599 } 600 601 602 603 GPSCP03.test = function() { 604 var crypto = new Crypto(); 605 606 var kmac = new Key(); 607 kmac.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 608 609 var kenc = new Key(); 610 kenc.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 611 612 var kdek = new Key(); 613 kdek.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 614 615 var card = new Card(_scsh3.reader); 616 617 card.sendApdu(0x00, 0xA4, 0x04, 0x00, new ByteString("A000000151000000", HEX)); 618 619 var scp = new GPSCP03(crypto, kenc, kmac, kdek); 620 621 scp.initializeUpdate(card, 0, 0); 622 scp.deriveSessionKeys(); 623 assert(scp.verifyCardCryptogram()); 624 card.setCredential(scp); 625 scp.externalAuthenticate(card, 1); 626 627 var aid = new ByteString("E82B0601040181C31F0202", HEX); 628 var cdata = (new ASN1(0x4F, aid)).getBytes(); 629 630 card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, [0x9000, 0x6A88]); 631 card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, 0, [0x9000, 0x6A88]); 632 } 633