Skip to content

Commit b2e1c11

Browse files
committed
Digital Credentials: ignore unknown digital credential types
rdar://166673454 https://bugs.webkit.org/show_bug.cgi?id=304158 Reviewed by Anne van Kesteren. WebKit relied on the IdentityCredentialProtocol.idl enum to prevent requests with unknown protocols being processed. However, this prevented unknown protocols from being ignored gracefully, as required by this spec change: w3c-fedid/digital-credentials#372 We now gracefully ignore unknown protocols by filtering them out, rather than throwing an error. We also now show a console warning, so developers are aware of ignored protocols. Includes upstream web platform test commit: web-platform-tests/wpt@2d00123 Tests: http/tests/digital-credentials/digital-credential-console-messages.https.html http/wpt/identity/digital-credential-protocol-filtering.https.html * LayoutTests/http/tests/digital-credentials/digital-credential-console-messages.https-expected.txt: Added. * LayoutTests/http/tests/digital-credentials/digital-credential-console-messages.https.html: Added. * LayoutTests/http/wpt/identity/digital-credential-protocol-filtering.https-expected.txt: Added. * LayoutTests/http/wpt/identity/digital-credential-protocol-filtering.https.html: Added. * LayoutTests/imported/w3c/web-platform-tests/digital-credentials/create.tentative.https.html: * LayoutTests/imported/w3c/web-platform-tests/digital-credentials/get.tentative.https.html: * LayoutTests/imported/w3c/web-platform-tests/digital-credentials/support/helper.js: * LayoutTests/platform/glib/TestExpectations: * LayoutTests/platform/ios-18/TestExpectations: * LayoutTests/platform/ios/TestExpectations: * LayoutTests/platform/ios/imported/w3c/web-platform-tests/digital-credentials/get.tentative.https-expected.txt: * LayoutTests/platform/mac-sequoia/TestExpectations: * LayoutTests/platform/mac-wk1/TestExpectations: * LayoutTests/platform/visionos/TestExpectations: * LayoutTests/platform/win/TestExpectations: * LayoutTests/platform/wpe/TestExpectations: * Source/WebCore/Modules/identity/DigitalCredential.cpp: (WebCore::convertProtocolString): (WebCore::jsToCredentialRequest): (WebCore::DigitalCredential::convertObjectsToDigitalPresentationRequests): * Source/WebCore/Modules/identity/DigitalCredentialRequest.h: * Source/WebCore/Modules/identity/DigitalCredentialRequest.idl: Canonical link: https://commits.webkit.org/305257@main
1 parent ad11072 commit b2e1c11

File tree

18 files changed

+327
-40
lines changed

18 files changed

+327
-40
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CONSOLE MESSAGE: Ignoring DigitalCredentialRequest with unsupported protocol: "unknown-protocol-1"
2+
CONSOLE MESSAGE: Ignoring DigitalCredentialRequest with unsupported protocol: "unknown-protocol-2"
3+
CONSOLE MESSAGE: Ignoring DigitalCredentialRequest with unsupported protocol: "unknown-before"
4+
CONSOLE MESSAGE: Ignoring DigitalCredentialRequest with unsupported protocol: "unknown-after"
5+
PASS successfullyParsed is true
6+
7+
TEST COMPLETE
8+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8" />
3+
<title>Digital Credential API: Console Message Tests</title>
4+
<script src="/js-test-resources/js-test.js"></script>
5+
<body onload="setup().then(generateConsoleMessages)"></body>
6+
<script>
7+
async function setup() {
8+
if (document.visibilityState === "hidden") {
9+
await new Promise((resolve) => {
10+
document.onvisibilitychange = resolve;
11+
testRunner.setPageVisibility("visible");
12+
});
13+
}
14+
}
15+
16+
async function generateConsoleMessages() {
17+
try {
18+
await navigator.credentials.get({
19+
digital: {
20+
requests: [
21+
{ protocol: "unknown-protocol-1", data: {} },
22+
{ protocol: "unknown-protocol-2", data: {} },
23+
],
24+
},
25+
mediation: "required",
26+
});
27+
} catch {}
28+
29+
try {
30+
await navigator.credentials.get({
31+
digital: {
32+
requests: [
33+
{ protocol: "unknown-before", data: {} },
34+
{
35+
protocol: "org-iso-mdoc",
36+
data: {
37+
deviceRequest:
38+
"omd2ZXJzaW9uYzEuMGtkb2NSZXF1ZXN0c4GhbGl0ZW1zUmVxdWVzdNgYWIKiZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4x9pWthZ2Vfb3Zlcl8yMfRqZ2l2ZW5fbmFtZfRrZmFtaWx5X25hbWX0cmRyaXZpbmdfcHJpdmlsZWdlc_RocG9ydHJhaXT0",
39+
encryptionInfo:
40+
"gmVkY2FwaaJlbm9uY2VYICBetSsDkKlE_G9JSIHwPzr3ctt6Ol9GgmCH8iGdGQNJcnJlY2lwaWVudFB1YmxpY0tleaQBAiABIVggKKm1iPeuOb9bDJeeJEL4QldYlWvY7F_K8eZkmYdS9PwiWCCm9PLEmosiE_ildsE11lqq4kDkjhfQUKPpbX-Hm1ZSLg",
41+
},
42+
},
43+
{ protocol: "unknown-after", data: {} },
44+
],
45+
},
46+
mediation: "required",
47+
});
48+
} catch {}
49+
}
50+
</script>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
PASS Valid protocol is accepted and processed
3+
PASS Unknown protocols are filtered out - all unknown protocols result in TypeError
4+
PASS Single unknown protocol is filtered out and results in TypeError
5+
PASS Mixed known and unknown protocols - unknown ones are silently filtered out
6+
PASS Multiple valid and unknown protocols - unknown ones are filtered out, valid ones processed
7+
PASS Empty protocol string is treated as unknown protocol
8+
PASS Protocol matching is case sensitive
9+
PASS Protocol with extra characters is treated as unknown
10+
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8" />
3+
<title>Digital Credential API: Protocol Filtering Tests</title>
4+
<script src="/resources/testdriver.js"></script>
5+
<script src="/resources/testdriver-vendor.js"></script>
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<body></body>
9+
<script>
10+
promise_setup(async () => {
11+
if (document.visibilityState === "hidden") {
12+
await new Promise((resolve) => {
13+
document.onvisibilitychange = resolve;
14+
testRunner.setPageVisibility("visible");
15+
});
16+
}
17+
assert_equals(document.visibilityState, "visible", "should be visible");
18+
});
19+
20+
const validMDocRequest = {
21+
protocol: "org-iso-mdoc",
22+
data: {
23+
deviceRequest:
24+
"omd2ZXJzaW9uYzEuMGtkb2NSZXF1ZXN0c4GhbGl0ZW1zUmVxdWVzdNgYWIKiZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4x9pWthZ2Vfb3Zlcl8yMfRqZ2l2ZW5fbmFtZfRrZmFtaWx5X25hbWX0cmRyaXZpbmdfcHJpdmlsZWdlc_RocG9ydHJhaXT0",
25+
encryptionInfo:
26+
"gmVkY2FwaaJlbm9uY2VYICBetSsDkKlE_G9JSIHwPzr3ctt6Ol9GgmCH8iGdGQNJcnJlY2lwaWVudFB1YmxpY0tleaQBAiABIVggKKm1iPeuOb9bDJeeJEL4QldYlWvY7F_K8eZkmYdS9PwiWCCm9PLEmosiE_ildsE11lqq4kDkjhfQUKPpbX-Hm1ZSLg",
27+
},
28+
};
29+
30+
const unknownProtocolRequest1 = {
31+
protocol: "unknown-protocol-1",
32+
data: { someField: "someValue" },
33+
};
34+
35+
const unknownProtocolRequest2 = {
36+
protocol: "unknown-protocol-2",
37+
data: { anotherField: "anotherValue" },
38+
};
39+
40+
// test passing just the valid protocol to ensure it works as expected
41+
promise_test(async (t) => {
42+
try {
43+
await navigator.credentials.get({
44+
digital: {
45+
requests: [validMDocRequest],
46+
},
47+
mediation: "required",
48+
});
49+
assert_unreached(
50+
"Should not reach here in test environment - no credentials available"
51+
);
52+
} catch (error) {
53+
// Expect some other error, but NOT TypeError which would indicate valid protocol wasn't recognized
54+
assert_not_equals(
55+
error.name,
56+
"TypeError",
57+
`Should not get TypeError when valid protocol is used: ${error.message}`
58+
);
59+
}
60+
}, "Valid protocol is accepted and processed");
61+
62+
promise_test(async (t) => {
63+
await promise_rejects_js(
64+
t,
65+
TypeError,
66+
navigator.credentials.get({
67+
digital: {
68+
requests: [
69+
unknownProtocolRequest1,
70+
unknownProtocolRequest2,
71+
],
72+
},
73+
mediation: "required",
74+
}),
75+
"Two unknown protocols should result in TypeError due to empty validated requests"
76+
);
77+
}, "Unknown protocols are filtered out - all unknown protocols result in TypeError");
78+
79+
promise_test(async (t) => {
80+
await promise_rejects_js(
81+
t,
82+
TypeError,
83+
navigator.credentials.get({
84+
digital: {
85+
requests: [unknownProtocolRequest1],
86+
},
87+
mediation: "required",
88+
}),
89+
"Single unknown protocol should result in TypeError due to empty validated requests"
90+
);
91+
}, "Single unknown protocol is filtered out and results in TypeError");
92+
93+
promise_test(async (t) => {
94+
try {
95+
await navigator.credentials.get({
96+
digital: {
97+
requests: [
98+
unknownProtocolRequest1,
99+
validMDocRequest,
100+
unknownProtocolRequest2,
101+
],
102+
},
103+
mediation: "required",
104+
});
105+
assert_unreached(
106+
"Should not reach here in test environment - no credentials available"
107+
);
108+
} catch (error) {
109+
// Expect some other error, but NOT TypeError which would indicate unknown protocols weren't filtered
110+
assert_not_equals(
111+
error.name,
112+
"TypeError",
113+
`Should not get TypeError when mixing known and unknown protocols - unknown should be filtered out: ${error.message}`
114+
);
115+
}
116+
}, "Mixed known and unknown protocols - unknown ones are silently filtered out");
117+
118+
promise_test(async (t) => {
119+
const validMDocRequest2 = {
120+
protocol: "org-iso-mdoc",
121+
data: {
122+
deviceRequest:
123+
"omd2ZXJzaW9uYzEuMGtkb2NSZXF1ZXN0c4GhbGl0ZW1zUmVxdWVzdNgYWIKiZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4x9pWthZ2Vfb3Zlcl8yMfRqZ2l2ZW5fbmFtZfRrZmFtaWx5X25hbWX0cmRyaXZpbmdfcHJpdmlsZWdlc_RocG9ydHJhaXT0",
124+
encryptionInfo:
125+
"gmVkY2FwaaJlbm9uY2VYICBetSsDkKlE_G9JSIHwPzr3ctt6Ol9GgmCH8iGdGQNJcnJlY2lwaWVudFB1YmxpY0tleaQBAiABIVggKKm1iPeuOb9bDJeeJEL4QldYlWvY7F_K8eZkmYdS9PwiWCCm9PLEmosiE_ildsE11lqq4kDkjhfQUKPpbX-Hm1ZSLg",
126+
},
127+
};
128+
129+
try {
130+
await navigator.credentials.get({
131+
digital: {
132+
requests: [
133+
unknownProtocolRequest1,
134+
validMDocRequest,
135+
unknownProtocolRequest2,
136+
validMDocRequest2,
137+
{ protocol: "yet-another-unknown", data: {} },
138+
],
139+
},
140+
mediation: "required",
141+
});
142+
assert_unreached(
143+
"Should not reach here in test environment - no credentials available"
144+
);
145+
} catch (error) {
146+
assert_not_equals(
147+
error.name,
148+
"TypeError",
149+
"Should not get TypeError when valid protocols are present - unknown should be filtered out: " + error.message
150+
);
151+
}
152+
}, "Multiple valid and unknown protocols - unknown ones are filtered out, valid ones processed");
153+
154+
promise_test(async (t) => {
155+
await promise_rejects_js(
156+
t,
157+
TypeError,
158+
navigator.credentials.get({
159+
digital: {
160+
requests: [{ protocol: "", data: {} }],
161+
},
162+
mediation: "required",
163+
}),
164+
"Empty protocol string should result in TypeError"
165+
);
166+
}, "Empty protocol string is treated as unknown protocol");
167+
168+
promise_test(async (t) => {
169+
await promise_rejects_js(
170+
t,
171+
TypeError,
172+
navigator.credentials.get({
173+
digital: {
174+
requests: [{ protocol: "ORG-ISO-MDOC", data: {} }],
175+
},
176+
}),
177+
"Protocol matching should be case sensitive"
178+
);
179+
}, "Protocol matching is case sensitive");
180+
181+
promise_test(async (t) => {
182+
await promise_rejects_js(
183+
t,
184+
TypeError,
185+
navigator.credentials.get({
186+
digital: {
187+
requests: [{ protocol: "org-iso-mdoc-extra", data: {} }],
188+
},
189+
mediation: "required",
190+
}),
191+
"Protocol with extra characters should be treated as unknown"
192+
);
193+
}, "Protocol with extra characters is treated as unknown");
194+
</script>

LayoutTests/imported/w3c/web-platform-tests/digital-credentials/create.tentative.https.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,16 @@
246246
);
247247
}
248248
}, "Throws TypeError when request data is not JSON stringifiable.");
249+
250+
promise_test(async (t) => {
251+
const options = {
252+
password: document.createElement("form"),
253+
};
254+
await promise_rejects_js(
255+
t,
256+
TypeError,
257+
navigator.credentials.create(options),
258+
"Should throw for invalid form element"
259+
);
260+
}, "Throws when form element does not contain password.");
249261
</script>

LayoutTests/imported/w3c/web-platform-tests/digital-credentials/get.tentative.https.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
});
3030

3131
promise_test(async (t) => {
32-
const invalidData = [null, undefined, "", 123, true, false];
32+
const invalidData = [null, "", 123, true, false];
3333
for (const data of invalidData) {
3434
const options = makeGetOptions({ data });
3535
await test_driver.bless("user activation");

LayoutTests/imported/w3c/web-platform-tests/digital-credentials/support/helper.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,6 @@
1515
* @typedef {GetProtocol | CreateProtocol} Protocol
1616
*/
1717

18-
/** @type {GetProtocol[]} */
19-
const GET_PROTOCOLS = /** @type {const} */ ([
20-
"openid4vp-v1-unsigned",
21-
"openid4vp-v1-signed",
22-
"openid4vp-v1-multisigned",
23-
"org-iso-mdoc",
24-
]);
25-
26-
/** @type {CreateProtocol[]} */
27-
const CREATE_PROTOCOLS = /** @type {const} */ (["openid4vci"]);
28-
29-
const SUPPORTED_GET_PROTOCOL = GET_PROTOCOLS.find((protocol) =>
30-
DigitalCredential.userAgentAllowsProtocol(protocol)
31-
);
32-
const SUPPORTED_CREATE_PROTOCOL = CREATE_PROTOCOLS.find((protocol) =>
33-
DigitalCredential.userAgentAllowsProtocol(protocol)
34-
);
35-
3618
/** @type {Record<Protocol, object | MobileDocumentRequest>} */
3719
const CANONICAL_REQUEST_OBJECTS = {
3820
openid4vci: {
@@ -174,8 +156,9 @@ function makeCredentialOptionsFromConfig(config, mapping) {
174156
* @returns {CredentialRequestOptions}
175157
*/
176158
export function makeGetOptions(config = {}) {
159+
/** @type {MakeGetOptionsConfig} */
177160
const configWithDefaults = {
178-
protocol: SUPPORTED_GET_PROTOCOL,
161+
protocol: ["openid4vp-v1-unsigned", "org-iso-mdoc"],
179162
...config,
180163
};
181164

@@ -191,8 +174,9 @@ export function makeGetOptions(config = {}) {
191174
* @returns {CredentialCreationOptions}
192175
*/
193176
export function makeCreateOptions(config = {}) {
177+
/** @type {MakeCreateOptionsConfig} */
194178
const configWithDefaults = {
195-
protocol: SUPPORTED_CREATE_PROTOCOL,
179+
protocol: "openid4vci",
196180
...config,
197181
};
198182

LayoutTests/platform/glib/TestExpectations

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4569,6 +4569,7 @@ webkit.org/b/98927 fast/dom/DeviceMotion/ [ Skip ]
45694569
webkit.org/b/98927 fast/dom/DeviceOrientation/ [ Skip ]
45704570

45714571
# The Digital Credentials API is not supported in GTK and WPE ports.
4572+
http/tests/digital-credentials/ [ Skip ]
45724573
http/wpt/identity/ [ Skip ]
45734574
imported/w3c/web-platform-tests/digital-credentials/ [ Skip ]
45744575

LayoutTests/platform/ios-18/TestExpectations

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ imported/w3c/web-platform-tests/mixed-content/gen/top.meta/unset/sharedworker-im
110110

111111
# Digital Credentials supported added in iOS 26
112112
imported/w3c/web-platform-tests/digital-credentials/ [ Skip ]
113+
http/tests/digital-credentials/ [ Skip ]
113114
http/wpt/identity/ [ Skip ]
114115

115116
# -- The above has different, expected behavior on iOS 26 than iOS 18 --

LayoutTests/platform/ios/TestExpectations

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8140,7 +8140,6 @@ http/tests/websocket/tests/hybi/websocket-allowed-setting-cookie-as-third-party-
81408140
http/tests/workers/service/basic-install-event-sw-fetch-third-party.html [ Failure ]
81418141
http/wpt/webcodecs/videoFrame-negative-timestamp.html [ Failure ]
81428142
imported/w3c/web-platform-tests/css/selectors/invalidation/has-with-nesting-parent-containing-hover.html [ Failure ]
8143-
imported/w3c/web-platform-tests/digital-credentials/get.tentative.https.html [ Failure ]
81448143
imported/w3c/web-platform-tests/shadow-dom/reference-target/tentative/interesttarget.tentative.html [ Failure ]
81458144
imported/w3c/web-platform-tests/uievents/mouse/mousemove_after_mouseover_target_removed.html [ Failure ]
81468145
platform/ipad/fast/viewport/viewport-overriden-by-minimum-effective-width-if-ignore-meta-viewport.html [ Failure ]

0 commit comments

Comments
 (0)