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  * 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