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