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