      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