      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