      1 /**
      2  *  ---------
      3  * |.##> <##.|  SmartCard-HSM Support Scripts
      4  * |#       #|
      5  * |#       #|  Copyright (c) 2011-2016 CardContact Systems GmbH
      6  * |'##> <##'|  Schuelerweg 38, 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 SmartCard-HSM Crypto Provider for EAC-PKI
     25  */
     26
     27 var CVC					= require('scsh/eac/CVC').CVC;
     28 var PublicKeyReference			= require('scsh/eac/PublicKeyReference').PublicKeyReference;
     29 var SmartCardHSM			= require('scsh/sc-hsm/SmartCardHSM').SmartCardHSM;
     30 var SmartCardHSMKey			= require('scsh/sc-hsm/SmartCardHSM').SmartCardHSMKey;
     31 var SmartCardHSMKeySpecGenerator	= require('scsh/sc-hsm/SmartCardHSM').SmartCardHSMKeySpecGenerator;
     32
     33
     34
     35 /**
     36  * Crypto Provider for EAC-PKI
     37  *
     38  * @param {SmartCardHSM} sc the associated SmartCard-HSM
     39  */
     40 function EACCryptoProvider(sc, id) {
     41 	this.sc = sc;
     42 	this.id = id;
     43 	sc.enumerateKeys();
     44 }
     45
     46 exports.EACCryptoProvider = EACCryptoProvider;
     47
     48 EACCryptoProvider.CVCCACERTIFICATEPREFIX = 0xD0;
     49 EACCryptoProvider.PATH_EF_FID = 0xD100;
     50
     51
     52
     53 /**
     54  * Replace SmartCard-HSM
     55  *
     56  * @param {SmartCardHSM} sc the new SmartCard-HSM
     57  */
     58 EACCryptoProvider.prototype.setSmartCardHSM = function(sc) {
     59 	this.sc = sc;
     60 	sc.enumerateKeys();
     61 }
     62
     63
     64
     65 /**
     66  * Get crypto object
     67  *
     68  * This method is part of the API.
     69  *
     70  * @type HSMCrypto
     71  * @return the HSMCrypto object
     72  */
     73 EACCryptoProvider.prototype.getCrypto = function() {
     74 	if (this.sc) {
     75 		return this.sc.getCrypto();
     76 	}
     77 	return new Crypto();
     78 }
     79
     80
     81
     82 /**
     83  * Transform path and certificate holder into a label
     84  *
     85  * @param {String} path the path
     86  * @param {PublicKeyReference} chr the certificate holder reference
     87  * @type String
     88  * @return the key label
     89  */
     90 EACCryptoProvider.path2label = function(path, chr) {
     91 	return path.substr(1) + chr.getSequenceNo();
     92 }
     93
     94
     95
     96 /**
     97  * Get a handle for a private key stored on the SmartCard-HSM
     98  *
     99  * This method is part of the API.
    100  *
    101  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
    102  * @param {PublicKeyReference} chr the public key reference for this key
    103  * @param {ByteString} blob the optional key blob
    104  * @returns the private key or null if not found
    105  * @type Key
    106  */
    107 EACCryptoProvider.prototype.getPrivateKey = function(path, chr, blob) {
    108 	var label = EACCryptoProvider.path2label(path, chr);
    109 	GPSystem.log(GPSystem.DEBUG, module.id, "Get private key " + label);
    110
    111 	return this.sc.getKey(label);
    112 }
    113
    114
    115
    116 /**
    117  * Delete private key from SmartCard-HSM
    118  *
    119  * This method is part of the API.
    120  *
    121  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
    122  * @param {PublicKeyReference} chr the public key reference for this key
    123  */
    124 EACCryptoProvider.prototype.deletePrivateKey = function(path, chr) {
    125 	var label = EACCryptoProvider.path2label(path, chr);
    126 	GPSystem.log(GPSystem.DEBUG, module.id, "Delete private key " + label);
    127 	var key = this.sc.getKey(label);
    128 	if (!key) {
    129 		return;
    130 	}
    131
    132 	var kid = key.getId();
    133 	var fid = ByteString.valueOf((SmartCardHSM.KEYPREFIX << 8) + kid);
    134 	this.sc.deleteFile(fid);
    135
    136 	try	{
    137 		var fid = ByteString.valueOf((SmartCardHSM.PRKDPREFIX << 8) + kid);
    138 		this.sc.deleteFile(fid);
    139
    140 		var fid = ByteString.valueOf((SmartCardHSM.EECERTIFICATEPREFIX << 8) + kid);
    141 		this.sc.deleteFile(fid);
    142 	}
    143 	catch(e) {
    144 		// Ignore
    145 	}
    146 	this.sc.enumerateKeys();
    147 }
    148
    149
    150
    151 /**
    152  * Generate a certificate request using a private key in the SmartCard-HSM
    153  *
    154  * This method is part of the API.
    155  *
    156  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1")
    157  * @param {PublicKeyReference} currentchr the current CHR for the key in use
    158  * @param {PublicKeyReference} nextchr the next available CHR
    159  * @param {PublicKeyReference} car the CA at which this request is addressed
    160  * @param {boolean} forceInitial force an initial request, even if a current certificate is available
    161  * @param {boolean} signinitial sign with initial key (sequence = 00000)
    162  * @param {Key} keyspec a key object containing key parameters (e.g. EC Curve)
    163  * @param {ByteString} algo the terminal authentication algorithm object identifier
    164  * @return the certificate request
    165  * @type CVC
    166  */
    167 EACCryptoProvider.prototype.generateRequest = function(path, currentchr, nextchr, car, forceinitial, signinitial, keyspec, algo) {
    168
    169 	var label = EACCryptoProvider.path2label(path, nextchr);
    170 	GPSystem.log(GPSystem.DEBUG, module.id, "Generate private key " + label);
    171
    172 	if (car == null) {			// CAR is not optional in SmartCard-HSM generated requests
    173 		car = nextchr;			// Use the CHR if no CAR defined.
    174 	}
    175
    176 	var key = this.sc.getKey(label);
    177 	if (key) {
    178 		var newkid = key.getId();
    179 	} else {
    180 		var newkid = this.sc.determineFreeKeyId();
    181 	}
    182
    183 	var kg = null;
    184 	if (typeof(keyspec.getComponent(Key.ECC_P)) != "undefined") {
    185 		var keysize = keyspec.getSize();
    186 		if (keysize < 0) {
    187 			var keysize = keyspec.getComponent(Key.ECC_P).length << 3;
    188 		}
    189 		if (keysize == 528) {
    190 			keysize = 521;
    191 		}
    192 		var kg = new SmartCardHSMKeySpecGenerator(Crypto.EC, keyspec);
    193 		var keydesc = SmartCardHSM.buildPrkDforECC(newkid, label, keysize);
    194 	} else {
    195 		var kg = new SmartCardHSMKeySpecGenerator(Crypto.RSA, keyspec.getSize());
    196 		var keydesc = SmartCardHSM.buildPrkDforRSA(newkid, label, keyspec.getSize());
    197 	}
    198
    199 	kg.defaultAlgo = algo;
    200 	kg.setInnerCAR(car)
    201 	kg.setCHR(nextchr);
    202 	GPSystem.log(GPSystem.DEBUG, module.id, "key domain slot " + this.id);
    203 	if (this.id >= 0) {
    204 		GPSystem.log(GPSystem.DEBUG, module.id, "set key domain slot to " + this.id);
    205 		kg.setKeyDomain(this.id);
    206 	}
    207 	var keydata = kg.encode();
    208 	GPSystem.log(GPSystem.DEBUG, module.id, "gakp cdata");
    209 	GPSystem.log(GPSystem.DEBUG, module.id, keydata);
    210
    211 	var reqbin = this.sc.generateAsymmetricKeyPair(newkid, 0, keydata);
    212
    213 	var fid = ByteString.valueOf((SmartCardHSM.PRKDPREFIX << 8) + newkid);
    214 	this.sc.updateBinary(fid, 0, keydesc.getBytes());
    215
    216 	if (((currentchr == null) || forceinitial) && !signinitial) {
    217 		var a = new ASN1(reqbin);
    218 		a = a.get(0);
    219 		var req = new CVC(a.getBytes());
    220 	} else {
    221 		var req = new CVC(reqbin);
    222 	}
    223
    224 	var hkey = new SmartCardHSMKey(this.sc, newkid);
    225 	hkey.setDescription(keydesc);
    226 	this.sc.addKeyToMap(hkey);
    227
    228 	return req;
    229 }
    230
    231
    232 // ---- Functions below are used to synchronize Certificates between the certstore and SmartCard-HSM
    233
    234 /**
    235  * Read the systems path from the SmartCard-HSM
    236  *
    237  * The path is used to configure the namespace for the node to which the SmartCard-HSM is connected
    238  */
    239 EACCryptoProvider.prototype.getPathFromDevice = function() {
    240 	var fid = ByteString.valueOf(EACCryptoProvider.PATH_EF_FID);
    241 	if (!this.sc.hasFile(fid)) {
    242 		return;
    243 	}
    244 	var s = this.sc.readBinary();
    245 	return s.toString(ASCII);
    246 }
    247
    248
    249
    250 /**
    251  * Enumerate keys on the SmartCard-HSM
    252  *
    253  * The method returns an array of objects with the following properties
    254  *
    255  * label       String - The key name on the device
    256  * path        String - The label transformed into a PKI path
    257  * chr         PublicKeyReference - the CHR for the key
    258  * kid         Number - The key identifier on the device
    259  * cvc         CVC - the card verifiable certificate or undefined if none on the device
    260  *
    261  * @type Object[]
    262  * @return the list of keys on the device
    263  */
    264 EACCryptoProvider.prototype.enumerateKeys = function() {
    265 	var list = this.sc.enumerateKeys();
    266
    267 	var keylist = [];
    268
    269 	for each (label in list) {
    270 		if (label.indexOf("/") < 0) {
    271 			continue;
    272 		}
    273
    274 		var pe = label.split("/");
    275 		var chr = new PublicKeyReference(pe[pe.length - 1]);
    276
    277 		var path = "";
    278 		for (var i = 0; i < pe.length - 1; i++) {
    279 			path += "/" + pe[i];
    280 		}
    281 		path += "/" + chr.getHolder();
    282
    283 		var key = this.sc.getKey(label);
    284 		var kid = key.getId();
    285
    286 		var cvc = undefined;
    287 		var fid = ByteString.valueOf((SmartCardHSM.EECERTIFICATEPREFIX << 8) + kid);
    288 		if (this.sc.hasFile(fid)) {
    289 			var certbin = this.sc.readBinary(fid);
    290 			cvc = new CVC(certbin);
    291 		}
    292 		keylist.push( { label: label, path: path, chr: chr, kid: kid, cvc: cvc } );
    293 	}
    294
    295 	return keylist;
    296 }
    297
    298
    299
    300 /**
    301  * Enumerate CVC certificates for CAs
    302  *
    303  * @type CVC[]
    304  * @return the list of CA certificates on the device
    305  */
    306 EACCryptoProvider.prototype.enumerateCertificates = function() {
    307 	var fids = this.sc.enumerateObjects();
    308
    309 	var certlist = [];
    310
    311 	for (var i = 0; i < fids.length; i += 2) {
    312 		if (fids.byteAt(i) == EACCryptoProvider.CVCCACERTIFICATEPREFIX) {
    313 			var fid = fids.bytes(i, 2);
    314 			try	{
    315 				var certbin = this.sc.readBinary(fid);
    316 				cvc = new CVC(certbin);
    317 				certlist.push(cvc);
    318 			}
    319 			catch(e) {
    320 				GPSystem.log(GPSystem.ERROR, module.id, "Trying to read certificate : " + e);
    321 			}
    322 		}
    323 	}
    324
    325 	return certlist;
    326 }
    327
    328
    329
    330 /**
    331  * Delete CA certificates on device
    332  */
    333 EACCryptoProvider.prototype.deleteCertificates = function() {
    334 	var fids = this.sc.enumerateObjects();
    335
    336 	for (var i = 0; i < fids.length; i += 2) {
    337 		if (fids.byteAt(i) == EACCryptoProvider.CVCCACERTIFICATEPREFIX) {
    338 			var fid = fids.bytes(i, 2);
    339 			this.sc.deleteFile(fid);
    340 		}
    341 	}
    342 }
    343
    344
    345
    346 /**
    347  * Delete certificate on device for a given key
    348  *
    349  * @param {Object} k the key as returned by enumerateKeys()
    350  */
    351 EACCryptoProvider.prototype.deleteCertificateForKey = function(k) {
    352 	var fid = ByteString.valueOf((SmartCardHSM.EECERTIFICATEPREFIX << 8) + k.kid);
    353 	this.sc.deleteFile(fid);
    354 }
    355
    356
    357
    358 /**
    359  * Export CA and EE certificates
    360  *
    361  * This method erases all existing CA certificates on the device first
    362  *
    363  * @param {String} path the PKI path to be exported
    364  * @param {CVC[]} eecertlist the list of end-entity certificates
    365  * @param {CVC[]} cacertlist the list of CA certificates
    366  */
    367 EACCryptoProvider.prototype.exportCertificates = function(path, eecertlist, cacertlist) {
    368 	this.deleteCertificates();
    369
    370 	var keylist = this.enumerateKeys();
    371
    372 	var cnt = 0;
    373 	for (var i = 0; i < keylist.length; i++) {
    374 		var k = keylist[i];
    375 		GPSystem.log(GPSystem.DEBUG, module.id, "Find matching certificate for " + k.label );
    376
    377 		for (j = 0; j < eecertlist.length; j++) {
    378 			var cvc = eecertlist[j];
    379
    380 			if (cvc.getCHR().equals(k.chr)) {
    381 				GPSystem.log(GPSystem.DEBUG, module.id, "Found matching certificate " + cvc );
    382 				var fid = ByteString.valueOf((SmartCardHSM.EECERTIFICATEPREFIX << 8) + k.kid);
    383
    384 				if (k.cvc) {
    385 					if (!k.cvc.getBytes().equals(cvc.getBytes())) {
    386 						this.sc.deleteFile(fid);
    387 						this.sc.updateBinary(fid, 0, cvc.getBytes());
    388 					} else {
    389 						GPSystem.log(GPSystem.DEBUG, module.id, "Certificate already on device - not saved");
    390 					}
    391 				} else {
    392 					this.sc.updateBinary(fid, 0, cvc.getBytes());
    393 				}
    394
    395 				cnt++;
    396 			}
    397 		}
    398 	}
    399
    400 	for (var i = 0; i < cacertlist.length; i++) {
    401 		var cvc = cacertlist[i];
    402 		var fid = ByteString.valueOf((EACCryptoProvider.CVCCACERTIFICATEPREFIX << 8) + i);
    403 		GPSystem.log(GPSystem.DEBUG, module.id, "Writing to " + fid + " the certificate " + cvc );
    404 		this.sc.updateBinary(fid, 0, cvc.getBytes());
    405 		cnt++;
    406 	}
    407
    408 	GPSystem.log(GPSystem.DEBUG, module.id, "Writing path " + path );
    409 	var fid = ByteString.valueOf(EACCryptoProvider.PATH_EF_FID);
    410 	try 	{
    411 		this.sc.deleteFile(fid);
    412 	}
    413 	catch(e) {
    414 		// Ignore
    415 	}
    416
    417 	this.sc.updateBinary(fid, 0, new ByteString(path, ASCII));
    418
    419 	return cnt;
    420 }
    421