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