从JavaScript脚本连接到RFID / NFC(ACR122)阅读器

时间:2011-07-27 16:36:14

标签: javascript rfid

我正在使用一个从e-Passport读取数据的脚本。但是我需要一些帮助。

我的脚本来自另一个名为SmartCardShell 3的程序。这是代码:

  /**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|  
 * |#       #|  Copyright (c) 1999-2006 CardContact Software & System Consulting
 * |'##> <##'|  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<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<
// L898902C<3UTO6908061F9406236ZE184226B<<<<<14
// '-DocNo--'   '-DoB-' '-DoE-'
//var mrz2 = "L898902C<3UTO6908061F9406236ZE184226B<<<<<14";

// Harvinder Khaira MRZ:
// P<GBRKHAIRA<<HARVINDER<SINGH<<<<<<<<<<<<<<<<
// 4604503228GBR8711164M1711291<<<<<<<<<<<<<<06
var mrz2 = "4604503228GBR8711164M1711291<<<<<<<<<<<<<<06";

// MRZ of Tsukuba data set
//
// WG30004036UTO6007078M0511014<<<<<<<<<<<<<<06
// '-DocNo--'   '-DoB-' '-DoE-'

// var mrz2 = "WG30004036UTO6007078M0511014<<<<<<<<<<<<<<06";

//============================================tools.js=========================

// Import some tools
/*
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|  
 * |#       #|  Copyright (c) 1999-2006 CardContact Software & System Consulting
 * |'##> <##'|  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<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<
 *   L898902C<3UTO6908061F9406236ZE184226B<<<<<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 >> 24) & 0xFF);
    bb.append((c >> 16) & 0xFF);
    bb.append((c >>  8) & 0xFF);
    bb.append((c      ) & 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 > 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 > 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 > 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 < 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 > 0) && (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)) >= 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 > 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) << 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 >= 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");

我已将2个脚本(dumpmrtd.js和tools.js)合并在一起。

问题是在底部:

// Create card and crypto object
var card = new Card( /* somehow connect to ACR122 reader */);
card.reset(Card.RESET_COLD);

为了创建一个新的Card对象,它需要一个String阅读器。这是我的问题! 我不知道如何获得这个“读者”。

有人有什么想法吗?

提前致谢

** PS。抱歉可怕的缩进/代码报价。这是我第一次在这个网站上发布

0 个答案:

没有答案