Home | History | Annotate | Download | only in cryptotoken
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview usbGnubby methods related to U2F support.
      7  */
      8 'use strict';
      9 
     10 /** Enroll */
     11 usbGnubby.U2F_ENROLL = 0x01;
     12 /** Request signature */
     13 usbGnubby.U2F_SIGN = 0x02;
     14 /** Request protocol version */
     15 usbGnubby.U2F_VERSION = 0x03;
     16 
     17 /** Request applet version */
     18 usbGnubby.APPLET_VERSION = 0x11;  // First 3 bytes are applet version.
     19 
     20 // APDU.P1 flags
     21 /** Test of User Presence required */
     22 usbGnubby.P1_TUP_REQUIRED = 0x01;
     23 /** Consume a Test of User Presence */
     24 usbGnubby.P1_TUP_CONSUME = 0x02;
     25 /** Test signature only, no TUP. E.g. to check for existing enrollments. */
     26 usbGnubby.P1_TUP_TESTONLY = 0x04;
     27 /** Attest with device key */
     28 usbGnubby.P1_INDIVIDUAL_KEY = 0x80;
     29 
     30 // Version values
     31 /** V1 of the applet. */
     32 usbGnubby.U2F_V1 = 'U2F_V1';
     33 /** V2 of the applet. */
     34 usbGnubby.U2F_V2 = 'U2F_V2';
     35 
     36 /** Perform enrollment
     37  * @param {ArrayBuffer|Uint8Array} challenge Enrollment challenge
     38  * @param {ArrayBuffer|Uint8Array} appIdHash Hashed application id
     39  * @param {function(...)} cb Result callback
     40  */
     41 usbGnubby.prototype.enroll = function(challenge, appIdHash, cb) {
     42   var apdu = new Uint8Array(
     43       [0x00,
     44        usbGnubby.U2F_ENROLL,
     45        usbGnubby.P1_TUP_REQUIRED | usbGnubby.P1_TUP_CONSUME |
     46          usbGnubby.P1_INDIVIDUAL_KEY,
     47        0x00, 0x00, 0x00,
     48        challenge.length + appIdHash.length]);
     49   // TODO: only use P1_INDIVIDUAL_KEY for corp appIdHashes.
     50   var u8 = new Uint8Array(apdu.length + challenge.length +
     51       appIdHash.length + 2);
     52   for (var i = 0; i < apdu.length; ++i) u8[i] = apdu[i];
     53   for (var i = 0; i < challenge.length; ++i) u8[i + apdu.length] =
     54     challenge[i];
     55   for (var i = 0; i < appIdHash.length; ++i) {
     56     u8[i + apdu.length + challenge.length] = appIdHash[i];
     57   }
     58   this.apduReply_(u8.buffer, cb);
     59 };
     60 
     61 /** Request signature
     62  * @param {ArrayBuffer|Uint8Array} challengeHash Hashed signature challenge
     63  * @param {ArrayBuffer|Uint8Array} appIdHash Hashed application id
     64  * @param {ArrayBuffer|Uint8Array} keyHandle Key handle to use
     65  * @param {function(...)} cb Result callback
     66  * @param {boolean=} opt_nowink Request signature without winking
     67  *     (e.g. during enroll)
     68  */
     69 usbGnubby.prototype.sign = function(challengeHash, appIdHash, keyHandle, cb,
     70                                     opt_nowink) {
     71   var self = this;
     72   // The sign command's format is ever-so-slightly different between V1 and V2,
     73   // so get this gnubby's version prior to sending it.
     74   this.version(function(rc, opt_data) {
     75     if (rc) {
     76       cb(rc);
     77       return;
     78     }
     79     var version = UTIL_BytesToString(new Uint8Array(opt_data || []));
     80     var apduDataLen =
     81       challengeHash.length + appIdHash.length + keyHandle.length;
     82     if (version != usbGnubby.U2F_V1) {
     83       // The V2 sign command includes a length byte for the key handle.
     84       apduDataLen++;
     85     }
     86     var apdu = new Uint8Array(
     87         [0x00,
     88          usbGnubby.U2F_SIGN,
     89          usbGnubby.P1_TUP_REQUIRED | usbGnubby.P1_TUP_CONSUME,
     90          0x00, 0x00, 0x00,
     91          apduDataLen]);
     92     if (opt_nowink) {
     93       // A signature request that does not want winking.
     94       // These are used during enroll to figure out whether a gnubby was already
     95       // enrolled.
     96       // Tell applet to not actually produce a signature, even
     97       // if already touched.
     98       apdu[2] |= usbGnubby.P1_TUP_TESTONLY;
     99     }
    100     var u8 = new Uint8Array(apdu.length + apduDataLen + 2);
    101     for (var i = 0; i < apdu.length; ++i) u8[i] = apdu[i];
    102     for (var i = 0; i < challengeHash.length; ++i) u8[i + apdu.length] =
    103       challengeHash[i];
    104     for (var i = 0; i < appIdHash.length; ++i) {
    105       u8[i + apdu.length + challengeHash.length] = appIdHash[i];
    106     }
    107     var keyHandleOffset = apdu.length + challengeHash.length + appIdHash.length;
    108     if (version != usbGnubby.U2F_V1) {
    109       u8[keyHandleOffset++] = keyHandle.length;
    110     }
    111     for (var i = 0; i < keyHandle.length; ++i) {
    112       u8[i + keyHandleOffset] = keyHandle[i];
    113     }
    114     self.apduReply_(u8.buffer, cb, opt_nowink);
    115   });
    116 };
    117 
    118 /** Request version information
    119  * @param {function(...)} cb Callback
    120  */
    121 usbGnubby.prototype.version = function(cb) {
    122   if (!cb) cb = usbGnubby.defaultCallback;
    123   if (this.version_) {
    124     cb(-llGnubby.OK, this.version_);
    125     return;
    126   }
    127   var self = this;
    128   var apdu = new Uint8Array([0x00, usbGnubby.U2F_VERSION, 0x00, 0x00, 0x00,
    129       0x00, 0x00, 0x00, 0x00]);
    130   this.apduReply_(apdu.buffer, function(rc, data) {
    131     if (rc == 0x6d00) {
    132       // Command not implemented. Pretend this is v1.
    133       var v1 = new Uint8Array(UTIL_StringToBytes(usbGnubby.U2F_V1));
    134       self.version_ = v1.buffer;
    135       cb(-llGnubby.OK, v1.buffer);
    136     } else {
    137       if (!rc) {
    138         self.version_ = data;
    139       }
    140       cb(rc, data);
    141     }
    142   });
    143 };
    144