/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 1999-2024 CardContact Software & System Consulting
 * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
 *  ---------
 *
 *  This file is part of OpenSCDP.
 *
 *  OpenSCDP is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  OpenSCDP is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenSCDP; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @fileoverview Service request model for unblock the user PIN
 */

var ServiceRequestModel		= require('pki-as-a-service/service/ServiceRequestModel').ServiceRequestModel;
var SmartCardHSM		= require('scsh/sc-hsm/SmartCardHSM').SmartCardHSM;
var HSMKeyStore = require('scsh/sc-hsm/HSMKeyStore').HSMKeyStore;



/**
 * Data model for requesting a certificate
 *
 *
 * @constructor
 */
function ResetRetryCounterServiceRequestModel(service, bo) {
	ServiceRequestModel.call(this, service, bo);
}

ResetRetryCounterServiceRequestModel.prototype = Object.create(ServiceRequestModel.prototype);
ResetRetryCounterServiceRequestModel.constructor = ResetRetryCounterServiceRequestModel;

exports.ResetRetryCounterServiceRequestModel = ResetRetryCounterServiceRequestModel;



ResetRetryCounterServiceRequestModel.VERIFICATION_METHOD_POST_IDENT = "Post-Ident";
ResetRetryCounterServiceRequestModel.VERIFICATION_METHOD_BASIS_KEY_USER = "Basis-KeyUser";
ResetRetryCounterServiceRequestModel.verificationMethods = [
	ResetRetryCounterServiceRequestModel.VERIFICATION_METHOD_POST_IDENT,
	ResetRetryCounterServiceRequestModel.VERIFICATION_METHOD_BASIS_KEY_USER
];



ResetRetryCounterServiceRequestModel.prototype.getForm = function(user) {
	if (this.form == undefined) {
		var tokenDAO = this.service.daof.getTokenDAO();
		var token = tokenDAO.getTokenById(this.model.tokenId);

		this.form = [{
			id: "token",
			legend: "msg.token",
			fields: [
				{ id: "tokenpath", label: "msg.tokenpath", type: "text", editable: false, required: false, value: token.path },
		]}];

		if ((typeof(this.model.pin) != "undefined") && this.isRegistrationOfficer(user)) {
			var editable = this.bo.lifecycle <= ServiceRequestModel.LIFECYCLE_APPROVE;
			this.form[0].fields.push(
				{ id: "pin", label: "msg.rrc.pin", type: "text", editable: editable, required: true, value: this.model.pin }
			);
		}
	}

	return this.form;
}



ResetRetryCounterServiceRequestModel.prototype.isRegistrationOfficer = function(user) {
// 	return this.getRecipient().isRegistrationOfficer(user);
	var raRoleId = this.getRARoleId();
	return (typeof(user.roles) != "undefined") && (user.roles.indexOf(raRoleId) >= 0);
}



ResetRetryCounterServiceRequestModel.prototype.isAssignedToRegistrationOfficer = function(user) {
	if (!this.bo.assignedToRole) {
		return false;
	}

	var raRoleId = this.getRARoleId();

	if (this.bo.assignedToRole != raRoleId) {
		return false;
	}

	return (typeof(user.roles) != "undefined") && (user.roles.indexOf(raRoleId) >= 0);
}



ResetRetryCounterServiceRequestModel.prototype.isManagerOfRequester = function(user) {
	return this.getOriginator().isManager(user);
}



ResetRetryCounterServiceRequestModel.prototype.getActionList = function(user) {
	if (this.actionList != undefined) {
		return this.actionList;
	}

	this.actionList = [];

	if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_CLOSED) {
		return this.actionList;
	}

	if (this.isRegistrationOfficer(user)) {
		if (this.bo.lifecycle <= ServiceRequestModel.LIFECYCLE_APPROVE) {
			if (typeof(this.model.pin) != "undefined") {
				this.actionList.push("action.save");
			}
			this.actionList.push("action.approve");
			this.actionList.push("action.reject");
		}

		if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_REJECTED ||
			this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_ERROR) {
			this.actionList.push("action.close");
		}
	}

	return this.actionList;
}



ResetRetryCounterServiceRequestModel.prototype.getRARoleId = function() {
// 	return this.model.raRoleId;
	return this.getRecipient().getRARoleId()
}



ResetRetryCounterServiceRequestModel.prototype.handleAction = function(user, action) {
	GPSystem.log(GPSystem.DEBUG, module.id, "handleAction(" + user.id + "," + action + ")");

	switch(action) {
		case "action.save":
			this.handleSave();
			break;
		case "action.approve":
			var tokenDAO = this.service.daof.getTokenDAO();
			tokenDAO.addTokenAction(this.model.tokenId, this.getId());

			this.assignToRole(0);

			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_PRODUCE);
			this.setStatusInfo("Request approved");
			break;
		case "action.reject":
			this.assignToRole(0);
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_REJECTED);
			this.setStatusInfo("Request rejected");
			break;
		case "action.close":
			this.assignToRole(0);
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_CLOSED);
			this.setStatusInfo("Request closed");
			break;
	}
}



ResetRetryCounterServiceRequestModel.prototype.perform = function(user, action) {
	GPSystem.log(GPSystem.DEBUG, module.id, "perform(" + user.id + "," + action + ")");

	var actionList = this.getActionList(user);

	if (actionList.indexOf(action) == -1) {
		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Action " + action + " unknown");
	}

	this.handleAction(user, action);

	this.commit(user.id);
	return true;
}



ResetRetryCounterServiceRequestModel.prototype.handleSave = function() {
	if (this.getLifeCycle() == ServiceRequestModel.LIFECYCLE_NEW) {
		this.setLifeCycle(ServiceRequestModel.LIFECYCLE_EDIT);
	}
	this.setStatusInfo("Request data entered");
}



ResetRetryCounterServiceRequestModel.prototype.getHSMList = function() {
	var trustCenter = this.getRecipient();
	var tokenList = trustCenter.getToken();

	var hsmList = [];

	for (var i = 0; i < tokenList.length; i++) {
		var token = tokenList[i];
		hsm = this.service.hsmService.getHSMState(token.path);

		if (!hsm || hsm.isOffline()) {
			GPSystem.log(GPSystem.DEBUG, module.id, "Skip HSM " + hsm + " for path " + token.path);
			continue;
		}

		if (!hsm.isUserAuthenticated()) {
			GPSystem.log(GPSystem.DEBUG, module.id, "Skip HSM " + hsm + " because it is not authenticated");
			continue;
		}

		hsmList.push(hsm);
	}

	return hsmList
}



ResetRetryCounterServiceRequestModel.prototype.getKCV = function(key) {
	var kcv = key.getPKCS15Id();
	if (kcv.length != 8) {
		kcv = key.sign(Crypto.AES_CMAC, new ByteString("KeyCheckValue", ASCII)).left(8);
	}
	return kcv;
}



ResetRetryCounterServiceRequestModel.prototype.getTokenManagementKey = function(tmkkcv) {

	var hsmList = this.getHSMList();

	for (var i = 0; i < hsmList.length; i++) {
		var hsm = hsmList[i];
		var keyStore = new HSMKeyStore(hsm.sc);

		var labels = keyStore.enumerateKeys();
		for (var j = 0; j < labels.length; j++) {
			var label = labels[j];
			var key = keyStore.getKey(label);
			if (key.getType() == "AES") {
				if (tmkkcv.equals(this.getKCV(key))) {
					return key;
				}
			}
		}
	}
}



/**
 * Derive the SO-PIN using the token management key
 */
ResetRetryCounterServiceRequestModel.prototype.deriveSOPIN = function(key, tokenPath, salt) {
	var inp = new ByteString(tokenPath, ASCII);
	if (salt) {
		inp = inp.concat(salt);
	}

	var cmac = key.sign(Crypto.AES_CMAC, inp);
	return cmac.left(8);
}



ResetRetryCounterServiceRequestModel.prototype.resetRetryCounter = function(sc, tokenPath) {
	GPSystem.log(GPSystem.DEBUG, module.id, "resetRetryCounter");

	var tmkkcv = sc.getTokenManagementKeyKCV();
	if (!tmkkcv) {
		this.assignToRole(this.getRARoleId());
		this.setLifeCycle(ServiceRequestModel.LIFECYCLE_ERROR);
		var msg = "No Token Management Key defined";
		this.setStatusInfo(msg);
		sc.card.nativeCardTerminal.sendNotification(-1, msg);
		return false;
	}
	var salt = sc.getTokenManagementKeySalt();

	var key = this.getTokenManagementKey(tmkkcv);
	if (!key) {
		this.assignToRole(this.getRARoleId());
		this.setLifeCycle(ServiceRequestModel.LIFECYCLE_APPROVE);
		var msg = "Token Management Key not found or not authorized";
		this.setStatusInfo(msg);
		sc.card.nativeCardTerminal.sendNotification(-1, msg);
		return false;
	}

	var soPIN = this.deriveSOPIN(key, tokenPath, salt);
	GPSystem.log(GPSystem.DEBUG, module.id, "soPIN: " + soPIN.toString(HEX));
	if (typeof(this.model.pin) != "undefined") {
		var pin = new ByteString(this.model.pin, ASCII);
	} else {
		var pin = null;
	}
	sc.unblockUserPIN(soPIN, pin);

	this.setLifeCycle(ServiceRequestModel.LIFECYCLE_COMPLETE);

	var msg = "Retry Counter reset";
	this.setStatusInfo("Retry Counter reset");
	sc.card.nativeCardTerminal.sendNotification(0, msg);

	return false;
}



ResetRetryCounterServiceRequestModel.prototype.handleSmartCardHSM = function(sc, chain, user) {
	GPSystem.log(GPSystem.DEBUG, module.id, "handleCardAction for status: " + this.bo.lifecycle);
	var keepTokenAction = true;

	switch(this.bo.lifecycle) {
		case ServiceRequestModel.LIFECYCLE_PRODUCE:
			keepTokenAction = this.resetRetryCounter(sc, chain.path);
			break;
		default:
			GPSystem.log(GPSystem.ERROR, module.id, "Unexpected handleCard in state " + this.bo.lifecycle);
			return;
	}

	if (typeof(user) == "undefined") {
		var id = this.getOriginatorId();
	} else {
		var id = user.id;
	}
	this.commit(id);

	return keepTokenAction;
}



ResetRetryCounterServiceRequestModel.prototype.handleCard = function(card, session, reqinfo) {
	var user = session.user;

	var sc = new SmartCardHSM(card);
	var devAutCert = sc.readBinary(SmartCardHSM.C_DevAut);
	var crypto = new Crypto();
	var chain = SmartCardHSM.validateCertificateChain(crypto, devAutCert);
	if (chain == null) {
		this.setStatusInfo("Could not authenticate SmartCard-HSM");
		return;
	}

	sc.openSecureChannel(crypto, chain.publicKey);

	this.handleSmartCardHSM(sc, chain, user);
}
