summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webauthn/public-key-credential-to-json.https.window.js
blob: 8de3b8c3cd09ac92fc2f1ade93e3ab7888cddc72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// META: script=/resources/testharness.js
// META: script=/resources/testharnessreport.js
// 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: '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);
}

// 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 (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;
  }, {});
}

// 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) {
  assert_equals(
      JSON.stringify(deepSortKeys(actual)),
      JSON.stringify(deepSortKeys(expected)), optMsg);
}

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()');