1 /** 2 * --------- 3 * |.##> <##.| SmartCard-HSM Support Scripts 4 * |# #| 5 * |# #| Copyright (c) 2011-2012 CardContact Software & System Consulting 6 * |'##> <##'| Andreas Schwier, 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 Public Key Authentication Support 25 */ 26 27 var CVC = require("scsh/eac/CVC").CVC; 28 var PublicKeyReference = require("scsh/eac/PublicKeyReference").PublicKeyReference; 29 30 31 32 /** 33 * Manage Public Key Authentication 34 * 35 * @class Class providing support for public key authentication 36 * @constructor 37 * @param {SmartCardHSM} sc the SmartCard-HSM used as target for PKA 38 * @param {ByteString} deviceId the device id as returned by getCHR().getBytes() for the device certificate 39 */ 40 function ManagePKA(sc, deviceId) { 41 42 this.sc = sc; 43 this.deviceId = deviceId; 44 this.card = sc.card; 45 46 this.numberOfPublicKeys = 0; // Dummy 47 this.missingPublicKeys = 0; 48 this.requiredPublicKeysForAuthentication = 0; 49 this.authenticatedPublicKeys = 0; 50 } 51 52 exports.ManagePKA = ManagePKA; 53 54 55 56 /** 57 * Update internal status 58 * @private 59 */ 60 ManagePKA.prototype.updateStatus = function(status) { 61 this.numberOfPublicKeys = status.byteAt(0); 62 this.missingPublicKeys = status.byteAt(1); 63 this.requiredPublicKeysForAuthentication = status.byteAt(2); 64 this.authenticatedPublicKeys = status.byteAt(3); 65 } 66 67 68 69 /** 70 * Check if public key authentication is active for device 71 * 72 * @type boolean 73 * @return true if supported and active 74 */ 75 ManagePKA.prototype.isActive = function() { 76 var status = this.card.sendSecMsgApdu(Card.ALL, 0x80, 0x54, 0x00, 0x00, 0); 77 if (this.card.SW != 0x9000) { 78 return false; 79 } 80 this.updateStatus(status); 81 return true; 82 } 83 84 85 86 /** 87 * Check if registered public keys can be enumerated 88 * 89 * @type boolean 90 * @return true if enumeration is supported 91 */ 92 ManagePKA.prototype.canEnumeratePublicKeys = function() { 93 this.card.sendSecMsgApdu(Card.ALL, 0x80, 0x54, 0x02, 0x00, 0, [0x9000, 0x9001, 0x6A88, 0x6A86 ] ); 94 return this.card.SW != 0x6A86; 95 } 96 97 98 99 ManagePKA.KEY_PRESENT = 1; 100 ManagePKA.KEY_NOT_FOUND = 2; 101 ManagePKA.KEY_ALREADY_AUTHENTICATED = 3; 102 103 /** 104 * Check status of public key 105 * 106 * @param {PublicKeyReference} chr the public key reference under which the public key is registered 107 * @type Number 108 * @return ManagePKA.KEY_PRESENT if key is registered in SmartCard-HSM, ManagePKA.KEY_NOT_FOUND if not. 109 * ManagePKA.KEY_ALREADY_AUTHENTICATED is returned if the public key has already been authenticated 110 */ 111 ManagePKA.prototype.checkKeyStatus = function(chr) { 112 var pukrefdo = new ASN1(0x83, chr.getBytes()); 113 var pukref = pukrefdo.getBytes(); 114 115 this.card.sendSecMsgApdu(Card.ALL, 0x00, 0x22, 0x81, 0xA4, pukref, [0x9000, 0x6A88, 0x6985 ]); 116 117 switch(this.card.SW) { 118 case 0x9000: return ManagePKA.KEY_PRESENT; 119 case 0x6A88: return ManagePKA.KEY_NOT_FOUND; 120 case 0x6985: return ManagePKA.KEY_ALREADY_AUTHENTICATED; 121 } 122 } 123 124 125 126 /** 127 * Perform authentication with source SmartCard-HSM and key on that device 128 * 129 * @param {SmartCardHSM} srcsc the SmartCard-HSM containing the private key for authentication 130 * @param {Key} key the private key 131 */ 132 ManagePKA.prototype.performAuthentication = function(srcsc, key) { 133 var challenge = this.card.sendSecMsgApdu(Card.ALL, 0x00, 0x84, 0x00, 0x00, 8, [0x9000]); 134 135 var input = this.deviceId.concat(challenge); 136 137 var crypto = srcsc.getCrypto(); 138 139 var signature = crypto.sign(key, Crypto.ECDSA_SHA256, input); 140 141 signature = CVC.unwrapSignature(signature, 32); 142 this.card.sendSecMsgApdu(Card.ALL, 0x00, 0x82, 0x00, 0x00, signature, [0x9000]); 143 144 var status = this.card.sendSecMsgApdu(Card.ALL, 0x80, 0x54, 0x00, 0x00, 0, [0x9000] ); 145 this.updateStatus(status); 146 } 147 148 149 150 /** 151 * Get number of public keys (m) 152 * 153 * @type Number 154 * @return the number of public keys (m) 155 */ 156 ManagePKA.prototype.getNumberOfPublicKeys = function() { 157 return this.numberOfPublicKeys; 158 } 159 160 161 162 /** 163 * Get number of public keys missing for complete setup 164 * 165 * @type Number 166 * @return the number of public keys missing for complete setup 167 */ 168 ManagePKA.prototype.getMissingPublicKeys = function() { 169 return this.missingPublicKeys; 170 } 171 172 173 174 /** 175 * Get number of keys required for successfull authentication (n) 176 * 177 * @type Number 178 * @return the number of keys required for successfull authentication (n) 179 */ 180 ManagePKA.prototype.getRequiredPublicKeysForAuthentication = function() { 181 return this.requiredPublicKeysForAuthentication; 182 } 183 184 185 /** 186 * Get number of keys already authenticated in this session 187 * 188 * @type Number 189 * @return the number of keys already authenticated in this session 190 */ 191 ManagePKA.prototype.getAuthenticatedPublicKeys = function() { 192 return this.authenticatedPublicKeys; 193 } 194 195 196 197 /** 198 * Describe current status in human readable form 199 * 200 * @type String 201 * @return the status 202 */ 203 ManagePKA.prototype.describeStatus = function() { 204 if (this.missingPublicKeys) { 205 return "" + this.missingPublicKeys + " missing key(s) in " + this.requiredPublicKeysForAuthentication + " of " + this.numberOfPublicKeys + " public key authentication"; 206 } else { 207 return "" + this.authenticatedPublicKeys + " authenticated public key(s) in " + this.requiredPublicKeysForAuthentication + " of " + this.numberOfPublicKeys + " scheme"; 208 } 209 } 210 211 212 213 /** 214 * Enumerate names (CHR) or registered public keys 215 * 216 * @type Object 217 * @return Object with property chr containing the PublicKeyReference and with property 218 * status containing ManagePKA.KEY_PRESENT if a key is registered at that slot, 219 * ManagePKA.KEY_NOT_FOUND if not. ManagePKA.KEY_ALREADY_AUTHENTICATED is returned 220 * if the public key has already been authenticated. 221 */ 222 ManagePKA.prototype.enumeratePublicKeys = function() { 223 var list = []; 224 225 for (var i = 0; i < this.numberOfPublicKeys; i++) { 226 var bin = this.card.sendSecMsgApdu(Card.ALL, 0x80, 0x54, 0x02, i, 0, [0x9000, 0x9001, 0x6A88 ] ); 227 228 if (this.card.SW == 0x6A88) { 229 list.push( { status: ManagePKA.KEY_NOT_FOUND } ); 230 } else { 231 var chr = new PublicKeyReference(bin); 232 if (this.card.SW == 0x9000) { 233 status = ManagePKA.KEY_PRESENT; 234 } else { 235 status = ManagePKA.KEY_ALREADY_AUTHENTICATED; 236 } 237 list.push( { chr: chr, status: status} ); 238 } 239 } 240 return list; 241 } 242 243 244 245 /** 246 * Validate and register public key 247 * 248 * @param {CVC} pk public key in CSR format from card 249 * @param {CVC} devcert device certificate 250 * @param {CVC} dicacert device issuer certificate 251 * @param {Number} replace the id of the key to be replaced (0-based) or undefined 252 */ 253 ManagePKA.prototype.registerPublicKey = function(pk, devcert, dicacert, replace) { 254 if (pk.getPublicKey().getSize() != 256) { 255 throw new GPError(module.id, GPError.INVALID_DATA, 0, "Authentication key must be a 256 bit EC key"); 256 } 257 258 this.sc.verifyCertificate(dicacert); 259 this.sc.verifyCertificate(devcert); 260 261 var car = pk.getOuterCAR().getBytes(); 262 263 var pukrefdo = new ASN1(0x83, car); 264 var pukref = pukrefdo.getBytes(); 265 266 this.card.sendSecMsgApdu(Card.ALL, 0x00, 0x22, 0x81, 0xB6, pukref, [0x9000]); 267 268 // Extract value of 67 269 var tl = new TLVList(pk.getBytes(), TLV.EMV); 270 var t = tl.index(0); 271 var v = t.getValue(); 272 273 if (typeof(replace) == "number") { 274 var p1 = 1; 275 var p2 = replace; 276 } else { 277 var p1 = 0; 278 var p2 = 0; 279 } 280 281 var status = this.card.sendSecMsgApdu(Card.ALL, 0x80, 0x54, p1, p2, v, 0, [0x9000] ); 282 this.updateStatus(status); 283 } 284