1/**
2 * Reads the raw `authData` bytes out of a CBOR attestation object
3 * (`{ fmt, attStmt, authData }`). Returns undefined for anything malformed.
4 * Only the definite-length encodings authenticators emit are understood.
5 */
6export const authenticatorDataFromAttestation = (bytes: Uint8Array) => {
7 let offset = 0;
8 const readHead = () => {
9 const initial = bytes[offset++];
10 if (initial === undefined) return undefined;
11 const info = initial & 31;
12 let length = info;
13 if (info >= 24) {
14 if (info > 27) return undefined;
15 const size = 1 << (info - 24);
16 if (offset + size > bytes.length) return undefined;
17 length = 0;
18 for (let index = 0; index < size; index++) length = length * 256 + (bytes[offset++] ?? 0);
19 }
20 return { major: initial >> 5, length };
21 };
22 const skipValue = (): boolean => {
23 const head = readHead();
24 if (!head) return false;
25 switch (head.major) {
26 case 2:
27 case 3:
28 offset += head.length;
29 return offset <= bytes.length;
30 case 4:
31 for (let index = 0; index < head.length; index++) if (!skipValue()) return false;
32 return true;
33 case 5:
34 for (let index = 0; index < head.length * 2; index++) if (!skipValue()) return false;
35 return true;
36 case 6:
37 return skipValue();
38 default:
39 return true;
40 }
41 };
42
43 const map = readHead();
44 if (map?.major !== 5) return undefined;
45 for (let entry = 0; entry < map.length; entry++) {
46 const key = readHead();
47 if (key?.major !== 3 || offset + key.length > bytes.length) return undefined;
48 const name = new TextDecoder().decode(bytes.subarray(offset, offset + key.length));
49 offset += key.length;
50 if (name !== "authData") {
51 if (!skipValue()) return undefined;
52 continue;
53 }
54 const value = readHead();
55 if (value?.major !== 2 || offset + value.length > bytes.length) return undefined;
56 return bytes.slice(offset, offset + value.length);
57 }
58 return undefined;
59};