Skip to content

Instantly share code, notes, and snippets.

@z4yx
Last active November 18, 2023 05:44
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save z4yx/218116240e2759759b239d16fed787ca to your computer and use it in GitHub Desktop.
Save z4yx/218116240e2759759b239d16fed787ca to your computer and use it in GitHub Desktop.
Wireshark protocol decoder for FIDO(U2F) and FIDO2(WebAuthn) over USB HID
cbor = Dissector.get("cbor")
iso7816 = Dissector.get("iso7816")
ctap_proto = Proto("ctaphid","ctap hid")
-- Field Extractor
direction_fe = Field.new("usb.endpoint_address.direction")
udp_srcport_fe = Field.new("udp.srcport")
CTAPHID_COMMAND_CODE = {
[0x03]='CTAPHID_MSG',
[0x10]='CTAPHID_CBOR',
[0x06]='CTAPHID_INIT',
[0x01]='CTAPHID_PING',
[0x11]='CTAPHID_CANCEL',
[0x3F]='CTAPHID_ERROR',
[0x3B]='CTAPHID_KEEPALIVE',
[0x08]='CTAPHID_WINK',
[0x04]='CTAPHID_LOCK',
}
CTAP_COMMAND_CODE = {
[0x01]='authenticatorMakeCredential',
[0x02]='authenticatorGetAssertion',
[0x04]='authenticatorGetInfo',
[0x06]='authenticatorClientPIN',
[0x07]='authenticatorReset',
[0x08]='authenticatorGetNextAssertion',
[0x40]='authenticatorVendorFirst',
[0xBF]='authenticatorVendorLast'
}
CTAP_RESPONSE_CODE = {
[0x00]='CTAP1_ERR_SUCCESS',
[0x01]='CTAP1_ERR_INVALID_COMMAND',
[0x02]='CTAP1_ERR_INVALID_PARAMETER',
[0x03]='CTAP1_ERR_INVALID_LENGTH',
[0x04]='CTAP1_ERR_INVALID_SEQ',
[0x05]='CTAP1_ERR_TIMEOUT',
[0x06]='CTAP1_ERR_CHANNEL_BUSY',
[0x0A]='CTAP1_ERR_LOCK_REQUIRED',
[0x0B]='CTAP1_ERR_INVALID_CHANNEL',
[0x11]='CTAP2_ERR_CBOR_UNEXPECTED_TYPE',
[0x12]='CTAP2_ERR_INVALID_CBOR',
[0x14]='CTAP2_ERR_MISSING_PARAMETER',
[0x15]='CTAP2_ERR_LIMIT_EXCEEDED',
[0x16]='CTAP2_ERR_UNSUPPORTED_EXTENSION',
[0x19]='CTAP2_ERR_CREDENTIAL_EXCLUDED',
[0x21]='CTAP2_ERR_PROCESSING',
[0x22]='CTAP2_ERR_INVALID_CREDENTIAL',
[0x23]='CTAP2_ERR_USER_ACTION_PENDING',
[0x24]='CTAP2_ERR_OPERATION_PENDING',
[0x25]='CTAP2_ERR_NO_OPERATIONS',
[0x26]='CTAP2_ERR_UNSUPPORTED_ALGORITHM',
[0x27]='CTAP2_ERR_OPERATION_DENIED',
[0x28]='CTAP2_ERR_KEY_STORE_FULL',
[0x29]='CTAP2_ERR_NOT_BUSY',
[0x2A]='CTAP2_ERR_NO_OPERATION_PENDING',
[0x2B]='CTAP2_ERR_UNSUPPORTED_OPTION',
[0x2C]='CTAP2_ERR_INVALID_OPTION',
[0x2D]='CTAP2_ERR_KEEPALIVE_CANCEL',
[0x2E]='CTAP2_ERR_NO_CREDENTIALS',
[0x2F]='CTAP2_ERR_USER_ACTION_TIMEOUT',
[0x30]='CTAP2_ERR_NOT_ALLOWED',
[0x31]='CTAP2_ERR_PIN_INVALID',
[0x32]='CTAP2_ERR_PIN_BLOCKED',
[0x33]='CTAP2_ERR_PIN_AUTH_INVALID',
[0x34]='CTAP2_ERR_PIN_AUTH_BLOCKED',
[0x35]='CTAP2_ERR_PIN_NOT_SET',
[0x36]='CTAP2_ERR_PIN_REQUIRED',
[0x37]='CTAP2_ERR_PIN_POLICY_VIOLATION',
[0x38]='CTAP2_ERR_PIN_TOKEN_EXPIRED',
[0x39]='CTAP2_ERR_REQUEST_TOO_LARGE',
[0x3A]='CTAP2_ERR_ACTION_TIMEOUT',
[0x3B]='CTAP2_ERR_UP_REQUIRED',
[0x7F]='CTAP1_ERR_OTHER',
[0xDF]='CTAP2_ERR_SPEC_LAST',
[0xE0]='CTAP2_ERR_EXTENSION_FIRST',
[0xEF]='CTAP2_ERR_EXTENSION_LAST',
[0xF0]='CTAP2_ERR_VENDOR_FIRST',
[0xFF]='CTAP2_ERR_VENDOR_LAST'
}
function payload_dissector(pinfo, subtree, state)
local t = ByteArray.tvb(state.payload_buffer:subset(0, state.total_len), "payload")
local isIN = 0
local usb_dir = direction_fe()
if usb_dir ~= nil then
isIN = usb_dir.value
else
if tostring(udp_srcport_fe()) == "8111" then
isIN = 1
else
isIN = 0
end
end
-- pinfo.cols.protocol = tostring(udp_srcport_fe())
if state.ctaphid_cmd == 0x10 then
local subtree = subtree:add(t(0),"FIDO2 Payload")
local ctap_cmd = t(0,1):uint();
local text = ({[0]=CTAP_COMMAND_CODE, [1]=CTAP_RESPONSE_CODE})[isIN][ctap_cmd]
pinfo.cols.protocol = "CTAP " .. text
subtree:add(t(0,1),string.format('CTAP CMD/Status: %s (0x%02x)', text, ctap_cmd))
if t(1):len() > 0 then
cbor:call(t(1):tvb(), pinfo, subtree)
end
elseif state.ctaphid_cmd == 0x03 then
local subtree = subtree:add(t(0),"FIDO/U2F Payload")
iso7816:call(t, pinfo, subtree)
else
local subtree = subtree:add(t(0),"Other Payload")
end
end
cached_info = {}
total_len = 0
ctaphid_cmd = 0
payload_buffer = nil
-- create a function to dissect it
function ctap_proto.dissector(buffer,pinfo,tree)
pinfo.cols.protocol = "CTAPHID"
local subtree = tree:add(ctap_proto,buffer(),"CTAPHID")
local data = nil
-- subtree:add(buffer,"length=" .. string.format('0x%08x',buffer:len()) .. " " .. direction_fe().value)
local cid = buffer(0,4):uint()
subtree:add(buffer(0,4),string.format('CID: 0x%08x',cid))
local cmd = buffer(4,1):uint()
if bit.band(cmd, 0x80) == 0x80 then
ctaphid_cmd = bit.band(cmd, 0x7f)
subtree:add(buffer(4,1),string.format('CMD: %s (0x%02x)',CTAPHID_COMMAND_CODE[ctaphid_cmd],ctaphid_cmd))
total_len = buffer(5,2):uint()
subtree:add(buffer(5,2),string.format('Length: 0x%04x',total_len))
payload_buffer = ByteArray.new()
data = buffer(7)
else
subtree:add(buffer(4,1),string.format('SEQ: 0x%02x',cmd))
data = buffer(5)
end
local state = cached_info[pinfo.number]
if state ~= nil then
if state.ctaphid_cmd ~= nil then
payload_dissector(pinfo, subtree, state)
end
else
-- first interation
state = {}
payload_buffer:append(data:bytes())
if payload_buffer:len() >= total_len then
-- cache info for later display
state.ctaphid_cmd = ctaphid_cmd
state.payload_buffer = payload_buffer
state.total_len = total_len
payload_dissector(pinfo, subtree, state)
end
cached_info[pinfo.number] = state
end
end
usb_table = DissectorTable.get("usb.product")
usb_table:add(0x10500407,ctap_proto) -- VID/PID of Yubikey
udp_table = DissectorTable.get("udp.port")
udp_table:add(8111,ctap_proto) -- Solokeys simulation
udp_table:add(7112,ctap_proto) -- Solokeys simulation
@scaszoo
Copy link

scaszoo commented Nov 19, 2019

Wasn't working on my system. Cause was the VID/PID of my Yubikey wasn't matching.
Get your VID/PID from https://support.yubico.com/support/solutions/articles/15000028104-yubikey-usb-id-values list or by capturing the traffic when connecting your Yubikey to your usb port and filtering in wireshark for usb.idVendor and extract idVendor and idProduct

@mesink
Copy link

mesink commented Sep 19, 2022

This seems to work intermittently only on Mac for me, but consistently on Windows. I'm fairly new to wireshark, so still looking, but any ideas?

@scaszoo
Copy link

scaszoo commented Sep 19, 2022

@DanBujak I am not sure what your question is . . .

@mesink
Copy link

mesink commented Sep 19, 2022

Hey, thanks for the quick reply! When using a Mac, I need to reload the file several times to get it to work. Even then sometimes it doesn't work. On Windows it works consistently. The question is if you have any ideas as to why it would be less reliable on a Mac or things I can try.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment