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 		var kg = new SmartCardHSMKeySpecGenerator(Crypto.EC, keyspec);
190 		var keydesc = SmartCardHSM.buildPrkDforECC(newkid, label, keysize);
191 	} else {
192 		var kg = new SmartCardHSMKeySpecGenerator(Crypto.RSA, keyspec.getSize());
193 		var keydesc = SmartCardHSM.buildPrkDforRSA(newkid, label, keyspec.getSize());
194 	}
195 
196 	kg.defaultAlgo = algo;
197 	kg.setInnerCAR(car)
198 	kg.setCHR(nextchr);
199 	GPSystem.log(GPSystem.DEBUG, module.id, "key domain slot " + this.id);
200 	if (this.id >= 0) {
201 		GPSystem.log(GPSystem.DEBUG, module.id, "set key domain slot to " + this.id);
202 		kg.setKeyDomain(this.id);
203 	}
204 	var keydata = kg.encode();
205 	GPSystem.log(GPSystem.DEBUG, module.id, "gakp cdata");
206 	GPSystem.log(GPSystem.DEBUG, module.id, keydata);
207 
208 	var reqbin = this.sc.generateAsymmetricKeyPair(newkid, 0, keydata);
209 
210 	var fid = ByteString.valueOf((SmartCardHSM.PRKDPREFIX << 8) + newkid);
211 	this.sc.updateBinary(fid, 0, keydesc.getBytes());
212 
213 	if (((currentchr == null) || forceinitial) && !signinitial) {
214 		var a = new ASN1(reqbin);
215 		a = a.get(0);
216 		var req = new CVC(a.getBytes());
217 	} else {
218 		var req = new CVC(reqbin);
219 	}
220 
221 	var hkey = new SmartCardHSMKey(this.sc, newkid);
222 	hkey.setDescription(keydesc);
223 	this.sc.addKeyToMap(hkey);
224 
225 	return req;
226 }
227 
228 
229 // ---- Functions below are used to synchronize Certificates between the certstore and SmartCard-HSM
230 
231 /**
232  * Read the systems path from the SmartCard-HSM
233  *
234  * The path is used to configure the namespace for the node to which the SmartCard-HSM is connected
235  */
236 EACCryptoProvider.prototype.getPathFromDevice = function() {
237 	var fid = ByteString.valueOf(EACCryptoProvider.PATH_EF_FID);
238 	if (!this.sc.hasFile(fid)) {
239 		return;
240 	}
241 	var s = this.sc.readBinary();
242 	return s.toString(ASCII);
243 }
244 
245 
246 
247 /**
248  * Enumerate keys on the SmartCard-HSM
249  *
250  * The method returns an array of objects with the following properties
251  *
252  * label       String - The key name on the device
253  * path        String - The label transformed into a PKI path
254  * chr         PublicKeyReference - the CHR for the key
255  * kid         Number - The key identifier on the device
256  * cvc         CVC - the card verifiable certificate or undefined if none on the device
257  *
258  * @type Object[]
259  * @return the list of keys on the device
260  */
261 EACCryptoProvider.prototype.enumerateKeys = function() {
262 	var list = this.sc.enumerateKeys();
263 
264 	var keylist = [];
265 
266 	for each (label in list) {
267 		if (label.indexOf("/") < 0) {
268 			continue;
269 		}
270 
271 		var pe = label.split("/");
272 		var chr = new PublicKeyReference(pe[pe.length - 1]);
273 
274 		var path = "";
275 		for (var i = 0; i < pe.length - 1; i++) {
276 			path += "/" + pe[i];
277 		}
278 		path += "/" + chr.getHolder();
279 
280 		var key = this.sc.getKey(label);
281 		var kid = key.getId();
282 
283 		var cvc = undefined;
284 		var fid = ByteString.valueOf((SmartCardHSM.EECERTIFICATEPREFIX << 8) + kid);
285 		if (this.sc.hasFile(fid)) {
286 			var certbin = this.sc.readBinary(fid);
287 			cvc = new CVC(certbin);
288 		}
289 		keylist.push( { label: label, path: path, chr: chr, kid: kid, cvc: cvc } );
290 	}
291 
292 	return keylist;
293 }
294 
295 
296 
297 /**
298  * Enumerate CVC certificates for CAs
299  *
300  * @type CVC[]
301  * @return the list of CA certificates on the device
302  */
303 EACCryptoProvider.prototype.enumerateCertificates = function() {
304 	var fids = this.sc.enumerateObjects();
305 
306 	var certlist = [];
307 
308 	for (var i = 0; i < fids.length; i += 2) {
309 		if (fids.byteAt(i) == EACCryptoProvider.CVCCACERTIFICATEPREFIX) {
310 			var fid = fids.bytes(i, 2);
311 			try	{
312 				var certbin = this.sc.readBinary(fid);
313 				cvc = new CVC(certbin);
314 				certlist.push(cvc);
315 			}
316 			catch(e) {
317 				GPSystem.log(GPSystem.ERROR, module.id, "Trying to read certificate : " + e);
318 			}
319 		}
320 	}
321 
322 	return certlist;
323 }
324 
325 
326 
327 /**
328  * Delete CA certificates on device
329  */
330 EACCryptoProvider.prototype.deleteCertificates = function() {
331 	var fids = this.sc.enumerateObjects();
332 
333 	for (var i = 0; i < fids.length; i += 2) {
334 		if (fids.byteAt(i) == EACCryptoProvider.CVCCACERTIFICATEPREFIX) {
335 			var fid = fids.bytes(i, 2);
336 			this.sc.deleteFile(fid);
337 		}
338 	}
339 }
340 
341 
342 
343 /**
344  * Delete certificate on device for a given key
345  *
346  * @param {Object} k the key as returned by enumerateKeys()
347  */
348 EACCryptoProvider.prototype.deleteCertificateForKey = function(k) {
349 	var fid = ByteString.valueOf((SmartCardHSM.EECERTIFICATEPREFIX << 8) + k.kid);
350 	this.sc.deleteFile(fid);
351 }
352 
353 
354 
355 /**
356  * Export CA and EE certificates
357  *
358  * This method erases all existing CA certificates on the device first
359  *
360  * @param {String} path the PKI path to be exported
361  * @param {CVC[]} eecertlist the list of end-entity certificates
362  * @param {CVC[]} cacertlist the list of CA certificates
363  */
364 EACCryptoProvider.prototype.exportCertificates = function(path, eecertlist, cacertlist) {
365 	this.deleteCertificates();
366 
367 	var keylist = this.enumerateKeys();
368 
369 	var cnt = 0;
370 	for (var i = 0; i < keylist.length; i++) {
371 		var k = keylist[i];
372 		GPSystem.log(GPSystem.DEBUG, module.id, "Find matching certificate for " + k.label );
373 
374 		for (j = 0; j < eecertlist.length; j++) {
375 			var cvc = eecertlist[j];
376 
377 			if (cvc.getCHR().equals(k.chr)) {
378 				GPSystem.log(GPSystem.DEBUG, module.id, "Found matching certificate " + cvc );
379 				var fid = ByteString.valueOf((SmartCardHSM.EECERTIFICATEPREFIX << 8) + k.kid);
380 
381 				if (k.cvc) {
382 					if (!k.cvc.getBytes().equals(cvc.getBytes())) {
383 						this.sc.deleteFile(fid);
384 						this.sc.updateBinary(fid, 0, cvc.getBytes());
385 					} else {
386 						GPSystem.log(GPSystem.DEBUG, module.id, "Certificate already on device - not saved");
387 					}
388 				} else {
389 					this.sc.updateBinary(fid, 0, cvc.getBytes());
390 				}
391 
392 				cnt++;
393 			}
394 		}
395 	}
396 
397 	for (var i = 0; i < cacertlist.length; i++) {
398 		var cvc = cacertlist[i];
399 		var fid = ByteString.valueOf((EACCryptoProvider.CVCCACERTIFICATEPREFIX << 8) + i);
400 		GPSystem.log(GPSystem.DEBUG, module.id, "Writing to " + fid + " the certificate " + cvc );
401 		this.sc.updateBinary(fid, 0, cvc.getBytes());
402 		cnt++;
403 	}
404 
405 	GPSystem.log(GPSystem.DEBUG, module.id, "Writing path " + path );
406 	var fid = ByteString.valueOf(EACCryptoProvider.PATH_EF_FID);
407 	try 	{
408 		this.sc.deleteFile(fid);
409 	}
410 	catch(e) {
411 		// Ignore
412 	}
413 
414 	this.sc.updateBinary(fid, 0, new ByteString(path, ASCII));
415 
416 	return cnt;
417 }
418