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