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 (lc > 255) { 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 (lc > 255) { 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 * Determine random or pseudo-random card challenge 322 * 323 */ 324 GPSCP03.prototype.determineCardChallenge = function() { 325 if (typeof(this.sequenceCounter) == "undefined") { 326 this.cardChallenge = this.crypto.generateRandom(8); 327 } else { 328 var cnt = this.sequenceCounter.toUnsigned() + 1; 329 this.sequenceCounter = ByteString.valueOf(cnt, 3); 330 var context = this.sequenceCounter.concat(this.aid); 331 this.cardChallenge = this.deriveKey(this.kenc, 2, 8, context); 332 } 333 } 334 335 336 337 /** 338 * Handle processing of INITIALIZE UPDATE command 339 * 340 * @param {Number} keyVersion the version of the key to use (0 for default) 341 * @param {Number} keyId the key id to use (usually 0) 342 * @param {ByteString} hostCryptogram the cryptogram calculated at the host 343 */ 344 GPSCP03.prototype.handleInitializeUpdate = function(keyVersion, keyId, hostChallenge) { 345 this.hostChallenge = hostChallenge; 346 this.determineCardChallenge(); 347 348 this.diversificationData = new ByteString("0102030405060708090A", HEX); 349 this.keyInformation = new ByteString("010300", HEX); 350 351 this.deriveSessionKeys(); 352 var context = this.hostChallenge.concat(this.cardChallenge); 353 this.cardCryptogram = this.deriveKey(this.skmac, 0, 8, context); 354 355 this.seclevel = 1; 356 357 var bb = new ByteBuffer(); 358 bb.append(this.diversificationData); 359 bb.append(this.keyInformation); 360 bb.append(this.cardChallenge); 361 bb.append(this.cardCryptogram); 362 if (this.sequenceCounter != undefined) { 363 bb.append(this.sequenceCounter); 364 } 365 return bb.toByteString(); 366 } 367 368 369 370 /** 371 * Issue EXTERNAL AUTHENTICATE command to card. 372 * 373 * @param {Card} card the card to use 374 * @param {Number} level the security level (a combination of bits B5 (R-ENC), B4 (R-MAC), B2 (C-ENC) and B1 (C-MAC)) 375 * @param {ByteString} hostCryptogram optional parameter, calculated internally if missing 376 */ 377 GPSCP03.prototype.externalAuthenticate = function(card, level, hostCryptogram) { 378 assert(card instanceof Card, "Argument card must be instance of Card"); 379 assert(typeof(level) == "number", "Argument level must be a number"); 380 assert((level & ~0x33) == 0, "Argument level must use only bits B5,B4,B2 or B1"); 381 assert((hostCryptogram == undefined) || (hostCryptogram instanceof ByteString), "hostCryptogram must be missing or ByteString"); 382 383 if (hostCryptogram == undefined) { 384 this.calculateHostCryptogram(); 385 } else { 386 this.hostCryptogram = hostCryptogram; 387 } 388 this.seclevel = 1; 389 390 card.sendSecMsgApdu(Card.ALL, 0x80, 0x82, level, 0x00, this.hostCryptogram, [0x9000]); 391 this.seclevel = level; 392 this.enccnt = 1; 393 } 394 395 396 397 /** 398 * Derive S-ENC, S-MAC and S-RMAC session keys 399 */ 400 GPSCP03.prototype.deriveSessionKeys = function() { 401 var context = this.hostChallenge.concat(this.cardChallenge); 402 403 var keysize = this.kmac.getSize() >> 3; 404 405 // Derive S-MAC 406 var ss = this.deriveKey(this.kmac, 6, keysize, context); 407 this.skmac = new Key(); 408 this.skmac.setComponent(Key.AES, ss); 409 410 // Derive S-RMAC 411 var ss = this.deriveKey(this.kmac, 7, keysize, context); 412 this.skrmac = new Key(); 413 this.skrmac.setComponent(Key.AES, ss); 414 415 // Derive S-ENC 416 var ss = this.deriveKey(this.kenc, 4, keysize, context); 417 this.skenc = new Key(); 418 this.skenc.setComponent(Key.AES, ss); 419 } 420 421 422 423 /** 424 * Verify card cryptogram 425 * 426 * @type boolean 427 * @return true if cryptogram is valid 428 */ 429 GPSCP03.prototype.verifyCardCryptogram = function() { 430 var context = this.hostChallenge.concat(this.cardChallenge); 431 432 var ss = this.deriveKey(this.skmac, 0, 8, context); 433 return ss.equals(this.cardCryptogram); 434 } 435 436 437 438 /** 439 * Calculate host cryptogram using the key derive method 440 * 441 * @type ByteString 442 * @return the 8 byte cryptogram 443 */ 444 GPSCP03.prototype.calculateHostCryptogram = function() { 445 var context = this.hostChallenge.concat(this.cardChallenge); 446 447 this.hostCryptogram = this.deriveKey(this.skmac, 1, 8, context); 448 return this.hostCryptogram; 449 } 450 451 452 453 /** 454 * Encrypt key and prepare key block for PUT KEY command 455 * 456 * @param {Key} dek the data encryption key 457 * @param {Key} key the key to wrap 458 * @type ByteString 459 * @return the key type, length, cryptogram and key check value 460 */ 461 GPSCP03.prototype.prepareKeyBlock = function(dek, key) { 462 assert(dek instanceof Key, "Argument dek must be instance of Key"); 463 assert(key instanceof Key, "Argument key must be instance of Key"); 464 465 var keysize = key.getSize() >> 3; 466 var ones = new ByteString("01010101010101010101010101010101", HEX); 467 var kcv = this.crypto.encrypt(key, Crypto.AES_ECB, ones).left(3); 468 469 var iv = ByteString.valueOf(0, 16); 470 var ck = this.crypto.encrypt(dek, Crypto.AES_CBC, key.getComponent(Key.AES), iv); 471 var ckbin = new ByteBuffer("88", HEX); 472 ckbin.append(keysize + 1); 473 ckbin.append(keysize); 474 ckbin.append(ck); 475 ckbin.append(3); 476 ckbin.append(kcv); 477 478 return ckbin.toByteString(); 479 } 480 481 482 483 /** 484 * Derive a session key or cryptogram 485 * 486 * @param {Key} key the master key 487 * @param {Number} ddc the data derivation constant 488 * @param {Number} size the size of byte of the resulting key or cryptogram 489 * @param {ByteString} context the context (usually hostChallenge || cardChallenge) 490 * @return the derived value 491 * @type ByteString 492 */ 493 GPSCP03.prototype.deriveKey = function(key, ddc, size, context) { 494 assert(key instanceof Key, "Argument key must be instance of Key"); 495 assert(typeof(ddc) == "number", "Argument ddc must be a number"); 496 assert(typeof(size) == "number", "Argument size must be a number"); 497 assert(context instanceof ByteString, "Argument context must be instance of ByteString"); 498 499 var dd = new ByteBuffer(); 500 var os = size; 501 var iter = 1; 502 while (os > 0) { 503 var dp = new ByteBuffer(); 504 dp.append(ByteString.valueOf(ddc, 12)); 505 dp.append(0); 506 dp.append(ByteString.valueOf(size << 3, 2)); 507 dp.append(ByteString.valueOf(iter)); 508 dp.append(context); 509 510 var mac = this.crypto.sign(key, Crypto.AES_CMAC, dp.toByteString()); 511 512 dd.append(mac.left(os > mac.length ? mac.length : os)); 513 os -= mac.length; 514 iter++; 515 } 516 return dd.toByteString(); 517 } 518 519 520 521 GPSCP03.test = function() { 522 var crypto = new Crypto(); 523 524 var kmac = new Key(); 525 kmac.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 526 527 var kenc = new Key(); 528 kenc.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 529 530 var kdek = new Key(); 531 kdek.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 532 533 var card = new Card(_scsh3.reader); 534 535 card.sendApdu(0x00, 0xA4, 0x04, 0x00, new ByteString("A000000151000000", HEX)); 536 537 var scp = new GPSCP03(crypto, kenc, kmac, kdek); 538 539 scp.initializeUpdate(card, 0, 0); 540 scp.deriveSessionKeys(); 541 assert(scp.verifyCardCryptogram()); 542 card.setCredential(scp); 543 scp.externalAuthenticate(card, 1); 544 545 var aid = new ByteString("E82B0601040181C31F0202", HEX); 546 var cdata = (new ASN1(0x4F, aid)).getBytes(); 547 548 card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, [0x9000, 0x6A88]); 549 card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, 0, [0x9000, 0x6A88]); 550 } 551