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 Implementation of an eID specific command interpreter
 25  */
 26 
 27 load("../cardsim/commandinterpreter.js");
 28 load("../icao/pace.js");
 29 load("tools/eccutils.js");
 30 
 31 
 32 /**
 33  * Create a command interpreter
 34  *
 35  * @class Class implementing a command interpreter that handles ISO 7816-4 command APDUs
 36  * @constructor
 37  * @param {FileSelector} fileSelector the file selector object
 38  */
 39 function eIDCommandInterpreter(fileSelector) {
 40 	CommandInterpreter.call(this, fileSelector);
 41 
 42 	this.pacedp = new Key();
 43 	this.pacedp.setComponent(Key.ECC_CURVE_OID, new ByteString("1.3.36.3.3.2.8.1.1.7", OID));
 44 	this.challenge = null;
 45 	this.crypto = new Crypto();
 46 	this.trustedDVCA = null;
 47 	this.trustedTerminal = null;
 48 	this.effectiveRights = null;
 49 	this.lastINS = 0;
 50 }
 51 
 52 
 53 // Inherit from CommandInterpreter
 54 eIDCommandInterpreter.prototype = new CommandInterpreter();
 55 eIDCommandInterpreter.constructor = eIDCommandInterpreter;
 56 
 57 
 58 
 59 /**
 60  * Determine if the terminal has been authenticated
 61  *
 62  * @type boolean
 63  * @return true if authenticated
 64  */
 65 eIDCommandInterpreter.prototype.isAuthenticatedTerminal = function() {
 66 	return (this.effectiveRights != null);
 67 }
 68 
 69 
 70 
 71 /**
 72  * Return terminal role from CHAT
 73  * @type ByteString
 74  * @return the object identifier value
 75  */
 76 eIDCommandInterpreter.prototype.getTerminalRole = function() {
 77 	if (this.isAuthenticatedTerminal()) {
 78 		return this.trustedTerminal.getCHAT().get(0).value;
 79 	}
 80 }
 81 
 82 
 83 
 84 /**
 85  * Determine the current date
 86  *
 87  * @type Date
 88  * @return the current Date
 89  */
 90 eIDCommandInterpreter.prototype.getDate = function() {
 91 	var dateobj = this.fileSelector.getMeta("currentDate");
 92 //	print("getDate() = " + dateobj.currentDate);
 93 	return dateobj.currentDate;
 94 }
 95 
 96 
 97 
 98 /**
 99  * Set the current date
100  *
101  * @param {Date} date the new date
102  */
103 eIDCommandInterpreter.prototype.setDate = function(date) {
104 	var dateobj = this.fileSelector.getMeta("currentDate");
105 //	print("setDate() = " + date);
106 	dateobj.currentDate = date;
107 }
108 
109 
110 
111 /**
112  * Update EF.CVCA to indicate new trust anchor for id-IS
113  *
114  * @param {Date} date the new date
115  */
116 eIDCommandInterpreter.prototype.updateEFCVCA = function(content) {
117 	var ef = this.fileSelector.getMeta("efCVCA");
118 	ef.content = content;
119 //	print(ef.content);
120 }
121 
122 
123 
124 /**
125  * Process GENERAL AUTHENTICATE command
126  *
127  * @param {APDU} the apdu
128  */
129 eIDCommandInterpreter.prototype.generalAuthenticate = function(apdu) {
130 	var a = new ASN1(apdu.getCData());
131 
132 	if (a.tag != 0x7C)
133 		throw new GPError("EACSIM", GPError.INVALID_DATA, 0, "Body must contain data element 0x7C");
134 
135 	if (a.elements > 0) {
136 		var ddtag = a.get(0).tag;
137 		if (ddtag == 0x80) {
138 			this.performChipAuthenticationV2(apdu);
139 			return;
140 		}
141 		if ((ddtag == 0xA0) || (ddtag == 0xA2)) {
142 			this.performRestrictedIdentification(apdu);
143 			return;
144 		}
145 	}
146 
147 	this.performPACE(apdu);
148 }
149 
150 
151 
152 /**
153  * Process GENERAL AUTHENTICATE command to perform PACE
154  *
155  * @param {APDU} the apdu
156  */
157 eIDCommandInterpreter.prototype.performPACE = function(apdu) {
158 
159 	var a = new ASN1(apdu.getCData());
160 	var response = new ASN1(0x7C);
161 
162 	if (a.elements == 0) {		// 1st General Authenticate
163 		if (!apdu.isChained()) {
164 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Command must be chained");
165 		}
166 		
167 		var se = this.fileSelector.getSecurityEnvironment().VEXK;
168 
169 		if (!se.t.AT) {
170 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Security environment not set");
171 		}
172 
173 //		print(se);
174 
175 		var protocol = se.t.AT.find(0x80);
176 		if (!protocol) {
177 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No protocol defined in security environment");
178 		}
179 		var protocol = protocol.value;
180 //		print("Protocol: " + protocol);
181 
182 		var keyid = se.t.AT.find(0x83);
183 		if (!keyid) {
184 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No key id defined in security environment");
185 		}
186 		var keyid = keyid.value.toUnsigned();
187 //		print("KeyID: " + keyid);
188 
189 		var chat = se.t.AT.find(0x7F4C);
190 		this.chat = chat;
191 
192 		this.paceao = this.fileSelector.getObject(AuthenticationObject.TYPE_PACE, keyid);
193 		if (!this.paceao) {
194 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found");
195 		}
196 
197 		if (this.paceao.isBlocked()) {
198 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_AUTHMETHLOCKED, "PACE password blocked");
199 		}
200 
201 		if (this.paceao.isSuspended()) {
202 			if (!this.fileSelector.isAuthenticated(true, this.paceao.unsuspendAuthenticationObject)) {
203 				throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "PACE password suspended");
204 			}
205 		}
206 
207 		this.paceao.decreaseRetryCounter();
208 
209 		var paceInfo = this.fileSelector.getMeta("paceInfo");
210 		assert(paceInfo, "paceInfo must be defined in meta data");
211 
212 		this.pace = new PACE(this.crypto, protocol, this.pacedp, paceInfo.version);
213 		this.pace.setPassword(this.paceao.value);
214 		var encnonce = this.pace.getEncryptedNonce();
215 		response.add(new ASN1(0x80, encnonce));
216 	} else {
217 		if (!this.pace)
218 			throw new GPError("EACSIM", GPError.INVALID_MECH, APDU.SW_CONDOFUSENOTSAT, "PACE must have been initialized");
219 
220 		if (a.elements != 1)
221 			throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_INVDATA, "Dynamic Authentication Data may only contain 1 element");
222 
223 		a = a.get(0);
224 
225 		switch(a.tag) {
226 		case 0x81:
227 			if (!apdu.isChained())
228 				throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Command must be chained");
229 
230 			if ((this.lastINS != APDU.INS_GENERAL_AUTHENTICATE) || !this.pace.hasNonce())
231 				throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. First GA missing");
232 			
233 			if (this.pace.hasMapping())
234 				throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. Steps was already performed");
235 
236 			if (a.value.byteAt(0) != 0x04) 
237 				throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key does not start with '04'");
238 
239 			var mappingData = this.pace.getMappingData();
240 			response.add(new ASN1(0x82, mappingData));
241 
242 			this.pace.performMapping(a.value);
243 			break;
244 		case 0x83:
245 			if (!apdu.isChained())
246 				throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Command must be chained");
247 			
248 			if ((this.lastINS != APDU.INS_GENERAL_AUTHENTICATE) || (!this.pace.hasMapping()))
249 				throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. Second GA missing");
250 			
251 			if (a.value.byteAt(0) != 0x04) 
252 				throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key does not start with '04'");
253 			
254 			var ephKey = this.pace.getEphemeralPublicKey();
255 			response.add(new ASN1(0x84, ephKey));
256 
257 			// Store idPICC for later terminal authentication
258 			this.idPICC = ephKey.bytes(1, (ephKey.length - 1) >> 1);
259 
260 			this.pace.performKeyAgreement(a.value);
261 			break;
262 		case 0x85:
263 			if (apdu.isChained())
264 				throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_LASTCMDEXPECTED, "Last PACE command must not be chained");
265 			
266 			if (this.lastINS != APDU.INS_GENERAL_AUTHENTICATE)
267 				throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. Second GA missing");
268 
269 			if (!this.pace.verifyAuthenticationToken(a.value)) {
270 				var sw = APDU.SW_WARNINGNVCHG;
271 				if (this.paceao.initialretrycounter) {
272 					sw |= 0xC0 + this.paceao.retrycounter;
273 				}
274 				throw new GPError("EACSIM", GPError.INVALID_DATA, sw, "Verification of authentication token failed");
275 			}
276 
277 			this.paceao.restoreRetryCounter();
278 			this.fileSelector.addAuthenticationState(true, this.paceao);
279 //			print(this.fileSelector);
280 //			print(this.fileSelector.isAuthenticated(true, this.paceao));
281 
282 			var authToken = this.pace.calculateAuthenticationToken();
283 
284 			response.add(new ASN1(0x86, authToken));
285 			if (this.chat) {
286 				var pkiid = this.chat.get(0).value.right(1).toUnsigned();
287 				var anchor = this.fileSelector.getObject(TrustAnchor.TYPE, pkiid);
288 				if (!anchor) {
289 					throw new GPError("EACSIM", GPError.INVALID_DATA, 0, "Invalid PKI in chat");
290 				}
291 				anchor.addCARforPACE(response);
292 			}
293 
294 			var symalgo = this.pace.getSymmetricAlgorithm();
295 
296 			if (symalgo == Key.AES) {
297 				var sm = new SecureChannel(this.crypto);
298 				sm.setSendSequenceCounterPolicy(IsoSecureChannel.SSC_SYNC_ENC_POLICY);
299 				sm.setMacKey(this.pace.kmac);
300 				sm.setEncKey(this.pace.kenc);
301 				sm.setMACSendSequenceCounter(new ByteString("00000000000000000000000000000000", HEX));
302 			} else {
303 				var sm = new SecureChannel(this.crypto);
304 				sm.setMacKey(this.pace.kmac);
305 				sm.setEncKey(this.pace.kenc);
306 				sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX));
307 			}
308 			this.setSecureChannel(sm);
309 
310 			break;
311 		default:
312 			throw new GPError("EACSIM", GPError.INVALID_DATA, 0, "Unsupported Dynamic Authentication Data");
313 		}
314 	}
315 
316 	apdu.setRData(response.getBytes());
317 	apdu.setSW(APDU.SW_OK);
318 }
319 
320 
321 
322 /**
323  * Intercept MANAGE SE for PACE to determine status of PIN
324  *
325  * @param {APDU} the apdu
326  */
327 eIDCommandInterpreter.prototype.determinePINStatus = function(apdu) {
328 	var tlv = new ASN1(apdu.getP2(), apdu.getCData());
329 	tlv = new ASN1(tlv.getBytes());		// Dirty trick to deserialize as TLV tree
330 	var keyref = tlv.find(0x83);
331 	if (!keyref) {
332 		return;
333 	}
334 	
335 	var paceao = this.fileSelector.getObject(AuthenticationObject.TYPE_PACE, keyref.value.toUnsigned());
336 	if (!paceao) {
337 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found");
338 	}
339 	if (!paceao.isActive) {
340 		apdu.setSW(APDU.SW_INVFILE);
341 	} else {
342 		if (paceao.initialretrycounter) {
343 			if (paceao.retrycounter != paceao.initialretrycounter) {
344 				apdu.setSW(APDU.SW_WARNINGCOUNT + paceao.retrycounter);
345 			}
346 		}
347 	}
348 }
349 
350 
351 
352 /**
353  * Process GENERAL AUTHENTICATE command to perform chip authentication in version 1
354  *
355  * @param {APDU} the apdu
356  */
357 eIDCommandInterpreter.prototype.performChipAuthenticationV1 = function(apdu) {
358 
359 	var a = new ASN1(0x30, apdu.getCData());
360 	a = new ASN1(a.getBytes());
361 
362 	if ((a.elements == 0) || (a.elements > 2)) {
363 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data must contain 1..2 TLV elements");
364 	}
365 
366 	if (a.get(0).tag != 0x91) {
367 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key data elements must have tag '91'");
368 	}
369 
370 	var chipAuthenticationInfo = this.fileSelector.getMeta("chipAuthenticationInfo");
371 	var chipAuthenticationPublicKey = this.fileSelector.getMeta("chipAuthenticationPublicKey");
372 	var chipAuthenticationPrivateKey = this.fileSelector.getMeta("chipAuthenticationPrivateKey");
373 
374 	assert(chipAuthenticationInfo);
375 	assert(chipAuthenticationPublicKey);
376 	assert(chipAuthenticationPrivateKey);
377 
378 	// ToDo: Select key based on MSE SET
379 	var ca = new ChipAuthentication(this.crypto, chipAuthenticationInfo.protocol, chipAuthenticationPublicKey);
380 
381 	ca.setKeyPair(chipAuthenticationPrivateKey, chipAuthenticationPublicKey);
382 
383 	var puk = a.get(0).value;
384 	ca.performKeyAgreement(puk);
385 
386 	this.idIFD = puk.bytes(1).left(puk.length >> 1);
387 
388 	var sm = new SecureChannel(this.crypto);
389 	sm.setMacKey(ca.kmac);
390 	sm.setEncKey(ca.kenc);
391 	sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX));
392 	this.setSecureChannel(sm);
393 
394 	apdu.setSW(APDU.SW_OK);
395 }
396 
397 
398 
399 /**
400  * Process GENERAL AUTHENTICATE command to perform chip authentication in version 2
401  *
402  * @param {APDU} the apdu
403  */
404 eIDCommandInterpreter.prototype.performChipAuthenticationV2 = function(apdu) {
405 
406 	if (apdu.isChained()) {
407 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in GENERAL AUTHENTICATE for chip authentication");
408 	}
409 
410 	var a = new ASN1(apdu.getCData());
411 	var response = new ASN1(0x7C);
412 
413 	var chipAuthenticationInfo = this.fileSelector.getMeta("groupChipAuthenticationInfo");
414 	var chipAuthenticationPublicKey = this.fileSelector.getMeta("groupChipAuthenticationPublicKey");
415 	var chipAuthenticationPrivateKey = this.fileSelector.getMeta("groupChipAuthenticationPrivateKey");
416 
417 	assert(chipAuthenticationInfo);
418 	assert(chipAuthenticationPublicKey);
419 	assert(chipAuthenticationPrivateKey);
420 
421 	var uniqueChipAuthenticationInfo = this.fileSelector.getMeta("uniqueChipAuthenticationInfo");
422 	var uniqueChipAuthenticationPublicKey = this.fileSelector.getMeta("uniqueChipAuthenticationPublicKey");
423 	var uniqueChipAuthenticationPrivateKey = this.fileSelector.getMeta("uniqueChipAuthenticationPrivateKey");
424 
425 	var se = this.fileSelector.getSecurityEnvironment().CDIK;
426 
427 	if (!se.t.AT) {
428 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "No security environment found");
429 	}
430 
431 	var keyref = se.t.AT.find(0x84);
432 	if (keyref) {
433 		var uniqueChipAuthenticationInfo = this.fileSelector.getMeta("uniqueChipAuthenticationInfo");
434 		if (uniqueChipAuthenticationInfo && (uniqueChipAuthenticationInfo.keyId == keyref.value.toUnsigned())) {
435 			var ac = this.fileSelector.getMeta("accessController");
436 
437 			if (ac && !ac.checkRight(this, apdu, 3)) {
438 				throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "No privileged terminal right to access unique chip authentication key");
439 			}
440 
441 			chipAuthenticationInfo = uniqueChipAuthenticationInfo;
442 			chipAuthenticationPublicKey = this.fileSelector.getMeta("uniqueChipAuthenticationPublicKey");
443 			chipAuthenticationPrivateKey = this.fileSelector.getMeta("uniqueChipAuthenticationPrivateKey");
444 		} else {
445 			if (chipAuthenticationInfo.keyId && (keyref.value.toUnsigned() != chipAuthenticationInfo.keyId)) {
446 				throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key id " + keyref.value.toUnsigned() + " does not match a chip authentication key");
447 			}
448 		}
449 	}
450 
451 	// Extract idIFD and make sure it's the same used in TA
452 	var idIFD = a.get(0).value.bytes(1);		// Skip '04' and extract public key
453 	idIFD = idIFD.left(idIFD.length >> 1);
454 
455 	if (!idIFD.equals(this.idIFD)) {
456 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Terminal public key does not match key signed in terminal authentication");
457 	}
458 
459 	var ca = new ChipAuthentication(this.crypto, chipAuthenticationInfo.protocol, chipAuthenticationPublicKey);
460 
461 	ca.setKeyPair(chipAuthenticationPrivateKey, chipAuthenticationPublicKey);
462 
463 	var nonce = this.crypto.generateRandom(8);
464 
465 	ca.performKeyAgreement(a.get(0).value, nonce);
466 	var token = ca.calculateAuthenticationToken();
467 
468 	response.add(new ASN1(0x81, nonce));
469 	response.add(new ASN1(0x82, token));
470 
471 	apdu.setRData(response.getBytes());
472 	
473 	if (ca.algo.equals(ChipAuthentication.id_CA_ECDH_3DES_CBC_CBC)) {
474 //		print("DES");
475 		var sm = new SecureChannel(this.crypto);
476 		sm.setMacKey(ca.kmac);
477 		sm.setEncKey(ca.kenc);
478 		sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX));
479 	} else {
480 //		print("AES");
481 		var sm = new SecureChannel(this.crypto);
482 		sm.setSendSequenceCounterPolicy(IsoSecureChannel.SSC_SYNC_ENC_POLICY);
483 		sm.setMacKey(ca.kmac);
484 		sm.setEncKey(ca.kenc);
485 		sm.setMACSendSequenceCounter(new ByteString("00000000000000000000000000000000", HEX));
486 	}
487 	this.setSecureChannel(sm);
488 
489 	apdu.setSW(APDU.SW_OK);
490 }
491 
492 
493 
494 /**
495  * Locate public key either in trust anchor, trusted DVCA or trusted terminal
496  *
497  * @param {PublicKeyReference} keyid the public key reference to look for
498  * @type object
499  * @return object with properties level (issuer is 0-CVCA, 1-DVCA or 2-Terminal) and anchor (Trust Anchor)
500  */
501 eIDCommandInterpreter.prototype.locatePublicKey = function(keyid) {
502 	var idlist = this.fileSelector.enumerateObjects(TrustAnchor.TYPE);
503 //	print(idlist);
504 
505 	for each (var i in idlist) {
506 		var anchor = this.fileSelector.getObject(TrustAnchor.TYPE, i);
507 //		print(anchor.root.getCHR().toString());
508 		if (anchor.isIssuer(keyid)) {
509 			return { level: 0, anchor: anchor };
510 		}
511 	}
512 
513 	if (this.trustedDVCA && (this.trustedDVCA.getCHR().equals(keyid))) {
514 		var r = this.locatePublicKey(this.trustedDVCA.getCAR());
515 		r.level = 1;
516 		return r;
517 	}
518 
519 	if (this.trustedTerminal && (this.trustedTerminal.getCHR().equals(keyid))) {
520 		var r = this.locatePublicKey(this.trustedDVCA.getCAR());
521 		r.level = 2;
522 		return r;
523 	}
524 	return null;
525 }
526 
527 
528 
529 /**
530  * Process PSO VERIFY CERTIFICATE command
531  *
532  * @param {APDU} the apdu
533  */
534 eIDCommandInterpreter.prototype.verifyCertificate = function(apdu) {
535 
536 	if (apdu.isChained()) {
537 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in PSO VERIFY CERTIFICATE");
538 	}
539 	
540 	if (!apdu.hasCData()) {
541 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data expected in PSO VERIFY CERTIFICATE");
542 	}
543 	
544 	if ((apdu.getP1() != 0x00) || (apdu.getP2() != 0xBE)) {
545 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
546 	}
547 
548 	// Reconstruct CVC
549 	var a = new ASN1(0x7F21, apdu.getCData());
550 	try	{
551 		a = new ASN1(a.getBytes());
552 	}
553 	catch(e) {
554 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid certificate format");
555 	}
556 
557 	// Do some basic format checking
558 	if ((a.elements != 2) || (a.get(0).tag != 0x7F4e) || (a.get(1).tag != 0x5F37)) {
559 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid certificate format");
560 	}
561 
562 	var cvc = new CVC(a);
563 
564 //	print(cvc);
565 
566 	// Determine public key for checking CVC signature
567 	var se = this.fileSelector.getSecurityEnvironment().VEXK;
568 //	print(se);
569 
570 	if (!se.t.DST) {
571 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No public key set in MANAGE SE for verification");
572 	}
573 
574 	var keyref = se.t.DST.find(0x83);
575 	if (!keyref) {
576 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No public key set in MANAGE SE for verification");
577 	}
578 
579 	var keyid = new PublicKeyReference(keyref.value);
580 //	print("KeyID: " + keyid);
581 
582 	var rc = this.locatePublicKey(keyid);
583 	if (rc == null) {
584 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Referenced public key " + keyid + " not found");
585 	}
586 
587 	var anchor = rc.anchor;
588 //	print(anchor);
589 //	print(rc.level);
590 	
591 	switch(rc.level) {
592 		case 0:
593 			anchor.validateCertificateIssuedByCVCA(this.crypto, cvc, this);
594 
595 			var chat = cvc.getCHAT();
596 			var certtype = chat.get(1).value.byteAt(0) & 0xC0;
597 
598 			if ((certtype == 0x80) || (certtype == 0x40)) {
599 				this.trustedDVCA = cvc;
600 			}
601 			break;
602 		case 1:
603 			anchor.validateCertificateIssuedByDVCA(this.crypto, cvc, this.trustedDVCA, this);
604 			this.trustedTerminal = cvc;
605 			break;
606 		default:
607 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Referenced public key " + keyid + " is a terminal key");
608 	}
609 
610 	apdu.setSW(APDU.SW_OK);
611 }
612 
613 
614 
615 /**
616  * Process EXTERNAL AUTHENTICATE command to perform terminal authentication
617  *
618  * @param {APDU} the apdu
619  * @param {SecurityEnvironment} se the security environment for external authentication
620  */
621 eIDCommandInterpreter.prototype.externalAuthenticateForTA = function(apdu, se) {
622 	if (apdu.isChained()) {
623 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in EXTERNAL AUTHENTICATE");
624 	}
625 
626 	if (!apdu.hasCData()) {
627 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data expected in EXTERNAL AUTHENTICATE");
628 	}
629 
630 	if ((apdu.getP1() != 0x00) || (apdu.getP2() != 0x00)) {
631 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
632 	}
633 
634 	if (!apdu.isSecureMessaging()) {
635 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Terminal authentication can only be performed with secure messaging");
636 	}
637 
638 	if (this.isAuthenticatedTerminal()) {
639 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Terminal authentication can only be performed once in a session");
640 	}
641 
642 	if (!this.challenge) {
643 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Must obtain challenge before external authenticate");
644 	}
645 
646 	if (this.challenge.length < 8) {
647 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Challenge must be larger or equal 8 bytes");
648 	}
649 
650 	// Invalidate challenge
651 	var challenge = this.challenge;
652 	this.challenge = null;
653 
654 	if (this.trustedTerminal == null) {
655 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No terminal certificate found");
656 	}
657 
658 	if (this.chat) {
659 		var tchat = this.trustedTerminal.getCHAT();
660 		if (!tchat.get(0).value.equals(this.chat.get(0).value)) {
661 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "CHAT in terminal certificate does not match CHAT in PACE");
662 		}
663 		if (tchat.get(1).value.length != this.chat.get(1).value.length) {
664 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "CHAT in terminal certificate has different length than CHAT in PACE");
665 		}
666 	}
667 
668 	var keyref = se.t.AT.find(0x83);
669 	if (!keyref) {
670 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No public key set in MANAGE SE for terminal authentication");
671 	}
672 
673 	var keyid = new PublicKeyReference(keyref.value);
674 //	print("KeyID: " + keyid);
675 
676 	var rc = this.locatePublicKey(keyid);
677 	if (rc == null) {
678 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Referenced public key " + keyid + " not found");
679 	}
680 
681 	if (rc.level != 2) {
682 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Referenced public key is not a terminal key");
683 	}
684 
685 	var dp = rc.anchor.getPublicKeyFor(this.trustedDVCA.getCAR());
686 	var puk = this.trustedTerminal.getPublicKey(dp);
687 
688 	if (typeof(this.idIFD) == "undefined") {			// CA not already performed ? Then we do EAC 2.x
689 		var cakey = se.t.AT.find(0x91);
690 		if (!cakey) {
691 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No chip authentication ephemeral key found");
692 		}
693 		this.idIFD = cakey.value;
694 	}
695 
696 	if (typeof(this.idPICC) == "undefined") {
697 		this.idPICC = this.fileSelector.getMeta("idPICC");
698 	}
699 
700 	var bb = new ByteBuffer();
701 	bb.append(this.idPICC);
702 	bb.append(challenge);
703 	bb.append(this.idIFD);
704 
705 	var auxdata = se.t.AT.find(0x67);
706 
707 	if (auxdata) {
708 		bb.append(auxdata.getBytes());
709 	}
710 	var signatureInput = bb.toByteString();
711 
712 	var signature = ECCUtils.wrapSignature(apdu.getCData());
713 	var mech = CVC.getSignatureMech(this.trustedTerminal.getPublicKeyOID(dp));
714 	if (!this.crypto.verify(puk, mech, signatureInput, signature)) {
715 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WARNINGNVCHG, "Verification of terminal authentication signature failed");
716 	}
717 
718 	// Determine effective rights
719 	var cvc = rc.anchor.getCertificateFor(this.trustedDVCA.getCAR());
720 	var er = cvc.getCHAT().get(1).value;
721 
722 	er = er.and(this.trustedDVCA.getCHAT().get(1).value);
723 	er = er.and(this.trustedTerminal.getCHAT().get(1).value);
724 
725 	if (this.chat) {
726 		er = er.and(this.chat.get(1).value);
727 	}
728 
729 //	print("Effective rights : " + er);
730 	this.effectiveRights = er;
731 
732 	apdu.setSW(APDU.SW_OK);
733 }
734 
735 
736 
737 /**
738  * Process GENERAL AUTHENTICATE command to perform restricted identification
739  *
740  * @param {APDU} the apdu
741  */
742 eIDCommandInterpreter.prototype.performRestrictedIdentification = function(apdu) {
743 	if (apdu.isChained()) {
744 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in GENERAL AUTHENTICATE for chip authentication");
745 	}
746 
747 	if (!apdu.hasLe()) {
748 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field");
749 	}
750 
751 	if (apdu.isChained()) {
752 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in ");
753 	}
754 
755 	var rikeys = this.fileSelector.getMeta("RIKeys");
756 	assert(rikeys, "No RI keys defined im meta data");
757 
758 	var se = this.fileSelector.getSecurityEnvironment().CDIK;
759 //	print(se);
760 
761 	var keyid = 0;
762 	if (se.t.AT) {
763 		var keyref = se.t.AT.find(0x84);
764 		if (keyref) {
765 			keyid = keyref.value.toUnsigned();
766 		}
767 	}
768 
769 	if (!rikeys[keyid]) {
770 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Restricted identification key not found");
771 	}
772 
773 	var ri = rikeys[keyid];
774 	GPSystem.trace("Selected key " + keyid + " for restricted identification(authorizedOnly=" + (ri.authorizedOnly ? "true" : "false") + ")");
775 
776 	var ac = this.fileSelector.getMeta("accessController");
777 
778 	if (ac && ri.authorizedOnly) {
779 		if (!ac.checkRight(this, apdu, 2)) {
780 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Restricted identification requires right in id-AT");
781 		}
782 	}
783 
784 	var a = new ASN1(apdu.getCData());
785 	var rido = a.get(0);
786 	
787 	var response = new ASN1(0x7C);
788 
789 	var pk = new ASN1(0x7F49, rido.value);
790 	pk = new ASN1(pk.getBytes());		// Rebuild tree after replacing tag
791 
792 //	print(pk);
793 	var mech = CVC.getHashMech(this.trustedDVCA.getPublicKeyOID());
794 	var hash = this.crypto.digest(mech, pk.getBytes());
795 //	print(hash);
796 
797 	var ext = this.trustedTerminal.getExtension(new ByteString("id-sector", OID));
798 	if (!ext) {
799 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Terminal certificate does not contain a sector-id");
800 	}
801 
802 	if (rido.tag == 0xA0) {
803 		var stag = 0x80;
804 		var rtag = 0x81;
805 	} else if (rido.tag == 0xA2) {
806 		var stag = 0x81;
807 		var rtag = 0x83;
808 	} else {
809 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Dynamic data for restricted identification requires either 'A0' or 'A2' data element");
810 	}
811 
812 	var hashdo = ext.find(stag);
813 	if (!hashdo) {
814 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Sector-id extension does not contain a hash value");
815 	}
816 
817 	if (!hashdo.value.equals(hash)) {
818 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Sector-id does not match provided public key");
819 	}
820 
821 	var sectorPuK = new Key();
822 	sectorPuK.setType(Key.PUBLIC);
823 	CVC.decodeECPublicKey(pk, sectorPuK);
824 
825 	var inp = sectorPuK.getComponent(Key.ECC_QX).concat(sectorPuK.getComponent(Key.ECC_QY));
826 	var id = this.crypto.digest(mech, this.crypto.decrypt(ri.prk, Crypto.ECDH, inp));
827 
828 	response.add(new ASN1(rtag, id));
829 
830 	apdu.setRData(response.getBytes());
831 	apdu.setSW(APDU.SW_OK);
832 }
833 
834 
835 
836 /**
837  * Process GET CHALLENGE command
838  *
839  * @param {APDU} the apdu
840  */
841 eIDCommandInterpreter.prototype.getChallenge = function(apdu) {
842 	if (!apdu.hasLe()) {
843 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field");
844 	}
845 
846 	if (apdu.isChained()) {
847 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in GET CHALLENGE");
848 	}
849 	
850 	if (apdu.hasCData()) {
851 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data not expected in GET CHALLENGE");
852 	}
853 	
854 	var l = apdu.getNe();
855 	this.challenge = this.crypto.generateRandom(l);
856 	apdu.setRData(this.challenge);
857 	
858 	apdu.setSW(APDU.SW_OK);
859 }
860 
861 
862 
863 /**
864  * Performs an EXTERNAL AUTHENTICATE command for BAC
865  *
866  * @param {APDU} the apdu
867  */
868 eIDCommandInterpreter.prototype.externalAuthenticateForBAC = function(apdu) {
869 	if (!apdu.hasLe()) {
870 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field");
871 	}
872 
873 	if (apdu.isChained()) {
874 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in EXTERNAL AUTHENTICATE");
875 	}
876 	
877 	if (!apdu.hasCData()) {
878 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command requires C-data");
879 	}
880 
881 	if ((apdu.getP1() != 0x00) || (apdu.getP2() != 0x00)) {
882 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
883 	}
884 
885 	if (!this.challenge) {
886 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Must obtain challenge before external authenticate");
887 	}
888 
889 	if (this.challenge.length < 8) {
890 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Challenge must be larger or equal 8 bytes");
891 	}
892 
893 	var challenge = this.challenge;
894 	this.challenge = null;
895 
896 	var cryptogram = apdu.getCData();
897 
898 	if (cryptogram.length != 40) {
899 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Cryptogram must be 40 bytes long");
900 	}
901 
902 	var k_enc_bac = this.fileSelector.getMeta("KENC");
903 	var k_mac_bac = this.fileSelector.getMeta("KMAC");
904 
905 	if (!k_enc_bac || !k_mac_bac) {
906 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_GENERALERROR, "No K_ENC or K_MAC defined for DF");
907 	}
908 
909 	var mac = cryptogram.right(8);
910 	cryptogram = cryptogram.left(32);
911 
912 	if (!this.crypto.verify(k_mac_bac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2), mac)) {
913 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WARNINGNVCHG, "Authentication failed");
914 	}
915 
916 	var plain = this.crypto.decrypt(k_enc_bac, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX));
917 
918 	var rndifd = plain.bytes(0, 8);
919 	var rndicc = plain.bytes(8, 8);
920 	var kifd = plain.bytes(16, 16);
921 
922 	if (!rndicc.equals(challenge)) {
923 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WARNINGNVCHG, "RNDicc in cryptogram does not match last response in GET CHALLENGE");
924 	}
925 
926 	var kicc = this.crypto.generateRandom(16);
927 	var plain = rndicc.concat(rndifd).concat(kicc);
928 
929 	var cryptogram = this.crypto.encrypt(k_enc_bac, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX));
930 	var mac = this.crypto.sign(k_mac_bac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2));
931 
932 	apdu.setRData(cryptogram.concat(mac));
933 
934 	keyinp = kicc.xor(kifd);
935 
936 	var hashin = keyinp.concat(new ByteString("00000001", HEX));
937 	var kencval = this.crypto.digest(Crypto.SHA_1, hashin);
938 	kencval = kencval.bytes(0, 16);
939 	var kenc = new Key();
940 	kenc.setComponent(Key.DES, kencval);
941 
942 	var hashin = keyinp.concat(new ByteString("00000002", HEX));
943 	var kmacval = this.crypto.digest(Crypto.SHA_1, hashin);
944 	kmacval = kmacval.bytes(0, 16);
945 	var kmac = new Key();
946 	kmac.setComponent(Key.DES, kmacval);
947 
948 	var ssc = rndicc.bytes(4, 4).concat(rndifd.bytes(4, 4));
949 
950 	var sm = new SecureChannel(this.crypto);
951 	sm.setMacKey(kmac);
952 	sm.setEncKey(kenc);
953 	sm.setMACSendSequenceCounter(ssc);
954 	this.setSecureChannel(sm);
955 
956 	apdu.setSW(APDU.SW_OK);
957 }
958 
959 
960 
961 /**
962  * Process VERIFY(AD)
963  *
964  * @param {APDU} the apdu
965  */
966 eIDCommandInterpreter.prototype.verifyAuxiliaryData = function(apdu) {
967 	if (apdu.isChained()) {
968 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in VERIFY(AUX)");
969 	}
970 
971 	if (!apdu.hasCData()) {
972 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command requires C-data");
973 	}
974 
975 	if ((apdu.getP1() != 0x80) || (apdu.getP2() != 0x00)) {
976 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
977 	}
978 
979 	var oid = apdu.getCData();
980 	if ((oid.byteAt(0) != 0x06) || (oid.byteAt(1) != oid.length - 2)) {
981 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Malformed object identifier in C-Data");
982 	}
983 	oid = oid.bytes(2);
984 
985 	if (!oid.equals(new ByteString("id-DateOfExpiry", OID)) && 
986 		!oid.equals(new ByteString("id-CommunityID", OID)) &&
987 		!oid.equals(new ByteString("id-DateOfBirth", OID))) {
988 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Unknown object identifier in C-Data");
989 	}
990 
991 	var se = this.fileSelector.getSecurityEnvironment().VEXK;
992 
993 	if (!se.t.AT) {
994 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "No security environment found");
995 	}
996 
997 	var ad = se.t.AT.find(0x67);
998 	if (!ad) {
999 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "No auxiliary data found in security environment");
1000 	}
1001 	
1002 	var refdata;
1003 	for (var i = 0; i < ad.elements; i++) {
1004 		var ade = ad.get(i);
1005 		if (ade.tag == 0x73) {
1006 			if ((ade.elements != 2) || (ade.get(0).tag != ASN1.OBJECT_IDENTIFIER) || (ade.get(1).tag != 0x53)) {
1007 				throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Malformed auxiliary data found in security environment");
1008 			}
1009 			if (ade.get(0).value.equals(oid)) {
1010 				var refdata = ade.get(1).value;
1011 				break;
1012 			}
1013 		}
1014 	}
1015 
1016 //	print("RefData: " + refdata.toString(ASCII));
1017 
1018 	var ac = this.fileSelector.getMeta("accessController");
1019 	
1020 	if (oid.equals(new ByteString("id-CommunityID", OID))) {
1021 		if (ac && !ac.checkRight(this, apdu, 1)) {
1022 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Community ID Verification not allowed");
1023 		}
1024 		var id = this.fileSelector.getMeta("CommunityID");
1025 		assert(id, "Community ID not defined as part of meta data");
1026 		id = new ByteString(id, ASCII);
1027 		if (id.startsWith(refdata) == refdata.length) {
1028 			apdu.setSW(APDU.SW_OK);
1029 		} else {
1030 			apdu.setSW(APDU.SW_WARNINGNVCHG);
1031 		}
1032 	} else if (oid.equals(new ByteString("id-DateOfExpiry", OID))) {
1033 		var doe = this.fileSelector.getMeta("DateOfExpiry");
1034 		assert(doe, "Data of expiry not defined as part of meta data");
1035 		if (refdata.toString(ASCII) <= doe) {
1036 			apdu.setSW(APDU.SW_OK);
1037 		} else {
1038 			apdu.setSW(APDU.SW_WARNINGNVCHG);
1039 		}
1040 	} else {
1041 		if (ac && !ac.checkRight(this, apdu, 0)) {
1042 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Age Verification not allowed");
1043 		}
1044 		var dob = this.fileSelector.getMeta("DateOfBirth");
1045 		assert(dob, "Data of birth not defined as part of meta data");
1046 		if (refdata.toString(ASCII) >= dob) {
1047 			apdu.setSW(APDU.SW_OK);
1048 		} else {
1049 			apdu.setSW(APDU.SW_WARNINGNVCHG);
1050 		}
1051 	}
1052 }
1053 
1054 
1055 
1056 /**
1057  * Performs an EXTERNAL AUTHENTICATE command
1058  *
1059  * @param {APDU} the apdu
1060  */
1061 eIDCommandInterpreter.prototype.externalAuthenticate = function(apdu) {
1062 	var se = this.fileSelector.getSecurityEnvironment().VEXK;
1063 
1064 	if (se.t.AT) {
1065 		this.externalAuthenticateForTA(apdu, se);
1066 	} else {
1067 		this.externalAuthenticateForBAC(apdu);
1068 	}
1069 }
1070 
1071 
1072 
1073 /**
1074  * Performs an ACTIVATE/DEACTIVATE command
1075  *
1076  * @param {APDU} the apdu
1077  */
1078 eIDCommandInterpreter.prototype.manageActiveState = function(apdu) {
1079 	if (apdu.isChained()) {
1080 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command");
1081 	}
1082 	if (apdu.hasCData()) {
1083 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must not have C-data");
1084 	}
1085 	if (apdu.getP1() != 0x10) {
1086 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
1087 	}
1088 
1089 	var pacekeys = this.fileSelector.getMeta(AuthenticationObject.TYPE_PACE);
1090 	assert(pacekeys, "No PACE authentication objects defined");
1091 
1092 	var paceao = pacekeys[apdu.getP2()];
1093 	if (!paceao) {
1094 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found");
1095 	}
1096 
1097 	var ac = this.fileSelector.getMeta("accessController");
1098 	if (!(ac && ac.checkRight(this, apdu, 5))) {
1099 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Activate requires right 5 in id-AT");
1100 	}
1101 
1102 	if (apdu.getINS() == APDU.INS_ACTIVATE) {
1103 		paceao.activate();
1104 	} else {
1105 		paceao.deactivate();
1106 	}
1107 
1108 	apdu.setSW(APDU.SW_OK);
1109 }
1110 
1111 
1112 
1113 /**
1114  * Performs a RESET RETRY COUNTER command for PACE keys
1115  *
1116  * @param {APDU} the apdu
1117  */
1118 eIDCommandInterpreter.prototype.resetRetryCounterPACE = function(apdu) {
1119 	if (apdu.isChained()) {
1120 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command");
1121 	}
1122 	var p1 = apdu.getP1();
1123 
1124 	if (p1 == 0x02) {
1125 		if (!apdu.hasCData()) {
1126 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data");
1127 		}
1128 	} else if (p1 == 0x03) {
1129 		if (apdu.hasCData()) {
1130 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must not have C-data");
1131 		}
1132 	} else {
1133 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
1134 	}
1135 
1136 	var pacekeys = this.fileSelector.getMeta(AuthenticationObject.TYPE_PACE);
1137 	assert(pacekeys, "No PACE authentication objects defined");
1138 
1139 	var paceao = pacekeys[apdu.getP2()];
1140 	if (!paceao) {
1141 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found");
1142 	}
1143 
1144 	if (!apdu.isSecureMessaging()) {
1145 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Secure messaging required for reset retry counter");
1146 	}
1147 
1148 	var ac = this.fileSelector.getMeta("accessController");
1149 
1150 	if (!(	this.fileSelector.isAuthenticated(true, paceao) ||
1151 			((typeof(paceao.unblockAuthenticationObject) != "undefined") && this.fileSelector.isAuthenticated(true, paceao.unblockAuthenticationObject)) ||
1152 			(ac && ac.checkRight(this, apdu, 5))   )) {
1153 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Security condition for reset retry counter not satisfied");
1154 	}
1155 	paceao.resetRetryCounter(apdu.getCData());
1156 
1157 	apdu.setSW(APDU.SW_OK);
1158 }
1159 
1160 
1161 
1162 /**
1163  * Performs a TERMINATE(PIN) command
1164  *
1165  * @param {APDU} the apdu
1166  */
1167 eIDCommandInterpreter.prototype.terminatePIN = function(apdu) {
1168 	if (apdu.hasCData()) {
1169 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must not have C-data");
1170 	}
1171 
1172 	var pinao = this.fileSelector.getObject(AuthenticationObject.TYPE_PIN, apdu.getP2());
1173 	if (!pinao) {
1174 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PIN with reference " + apdu.getP2() + " not found");
1175 	}
1176 
1177 	pinao.terminate();
1178 	apdu.setSW(APDU.SW_OK);
1179 }
1180 
1181 
1182 
1183 /**
1184  * Performs a TERMINATE(Key) command
1185  *
1186  * @param {APDU} the apdu
1187  */
1188 eIDCommandInterpreter.prototype.terminateKey = function(apdu) {
1189 	if (!apdu.hasCData()) {
1190 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data");
1191 	}
1192 	var crt = new ASN1(apdu.getCData());
1193 
1194 	if (crt.tag != 0xB6) {
1195 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Digital Signature Template (DST 'B6') not found");
1196 	}
1197 
1198 	var ref = crt.find(0x84);
1199 	if (!ref) {
1200 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Private key reference object '84' not found");
1201 	}
1202 	var id = ref.value.toUnsigned();
1203 
1204 	var key = this.fileSelector.getObject(SignatureKey.TYPE_KEY, id);
1205 	if (!key) {
1206 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key with reference " + id + " not found");
1207 	}
1208 
1209 	if (!key.useAuthenticationObject.isTerminated) {
1210 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_REFDATANOTUSABLE, "Authentication object is not terminated");
1211 	}
1212 	
1213 	key.terminate();
1214 	apdu.setSW(APDU.SW_OK);
1215 }
1216 
1217 
1218 
1219 /**
1220  * Performs a TERMINATE command
1221  *
1222  * @param {APDU} the apdu
1223  */
1224 eIDCommandInterpreter.prototype.terminate = function(apdu) {
1225 	if (apdu.isChained()) {
1226 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command");
1227 	}
1228 
1229 	switch(apdu.getP1()) {
1230 	case 0x10:
1231 		this.terminatePIN(apdu);
1232 		break;
1233 	case 0x21:
1234 		this.terminateKey(apdu);
1235 		break;
1236 	default:
1237 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
1238 	}
1239 
1240 	apdu.setSW(APDU.SW_OK);
1241 }
1242 
1243 
1244 
1245 /**
1246  * Performs a TERMINATE(Key) command
1247  *
1248  * @param {APDU} the apdu
1249  */
1250 eIDCommandInterpreter.prototype.generateAsymmetricKeyPair = function(apdu) {
1251 	if (!apdu.hasCData()) {
1252 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data");
1253 	}
1254 	if ((apdu.getP1() != 0x82) || (apdu.getP2() != 0x00)) {
1255 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
1256 	}
1257 	var a = new ASN1(0x30, apdu.getCData());
1258 	a = new ASN1(a.getBytes());
1259 //	print(a);
1260 
1261 	var crt = a.get(0);
1262 	if (crt.tag != 0xB6) {
1263 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Digital Signature Template (DST 'B6') not found");
1264 	}
1265 
1266 	var ref = crt.find(0x84);
1267 	if (!ref) {
1268 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Private key reference object '84' not found");
1269 	}
1270 	var id = ref.value.toUnsigned();
1271 
1272 	var keyobj = this.fileSelector.getObject(SignatureKey.TYPE_KEY, id);
1273 	if (!keyobj) {
1274 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key with reference " + id + " not found");
1275 	}
1276 
1277 	var dpt = a.get(1);
1278 	if (dpt.tag != 0x7F49) {
1279 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Private key reference object '84' not found");
1280 	}
1281 
1282 	if (dpt.elements != 7) {
1283 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key must contain 7 elements");
1284 	}
1285 
1286 	var puk = new Key();
1287 	puk.setType(Key.PUBLIC);
1288 	puk.setComponent(Key.ECC_P, dpt.find(0x81).value);
1289 	puk.setComponent(Key.ECC_A, dpt.find(0x82).value);
1290 	puk.setComponent(Key.ECC_B, dpt.find(0x83).value);
1291 	var g = dpt.find(0x84).value.bytes(1);
1292 	puk.setComponent(Key.ECC_GX, g.left(g.length >> 1));
1293 	puk.setComponent(Key.ECC_GY, g.right(g.length >> 1));
1294 	puk.setComponent(Key.ECC_N, dpt.find(0x85).value);
1295 	puk.setComponent(Key.ECC_H, dpt.find(0x87).value);
1296 
1297 	keyobj.privateKey = new Key();
1298 	keyobj.privateKey.setType(Key.PRIVATE);
1299 	keyobj.isTerminated = false;
1300 	
1301 	this.crypto.generateKeyPair(Crypto.EC, puk, keyobj.privateKey);
1302 
1303 	var encpuk = PACE.encodePublicKey("ecdsa-plain-signatures", puk, true);
1304 	apdu.setRData(encpuk.getBytes());
1305 
1306 	apdu.setSW(APDU.SW_OK);
1307 }
1308 
1309 
1310 
1311 /**
1312  * Performs a COMPUTE DIGITAL SIGNATURE command
1313  *
1314  * @param {APDU} the apdu
1315  */
1316 eIDCommandInterpreter.prototype.computeDigitalSignature = function(apdu) {
1317 	if (!apdu.hasCData()) {
1318 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data");
1319 	}
1320 	if (apdu.getP2() != 0x9A) {
1321 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
1322 	}
1323 
1324 	var se = this.fileSelector.getSecurityEnvironment().CDIK;
1325 //	print(se);
1326 
1327 	var keyid = 0x81;
1328 	if (se.t.DST) {
1329 		var keyref = se.t.DST.find(0x84);
1330 		if (keyref) {
1331 			keyid = keyref.value.toUnsigned();
1332 		}
1333 	}
1334 
1335 //	print("KeyID: " + keyid);
1336 
1337 	var keyobj = this.fileSelector.getObject(SignatureKey.TYPE_KEY, keyid);
1338 	if (!keyobj) {
1339 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key with reference " + keyid + " not found");
1340 	}
1341 
1342 	if (keyobj.isTerminated) {
1343 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_REFDATANOTUSABLE, "Key is terminated");
1344 	}
1345 	
1346 	var signature = this.crypto.sign(keyobj.privateKey, Crypto.ECDSA, apdu.getCData());
1347 
1348 	apdu.setRData(ECCUtils.unwrapSignature(signature), keyobj.privateKey.getComponent(Key.ECC_P).length);
1349 	apdu.setSW(APDU.SW_OK);
1350 }
1351 
1352 
1353 
1354 /**
1355  * Dispatch to command handler based in INS byte in APDU
1356  *
1357  * @param {APDU} apdu the apdu
1358  * @param {Number} ins the normalized instruction code
1359  */
1360 eIDCommandInterpreter.prototype.dispatch = function(apdu, ins) {
1361 	if (!apdu.isISO() && (ins != APDU.INS_VERIFY)) {
1362 		apdu.setSW(APDU.SW_INVCLA);
1363 		return;
1364 	}
1365 
1366 	switch(ins) {
1367 	case APDU.INS_GENERAL_AUTHENTICATE:
1368 		this.generalAuthenticate(apdu);
1369 		break;
1370 	case APDU.INS_GET_CHALLENGE:
1371 		this.getChallenge(apdu);
1372 		break;
1373 	case APDU.INS_EXTERNAL_AUTHENTICATE:
1374 		this.externalAuthenticate(apdu);
1375 		break;
1376 	case APDU.INS_MANAGE_SE:
1377 		if ((apdu.getP1() == 0x41) && (apdu.getP2() == 0xA6)) {
1378 			this.performChipAuthenticationV1(apdu);
1379 		} else {
1380 			CommandInterpreter.prototype.dispatch.call(this, apdu, ins);
1381 			if ((apdu.getP1() == 0xC1) && (apdu.getP2() == 0xA4)) {
1382 				this.determinePINStatus(apdu);
1383 			}
1384 		}
1385 		break;
1386 	case APDU.INS_VERIFY:
1387 		if ((apdu.getCLA() & 0x80) == 0x80) {
1388 			this.verifyAuxiliaryData(apdu);
1389 		} else {
1390 			CommandInterpreter.prototype.dispatch.call(this, apdu, ins);
1391 		}
1392 		break;
1393 	case APDU.INS_RESET_RETRY_COUNTER:
1394 		if (!(apdu.getP2() & 0x80)) {
1395 			this.resetRetryCounterPACE(apdu);				// PACE eID-PIN
1396 		} else {
1397 			CommandInterpreter.prototype.dispatch.call(this, apdu, ins);		// eSign PIN
1398 		}
1399 		break;
1400 	case APDU.INS_ACTIVATE:
1401 		this.manageActiveState(apdu);
1402 		break;
1403 	case APDU.INS_DEACTIVATE:
1404 		this.manageActiveState(apdu);
1405 		break;
1406 	case APDU.INS_TERMINATE:
1407 		this.terminate(apdu);
1408 		break;
1409 	case APDU.INS_PSO:
1410 		if (apdu.getP2() == APDU.INS_VERIFY_CERTIFICATE) {
1411 			this.verifyCertificate(apdu);
1412 		} else if (apdu.getP1() == APDU.INS_COMPUTE_DIGITAL_SIGN) {
1413 			this.computeDigitalSignature(apdu);
1414 		} else {
1415 			CommandInterpreter.prototype.dispatch.call(this, apdu, ins);
1416 		}
1417 		break;
1418 	case APDU.INS_GENERATE_KEY_PAIR:
1419 		this.generateAsymmetricKeyPair(apdu);
1420 		break;
1421 	default:
1422 		CommandInterpreter.prototype.dispatch.call(this, apdu, ins);
1423 	}
1424 
1425 	this.lastINS = ins;
1426 }
1427