PortalPersonalizer.js

Summary

Personalization Client forthe PKI-as-a-Service Portal


Class Summary
PortalPersonalizer  

/**
 *  ---------
 * |.##> <##.|  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 Personalization Client forthe PKI-as-a-Service Portal
 */

var SmartCardHSM	= require('scsh/sc-hsm/SmartCardHSM').SmartCardHSM;
var CVC 		= require("scsh/eac/CVC").CVC;
var DICATask		= require("DICATask").DICATask;


function PortalPersonalizer(job) {
	this.job = job;
}


exports.PortalPersonalizer = PortalPersonalizer;




PortalPersonalizer.prototype.configure = function() {
	this.parseSubjectURL();
	this.login();
	if (typeof(this.job.dicaId) != "undefined") {
		this.dicaId = this.job.dicaId;
	} else {
		this.getDICA();
	}
	if (Dialog.prompt("Connect DICA Token to Portal ?")) {
		this.attachDICA();
	}
}



PortalPersonalizer.prototype.parseSubjectURL = function(url) {

	var url = "https://www.pki-as-a-service.net/se/paas/subject/<id>";
	var subjectPath = "/se/paas/subject/";
	var prompt = true;

	if (typeof(this.job.url) != "undefined") {
		url = this.job.url;
	}

	while (true) {
		var pathIdx = url.indexOf(subjectPath);
		if (pathIdx < 0) {
			assert(Dialog.prompt("URL must contain " + subjectPath));
		} else {
			var id = parseInt(url.substring(pathIdx + subjectPath.length));
			if (isNaN(id)) {
				assert(Dialog.prompt("URL must contain a subject id after " + subjectPath));
			} else {
				break;
			}
		}

		url = Dialog.prompt("Enter the URL of the Device Issuer", url);
		assert(url, "User abort");
	}

	this.host = url.substring(0, pathIdx);
	this.subjectId = id;
}



PortalPersonalizer.prototype.attachDICA = function() {
	var readerList = Card.getReaderList();
	var lastreader = _scsh3.DICAToken;
	if (!lastreader) {
		lastreader = _scsh3.reader;
	}
	while (true) {
		var reader = Dialog.prompt("Please select reader with DICA token", lastreader, readerList);
		assert(reader, "User abort");
		_scsh3.setProperty("DICAToken", reader);

		try	{
			var card = new Card(reader);
			var sc = new SmartCardHSM(card);
			sc.verifyUserPIN();
			break;
		}
		catch(e) {
			print(e);
		}
	}

	var url = this.host + "/rt/hsm";
	this.dicatask = new DICATask(card, url);
	this.dicatask.start();
}



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

	// Get new session
	var baseURL = this.host + "/se";
	var c = new URLConnection(baseURL);
	var result = c.get();

	var cookie = c.getHeaderField("Set-Cookie");
	this.session = cookie[0].substr(0, cookie[0].indexOf(";"));

	// Get connection string

	c.addHeaderField("Cookie", this.session);
	var result = c.get();

	var html = result.match(/<html xmlns="http:\/\/www.w3.org\/1999\/xhtml" lang="en" xml:lang="en">[\s\S]*<\/html>/);
	var page = new XML(html);

	var content = page..div.(@id == "content");
	var img = content..img;
	var remoteStr = img.@src.toXMLString();

	var queryStr = remoteStr.substr(remoteStr.indexOf("?") + 1);

	var params = queryStr.split("&amp;");
	var conInfo = {};
	for (var i = 0; i < params.length; i++) {
		var param = params[i];
		var res = param.split("=");
		conInfo[res[0]] = res[1];
	}

	// Authenticate with login token

	var readerList = Card.getReaderList();
	var lastreader = _scsh3.portalReader;
	if (!lastreader) {
		lastreader = _scsh3.reader;
	}
	var reader = Dialog.prompt("Please select reader with token to log in", lastreader, readerList);
	assert(reader, "User abort");
	_scsh3.setProperty("portalReader", reader);

	var card = new Card(reader);

	if (conInfo.pinrequired == 1) {
		GPSystem.trace("Authenticate User");
		var sc = new SmartCardHSM(card);
		sc.verifyUserPIN();
	}

	GPSystem.trace("Connect Card");
	card.remoteUpdate(conInfo.url, conInfo.sessionId);
	var sel = Dialog.prompt("Please remove the token to prevent accidently overwriting it");
	assert(sel, "User abort");
}



PortalPersonalizer.prototype.getDICA = function() {
	// Retrieve DICA

	var dicaURL = this.host + "/se/paas/subject-rest/" + this.subjectId + "/dica";

	var c = new URLConnection(dicaURL);
	c.addHeaderField("Cookie", this.session);
	var result = c.get();
	GPSystem.trace(result);

	var deviceIssuer = JSON.parse(result);
	var labels = [];
	for (var i = 0; i < deviceIssuer.dica.length; i++) {
	labels.push(deviceIssuer.dica[i].label);
	}

	var dicaLabel = Dialog.prompt("Select DICA", deviceIssuer.dica[0].label, labels);

	for (var i = 0; i < deviceIssuer.dica.length; i++) {
		var dica = deviceIssuer.dica[i];
		if (dica.label == dicaLabel) {
			this.dicaId = dica.id;
			print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
			print("You have selected the dica " + dicaLabel + " of Device Issuer "  + deviceIssuer.name + ".");
			print("Update your jobs.js configuration to the following to remember this decision for future use.");
			print("");
			print("PortalPersonalizer: {");
			print("\turl: \"" + this.host + "/se/paas/subject/" + this.subjectId + "\",");
			print("\tdicaId: " + this.dicaId);
			print("}");
			print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
		}
	}
}



PortalPersonalizer.prototype.createServiceRequest = function() {
	var srURL = this.host + "/se/paas/sr-rest/new";
	srURL += "?process=SmartCardHSMPersonalization";
	srURL += "&ca=" + this.subjectId;
	srURL += "&issuer=" + this.dicaId;
	if (typeof(this.job.ccr) != "undefined") {
		srURL += "&ccr=" + this.job.ccr;
	}


//	print("Post Personalization Request");

	var c = new URLConnection(srURL);
	c.addHeaderField("Cookie", this.session);
	var result = c.post("");
	if (c.responseCode != 201) {
		throw new GPError(module.id, GPError.OBJECTCREATIONFAILED, 0, "Failed to create personalization service request (" + c.responseCode + ")");
	}

	var authToken = JSON.parse(result);
	return authToken;
}



PortalPersonalizer.prototype.handleCard = function(card) {
	var authToken = this.createServiceRequest();

	var remoteUpdateURL = this.host + "/rt/paas?" + authToken;

	GPSystem.trace("Connect card to service request");
	card.remoteUpdate(remoteUpdateURL);

	if (card.remoteMessageId != 0) {
		throw new GPError(module.id, GPError.OBJECTCREATIONFAILED, 0, "Personalization service request failed: " + card.remoteMessage);
	}

	// check personalization status

	GPSystem.wait(1000); // waiting for db commit

	var inp = new ByteString(authToken, HEX);
	var mac = inp.right(8);
	var inp = inp.bytes(0, inp.length - mac.length);
	var id = inp.toUnsigned();

	var srURL = this.host + "/se/paas/sr-rest/" + id;
	var c = new URLConnection(srURL);
	c.addHeaderField("Cookie", this.session);
	var result = c.get();
//	print("Result:");
//	print(result);

	var sr = JSON.parse(result);

	if (sr.lifeCycle != 19) {
		throw new GPError(module.id, GPError.OBJECTCREATIONFAILED, 0, "Personalization service request failed: " + sr.statusInfo);
	}

	return this.testCard(card);
}



PortalPersonalizer.prototype.testCard = function(card) {
	card.reset(Card.RESET_COLD);

	// Get some information using the identify APDU
	var id = card.sendApdu(0x00, 0xA4, 0x04, 0x00, new ByteString("A000000167413000FF", HEX), [0x6A82,0x6A86]);
	var sc = new SmartCardHSM(card);

	// authenticate the Device Authentication Certificate
	var devAutCert = sc.readBinary(new ByteString("2F02", HEX));
	var cvc = new CVC(devAutCert);
	GPSystem.trace("Device Certificate    : " + cvc);

	var crypto = new Crypto();
	var dica = new CVC(devAutCert.bytes(cvc.getASN1().size));
	GPSystem.trace("Device Issuer CA      : " + dica);
	var srca = SmartCardHSM.rootCerts[dica.getCAR()];
	GPSystem.trace("SmartCard-HSM Root CA : " + srca);

	if (!srca) {
		var certbin = new ByteString("7F218201B27F4E82016A5F290100420C5858535243413130303030317F4982011D060A04007F000702020202038120A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E537782207D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9832026DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B68441048BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F0469978520A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A78641040E456F4C06B7D27B12CC7A4C7C75E0E6C0B32EA7066177D75D8E220F68B47FDB709819A1B82354508A996C023412A374472F4C050958E1585A88430B108FA17B8701015F200C5858535243413130303030317F4C10060B2B0601040181C31F0301015301C05F25060200000502065F240604000005020565005F3740522575F269042788FAB77C6364D229705F607F7768BD008BBBEA74654E754E6747D26EA6D2996BD4AE2D6EACFB7BBDD44F78187545AA96D25606B0DA3C9205FE", HEX);
		var srca = new CVC(certbin);
		SmartCardHSM.rootCerts.XXSRCA100001 = srca;
	}

	var srcapuk = srca.getPublicKey();
	var oid = srca.getPublicKeyOID();
	assert(dica.verifyWith(crypto, srcapuk, oid), "DICA certificate not verified");

	var dicapuk = dica.getPublicKey(srcapuk);
	assert(cvc.verifyWith(crypto, dicapuk, oid), "Device certificate verification failed");
	var devicepuk = cvc.getPublicKey(srcapuk);

	sc.openSecureChannel(crypto, devicepuk);

	if ((id.length > 0) && (id.byteAt(14) == 0x00)) {
		print("\nWARNING: Device is ***not*** fused. For internal use only.\n");
	}

	return "OK " + cvc.getCHR().toString();
}


Documentation generated by JSDoc on Sat Feb 24 15:17:19 2024