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