title: Connecting to an RFID/NFC (ACR122) reader from a JavaScript script
body: <p>I'm using a script that reads the data off the e-Passport. However I need some help.</p>
  
  <p>The script I have is from another program called SmartCardShell 3. This is the code:</p>
  
  <pre><code>  /**
   *  ---------
   * |.##&gt; &lt;##.|  Open Smart Card Development Platform (www.openscdp.org)
   * |#       #|  
   * |#       #|  Copyright (c) 1999-2006 CardContact Software &amp; System Consulting
   * |'##&gt; &lt;##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
   *  --------- 
   *
   *  This file is part of OpenSCDP.
   *
   *  OpenSCDP is free software; you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License version 2 as
   *  published by the Free Software Foundation.
   *
   *  OpenSCDP is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with OpenSCDP; if not, write to the Free Software
   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
   *
   *  @fileoverview Dump the content of a passport with Basic Access Control to file
   *
   *  Before running this script, please make sure that the variable mrz2 is set
   *  to the second line of the machine readable zone on your passport.
   */
  
  // MRZ of silver data set
  //
  // P&lt;UTOERIKSSON&lt;&lt;ANNA&lt;MARIA&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
  // L898902C&lt;3UTO6908061F9406236ZE184226B&lt;&lt;&lt;&lt;&lt;14
  // '-DocNo--'   '-DoB-' '-DoE-'
  //var mrz2 = "L898902C&lt;3UTO6908061F9406236ZE184226B&lt;&lt;&lt;&lt;&lt;14";
  
  // Harvinder Khaira MRZ:
  // P&lt;GBRKHAIRA&lt;&lt;HARVINDER&lt;SINGH&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
  // 4604503228GBR8711164M1711291&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;06
  var mrz2 = "4604503228GBR8711164M1711291&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;06";
  
  // MRZ of Tsukuba data set
  //
  // WG30004036UTO6007078M0511014&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;06
  // '-DocNo--'   '-DoB-' '-DoE-'
  
  // var mrz2 = "WG30004036UTO6007078M0511014&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;06";
  
  //============================================tools.js=========================
  
  // Import some tools
  /*
   *  ---------
   * |.##&gt; &lt;##.|  Open Smart Card Development Platform (www.openscdp.org)
   * |#       #|  
   * |#       #|  Copyright (c) 1999-2006 CardContact Software &amp; System Consulting
   * |'##&gt; &lt;##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
   *  --------- 
   *
   *  This file is part of OpenSCDP.
   *
   *  OpenSCDP is free software; you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License version 2 as
   *  published by the Free Software Foundation.
   *
   *  OpenSCDP is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with OpenSCDP; if not, write to the Free Software
   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
   *
   *  Tools for accessing a ICAO conform Machine Readable Travel Document
   */
  
  
  
  /*
   * Calculate a single Basic Access Control (BAC) key from the second
   * line of the Machine Readable Zone (MRZ).
   *
   * The function extracts the Document Number, Date of Birth and Date of Expiration
   * from the second line of the machine readable zone
   *
   * E.g. MRZ of Silver Data Set
   *   P&lt;UTOERIKSSON&lt;&lt;ANNA&lt;MARIA&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
   *   L898902C&lt;3UTO6908061F9406236ZE184226B&lt;&lt;&lt;&lt;&lt;14
   *   '-DocNo--'   '-DoB-' '-DoE-'
   *
   * This extract is then hashed, concatenated with a key number and
   * hashed again.
  
   * crypto   Crypto object used for hashing
   * mrz2     String containing second line of MRZ
   * keyno    Number of key to calculate (1 for Kenc and 2 for Kmac)
   *
   * Returns  Key object
   */
  
  function calculateBACKey(crypto, mrz2, keyno) {
  
      // Convert to byte string
      var strbin = new ByteString(mrz2, ASCII);
  
      // Extract Document Number, Date of Birth and Date of Expiration
      var hash_input = strbin.bytes(0, 10);
      hash_input = hash_input.concat(strbin.bytes(13, 7));
      hash_input = hash_input.concat(strbin.bytes(21, 7));
      printlnln("Hash Input   : " + hash_input.toString(ASCII));
  
      // Hash input   
      var mrz_hash = crypto.digest(Crypto.SHA_1, hash_input);
      printlnln("MRZ Hash     : " + mrz_hash);
  
      // Extract first 16 byte and append 00000001 or 00000002
      var bb = new ByteBuffer(mrz_hash.bytes(0, 16));
      bb.append(new ByteString("000000", HEX));
      bb.append(keyno);
  
      // Hash again to calculate key value    
      var keyval = crypto.digest(Crypto.SHA_1, bb.toByteString());
      keyval = keyval.bytes(0, 16);
      printlnln("Value of Key : " + keyval);
      var key = new Key();
      key.setComponent(Key.DES, keyval);
  
      return key;
  }
  
  
  
  // The SecureChannel object is required as a credential for the CardFile objects
  // SecureChannel objects must at least implement a wrap() method. The unwrap()
  // method is optional, but called when defined
  
  function SecureChannel(crypto, kenc, kmac, ssc) {
      this.crypto = crypto;
      this.kenc = kenc;
      this.kmac = kmac;
      this.ssc = ssc;
      this.trace = false;
  }
  
  
  
  SecureChannel.prototype.enableTrace = function () {
      this.trace = true;
  }
  
  
  
  //
  // Increment send sequence counter
  //
  SecureChannel.prototype.incssc = function () {
      var c = this.ssc.bytes(4, 4).toUnsigned() + 1;
      bb = new ByteBuffer(this.ssc.bytes(0, 4));
      bb.append((c &gt;&gt; 24) &amp; 0xFF);
      bb.append((c &gt;&gt; 16) &amp; 0xFF);
      bb.append((c &gt;&gt;  8) &amp; 0xFF);
      bb.append((c      ) &amp; 0xFF);
      this.ssc = bb.toByteString();
  }
  
  
  
  //
  // Wrap command-APDU with secure messaging
  //
  SecureChannel.prototype.wrap = function(apduToWrap) {
      if (this.trace) {
          printlnln("Command-APDU to wrap :");
          printlnln(apduToWrap);
      }
  
      var b = new ByteBuffer();
      var macb = new ByteBuffer();
  
      // Transform CLA byte and add header    
      var cla = apduToWrap.byteAt(0);
      cla |= 0x0C;
      b.append(cla);
      b.append(apduToWrap.bytes(1, 3));
  
      this.incssc();
      macb.append(this.ssc);
      macb.append(b.toByteString().pad(Crypto.ISO9797_METHOD_2));
  
      var do87 = null;
  
      var le = apduToWrap.bytes(apduToWrap.length - 1, 1);
  
      if (apduToWrap.length &gt; 5) {
          var lc = apduToWrap.byteAt(4);
          var plain = apduToWrap.bytes(5, lc);
          plain = plain.pad(Crypto.ISO9797_METHOD_2);
          if (this.trace) {
              printlnln("Input to cipher:");
              printlnln(plain);
          }
  
          var cipher = this.crypto.encrypt(this.kenc, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX));
          do87 = new ByteString("01", HEX);
          do87 = do87.concat(cipher);
          do87 = new TLV(0x87, do87, TLV.EMV);
          do87 = do87.getTLV();
  
          macb.append(do87);
  
          if (apduToWrap.length == 5 + lc) {
              le = new ByteString("", HEX);
          }
      } else if (apduToWrap.length == 4) {
          le = new ByteString("", HEX);
      }
  
      var do97;
      if (le.length &gt; 0) {    
          do97 = new ByteString("9701", HEX);
          do97 = do97.concat(le);
          macb.append(do97);
      } else {
          do97 = new ByteString("", HEX);
      }
  
      if (this.trace) {
          printlnln("Input to MAC calculation :");
      }
  
      var macinput = macb.toByteString().pad(Crypto.ISO9797_METHOD_2);
      if (this.trace) {
          printlnln(macinput);
      }
  
      var mac = this.crypto.sign(this.kmac, Crypto.DES_MAC_EMV, macinput);
      if (this.trace) {
          printlnln("Calculated MAC :");
          printlnln(mac);
      }
  
      var macdo = new ByteString("8E08", HEX);
      macdo = macdo.concat(mac);
  
      if (do87 != null) {
          b.append(do87.length + do97.length + macdo.length);
          b.append(do87);
      } else {
          b.append(do97.length + macdo.length);
      }
  
      b.append(do97);
      b.append(macdo);
  
      if (le.length &gt; 0) {
          b.append(0);
      }
  
      if (this.trace) {
          printlnln("Wrapped Command-APDU :");
          printlnln(b.toByteString());
      }
  
      return(b.toByteString());
  }
  
  
  
  //
  // Unwrap response-APDU with secure messaging
  //
  SecureChannel.prototype.unwrap = function(apduToUnwrap) {
      if (this.trace) {
          printlnln("Response-APDU to unwrap :");
          printlnln(apduToUnwrap);
      }
  
      if (apduToUnwrap.length == 2) {
          return(apduToUnwrap);
      }
  
      var b = new ByteBuffer();
      var macb = new ByteBuffer();
  
      this.incssc();
  
      macb.append(this.ssc);
  
      var tl = new TLVList(apduToUnwrap.left(apduToUnwrap.length - 2), TLV.EMV);
  
      var mac = null;
      for (i = 0; i &lt; tl.length; i++) {
          var t = tl.index(i);
  
          if (t.getTag() == 0x8E) {
              mac = t.getValue();
          } else {
              macb.append(t.getTLV());
          }
      }
  
      if (mac == null) {
          throw new GPError("SecureChannelCredential", GPError.OBJECT_NOT_FOUND, 0, "MAC data object missing");
      }
  
      if (this.trace) {
          printlnln(macb.toByteString());
      }
  
      if (!this.crypto.verify(this.kmac, Crypto.DES_MAC_EMV, macb.toByteString().pad(Crypto.ISO9797_METHOD_2), mac)) {
          throw new GPError("SecureChannelCredential", GPError.CRYPTO_FAILED, 0, "MAC verification failed");
      }
  
      var t = tl.find(0x87);
      if (t != null) {
          var cryptogram = t.getValue();
          var padding = cryptogram.byteAt(0);
          cryptogram = cryptogram.right(cryptogram.length - 1);
  
          if (padding != 0x01) {
              throw new GPError("SecureChannelCredential", GPError.INVALID_MECH, padding, "Unsupported padding mode " + padding + " in cryptogram");
          }
  
          var plain = this.crypto.decrypt(this.kenc, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX));
          for (i = plain.length - 1; (i &gt; 0) &amp;&amp; (plain.byteAt(i) != 0x80); i--);
  
          b.append(plain.left(i));
      }
  
      var t = tl.find(0x81);
  
      if (t != null) {
          b.append(t.getValue());
      }
  
      var t = tl.find(0x99);
      if (t == null) {
          b.append(apduToUnwrap.right(2));
      } else {
          b.append(t.getValue());
      }
  
      if (this.trace) {
          printlnln("Unwrapped Response-APDU :");
          printlnln(b.toByteString());
      }
      return(b.toByteString());
  }
  
  
  /*
   * Open secure channel using basic access control keys
   *
   * card     Card object for access to passport
   * crypto   Crypto object to be used for cryptographic operations
   * kenc     Kenc key
   * kmac     Kmac key
   *
   * Returns  Open secure channel object
   */
  
  function openSecureChannel(card, crypto, kenc, kmac) {
  
      // Perform mutual authentication procedure
      printlnln("Performing mutual authentication");
      var rndicc = card.sendApdu(0x00, 0x84, 0x00, 0x00, 0x08, [0x9000]);
      //printlnln("======RNDicc : " + rndicc);
      var rndifd = crypto.generateRandom(8);
      //println("======RNDifd : " + rndifd);
      var kifd = crypto.generateRandom(16);
  
      var plain = rndifd.concat(rndicc).concat(kifd);
      println("Plain Block  : " + plain);
  
      var cryptogram = crypto.encrypt(kenc, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX));
      println("Cryptogram   : " + cryptogram);
  
      var mac = crypto.sign(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2));
      println("MAC          : " + mac);
  
      var autresp = card.sendApdu(0x00, 0x82, 0x00, 0x00, cryptogram.concat(mac), 0);
  
      if (card.SW != 0x9000) {
          println("Mutual authenticate failed with " + card.SW.toString(16) + " \"" + card.SWMSG + "\". MRZ correct ?");
          throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card did not accept MAC");
      }
  
      println("Response     : " + autresp);
  
      cryptogram = autresp.bytes(0, 32);
      mac = autresp.bytes(32, 8);
  
      if (!crypto.verify(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2), mac)) {
          throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card MAC did not verify correctly");
      }
  
      plain = crypto.decrypt(kenc, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX));
      println("Plain Block  : " + plain);
      //var iccifd = rndicc.concat(rndifd);
      //println ("==RNDicc + RNDifd : " + iccifd);
  
      if (!plain.bytes(0, 8).equals(rndicc)) {
          throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.ICC");
      }
  
      if (!plain.bytes(8, 8).equals(rndifd)) {
          throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.IFD");
      }
  
      var kicc = plain.bytes(16, 16);
      keyinp = kicc.xor(kifd);
  
      var hashin = keyinp.concat(new ByteString("00000001", HEX));
      var kencval = crypto.digest(Crypto.SHA_1, hashin);
      kencval = kencval.bytes(0, 16);
      println("Kenc         : " + kencval);
      var kenc = new Key();
      kenc.setComponent(Key.DES, kencval);
  
      var hashin = keyinp.concat(new ByteString("00000002", HEX));
      var kmacval = crypto.digest(Crypto.SHA_1, hashin);
      kmacval = kmacval.bytes(0, 16);
      println("Kmac         : " + kmacval);
      var kmac = new Key();
      kmac.setComponent(Key.DES, kmacval);
  
      var ssc = rndicc.bytes(4, 4).concat(rndifd.bytes(4, 4));
      println("SSC          : " + ssc);
  
  // Disable to use script-secure messaging secure messaging
      var sc = new IsoSecureChannel(crypto);
      sc.setEncKey(kenc);
      sc.setMacKey(kmac);
      sc.setSendSequenceCounter(ssc);
      return sc;
  //
  
  // Enable to use script-secure messaging secure messaging
  //  return new SecureChannel(crypto, kenc, kmac, ssc);
  //
  }
  
  
  
  /*
   * Write a byte string object to file
   *
   * The filename is mapped to the location of the script
   *
   * name     Name of file
   * content  ByteString content for file
   *
   */
  
  function writeFileOnDisk(name, content) {
  
      // Map filename
      var filename = GPSystem.mapFilename(name, GPSystem.USR);
  
      println("Writing " + filename);
  
      var file = new java.io.FileOutputStream(filename);
      file.write(content);
      file.close();
  }
  
  
  
  /*
   * Read a byte string object from file
   *
   * The filename is mapped to the location of the script
   *
   * name     Name of file
   *
   */
  
  function readFileFromDisk(name) {
  
      // Map filename
      var filename = GPSystem.mapFilename(name, GPSystem.USR);
      println("Reading " + filename);
  
      var file = new java.io.FileInputStream(filename);
  
      var content = new ByteBuffer();
      var buffer = new ByteString("                                                                                                                                                                                                                                                                ", ASCII);
      var len;
  
      while ((len = file.read(buffer)) &gt;= 0) {
          content.append(buffer.bytes(0, len));
      }
  
      file.close();
      return(content.toByteString());
  }
  
  
  /*
   * Extract the length of the file from the TLV encoding at the beginning of the
   * file
   *
   * header   First bytes read from file
   *
   * Return   Total length of TLV object
   */
  
  function lengthFromHeader(header) {
      var value;
  
      value = header.byteAt(1);
  
      if (value &gt; 0x82) {
          throw new GPError("lengthfromheader()", GPError.INVALID_DATA, value, "");
      }
  
      switch(value) {
          case 0x81:
              value = header.byteAt(2) + 1;
              break;
          case 0x82:
              value = (header.byteAt(2) &lt;&lt; 8) + header.byteAt(3) + 2;
              break;
      }
      return value + 2;
  }
  
  //===========================================end of tools.js===================
  
  /*
   * Read file from passport and save to disk
   *
   */
  function handleFile(secureChannel, lds, name, fid) {
      printlnln("Reading " + name + " (" + fid + ")...");
  
      // Select file
      var ef = new CardFile(lds, ":" + fid);
  
      if (secureChannel) {    
          // Set secure channel as credential for read access
          ef.setCredential(CardFile.READ, Card.ALL, secureChannel);
      }
  
      // Read first 4 bytes of file
      var res = ef.readBinary(0, 4);
  
      // Determine file length from TLV header
      var len = lengthFromHeader(res);
  
      // Read complete file
      var res = ef.readBinary(0, len);
      printlnln("Content");
      printlnln(res);
  
      writeFileOnDisk(name + ".bin", res);
  
      return res;
  }
  
  
  
  /*
   * Save picture from DG2
   *
   */
  function savePicture(dg2) {
      // Save picture to .jpeg file
      var tlv = new ASN1(dg2);
      var bin = tlv.get(0).get(1).get(1).value;
      var offset = bin.find(new ByteString("FFD8", HEX));
  
      if (offset &gt;= 0) {
          writeFileOnDisk("face.jpg", bin.bytes(offset));
      }
  }
  
  
  // Create card and crypto object
  var card = new Card( /* somehow connect to ACR122 reader */);
  card.reset(Card.RESET_COLD);
  
  var crypto = new Crypto();
  
  // Select LDS application
  var lds = new CardFile(card, "#A0000002471001");
  
  var secureChannel = null;
  
  // Try reading EF_COM to figure out if BAC is needed
  card.sendApdu(0x00, 0xB0, 0x9E, 0x00, 0x01);
  
  if (card.SW != 0x9000) {
  
      // Calculate kenc and kmac for mutual authentication from the MRZ data
      printlnln("Trying BAC with MRZ2=" + mrz2);
  
      var kenc = calculateBACKey(crypto, mrz2, 1);
      var kmac = calculateBACKey(crypto, mrz2, 2);
  
      // Dummy to load crypto libraries (Saves some time later)
      crypto.encrypt(kenc, Crypto.DES_CBC, new ByteString("0000000000000000", HEX), new ByteString("0000000000000000", HEX));
  
      secureChannel = openSecureChannel(card, crypto, kenc, kmac);
  
      /* Only works with script based secure messaging. See tools.js for details
      secureChannel.enableTrace();
      */
  
      // Enable SELECT commands to be send in secure messaging
      // lds.setCredential(CardFile.SELECT, CardFile.ALL, secureChannel);
  
      /*
      card.setCredential(secureChannel);
      var resp = card.sendSecMsgApdu(Card.ALL, 0x00, 0xA4, 0x02, 0x0C, new ByteString("011E", HEX));
      println(resp);
      */
      }
      handleFile(secureChannel, lds, "EF_COM", "1E");
      handleFile(secureChannel, lds, "EF_DG1", "01");
      var dg2 = handleFile(secureChannel, lds, "EF_DG2", "02");
      savePicture(dg2);
      handleFile(secureChannel, lds, "EF_SOD", "1D");
  </code></pre>
  
  <p>I have incorporated 2 scripts (dumpmrtd.js and tools.js) together.</p>
  
  <p>The issue is that towards the bottom where:</p>
  
  <pre><code>// Create card and crypto object
  var card = new Card( /* somehow connect to ACR122 reader */);
  card.reset(Card.RESET_COLD);
  </code></pre>
  
  <p>In order to create a new Card object, it requires a String reader. This is my problem!
  I do not know how to obtain this "reader".</p>
  
  <p>Anyone have any ideas?</p>
  
  <p>Thanks in advance</p>
  
  <p>**PS. sorry for the terrible indentation/code quotation. This is the first time im actually posting on this website</p>