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

Credential management #17

Merged
merged 20 commits into from
Aug 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@ pub fn build(b: *std.build.Builder) !void {
});
const zbor_module = zbor_dep.module("zbor");

//const hidapi_dep = b.dependency("hidapi", .{
// .target = target,
// .optimize = optimize,
//});
//_ = hidapi_dep;
const uuid_dep = b.dependency("uuid", .{
.target = target,
.optimize = optimize,
});
const uuid_module = uuid_dep.module("uuid");

//const clap_dep = b.dependency("clap", .{
// .target = target,
// .optimize = optimize,
//});
//const clap_module = clap_dep.module("clap");
//_ = clap_module;
const snorlax_dep = b.dependency("snorlax", .{
.target = target,
.optimize = optimize,
});
const snorlax_module = snorlax_dep.module("snorlax");

// ++++++++++++++++++++++++++++++++++++++++++++
// Module
Expand All @@ -43,13 +42,26 @@ pub fn build(b: *std.build.Builder) !void {

try b.modules.put(b.dupe("cks"), cks_module);

// Allocator Module
// ------------------------------------------------

const allocator_module = b.addModule("cks", .{
.source_file = .{ .path = "profiling_allocator/main.zig" },
.dependencies = &.{
.{ .name = "profiling_allocator", .module = zbor_module },
},
});

try b.modules.put(b.dupe("profiling_allocator"), allocator_module);

// Authenticator Module
// ------------------------------------------------

const fido_module = b.addModule("fido", .{
.source_file = .{ .path = "lib/main.zig" },
.dependencies = &.{
.{ .name = "zbor", .module = zbor_module },
.{ .name = "uuid", .module = uuid_module },
.{ .name = "cks", .module = cks_module },
},
});
Expand All @@ -68,6 +80,8 @@ pub fn build(b: *std.build.Builder) !void {
});
authenticator.addModule("fido", fido_module);
authenticator.addModule("cks", cks_module);
authenticator.addModule("profiling_allocator", allocator_module);
authenticator.addModule("snorlax", snorlax_module);
authenticator.linkSystemLibraryPkgConfigOnly("libnotify");
authenticator.linkLibC();
b.installArtifact(authenticator);
Expand Down
12 changes: 10 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@

.dependencies = .{
.zbor = .{
.url = "https://github.com/r4gus/zbor/archive/master.tar.gz",
.hash = "1220a1b2ab5f3092f4f29d85b0627cc6adc54c973ae588ddc75e630b624293ea4722",
.url = "https://github.com/r4gus/zbor/archive/refs/tags/0.11.0.tar.gz",
.hash = "12204bf1073e9bc1eb12d09a2c298c687ed9c782c687f98ccda3bc95daad8c216845",
},
.uuid = .{
.url = "https://github.com/r4gus/uuid-zig/archive/refs/tags/0.1.0.tar.gz",
.hash = "1220b32a616236fd26f0e4f063f3f11f5c5b83943d1fd71c367cf0bdbebedbf42385",
},
.snorlax = .{
.url = "https://github.com/r4gus/snorlax/archive/master.tar.gz",
.hash = "1220911c2a1f74b601affd25af9c9e32854fda1ab6b9205db01dc63e0e3ba92df919",
},
},
}
9 changes: 9 additions & 0 deletions lib/common/AttestationStatement.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,13 @@ pub const AttestationStatement = union(fido.common.AttestationStatementFormatIde
/// receive attestation information, see § 5.4.7 Attestation Conveyance Preference
/// Enumeration (enum AttestationConveyancePreference).
none: struct {}, // no attestation

pub fn deinit(self: *const @This(), a: std.mem.Allocator) void {
switch (self.*) {
.@"packed" => |x| {
a.free(x.sig);
},
else => {},
}
}
};
109 changes: 90 additions & 19 deletions lib/ctap/auth/Authenticator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,46 +29,117 @@ token: struct {
},

credential_list: ?struct {
list: []const fido.ctap.crypto.Id,
list: []fido.ctap.authenticator.Credential,
credentialCounter: usize = 0,
time_stamp: i64,
authData: fido.common.AuthenticatorData = undefined,
clientDataHash: fido.ctap.crypto.ClientDataHash = undefined,

pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
for (self.list) |item| {
item.deinit(allocator);
}
allocator.free(self.list);
}
} = null,

cred_mngmnt: ?struct {
ids: std.ArrayList([]const u8),
time_stamp: i64,
prot: fido.ctap.pinuv.common.PinProtocol,
token: [32]u8,

pub fn deinit(self: *const @This(), allocator: std.mem.Allocator) void {
for (self.ids.items) |item| {
allocator.free(item);
}
self.ids.deinit();
}
} = null,

allocator: std.mem.Allocator,

pub fn init(self: *@This()) !void {
var settings = if (self.callbacks.getEntry("Settings")) |settings| settings else blk: {
std.log.warn("No Settings entry found", .{});

var s = try self.callbacks.createEntry("Settings");
try s.addField(.{ .key = "Retries", .value = "\x08" }, std.time.milliTimestamp());
try s.addField(.{ .key = "ForcePinChange", .value = "False" }, std.time.milliTimestamp());
try s.addField(.{ .key = "MinPinLength", .value = "\x04" }, std.time.milliTimestamp());
try s.addField(.{ .key = "AlwaysUv", .value = "True" }, std.time.milliTimestamp());
try s.addField(.{
.key = "Secret",
.value = &fido.ctap.crypto.master_secret.createMasterSecret(self.callbacks.rand),
}, self.callbacks.millis());
try self.callbacks.addEntry(s);

break :blk self.callbacks.getEntry("Settings").?;
/// TODO: We use AES256-OCB and HMAC. It should be fine to use the same key
/// for HMAC and AES256-OCB encryption but maybe its still better to use
/// two different keys.
secret: fido.ctap.authenticator.Meta.Keys = undefined,

/// Initialize the authenticator
///
/// This will try to load dynamic authenticator settings, i.e. those
/// that will change over time.
///
/// The settings will be created, if they don't exist. This includes a new
/// master secret, used to encrypt all credentials. The master secret itself
/// is encrypted using the provided password (a key derived from the password
/// to be more precise).
pub fn init(self: *@This(), password: []const u8) !void {
var new: bool = false;
var setting = self.callbacks.readSettings(self.allocator) catch |err| blk: {
if (err == error.DoesNotExist) {
std.log.warn("No Settings entry found", .{});
var meta = fido.ctap.authenticator.Meta{
._id = try self.allocator.dupe(u8, "Settings"),
};

// First we derive a key from the given password.
// This will also generate a random salt that is stored with
// the rest of the meta data and used for the key derivation.
//
// From now on we can call meta.deriveKey(password) to derive the key.
self.secret = try meta.newKey(password, self.callbacks.rand, self.allocator);

// Lets create a new master secret. This is used to encrypt all generated credentials.
// We use this indirection so we don't need to re-encrypt all credentials if the user
// changes the password.
const ms = fido.ctap.crypto.master_secret.createMasterSecret(self.callbacks.rand);
meta.setSecret(ms, self.secret.enc, self.callbacks.rand);

// Finally, we calculate a mac over the data. The master secret uses authenticated encryption
// but I don't want to reencrypt the master secret every time one of the other fields changes.
meta.updateMac(&self.secret.mac);

self.callbacks.updateSettings(&meta, self.allocator) catch |e| {
std.log.err("unable to persist new settings ({any})", .{e});
return error.Fatal;
};

new = true;
break :blk meta;
} else {
std.log.err("fatal error while loading settings", .{});
return error.Fatal;
}
};
_ = settings;
defer setting.deinit(self.allocator);

if (!new) {
self.secret = try setting.deriveKey(password, self.allocator);
}

if (!setting.verifyMac(&self.secret.mac)) {
std.log.err("MAC verification for the given settings failed", .{});
return error.Fatal;
}

try self.callbacks.persist();
if (self.token.one) |*one| {
one.initialize();
}

if (self.token.two) |*two| {
two.initialize();
}
}

pub fn deinit(self: *@This()) void {
if (self.credential_list != null) {
self.credential_list.?.deinit(self.allocator);
self.credential_list = null;
}
if (self.cred_mngmnt != null) {
self.cred_mngmnt.?.deinit(self.allocator);
self.cred_mngmnt = null;
}
}

pub fn handle(self: *@This(), command: []const u8) Response {
Expand Down
24 changes: 17 additions & 7 deletions lib/ctap/auth/Callbacks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ const cks = @import("cks");

pub const LoadError = error{
DoesNotExist,
NotEnoughMemory,
OutOfMemory,
Other,
};

pub const StoreError = error{
KeyStoreFull,
OutOfMemory,
Other,
};

/// Result value of the `up` callback
Expand All @@ -40,6 +43,13 @@ pub const CredentialSelectionResult = union(CredentialSelectionResultTag) {
timeout: void,
};

pub const ReadCredParamId = enum { id, rpId, all };
pub const ReadCredParam = union(ReadCredParamId) {
id: []const u8,
rpId: []const u8,
all: bool,
};

/// Interface for a thread local CSPRNG
rand: std.rand.Random,

Expand Down Expand Up @@ -68,12 +78,12 @@ select_discoverable_credential: ?*const fn (
users: []const fido.common.User,
) CredentialSelectionResult = null,

createEntry: *const fn (id: []const u8) cks.Error!cks.Entry,
getEntry: *const fn (id: []const u8) ?*cks.Entry,
getEntries: *const fn (filters: []const cks.Data.Filter, a: std.mem.Allocator) ?[]const *cks.Entry,
addEntry: *const fn (entry: cks.Entry) cks.Error!void,
removeEntry: *const fn (id: []const u8) cks.Error!void,
persist: *const fn () error{Fatal}!void,
readSettings: *const fn (a: std.mem.Allocator) LoadError!fido.ctap.authenticator.Meta,
updateSettings: *const fn (settings: *fido.ctap.authenticator.Meta, a: std.mem.Allocator) StoreError!void,

readCred: *const fn (id: ReadCredParam, a: std.mem.Allocator) LoadError![]fido.ctap.authenticator.Credential,
updateCred: *const fn (cred: *fido.ctap.authenticator.Credential, a: std.mem.Allocator) StoreError!void,
deleteCred: *const fn (cred: *fido.ctap.authenticator.Credential) LoadError!void,

//// Called on a reset
////
Expand Down
Loading
Loading