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