// META: script=/resources/testdriver.js // META: script=/resources/testdriver-vendor.js // META: script=/resources/utils.js // META: script=helpers.js function assertObjectKeysEq(a, b) { let a_keys = new Set(Object.keys(a)); let b_keys = new Set(Object.keys(b)); assert_true( a_keys.length == b_keys.length && [...a_keys].every(k => b_keys.has(k)), `keys differ: ${a_keys} != ${b_keys}`); } // Returns the JSON encoding for `value`. If `value` is a function, `optParent` // is the object to which execution should be bound. function convertValue(value, optParent) { switch (typeof value) { case 'undefined': case 'boolean': case 'number': case 'bigint': case 'string': case 'symbol': return value; case 'function': return value.apply(optParent); case 'object': if (value.__proto__.constructor === Object) { var result = {}; Object.entries(value).map((k, v) => { result[k] = convertValue(k, v); }); return result; } if (value instanceof Array) { return value.map(convertValue); } if (value instanceof ArrayBuffer) { return base64urlEncode(new Uint8Array(value)); } throw `can't convert value ${value} in ${parent}`; default: throw `${value} has unexpected type`; } } // Conversion spec for a single attribute. // @typedef {Object} ConvertParam // @property {string} name - The name of the attribute to convert from // @property {string=} target - The name of the attribute to convert to, if // different from `name` // @property {function=} func - Method to convert this property. Defaults to // convertValue(). // Returns the JSON object for `obj`. // // @param obj // @param {Array<(string|ConvertParam)>} keys - The names of parameters in // `obj` to convert, or instances of ConvertParam for complex cases. function convertObject(obj, params) { let result = {}; params.forEach((param) => { switch (typeof (param)) { case 'string': assert_true(param in obj, `missing ${param}`); if (obj[param] !== null) { result[param] = convertValue(obj[param], obj); } break; case 'object': assert_true(param.name in obj, `missing ${param.name}`); const val = obj[param.name]; const target_key = param.target || param.name; const convert_func = param.func || convertValue; try { result[target_key] = convert_func(((typeof val) == 'function' ? val.apply(obj) : val)); } catch (e) { throw `failed to convert ${param.name}: ${e}` } break; default: throw `invalid key ${param}`; } }); return result; } // Converts an AuthenticatorResponse instance into a JSON object. // @param {!AuthenticatorResponse} function authenticatorResponseToJson(response) { assert_true( (response instanceof AuthenticatorAttestationResponse) || (response instanceof AuthenticatorAssertionResponse)); const isAttestation = (response instanceof AuthenticatorAttestationResponse); const keys = (isAttestation ? [ 'clientDataJSON', 'attestationObject', {name: 'getAuthenticatorData', target: 'authenticatorData'}, {name: 'getPublicKey', target: 'publicKey'}, {name: 'getPublicKeyAlgorithm', target: 'publicKeyAlgorithm'}, {name: 'getTransports', target: 'transports'} ] : ['clientDataJSON', 'authenticatorData', 'signature', 'userHandle']); return convertObject(response, keys); } // Converts a PublicKeyCredential instance to a JSON object. // @param {!PublicKeyCredential} function publicKeyCredentialToJson(cred) { const keys = [ 'id', 'rawId', {name: 'response', func: authenticatorResponseToJson}, 'authenticatorAttachment', {name: 'getClientExtensionResults', target: 'clientExtensionResults'}, 'type' ]; return convertObject(cred, keys); } virtualAuthenticatorPromiseTest( async t => { let credential = await createCredential(); assertJsonEquals( credential.toJSON(), publicKeyCredentialToJson(credential)); let assertion = await assertCredential(credential); assertJsonEquals( assertion.toJSON(), publicKeyCredentialToJson(assertion)); }, { protocol: 'ctap2_1', transport: 'usb', }, 'toJSON()');