*/
function handleWebEnrollRequest(sender, request, sendResponse) {
var sentResponse = false;
- var closeable;
+ var closeable = null;
- function sendErrorResponse(u2fCode) {
+ function sendErrorResponse(error) {
var response = makeWebErrorResponse(request,
- mapErrorCodeToGnubbyCodeType(u2fCode, false /* forSign */));
+ mapErrorCodeToGnubbyCodeType(error.errorCode, false /* forSign */));
sendResponseOnce(sentResponse, closeable, response, sendResponse);
}
sendResponseOnce(sentResponse, closeable, response, sendResponse);
}
- closeable =
- validateAndBeginEnrollRequest(
+ var enroller =
+ validateEnrollRequest(
sender, request, 'enrollChallenges', 'signData',
sendErrorResponse, sendSuccessResponse);
+ if (enroller) {
+ var registerRequests = request['enrollChallenges'];
+ var signRequests = getSignRequestsFromEnrollRequest(request, 'signData');
+ closeable = /** @type {Closeable} */ (enroller);
+ enroller.doEnroll(registerRequests, signRequests, request['appId']);
+ }
return closeable;
}
*/
function handleU2fEnrollRequest(sender, request, sendResponse) {
var sentResponse = false;
- var closeable;
+ var closeable = null;
- function sendErrorResponse(u2fCode) {
- var response = makeU2fErrorResponse(request, u2fCode);
+ function sendErrorResponse(error) {
+ var response = makeU2fErrorResponse(request, error.errorCode,
+ error.errorMessage);
sendResponseOnce(sentResponse, closeable, response, sendResponse);
}
sendResponseOnce(sentResponse, closeable, response, sendResponse);
}
- closeable =
- validateAndBeginEnrollRequest(
+ var enroller =
+ validateEnrollRequest(
sender, request, 'registerRequests', 'signRequests',
- sendErrorResponse, sendSuccessResponse);
+ sendErrorResponse, sendSuccessResponse, 'registeredKeys');
+ if (enroller) {
+ var registerRequests = request['registerRequests'];
+ var signRequests = getSignRequestsFromEnrollRequest(request,
+ 'signRequests', 'registeredKeys');
+ closeable = /** @type {Closeable} */ (enroller);
+ enroller.doEnroll(registerRequests, signRequests, request['appId']);
+ }
return closeable;
}
/**
- * Validates an enroll request using the given parameters, and, if valid, begins
- * handling the enroll request.
+ * Validates an enroll request using the given parameters.
* @param {MessageSender} sender The sender of the message.
* @param {Object} request The web page's enroll request.
* @param {string} enrollChallengesName The name of the enroll challenges value
* in the request.
* @param {string} signChallengesName The name of the sign challenges value in
* the request.
- * @param {function(ErrorCodes)} errorCb Error callback.
+ * @param {function(U2fError)} errorCb Error callback.
* @param {function(string, string, (string|undefined))} successCb Success
* callback.
- * @return {Closeable} Request handler that should be closed when the browser
- * message channel is closed.
+ * @param {string=} opt_registeredKeysName The name of the registered keys
+ * value in the request.
+ * @return {Enroller} Enroller object representing the request, if the request
+ * is valid, or null if the request is invalid.
*/
-function validateAndBeginEnrollRequest(sender, request,
- enrollChallengesName, signChallengesName, errorCb, successCb) {
+function validateEnrollRequest(sender, request,
+ enrollChallengesName, signChallengesName, errorCb, successCb,
+ opt_registeredKeysName) {
var origin = getOriginFromUrl(/** @type {string} */ (sender.url));
if (!origin) {
- errorCb(ErrorCodes.BAD_REQUEST);
+ errorCb({errorCode: ErrorCodes.BAD_REQUEST});
return null;
}
if (!isValidEnrollRequest(request, enrollChallengesName,
- signChallengesName)) {
- errorCb(ErrorCodes.BAD_REQUEST);
+ signChallengesName, opt_registeredKeysName)) {
+ errorCb({errorCode: ErrorCodes.BAD_REQUEST});
return null;
}
- var enrollChallenges = request[enrollChallengesName];
- var signChallenges = request[signChallengesName];
- var logMsgUrl = request['logMsgUrl'];
-
var timer = createTimerForRequest(
FACTORY_REGISTRY.getCountdownFactory(), request);
+ var logMsgUrl = request['logMsgUrl'];
var enroller = new Enroller(timer, origin, errorCb, successCb,
sender.tlsChannelId, logMsgUrl);
- enroller.doEnroll(enrollChallenges, signChallenges);
- return /** @type {Closeable} */ (enroller);
+ return enroller;
}
/**
* in the request.
* @param {string} signChallengesName The name of the sign challenges value in
* the request.
+ * @param {string=} opt_registeredKeysName The name of the registered keys
+ * value in the request.
* @return {boolean} Whether the request appears valid.
*/
function isValidEnrollRequest(request, enrollChallengesName,
- signChallengesName) {
+ signChallengesName, opt_registeredKeysName) {
if (!request.hasOwnProperty(enrollChallengesName))
return false;
var enrollChallenges = request[enrollChallengesName];
if (!enrollChallenges.length)
return false;
- if (!isValidEnrollChallengeArray(enrollChallenges))
+ var hasAppId = request.hasOwnProperty('appId');
+ if (!isValidEnrollChallengeArray(enrollChallenges, !hasAppId))
return false;
var signChallenges = request[signChallengesName];
// A missing sign challenge array is ok, in the case the user is not already
// 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 true;
}
/**
* @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges to
* 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
* challenges array.
*/
-function isValidEnrollChallengeArray(enrollChallenges) {
+function isValidEnrollChallengeArray(enrollChallenges, appIdRequired) {
var seenVersions = {};
for (var i = 0; i < enrollChallenges.length; i++) {
var enrollChallenge = enrollChallenges[i];
return false;
}
seenVersions[version] = version;
- if (!enrollChallenge['appId']) {
+ if (appIdRequired && !enrollChallenge['appId']) {
return false;
}
if (!enrollChallenge['challenge']) {
}
/**
+ * Gets the expanded sign challenges from an enroll request, potentially by
+ * modifying the request to contain a challenge value where one was omitted.
+ * (For enrolling, the server isn't interested in the value of a signature,
+ * only whether the presented key handle is already enrolled.)
+ * @param {Object} request The request.
+ * @param {string} signChallengesName The name of the sign challenges value in
+ * the request.
+ * @param {string=} opt_registeredKeysName The name of the registered keys
+ * value in the request.
+ * @return {Array.<SignChallenge>}
+ */
+function getSignRequestsFromEnrollRequest(request, signChallengesName,
+ opt_registeredKeysName) {
+ 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];
+ }
+ return signChallenges;
+}
+
+/**
* Creates a new object to track enrolling with a gnubby.
* @param {!Countdown} timer Timer for enroll request.
* @param {string} origin The origin making the request.
- * @param {function(ErrorCodes)} errorCb Called upon enroll failure with an
- * error code.
+ * @param {function(U2fError)} errorCb Called upon enroll failure.
* @param {function(string, string, (string|undefined))} successCb Called upon
* enroll success with the version of the succeeding gnubby, the enroll
* data, and optionally the browser data associated with the enrollment.
this.timer_ = timer;
/** @private {string} */
this.origin_ = origin;
- /** @private {function(ErrorCodes)} */
+ /** @private {function(U2fError)} */
this.errorCb_ = errorCb;
/** @private {function(string, string, (string|undefined))} */
this.successCb_ = successCb;
* @param {Array.<EnrollChallenge>} enrollChallenges A set of enroll challenges.
* @param {Array.<SignChallenge>} signChallenges A set of sign challenges for
* existing enrollments for this user and appId.
+ * @param {string=} opt_appId The app id for the entire request.
*/
-Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges) {
- var encodedEnrollChallenges = this.encodeEnrollChallenges_(enrollChallenges);
- var encodedSignChallenges = encodeSignChallenges(signChallenges);
+Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges,
+ opt_appId) {
+ var encodedEnrollChallenges =
+ this.encodeEnrollChallenges_(enrollChallenges, opt_appId);
+ var encodedSignChallenges = encodeSignChallenges(signChallenges, opt_appId);
var request = {
type: 'enroll_helper_request',
enrollChallenges: encodedEnrollChallenges,
// Begin fetching/checking the app ids.
var enrollAppIds = [];
+ if (opt_appId) {
+ enrollAppIds.push(opt_appId);
+ }
for (var i = 0; i < enrollChallenges.length; i++) {
- enrollAppIds.push(enrollChallenges[i]['appId']);
+ if (enrollChallenges[i].hasOwnProperty('appId')) {
+ enrollAppIds.push(enrollChallenges[i]['appId']);
+ }
+ }
+ // Sanity check
+ if (!enrollAppIds.length) {
+ console.warn(UTIL_fmt('empty enroll app ids?'));
+ this.notifyError_({errorCode: ErrorCodes.BAD_REQUEST});
+ return;
}
var self = this;
this.checkAppIds_(enrollAppIds, signChallenges, function(result) {
(self.helperComplete_.bind(self));
self.handler_.run(helperComplete);
} else {
- self.notifyError_(ErrorCodes.OTHER_ERROR);
+ self.notifyError_({errorCode: ErrorCodes.OTHER_ERROR});
}
} else {
- self.notifyError_(ErrorCodes.BAD_REQUEST);
+ self.notifyError_({errorCode: ErrorCodes.BAD_REQUEST});
}
});
};
/**
* Encodes the enroll challenge as an enroll helper challenge.
* @param {EnrollChallenge} enrollChallenge The enroll challenge to encode.
+ * @param {string=} opt_appId The app id for the entire request.
* @return {EnrollHelperChallenge} The encoded challenge.
* @private
*/
-Enroller.encodeEnrollChallenge_ = function(enrollChallenge) {
+Enroller.encodeEnrollChallenge_ = function(enrollChallenge, opt_appId) {
var encodedChallenge = {};
var version;
if (enrollChallenge['version']) {
version = 'U2F_V1';
}
encodedChallenge['version'] = version;
- encodedChallenge['challenge'] = enrollChallenge['challenge'];
- encodedChallenge['appIdHash'] =
- B64_encode(sha256HashOfString(enrollChallenge['appId']));
+ encodedChallenge['challengeHash'] = enrollChallenge['challenge'];
+ var 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);
};
/**
* Encodes the given enroll challenges using this enroller's state.
* @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.
* @private
*/
-Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges) {
+Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges,
+ opt_appId) {
var challenges = [];
for (var i = 0; i < enrollChallenges.length; i++) {
var enrollChallenge = enrollChallenges[i];
this.browserData_[version] =
B64_encode(UTIL_StringToBytes(browserData));
challenges.push(Enroller.encodeEnrollChallenge_(
- /** @type {EnrollChallenge} */ (modifiedChallenge)));
+ /** @type {EnrollChallenge} */ (modifiedChallenge), opt_appId));
} else {
- challenges.push(Enroller.encodeEnrollChallenge_(enrollChallenge));
+ challenges.push(
+ Enroller.encodeEnrollChallenge_(enrollChallenge, opt_appId));
}
}
return challenges;
*/
Enroller.prototype.originChecked_ = function(appIds, cb, result) {
if (!result) {
- this.notifyError_(ErrorCodes.BAD_REQUEST);
+ this.notifyError_({errorCode: ErrorCodes.BAD_REQUEST});
return;
}
/** @private {!AppIdChecker} */
};
/**
- * Notifies the caller with the error code.
- * @param {ErrorCodes} code Error code
+ * Notifies the caller with the error.
+ * @param {U2fError} error Error.
* @private
*/
-Enroller.prototype.notifyError_ = function(code) {
+Enroller.prototype.notifyError_ = function(error) {
if (this.done_)
return;
this.close();
this.done_ = true;
- this.errorCb_(code);
+ this.errorCb_(error);
};
/**
*/
Enroller.prototype.helperComplete_ = function(reply) {
if (reply.code) {
- var reportedError = mapDeviceStatusCodeToErrorCode(reply.code);
+ var reportedError = mapDeviceStatusCodeToU2fError(reply.code);
console.log(UTIL_fmt('helper reported ' + reply.code.toString(16) +
- ', returning ' + reportedError));
+ ', returning ' + reportedError.errorCode));
this.notifyError_(reportedError);
} else {
console.log(UTIL_fmt('Gnubby enrollment succeeded!!!!!'));