Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<title>Digital Credential API: org-iso-mdoc EncryptionInfo validation</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<body></body>
<script type="module">
// Tests for ISO 18013-7 Annex C EncryptionInfo structure validation.
// EncryptionInfo = ["dcapi", {"nonce": bstr(>=16), "recipientPublicKey": COSE_Key}]
// Minimal CBOR encoder (inline for test self-containment)
function cborHead(mt, val) {
const m = mt << 5;
if (val < 24) return [m | val];
if (val < 256) return [m | 24, val];
if (val < 65536) return [m | 25, val >> 8, val & 0xff];
return [m | 26, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff];
}
function cborUint(v) { return cborHead(0, v); }
function cborNegInt(v) { return cborHead(1, -1 - v); }
function cborBstr(bytes) { return [...cborHead(2, bytes.length), ...bytes]; }
function cborTstr(s) { const b = new TextEncoder().encode(s); return [...cborHead(3, b.length), ...b]; }
function cborArray(items) { return [...cborHead(4, items.length), ...items.flat()]; }
function cborMapFromPairs(pairs) {
return [...cborHead(5, pairs.length), ...pairs.flatMap(([k, v]) => [...k, ...v])];
}
function toBase64url(bytes) {
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
// Valid P-256 point (from Martijn's Annex C reference vectors, verified on curve)
function validCoseKey() {
const x = [0x60,0xe3,0x39,0x23,0x85,0x04,0x1f,0x51,0x40,0x30,0x51,0xf2,0x41,0x55,0x31,0xcb,
0x56,0xdd,0x3f,0x99,0x9c,0x71,0x68,0x70,0x13,0xaa,0xc6,0x76,0x8b,0xc8,0x18,0x7e];
const y = [0xe5,0x8d,0xeb,0x8f,0xdb,0xe9,0x07,0xf7,0xdd,0x53,0x68,0x24,0x55,0x51,0xa3,0x47,
0x96,0xf7,0xd2,0x21,0x5c,0x44,0x0c,0x33,0x9b,0xb0,0xf7,0xb6,0x7b,0xec,0xcd,0xfa];
return cborMapFromPairs([
[cborUint(1), cborUint(2)],
[cborNegInt(-1), cborUint(1)],
[cborNegInt(-2), cborBstr(x)],
[cborNegInt(-3), cborBstr(y)],
]);
}
function validNonce(len = 16) {
return new Array(len).fill(0xab);
}
function makeEncryptionInfo(protocolTag, params) {
return cborArray([cborTstr(protocolTag), params]);
}
// Valid DeviceRequest (ISO 18013-7 Annex C reference vector)
const VALID_DR = "omd2ZXJzaW9uYzEuMGtkb2NSZXF1ZXN0c4GhbGl0ZW1zUmVxdWVzdNgYWFyiZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xomtmYW1pbHlfbmFtZfRvZG9jdW1lbnRfbnVtYmVy9A";
async function assertRejectsGracefully(t, deviceRequest, encryptionInfo) {
const options = {
digital: {
requests: [{
protocol: "org-iso-mdoc",
data: { deviceRequest, encryptionInfo }
}]
},
};
await test_driver.bless("user activation");
try {
await navigator.credentials.get(options);
assert_unreached("Should not resolve with malformed EncryptionInfo");
} catch (e) {
// An immediate abort would mask whether the malformed request was actually
// rejected (AbortError is a DOMException), so exclude AbortError here.
assert_true(
e instanceof TypeError || (e instanceof DOMException && e.name !== "AbortError"),
`Expected a non-abort rejection, got: ${e.constructor.name}: ${e.name}`
);
}
}
// --- Protocol tag tests ---
promise_test(async (t) => {
const params = cborMapFromPairs([
[cborTstr("nonce"), cborBstr(validNonce())],
[cborTstr("recipientPublicKey"), validCoseKey()],
]);
const ei = toBase64url(makeEncryptionInfo("evil", params));
await assertRejectsGracefully(t, VALID_DR, ei);
}, "EncryptionInfo protocol tag 'evil' instead of 'dcapi' is rejected.");
promise_test(async (t) => {
const params = cborMapFromPairs([
[cborTstr("nonce"), cborBstr(validNonce())],
[cborTstr("recipientPublicKey"), validCoseKey()],
]);
const ei = toBase64url(makeEncryptionInfo("", params));
await assertRejectsGracefully(t, VALID_DR, ei);
}, "EncryptionInfo with empty protocol tag is rejected.");
// --- Nonce tests (spec: minimum entropy 16 bytes) ---
promise_test(async (t) => {
const params = cborMapFromPairs([
[cborTstr("recipientPublicKey"), validCoseKey()],
]);
const ei = toBase64url(makeEncryptionInfo("dcapi", params));
await assertRejectsGracefully(t, VALID_DR, ei);
}, "EncryptionInfo missing nonce field is rejected.");
</script>