      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