1 /**
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|
  5  * |#       #|  Copyright (c) 1999-2006 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 Remote Application Management over HTTP (RAMoverHTTP) client
 25  */
 26 
 27 /**
 28  * Create a client instance for attaching a card to a server via the RAMOverHTTP procotol
 29  *
 30  * @Class Class implementing a RAMOverHTTP client
 31  * @constructor
 32  * @param {Card} card the card instance to attach
 33  * @param {String} url the URL to connect to
 34  * @param {String} session the session cookie
 35  */
 36 function RAMClient(card, url, session) {
 37 	this.card = card;
 38 	this.url = url;
 39 	this.session = session;
 40 	this.httpCode = -1;
 41 	this.apduCount = 0;
 42 	this.aPDULimit = 100000;
 43 	this.httpcount = 0;
 44 	this.httplimit = 100000;
 45 	this.forceFailSW = 0;
 46 	this.returnAllSW = false;
 47 }
 48 
 49 
 50 
 51 exports.RAMClient = RAMClient;
 52 
 53 
 54 
 55 /**
 56  * Prepend a 0, if the encoded integer is interpreted as negative value otherwise
 57  */
 58 RAMClient.asSignedInteger = function(val) {
 59 	var enc = ByteString.valueOf(val);
 60 	if (enc.byteAt(0) >= 0x80) {
 61 		enc = ByteString.valueOf(0).concat(enc);
 62 	}
 63 	return enc;
 64 }
 65 
 66 
 67 
 68 /**
 69  * @private
 70  */
 71 RAMClient.prototype.encodeInitiation = function(atr) {
 72 	var pit = new ASN1(0xE8, new ASN1(0xC0, atr));
 73 	if (typeof(this.maxCAPDUSize) == "number") {
 74 		pit.add(new ASN1(0xC1, RAMClient.asSignedInteger(this.maxCAPDUSize)));
 75 	}
 76 	if (typeof(this.maxRAPDUSize) == "number") {
 77 		pit.add(new ASN1(0xC2, RAMClient.asSignedInteger(this.maxRAPDUSize)));
 78 	}
 79 	return pit.getBytes();
 80 }
 81 
 82 
 83 
 84 /**
 85  * Set a notification listener.
 86  *
 87  * The listener object must implement a notificationReceived() method that
 88  * receives the three parameter id, msg and ttc.
 89  *
 90  * @param {Object} the listener object implementing notificationReceived()
 91  */
 92 RAMClient.prototype.setNotificationListener = function(notificationListener) {
 93 	this.notificationListener = notificationListener;
 94 }
 95 
 96 
 97 
 98 /**
 99  * Set a limiting number of APDUs
100  *
101  * This is helpful for testing to intercept processing after a certain number
102  * of APDUs (Default 100000).
103  *
104  * @param {Number} aPDULimit the maximum number of APDU processing in update()
105  * @param {Number} forceFailSW the final SW1/SW2
106  */
107 RAMClient.prototype.setAPDULimit = function(aPDULimit, forceFailSW) {
108 	this.aPDULimit = aPDULimit;
109 	this.forceFailSW = forceFailSW;
110 }
111 
112 
113 
114 /**
115  * Set a limiting number of HTTP roundtrips
116  *
117  * This is helpful for testing to intercept processing after a certain number
118  * of HTTP roundtrips (Default 100000).
119  *
120  * @param {Number} aPDULimit the maximum number of APDU processing in update()
121  * @param {Number} forceFailSW the final SW1/SW2
122  */
123 RAMClient.prototype.setHTTPLimit = function(httplimit) {
124 	this.httplimit = httplimit;
125 }
126 
127 
128 
129 /**
130  * Set a maximum command and response APDU size
131  *
132  * @param {Number} maxCAPDUSize The maximum number of bytes in the command APDU
133  * @param {Number} maxRAPDUSize The maximum number of bytes in the response APDU
134  */
135 RAMClient.prototype.setMaxAPDUSize = function(maxCAPDUSize, maxRAPDUSize) {
136 	this.maxCAPDUSize = maxCAPDUSize;
137 	this.maxRAPDUSize = maxRAPDUSize;
138 }
139 
140 
141 
142 /**
143  * @private
144  */
145 RAMClient.prototype.getURLConnection = function() {
146 	if (typeof(this.connectionFactory) != "undefined") {
147 		c = this.connectionFactory.getConnection(this.url, this.session);
148 	} else {
149 		var c = new URLConnection(this.url);
150 		if (typeof(this.session) != "undefined") {
151 //			print(typeof(this.session));
152 			c.addHeaderField("Cookie", this.session);
153 		}
154 	}
155 	c.addHeaderField("Content-Type", "application/org.openscdp-content-mgt-response;version=1.0");
156 	return c;
157 }
158 
159 
160 
161 /**
162  * @private
163  */
164 RAMClient.prototype.initialConnect = function() {
165 	var c = this.getURLConnection();
166 	var atr = this.card.getATR();
167 	var b = this.encodeInitiation(atr.toByteString());
168 	var cst = c.postBinary(b);
169 
170 	var cookie = c.getHeaderField("Set-Cookie");
171 	if (cookie) {
172 //		print(cookie);
173 		this.session = cookie[0];
174 	}
175 
176 	this.httpCode = c.responseCode;
177 	return cst;
178 }
179 
180 
181 
182 /**
183  * @private
184  */
185 RAMClient.prototype.next = function(rst) {
186 	var c = this.getURLConnection();
187 	var cst = c.postBinary(rst);
188 
189 	this.httpCode = c.responseCode;
190 	return cst;
191 }
192 
193 
194 
195 /**
196  * @private
197  */
198 RAMClient.prototype.processAPDU = function(capdu) {
199 	var rapdu = this.card.plainApdu(capdu);
200 	rapdu = rapdu.concat(ByteString.valueOf(this.card.SW, 2));
201 	this.apduCount++;
202 
203 	if ((this.apduCount >= this.aPDULimit) && this.forceFailSW) {
204 		rapdu = ByteString.valueOf(this.forceFailSW, 2);
205 	}
206 	return new ASN1(0x23, rapdu);
207 }
208 
209 
210 
211 /**
212  * @private
213  */
214 RAMClient.prototype.processReset = function() {
215 	var atr = this.card.reset(Card.RESET_COLD);
216 	if (atr instanceof Atr) {
217 		atr = atr.toByteString();
218 	}
219 	return new ASN1(0xC0, atr);
220 }
221 
222 
223 
224 /**
225  * @private
226  */
227 RAMClient.prototype.processNotify = function(e) {
228 	if (this.notificationListener) {
229 		var id = e.get(0).value.toSigned();
230 		var str = e.get(1).value.toString(UTF8);
231 		var ttc = undefined;
232 		if (e.elements > 2) {
233 			ttc = e.get(0).value.toUnsigned();
234 		}
235 		this.notificationListener.notificationReceived(id, str, ttc);
236 	} else {
237 		print("Notify:");
238 		print(e);
239 	}
240 }
241 
242 
243 
244 RAMClient.SW9000 = new ByteString("9000", HEX);
245 
246 /**
247  * @private
248  */
249 RAMClient.prototype.process = function(cst) {
250 //	print(cst);
251 	var a = new ASN1(cst);
252 //	print(a);
253 
254 	var rst = new ASN1(0xAB);
255 
256 	var cnt = 0;
257 	var rsp;
258 
259 	for (var i = 0; (i < a.elements); i++) {
260 		var e = a.get(i);
261 		switch(e.tag) {
262 			case 0x22:
263 				// Abort processing of APDU sequence if SW != 9000
264 				if (rsp && !rsp.value.right(2).equals(RAMClient.SW9000)) {
265 					print("Abort sequence");
266 					print(rsp);
267 					i = a.elements;
268 					break;
269 				}
270 				rsp = this.processAPDU(e.value);
271 				if (this.returnAllSW) {
272 					rst.add(rsp);
273 				}
274 				cnt++;
275 				break;
276 			case 0xC0:
277 				rst.add(this.processReset());
278 				break;
279 			case 0xE0:
280 				this.processNotify(e);
281 				break;
282 		}
283 	}
284 
285 	if (rsp && !this.returnAllSW) {
286 		rst.add(rsp);
287 	}
288 	rst.add(new ASN1(0x80, RAMClient.asSignedInteger(cnt)));
289 //	print(rst);
290 //	print(rst.getBytes());
291 	return rst.getBytes();
292 }
293 
294 
295 
296 /**
297  * Close connection, e.g. after exception on the client side
298  *
299  * @param {Number} id the message id
300  * @param {String} msg the closing message
301  */
302 RAMClient.prototype.close = function(id, msg) {
303 	var c = this.getURLConnection();
304 
305 	var rst = new ASN1(0xAB,
306 			new ASN1(0xE1,
307 				 new ASN1(ASN1.INTEGER, RAMClient.asSignedInteger(id)),
308 				 new ASN1(ASN1.UTF8String, new ByteString(msg, UTF8))
309 			    )
310 		      ).getBytes();
311 
312 	var cst = this.next(rst);
313 	while (this.httpCode == 200) {
314 		var rst = this.process(cst);
315 		cst = this.next(rst);
316 	}
317 }
318 
319 
320 
321 /**
322  * Perform update operation
323  */
324 RAMClient.prototype.update = function() {
325 	this.httpCode = -1;
326 	this.apduCount = 0;
327 	var cst = this.initialConnect();
328 	while ((this.httpCode == 200) && (this.httpcount < this.httplimit)) {
329 		this.httpcount++;
330 		var rst = this.process(cst);
331 		cst = this.next(rst);
332 	}
333 }
334