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