/**
 *  ---------
 * |.##> <##.|  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 Card related actions performed in the UI
 */



var CardAction = require('scsh/srv-cc1/CardAction').CardAction;
var HSMKeyStore = require('scsh/sc-hsm/HSMKeyStore').HSMKeyStore;
var IssueTokenRequestModel = require('pki-as-a-service/processes/IssueTokenRequestModel').IssueTokenRequestModel;


function InitializationWizard(ui, sr, redirectUrl) {
	CardAction.call(this, ui);
	this.sr = sr;
	this.redirectUrl = redirectUrl;
	this.requestPIN = -1;
	GPSystem.log(GPSystem.DEBUG, module.id, "new InitializationWizard(" + sr + "," + redirectUrl + ")");

	// default values
	this.state = "derivationKey";

	this.initWizardFromSR();
}


InitializationWizard.prototype = Object.create(CardAction.prototype);
InitializationWizard.constructor = InitializationWizard;

exports.InitializationWizard = InitializationWizard;


InitializationWizard.USER_PIN = "User PIN";
InitializationWizard.TRANSPORT_PIN = "Transport PIN";
InitializationWizard.PUBLIC_KEY_AUTHENTICATION = "Public Key Authentication";



InitializationWizard.prototype.handleRequest = function(req, res) {
	default xml namespace = "http://www.w3.org/1999/xhtml";
	GPSystem.log(GPSystem.DEBUG, module.id, "handleRequest");

	if (req.method == "POST" && this.state != CardAction.SUCCESS) {
		if (req.parameters.change) {
			this.state = "derivationKey";
		} else if (req.parameters.derivationKey) {
			this.derivationKey = req.parameters.derivationKey;
			if (this.sr.hasTokenManagementKeyKCV()) {
				this.state = "changeSOPIN";
			} else {
				this.state = "rrcMode";
			}
		} else if (typeof(req.parameters.changeSOPIN) != "undefined") {
			this.changeSOPIN = req.parameters.changeSOPIN == "true";
			this.state = "rrcMode";
		} else if (req.parameters.rrcMode) {
			this.rrcMode = parseInt(req.parameters.rrcMode);
			this.state = "authMech";
		} else if (req.parameters.authMech) {
			this.authMech = req.parameters.authMech;
			this.pinLength = parseInt(req.parameters.pinLength);
			this.state = "pin";
		} else if (req.parameters.pin) {
			if (req.parameters.pin.length != this.pinLength) {
				this.ui.setErrorMessage("Error", "The selected PIN is not " + this.pinLength + " digits long");
				GPSystem.log(GPSystem.ERROR, module.id, "The selected PIN is not " + this.pinLength
					+ " digits long (expected " + this.pinLength + ")" );
				this.ui.redirectTo(req, res, req.url, "/wizard");
				return true;
			}
			this.pin = req.parameters.pin;
			this.state = "noOfKeyDomains";
		} else if (req.parameters.noOfKeyDomains) {
			this.noOfKeyDomains = parseInt(req.parameters.noOfKeyDomains);
			this.state = "summary";
		} else if (req.parameters.initialize) {

			this.sr.setInitializeData(
				this.ui.session.user,
				{
					derivationKeyLabel: this.derivationKey,
					rrcMode: this.rrcMode,
					pinLength: this.pinLength,
					transportMode: this.authMech == InitializationWizard.TRANSPORT_PIN,
					pin: this.pin,
					noOfKeyDomains: this.noOfKeyDomains,
					changeSOPIN: this.changeSOPIN
				}
			);

			this.state = CardAction.NEW;
		} else if (req.parameters.cancel) {
			GPSystem.log(GPSystem.DEBUG, module.id, "redirect to " + this.redirectUrl);
			this.ui.redirectTo(req, res, req.url, this.redirectUrl);
			return;
		}

		this.ui.redirectTo(req, res, req.url, "/wizard");
		return true;
	}

	var page = this.getPage(req, res, req.url);
	if (page) {
		var t = this.ui.generateTemplate(req.url);
		var c = t..div.(@id == "content");
		c.div = page;
		this.ui.sendPage(req, res, req.url, t);
	} else {
		GPSystem.log(GPSystem.DEBUG, module.id, "redirect to " + this.redirectUrl);
		this.ui.redirectTo(req, res, req.url, this.redirectUrl);
	}
}



InitializationWizard.prototype.initWizardFromSR = function() {
	var config = this.sr.getInitializeData();
	this.derivationKey = config.derivationKeyLabel;
	this.rrcMode = config.rrcMode;
	if (config.transportMode) {
		this.authMech = InitializationWizard.TRANSPORT_PIN;
	} else {
		this.authMech = InitializationWizard.USER_PIN;
	}
	this.pinLength = config.pinLength;
	this.noOfKeyDomains = config.noOfKeyDomains;
	this.changeSOPIN = config.changeSOPIN;
	this.pin = config.pin;

	var hsmList = this.sr.getHSMList();

	if (hsmList.length == 0) {
		// Show no HSM connected page
		this.state = "derivationKey";
	} else if (this.sr.getDerivationKeyLabel() && !this.sr.getDerivationKey()) {
		// Management token not connected
		this.state = "connectKey";
	} else {
		this.state = "summary";
	}
}



InitializationWizard.prototype.getDerivationKeyPage = function(url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var hsmList = this.sr.getHSMList();

	if (hsmList.length == 0) {
		var page =
			<div>
				<div id="error"/>

				<h1>Initialize Token Wizard</h1>

				<h3>SO-PIN</h3>

				<p>The SO-PIN will be derived from a derivation key of one the connected trust center token.</p>

				<p>There is currently no authenticated trust center token online.</p>

				<p>Please connect a trust center token to select a derivation key. <a href="">continue</a>.</p>

				<pre> java -jar ocf-client.jar -r {"\"<reader name>\" " + ApplicationServer.instance.serverURL + "/rt/hsm"}</pre>

				<p>Check the authentication state of connected trust center token <a href={this.ui.homeURL(url) + "/subject/" + this.sr.getRecipientId()}>here</a>.</p>
			</div>
	} else {
		var page =
			<div>
				<div id="error"/>

				<h1>Initialize Token Wizard</h1>

				<h3>SO-PIN</h3>

				<p>The SO-PIN will be derived from a derivation key of one the connected trust center token.</p>

				<form class="pure-form pure-form-aligned" action="" method="post" enctype="multipart/form-data">
					<fieldset>
						<legend>Please select a derivation key</legend>
						<div class="pure-control-group">
							<label for="derivationKey">Key</label>
							<select id="derivationKey" name="derivationKey"/>
							<button class="pure-button" type="submit">Select</button>
						</div>
					</fieldset>
				</form>
			</div>

		var keyList = page.form[0].fieldset.div.select;

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

			// Populate key list

			var keys = this.getSymmetricKeys(hsm.sc);

			var optgroup = <optgroup label={ hsm.path } />;

			for (var j = 0; j < keys.length; j++) {
				var key = keys[j];

				var option = <option value={key.getLabel()}>{key.getLabel()}</option>

				if (key.getLabel() == this.derivationKey) {
					option.@selected = "selected";
				}

				optgroup.appendChild(option);
			}

			if (keys.length > 0) {
				keyList.appendChild(optgroup);
			}
		}

		if (keyList.*.length() == 0) {
			keyList.appendChild( <option value="n/a" disabled="disabled=" selected = "selected">No symmetric key found</option> );
			page.form[0].fieldset.div.button.@disabled = "disabled";
		}
	}

	return page;
}



InitializationWizard.prototype.getConnectKeyPage = function(url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var page =
		<div>
			<div id="error"/>

			<h1>Initialize Token Wizard</h1>

			<h3>SO-PIN</h3>

			<p>The SO-PIN will be derived from the derivation key with label <strong>{this.derivationKey}</strong>.</p>

			<p>There is currently <strong>no trust center token online</strong> which contains this derivation key <strong>or the token is <a href={this.ui.homeURL(url) + "/subject/" + this.sr.getRecipientId()}>not logged in</a></strong>.</p>

			<p>Please connect the corresponding trust center token to <a href="">continue</a>.</p>

			<pre> java -jar ocf-client.jar -r {"\"<reader name>\" " + ApplicationServer.instance.serverURL + "/rt/hsm"}</pre>
		</div>

	return page;
}



InitializationWizard.prototype.getSymmetricKeys = function(sc) {
	var aesKeys = [];

	var keyStore = new HSMKeyStore(sc);
	var labels = keyStore.enumerateKeys();
	for (var i = 0; i < labels.length; i++) {
		var label = labels[i];
		GPSystem.log(GPSystem.DEBUG, module.id, "label: " + label);
		var key = keyStore.getKey(label);
		GPSystem.log(GPSystem.DEBUG, module.id, "type: " + key.getType());
		if (key.getType() == "AES") {
			GPSystem.log(GPSystem.DEBUG, module.id, "Add AES Key: " + label);
			aesKeys.push(key);
		}
	}

	return aesKeys;
}



InitializationWizard.prototype.getConfigPage = function(req, res, url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var page = <div>
		<div id="error"/>

		<h1>Initialize Token Wizard</h1>

		<h3>Configure Initialization Data</h3>

		<p>Please configure the initialization options.</p>

		<form class="pure-form pure-form-aligned" action="" method="post" enctype="multipart/form-data">
			<div class="pure-control-group">
				<label for="authMech">Authentication Mechanism</label>
				<select id="authMech" name="authMech"/>
			</div>
			<div class="pure-control-group">
				<label for="pinLength">PIN Length</label>
				<input id="pinLength" name="pinLength" type="number" max="16" value={ this.pinLength } />
			</div>
			<button class="pure-button" type="submit">Submit</button>
		</form>
	</div>

	var select = page.form.div.select;
	var option = <option value={InitializationWizard.TRANSPORT_PIN}>Transport PIN</option>;
	if (this.authMech == InitializationWizard.TRANSPORT_PIN) {
		option.@selected = "selected";
	}
	select.appendChild(option);

	var option = <option value={InitializationWizard.USER_PIN}>User PIN</option>
	if (this.authMech == InitializationWizard.USER_PIN) {
		option.@selected = "selected";
	}
	select.appendChild(option);

	return page;
}



InitializationWizard.prototype.getChangeSOPINPage = function(req, res, url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var page = <div>
		<div id="error"/>

		<h1>Initialize Token Wizard</h1>

		<h3>Configure Initialization Data</h3>

		<p>Please configure the initialization options.</p>

		<form class="pure-form" action="" method="post" enctype="multipart/form-data">
			<label for="changeSOPIN">Change current SO-PIN</label>
			<select id="changeSOPIN" name="changeSOPIN">
				<option value={false}>No, keep SO PIN</option>
				<option value={true}>Yes, I want a new SO PIN</option>
			</select>
			<button class="pure-button" type="submit">Submit</button>
		</form>
	</div>

	return page;
}



InitializationWizard.prototype.getAllowRRCPage = function(req, res, url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var page = <div>
		<div id="error"/>

		<h1>Initialize Token Wizard</h1>

		<h3>Configure Initialization Data</h3>

		<p>Please configure the initialization options.</p>

		<form class="pure-form" action="" method="post" enctype="multipart/form-data">
			<label for="rrcMode">Resetting PIN with SO-PIN</label>
			<select id="rrcMode" name="rrcMode"/>
			<button class="pure-button" type="submit">Submit</button>
		</form>
	</div>

	var select = page.form.select;
	var option = <option value={IssueTokenRequestModel.WITHOUT_RRC}>Not Allowed</option>
	if (this.rrcMode == IssueTokenRequestModel.WITHOUT_RRC) {
		option.@selected = "selected";
	}
	select.appendChild(option);

	var option = <option value={IssueTokenRequestModel.WITH_RRC}>Changing and Unblocking of the PIN Allowed</option>
	if (this.rrcMode == IssueTokenRequestModel.WITH_RRC) {
		option.@selected = "selected";
	}
	select.appendChild(option);

	var option = <option value={IssueTokenRequestModel.WITH_RRC_RESET_ONLY}>Only Unblocking of the PIN Allowed</option>
	if (this.rrcMode == IssueTokenRequestModel.WITH_RRC_RESET_ONLY) {
		option.@selected = "selected";
	}
	select.appendChild(option);

	return page;
}



InitializationWizard.prototype.getEnterPINPage = function(req, res, url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	if (this.authMech == InitializationWizard.TRANSPORT_PIN) {
		var pinLabel = "Transport PIN";
	} else {
		var pinLabel = "PIN";
	}

	var crypto = new Crypto();
	this.pin = this.sr.service.generateActivationCode(this.pinLength);

	var page = <div>
		<div id="error"/>

		<h1>Initialize Token Wizard</h1>

		<h3>Configure Initialization Data</h3>

		<p>Please configure the initialization options.</p>

		<form class="pure-form" action="" method="post" enctype="multipart/form-data">
			<label for="pin">{pinLabel}</label>
			<input id="pin" name="pin" type="text" value={ this.pin } />
			<button class="pure-button" type="submit">Submit</button>
		</form>
	</div>

	return page;
}



InitializationWizard.prototype.getNoOfKeyDomainsPage = function(req, res, url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var page = <div>
		<div id="error"/>

		<h1>Initialize Token Wizard</h1>

		<h3>Configure Initialization Data</h3>

		<p>Please configure the initialization options.</p>

		<form class="pure-form" action="" method="post" enctype="multipart/form-data">
			<label for="noOfKeyDomains">Number of Key Domains</label>
			<input id="noOfKeyDomains" name="noOfKeyDomains" type="number" max="255" value={ this.noOfKeyDomains } />
			<button class="pure-button" type="submit">Submit</button>
		</form>
	</div>

	return page;
}



InitializationWizard.prototype.getInitializePage = function(req, res, url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	if (this.rrcMode == IssueTokenRequestModel.WITH_RRC) {
		var rrcConfig = "Changing and Unblocking of the PIN Allowed";
	} else if (this.rrcMode == IssueTokenRequestModel.WITH_RRC_RESET_ONLY) {
		var rrcConfig = "Only Unblocking of the PIN Allowed";
	} else {
		var rrcConfig = "Not Allowed";
	}

	var page = <div>
		<div id="error"/>

		<h1>Initialize Token Wizard</h1>

		<h3>Summary</h3>

		<table class="pure-table pure-table-horizontal form-table">
				<tr>
					<th>Token</th>
					<td>{this.sr.getTokenPath()}</td>
				</tr>
				<tr>
					<th>SO-PIN Derivation Key</th>
					<td>{this.derivationKey}</td>
				</tr>
				<tr>
					<th>PIN Unblock</th>
					<td>{rrcConfig}</td>
				</tr>
				<tr>
					<th>Authentication Mechanism</th>
					<td>{this.authMech}</td>
				</tr>
				<tr>
					<th>PIN Length</th>
					<td>{this.pinLength}</td>
				</tr>
				<tr>
					<th>PIN</th>
					<td>{this.pin}</td>
				</tr>
				<tr>
					<th>Number of Key Domains</th>
					<td>{this.noOfKeyDomains}</td>
				</tr>
		</table>

		<p>
			<form class="pure-form" action="" method="post" enctype="multipart/form-data">
				<button class="pure-button" type="submit" name="initialize" value="true">Initialize</button>
				<button class="pure-button" type="submit" name="change" value="true">Change</button>
				<button class="pure-button" type="submit" name="cancel" value="true">Cancel</button>
			</form>
		</p>
	</div>

	if (this.sr.hasTokenManagementKeyKCV()) {
		var tr =
			<tr>
				<th>Change current SO-PIN</th>
				<td>{this.changeSOPIN ? "Yes" : "No"}</td>
			</tr>
		page.table.insertChildAfter(page.table.tr[1], tr);
	}

	return page;
}



InitializationWizard.prototype.getPage = function(req, res, url) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	switch(this.state) {
		case "derivationKey":
			var page = this.getDerivationKeyPage(url);
			break;
		case "connectKey":
			if (!this.sr.getDerivationKey()) {
				var page = this.getConnectKeyPage(url);
				break;
			}
			if (this.sr.hasTokenManagementKeyKCV()) {
				this.state = "changeSOPIN";
			} else {
				this.state = "rrcMode";
			}
		case "changeSOPIN":
			var page = this.getChangeSOPINPage();
			break;
		case "rrcMode":
			var page = this.getAllowRRCPage();
			break;
		case "authMech":
			var page = this.getConfigPage();
			break;
		case "pin":
			var page = this.getEnterPINPage();
			break;
		case "noOfKeyDomains":
			var page = this.getNoOfKeyDomainsPage();
			break;
		case "summary":
			var page = this.getInitializePage();
			break;
		case CardAction.NEW:
			this.state = CardAction.NOCLIENT;
			var page = this.getProgressPage(req, res);
			break;
		case CardAction.NOCLIENT:
			var page = this.getNoClientPage(req, res);
			this.state = CardAction.NEW;
			break;
		case CardAction.ERROR:
			var page = this.getErrorPage(req, res);
			this.state = CardAction.NEW;
			break;
		case CardAction.SUCCESS:	// reached after handleCard
			return null;
			break;
		default:
			var page = this.getDerivationKeyPage();
	}

	return page;
}





InitializationWizard.prototype.handleCardAction = function(card, session, pathInfo) {
	GPSystem.log(GPSystem.DEBUG, module.id, "handleCardAction");

	this.state = CardAction.ERROR;

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

	if (this.sr.getTokenPath() != chain.path) {
		this.reportError("Wrong token presented. Expecting token " + this.sr.getTokenPath());
		return;
	}

	sc.openSecureChannel(crypto, chain.publicKey);

	try {
		this.sr.initialize(card, chain, session.user);
		// Preserve authentication state after SM termination
		sc.card.sendSecMsgApdu(Card.ALL, 0x00, 0x20, 0x01, 0x81, 0, [0x9000,0x6985]);

		this.state = CardAction.SUCCESS;
	} catch (e) {
		this.reportError("Initialization failed: " + e.message);
		GPSystem.log(GPSystem.ERROR, module.id, "Initialization failed: " + e.message + "\n" + e.stack);
	}
}
