NaviServer Built-in Commands – 5.1.0
ns_cbor - Minimal CBOR decoder for WebAuthn needs.
The command ns_cbor provides a minimal CBOR (RFC 8949) decoder intended primarily for WebAuthn and related protocols. It decodes exactly one CBOR data item from the provided input.
The decoder is intentionally limited in scope and focuses on CBOR data types required by WebAuthn, such as maps, arrays, integers, byte strings, and text strings. It does not aim to be a full general-purpose CBOR implementation. Two subcommands are provided:
ns_cbor decode decodes a single CBOR item and returns its Tcl representation.
ns_cbor scan decodes a single CBOR item and additionally returns the number of bytes consumed from the input.
Decode a single CBOR item from value and return the decoded Tcl value.
CBOR major types are mapped as follows:
Unsigned and negative integers are returned as Tcl integers.
CBOR byte strings (bstr) are returned as binary Tcl values or encoded strings, depending on -encoding.
CBOR text strings (tstr) are returned as Tcl strings.
CBOR arrays are returned as Tcl lists.
CBOR maps are returned as Tcl dicts.
The simple values true, false, and null are returned as the corresponding Tcl boolean or empty value.
Only a single CBOR item is decoded. Any trailing data in the input is ignored.
Decode a single CBOR item from value and return a two-element list. The first element is the decoded Tcl value, and the second element is the number of bytes consumed from the input. This subcommand is useful when parsing CBOR data embedded inside larger binary structures, such as WebAuthn authenticatorData or COSE structures.
Decode a CBOR map encoded as base64url
set cbor_b64u "omNmbXRkbm9uZWdhdHRTdG10oA"
set bin [ns_base64urldecode -binary -- $cbor_b64u]
set value [ns_cbor decode -binary -encoding binary $bin]
## output: fmt none attStmt {}
# Access map entries
dict get $value fmt
## output: none
Decode sample authenticatorData. Note that many of the commands output binary data (byte array). The final list command provides readable data.
# Fake authenticatorData header (37 bytes like real authData: rpIdHash(32) + flags(1) + signCount(4))
# Here all zeros, just to provide a realistic prefix length.
set authData [binary format H* [string map {" " ""} {\
0000000000000000000000000000000000000000000000000000000000000000 \
01 \
00000000}]]
# COSE_Key (CBOR map) for ES256 / P-256:
# 1:2 (kty=EC2)
# 3:-7 (alg=ES256)
# -1:1 (crv=P-256)
# -2: <32 bytes> (x)
# -3: <32 bytes> (y)
#
# x = 0x01..0x20
# y = 0x21..0x40
set cose_bin [binary format H* [string map {" " ""} {\
a5 \
01 02 \
03 26 \
20 01 \
21 58 20 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 \
22 58 20 2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40}]]
# In real authData, COSE_Key starts at some offset.
# For this example, it's exactly after the 37-byte header.
set offset 37
append authData $cose_bin
set cose_bin [string range $authData $offset end]
set cose [ns_cbor decode -binary -encoding binary $cose_bin]
set x [dict get $cose -2]
set y [dict get $cose -3]
list x_hex [binary encode hex $x] y_hex [binary encode hex $y]
## output: x_hex 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 y_hex 2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40