Commit e3c917a0 authored by Casey Piper's avatar Casey Piper Committed by Commit Bot

WebAuthn: Call WebAuthn register APIs from Cryptotoken

When a registration request is received by Cryptotoken,
proxy that request to WebAuthn.

Bug: 906881
Change-Id: Ie3aac8bfb8bd4349eb29e7741bd5242b70842236
Reviewed-on: https://chromium-review.googlesource.com/c/1342687
Commit-Queue: Casey Piper <piperc@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Reviewed-by: default avatarKim Paulhamus <kpaulhamus@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611307}
parent 25617f72
...@@ -151,6 +151,7 @@ ...@@ -151,6 +151,7 @@
<include name="IDR_CRYPTOTOKEN_USBGNUBBYFACTORY_JS" file="cryptotoken/usbgnubbyfactory.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_USBGNUBBYFACTORY_JS" file="cryptotoken/usbgnubbyfactory.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_DEVICESTATUSCODES_JS" file="cryptotoken/devicestatuscodes.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_DEVICESTATUSCODES_JS" file="cryptotoken/devicestatuscodes.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_ASN1_JS" file="cryptotoken/asn1.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_ASN1_JS" file="cryptotoken/asn1.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_CBOR_JS" file="cryptotoken/cbor.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_ENROLLER_JS" file="cryptotoken/enroller.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_ENROLLER_JS" file="cryptotoken/enroller.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_USBENROLLHANDLER_JS" file="cryptotoken/usbenrollhandler.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_USBENROLLHANDLER_JS" file="cryptotoken/usbenrollhandler.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_REQUESTQUEUE_JS" file="cryptotoken/requestqueue.js" type="BINDATA" /> <include name="IDR_CRYPTOTOKEN_REQUESTQUEUE_JS" file="cryptotoken/requestqueue.js" type="BINDATA" />
......
...@@ -8,6 +8,7 @@ js_type_check("closure_compile") { ...@@ -8,6 +8,7 @@ js_type_check("closure_compile") {
deps = [ deps = [
":approvedorigins", ":approvedorigins",
":b64", ":b64",
":cbor",
":closeable", ":closeable",
":countdown", ":countdown",
":errorcodes", ":errorcodes",
...@@ -28,6 +29,9 @@ js_library("b64") { ...@@ -28,6 +29,9 @@ js_library("b64") {
js_library("approvedorigins") { js_library("approvedorigins") {
} }
js_library("cbor") {
}
js_library("closeable") { js_library("closeable") {
} }
......
// Copyright 2018 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.
'use strict';
class Cbor {
constructor(buffer) {
this.slice = new Uint8Array(buffer);
}
get data() {
return this.slice;
}
get length() {
return this.slice.length;
}
get empty() {
return this.slice.length == 0;
}
get hex() {
const hexTable = '0123456789abcdef';
let s = '';
for (let i = 0; i < this.data.length; i++) {
s += hexTable.charAt(this.data[i] >> 4);
s += hexTable.charAt(this.data[i] & 15);
}
return s;
}
base64Encode(chars, padding) {
const len3 = 3 * Math.floor(this.slice.length / 3);
var chunks = [];
for (let i = 0; i < len3; i += 3) {
const v =
(this.slice[i] << 16) + (this.slice[i + 1] << 8) + this.slice[i + 2];
chunks.push(
chars[v >> 18] + chars[(v >> 12) & 0x3f] + chars[(v >> 6) & 0x3f] +
chars[v & 0x3f]);
}
const remainder = this.slice.length - len3;
if (remainder == 1) {
const v = this.slice[len3];
chunks.push(chars[v >> 2] + chars[(v << 4) & 0x3f]);
if (padding == 1 /* Include */) {
chunks.push('==');
}
} else if (remainder == 2) {
const v = (this.slice[len3] << 8) + this.slice[len3 + 1];
chunks.push(
chars[v >> 10] + chars[(v >> 4) & 0x3f] + chars[(v << 2) & 0x3f]);
if (padding == 1 /* Include */) {
chunks.push('=');
}
}
return chunks.join('');
}
webSafeBase64() {
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
return this.base64Encode(chars, 0 /* None */);
}
base64() {
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
return this.base64Encode(chars, 1 /* Include */);
}
compare(other) {
if (this.length < other.length) {
return -1;
} else if (this.length > other.length) {
return 1;
}
for (let i = 0; i < this.length; i++) {
if (this.slice[i] < other.slice[i]) {
return -1;
} else if (this.slice[i] > other.slice[i]) {
return 1;
}
}
return 0;
}
getU8() {
if (this.empty) {
throw('Cbor: empty during getU8');
}
const byte = this.slice[0];
this.slice = this.slice.subarray(1);
return byte;
}
skip(n) {
if (this.length < n) {
throw('Cbor: too few bytes to skip');
}
this.slice = this.slice.subarray(n);
}
getBytes(n) {
if (this.length < n) {
throw('Cbor: insufficient bytes in getBytes');
}
const ret = this.slice.subarray(0, n);
this.slice = this.slice.subarray(n);
return ret;
}
getUnsigned(n) {
const bytes = this.getBytes(n);
let value = 0;
for (let i = 0; i < n; i++) {
value <<= 8;
value |= bytes[i];
}
return value;
}
getU16() {
return this.getUnsigned(2);
}
getU32() {
return this.getUnsigned(4);
}
getASN1_(expectedTag, includeHeader) {
if (this.empty) {
throw 'getASN1: empty slice, expected tag ' + expectedTag;
}
const v = this.getAnyASN1();
if (v.tag != expectedTag) {
throw 'getASN1: got tag ' + v.tag + ', want ' + expectedTag;
}
if (!includeHeader) {
v.val.skip(v.headerLen);
}
return v.val;
}
getASN1(expectedTag) {
return this.getASN1_(expectedTag, false);
}
getASN1Element(expectedTag) {
return this.getASN1_(expectedTag, true);
}
getOptionalASN1(expectedTag) {
if (this.slice.length < 1 || this.slice[0] != expectedTag) {
return null;
}
return this.getASN1(expectedTag);
}
getAnyASN1() {
const header = new Cbor(this.slice);
const tag = header.getU8();
const lengthByte = header.getU8();
if ((tag & 0x1f) == 0x1f) {
throw 'getAnyASN1: long-form tag found';
}
let len = 0;
let headerLen = 0;
if ((lengthByte & 0x80) == 0) {
// Short form length.
len = lengthByte + 2;
headerLen = 2;
} else {
// The high bit indicates that this is the long form, while the next 7
// bits encode the number of subsequent octets used to encode the length
// (ITU-T X.690 clause 8.1.3.5.b).
const numBytes = lengthByte & 0x7f;
// Bitwise operations are always on signed 32-bit two's complement
// numbers. This check ensures that we stay under this limit. We could
// do this in a better way, but there's no need to process very large
// objects.
if (numBytes == 0 || numBytes > 3) {
throw 'getAnyASN1: bad ASN.1 long-form length';
}
const lengthBytes = header.getBytes(numBytes);
for (let i = 0; i < numBytes; i++) {
len <<= 8;
len |= lengthBytes[i];
}
if (len < 128 || (len >> ((numBytes - 1) * 8)) == 0) {
throw 'getAnyASN1: incorrectly encoded ASN.1 length';
}
headerLen = 2 + numBytes;
len += headerLen;
}
if (this.slice.length < len) {
throw 'getAnyASN1: too few bytes in input';
}
const prefix = this.slice.subarray(0, len);
this.slice = this.slice.subarray(len);
return {tag: tag, headerLen: headerLen, val: new Cbor(prefix)};
}
getBase128Int() {
let lookahead = this.slice.length;
if (lookahead > 4) {
lookahead = 4;
}
let len = 0;
for (let i = 0; i < lookahead; i++) {
if (!(this.slice[i] & 0x80)) {
len = i + 1;
break;
}
}
if (len == 0) {
throw 'base128 value too large';
}
let n = 0;
let octets = this.getBytes(len);
for (let i = 0; i < len; i++) {
if ((n & 0xff000000) != 0) {
throw 'base128 value too large';
}
n <<= 7;
n |= octets[i] & 0x7f;
}
return n;
}
getASN1ObjectIdentifier() {
let b = this.getASN1(6 /* OBJECT */);
let first = b.getBase128Int();
let result = [0, 0];
result[1] = first % 40;
result[0] = (first - result[1]) / 40;
while (!b.empty) {
result.push(b.getBase128Int());
}
return result;
}
getCBORHeader() {
const copy = new Cbor(this.slice);
const a = this.getU8();
const majorType = a >> 5;
const info = a & 31;
if (info < 24) {
return [majorType, info, new Cbor(copy.getBytes(1))];
} else if (info < 28) {
const lengthLength = 1 << (info - 24);
let data = this.getBytes(lengthLength);
let value = 0;
for (let i = 0; i < lengthLength; i++) {
// Javascript has problems handling uint64s given the limited range of
// a double.
if (value > 35184372088831) {
throw('Cbor: cannot represent CBOR number');
}
// Not using bitwise operations to avoid truncating to 32 bits.
value *= 256;
value += data[i];
}
switch (lengthLength) {
case 1:
if (value < 24) {
throw('Cbor: value should have been encoded in single byte');
}
break;
case 2:
if (value < 256) {
throw('Cbor: non-minimal integer');
}
break;
case 4:
if (value < 65536) {
throw('Cbor: non-minimal integer');
}
break;
case 8:
if (value < 4294967296) {
throw('Cbor: non-minimal integer');
}
break;
}
return [majorType, value, new Cbor(copy.getBytes(1 + lengthLength))];
} else {
throw('Cbor: CBOR contains unhandled info value ' + info);
}
}
getCBOR() {
const [major, value] = this.getCBORHeader();
switch (major) {
case 0:
return value;
case 1:
return 0 - (1 + value);
case 2:
return this.getBytes(value);
case 3:
return this.getBytes(value);
case 4: {
let ret = new Array(value);
for (let i = 0; i < value; i++) {
ret[i] = this.getCBOR();
}
return ret;
}
case 5:
if (value == 0) {
return {};
}
let copy = new Cbor(this.data);
const [firstKeyMajor] = copy.getCBORHeader();
if (firstKeyMajor == 3) {
// String-keyed map.
let lastKeyHeader = new Cbor(new Uint8Array(0));
let lastKeyBytes = new Cbor(new Uint8Array(0));
let ret = {};
for (let i = 0; i < value; i++) {
const [keyMajor, keyLength, keyHeader] = this.getCBORHeader();
if (keyMajor != 3) {
throw('Cbor: non-string in string-valued map');
}
const keyBytes = new Cbor(this.getBytes(keyLength));
if (i > 0) {
const headerCmp = lastKeyHeader.compare(keyHeader);
if (headerCmp > 0 ||
(headerCmp == 0 && lastKeyBytes.compare(keyBytes) >= 0)) {
throw(
'Cbor: map keys in wrong order: ' + lastKeyHeader.hex +
'/' + lastKeyBytes.hex + ' ' + keyHeader.hex + '/' +
keyBytes.hex);
}
}
lastKeyHeader = keyHeader;
lastKeyBytes = keyBytes;
ret[keyBytes.parseUTF8()] = this.getCBOR();
}
return ret;
} else if (firstKeyMajor == 0 || firstKeyMajor == 1) {
// Number-keyed map.
let lastKeyHeader = new Cbor(new Uint8Array(0));
let ret = {};
for (let i = 0; i < value; i++) {
let [keyMajor, keyValue, keyHeader] = this.getCBORHeader();
if (keyMajor != 0 && keyMajor != 1) {
throw('Cbor: non-number in number-valued map');
}
if (i > 0 && lastKeyHeader.compare(keyHeader) >= 0) {
throw(
'Cbor: map keys in wrong order: ' + lastKeyHeader.hex + ' ' +
keyHeader.hex);
}
lastKeyHeader = keyHeader;
if (keyMajor == 1) {
keyValue = 0 - (1 + keyValue);
}
ret[keyValue] = this.getCBOR();
}
return ret;
} else {
throw('Cbor: map keyed by invalid major type ' + firstKeyMajor);
}
default:
throw('Cbor: unhandled major type ' + major);
}
}
parseUTF8() {
return (new TextDecoder('utf-8')).decode(this.slice);
}
}
...@@ -102,11 +102,15 @@ function transportType(der) { ...@@ -102,11 +102,15 @@ function transportType(der) {
* makeCertAndKey creates a new ECDSA keypair and returns the private key * makeCertAndKey creates a new ECDSA keypair and returns the private key
* and a cert containing the public key. * and a cert containing the public key.
* *
* @param {!Uint8Array} original The certificate being replaced, as DER bytes. * @param {!Uint8Array=} opt_original The certificate being replaced, as DER
* bytes.
* @return {Promise<{privateKey: !webCrypto.CryptoKey, certDER: !Uint8Array}>} * @return {Promise<{privateKey: !webCrypto.CryptoKey, certDER: !Uint8Array}>}
*/ */
async function makeCertAndKey(original) { async function makeCertAndKey(opt_original) {
var transport = transportType(original); var transport = null;
if (opt_original) {
transport = transportType(opt_original);
}
if (transport !== null) { if (transport !== null) {
if (transport.length != 2) { if (transport.length != 2) {
throw Error('bad extension length'); throw Error('bad extension length');
...@@ -805,25 +809,230 @@ Enroller.prototype.sendEnrollRequestToHelper_ = function() { ...@@ -805,25 +809,230 @@ Enroller.prototype.sendEnrollRequestToHelper_ = function() {
return; return;
} }
var self = this; var self = this;
this.checkAppIds_(enrollAppIds, function(result) { this.checkAppIds_(enrollAppIds, async (result) => {
if (self.done_) if (self.done_)
return; return;
if (result) { if (result) {
self.handler_ = FACTORY_REGISTRY.getRequestHelper().getHandler(request); // AppID is valid, so the request should be sent.
if (self.handler_) { await new Promise(resolve => {
var helperComplete = if (!chrome.cryptotokenPrivate || !window.PublicKeyCredential) {
/** @type {function(HelperReply)} */ resolve(false);
(self.helperComplete_.bind(self)); } else {
self.handler_.run(helperComplete); chrome.cryptotokenPrivate.canProxyToWebAuthn(resolve);
} else { }
self.notifyError_({errorCode: ErrorCodes.OTHER_ERROR}); }).then(shouldUseWebAuthn => {
} let v2Challenge;
for (let index = 0; index < self.enrollChallenges_.length; index++) {
if (self.enrollChallenges_[index]['version'] === 'U2F_V2') {
v2Challenge = self.enrollChallenges_[index]['challenge'];
}
}
if (v2Challenge && shouldUseWebAuthn) {
// If we can proxy to WebAuthn, send the request via WebAuthn.
this.doRegisterWebAuthn_(enrollAppIds[0], v2Challenge, request);
} else {
self.handler_ =
FACTORY_REGISTRY.getRequestHelper().getHandler(request);
if (self.handler_) {
var helperComplete =
/** @type {function(HelperReply)} */
(self.helperComplete_.bind(self));
self.handler_.run(helperComplete);
} else {
self.notifyError_({errorCode: ErrorCodes.OTHER_ERROR});
}
}
});
} else { } else {
self.notifyError_({errorCode: ErrorCodes.BAD_REQUEST}); self.notifyError_({errorCode: ErrorCodes.BAD_REQUEST});
} }
}); });
}; };
/**
* Proxies the registration request over the WebAuthn API.
* @private
*/
Enroller.prototype.doRegisterWebAuthn_ = function(appId, challenge, request) {
// Set a random ID.
const randomId = new Uint8Array(new ArrayBuffer(16));
crypto.getRandomValues(randomId);
const excludeList = [];
for (let index = 0; index < request['signData'].length; index++) {
const element = request['signData'][index];
excludeList.push({
type: 'public-key',
id: new Uint8Array(B64_decode(element['keyHandle'])).buffer,
transports: ['usb'],
});
}
const options = {
publicKey: {
rp: {
id: appId,
name: this.sender_.origin,
},
user: {
id: randomId.buffer,
displayName: this.sender_.origin,
name: this.sender_.origin,
},
challenge: new Uint8Array(B64_decode(challenge)).buffer,
pubKeyCredParams: [{
type: 'public-key',
alg: -7, // ES-256
}],
timeout: this.timer_.millisecondsUntilExpired(),
excludeCredentials: excludeList,
authenticatorSelection: {
authenticatorAttachment: 'cross-platform',
requireResidentKey: false,
userVerification: 'discouraged',
},
attestation: 'direct',
},
};
navigator.credentials.create(options)
.then(response => {
this.onWebAuthnSuccess_(response, appId);
})
.catch(exception => {
this.onWebAuthnError_(exception);
});
};
/**
* Handles a successful credential response from WebAuthn's make credential
* request.
* @private
*/
Enroller.prototype.onWebAuthnSuccess_ =
async function(publicKeyCredential, appId) {
const clientData =
new Uint8Array(publicKeyCredential['response']['clientDataJSON']);
const browserData = B64_encode(Array.from(clientData));
const u2fResponseData = await this.parseU2fResponseFromAttestationObject_(
publicKeyCredential['response']['attestationObject'], appId, browserData);
this.notifySuccess_('U2F_V2', u2fResponseData, browserData);
};
/**
* Parses the attestation object received from a WebAuthn make credential call
* and converts it into a U2F response message formatted into Base64.
* @private
*/
Enroller.prototype.parseU2fResponseFromAttestationObject_ =
async function(attestationObject, appId, clientData) {
// The first byte of the registration response is always 0x5.
let u2fResponse = [0x5];
// Parse the attestation object from CBOR into a JavaScript object.
const attestationObjectCbor = new Cbor(attestationObject).getCBOR();
// Authenticator data must be at least 120 bytes in length.
// https://www.w3.org/TR/webauthn/#fig-attStructs
if (!attestationObjectCbor['authData'] ||
attestationObjectCbor['authData'].length < 120) {
console.warn('Received invalid authenticator response');
this.notifyError_({
errorCode: ErrorCodes.OTHER_ERROR,
errorMessage: 'Invalid response message',
});
return;
}
const authData = attestationObjectCbor['authData'];
// Attested credential data starts after a 32 byte RP ID hash, a 1 byte flag,
// and a 4 byte counter value.
// https://www.w3.org/TR/webauthn/#sctn-attestation
const attestedCredentialData = authData.slice(37, authData.length);
let index = 16;
let credentialIdLength = (attestedCredentialData[index++] & 0xFF) << 8;
credentialIdLength |= (attestedCredentialData[index++] & 0xFF);
const credentialId =
attestedCredentialData.slice(index, index + credentialIdLength);
index += credentialIdLength;
const encodedPublicKey =
attestedCredentialData.slice(index, attestedCredentialData.length);
// Parse public key and format it in X509 format [0x4, 32-byte X, 32-byte Y].
const coseKey = new Cbor(encodedPublicKey).getCBOR();
const publicKeyArray = ([0x4].concat(Array.from(coseKey['-2'])))
.concat(Array.from(coseKey['-3']));
// Concatenate U2F registration response from the public key, key handle
// length, key handle, attestatation certificate, and signature.
u2fResponse = u2fResponse.concat(publicKeyArray);
u2fResponse.push(credentialIdLength);
u2fResponse = u2fResponse.concat(Array.from(credentialId));
const fmt = attestationObjectCbor['fmt'];
const attStatement = attestationObjectCbor['attStmt'];
let x5c;
let signature;
switch (new TextDecoder('utf-8').decode(fmt)) {
case 'fido-u2f':
x5c = attStatement['x5c'][0];
signature = attStatement['sig'];
break;
case 'none':
// Append empty x509 cert and signature to the registration message.
const emptySequence = new Uint8Array([0x30, 0]); // empty ASN.1 SEQUENCE.
const registrationData =
B64_encode(u2fResponse.concat(Array.from(emptySequence))
.concat(Array.from(emptySequence)));
const reg = new Registration(registrationData, appId, null, clientData);
const keypair = await makeCertAndKey();
signature = await reg.sign(keypair.privateKey);
x5c = keypair.certDER;
break;
default:
console.warn('Received unsupported non-U2F attestation');
this.notifyError_({
errorCode: ErrorCodes.OTHER_ERROR,
errorMessage: 'Invalid response message',
});
return;
}
u2fResponse = u2fResponse.concat(Array.from(x5c));
u2fResponse = u2fResponse.concat(Array.from(signature));
return B64_encode(u2fResponse);
};
/**
* Handles DOMExceptions returned as errors from the WebAuthn make credential
* call. Converts exceptions into U2F compatible exceptions.
* @param {*} exception Exception returned from the WebAuthn request.
* @private
*/
Enroller.prototype.onWebAuthnError_ = function(exception) {
const domError = /** @type {!DOMException} */ (exception);
let errorCode = ErrorCodes.OTHER_ERROR;
let errorDetails;
if (domError && domError.name) {
switch (domError.name) {
case 'NotAllowedError':
errorCode = ErrorCodes.TIMEOUT;
break;
case 'InvalidStateError':
errorCode = ErrorCodes.DEVICE_INELIGIBLE;
break;
default:
// Fall through
break;
}
}
this.notifyError_({
errorCode: errorCode,
errorMessage: domError.toString(),
});
};
/** /**
* 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.
......
{ {
"name": "CryptoTokenExtension", "name": "CryptoTokenExtension",
"description": "CryptoToken Component Extension", "description": "CryptoToken Component Extension",
"version": "0.9.73", "version": "0.9.74",
"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": [
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
"scripts": [ "scripts": [
"util.js", "util.js",
"b64.js", "b64.js",
"cbor.js",
"sha256.js", "sha256.js",
"timer.js", "timer.js",
"countdown.js", "countdown.js",
...@@ -48,7 +49,7 @@ ...@@ -48,7 +49,7 @@
"factoryregistry.js", "factoryregistry.js",
"closeable.js", "closeable.js",
"requesthelper.js", "requesthelper.js",
"asn1.js", "asn1.js",
"enroller.js", "enroller.js",
"requestqueue.js", "requestqueue.js",
"signer.js", "signer.js",
......
...@@ -643,9 +643,11 @@ void AuthenticatorImpl::MakeCredential( ...@@ -643,9 +643,11 @@ void AuthenticatorImpl::MakeCredential(
// used to communicate with the origin. // used to communicate with the origin.
if (OriginIsCryptoTokenExtension(caller_origin_)) { if (OriginIsCryptoTokenExtension(caller_origin_)) {
// As Cryptotoken validates the origin, accept the relying party id as the // As Cryptotoken validates the origin, accept the relying party id as the
// origin from requests originating from Cryptotoken. // origin from requests originating from Cryptotoken. The origin is provided
// in Cryptotoken requests as the relying party name, which should be used
// as part of client data.
client_data_json_ = SerializeCollectedClientDataToJson( client_data_json_ = SerializeCollectedClientDataToJson(
client_data::kU2fRegisterType, relying_party_id_, client_data::kU2fRegisterType, options->relying_party->name,
std::move(options->challenge), true /* use_legacy_u2f_type_key */); std::move(options->challenge), true /* use_legacy_u2f_type_key */);
} else { } else {
client_data_json_ = SerializeCollectedClientDataToJson( client_data_json_ = SerializeCollectedClientDataToJson(
......
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