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