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 ISO 7816-4 command interpreter
 25  */
 26 
 27 
 28 
 29 /**
 30  * Create a command interpreter
 31  *
 32  * @class Class implementing a command interpreter that handles ISO 7816-4 command APDUs
 33  * @constructor
 34  * @param {FileSelector} fileSelector the file selector object
 35  */
 36 function CommandInterpreter(fileSelector) {
 37 	this.fileSelector = fileSelector;
 38 }
 39 
 40 
 41 
 42 /**
 43  * Set secure channel
 44  *
 45  * @param {SecureChannel} secureChannel the secure channel to used for unwrapping and wrapping APDUs
 46  */
 47 CommandInterpreter.prototype.setSecureChannel = function(secureChannel) {
 48 	this.secureChannel = secureChannel;
 49 }
 50 
 51 
 52 
 53 /**
 54  * Return status of secure channel
 55  *
 56  * @type boolean
 57  * @return true if secure channel is active
 58  */
 59 CommandInterpreter.prototype.hasSecureChannel = function() {
 60 	return (typeof(this.secureChannel) != "undefined") && (this.secureChannel != null);
 61 }
 62 
 63 
 64 
 65 /**
 66  * Process a READ BINARY APDU
 67  *
 68  * @param {APDU} apdu the command and response APDU
 69  */
 70 CommandInterpreter.prototype.readBinary = function(apdu) {
 71 	if (!apdu.hasLe()) {
 72 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field");
 73 	}
 74 
 75 	if (apdu.isChained()) {
 76 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in READ BINARY");
 77 	}
 78 	
 79 	var dua = new DataUnitAPDU(apdu);
 80 	
 81 	if (dua.hasCData()) {
 82 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data not expected in  READ BINARY");
 83 	}
 84 
 85 	var ef;
 86 	var sfi = dua.getSFI();
 87 	var fid = dua.getFID();
 88 	
 89 	if (sfi >= 0) {
 90 		ef = this.fileSelector.selectSFI(sfi);
 91 	} else if (fid != null) {
 92 		ef = this.fileSelector.selectFID(fid, false, false);
 93 	} else {
 94 		ef = this.fileSelector.getCurrentEF();
 95 	
 96 		if (ef == null) {
 97 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_COMNOTALLOWNOEF, "No current EF in READ BINARY");
 98 		}
 99 	}
100 	
101 	if (!(ef instanceof TransparentEF)) {
102 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_COMINCOMPATIBLE, "EF is not a transparent file in READ BINARY");
103 	}
104 
105 	var ac = this.fileSelector.getMeta("accessController");
106 	if (ac && !ac.checkFileReadAccess(this, apdu, ef)) {
107 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Read access not allowed as determined by " + ac);
108 	}
109 
110 	var offset = dua.getOffset();
111 	var length = apdu.getNe();
112 
113 	var data = ef.readBinary(apdu, offset, length);
114 
115 	apdu.setRData(data);
116 }
117 
118 
119 
120 /**
121  * Process an UPDATE BINARY APDU
122  *
123  * @param {APDU} apdu the command and response APDU
124  */
125 CommandInterpreter.prototype.updateBinary = function(apdu) {
126 	if (apdu.isChained()) {
127 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in READ BINARY");
128 	}
129 	
130 	if (!apdu.hasCData()) {
131 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "No data found in UPDATE BINARY");
132 	}
133 
134 	var dua = new DataUnitAPDU(apdu);
135 	
136 	if (!dua.hasCData()) {
137 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "No data found in UPDATE BINARY");
138 	}
139 
140 	var ef;
141 	var sfi = dua.getSFI();
142 	var fid = dua.getFID();
143 	
144 	if (sfi >= 0) {
145 		ef = this.fileSelector.selectSFI(sfi);
146 	} else if (fid != null) {
147 		ef = this.fileSelector.selectFID(fid, false, false);
148 	} else {
149 		ef = this.fileSelector.getCurrentEF();
150 	
151 		if (ef == null) {
152 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_COMNOTALLOWNOEF, "No current EF in UPDATE BINARY");
153 		}
154 	}
155 	
156 	if (!(ef instanceof TransparentEF)) {
157 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_COMINCOMPATIBLE, "EF is not a transparent file in UPDATE BINARY");
158 	}
159 
160 	var ac = this.fileSelector.getMeta("accessController");
161 	if (ac && !ac.checkFileWriteAccess(this, apdu, ef)) {
162 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Write access not allowed as determined by " + ac);
163 	}
164 
165 	var offset = dua.getOffset();
166 	var data = dua.getCData();
167 
168 	ef.updateBinary(apdu, offset, data);
169 }
170 
171 
172 
173 /**
174  * Process a READ RECORD APDU
175  *
176  * @param {APDU} apdu the command and response APDU
177  */
178 CommandInterpreter.prototype.readRecord = function(apdu) {
179 	if (!apdu.hasLe()) {
180 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field");
181 	}
182 
183 	if (apdu.isChained()) {
184 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in READ RECORD");
185 	}
186 
187 	var recno = apdu.getP1();
188 	var sfi = apdu.getP2() >> 3;
189 	var qualifier = apdu.getP2() & 0x7;
190 
191 	if (sfi > 0) {
192 		ef = this.fileSelector.selectSFI(sfi);
193 	} else {
194 		ef = this.fileSelector.getCurrentEF();
195 
196 		if (ef == null) {
197 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_COMNOTALLOWNOEF, "No current EF in READ RECORD");
198 		}
199 	}
200 
201 	if (!(ef instanceof LinearEF)) {
202 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_COMINCOMPATIBLE, "EF is not a linear file in READ RECORD");
203 	}
204 
205 	var ac = this.fileSelector.getMeta("accessController");
206 	if (ac && !ac.checkFileReadAccess(this, apdu, ef)) {
207 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Read access not allowed as determined by " + ac);
208 	}
209 
210 	var data = ef.readRecord(apdu, recno, qualifier, apdu.getNe());
211 
212 	apdu.setRData(data);
213 }
214 
215 
216 
217 /**
218  * Performs a VERIFY command
219  *
220  * @param {APDU} the apdu
221  */
222 CommandInterpreter.prototype.verify = function(apdu) {
223 	if (apdu.isChained()) {
224 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command");
225 	}
226 	
227 	var pinao = this.fileSelector.getObject(AuthenticationObject.TYPE_PIN, apdu.getP2());
228 	if (!pinao) {
229 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PIN with reference " + apdu.getP2() + " not found");
230 	}
231 
232 	if (apdu.hasCData()) {
233 		pinao.verify(apdu.getCData());
234 	} else {
235 		if (!this.fileSelector.isAuthenticated(ao)) {
236 			pinao.determineStatus();
237 		}
238 	}
239 
240 	apdu.setSW(APDU.SW_OK);
241 }
242 
243 
244 
245 /**
246  * Performs a CHANGE REFERENCE DATA command
247  *
248  * @param {APDU} the apdu
249  */
250 CommandInterpreter.prototype.changeReferenceData = function(apdu) {
251 	if (apdu.isChained()) {
252 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command");
253 	}
254 	if (!apdu.hasCData()) {
255 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data");
256 	}
257 
258 	var pinao = this.fileSelector.getObject(AuthenticationObject.TYPE_PIN, apdu.getP2());
259 	if (!pinao) {
260 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PIN with reference " + apdu.getP2() + " not found");
261 	}
262 
263 	pinao.changeReferenceData(apdu.getP1(), apdu.getCData());
264 
265 	apdu.setSW(APDU.SW_OK);
266 }
267 
268 
269 
270 /**
271  * Performs a RESET RETRY COUNTER command
272  *
273  * @param {APDU} the apdu
274  */
275 CommandInterpreter.prototype.resetRetryCounter = function(apdu) {
276 	if (apdu.isChained()) {
277 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command");
278 	}
279 	var p1 = apdu.getP1();
280 
281 	if (p1 == 0x02) {
282 		if (!apdu.hasCData()) {
283 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data");
284 		}
285 	} else if (p1 == 0x03) {
286 		if (apdu.hasCData()) {
287 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must not have C-data");
288 		}
289 	} else {
290 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2");
291 	}
292 
293 	var pinao = this.fileSelector.getObject(AuthenticationObject.TYPE_PIN, apdu.getP2());
294 	if (!pinao) {
295 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PIN with reference " + apdu.getP2() + " not found");
296 	}
297 
298 	pinao.resetRetryCounter(apdu.getCData());
299 
300 	apdu.setSW(APDU.SW_OK);
301 }
302 
303 
304 
305 /**
306  * Process a MANAGE SECURITY ENVIRONMENT APDU
307  *
308  * @param {APDU} apdu the command and response APDU
309  */
310 CommandInterpreter.prototype.manageSecurityEnvironment = function(apdu) {
311 	var p1 = apdu.getP1();
312 	var p2 = apdu.getP2();
313 	
314 	var se = this.fileSelector.getSecurityEnvironment();
315 
316 	if ((p1 & 0x0F) != 1) { 	// SET
317 		throw new GPError("CommandInterpreter", GPError.INVALID_TYPE, APDU.APDU.SW_FUNCNOTSUPPORTED, "Only MANAGE SE set variant supported");
318 	}
319 	
320 	var tlv = new ASN1(p2, apdu.getCData());
321 
322 	if (p1 & 0x80) {					// Verification, Encryption, External Authentication and Key Agreement
323 		var t = new ASN1(tlv.getBytes());		// Dirty trick to deserialize as TLV tree
324 		se.VEXK.add(t);
325 	}
326 	if (p1 & 0x40) {					// Calculation, Decryption, Internal Authentication and Key Agreement
327 		var t = new ASN1(tlv.getBytes());		// Dirty trick to deserialize as TLV tree
328 		se.CDIK.add(t);
329 	}
330 	if (p1 & 0x20) {					// Secure Messaging Response
331 		var t = new ASN1(tlv.getBytes());		// Dirty trick to deserialize as TLV tree
332 		se.SMRES.add(t);
333 	}
334 	if (p1 & 0x10) {					// Secure Messaging Command
335 		var t = new ASN1(tlv.getBytes());		// Dirty trick to deserialize as TLV tree
336 		se.SMCOM.add(t);
337 	}
338 	apdu.setSW(APDU.SW_OK);
339 }
340 
341 
342 
343 /**
344  * Process a secure messaging command APDU, if secure messaging is active.
345  *
346  * @param {APDU} apdu the command APDU
347  */
348 CommandInterpreter.prototype.handleSecMsgCommandAPDU = function(apdu) {
349 	if (apdu.isSecureMessaging()) {
350 		if (this.hasSecureChannel()) {
351 			try	{
352 				apdu.setSecureChannel(this.secureChannel);
353 				apdu.unwrap();
354 			}
355 			catch(e) {
356 				this.setSecureChannel();	// Reset secure channel
357 				throw e;
358 			}
359 		} else {
360 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SMNOTSUPPORTED, "No secure messaging channel");
361 		}
362 	} else {
363 		if (this.hasSecureChannel()) {
364 			this.setSecureChannel();	// Reset secure channel
365 		}
366 	}
367 }
368 
369 
370 
371 /**
372  * Process a secure messaging response APDU, if secure messaging is active
373  *
374  * @param {APDU} apdu the response APDU
375  */
376 CommandInterpreter.prototype.handleSecMsgResponseAPDU = function(apdu) {
377 	if (apdu.isSecureMessaging() && this.hasSecureChannel()) {
378 		apdu.wrap();
379 	}
380 }
381 
382 
383 
384 /**
385  * Dispatch to command handler based on instruction code
386  *
387  * @param {APDU} apdu the command and response APDU
388  * @param {Number} ins instruction code
389  */
390 CommandInterpreter.prototype.dispatch = function(apdu, ins) {
391 	if (!apdu.isISO()) {
392 		apdu.setSW(APDU.SW_INVCLA);
393 		return;
394 	}
395 
396 	if (apdu.isChained()) {
397 		throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported");
398 	}
399 
400 	switch(ins) {
401 		case APDU.INS_SELECT:
402 			this.fileSelector.processSelectAPDU(apdu);
403 			break;
404 		case APDU.INS_READ_BINARY:
405 			this.readBinary(apdu);
406 			break;
407 		case APDU.INS_UPDATE_BINARY:
408 			this.updateBinary(apdu);
409 			break;
410 		case APDU.INS_READ_RECORD:
411 			this.readRecord(apdu);
412 			break;
413 		case APDU.INS_VERIFY:
414 			this.verify(apdu);
415 			break;
416 		case APDU.INS_RESET_RETRY_COUNTER:
417 			this.resetRetryCounter(apdu);
418 			break;
419 		case APDU.INS_CHANGE_REFERENCE_DATA:
420 			this.changeReferenceData(apdu);
421 			break;
422 		case APDU.INS_MANAGE_SE:
423 			this.manageSecurityEnvironment(apdu);
424 			break;
425 		default:
426 			apdu.setSW(APDU.SW_INVINS);
427 	}
428 }
429 
430 
431 
432 /**
433  * Process a command APDU
434  *
435  * @param {APDU} apdu the command and response APDU
436  */
437 CommandInterpreter.prototype.processAPDU = function(apdu) {
438 	try	{
439 		this.handleSecMsgCommandAPDU(apdu);
440 		
441 		var cla = apdu.getCLA();
442 		var ins = apdu.getINS();
443 		var tlv = (ins & 1) == 1;
444 		ins &= 0xFE;
445 		
446 		var ac = this.fileSelector.getMeta("accessController");
447 		if (ac && !ac.checkCommandAccess(this, apdu)) {
448 			throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Command not allowed as determined by " + ac);
449 		}
450 
451 		this.dispatch(apdu, ins);
452 	}
453 	catch(e) {
454 		GPSystem.trace(e.fileName + "#" + e.lineNumber + ": " + e);
455 		var sw = APDU.SW_GENERALERROR;
456 		if ((e instanceof GPError) && (e.reason >= 0x6200)) {
457 			apdu.setSW(e.reason);
458 		} else {
459 			apdu.setSW(APDU.SW_GENERALERROR);
460 		}
461 	}
462 	this.handleSecMsgResponseAPDU(apdu);
463 }
464 
465