/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 1999-2020 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 issuing a token
 */

var ServiceRequestModel = require('pki-as-a-service/service/ServiceRequestModel').ServiceRequestModel;
var CVCertificateStore = require('scsh/eac/CVCertificateStore').CVCertificateStore;
var SmartCardHSM = require('scsh/sc-hsm/SmartCardHSM').SmartCardHSM;
var SmartCardHSMInitializer = require('scsh/sc-hsm/SmartCardHSM').SmartCardHSMInitializer;
var HSMKeyStore = require('scsh/sc-hsm/HSMKeyStore').HSMKeyStore;
var CryptoProvider = require('scsh/sc-hsm/CryptoProvider').CryptoProvider;
var SubjectDAO = require('scsh/pki-db/db/SubjectDAO').SubjectDAO;



/**
 * Data model for issuing a token
 *
 * @constructor
 */
function IssueTokenRequestModel(service, bo) {
	ServiceRequestModel.call(this, service, bo);
	this.validationResult = [];
}

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

exports.IssueTokenRequestModel = IssueTokenRequestModel;



IssueTokenRequestModel.WITHOUT_RRC = 0;
IssueTokenRequestModel.WITH_RRC = 1;
IssueTokenRequestModel.WITH_RRC_RESET_ONLY = 2;



IssueTokenRequestModel.prototype.getForm = function(user) {
	if (this.form == undefined) {
		this.form = [];

		if (this.bo.lifecycle < ServiceRequestModel.LIFECYCLE_COMPLETE) {
			var editable = this.canEdit(user) && (this.bo.lifecycle <= ServiceRequestModel.LIFECYCLE_EDIT);

			var subject = this.getSubject();
			var fields = [];
			if (subject.name) {
				fields.push({ id: "name", label: "msg.name", type: "text", editable: false, required: false, value: subject.name });
			}

			if (subject.email) {
				fields.push({ id: "email", label: "msg.email", type: "text", editable: false, required: true, value: subject.email });
			}

			this.form.push( {
				id: "subject",
				legend: "msg.subject",
				fields: fields
				}
			);

			var issuerList = [];

			issuerList.push( { id: 0, value: "No certificate issued", selected: !this.model.issuer });

			var trustCenter = this.getRecipient();
			var issuerModel = {
				optgroup: true,
				label: trustCenter.getName(),
				value: []
			};
			var caList = trustCenter.getCAList(user);
			for (var i = 0; i < caList.length; i++) {
				var ca = caList[i];

				issuerModel.value.push( { id: ca.id, value: ca.getReadablePath(), selected: ca.id == this.model.issuer } ) ;
			}
			issuerList.push(issuerModel);

			this.form.push( {
				id: "issuer",
				legend: "msg.it.issuer",
				fields: [
					{ id: "issuer", label: "msg.it.ca", type: "select", editable: editable, value: issuerList },
				]
			});
		}

		if (this.bo.lifecycle >= ServiceRequestModel.LIFECYCLE_EDIT) {
			this.form.push(this.getTokenInfoSection());
		}

		if (this.model.pin) {
			this.form.push(this.getInitializationFieldset());
		}
	}

	return this.form;
}



IssueTokenRequestModel.prototype.canEdit = function(user) {
	return this.getRecipient().isRegistrationOfficer(user);
}


IssueTokenRequestModel.prototype.getTokenInfoSection = function() {
	var tokenStatusDesc = "";
	switch(this.model.tokenStatus) {
		case "unregistered_token":
			tokenStatusDesc = "The token is not used (unregistered_token)";
			break;
		case "registered_token":
			tokenStatusDesc = "The token is assigned to a subject (registered_token)";
			break;
		case "pending_registration":
			tokenStatusDesc = "The token is assigned to a subject and waiting for registration to complete (pending_registration)";
			break;
		case "session_token":
			tokenStatusDesc = "Your own token used to login (session_token)";
			break;
	}

	var section = {
		id: "token",
		legend: "msg.token",
		fields: [
			{ id: "tokenPath", label: "msg.tokenpath", type: "text", editable: false, required: false, value: (this.model.selectedTokenPath ? this.model.selectedTokenPath : "")},
			{ id: "tokenStatus", label: "msg.status", type: "text", editable: false, required: false, value: tokenStatusDesc }
		]};

	if (this.model.assignedTo) {
		var seealso = { id: "seealso", type: "link", value: "msg.it.assignedTo", href: "../subject/" + this.model.assignedTo };
		section.fields.push(seealso);
	}
	return section;
}



IssueTokenRequestModel.prototype.getInitializationFieldset = function() {
	if (this.model.pin) {
		var rrcConfig = [];
		// Only push the selected option to the list
		// because using Firefox a forced reload of the page would be
		// necessary to see changes to the list
		if (this.model.rrcMode == IssueTokenRequestModel.WITHOUT_RRC) {
			rrcConfig.push( { id: IssueTokenRequestModel.WITHOUT_RRC, msg: "msg.it.withoutRRC", selected: this.model.rrcMode == IssueTokenRequestModel.WITHOUT_RRC } ) ;
		} else if (this.model.rrcMode == IssueTokenRequestModel.WITH_RRC) {
			rrcConfig.push( { id: IssueTokenRequestModel.WITH_RRC, msg: "msg.it.withRRC", selected: this.model.rrcMode == IssueTokenRequestModel.WITH_RRC } ) ;
		} else {
			rrcConfig.push( { id: IssueTokenRequestModel.WITH_RRC_RESET_ONLY, msg: "msg.it.resetOnly", selected: this.model.rrcMode == IssueTokenRequestModel.WITH_RRC_RESET_ONLY } ) ;
		}

		return  {
			id: "initialization",
			legend: "msg.it.init",
			fields: [
				{ id: "derivationKeyLabel", label: "msg.it.derivationKeyLabel", type: "text", editable: false, required: false, value: this.model.derivationKeyLabel},
				{ id: "rrcMode", label: "msg.it.rrcMode", type: "select", editable: false, required: false, value: rrcConfig},
				{ id: "transportMode", label: "msg.it.transportMode", type: "text", editable: false, required: false, value: this.model.transportMode},
				{ id: "pin", label: "msg.it.pin", type: "text", editable: false, required: false, value: this.model.pin},
				{ id: "noOfKeyDomains", label: "msg.it.noOfKeyDomains", type: "number", editable: false, required: false, value: this.model.noOfKeyDomains}
			]
		}
	}
}



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

	this.actionList = [];

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

	if (this.canEdit(user)) {
		if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_EDIT) {
			this.actionList.push("action.save");
			if (this.model.registerTokenSRID == undefined) {
				this.actionList.push("action.selectToken");
				if (this.model.tokenStatus) {
					if (this.model.tokenStatus == "unregistered_token") {
						this.actionList.push("action.registerToken");
					} else {
						if (this.model.tokenStatus == "registered_token") {
							this.actionList.push("action.deregisterToken");
						}
					}
				}
			}
		}

		if (this.bo.lifecycle >= ServiceRequestModel.LIFECYCLE_VALIDATE) {
			this.actionList.push("action.initialize");
			if (this.model.email == undefined) {
				this.actionList.push("action.deregisterToken");
			}
		}

		if (this.model.issuer && (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_VALIDATE)) {
			this.actionList.push("action.reqCert");
		}
	}

	return this.actionList;
}



IssueTokenRequestModel.prototype.validateField = function(fld, val) {
	switch(fld.id) {
		default:
			return this.validateFieldDefault(fld, val);
	}
	return true;
}



IssueTokenRequestModel.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");
	}

	switch(action) {
		case "action.save":
			this.setStatusInfo("Update");
			break;
		case "action.registerToken":
			if (!this.model.selectedTokenPath) {
				return;
			}

			var subject = this.getSubject();
			if (subject.type == SubjectDAO.TYPE_PERSON) {
				this.registerToken(user, subject.email);
				return true;
			} else {
				var tokenDAO = this.service.daof.getTokenDAO();
				var token = tokenDAO.getToken(this.model.selectedTokenPath);
				tokenDAO.updateSubjectId(token, this.model.subjectId);
				tokenDAO.updateServiceRequestId(token, this.getId());
				this.model.tokenStatus = "registered_token";
			}
			this.setStatusInfo("Token registered");
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_VALIDATE);
			break;
		case "action.deregisterToken":
			if (!this.model.selectedTokenPath) {
				return;
			}
			var tokenDAO = this.service.daof.getTokenDAO();
			tokenDAO.deregisterToken(this.model.selectedTokenPath);
			this.service.removeRegistrationTokenAction(this.model.selectedTokenPath);
			this.model.tokenStatus = "unregistered_token";
			delete this.model.assignedTo;
			this.setStatusInfo("Token deregistered");
			if (this.bo.lifecycle > ServiceRequestModel.LIFECYCLE_EDIT) {
				this.setLifeCycle(ServiceRequestModel.LIFECYCLE_TERMINATED);
			}
			break;
		case "action.reqCert":
			this.requestCertificate(user);
			break;
	}

	this.commit(user.id, this.getRecipient().getRARoleId());

	return true;
}



IssueTokenRequestModel.prototype.getSubject = function() {
	var subjectDAO = this.service.daof.getSubjectDAO();
	return subjectDAO.getSubject(this.model.subjectId);
}



IssueTokenRequestModel.prototype.registerToken = function(user, email) {
	if (!this.model.selectedTokenPath) {
		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Cannot start RegisterMyTokenServiceRequest without a token path");
	}

	// Create related registration request
	var controller = this.service.getControllerForProcess("RegisterMyTokenServiceRequest");

	var tokenDAO = this.service.daof.getTokenDAO();
	var token = tokenDAO.getToken(this.model.selectedTokenPath);

	var parameter = {
		token: token,
		lang: user.lang,
		originatorId: this.model.subjectId,
		raRoleID: this.getRecipient().getRARoleId(),
		subjectId: this.model.subjectId,
		enrollmentServiceRequestId: this.bo.id
	};

	var bo = controller.createRequest(this.service, parameter);
	this.model.registerTokenSRID = bo.id;
	this.model.tokenStatus = "pending_registration";
	this.setLifeCycle(ServiceRequestModel.LIFECYCLE_VALIDATE);
	// Commit now to preserve state if sending e-mail fails.
	this.commit(user.id);

	// Make a single commit, so I'm in the history and are able to access the request
	var sr = this.service.getServiceRequestById(bo.id);
	sr.setStatusInfo("Request created");
	sr.setLifeCycle(ServiceRequestModel.LIFECYCLE_EDIT);
	sr.commit(user.id, parameter.raRoleID);

	sr.setEmail(email, user);
}



IssueTokenRequestModel.prototype.requestCertificate = function(user) {
	GPSystem.log(GPSystem.DEBUG, module.id, "requestCertificate(" + user.id + ")");

	var controller = this.service.getControllerForProcess("CertificateServiceRequest");

	var tokenDAO = this.service.daof.getTokenDAO();
	var token = tokenDAO.getToken(this.model.selectedTokenPath);

	var subject = this.getSubject();
	var cn = subject.name ? subject.name : subject.email;

	var template = {
		originatorId: this.model.subjectId,
		recipientId: this.getRecipientId(),
		assignedToRole: this.getRecipient().getRARoleId(),
		commonName: cn,
		parent: this.getId(),
		lang: user.lang,
		issuer: this.model.issuer

	}
	var bo = controller.createInitializedRequest(this.service, template);
	this.model.reqCertSRID = bo.id;

	this.setLifeCycle(ServiceRequestModel.LIFECYCLE_APPROVE);
}



IssueTokenRequestModel.prototype.checkSelectedToken = function(user, tokenPath, sc) {
	var tokenDAO = this.service.daof.getTokenDAO();

	this.model.selectedTokenPath = tokenPath;

	// Retrieve the label of the token management key
	// if the card has already been initialized
	// and the management token is online and authenticated
	var tmkkcv = sc.getTokenManagementKeyKCV();
	if (tmkkcv) {
		this.model.tmkkcv = tmkkcv;
		var tmk = this.getTokenManagementKey(tmkkcv);
		if (tmk) {
			this.model.derivationKeyLabel = tmk.getLabel();
		}
	}

	var salt = sc.getTokenManagementKeySalt();
	if (salt) {
		this.model.salt = salt;
	}

	this.setStatusInfo("Token identified");

	if (user.tokenPath == tokenPath) { // Verify that the selected token is not the user token
		this.model.tokenStatus = "session_token";
	} else {
		var token = tokenDAO.getToken(tokenPath);
		if (!token) {
			token = tokenDAO.newToken(this.model.selectedTokenPath);
		}

		if (token.subjectId) { // Check if token was assigned in the past
			this.model.tokenStatus = "registered_token";
			this.model.assignedTo = token.subjectId;
		} else {
			delete this.model.assignedTo;
			if (this.service.getRegistrationBOFromTokenActions(token)) {
				this.model.tokenStatus = "pending_registration";
			} else {
				this.model.tokenStatus = "unregistered_token";
			}
		}
	}

	this.commit(user.id, this.getRecipient().getRARoleId());
}



IssueTokenRequestModel.prototype.getTokenPath = function() {
	return this.model.selectedTokenPath;
}



IssueTokenRequestModel.prototype.getDerivationKeyLabel = function() {
	return this.model.derivationKeyLabel;
}



IssueTokenRequestModel.prototype.hasTokenManagementKeyKCV = function() {
	return typeof(this.model.tmkkcv) != "undefined";
}



IssueTokenRequestModel.prototype.isResetRetryCounterAllowed = function() {
	return this.model.rrcMode > IssueTokenRequestModel.WITHOUT_RRC;
}



IssueTokenRequestModel.prototype.isResetRetryCounterWithNewPINAllowed = function() {
	return this.model.rrcMode == IssueTokenRequestModel.WITH_RRC;
}



IssueTokenRequestModel.prototype.getFirstDerivationKeyLabel = function() {
	var hsmList = this.getHSMList();
	if (hsmList.length > 0) {
		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];
// 				GPSystem.log(GPSystem.DEBUG, module.id, "label: " + label);
				var key = keyStore.getKey(label);
				if (key.getType() == "AES") {
					return label;
				}
			}
		}
	}

	return null;
}



IssueTokenRequestModel.prototype.getInitializeData = function() {
	var tc = this.getRecipient();

	var defaultConfig = tc.bo.getContent().initializationConfig;
	if (typeof(defaultConfig) == "undefined") {
		var defaultConfig = {
			derivationKeyLabel: this.getFirstDerivationKeyLabel(),
			rrcMode: IssueTokenRequestModel.WITHOUT_RRC,
			pinLength: 6,
			transportMode: true,
			noOfKeyDomains: 4,
			changeSOPIN: false
		}
	}

	if (typeof(defaultConfig.pinLength) == "undefined") {
		// New parameter may not exist in TrustCenter configuration
		// before 2024-08-26
		defaultConfig.pinLength = 6;
	}

	defaultConfig.pin = this.service.generateActivationCode(defaultConfig.pinLength);

	if (typeof(this.model.derivationKeyLabel) != "undefined") {
		defaultConfig.derivationKeyLabel = this.model.derivationKeyLabel;
	}

	if (typeof(this.model.rrcMode) != "undefined") {
		defaultConfig.rrcMode = this.model.rrcMode;
	}

	if (typeof(this.model.transportMode) != "undefined") {
		defaultConfig.transportMode = this.model.transportMode;
	}

	if (typeof(this.model.noOfKeyDomains) != "undefined") {
		defaultConfig.noOfKeyDomains = this.model.noOfKeyDomains;
	}

	if (typeof(this.model.changeSOPIN) != "undefined") {
		defaultConfig.changeSOPIN = this.model.changeSOPIN;
	}

	return defaultConfig;
}



IssueTokenRequestModel.prototype.setInitializeData = function(user, initData) {
	GPSystem.log(GPSystem.DEBUG, module.id, "setInitializeData");
	this.model.derivationKeyLabel = initData.derivationKeyLabel;
	this.model.rrcMode = initData.rrcMode;
	this.model.transportMode = initData.transportMode;
	this.model.pin = initData.pin;
	this.model.noOfKeyDomains = initData.noOfKeyDomains;
	this.model.changeSOPIN = initData.changeSOPIN;

	// Update default configuration
	var config = {
		derivationKeyLabel: initData.derivationKeyLabel,
		rrcMode: initData.rrcMode,
		pinLength: initData.pinLength,
		transportMode: initData.transportMode,
		noOfKeyDomains: initData.noOfKeyDomains,
		changeSOPIN: initData.changeSOPIN
	}

	var trustCenter = this.getRecipient();
	var c = trustCenter.bo.getContent();
	c.initializationConfig = config;
	trustCenter.bo.setContent(c);
	var subjectDAO = this.service.daof.getSubjectDAO();
	subjectDAO.updateContent(trustCenter.bo);

	this.commit(user.id);
}



IssueTokenRequestModel.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
}



IssueTokenRequestModel.prototype.getDerivationKey = function() {
	GPSystem.log(GPSystem.DEBUG, module.id, "getDerivationKey");

	if (!this.model.derivationKeyLabel) {
		GPSystem.log(GPSystem.DEBUG, module.id, "No derivation key defined");
		return;
	}

	var hsmList = this.getHSMList();

	for (var i = 0; i < hsmList.length; i++) {
		var hsm = hsmList[i];
		var keyStore = new HSMKeyStore(hsm.sc);
		if (keyStore.hasKey(this.model.derivationKeyLabel)) {
			var key = keyStore.getKey(this.model.derivationKeyLabel)
			return key;
		}
	}
}



IssueTokenRequestModel.prototype.getTokenManagementKey = function(tmkkcv) {
	if (!tmkkcv) {
		GPSystem.log(GPSystem.DEBUG, module.id, "No Token Management Key defined");
		return;
	}

	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
 */
IssueTokenRequestModel.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);
}



IssueTokenRequestModel.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;
}



IssueTokenRequestModel.prototype.initialize = function(card, chain, user) {
	GPSystem.log(GPSystem.DEBUG, module.id, "initialize token");

	var key = this.getDerivationKey();
	var kcv = this.getKCV(key);
	if (typeof(this.model.tmkkcv) != "undefined") {
		if (!kcv.equals(this.model.tmkkcv)) {
			GPSystem.log(GPSystem.DEBUG, module.id, "Given    KCV: " + kcv.toString(HEX));
			GPSystem.log(GPSystem.DEBUG, module.id, "Expected KCV: " + kcv.toString(HEX));
			throw new GPError(module.id, GPError.INVALID_DATA, 0, "The card expected a Token Management Key KCV of " + this.model.tmkkcv.toString(HEX));
		}
	}

	var sc = new SmartCardHSM(card);
	// Get optional salt
	this.model.salt = sc.getTokenManagementKeySalt();

	var soPIN = this.deriveSOPIN(key, this.model.selectedTokenPath, this.model.salt);
// 	GPSystem.log(GPSystem.DEBUG, module.id, "soPIN: " + soPIN.toString(HEX));

	if (this.model.changeSOPIN) {
		GPSystem.log(GPSystem.DEBUG, module.id, "Generate a random salt to derive a new SO PIN");
		var crypto = new Crypto();
		this.model.salt = crypto.generateRandom(8);
		var newSOPIN = this.deriveSOPIN(key, this.model.selectedTokenPath, this.model.salt);
		GPSystem.log(GPSystem.DEBUG, module.id, "newSOPIN: " + newSOPIN.toString(HEX));
	}

	var sci = new SmartCardHSMInitializer(card);

	sci.setInitializationCode(soPIN);

	sci.setTokenManagementKey(kcv, this.model.salt);

	sci.setResetRetryCounterMode(this.isResetRetryCounterAllowed());
	if (this.model.rrcMode == IssueTokenRequestModel.WITH_RRC_RESET_ONLY) {
		sci.setResetRetryCounterResetOnlyMode(true);
	}

	sci.setTransportPINMode(this.model.transportMode);

	sci.setUserPIN(new ByteString(this.model.pin, ASCII));

	sci.setKeyDomains(this.model.noOfKeyDomains);

	var url = ApplicationServer.instance.serverURL + "/rt/paas"
	sci.setProvisioningURL(url);

	sci.initialize();

	if (this.model.changeSOPIN) {
		sc.changeInitializationCode(soPIN, newSOPIN);
	}

	this.persistDeviceCertificateChain(chain);

	if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_SUBMIT) {
		this.setLifeCycle(ServiceRequestModel.LIFECYCLE_VALIDATE);
	}

	this.setStatusInfo("Token initialized");
	this.commit(user.id);

	return true;
}



IssueTokenRequestModel.prototype.persistDeviceCertificateChain = function(chain) {
	GPSystem.log(GPSystem.DEBUG, module.id, "persistDeviceCertificateChain()");

	var cvcStore = new CVCertificateStore(this.service.daof);
	var crypto = new Crypto();
	var unprocessed = cvcStore.insertCertificates(crypto, [ chain.srca, chain.dica, chain.devicecert ], true);

	if (unprocessed.length != 0) {
		GPSystem.log(GPSystem.ERROR, module.id, "Failed to process " + unprocessed);
	}
}



IssueTokenRequestModel.prototype.handleSmartCardHSM = function(sc, chain, user) {
	GPSystem.log(GPSystem.DEBUG, module.id, "handleCardAction for status: " + this.bo.lifecycle);
	switch(this.bo.lifecycle) {
		case ServiceRequestModel.LIFECYCLE_EDIT:
			this.checkSelectedToken(user, chain.path, sc);
			break;
		default:
			GPSystem.log(GPSystem.ERROR, module.id, "Unexpected handleCard in state " + this.bo.lifecycle);
	}
}



IssueTokenRequestModel.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.addMessage("Could not authenticate SmartCard-HSM");
		return;
	}

	sc.openSecureChannel(crypto, chain.publicKey);

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



IssueTokenRequestModel.prototype.notify = function(sr) {
	GPSystem.log(GPSystem.DEBUG, module.id, "notify( " + sr.getLifeCycle() + " )");
	if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_APPROVE) {

		if (sr.getId() == this.model.registerTokenSRID) {
			this.model.registerTokenSRLifeCycle = sr.getLifeCycle()
		}

		if (sr.getId() == this.model.reqCertSRID) {
			this.model.reqCertSRLifeCycle = sr.getLifeCycle()
		}

		if (this.model.registerTokenSRID) { // Subject of type Person
			if (this.model.registerTokenSRLifeCycle == ServiceRequestModel.LIFECYCLE_COMPLETE
				&& this.model.reqCertSRLifeCycle >= ServiceRequestModel.LIFECYCLE_INUSE) {
				this.setLifeCycle(ServiceRequestModel.LIFECYCLE_COMPLETE);
				this.setStatusInfo("Person enrolled");
				this.model.tokenStatus = "registered_token";
			}
		} else { // Subject of type System
			if (this.model.reqCertSRLifeCycle >= ServiceRequestModel.LIFECYCLE_INUSE) {
				this.setLifeCycle(ServiceRequestModel.LIFECYCLE_COMPLETE);
				this.setStatusInfo("System enrolled");
			}
		}

		this.commit();
	}
}
