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