Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

U2F implementation #56

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from
Draft

U2F implementation #56

wants to merge 28 commits into from

Conversation

MatthewWilkes
Copy link
Member

@MatthewWilkes MatthewWilkes commented May 29, 2022

Add support for using the TiDAL as a secure 2nd factor authenticator.

Current status

The protocol for USB 2 factor auth support is complete and working on some devices. Users can register a TiDAL as an authenticator and authenticate requests, however this isn't working reliably across devices.

This branch contains a custom firmware that allows testing this functionality, however it requires that the crypto chip has been provisioned before it can be used. Once it's provisioned it cannot be reset, it is a destructive operation. We are not confident that the provisioning is correct. In particular, it may well require changes to support attestation. It will also prevent you being able to provision a known key onto the device, which will limit debugging.

This is done by calling
ecc108a.provision_slot() followed by ecc108a.lock_config_zone()

This is likely the problem, the following config (from ecc108a_tools.run()) works on slot 6 only:

Serial number: 01 23 07 9f f1 58 b9 fd ee
Revision number: 0x5100000
Reserved 1: 0xc0
Interface mode: I2C
Reserved 2: 0x90 0x0
I2C address: 0xc0
Reserved 3: 0x0
OTP mode: 0x55 (consumption)
Selector mode: unlimited
TTL reference mode: fixed
Watchdog timeout: 1.3s
Reserved 4: 0x0
UserExtra: 0x0
Selector: 0x0
LockValue: 0x55 (unlocked)
LockConfig: 0x0 (locked)
Reserved 5: 0xff
Reserved 6: 0xff
X509format[0].PublicPosition: 15
X509format[0].TemplateLength: 15
X509format[1].PublicPosition: 15
X509format[1].TemplateLength: 15
X509format[2].PublicPosition: 15
X509format[2].TemplateLength: 15
X509format[3].PublicPosition: 15
X509format[3].TemplateLength: 15
Key 0:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
  UseFlag: 0xff
  UpdateCount: 0
Key 1:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
  UseFlag: 0xff
  UpdateCount: 0
Key 2:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
  UseFlag: 0xff
  UpdateCount: 0
Key 3:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 0
Key 4:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 1300
  UseFlag: 0xff
  UpdateCount: 0
Key 5:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 0013
  UseFlag: 0xff
  UpdateCount: 0
Key 6:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 34
Key 7:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
  UseFlag: 0xff
  UpdateCount: 0
Key 8:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
Key 9:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
Key 10:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 3300
Key 11:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6081
  KeyConfig: 3300
Key 12:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8160
  KeyConfig: 3300
Key 13:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
Key 14:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
Key 15:
  SlotLocked: 1 (unlocked)
  SlotConfig: 8360
  KeyConfig: 3300
  Key use: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

but this one, where all keys replicate slot 6's config above, no slots work:

Serial number: 01 23 56 14 30 14 c1 f5 ee
Revision number: 0x1100080
Reserved 1: 0xc0
Interface mode: I2C
Reserved 2: 0xa6 0x0
I2C address: 0xc0
Reserved 3: 0x0
OTP mode: 0x55 (consumption)
Selector mode: unlimited
TTL reference mode: fixed
Watchdog timeout: 1.3s
Reserved 4: 0x0
UserExtra: 0x0
Selector: 0x9
LockValue: 0x55 (unlocked)
LockConfig: 0x0 (locked)
Reserved 5: 0xff
Reserved 6: 0xff
X509format[0].PublicPosition: 15
X509format[0].TemplateLength: 15
X509format[1].PublicPosition: 15
X509format[1].TemplateLength: 15
X509format[2].PublicPosition: 15
X509format[2].TemplateLength: 15
X509format[3].PublicPosition: 15
X509format[3].TemplateLength: 15
Key 0:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 136
Key 1:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 6
Key 2:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 1
Key 3:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 0
Key 4:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 0
Key 5:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 0
Key 6:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 1
Key 7:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  UseFlag: 0xff
  UpdateCount: 0
Key 8:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
Key 9:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
Key 10:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
Key 11:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
Key 12:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
Key 13:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
Key 14:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
Key 15:
  SlotLocked: 1 (unlocked)
  SlotConfig: 6083
  KeyConfig: 0033
  Key use: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

Excessive detail

HID mode

The badge

FIDO USB HID / CTAPHID

Interactions with U2F devices are tunneled over the USB HID protocol. An explanation of how those messages are sent is in the USB HID section below.

These messages are detailed at https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-hid-protocol-ps-20141009.html

The implementation in the badge is at https://github.com/emfcamp/TiDAL-Firmware/blob/feature/u2f-mode/drivers/tidal_usb/tidal_usb_u2f.c

There are three commands implemented, init, wink and msg. Wink is what makes a device flash for attention. Init begins a session, and 'msg' contains a message for the next protocol down in the stack.

FIDO / CTAP

That next protocol down is FIDO / CTAP. This is detailed in https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html and implemented in the handle_u2f_msg function of https://github.com/emfcamp/TiDAL-Firmware/blob/feature/u2f-mode/drivers/tidal_usb/tidal_usb_u2f.c

This message contains either a register or an authenticate command.

Register

In register mode, we need to allocate a 'handle' for the site. The handle is what the server sends to the device during authentication to identify itself. Many other devices use this to return an encrypted key, so the device then decrypts the handle, and uses that to sign the challenge. We don't have support for that, our handles are integers that reference which of the (few) key slots the crypto chip has is responsible for this site.

This is currently hard-coded to 6, in https://github.com/emfcamp/TiDAL-Firmware/blob/feature/u2f-mode/drivers/tidal_usb/u2f_crypto.c#L122-L124

We return from register with the handle, the pubkey to validate against, an attestation certificate and a signature. Attestation allows consumers to verify that an authenticator is itself authentic. For example, Yubikeys will be attested by Yubikey, to allow validation that the user is using one of an allowlist of known good authenticators

Authenticate

The authenticate message is relative simple, it contains a challenge, a counter and a signature. These are returned and checked by the host machine

Work remaining:

  • Hook up a GUI app that allows users to accept register requests and pick a slot to store the key in
    • The first available slot is used
  • Implement signing with an attestation certificate

USB HID

In order to get this to work, we need to support more USB HID types than just mouse and keyboard. We have therefore switched from the TinyUSB implementation in esp-iot-solution to a vendored-in one with more options. We have moved some of the callbacks into the TiDAL USB driver and added support for the U2F HID types at https://github.com/emfcamp/TiDAL-Firmware/blob/feature/u2f-mode/components/tinyusb/additions/src/descriptors_control.c#L21-L58

The HID implementation at https://github.com/emfcamp/TiDAL-Firmware/blob/feature/u2f-mode/drivers/tidal_usb/tidal_usb_hid.c always includes the standard HID code, but only includes the U2F hooks when enabled.

Work remaining:

  • Exposing multiple HID descriptors was causing issues. You need to choose between CONFIG_TINYUSB_HIDKEYBOARD_ENABLED and CONFIG_TINYUSB_U2FHID_ENABLED in sdkconfig.board. In future, having this switch at run-time rather than build-time would be preferable. This has not been a priority.
    • You must select the U2F mode in the settings panel and reboot the badge. This will stop the USB keyboard functionality from working until it's switched back.

ECC108a chip

The crypto chip is connected over i2c. The datasheet has many details. Some functions have been exposed in micropython, which may assist you in debugging. These are in the ecc108a module. There is also an ec108a_tools module which contains helper code for understanding the config zone.

None of the cryptography functions will work until the config zone is locked, and none of the provisioning and config controls will work once it is locked.

Below is an example transaction, which may be useful for people attempting to validate the behavior. The verify method is not working..

>>> ecc108a.get_pubkey(6)
(b')Y\x0f\xf3\x8e\xcb\\\xc5\xf3W\xc5\xf4+\x9f\x82v\x9e\x92 \x0fe\xe4\xecMrnnh6\x95\x14J', b'\xd0\xa2\xd41\x91\x9ce\xd5\x84D\xe9\x93\xact\xda/\x86\xf3\x07!s\xdc\xa01\xd5\x14[\nk\x03\x87\xa8')
>>> ecc108a.genkey(6)
(b'\xe4`\xb2\x93\xc9\x9bj\x9f\xfbD\x94\x1di\x9b\x96\xc7\x91F\x1c\xcf\x7f\xe4\xbe\x8d4%P\xe3\xbb\x18;\xa0', b'\x0b\x9e\xe8\xf1\x9cB\x06\xb1\x93\xd2&\x7f\xdb\x03\x17\xa82\x99\x18\x1f\t\x1cz\xe5H\xb2\xb3}fM\xabe')
>>> pubkey = ecc108a.get_pubkey(6)
>>> pubkey
(b'\xe4`\xb2\x93\xc9\x9bj\x9f\xfbD\x94\x1di\x9b\x96\xc7\x91F\x1c\xcf\x7f\xe4\xbe\x8d4%P\xe3\xbb\x18;\xa0', b'\x0b\x9e\xe8\xf1\x9cB\x06\xb1\x93\xd2&\x7f\xdb\x03\x17\xa82\x99\x18\x1f\t\x1cz\xe5H\xb2\xb3}fM\xabe')
>>> msg = hashlib.sha256("hello world").digest()
>>> msg
b"\xb9M'\xb9\x93M>\x08\xa5.R\xd7\xda}\xab\xfa\xc4\x84\xef\xe3zS\x80\xee\x90\x88\xf7\xac\xe2\xef\xcd\xe9"
>>> signature = ecc108a.sign(6, msg)
>>> signature
(b'\x08^\xecuD\xfa\xad\xf67\x06\xdb\xf4\\\xbdZ\x99\xd8$0\xfb\x03R\xc3\xfb\n+\xdaqc\xb1\xae\x1e', b'\\\xa6\xcb\xbbK\x01\x180\x02\xcf\xac-\xc2\x03\xd5\xe9d~\xeb\xbe\xda\x7f\xfe\xcf"`\'i\xf0t\x14e')
>>> ecc108a.verify(msg, pubkeky, signature)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: 244 
# N.B:  244 = ATCA_EXECUTION_ERROR

Work remaining:

  • Fix verify method

Debugging end-to-end

I recommend using Wireshark in USB mode and u2fcli for debugging.

MatthewWilkes and others added 19 commits May 28, 2022 11:03
This implements enough of the CTAP1 protocol to complete transactions, albeit with hard-coded responses that don't pass cryptographic muster.
This changes the u2f implementation around somewhat, so it no longer hard-codes
lengths of some variable length fields. It moves the crypto functions out to their
own file, ahead of implementation.
Fix reading of config zone to get all 128 bytes, split initialisation into a helper function and initial (non-working) version of genkey.
This sets up slot configs, suspected bit packing errors causing this not to work. It also exposes the lock functions in Python. Be very careful.
This allows provisioning the 108A to the point that genkey can be called, and offers helper functions for some basic funtionality. These functions can easily brick your badge, so don't run them unless  you know what you're doing. The crypto outputs haven't been verified yet.
At this stage, the critical cryptographic functions of the U2F implementation are linked in. Unfortunately, it's not yet working, currently because the signature parameters aren't parsing correctly.

I'm somewhat concerned that the fido raw message formats document specifies that cryptographic signatures are over the input bytestring, rather than the SHA-256 of that bytestring. The 108A only allows signatures of 32-byte strings, so I've gone that way. I don't think that's the problem I'm seeing yet though, I think it's a more general problem parsing.

In addition, the code is currently hard-coded to use handle 1 for attestation and handle 6 for authentication. This is because only handle 6 is set up correctly on my main test device. Handle 1 will need the keys from keys/* loaded into it - I'm aware that it's silly to put a key in git, but in this case it's not part of the trust path and we have no way of distributing the key without exposing it unless we do it in person in 2024.

No work yet on setting up the UI.
uint8_t signature[64];

ESP_LOGI(TAG, "Calculating digest");
atcab_hw_sha2_256(signature_input, 69, digest);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does this 69 length come from? the data to be signed over is (1+32+32+L+65, L=1, 131) bytes long isn't it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this is wrong. 69 is the length of the thing to be signed during authenticate, 131 is the length to be signed during register. The register signature is there to do the attestation, which we expect to fail anyway (the attestation key isn't provisioned in slot 1), but this would make it doubly wrong.

This might have done it.

@MatthewWilkes
Copy link
Member Author

MatthewWilkes commented Jul 7, 2024

The following is a minimal reproducer of the problem:

import ecc108a, hashlib
slot = 6
key = ecc108a.get_pubkey(slot)
message = "This is a test message"
sig = ecc108a.full_sign(slot, message)
hash = hashlib.sha256(message).digest()
assert ecc108a.verify(hash, sig, key)

This works on working slots/devices and fails on non-working ones. Checking the revision matches the one that I have that works is config = ecc108a.read_config(); hex(int.from_bytes(config[4:8], 'little')) == '0x5100000'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants