-
Notifications
You must be signed in to change notification settings - Fork 2
Getting Started
Welcome, this guide should help you get started with keylib. If you have any questions or encounter bugs, please open an issue.
Keylib is a library designed to assist you in implementing PassKeys, which are platform authenticators compatible with FIDO2. This library handles the task of generating new credentials and assertions when required, and your responsibility is to furnish it with the necessary callbacks. Keylib currently offers two interfaces, one for Zig and another one for C.
GCC + Make
Coming soon..Zig build system
If you don't have Zig available on your system, download it from here, unpack it, and then add it to your path.
Create a new project, e.g.:
mkdir my-project
cd my-project
zig init-exe
Then setup your project by creating a build.zig.zon
file (make sure you update the hash):
.{
.name = "my-project",
.version = "0.1.0",
.dependencies = .{
.keylib = .{
.url = "http://localhost:36603/1",
.hash = "1220a237a6e63eb11270b107847072a014704268de85257ccb5c7e40ae1f9fd0944c",
},
},
}
You can get the correct hash by using a wrong hash, and then running
zig build
. The correct hash will be displayed in a error message.
Update your build.zig
file:
C
Create the file `src/main.c` with a main function.Then update you build.zig
file:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const keylib_dep = b.dependency("keylib", .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "my-project",
.root_source_file = .{ .path = "src/main.c" },
.target = target,
.optimize = optimize,
});
exe.linkLibrary(keylib_dep.artifact("keylib"));
exe.linkLibrary(keylib_dep.artifact("uhid"));
exe.linkLibC();
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
You can include the required header using
#include "keylib/keylib.h"
in yourmain.c
.
Zig
Coming soon...Finally run zig build
to build the project. The binary can be found in zig-out/bin/my-project
. You can also run you project by executing zig build run
.
The first step when using this library is to implement the required callbacks (C, Zig).
C
The first thing we must do is include the keylib.h
, header file into our code. This file includes enum definitions, the callback signatures, the definition of the Data
struct which we will need when implementing the read
callback, and functions for interacting with the library.
#include "keylib/keylib.h" // Data, Denied, DoesNotExist
Next we implement the uv
(user verification) and up
(user presence) callbacks. The uv
callback is used to authenticate a user, e.g. by asking for a password. What authentication method you want to use for your application is up to you. The up
callback is used to verify the (physical) presence of a user. You can for example do this by displaying a button the user can click on. Both functions return Accepted
on success, Denied
if rejected (e.g. user entered wrong password or clicked cancel), and Timeout
if a timeout has occurred.
// Make a user presence check, e.g. display a button and ask the user if she wants to confirm the action.
// The arguments info, user, and rp are null terminated strings that provide additional context that
// you can display.
//
// This function should return Accepted, Denied, or Timeout
int my_up(const char* info, const char* user, const char* rp) {
printf("up\n");
return Denied;
}
// This callback should implement some form of user verification, e.g. when called, ask a user for a password.
//
// This function should return Accepted, Denied, or Timeout
int my_uv() {
printf("uv\n");
return Denied;
}
Next we implement the select_cred
callback. This callback is used if more than one credential is bound to the same relying party (the service the credential was created for). The callback will provide a null terminated array of user strings. Those strings should be displayed on the screen and the user must then select a user. The function returns either a user index (starting from 0) or a negative error value.
// Let the user select one of multiple credentials associated with a relying party.
//
// You should either return the users index or -1.
int my_select_cred(const char* rpId, char** users) {
printf("select\n");
return -1;
}
The authenticator also needs some way to read
, write
and delete
data. The read
callback takes a id
string (the unique credential id), a rp
string (the relying party id/ base url), and a pointer to a string array.
- If
id
is not null:- Create a string array with two elements, where the first element contains the requested data and the second element is a null terminator. Assign the array to out. Then return
SUCCESS
.
- Create a string array with two elements, where the first element contains the requested data and the second element is a null terminator. Assign the array to out. Then return
- If
id
is null andrp
is not null:- Create a null terminated string array that contains all data associated with the given
rp
. Assign the array to out. Then returnSUCCESS
.
- Create a null terminated string array that contains all data associated with the given
- Else:
- Create a null terminated string array that contains all stored data. Assign the array to out. Then return
SUCCESS
.
- Create a null terminated string array that contains all stored data. Assign the array to out. Then return
Return DoesNotExist
if no data could be found.
// Read data from permanent storage.
int my_read(const char* id, const char* rp, char*** out) {
printf("read");
return DoesNotExist;
}
NOTE: It is important that the array itself and all (data) strings are null terminated!
The following is an example how the creation of a array with one element could look like:
char** x = malloc(sizeof(char*) * 2); x[0] = data; // think of data as a hex encodex, null terminated string x[1] = NULL; *out = x; return Error_SUCCESS;
All memory assigned to
out
is owned by the authenticator, i.e. it's NOT you responsibility to free it.
The write
callback is used to persist the given data
. It is both used to create new entries and to update existing ones. You can assume that the credential id
is unique, i.e. if the id already exists update (overwrite) the existing entry, otherwise create a new one. Make sure you associate the data with the given relying party id (rp
) so you can find it later.
// Persist the given data and make sure that it can be found using its id and associated rp (relying party).
int my_write(const char* id, const char* rp, const char* data) {
printf("write");
return -1;
}
The delete
callback is quite simple, find and delete the data associated with the given id
.
// Delete the data associated with the given id.
int my_delete(const char* id, const char* rp) {
printf("delete\n");
return -1;
}
Zig
Coming soon...After we have implemented the required callbacks, the next step is to instantiate the Auth
struct. This is usually done at the beginning of your main
function.
C
int main() {
// Init Start
Callbacks c = {
my_up, my_uv, my_select_cred, my_read, my_write, my_delete
};
void* auth = auth_init(c);
// Init End
// Deinit Start
uhid_close(fd);
// Deinit End
return 0;
}
As you can see, we first create a Callbacks
struct from all the callbacks we created and pass this to auth_init
. The auth_init
function will create a new (default) authenticator, initialize it and then return a void*
that points to the struct. You should make sure that the returned pointer is not NULL
before using it.
Zig
Coming soon...On its own, the authenticator struct is not very useful. You need some way to interact with a client. Currently the CTAP2 spec lists three way: USB, NFC, and Bluetooth.
This library offers a CTAPHID interface that helps you implement a authenticator that communicates over USB. We also provide a wrapper for /dev/uhid
on Linux, if you want to create a virtual USB device. While the CTAPHID interface comes with keylib.h
, you must include keylib/uhid.h
(and also link the uhid
library that is part of this project) if you want to use the uhid
functions.
C
The following is meant to illustrate how a platform authenticator implementation on Linux could look like. If you want to support another platform or transport, you have to implement the interface yourself. If you do so, think about contributing back to this project.int main() {
// Init Start
// ...
void* ctaphid = ctaphid_init();
int fd = uhid_open();
// Init End
// Deinit Start
uhid_close(fd);
ctaphid_deinit(ctaphid);
// ...
// Deinit End
return 0;
}
The ctaphid_init
function returns a pointer to a CTAPHID handler. The handler expects USB packets and gradually builds a request message from them. After it has assembled a message, the handler will pass the message to the authenticator using the Auth.handle
function. The authenticator acts upon the request and returns a response message. The response from the authenticator is converted by the handler into a CtapHidMessageIterator
, which is returned to the calling function (main in our case). The iterator will provide you with USB packets ready for transmission.
int main() {
// Init Start
// Init End
while (1) {
char buffer[64];
int packet_length = uhid_read_packet(fd, &buffer[0]); // read a packet (if available)
if (packet_length) {
// The handler will either return NULL or a pointer to
// a ctaphid packet iterator.
void* iter = ctaphid_handle(ctaphid, &buffer[0], packet_length, auth);
// Every call to next will return a 64 byte packet ready
// to be sent to the host.
if (iter) {
char out[64];
while(ctaphid_iterator_next(iter, &out[0])) {
uhid_write_packet(fd, &out[0], 64);
}
// Don't forget to free the iterator
ctaphid_iterator_deinit(iter);
}
}
}
// Deinit Start
// Deinit End
return 0;
}
Zig
Coming soon...As you can see it's quite easy to implement a working authenticator using this project. The difficulty lies in choosing the right surrounding infrastructure (e.g. a database for storing credentials, a user interface, ...). You should also keep in mind that credentials should not be stored in plain text.
If you have any questions or encounter problems feel free to open a issue.