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