I'm developing a FIDO2 authenticator library in Zig and want to share some of my work with you.
The goal is to write a library that is not bound to a specific platform, i.e. it should enable you to write roaming and platform authenticators alike. When you create a new authenticator instance you must provide some functions, so the library knows how to get random numbers, read and write data and ask for user presence.
const Impl = struct {
pub fn rand() u32 {
regs.TRNG.CTRLA.modify(.{ .ENABLE = 1 });
while (regs.TRNG.INTFLAG.read().DATARDY == 0) {
// a new random number is generated every
// 84 CLK_TRNG_APB clock cycles (p. 1421).
}
regs.TRNG.CTRLA.modify(.{ .ENABLE = 0 });
return regs.TRNG.DATA.*;
}
pub fn requestPermission(user: ?*const User, rp: ?*const RelyingParty) bool {
_ = user;
_ = rp;
return true;
}
// ... some more
};
// Create a new authenticator
const Authenticator = fido.Auth(Impl);
// Initialize it
var versions = [_]fido.Versions{fido.Versions.FIDO_2_0};
pub const auth = Authenticator.initDefault(&versions, [_]u8{ 0xFA, 0x2B, 0x99, 0xDC, 0x9E, 0x39, 0x42, 0x57, 0x8F, 0x92, 0x4A, 0x30, 0xD2, 0x3C, 0x41, 0x18 });
You can find a reference implementation on Github.
Current state
The library supports the creation of credentials (with self attestation), assertions and I'm currently working on the pin protocol API.
Crypto
The library uses ES256 (Ecdsa with Sha-256) for signatures. Unfortunately I wasn't able to use the std-library implementation directly, due to the call to crypto.random.bytes
in Ecdsa.keypair.create
.
pub fn create(seed: ?[seed_length]u8) IdentityElementError!KeyPair {
var seed_ = seed;
if (seed_ == null) {
var random_seed: [seed_length]u8 = undefined;
crypto.random.bytes(&random_seed);
seed_ = random_seed;
}
const h = [_]u8{0x00} ** Hash.digest_length;
const k0 = [_]u8{0x01} ** SecretKey.encoded_length;
const secret_key = deterministicScalar(h, k0, seed_).toBytes(.Big);
return fromSecretKey(SecretKey{ .bytes = secret_key });
}
The easy fix was to just copy the code into my project and remove the unnecessary if block.
Apart from that, Zig provides a lot of useful cipher suits in its std-lib.
Reference impl
I'm using a SAM E51 Curiosity Nano dev-board to test my library with browsers and command line tools like libfido2. For USB communication I use tinyusb and a minimal implementation of the CTAPHID protocol in Zig. The stack looks something like this:
|----------------------------| --
| CTAP API (r4gus/fido2) | |
|----------------------------| |
| CBOR (r4gus/zbor) | |-- Zig
|----------------------------| |
| CTAPHID | |
|----------------------------| --
| tinyusb | |
|----------------------------| |-- C
| device drivers | |
|----------------------------| --
It was quite easy to compile the Zig code and then build the whole (tinyusb) project using make.
# Compile the Zig part of the app
zig build-obj \
-target thumb-freestanding-eabihf \
-mcpu=cortex_m4 \
-lc \
--pkg-begin fido libs/fido2/src/main.zig \
--pkg-begin zbor libs/fido2/libs/zbor/src/main.zig \
--pkg-end \
--pkg-end \
-freference-trace \
-OReleaseSmall \
src/main.zig
# Build the project
make BOARD=same51curiositynano all
include libs/tinyusb/tools/top.mk
include libs/tinyusb/examples/make.mk
INC += \
src \
$(TOP)/hw \
# Example source
PROJECT_SOURCE += $(wildcard src/*.c)
SRC_C += $(addprefix $(CURRENT_PATH)/, $(PROJECT_SOURCE))
ZIG_OBJ += main.o
include libs/tinyusb/examples/rules.mk
The first idea was to add tinyusb to microzig but that didn't work.
The code isn't well documented at the moment and the library is far from complete but I'm able to create new credentials and assertions (tested with webauthn.io, webauthn.me and libfido2).
It should be easy to adapt the implementation to another micro controller as long as it's supported by tinyusb.
Top comments (0)