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