/** * These are the default arguments that will be passed to navigator.credentials.create() * unless modified by a specific test case
*/ var createCredentialDefaultArgs = {
options: {
publicKey: { // Relying Party:
rp: {
name: "Acme",
},
// User:
user: {
id: new Uint8Array(16), // Won't survive the copy, must be rebuilt
name: "john.p.smith@example.com",
displayName: "John P. Smith",
},
/** * These are the default arguments that will be passed to navigator.credentials.get() * unless modified by a specific test case
*/ var getCredentialDefaultArgs = {
options: {
publicKey: {
timeout: 60000 // allowCredentials: [newCredential]
}
}
};
function createCredential(opts) {
opts = opts || {};
// set the default options var createArgs = cloneObject(createCredentialDefaultArgs);
let challengeBytes = new Uint8Array(16);
window.crypto.getRandomValues(challengeBytes);
createArgs.options.publicKey.challenge = challengeBytes;
createArgs.options.publicKey.user.id = new Uint8Array(16);
// change the defaults with any options that were passed in
extendObject(createArgs, opts);
// create the credential, return the Promise return navigator.credentials.create(createArgs.options);
}
function assertCredential(credential) { var options = cloneObject(getCredentialDefaultArgs);
let challengeBytes = new Uint8Array(16);
window.crypto.getRandomValues(challengeBytes);
options.challenge = challengeBytes;
options.allowCredentials = [{type: 'public-key', id: credential.rawId}]; return navigator.credentials.get({publicKey: options});
}
function createRandomString(len) { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for(var i = 0; i < len; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
} return text;
}
function ab2str(buf) { return String.fromCharCode.apply(null, new Uint8Array(buf));
}
// Useful constants for working with attestation data const authenticator_data_user_present = 0x01; const authenticator_data_user_verified = 0x04; const authenticator_data_attested_cred_data = 0x40; const authenticator_data_extension_data = 0x80;
function parseAuthenticatorData(buf) { if (buf.byteLength < 37) { thrownew TypeError ("parseAuthenticatorData: buffer must be at least 37 bytes");
}
printHex ("authnrData", buf);
var authnrData = new DataView(buf); var authnrDataObj = {};
authnrDataObj.length = buf.byteLength;
/** * TestCase * * A generic template for test cases * Is intended to be overloaded with subclasses that override testObject, testFunction and argOrder * The testObject is the default arguments for the testFunction * The default testObject can be modified with the modify() method, making it easy to create new tests based on the default * The testFunction is the target of the test and is called by the doIt() method. doIt() applies the testObject as arguments via toArgs() * toArgs() uses argOrder to make sure the resulting array is in the right order of the arguments for the testFunction
*/ class TestCase {
constructor() { this.testFunction = function() { thrownew Error("Test Function not implemented");
}; this.testObject = {}; this.argOrder = []; this.ctx = null;
}
/** * toObject * * return a copy of the testObject
*/
toObject() { return JSON.parse(JSON.stringify(this.testObject)); // cheap clone
}
/** * toArgs * * converts test object to an array that is ordered in the same way as the arguments to the test function
*/
toArgs() { var ret = []; // XXX, TODO: this won't necessarily produce the args in the right order for (let idx of this.argOrder) {
ret.push(this.testObject[idx]);
} return ret;
}
/** * modify * * update the internal object by a path / value combination * e.g. : * modify ("foo.bar", 3) * accepts three types of args: * "foo.bar", 3 * {path: "foo.bar", value: 3} * [{path: "foo.bar", value: 3}, ...]
*/
modify(arg1, arg2) { var mods;
// check for the two argument scenario if (typeof arg1 === "string" && arg2 !== undefined) {
mods = {
path: arg1,
value: arg2
};
} else {
mods = arg1;
}
// accept a single modification object instead of an array if (!Array.isArray(mods) && typeof mods === "object") {
mods = [mods];
}
// iterate through each of the desired modifications, and call recursiveSetObject on them for (let idx in mods) { var mod = mods[idx];
let paths = mod.path.split(".");
recursiveSetObject(this.testObject, paths, mod.value);
}
// iterates through nested `obj` using the `pathArray`, creating the path if it doesn't exist // when the final leaf of the path is found, it is assigned the specified value function recursiveSetObject(obj, pathArray, value) { var currPath = pathArray.shift(); if (typeof obj[currPath] !== "object") {
obj[currPath] = {};
} if (pathArray.length > 0) { return recursiveSetObject(obj[currPath], pathArray, value);
}
obj[currPath] = value;
}
returnthis;
}
/** * actually runs the test function with the supplied arguments
*/
doIt() { if (typeofthis.testFunction !== "function") { thrownew Error("Test function not found");
}
/** * run the test function with the top-level properties of the test object applied as arguments * expects the test to pass, and then validates the results
*/
testPasses(desc) { returnthis.doIt()
.then((ret) => { // check the result this.validateRet(ret); return ret;
});
}
/** * run the test function with the top-level properties of the test object applied as arguments * expects the test to fail
*/
testFails(t, testDesc, expectedErr) { if (typeof expectedErr == "string") { return promise_rejects_dom(t, expectedErr, this.doIt(), "Expected bad parameters to fail");
}
return promise_rejects_js(t, expectedErr, this.doIt(), "Expected bad parameters to fail");
}
/** * Runs the test that's implemented by the class by calling the doIt() function * @param {String} desc A description of the test being run * @param [Error|String] expectedErr A string matching an error type, such as "SecurityError" or an object with a .name value that is an error type string
*/
runTest(desc, expectedErr) {
promise_test((t) => { return Promise.resolve().then(() => { returnthis.testSetup();
}).then(() => { if (expectedErr === undefined) { returnthis.testPasses(desc);
} else { returnthis.testFails(t, desc, expectedErr);
}
}).then((res) => { returnthis.testTeardown(res);
})
}, desc)
}
/** * called before runTest * virtual method expected to be overridden by child class if needed
*/
testSetup() { if (this.beforeTestFn) { this.beforeTestFn.call(this);
}
return Promise.resolve();
}
/** * Adds a callback function that gets called in the TestCase context * and within the testing process.
*/
beforeTest(fn) { if (typeof fn !== "function") { thrownew Error ("Tried to call non-function before test");
}
this.beforeTestFn = fn;
returnthis;
}
/** * called after runTest * virtual method expected to be overridden by child class if needed
*/
testTeardown(res) { if (this.afterTestFn) { this.afterTestFn.call(this, res);
}
return Promise.resolve();
}
/** * Adds a callback function that gets called in the TestCase context * and within the testing process. Good for validating results.
*/
afterTest(fn) { if (typeof fn !== "function") { thrownew Error ("Tried to call non-function after test");
}
this.afterTestFn = fn;
returnthis;
}
/** * validates the value returned from the test function * virtual method expected to be overridden by child class
*/
validateRet() { thrownew Error("Not implemented");
}
}
function cloneObject(o) { return JSON.parse(JSON.stringify(o));
}
function isSimpleObject(o) { return (typeof o === "object" &&
!Array.isArray(o) &&
!(o instanceof ArrayBuffer) &&
!(o instanceof Uint8Array));
}
function isAbortSignal(o) { return (o instanceof AbortSignal);
}
/** * CreateCredentialTest * * tests the WebAuthn navigator.credentials.create() interface
*/ class CreateCredentialsTest extends TestCase {
constructor() { // initialize the parent class super();
// the function to be tested this.testFunction = navigator.credentials.create; // the context to call the test function with (i.e. - the 'this' object for the function) this.ctx = navigator.credentials;
// the default object to pass to makeCredential, to be modified with modify() for various tests
let challengeBytes = new Uint8Array(16);
window.crypto.getRandomValues(challengeBytes); this.testObject = cloneObject(createCredentialDefaultArgs); // cloneObject can't clone the BufferSource in user.id, so let's recreate it. this.testObject.options.publicKey.user.id = new Uint8Array(16); this.testObject.options.publicKey.challenge = challengeBytes;
// how to order the properties of testObject when passing them to makeCredential this.argOrder = [ "options"
];
// enable the constructor to modify the default testObject // would prefer to do this in the super class, but have to call super() before using `this.*` if (arguments.length) this.modify(...arguments);
}
/** * GetCredentialsTest * * tests the WebAuthn navigator.credentials.get() interface
*/ class GetCredentialsTest extends TestCase {
constructor(...args) { // initialize the parent class super();
// the function to be tested this.testFunction = navigator.credentials.get; // the context to call the test function with (i.e. - the 'this' object for the function) this.ctx = navigator.credentials;
// default arguments
let challengeBytes = new Uint8Array(16);
window.crypto.getRandomValues(challengeBytes); this.testObject = cloneObject(getCredentialDefaultArgs); this.testObject.options.publicKey.challenge = challengeBytes;
// how to order the properties of testObject when passing them to makeCredential this.argOrder = [ "options"
];
this.credentialPromiseList = [];
// set to true to pass an empty allowCredentials list to credentials.get this.isResidentKeyTest = false;
// enable the constructor to modify the default testObject // would prefer to do this in the super class, but have to call super() before using `this.*` if (arguments.length) { if (args.cred instanceof Promise) this.credPromise = args.cred; elseif (typeof args.cred === "object") this.credPromise = Promise.resolve(args.cred); delete args.cred; this.modify(...arguments);
}
}
addCredential(arg) { // if a Promise was passed in, add it to the list if (arg instanceof Promise) { this.credentialPromiseList.push(arg); returnthis;
}
// if a credential object was passed in, convert it to a Promise for consistency if (typeof arg === "object") { this.credentialPromiseList.push(Promise.resolve(arg)); returnthis;
}
// if no credential specified then create one var p = createCredential(); this.credentialPromiseList.push(p);
returnthis;
}
testSetup(desc) { if (!this.credentialPromiseList.length) { thrownew Error("Attempting list without defining credential to test");
}
/** * converts a uint8array to base64 url-safe encoding * based on similar function in resources/utils.js
*/ function base64urlEncode(array) {
let string = String.fromCharCode.apply(null, array);
let result = btoa(string); return result.replace(/=+$/g, '').replace(/\+/g, "-").replace(/\//g, "_");
} /** * runs assertions against a PublicKeyCredential object to ensure it is properly formatted
*/ function validatePublicKeyCredential(cred) { // class
assert_class_string(cred, "PublicKeyCredential", "Expected return to be instance of 'PublicKeyCredential' class"); // id
assert_idl_attribute(cred, "id", "should return PublicKeyCredential with id attribute");
assert_readonly(cred, "id", "should return PublicKeyCredential with readonly id attribute"); // rawId
assert_idl_attribute(cred, "rawId", "should return PublicKeyCredential with rawId attribute");
assert_readonly(cred, "rawId", "should return PublicKeyCredential with readonly rawId attribute");
assert_equals(cred.id, base64urlEncode(new Uint8Array(cred.rawId)), "should return PublicKeyCredential with id attribute set to base64 encoding of rawId attribute");
// type
assert_idl_attribute(cred, "type", "should return PublicKeyCredential with type attribute");
assert_equals(cred.type, "public-key", "should return PublicKeyCredential with type 'public-key'");
}
/** * runs assertions against a AuthenticatorAttestationResponse object to ensure it is properly formatted
*/ function validateAuthenticatorAttestationResponse(attr) { // class
assert_class_string(attr, "AuthenticatorAttestationResponse", "Expected credentials.create() to return instance of 'AuthenticatorAttestationResponse' class");
// clientDataJSON
assert_idl_attribute(attr, "clientDataJSON", "credentials.create() should return AuthenticatorAttestationResponse with clientDataJSON attribute");
assert_readonly(attr, "clientDataJSON", "credentials.create() should return AuthenticatorAttestationResponse with readonly clientDataJSON attribute"); // TODO: clientDataJSON() and make sure fields are correct
// attestationObject
assert_idl_attribute(attr, "attestationObject", "credentials.create() should return AuthenticatorAttestationResponse with attestationObject attribute");
assert_readonly(attr, "attestationObject", "credentials.create() should return AuthenticatorAttestationResponse with readonly attestationObject attribute"); // TODO: parseAuthenticatorData() and make sure flags are correct
}
/** * runs assertions against a AuthenticatorAssertionResponse object to ensure it is properly formatted
*/ function validateAuthenticatorAssertionResponse(assert) { // class
assert_class_string(assert, "AuthenticatorAssertionResponse", "Expected credentials.create() to return instance of 'AuthenticatorAssertionResponse' class");
// clientDataJSON
assert_idl_attribute(assert, "clientDataJSON", "credentials.get() should return AuthenticatorAssertionResponse with clientDataJSON attribute");
assert_readonly(assert, "clientDataJSON", "credentials.get() should return AuthenticatorAssertionResponse with readonly clientDataJSON attribute"); // TODO: clientDataJSON() and make sure fields are correct
// signature
assert_idl_attribute(assert, "signature", "credentials.get() should return AuthenticatorAssertionResponse with signature attribute");
assert_readonly(assert, "signature", "credentials.get() should return AuthenticatorAssertionResponse with readonly signature attribute");
// authenticatorData
assert_idl_attribute(assert, "authenticatorData", "credentials.get() should return AuthenticatorAssertionResponse with authenticatorData attribute");
assert_readonly(assert, "authenticatorData", "credentials.get() should return AuthenticatorAssertionResponse with readonly authenticatorData attribute"); // TODO: parseAuthenticatorData() and make sure flags are correct
}
function standardSetup(cb, options = {}) { // Setup an automated testing environment if available.
let authenticatorArgs = Object.assign(defaultAuthenticatorArgs(), options);
window.test_driver.add_virtual_authenticator(authenticatorArgs)
.then(authenticator => {
cb(); // XXX add a subtest to clean up the virtual authenticator since // testharness does not support waiting for promises on cleanup.
promise_test(
() =>
window.test_driver.remove_virtual_authenticator(authenticator), 'Clean up the test environment');
})
.catch(error => { if (error !== 'error: Action add_virtual_authenticator not implemented') { throw error;
} // The protocol is not available. Continue manually.
cb();
});
}
// virtualAuthenticatorPromiseTest runs |testCb| in a promise_test with a // virtual authenticator set up before and destroyed after the test, if the // virtual testing API is available. In manual tests, setup and teardown is // skipped. function virtualAuthenticatorPromiseTest(
testCb, options = {}, name = 'Virtual Authenticator Test') {
let authenticatorArgs = Object.assign(defaultAuthenticatorArgs(), options);
promise_test(async t => {
let authenticator; try {
authenticator =
await window.test_driver.add_virtual_authenticator(authenticatorArgs);
t.add_cleanup(
() => window.test_driver.remove_virtual_authenticator(authenticator));
} catch (error) { if (error !== 'error: Action add_virtual_authenticator not implemented') { throw error;
}
} return testCb(t, authenticator);
}, name);
}
function bytesEqual(a, b) { if (a instanceof ArrayBuffer) {
a = new Uint8Array(a);
} if (b instanceof ArrayBuffer) {
b = new Uint8Array(b);
} if (a.byteLength != b.byteLength) { returnfalse;
} for (let i = 0; i < a.byteLength; i++) { if (a[i] != b[i]) { returnfalse;
}
} returntrue;
}
// Compares two PublicKeyCredentialUserEntity objects. function userEntityEquals(a, b) { return bytesEqual(a.id, b.id) && a.name == b.name && a.displayName == b.displayName;
}
// Asserts that `actual` and `expected`, which are both JSON types, are equal. // The object key order is ignored for comparison. function assertJsonEquals(actual, expected, optMsg) { // Returns a copy of `jsonObj`, which must be a JSON type, with object keys // recursively sorted in lexicographic order; or simply `jsonObj` if it is not // an instance of Object. function deepSortKeys(jsonObj) { if (jsonObj instanceof Array) { return Array.from(jsonObj, (x) => { return deepSortKeys(x); })
} if (typeof jsonObj !== 'object' || jsonObj === null ||
jsonObj.__proto__.constructor !== Object ||
Object.keys(jsonObj).length === 0) { return jsonObj;
} return Object.keys(jsonObj).sort().reduce((acc, key) => {
acc[key] = deepSortKeys(jsonObj[key]); return acc;
}, {});
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.