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 AuthenticationObject - Password, PIN or key container for external authentication 25 */ 26 27 var FileSystemIdObject = require('scsh/cardsim/FileSystemIdObject').FileSystemIdObject; 28 var APDU = require('scsh/cardsim/APDU').APDU; 29 30 31 32 /** 33 * Create an authentication object 34 * 35 * @class Class implementing authentication objects like PINs, PACE passwords or keys 36 * 37 * @param {String} name the human readable name of the object 38 * @param {String} type one of AuthenticationObject.TYPE_PACE or AuthenticationObject.TYPE_PIN 39 * @param {Number} id the password or key id 40 * @param {ByteString} value the reference value 41 */ 42 function AuthenticationObject(name, type, id, value) { 43 FileSystemIdObject.call(this, name, id); 44 this.type = type; 45 this.value = value; 46 this.retrycounter = 3; 47 this.initialretrycounter = 3; 48 this.usecounter = -1; 49 this.resetcounter = -1; 50 this.minLength = 4; 51 this.isActive = true; // State after using ACTIVATE / DEACTIVATE 52 this.isEnabled = true; // State after using ENABLE / DISABLE VERIFICATION REQUIREMENT 53 this.isTransport = false; // State before first change PIN 54 this.isTerminated = false; // State after TERMINATE 55 this.allowActivate = false; 56 this.allowDeactivate = false; 57 this.allowEnable = false; 58 this.allowDisable = false; 59 this.allowResetRetryCounter = false; 60 this.allowResetValue = false; 61 this.allowTerminate = false; 62 this.unsuspendAuthenticationObject = null; 63 this.unblockAuthenticationObject = null; 64 } 65 66 AuthenticationObject.prototype = new FileSystemIdObject(); 67 AuthenticationObject.prototype.constructor = AuthenticationObject; 68 69 exports.AuthenticationObject = AuthenticationObject; 70 71 72 AuthenticationObject.TYPE_PACE = "pace"; 73 AuthenticationObject.TYPE_PIN = "pin"; 74 75 76 77 /** 78 * Override from base class 79 */ 80 AuthenticationObject.prototype.getType = function() { 81 return this.type; 82 } 83 84 85 86 AuthenticationObject.prototype.isBlocked = function() { 87 return ((this.initialretrycounter != 0) && (this.retrycounter == 0)); 88 } 89 90 91 92 AuthenticationObject.prototype.isSuspended = function() { 93 return ((this.initialretrycounter != 0) && (this.retrycounter == 1)); 94 } 95 96 97 98 /** 99 * Activate authentication object 100 */ 101 AuthenticationObject.prototype.activate = function() { 102 if (!this.allowActivate) { 103 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Activate not allowed for authentication object"); 104 } 105 this.isActive = true; 106 } 107 108 109 110 /** 111 * Deactivate authentication object 112 */ 113 AuthenticationObject.prototype.deactivate = function() { 114 if (!this.allowDeactivate) { 115 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Deactivate not allowed for authentication object"); 116 } 117 this.isActive = false; 118 } 119 120 121 122 /** 123 * Reset retry counter and optionally set new reference value 124 * 125 * @param {ByteString} newValue new reference value 126 */ 127 AuthenticationObject.prototype.resetRetryCounter = function(newValue) { 128 if (!this.allowResetRetryCounter) { 129 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Reset retry counter not allowed for authentication object"); 130 } 131 if (newValue && !this.allowResetValue) { 132 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Reset retry counter not allowed with new value for authentication object"); 133 } 134 if (this.resetcounter != -1) { 135 if (this.resetcounter == 0) { 136 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Reset retry counter is 0"); 137 } 138 this.resetcounter--; 139 } 140 if (newValue && (newValue.length < this.minLength)) { 141 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "New reference data too short"); 142 } 143 this.retrycounter = this.initialretrycounter; 144 this.isActive = true; 145 146 if (this.initialretrycounter) { 147 this.retrycounter = this.initialretrycounter; 148 } 149 if (newValue) { 150 this.isTransport = false; 151 this.value = newValue; 152 } 153 } 154 155 156 157 /** 158 * Change reference data, optionally verifying the old value before 159 * 160 * @param {Number} qualifier command qualifier, 00 = oldPIN||newPIN, 01 = newPIN 161 * @param {ByteString} value new reference value 162 */ 163 AuthenticationObject.prototype.changeReferenceData = function(qualifier, value) { 164 if (!this.allowChangeReferenceData) { 165 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Change reference data not allowed for authentication object"); 166 } 167 if (qualifier == 0x01) { 168 if (!this.isTerminated) { 169 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Change reference data with P1=01 not allowed non terminated authentication object"); 170 } 171 if (this.associatedKey && !this.associatedKey.isTerminated) { 172 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_REFDATANOTUSABLE, "Associated key is not terminated"); 173 } 174 } 175 if ((qualifier == 0x00) && (value.length <= this.value.length)) { 176 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data does not contain a new PIN value for P1=00"); 177 } 178 if (qualifier == 0x00) { 179 this.verify(value.left(this.value.length)); 180 value = value.bytes(this.value.length); 181 } 182 183 if (value.length < this.minLength) { 184 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "New reference data too short"); 185 } 186 187 this.value = value; 188 this.isTerminated = false; 189 } 190 191 192 193 /** 194 * Verify PIN value 195 * 196 * @param {ByteString} value reference value 197 */ 198 AuthenticationObject.prototype.verify = function(value) { 199 if (this.isBlocked()) { 200 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_AUTHMETHLOCKED, "Authentication method blocked"); 201 } 202 if (this.isTerminated) { 203 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_REFDATANOTUSABLE, "Authentication method terminated"); 204 } 205 this.decreaseRetryCounter(); 206 if (!this.value.equals(value)) { 207 var sw = APDU.SW_WARNINGCOUNT | this.retrycounter; 208 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, sw, "Authentication failed"); 209 } 210 this.restoreRetryCounter(); 211 } 212 213 214 215 /** 216 * Deactivate authentication object 217 */ 218 AuthenticationObject.prototype.decreaseRetryCounter = function() { 219 if (this.initialretrycounter) { 220 this.retrycounter--; 221 } 222 } 223 224 225 226 /** 227 * Deactivate authentication object 228 */ 229 AuthenticationObject.prototype.restoreRetryCounter = function() { 230 if (this.initialretrycounter) { 231 this.retrycounter = this.initialretrycounter; 232 } 233 } 234 235 236 237 /** 238 * Terminate authentication object 239 */ 240 AuthenticationObject.prototype.terminate = function() { 241 if (!this.allowTerminate) { 242 throw new GPError("AuthenticationObject", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Terminate not allowed for authentication object"); 243 } 244 this.isTerminated = true; 245 } 246 247 248 249 /** 250 * Convert object to a human readable string 251 */ 252 AuthenticationObject.prototype.toString = function() { 253 var state = ""; 254 if (this.isBlocked()) { 255 state += "blocked "; 256 } else if (this.isTerminated) { 257 state += "terminated "; 258 } else { 259 if (this.isActive) { 260 state += "active "; 261 } 262 if (this.isActive) { 263 state += "enabled "; 264 } else { 265 state += "disabled "; 266 } 267 if (this.isTransport) { 268 state += "transport "; 269 } 270 } 271 var str = this.type + ":" + this.name + "(" + this.id + ") is " + state; 272 if (this.initialretrycounter) { 273 str += " RC=" + this.retrycounter; 274 } 275 return str; 276 } 277 278 279