Commit 6fb1f2e7 authored by arnarb's avatar arnarb Committed by Commit bot

Update cryptotoken to 0.8.63

 - Request batch attestation instead of individual
 - Updates from U2F API revisions
 - Enumerate HID devieces based on usage page instead of vid/pid
 - Handle wrong keyHandle length and init messages
 - Smaller bug fixes and refactoring

BUG=416998
R=xiyuan,juanlang

Review URL: https://codereview.chromium.org/596083002

Cr-Commit-Position: refs/heads/master@{#296334}
parent ec689def
...@@ -199,6 +199,8 @@ ...@@ -199,6 +199,8 @@
<include name="IDR_CRYPTOTOKEN_DEVICEFACTORYREGISTRY_JS" file="cryptotoken/devicefactoryregistry.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_DEVICEFACTORYREGISTRY_JS" file="cryptotoken/devicefactoryregistry.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_GSTATICORIGINCHECK_JS" file="cryptotoken/gstaticorigincheck.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_GSTATICORIGINCHECK_JS" file="cryptotoken/gstaticorigincheck.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_ORIGINCHECK_JS" file="cryptotoken/origincheck.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_ORIGINCHECK_JS" file="cryptotoken/origincheck.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_INDIVIDUALATTEST_JS" file="cryptotoken/individualattest.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_GOOGLECORPINDIVIDUALATTESt_JS" file="cryptotoken/googlecorpindividualattest.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_CRYPTOTOKENBACKGROUND_JS" file="cryptotoken/cryptotokenbackground.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_CRYPTOTOKENBACKGROUND_JS" file="cryptotoken/cryptotokenbackground.js" type="BINDATA" />
<include name="IDR_WHISPERNET_PROXY_BACKGROUND_HTML" file="whispernet_proxy/background.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" /> <include name="IDR_WHISPERNET_PROXY_BACKGROUND_HTML" file="whispernet_proxy/background.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_WHISPERNET_PROXY_INIT_JS" file="whispernet_proxy/js/init.js" type="BINDATA" /> <include name="IDR_WHISPERNET_PROXY_INIT_JS" file="whispernet_proxy/js/init.js" type="BINDATA" />
......
...@@ -23,43 +23,19 @@ var FACTORY_REGISTRY = new FactoryRegistry( ...@@ -23,43 +23,19 @@ var FACTORY_REGISTRY = new FactoryRegistry(
var DEVICE_FACTORY_REGISTRY = new DeviceFactoryRegistry( var DEVICE_FACTORY_REGISTRY = new DeviceFactoryRegistry(
new UsbGnubbyFactory(gnubbies), new UsbGnubbyFactory(gnubbies),
TIMER_FACTORY); TIMER_FACTORY,
new GoogleCorpIndividualAttestation());
/**
* @param {Object} request Request object
* @param {MessageSender} sender Sender frame
* @param {Function} sendResponse Response callback
* @return {?Closeable} Optional handler object that should be closed when port
* closes
*/
function handleWebPageRequest(request, sender, sendResponse) {
switch (request.type) {
case GnubbyMsgTypes.ENROLL_WEB_REQUEST:
return handleWebEnrollRequest(sender, request, sendResponse);
case GnubbyMsgTypes.SIGN_WEB_REQUEST:
return handleWebSignRequest(sender, request, sendResponse);
case MessageTypes.U2F_REGISTER_REQUEST:
return handleU2fEnrollRequest(sender, request, sendResponse);
case MessageTypes.U2F_SIGN_REQUEST:
return handleU2fSignRequest(sender, request, sendResponse);
default:
sendResponse(
makeU2fErrorResponse(request, ErrorCodes.BAD_REQUEST, undefined,
MessageTypes.U2F_REGISTER_RESPONSE));
return null;
}
}
// Listen to individual messages sent from (whitelisted) webpages via // Listen to individual messages sent from (whitelisted) webpages via
// chrome.runtime.sendMessage // chrome.runtime.sendMessage
function messageHandlerExternal(request, sender, sendResponse) { function messageHandlerExternal(request, sender, sendResponse) {
var closeable = handleWebPageRequest(request, sender, function(response) { var closeable = handleWebPageRequest(request, sender, function(response) {
response['requestId'] = request['requestId']; response['requestId'] = request['requestId'];
try {
sendResponse(response); sendResponse(response);
} catch (e) {
console.warn(UTIL_fmt('caught: ' + e.message));
}
}); });
} }
chrome.runtime.onMessageExternal.addListener(messageHandlerExternal); chrome.runtime.onMessageExternal.addListener(messageHandlerExternal);
......
...@@ -11,13 +11,18 @@ ...@@ -11,13 +11,18 @@
/** /**
* @param {!GnubbyFactory} gnubbyFactory A Gnubby factory. * @param {!GnubbyFactory} gnubbyFactory A Gnubby factory.
* @param {!CountdownFactory} countdownFactory A countdown timer factory. * @param {!CountdownFactory} countdownFactory A countdown timer factory.
* @param {!IndividualAttestation} individualAttestation An individual
* attestation implementation.
* @constructor * @constructor
*/ */
function DeviceFactoryRegistry(gnubbyFactory, countdownFactory) { function DeviceFactoryRegistry(gnubbyFactory, countdownFactory,
individualAttestation) {
/** @private {!GnubbyFactory} */ /** @private {!GnubbyFactory} */
this.gnubbyFactory_ = gnubbyFactory; this.gnubbyFactory_ = gnubbyFactory;
/** @private {!CountdownFactory} */ /** @private {!CountdownFactory} */
this.countdownFactory_ = countdownFactory; this.countdownFactory_ = countdownFactory;
/** @private {!IndividualAttestation} */
this.individualAttestation_ = individualAttestation;
} }
/** @return {!GnubbyFactory} A Gnubby factory. */ /** @return {!GnubbyFactory} A Gnubby factory. */
...@@ -29,3 +34,9 @@ DeviceFactoryRegistry.prototype.getGnubbyFactory = function() { ...@@ -29,3 +34,9 @@ DeviceFactoryRegistry.prototype.getGnubbyFactory = function() {
DeviceFactoryRegistry.prototype.getCountdownFactory = function() { DeviceFactoryRegistry.prototype.getCountdownFactory = function() {
return this.countdownFactory_; return this.countdownFactory_;
}; };
/** @return {!IndividualAttestation} An individual attestation implementation.
*/
DeviceFactoryRegistry.prototype.getIndividualAttestation = function() {
return this.individualAttestation_;
};
...@@ -14,6 +14,12 @@ var DeviceStatusCodes = {}; ...@@ -14,6 +14,12 @@ var DeviceStatusCodes = {};
*/ */
DeviceStatusCodes.OK_STATUS = 0; DeviceStatusCodes.OK_STATUS = 0;
/**
* Device operation wrong length status.
* @const
*/
DeviceStatusCodes.WRONG_LENGTH_STATUS = 0x6700;
/** /**
* Device operation wait touch status. * Device operation wait touch status.
* @const * @const
......
...@@ -83,13 +83,14 @@ function handleU2fEnrollRequest(sender, request, sendResponse) { ...@@ -83,13 +83,14 @@ function handleU2fEnrollRequest(sender, request, sendResponse) {
closeable = closeable =
validateAndBeginEnrollRequest( validateAndBeginEnrollRequest(
sender, request, 'registerRequests', 'signRequests', sender, request, 'registerRequests', 'signRequests',
sendErrorResponse, sendSuccessResponse); sendErrorResponse, sendSuccessResponse, 'registeredKeys');
return closeable; return closeable;
} }
/** /**
* Validates an enroll request using the given parameters, and, if valid, begins * Validates an enroll request using the given parameters, and, if valid, begins
* handling the enroll request. * handling the enroll request. (The enroll request may be modified as a result
* of handling it.)
* @param {MessageSender} sender The sender of the message. * @param {MessageSender} sender The sender of the message.
* @param {Object} request The web page's enroll request. * @param {Object} request The web page's enroll request.
* @param {string} enrollChallengesName The name of the enroll challenges value * @param {string} enrollChallengesName The name of the enroll challenges value
...@@ -99,11 +100,14 @@ function handleU2fEnrollRequest(sender, request, sendResponse) { ...@@ -99,11 +100,14 @@ function handleU2fEnrollRequest(sender, request, sendResponse) {
* @param {function(ErrorCodes)} errorCb Error callback. * @param {function(ErrorCodes)} errorCb Error callback.
* @param {function(string, string, (string|undefined))} successCb Success * @param {function(string, string, (string|undefined))} successCb Success
* callback. * callback.
* @param {string=} opt_registeredKeysName The name of the registered keys
* value in the request.
* @return {Closeable} Request handler that should be closed when the browser * @return {Closeable} Request handler that should be closed when the browser
* message channel is closed. * message channel is closed.
*/ */
function validateAndBeginEnrollRequest(sender, request, function validateAndBeginEnrollRequest(sender, request,
enrollChallengesName, signChallengesName, errorCb, successCb) { enrollChallengesName, signChallengesName, errorCb, successCb,
opt_registeredKeysName) {
var origin = getOriginFromUrl(/** @type {string} */ (sender.url)); var origin = getOriginFromUrl(/** @type {string} */ (sender.url));
if (!origin) { if (!origin) {
errorCb(ErrorCodes.BAD_REQUEST); errorCb(ErrorCodes.BAD_REQUEST);
...@@ -111,20 +115,31 @@ function validateAndBeginEnrollRequest(sender, request, ...@@ -111,20 +115,31 @@ function validateAndBeginEnrollRequest(sender, request,
} }
if (!isValidEnrollRequest(request, enrollChallengesName, if (!isValidEnrollRequest(request, enrollChallengesName,
signChallengesName)) { signChallengesName, opt_registeredKeysName)) {
errorCb(ErrorCodes.BAD_REQUEST); errorCb(ErrorCodes.BAD_REQUEST);
return null; return null;
} }
var enrollChallenges = request[enrollChallengesName]; var enrollChallenges = request[enrollChallengesName];
var signChallenges = request[signChallengesName]; var signChallenges;
if (opt_registeredKeysName &&
request.hasOwnProperty(opt_registeredKeysName)) {
// Convert registered keys to sign challenges by adding a challenge value.
signChallenges = request[opt_registeredKeysName];
for (var i = 0; i < signChallenges.length; i++) {
// The actual value doesn't matter, as long as it's a string.
signChallenges[i]['challenge'] = '';
}
} else {
signChallenges = request[signChallengesName];
}
var logMsgUrl = request['logMsgUrl']; var logMsgUrl = request['logMsgUrl'];
var timer = createTimerForRequest( var timer = createTimerForRequest(
FACTORY_REGISTRY.getCountdownFactory(), request); FACTORY_REGISTRY.getCountdownFactory(), request);
var enroller = new Enroller(timer, origin, errorCb, successCb, var enroller = new Enroller(timer, origin, errorCb, successCb,
sender.tlsChannelId, logMsgUrl); sender.tlsChannelId, logMsgUrl);
enroller.doEnroll(enrollChallenges, signChallenges); enroller.doEnroll(enrollChallenges, signChallenges, request['appId']);
return /** @type {Closeable} */ (enroller); return /** @type {Closeable} */ (enroller);
} }
...@@ -135,22 +150,32 @@ function validateAndBeginEnrollRequest(sender, request, ...@@ -135,22 +150,32 @@ function validateAndBeginEnrollRequest(sender, request,
* in the request. * in the request.
* @param {string} signChallengesName The name of the sign challenges value in * @param {string} signChallengesName The name of the sign challenges value in
* the request. * the request.
* @param {string=} opt_registeredKeysName The name of the registered keys
* value in the request.
* @return {boolean} Whether the request appears valid. * @return {boolean} Whether the request appears valid.
*/ */
function isValidEnrollRequest(request, enrollChallengesName, function isValidEnrollRequest(request, enrollChallengesName,
signChallengesName) { signChallengesName, opt_registeredKeysName) {
if (!request.hasOwnProperty(enrollChallengesName)) if (!request.hasOwnProperty(enrollChallengesName))
return false; return false;
var enrollChallenges = request[enrollChallengesName]; var enrollChallenges = request[enrollChallengesName];
if (!enrollChallenges.length) if (!enrollChallenges.length)
return false; return false;
if (!isValidEnrollChallengeArray(enrollChallenges)) var hasAppId = request.hasOwnProperty('appId');
if (!isValidEnrollChallengeArray(enrollChallenges, !hasAppId))
return false; return false;
var signChallenges = request[signChallengesName]; var signChallenges = request[signChallengesName];
// A missing sign challenge array is ok, in the case the user is not already // A missing sign challenge array is ok, in the case the user is not already
// enrolled. // enrolled.
if (signChallenges && !isValidSignChallengeArray(signChallenges)) if (signChallenges && !isValidSignChallengeArray(signChallenges, !hasAppId))
return false;
if (opt_registeredKeysName) {
var registeredKeys = request[opt_registeredKeysName];
if (registeredKeys &&
!isValidRegisteredKeyArray(registeredKeys, !hasAppId)) {
return false; return false;
}
}
return true; return true;
} }
...@@ -166,10 +191,12 @@ var EnrollChallenge; ...@@ -166,10 +191,12 @@ var EnrollChallenge;
/** /**
* @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges to * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges to
* validate. * validate.
* @param {boolean} appIdRequired Whether the appId property is required on
* each challenge.
* @return {boolean} Whether the given array of challenges is a valid enroll * @return {boolean} Whether the given array of challenges is a valid enroll
* challenges array. * challenges array.
*/ */
function isValidEnrollChallengeArray(enrollChallenges) { function isValidEnrollChallengeArray(enrollChallenges, appIdRequired) {
var seenVersions = {}; var seenVersions = {};
for (var i = 0; i < enrollChallenges.length; i++) { for (var i = 0; i < enrollChallenges.length; i++) {
var enrollChallenge = enrollChallenges[i]; var enrollChallenge = enrollChallenges[i];
...@@ -186,7 +213,7 @@ function isValidEnrollChallengeArray(enrollChallenges) { ...@@ -186,7 +213,7 @@ function isValidEnrollChallengeArray(enrollChallenges) {
return false; return false;
} }
seenVersions[version] = version; seenVersions[version] = version;
if (!enrollChallenge['appId']) { if (appIdRequired && !enrollChallenge['appId']) {
return false; return false;
} }
if (!enrollChallenge['challenge']) { if (!enrollChallenge['challenge']) {
...@@ -300,10 +327,13 @@ Enroller.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; ...@@ -300,10 +327,13 @@ Enroller.DEFAULT_TIMEOUT_MILLIS = 30 * 1000;
* @param {Array.<EnrollChallenge>} enrollChallenges A set of enroll challenges. * @param {Array.<EnrollChallenge>} enrollChallenges A set of enroll challenges.
* @param {Array.<SignChallenge>} signChallenges A set of sign challenges for * @param {Array.<SignChallenge>} signChallenges A set of sign challenges for
* existing enrollments for this user and appId. * existing enrollments for this user and appId.
* @param {string=} opt_appId The app id for the entire request.
*/ */
Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges) { Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges,
var encodedEnrollChallenges = this.encodeEnrollChallenges_(enrollChallenges); opt_appId) {
var encodedSignChallenges = encodeSignChallenges(signChallenges); var encodedEnrollChallenges =
this.encodeEnrollChallenges_(enrollChallenges, opt_appId);
var encodedSignChallenges = encodeSignChallenges(signChallenges, opt_appId);
var request = { var request = {
type: 'enroll_helper_request', type: 'enroll_helper_request',
enrollChallenges: encodedEnrollChallenges, enrollChallenges: encodedEnrollChallenges,
...@@ -317,9 +347,20 @@ Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges) { ...@@ -317,9 +347,20 @@ Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges) {
// Begin fetching/checking the app ids. // Begin fetching/checking the app ids.
var enrollAppIds = []; var enrollAppIds = [];
if (opt_appId) {
enrollAppIds.push(opt_appId);
}
for (var i = 0; i < enrollChallenges.length; i++) { for (var i = 0; i < enrollChallenges.length; i++) {
if (enrollChallenges[i].hasOwnProperty('appId')) {
enrollAppIds.push(enrollChallenges[i]['appId']); enrollAppIds.push(enrollChallenges[i]['appId']);
} }
}
// Sanity check
if (!enrollAppIds.length) {
console.warn(UTIL_fmt('empty enroll app ids?'));
this.notifyError_(ErrorCodes.BAD_REQUEST);
return;
}
var self = this; var self = this;
this.checkAppIds_(enrollAppIds, signChallenges, function(result) { this.checkAppIds_(enrollAppIds, signChallenges, function(result) {
if (result) { if (result) {
...@@ -341,10 +382,11 @@ Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges) { ...@@ -341,10 +382,11 @@ Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges) {
/** /**
* Encodes the enroll challenge as an enroll helper challenge. * Encodes the enroll challenge as an enroll helper challenge.
* @param {EnrollChallenge} enrollChallenge The enroll challenge to encode. * @param {EnrollChallenge} enrollChallenge The enroll challenge to encode.
* @param {string=} opt_appId The app id for the entire request.
* @return {EnrollHelperChallenge} The encoded challenge. * @return {EnrollHelperChallenge} The encoded challenge.
* @private * @private
*/ */
Enroller.encodeEnrollChallenge_ = function(enrollChallenge) { Enroller.encodeEnrollChallenge_ = function(enrollChallenge, opt_appId) {
var encodedChallenge = {}; var encodedChallenge = {};
var version; var version;
if (enrollChallenge['version']) { if (enrollChallenge['version']) {
...@@ -354,19 +396,30 @@ Enroller.encodeEnrollChallenge_ = function(enrollChallenge) { ...@@ -354,19 +396,30 @@ Enroller.encodeEnrollChallenge_ = function(enrollChallenge) {
version = 'U2F_V1'; version = 'U2F_V1';
} }
encodedChallenge['version'] = version; encodedChallenge['version'] = version;
encodedChallenge['challenge'] = enrollChallenge['challenge']; encodedChallenge['challengeHash'] = enrollChallenge['challenge'];
encodedChallenge['appIdHash'] = var appId;
B64_encode(sha256HashOfString(enrollChallenge['appId'])); if (enrollChallenge['appId']) {
appId = enrollChallenge['appId'];
} else {
appId = opt_appId;
}
if (!appId) {
// Sanity check. (Other code should fail if it's not set.)
console.warn(UTIL_fmt('No appId?'));
}
encodedChallenge['appIdHash'] = B64_encode(sha256HashOfString(appId));
return /** @type {EnrollHelperChallenge} */ (encodedChallenge); return /** @type {EnrollHelperChallenge} */ (encodedChallenge);
}; };
/** /**
* Encodes the given enroll challenges using this enroller's state. * Encodes the given enroll challenges using this enroller's state.
* @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges. * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges.
* @param {string=} opt_appId The app id for the entire request.
* @return {!Array.<EnrollHelperChallenge>} The encoded enroll challenges. * @return {!Array.<EnrollHelperChallenge>} The encoded enroll challenges.
* @private * @private
*/ */
Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges) { Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges,
opt_appId) {
var challenges = []; var challenges = [];
for (var i = 0; i < enrollChallenges.length; i++) { for (var i = 0; i < enrollChallenges.length; i++) {
var enrollChallenge = enrollChallenges[i]; var enrollChallenge = enrollChallenges[i];
...@@ -393,9 +446,10 @@ Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges) { ...@@ -393,9 +446,10 @@ Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges) {
this.browserData_[version] = this.browserData_[version] =
B64_encode(UTIL_StringToBytes(browserData)); B64_encode(UTIL_StringToBytes(browserData));
challenges.push(Enroller.encodeEnrollChallenge_( challenges.push(Enroller.encodeEnrollChallenge_(
/** @type {EnrollChallenge} */ (modifiedChallenge))); /** @type {EnrollChallenge} */ (modifiedChallenge), opt_appId));
} else { } else {
challenges.push(Enroller.encodeEnrollChallenge_(enrollChallenge)); challenges.push(
Enroller.encodeEnrollChallenge_(enrollChallenge, opt_appId));
} }
} }
return challenges; return challenges;
......
...@@ -34,26 +34,25 @@ Gnubby.U2F_V1 = 'U2F_V1'; ...@@ -34,26 +34,25 @@ Gnubby.U2F_V1 = 'U2F_V1';
/** V2 of the applet. */ /** V2 of the applet. */
Gnubby.U2F_V2 = 'U2F_V2'; Gnubby.U2F_V2 = 'U2F_V2';
/**
* Google corporate appId hash
* @private
*/
Gnubby.GOOGLE_CORP_APP_ID_HASH_ = 'ZEZHL99u7Xvzwzcg8jZnbDbhtF6-BIXbiaPN_dJL1p8';
/** Perform enrollment /** Perform enrollment
* @param {ArrayBuffer|Uint8Array} challenge Enrollment challenge * @param {ArrayBuffer|Uint8Array} challenge Enrollment challenge
* @param {ArrayBuffer|Uint8Array} appIdHash Hashed application id * @param {ArrayBuffer|Uint8Array} appIdHash Hashed application id
* @param {function(...)} cb Result callback * @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) { 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( var apdu = new Uint8Array(
[0x00, [0x00,
Gnubby.U2F_ENROLL, Gnubby.U2F_ENROLL,
Gnubby.P1_TUP_REQUIRED | Gnubby.P1_TUP_CONSUME, p1,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
challenge.length + appIdHash.length]); challenge.length + appIdHash.length]);
if (B64_encode(appIdHash) == Gnubby.GOOGLE_CORP_APP_ID_HASH_)
apdu[2] |= Gnubby.P1_INDIVIDUAL_KEY;
var u8 = new Uint8Array(apdu.length + challenge.length + var u8 = new Uint8Array(apdu.length + challenge.length +
appIdHash.length + 2); appIdHash.length + 2);
for (var i = 0; i < apdu.length; ++i) u8[i] = apdu[i]; for (var i = 0; i < apdu.length; ++i) u8[i] = apdu[i];
......
// 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 Provides a Google corp implementation of IndividualAttestation.
*/
'use strict';
/**
* Google corp implementation of IndividualAttestation that requests
* individual certificates for corp accounts.
* @constructor
* @implements IndividualAttestation
*/
function GoogleCorpIndividualAttestation() {}
/**
* @param {string} appIdHash The app id hash.
* @return {boolean} Whether to request the individual attestation certificate
* for this app id.
*/
GoogleCorpIndividualAttestation.prototype.requestIndividualAttestation =
function(appIdHash) {
return appIdHash == GoogleCorpIndividualAttestation.GOOGLE_CORP_APP_ID_HASH;
};
/**
* App ID used by Google employee accounts.
* @const
*/
GoogleCorpIndividualAttestation.GOOGLE_CORP_APP_ID =
'https://www.gstatic.com/securitykey/a/google.com/origins.json';
/**
* Hash of the app ID used by Google employee accounts.
* @const
*/
GoogleCorpIndividualAttestation.GOOGLE_CORP_APP_ID_HASH =
B64_encode(sha256HashOfString(
GoogleCorpIndividualAttestation.GOOGLE_CORP_APP_ID));
...@@ -71,6 +71,12 @@ HidGnubbyDevice.prototype.destroy = function() { ...@@ -71,6 +71,12 @@ HidGnubbyDevice.prototype.destroy = function() {
this.dev = null; this.dev = null;
chrome.hid.disconnect(dev.connectionId, function() { chrome.hid.disconnect(dev.connectionId, function() {
if (chrome.runtime.lastError) {
console.warn(UTIL_fmt('Device ' + dev.connectionId +
' couldn\'t be disconnected:'));
console.warn(chrome.runtime.lastError);
return;
}
console.log(UTIL_fmt('Device ' + dev.connectionId + ' closed')); console.log(UTIL_fmt('Device ' + dev.connectionId + ' closed'));
}); });
}; };
...@@ -213,8 +219,9 @@ HidGnubbyDevice.prototype.checkLock_ = function(cid, cmd) { ...@@ -213,8 +219,9 @@ HidGnubbyDevice.prototype.checkLock_ = function(cid, cmd) {
if (this.lockCID != cid) { if (this.lockCID != cid) {
// Some other channel has active lock. // Some other channel has active lock.
if (cmd != GnubbyDevice.CMD_SYNC) { if (cmd != GnubbyDevice.CMD_SYNC &&
// Anything but SYNC gets an immediate busy. cmd != GnubbyDevice.CMD_INIT) {
// Anything but SYNC|INIT gets an immediate busy.
var busy = new Uint8Array( var busy = new Uint8Array(
[(cid >> 24) & 255, [(cid >> 24) & 255,
(cid >> 16) & 255, (cid >> 16) & 255,
...@@ -229,8 +236,9 @@ HidGnubbyDevice.prototype.checkLock_ = function(cid, cmd) { ...@@ -229,8 +236,9 @@ HidGnubbyDevice.prototype.checkLock_ = function(cid, cmd) {
return false; return false;
} }
// SYNC gets to go to the device to flush OS tx/rx queues. // SYNC|INIT gets to go to the device to flush OS tx/rx queues.
// The usb firmware always responds to SYNC, regardless of lock status. // The usb firmware is to alway respond to SYNC/INIT,
// regardless of lock status.
} }
} }
return true; return true;
...@@ -411,12 +419,18 @@ HidGnubbyDevice.enumerate = function(cb) { ...@@ -411,12 +419,18 @@ HidGnubbyDevice.enumerate = function(cb) {
} }
} }
try {
chrome.hid.getDevices({filters: [{usagePage: 0xf1d0}]}, cb);
} catch (e) {
console.log(e);
console.log(UTIL_fmt('falling back to vid/pid enumeration'));
GnubbyDevice.getPermittedUsbDevices(function(devs) { GnubbyDevice.getPermittedUsbDevices(function(devs) {
permittedDevs = devs; permittedDevs = devs;
for (var i = 0; i < devs.length; i++) { for (var i = 0; i < devs.length; i++) {
chrome.hid.getDevices(devs[i], enumerated); chrome.hid.getDevices(devs[i], enumerated);
} }
}); });
}
}; };
/** /**
......
// 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 Provides an interface to determine whether to request the
* individual attestation certificate during enrollment.
*/
'use strict';
/**
* Interface to determine whether to request the individual attestation
* certificate during enrollment.
* @interface
*/
function IndividualAttestation() {}
/**
* @param {string} appIdHash The app id hash.
* @return {boolean} Whether to request the individual attestation certificate
* for this app id.
*/
IndividualAttestation.prototype.requestIndividualAttestation =
function(appIdHash) {};
{ {
"name": "CryptoTokenExtension", "name": "CryptoTokenExtension",
"description": "CryptoToken Component Extension", "description": "CryptoToken Component Extension",
"version": "0.8.59", "version": "0.8.63",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq7zRobvA+AVlvNqkHSSVhh1sEWsHSqz4oR/XptkDe/Cz3+gW9ZGumZ20NCHjaac8j1iiesdigp8B1LJsd/2WWv2Dbnto4f8GrQ5MVphKyQ9WJHwejEHN2K4vzrTcwaXqv5BSTXwxlxS/mXCmXskTfryKTLuYrcHEWK8fCHb+0gvr8b/kvsi75A1aMmb6nUnFJvETmCkOCPNX5CHTdy634Ts/x0fLhRuPlahk63rdf7agxQv5viVjQFk+tbgv6aa9kdSd11Js/RZ9yZjrFgHOBWgP4jTBqud4+HUglrzu8qynFipyNRLCZsaxhm+NItTyNgesxLdxZcwOz56KD1Q4IQIDAQAB", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq7zRobvA+AVlvNqkHSSVhh1sEWsHSqz4oR/XptkDe/Cz3+gW9ZGumZ20NCHjaac8j1iiesdigp8B1LJsd/2WWv2Dbnto4f8GrQ5MVphKyQ9WJHwejEHN2K4vzrTcwaXqv5BSTXwxlxS/mXCmXskTfryKTLuYrcHEWK8fCHb+0gvr8b/kvsi75A1aMmb6nUnFJvETmCkOCPNX5CHTdy634Ts/x0fLhRuPlahk63rdf7agxQv5viVjQFk+tbgv6aa9kdSd11Js/RZ9yZjrFgHOBWgP4jTBqud4+HUglrzu8qynFipyNRLCZsaxhm+NItTyNgesxLdxZcwOz56KD1Q4IQIDAQAB",
"manifest_version": 2, "manifest_version": 2,
"permissions": [ "permissions": [
"usb",
"hid", "hid",
"usb",
"u2fDevices",
"https://www.gstatic.com/", "https://www.gstatic.com/",
{ {
"usbDevices": [ "usbDevices": [
...@@ -22,6 +23,10 @@ ...@@ -22,6 +23,10 @@
"vendorId": 4176, "vendorId": 4176,
"productId": 275 "productId": 275
}, },
{
"vendorId": 4176,
"productId": 276
},
{ {
"vendorId": 4176, "vendorId": 4176,
"productId": 277 "productId": 277
...@@ -34,6 +39,10 @@ ...@@ -34,6 +39,10 @@
"vendorId": 4176, "vendorId": 4176,
"productId": 1025 "productId": 1025
}, },
{
"vendorId": 4176,
"productId": 1026
},
{ {
"vendorId": 9601, "vendorId": 9601,
"productId": 61904 "productId": 61904
...@@ -42,6 +51,10 @@ ...@@ -42,6 +51,10 @@
"vendorId": 7223, "vendorId": 7223,
"productId": 61904 "productId": 61904
}, },
{
"vendorId": 1155,
"productId": 61904
},
{ {
"vendorId": 1419, "vendorId": 1419,
"productId": 24579 "productId": 24579
...@@ -51,9 +64,9 @@ ...@@ -51,9 +64,9 @@
], ],
"externally_connectable": { "externally_connectable": {
"matches": [ "matches": [
"https://login.corp.google.com/*",
"https://accounts.google.com/*", "https://accounts.google.com/*",
"https://security.google.com/*", "https://security.google.com/*"
"https://login.corp.google.com/*"
], ],
"accepts_tls_channel_id": true "accepts_tls_channel_id": true
}, },
...@@ -92,11 +105,13 @@ ...@@ -92,11 +105,13 @@
"multiplesigner.js", "multiplesigner.js",
"generichelper.js", "generichelper.js",
"inherits.js", "inherits.js",
"individualattest.js",
"devicefactoryregistry.js", "devicefactoryregistry.js",
"usbhelper.js", "usbhelper.js",
"usbenrollhandler.js", "usbenrollhandler.js",
"usbsignhandler.js", "usbsignhandler.js",
"usbgnubbyfactory.js", "usbgnubbyfactory.js",
"googlecorpindividualattest.js",
"cryptotokenbackground.js" "cryptotokenbackground.js"
] ]
} }
......
...@@ -262,7 +262,7 @@ MultipleGnubbySigner.prototype.addGnubby_ = function(gnubbyId) { ...@@ -262,7 +262,7 @@ MultipleGnubbySigner.prototype.addGnubby_ = function(gnubbyId) {
MultipleGnubbySigner.prototype.signCompletedCallback_ = MultipleGnubbySigner.prototype.signCompletedCallback_ =
function(tracker, result) { function(tracker, result) {
console.log( console.log(
UTIL_fmt(result.code ? 'failure.' : 'success!' + UTIL_fmt((result.code ? 'failure.' : 'success!') +
' gnubby ' + tracker.index + ' gnubby ' + tracker.index +
' got code ' + result.code.toString(16))); ' got code ' + result.code.toString(16)));
if (!tracker.stillGoing) { if (!tracker.stillGoing) {
......
...@@ -144,9 +144,20 @@ function validateAndEnqueueSignRequest(sender, request, ...@@ -144,9 +144,20 @@ function validateAndEnqueueSignRequest(sender, request,
} }
var signChallenges = request[signChallengesName]; var signChallenges = request[signChallengesName];
// A valid sign data has at least one challenge, so get the first appId from var appId;
if (request['appId']) {
appId = request['appId'];
} else {
// A valid sign data has at least one challenge, so get the appId from
// the first challenge. // the first challenge.
var firstAppId = signChallenges[0]['appId']; appId = signChallenges[0]['appId'];
}
// Sanity check
if (!appId) {
console.warn(UTIL_fmt('empty sign appId?'));
errorCb(ErrorCodes.BAD_REQUEST);
return null;
}
var timer = createTimerForRequest( var timer = createTimerForRequest(
FACTORY_REGISTRY.getCountdownFactory(), request); FACTORY_REGISTRY.getCountdownFactory(), request);
var logMsgUrl = request['logMsgUrl']; var logMsgUrl = request['logMsgUrl'];
...@@ -154,9 +165,9 @@ function validateAndEnqueueSignRequest(sender, request, ...@@ -154,9 +165,9 @@ function validateAndEnqueueSignRequest(sender, request,
// Queue sign requests from the same origin, to protect against simultaneous // Queue sign requests from the same origin, to protect against simultaneous
// sign-out on many tabs resulting in repeated sign-in requests. // sign-out on many tabs resulting in repeated sign-in requests.
var queuedSignRequest = new QueuedSignRequest(signChallenges, var queuedSignRequest = new QueuedSignRequest(signChallenges,
timer, nonNullOrigin, errorCb, successCb, sender.tlsChannelId, timer, nonNullOrigin, errorCb, successCb, appId, sender.tlsChannelId,
logMsgUrl); logMsgUrl);
var requestToken = signRequestQueue.queueRequest(firstAppId, nonNullOrigin, var requestToken = signRequestQueue.queueRequest(appId, nonNullOrigin,
queuedSignRequest.begin.bind(queuedSignRequest), timer); queuedSignRequest.begin.bind(queuedSignRequest), timer);
queuedSignRequest.setToken(requestToken); queuedSignRequest.setToken(requestToken);
return queuedSignRequest; return queuedSignRequest;
...@@ -177,7 +188,8 @@ function isValidSignRequest(request, signChallengesName) { ...@@ -177,7 +188,8 @@ function isValidSignRequest(request, signChallengesName) {
// be fulfilled. Fail. // be fulfilled. Fail.
if (!signChallenges.length) if (!signChallenges.length)
return false; return false;
return isValidSignChallengeArray(signChallenges); var hasAppId = request.hasOwnProperty('appId');
return isValidSignChallengeArray(signChallenges, !hasAppId);
} }
/** /**
...@@ -187,13 +199,14 @@ function isValidSignRequest(request, signChallengesName) { ...@@ -187,13 +199,14 @@ function isValidSignRequest(request, signChallengesName) {
* @param {string} origin Signature origin * @param {string} origin Signature origin
* @param {function(ErrorCodes)} errorCb Error callback * @param {function(ErrorCodes)} errorCb Error callback
* @param {function(SignChallenge, string, string)} successCb Success callback * @param {function(SignChallenge, string, string)} successCb Success callback
* @param {string|undefined} opt_appId The app id for the entire request.
* @param {string|undefined} opt_tlsChannelId TLS Channel Id * @param {string|undefined} opt_tlsChannelId TLS Channel Id
* @param {string|undefined} opt_logMsgUrl Url to post log messages to * @param {string|undefined} opt_logMsgUrl Url to post log messages to
* @constructor * @constructor
* @implements {Closeable} * @implements {Closeable}
*/ */
function QueuedSignRequest(signChallenges, timer, origin, errorCb, function QueuedSignRequest(signChallenges, timer, origin, errorCb,
successCb, opt_tlsChannelId, opt_logMsgUrl) { successCb, opt_appId, opt_tlsChannelId, opt_logMsgUrl) {
/** @private {!Array.<SignChallenge>} */ /** @private {!Array.<SignChallenge>} */
this.signChallenges_ = signChallenges; this.signChallenges_ = signChallenges;
/** @private {Countdown} */ /** @private {Countdown} */
...@@ -205,6 +218,8 @@ function QueuedSignRequest(signChallenges, timer, origin, errorCb, ...@@ -205,6 +218,8 @@ function QueuedSignRequest(signChallenges, timer, origin, errorCb,
/** @private {function(SignChallenge, string, string)} */ /** @private {function(SignChallenge, string, string)} */
this.successCb_ = successCb; this.successCb_ = successCb;
/** @private {string|undefined} */ /** @private {string|undefined} */
this.appId_ = opt_appId;
/** @private {string|undefined} */
this.tlsChannelId_ = opt_tlsChannelId; this.tlsChannelId_ = opt_tlsChannelId;
/** @private {string|undefined} */ /** @private {string|undefined} */
this.logMsgUrl_ = opt_logMsgUrl; this.logMsgUrl_ = opt_logMsgUrl;
...@@ -244,7 +259,7 @@ QueuedSignRequest.prototype.begin = function(token) { ...@@ -244,7 +259,7 @@ QueuedSignRequest.prototype.begin = function(token) {
this.signer_ = new Signer(this.timer_, this.origin_, this.signer_ = new Signer(this.timer_, this.origin_,
this.signerFailed_.bind(this), this.signerSucceeded_.bind(this), this.signerFailed_.bind(this), this.signerSucceeded_.bind(this),
this.tlsChannelId_, this.logMsgUrl_); this.tlsChannelId_, this.logMsgUrl_);
if (!this.signer_.setChallenges(this.signChallenges_)) { if (!this.signer_.setChallenges(this.signChallenges_, this.appId_)) {
token.complete(); token.complete();
this.errorCb_(ErrorCodes.BAD_REQUEST); this.errorCb_(ErrorCodes.BAD_REQUEST);
} }
...@@ -320,13 +335,16 @@ function Signer(timer, origin, errorCb, successCb, ...@@ -320,13 +335,16 @@ function Signer(timer, origin, errorCb, successCb,
/** /**
* Sets the challenges to be signed. * Sets the challenges to be signed.
* @param {Array.<SignChallenge>} signChallenges The challenges to set. * @param {Array.<SignChallenge>} signChallenges The challenges to set.
* @param {string=} opt_appId The app id for the entire request.
* @return {boolean} Whether the challenges could be set. * @return {boolean} Whether the challenges could be set.
*/ */
Signer.prototype.setChallenges = function(signChallenges) { Signer.prototype.setChallenges = function(signChallenges, opt_appId) {
if (this.challengesSet_ || this.done_) if (this.challengesSet_ || this.done_)
return false; return false;
/** @private {Array.<SignChallenge>} */ /** @private {Array.<SignChallenge>} */
this.signChallenges_ = signChallenges; this.signChallenges_ = signChallenges;
/** @private {string|undefined} */
this.appId_ = opt_appId;
/** @private {boolean} */ /** @private {boolean} */
this.challengesSet_ = true; this.challengesSet_ = true;
...@@ -340,6 +358,9 @@ Signer.prototype.setChallenges = function(signChallenges) { ...@@ -340,6 +358,9 @@ Signer.prototype.setChallenges = function(signChallenges) {
*/ */
Signer.prototype.checkAppIds_ = function() { Signer.prototype.checkAppIds_ = function() {
var appIds = getDistinctAppIds(this.signChallenges_); var appIds = getDistinctAppIds(this.signChallenges_);
if (this.appId_) {
appIds = UTIL_unionArrays([this.appId_], appIds);
}
if (!appIds || !appIds.length) { if (!appIds || !appIds.length) {
this.notifyError_(ErrorCodes.BAD_REQUEST); this.notifyError_(ErrorCodes.BAD_REQUEST);
return; return;
...@@ -405,7 +426,7 @@ Signer.prototype.doSign_ = function() { ...@@ -405,7 +426,7 @@ Signer.prototype.doSign_ = function() {
} }
var encodedChallenges = encodeSignChallenges(this.signChallenges_, var encodedChallenges = encodeSignChallenges(this.signChallenges_,
this.getChallengeHash_.bind(this)); this.appId_, this.getChallengeHash_.bind(this));
var timeoutSeconds = this.timer_.millisecondsUntilExpired() / 1000.0; var timeoutSeconds = this.timer_.millisecondsUntilExpired() / 1000.0;
var request = makeSignHelperRequest(encodedChallenges, timeoutSeconds, var request = makeSignHelperRequest(encodedChallenges, timeoutSeconds,
......
...@@ -72,8 +72,8 @@ function SingleGnubbySigner(gnubbyId, forEnroll, completeCb, timer, ...@@ -72,8 +72,8 @@ function SingleGnubbySigner(gnubbyId, forEnroll, completeCb, timer,
/** @private {boolean} */ /** @private {boolean} */
this.challengesSet_ = false; this.challengesSet_ = false;
/** @private {!Array.<string>} */ /** @private {!Object.<string, number>} */
this.notForMe_ = []; this.cachedError_ = [];
} }
/** @enum {number} */ /** @enum {number} */
...@@ -242,7 +242,7 @@ SingleGnubbySigner.prototype.openCallback_ = function(rc, gnubby) { ...@@ -242,7 +242,7 @@ SingleGnubbySigner.prototype.openCallback_ = function(rc, gnubby) {
// TODO: This won't be confused with success, but should it be // TODO: This won't be confused with success, but should it be
// part of the same namespace as the other error codes, which are // part of the same namespace as the other error codes, which are
// always in DeviceStatusCodes.*? // always in DeviceStatusCodes.*?
this.goToError_(rc); this.goToError_(rc, true);
} }
}; };
...@@ -254,7 +254,7 @@ SingleGnubbySigner.prototype.openCallback_ = function(rc, gnubby) { ...@@ -254,7 +254,7 @@ SingleGnubbySigner.prototype.openCallback_ = function(rc, gnubby) {
*/ */
SingleGnubbySigner.prototype.versionCallback_ = function(rc, opt_data) { SingleGnubbySigner.prototype.versionCallback_ = function(rc, opt_data) {
if (rc) { if (rc) {
this.goToError_(rc); this.goToError_(rc, true);
return; return;
} }
this.state_ = SingleGnubbySigner.State.IDLE; this.state_ = SingleGnubbySigner.State.IDLE;
...@@ -295,9 +295,9 @@ SingleGnubbySigner.prototype.doSign_ = function(challengeIndex) { ...@@ -295,9 +295,9 @@ SingleGnubbySigner.prototype.doSign_ = function(challengeIndex) {
var challengeHash = challenge.challengeHash; var challengeHash = challenge.challengeHash;
var appIdHash = challenge.appIdHash; var appIdHash = challenge.appIdHash;
var keyHandle = challenge.keyHandle; var keyHandle = challenge.keyHandle;
if (this.notForMe_.indexOf(keyHandle) != -1) { if (this.cachedError_.hasOwnProperty(keyHandle)) {
// Cache hit: return wrong data again. // Cache hit: return wrong data again.
this.signCallback_(challengeIndex, DeviceStatusCodes.WRONG_DATA_STATUS); this.signCallback_(challengeIndex, this.cachedError_[keyHandle]);
} else if (challenge.version && challenge.version != this.version_) { } else if (challenge.version && challenge.version != this.version_) {
// Sign challenge for a different version of gnubby: return wrong data. // Sign challenge for a different version of gnubby: return wrong data.
this.signCallback_(challengeIndex, DeviceStatusCodes.WRONG_DATA_STATUS); this.signCallback_(challengeIndex, DeviceStatusCodes.WRONG_DATA_STATUS);
...@@ -326,13 +326,14 @@ SingleGnubbySigner.prototype.signCallback_ = ...@@ -326,13 +326,14 @@ SingleGnubbySigner.prototype.signCallback_ =
return; return;
} }
// Cache wrong data result, re-asking the gnubby to sign it won't produce // Cache wrong data or wrong length results, re-asking the gnubby to sign it
// different results. // won't produce different results.
if (code == DeviceStatusCodes.WRONG_DATA_STATUS) { if (code == DeviceStatusCodes.WRONG_DATA_STATUS ||
code == DeviceStatusCodes.WRONG_LENGTH_STATUS) {
if (challengeIndex < this.challenges_.length) { if (challengeIndex < this.challenges_.length) {
var challenge = this.challenges_[challengeIndex]; var challenge = this.challenges_[challengeIndex];
if (this.notForMe_.indexOf(challenge.keyHandle) == -1) { if (!this.cachedError_.hasOwnProperty(challenge.keyHandle)) {
this.notForMe_.push(challenge.keyHandle); this.cachedError_[challenge.keyHandle] = code;
} }
} }
} }
...@@ -364,6 +365,7 @@ SingleGnubbySigner.prototype.signCallback_ = ...@@ -364,6 +365,7 @@ SingleGnubbySigner.prototype.signCallback_ =
break; break;
case DeviceStatusCodes.WRONG_DATA_STATUS: case DeviceStatusCodes.WRONG_DATA_STATUS:
case DeviceStatusCodes.WRONG_LENGTH_STATUS:
if (this.challengeIndex_ < this.challenges_.length - 1) { if (this.challengeIndex_ < this.challenges_.length - 1) {
this.doSign_(++this.challengeIndex_); this.doSign_(++this.challengeIndex_);
} else if (this.forEnroll_) { } else if (this.forEnroll_) {
...@@ -375,11 +377,11 @@ SingleGnubbySigner.prototype.signCallback_ = ...@@ -375,11 +377,11 @@ SingleGnubbySigner.prototype.signCallback_ =
default: default:
if (this.forEnroll_) { if (this.forEnroll_) {
this.goToError_(code); this.goToError_(code, true);
} else if (this.challengeIndex_ < this.challenges_.length - 1) { } else if (this.challengeIndex_ < this.challenges_.length - 1) {
this.doSign_(++this.challengeIndex_); this.doSign_(++this.challengeIndex_);
} else { } else {
this.goToError_(code); this.goToError_(code, true);
} }
} }
}; };
...@@ -387,11 +389,13 @@ SingleGnubbySigner.prototype.signCallback_ = ...@@ -387,11 +389,13 @@ SingleGnubbySigner.prototype.signCallback_ =
/** /**
* Switches to the error state, and notifies caller. * Switches to the error state, and notifies caller.
* @param {number} code Error code * @param {number} code Error code
* @param {boolean=} opt_warn Whether to warn in the console about the error.
* @private * @private
*/ */
SingleGnubbySigner.prototype.goToError_ = function(code) { SingleGnubbySigner.prototype.goToError_ = function(code, opt_warn) {
this.state_ = SingleGnubbySigner.State.COMPLETE; this.state_ = SingleGnubbySigner.State.COMPLETE;
console.log(UTIL_fmt('failed (' + code.toString(16) + ')')); var logFn = opt_warn ? console.warn.bind(console) : console.log.bind(console);
logFn(UTIL_fmt('failed (' + code.toString(16) + ')'));
// Since this gnubby can no longer produce a useful result, go ahead and // Since this gnubby can no longer produce a useful result, go ahead and
// close it. // close it.
this.close(); this.close();
......
...@@ -17,10 +17,12 @@ function TextFetcher() {} ...@@ -17,10 +17,12 @@ function TextFetcher() {}
/** /**
* @param {string} url The URL to fetch. * @param {string} url The URL to fetch.
* @param {string?} opt_method The HTTP method to use (default GET)
* @param {string?} opt_body The request body
* @return {!Promise.<string>} A promise for the fetched text. In case of an * @return {!Promise.<string>} A promise for the fetched text. In case of an
* error, this promise is rejected with an HTTP status code. * error, this promise is rejected with an HTTP status code.
*/ */
TextFetcher.prototype.fetch = function(url) {}; TextFetcher.prototype.fetch = function(url, opt_method, opt_body) {};
/** /**
* @constructor * @constructor
...@@ -31,13 +33,16 @@ function XhrTextFetcher() { ...@@ -31,13 +33,16 @@ function XhrTextFetcher() {
/** /**
* @param {string} url The URL to fetch. * @param {string} url The URL to fetch.
* @param {string?} opt_method The HTTP method to use (default GET)
* @param {string?} opt_body The request body
* @return {!Promise.<string>} A promise for the fetched text. In case of an * @return {!Promise.<string>} A promise for the fetched text. In case of an
* error, this promise is rejected with an HTTP status code. * error, this promise is rejected with an HTTP status code.
*/ */
XhrTextFetcher.prototype.fetch = function(url) { XhrTextFetcher.prototype.fetch = function(url, opt_method, opt_body) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', url, true); var method = opt_method || 'GET';
xhr.open(method, url, true);
xhr.onloadend = function() { xhr.onloadend = function() {
if (xhr.status != 200) { if (xhr.status != 200) {
reject(xhr.status); reject(xhr.status);
...@@ -45,6 +50,13 @@ XhrTextFetcher.prototype.fetch = function(url) { ...@@ -45,6 +50,13 @@ XhrTextFetcher.prototype.fetch = function(url) {
} }
resolve(xhr.responseText); resolve(xhr.responseText);
}; };
xhr.onerror = function() {
// Treat any network-level errors as though the page didn't exist.
reject(404);
};
if (opt_body)
xhr.send(opt_body);
else
xhr.send(); xhr.send();
}); });
}; };
...@@ -98,7 +98,8 @@ UsbEnrollHandler.prototype.signerFoundGnubby_ = ...@@ -98,7 +98,8 @@ UsbEnrollHandler.prototype.signerFoundGnubby_ =
// caller, as the gnubby is already enrolled. Map ok to WRONG_DATA, so the // caller, as the gnubby is already enrolled. Map ok to WRONG_DATA, so the
// caller knows what to do. // caller knows what to do.
this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS); this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS);
} else if (signResult.code == DeviceStatusCodes.WRONG_DATA_STATUS) { } else if (signResult.code == DeviceStatusCodes.WRONG_DATA_STATUS ||
signResult.code == DeviceStatusCodes.WRONG_LENGTH_STATUS) {
var gnubby = signResult['gnubby']; var gnubby = signResult['gnubby'];
// A valid helper request contains at least one enroll challenge, so use // A valid helper request contains at least one enroll challenge, so use
// the app id hash from the first challenge. // the app id hash from the first challenge.
...@@ -206,10 +207,13 @@ UsbEnrollHandler.prototype.tryEnroll_ = function(gnubby, version) { ...@@ -206,10 +207,13 @@ UsbEnrollHandler.prototype.tryEnroll_ = function(gnubby, version) {
this.removeWrongVersionGnubby_(gnubby); this.removeWrongVersionGnubby_(gnubby);
return; return;
} }
var challengeChallenge = B64_decode(challenge['challenge']); var challengeValue = B64_decode(challenge['challengeHash']);
var appIdHash = B64_decode(challenge['appIdHash']); var appIdHash = challenge['appIdHash'];
gnubby.enroll(challengeChallenge, appIdHash, var individualAttest =
this.enrollCallback_.bind(this, gnubby, version)); DEVICE_FACTORY_REGISTRY.getIndividualAttestation().
requestIndividualAttestation(appIdHash);
gnubby.enroll(challengeValue, B64_decode(appIdHash),
this.enrollCallback_.bind(this, gnubby, version), individualAttest);
}; };
/** /**
......
...@@ -77,8 +77,20 @@ UsbGnubbyDevice.prototype.destroy = function() { ...@@ -77,8 +77,20 @@ UsbGnubbyDevice.prototype.destroy = function() {
this.dev = null; this.dev = null;
chrome.usb.releaseInterface(dev, 0, function() { chrome.usb.releaseInterface(dev, 0, function() {
if (chrome.runtime.lastError) {
console.warn(UTIL_fmt('Device ' + dev.handle +
' couldn\'t be released:'));
console.warn(chrome.runtime.lastError);
return;
}
console.log(UTIL_fmt('Device ' + dev.handle + ' released')); console.log(UTIL_fmt('Device ' + dev.handle + ' released'));
chrome.usb.closeDevice(dev, function() { chrome.usb.closeDevice(dev, function() {
if (chrome.runtime.lastError) {
console.warn(UTIL_fmt('Device ' + dev.handle +
' couldn\'t be closed:'));
console.warn(chrome.runtime.lastError);
return;
}
console.log(UTIL_fmt('Device ' + dev.handle + ' closed')); console.log(UTIL_fmt('Device ' + dev.handle + ' closed'));
}); });
}); });
...@@ -276,8 +288,9 @@ UsbGnubbyDevice.prototype.checkLock_ = function(cid, cmd) { ...@@ -276,8 +288,9 @@ UsbGnubbyDevice.prototype.checkLock_ = function(cid, cmd) {
if (this.lockCID != cid) { if (this.lockCID != cid) {
// Some other channel has active lock. // Some other channel has active lock.
if (cmd != GnubbyDevice.CMD_SYNC) { if (cmd != GnubbyDevice.CMD_SYNC &&
// Anything but SYNC gets an immediate busy. cmd != GnubbyDevice.CMD_INIT) {
// Anything but SYNC|INIT gets an immediate busy.
var busy = new Uint8Array( var busy = new Uint8Array(
[(cid >> 24) & 255, [(cid >> 24) & 255,
(cid >> 16) & 255, (cid >> 16) & 255,
...@@ -292,8 +305,9 @@ UsbGnubbyDevice.prototype.checkLock_ = function(cid, cmd) { ...@@ -292,8 +305,9 @@ UsbGnubbyDevice.prototype.checkLock_ = function(cid, cmd) {
return false; return false;
} }
// SYNC gets to go to the device to flush OS tx/rx queues. // SYNC|INIT get to go to the device to flush OS tx/rx queues.
// The usb firmware always responds to SYNC, regardless of lock status. // The usb firmware is to always respond to SYNC|INIT,
// regardless of lock status.
} }
} }
return true; return true;
...@@ -439,7 +453,7 @@ var InterfaceDescriptor; ...@@ -439,7 +453,7 @@ var InterfaceDescriptor;
UsbGnubbyDevice.open = function(gnubbies, which, dev, cb) { UsbGnubbyDevice.open = function(gnubbies, which, dev, cb) {
/** @param {chrome.usb.ConnectionHandle=} handle Connection handle */ /** @param {chrome.usb.ConnectionHandle=} handle Connection handle */
function deviceOpened(handle) { function deviceOpened(handle) {
if (!handle) { if (chrome.runtime.lastError) {
console.warn(UTIL_fmt('failed to open device. permissions issue?')); console.warn(UTIL_fmt('failed to open device. permissions issue?'));
cb(-GnubbyDevice.NODEVICE); cb(-GnubbyDevice.NODEVICE);
return; return;
......
...@@ -25,27 +25,56 @@ function getOriginFromUrl(url) { ...@@ -25,27 +25,56 @@ function getOriginFromUrl(url) {
return origin; return origin;
} }
/**
* Returns whether the registered key appears to be valid.
* @param {Object} registeredKey The registered key object.
* @param {boolean} appIdRequired Whether the appId property is required on
* each challenge.
* @return {boolean} Whether the object appears valid.
*/
function isValidRegisteredKey(registeredKey, appIdRequired) {
if (appIdRequired && !registeredKey.hasOwnProperty('appId')) {
return false;
}
if (!registeredKey.hasOwnProperty('keyHandle'))
return false;
if (registeredKey['version']) {
if (registeredKey['version'] != 'U2F_V1' &&
registeredKey['version'] != 'U2F_V2') {
return false;
}
}
return true;
}
/**
* Returns whether the array of registered keys appears to be valid.
* @param {Array.<Object>} registeredKeys The array of registered keys.
* @param {boolean} appIdRequired Whether the appId property is required on
* each challenge.
* @return {boolean} Whether the array appears valid.
*/
function isValidRegisteredKeyArray(registeredKeys, appIdRequired) {
return registeredKeys.every(function(key) {
return isValidRegisteredKey(key, appIdRequired);
});
}
/** /**
* Returns whether the array of SignChallenges appears to be valid. * Returns whether the array of SignChallenges appears to be valid.
* @param {Array.<SignChallenge>} signChallenges The array of sign challenges. * @param {Array.<SignChallenge>} signChallenges The array of sign challenges.
* @param {boolean} appIdRequired Whether the appId property is required on
* each challenge.
* @return {boolean} Whether the array appears valid. * @return {boolean} Whether the array appears valid.
*/ */
function isValidSignChallengeArray(signChallenges) { function isValidSignChallengeArray(signChallenges, appIdRequired) {
for (var i = 0; i < signChallenges.length; i++) { for (var i = 0; i < signChallenges.length; i++) {
var incomingChallenge = signChallenges[i]; var incomingChallenge = signChallenges[i];
if (!incomingChallenge.hasOwnProperty('challenge')) if (!incomingChallenge.hasOwnProperty('challenge'))
return false; return false;
if (!incomingChallenge.hasOwnProperty('appId')) { if (!isValidRegisteredKey(incomingChallenge, appIdRequired)) {
return false; return false;
} }
if (!incomingChallenge.hasOwnProperty('keyHandle'))
return false;
if (incomingChallenge['version']) {
if (incomingChallenge['version'] != 'U2F_V1' &&
incomingChallenge['version'] != 'U2F_V2') {
return false;
}
}
} }
return true; return true;
} }
...@@ -66,6 +95,35 @@ function logMessage(logMsg, opt_logMsgUrl) { ...@@ -66,6 +95,35 @@ function logMessage(logMsg, opt_logMsgUrl) {
audio.src = opt_logMsgUrl + logMsg; audio.src = opt_logMsgUrl + logMsg;
} }
/**
* @param {Object} request Request object
* @param {MessageSender} sender Sender frame
* @param {Function} sendResponse Response callback
* @return {?Closeable} Optional handler object that should be closed when port
* closes
*/
function handleWebPageRequest(request, sender, sendResponse) {
switch (request.type) {
case GnubbyMsgTypes.ENROLL_WEB_REQUEST:
return handleWebEnrollRequest(sender, request, sendResponse);
case GnubbyMsgTypes.SIGN_WEB_REQUEST:
return handleWebSignRequest(sender, request, sendResponse);
case MessageTypes.U2F_REGISTER_REQUEST:
return handleU2fEnrollRequest(sender, request, sendResponse);
case MessageTypes.U2F_SIGN_REQUEST:
return handleU2fSignRequest(sender, request, sendResponse);
default:
sendResponse(
makeU2fErrorResponse(request, ErrorCodes.BAD_REQUEST, undefined,
MessageTypes.U2F_REGISTER_RESPONSE));
return null;
}
}
/** /**
* Makes a response to a request. * Makes a response to a request.
* @param {Object} request The request to make a response to. * @param {Object} request The request to make a response to.
...@@ -310,13 +368,16 @@ function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) { ...@@ -310,13 +368,16 @@ function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
/** /**
* Encodes the sign data as an array of sign helper challenges. * Encodes the sign data as an array of sign helper challenges.
* @param {Array.<SignChallenge>} signChallenges The sign challenges to encode. * @param {Array.<SignChallenge>} signChallenges The sign challenges to encode.
* @param {string=} opt_defaultAppId The app id to use for each challenge, if
* the challenge contains none.
* @param {function(string, string): string=} opt_challengeHashFunction * @param {function(string, string): string=} opt_challengeHashFunction
* A function that produces, from a key handle and a raw challenge, a hash * A function that produces, from a key handle and a raw challenge, a hash
* of the raw challenge. If none is provided, a default hash function is * of the raw challenge. If none is provided, a default hash function is
* used. * used.
* @return {!Array.<SignHelperChallenge>} The sign challenges, encoded. * @return {!Array.<SignHelperChallenge>} The sign challenges, encoded.
*/ */
function encodeSignChallenges(signChallenges, opt_challengeHashFunction) { function encodeSignChallenges(signChallenges, opt_defaultAppId,
opt_challengeHashFunction) {
function encodedSha256(keyHandle, challenge) { function encodedSha256(keyHandle, challenge) {
return B64_encode(sha256HashOfString(challenge)); return B64_encode(sha256HashOfString(challenge));
} }
...@@ -327,9 +388,15 @@ function encodeSignChallenges(signChallenges, opt_challengeHashFunction) { ...@@ -327,9 +388,15 @@ function encodeSignChallenges(signChallenges, opt_challengeHashFunction) {
var challenge = signChallenges[i]; var challenge = signChallenges[i];
var challengeHash = var challengeHash =
challengeHashFn(challenge['keyHandle'], challenge['challenge']); challengeHashFn(challenge['keyHandle'], challenge['challenge']);
var appId;
if (challenge.hasOwnProperty('appId')) {
appId = challenge['appId'];
} else {
appId = opt_defaultAppId;
}
var encodedChallenge = { var encodedChallenge = {
'challengeHash': challengeHash, 'challengeHash': challengeHash,
'appIdHash': B64_encode(sha256HashOfString(challenge['appId'])), 'appIdHash': B64_encode(sha256HashOfString(appId)),
'keyHandle': challenge['keyHandle'], 'keyHandle': challenge['keyHandle'],
'version': (challenge['version'] || 'U2F_V1') 'version': (challenge['version'] || 'U2F_V1')
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment