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 & 0x80 | 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 & 0x80 | 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