blob: 7e4778458a7b2f622179b1031b14c64f0d191ae9 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Gnubby methods related to U2F support.
*/
'use strict';
// Commands and flags of the Gnubby applet
/** Enroll */
Gnubby.U2F_ENROLL = 0x01;
/** Request signature */
Gnubby.U2F_SIGN = 0x02;
/** Request protocol version */
Gnubby.U2F_VERSION = 0x03;
/** Request applet version */
Gnubby.APPLET_VERSION = 0x11; // First 3 bytes are applet version.
// APDU.P1 flags
/** Test of User Presence required */
Gnubby.P1_TUP_REQUIRED = 0x01;
/** Consume a Test of User Presence */
Gnubby.P1_TUP_CONSUME = 0x02;
/** Test signature only, no TUP. E.g. to check for existing enrollments. */
Gnubby.P1_TUP_TESTONLY = 0x04;
/** Attest with device key */
Gnubby.P1_INDIVIDUAL_KEY = 0x80;
// Version values
/** V1 of the applet. */
Gnubby.U2F_V1 = 'U2F_V1';
/** V2 of the applet. */
Gnubby.U2F_V2 = 'U2F_V2';
/** Perform enrollment
* @param {Array<number>|ArrayBuffer|Uint8Array} challenge Enrollment challenge
* @param {Array<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
* id
* @param {function(...)} cb Result callback
* @param {boolean=} opt_individualAttestation Request the individual
* attestation cert rather than the batch one.
*/
Gnubby.prototype.enroll = function(
challenge, appIdHash, cb, opt_individualAttestation) {
var p1 = Gnubby.P1_TUP_REQUIRED | Gnubby.P1_TUP_CONSUME;
if (opt_individualAttestation) {
p1 |= Gnubby.P1_INDIVIDUAL_KEY;
}
var apdu = new Uint8Array([
0x00, Gnubby.U2F_ENROLL, p1, 0x00, 0x00, 0x00,
challenge.length + appIdHash.length
]);
var u8 =
new Uint8Array(apdu.length + challenge.length + appIdHash.length + 2);
for (var i = 0; i < apdu.length; ++i) {
u8[i] = apdu[i];
}
for (var i = 0; i < challenge.length; ++i) {
u8[i + apdu.length] = challenge[i];
}
for (var i = 0; i < appIdHash.length; ++i) {
u8[i + apdu.length + challenge.length] = appIdHash[i];
}
this.apduReply(u8.buffer, cb);
};
/** Request signature
* @param {Array<number>|ArrayBuffer|Uint8Array} challengeHash Hashed
* signature challenge
* @param {Array<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
* id
* @param {Array<number>|ArrayBuffer|Uint8Array} keyHandle Key handle to use
* @param {function(...)} cb Result callback
* @param {boolean=} opt_nowink Request signature without winking
* (e.g. during enroll)
*/
Gnubby.prototype.sign = function(
challengeHash, appIdHash, keyHandle, cb, opt_nowink) {
var self = this;
// The sign command's format is ever-so-slightly different between V1 and V2,
// so get this gnubby's version prior to sending it.
this.version(function(rc, opt_data) {
if (rc) {
cb(rc);
return;
}
var version = UTIL_BytesToString(new Uint8Array(opt_data || []));
var apduDataLen =
challengeHash.length + appIdHash.length + keyHandle.length;
if (version != Gnubby.U2F_V1) {
// The V2 sign command includes a length byte for the key handle.
apduDataLen++;
}
var apdu = new Uint8Array([
0x00, Gnubby.U2F_SIGN, Gnubby.P1_TUP_REQUIRED | Gnubby.P1_TUP_CONSUME,
0x00, 0x00, 0x00, apduDataLen
]);
if (opt_nowink) {
// A signature request that does not want winking.
// These are used during enroll to figure out whether a gnubby was already
// enrolled.
// Tell applet to not actually produce a signature, even
// if already touched.
apdu[2] |= Gnubby.P1_TUP_TESTONLY;
}
var u8 = new Uint8Array(apdu.length + apduDataLen + 2);
for (var i = 0; i < apdu.length; ++i) {
u8[i] = apdu[i];
}
for (var i = 0; i < challengeHash.length; ++i) {
u8[i + apdu.length] = challengeHash[i];
}
for (var i = 0; i < appIdHash.length; ++i) {
u8[i + apdu.length + challengeHash.length] = appIdHash[i];
}
var keyHandleOffset = apdu.length + challengeHash.length + appIdHash.length;
if (version != Gnubby.U2F_V1) {
u8[keyHandleOffset++] = keyHandle.length;
}
for (var i = 0; i < keyHandle.length; ++i) {
u8[i + keyHandleOffset] = keyHandle[i];
}
self.apduReply(u8.buffer, cb, opt_nowink);
});
};
/** Request version information
* @param {function(...)} cb Callback
*/
Gnubby.prototype.version = function(cb) {
if (!cb) {
cb = Gnubby.defaultCallback;
}
if (this.version_) {
cb(-GnubbyDevice.OK, this.version_);
return;
}
var self = this;
function gotResponse(rc, data) {
if (!rc) {
self.version_ = data;
}
cb(rc, data);
}
var apdu =
new Uint8Array([0x00, Gnubby.U2F_VERSION, 0x00, 0x00, 0x00, 0x00, 0x00]);
this.apduReply(apdu.buffer, function(rc, data) {
if (rc == 0x6d00) {
// Command not implemented. Pretend this is v1.
var v1 = new Uint8Array(UTIL_StringToBytes(Gnubby.U2F_V1));
self.version_ = v1.buffer;
cb(-GnubbyDevice.OK, v1.buffer);
return;
}
if (rc) {
// Error. Try with non-ISO 7816-4-conforming layout defined in
// earlier U2F drafts.
apdu = new Uint8Array(
[0x00, Gnubby.U2F_VERSION, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
self.apduReply(apdu.buffer, gotResponse);
return;
}
// Any other response: handle as final result.
gotResponse(rc, data);
});
};