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