Last active
January 13, 2024 04:36
-
-
Save joelonsql/28967ea50f93735de0381ebcfee0392a to your computer and use it in GitHub Desktop.
Decode WebAuthn CBOR Using PostgreSQL Recursive CTE
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE OR REPLACE FUNCTION decode_cbor(cbor bytea) | |
RETURNS TABLE ( | |
item integer, | |
map_item_count integer, | |
text_string text, | |
bytes bytea | |
) | |
LANGUAGE sql | |
AS $$ | |
/* | |
Only a few major types implemented partially, | |
the absolute minimum required to decode the | |
WebAuthn "attestationObject" | |
into a "ctapMakeCredResp" object. | |
https://github.com/fido-alliance/webauthn-demo/blob/master/utils.js#L200 | |
let ctapMakeCredResp = cbor.decodeAllSync(attestationBuffer)[0]; | |
*/ | |
WITH RECURSIVE x AS ( | |
SELECT | |
decode_cbor.cbor, | |
0::integer AS item, | |
NULL::integer AS map_item_count, | |
NULL::text COLLATE "default" AS text_string, | |
NULL::bytea AS bytes | |
UNION ALL | |
SELECT | |
substring(x.cbor,byte_offset) AS cbor, | |
item+1 AS item, | |
CASE WHEN major_type_value = 5 AND additional_type_value <= 23 | |
THEN additional_type_value | |
END AS map_item_count, | |
CASE WHEN major_type_value = 3 AND additional_type_value <= 23 | |
THEN convert_from(substring(x.cbor,2,additional_type_value),'utf8') | |
END::text COLLATE "default" AS text_string, | |
CASE WHEN major_type_value = 2 AND additional_type_value = 24 | |
THEN substring(x.cbor,3,get_byte(x.cbor,1)) | |
END AS bytes | |
FROM x | |
JOIN LATERAL (VALUES( | |
(get_byte(x.cbor,0)>>5)&'111'::bit(3)::integer, | |
get_byte(x.cbor,0)&'11111'::bit(5)::integer | |
)) AS data_item_header(major_type_value,additional_type_value) ON TRUE | |
JOIN LATERAL (VALUES(CASE | |
WHEN major_type_value IN (2,3) AND additional_type_value <= 23 THEN 2+additional_type_value | |
WHEN major_type_value = 5 THEN 2 | |
WHEN major_type_value = 2 AND additional_type_value = 24 THEN 3+get_byte(x.cbor,1) | |
END)) AS next_item(byte_offset) ON TRUE | |
WHERE length(x.cbor) > 0 | |
) | |
SELECT | |
item, | |
map_item_count, | |
text_string, | |
bytes | |
FROM x | |
WHERE item > 0 | |
ORDER BY item | |
$$; | |
CREATE OR REPLACE FUNCTION ctapMakeCredResp(attestationObject bytea) | |
RETURNS jsonb | |
LANGUAGE sql | |
AS $$ | |
WITH items AS ( | |
SELECT * FROM decode_cbor(attestationObject) | |
) | |
SELECT jsonb_object_agg( | |
keys.text_string, | |
COALESCE(values.text_string,encode(values.bytes,'base64')) | |
) | |
FROM items AS map | |
JOIN generate_series(1,map.map_item_count) AS map_index ON TRUE | |
JOIN items AS keys ON keys.item = map.item+map_index*2-1 | |
JOIN items AS values ON values.item = map.item+map_index*2 | |
WHERE map.map_item_count > 0 | |
$$; | |
SELECT jsonb_pretty(ctapMakeCredResp('\xa363666d74646e6f6e656761747453746d74a068617574684461746158be49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763455fc81524adce000235bcc60a648b0b25f1f05503003a01135c2d9aadb3a18061e400654075dba7e3c6549eb8658a1ce3d051955971bbd1faa840a59ae65db032d6fae0c3892bc532cc35985013b4b350a5010203262001215820ad1d5633b0ccbdaab0efb55c999543d1850aaf350d30434c4765f099b5f1b87e2258206cdf70a6ee3193d94351c2aea0179f320d7ede464aec1e43a386ee9eb0628182'::bytea)); | |
/* | |
{ | |
"fmt": "none", | |
"attStmt": null, | |
"authData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NFX8gVJK3OAAI1vMYKZIsLJfHwVQMAOgET\nXC2arbOhgGHkAGVAddun48ZUnrhlihzj0FGVWXG70fqoQKWa5l2wMtb64MOJK8UyzDWYUBO0s1Cl\nAQIDJiABIVggrR1WM7DMvaqw77VcmZVD0YUKrzUNMENMR2XwmbXxuH4iWCBs33Cm7jGT2UNRwq6g\nF58yDX7eRkrsHkOjhu6esGKBgg==" | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment