Skip to content

Commit

Permalink
authenticatorGetNextAssertion implemented but not yet tested
Browse files Browse the repository at this point in the history
  • Loading branch information
r4gus committed Aug 12, 2023
1 parent df0522c commit 1743f64
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 0 deletions.
34 changes: 34 additions & 0 deletions lib/ctap/auth/Authenticator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ token: struct {
two: ?fido.ctap.pinuv.PinUvAuth = null,
},

credential_list: ?struct {
list: []const fido.ctap.crypto.Id,
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 {
allocator.free(self.list);
}
} = null,

allocator: std.mem.Allocator,

pub fn init(self: *@This()) !void {
Expand All @@ -52,6 +64,13 @@ pub fn init(self: *@This()) !void {
try self.callbacks.persist();
}

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

pub fn handle(self: *@This(), command: []const u8) Response {
// Buffer for the response message
var res = std.ArrayList(u8).init(self.allocator);
Expand Down Expand Up @@ -175,6 +194,21 @@ pub fn handle(self: *@This(), command: []const u8) Response {

self.callbacks.reset();
},
.authenticatorGetNextAssertion => {
// Execute command
const status = fido.ctap.commands.authenticator.authenticatorGetNextAssertion(
self,
response,
) catch {
res.deinit();
return Response{ .err = @intFromEnum(StatusCodes.ctap1_err_other) };
};

if (status != .ctap1_err_success) {
res.deinit();
return Response{ .err = @intFromEnum(status) };
}
},
.authenticatorSelection => {
const status = fido.ctap.commands.authenticator.authenticatorSelection(self);

Expand Down
21 changes: 21 additions & 0 deletions lib/ctap/commands/authenticator/authenticatorGetAssertion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ pub fn authenticatorGetAssertion(
gap: *const fido.ctap.request.GetAssertion,
out: anytype,
) !fido.ctap.StatusCodes {
// Remove the credential list form the previous getAssertion
// call if one exists.
if (auth.credential_list != null) {
auth.credential_list.?.deinit(auth.allocator);
auth.credential_list = null;
}

// ++++++++++++++++++++++++++++++++++++++++++++++++
// 1. and 2. Verify pinUvAuthParam
// ++++++++++++++++++++++++++++++++++++++++++++++++
Expand Down Expand Up @@ -296,6 +303,14 @@ pub fn authenticatorGetAssertion(
std.log.warn("UserId field missing for id {s}. Returning the user id is mandatory for resident keys so expect errors.", .{std.fmt.fmtSliceHexUpper(cred.raw[0..])});
}
}

if (credentials.items.len >= 1) {
// Copy the remaining credential Ids for later use by authenticatorGetNextAssertion
auth.credential_list = .{
.list = try auth.allocator.dupe(fido.ctap.crypto.Id, credentials.items),
.time_stamp = auth.callbacks.millis(),
};
}
} else {
settings.times.usageCount += 1;
}
Expand Down Expand Up @@ -384,5 +399,11 @@ pub fn authenticatorGetAssertion(

try cbor.stringify(gar, .{ .allocator = auth.allocator }, out);

if (auth.credential_list) |*cl| {
// We remember authData and clientDataHash for authenticatorGetNextAssertion
cl.authData = auth_data;
cl.clientDataHash = gap.clientDataHash;
}

return status;
}
129 changes: 129 additions & 0 deletions lib/ctap/commands/authenticator/authenticatorGetNextAssertion.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
const std = @import("std");
const cbor = @import("zbor");
const cks = @import("cks");
const fido = @import("../../../main.zig");
const helper = @import("helper.zig");

pub fn authenticatorGetNextAssertion(
auth: *fido.ctap.authenticator.Authenticator,
out: anytype,
) !fido.ctap.StatusCodes {
if (auth.credential_list == null) {
return fido.ctap.StatusCodes.ctap2_err_not_allowed;
}

if (auth.credential_list.?.credentialCounter >= auth.credential_list.?.list.len or
(auth.callbacks.millis() - auth.credential_list.?.time_stamp) >= 30000)
{
auth.allocator.free(auth.credential_list.?.list);
auth.credential_list = null;
return fido.ctap.StatusCodes.ctap2_err_not_allowed;
}

// Fetch authenticator settings to get master secret
var settings = if (auth.callbacks.getEntry("Settings")) |settings| settings else {
std.log.err("Unable to fetch Settings", .{});
return fido.ctap.StatusCodes.ctap1_err_other;
};

var _ms = if (settings.getField("Secret", auth.callbacks.millis())) |ms| ms else {
std.log.err("Secret field missing in Settings", .{});
return fido.ctap.StatusCodes.ctap1_err_other;
};

const ms: fido.ctap.crypto.master_secret.MasterSecret = _ms[0..fido.ctap.crypto.master_secret.MS_LEN].*;

// Fetch next credential id
const id = auth.credential_list.?.list[auth.credential_list.?.credentialCounter];
auth.credential_list.?.credentialCounter += 1;

// Fetch the credential based on credential id and update the return data
var user: ?fido.common.User = null;
if (auth.callbacks.getEntry(id.raw[0..])) |entry| {
// Seems like this is a discoverable credential, because we
// just discovered it :)
auth.credential_list.?.authData.signCount = @as(u32, @intCast(entry.times.usageCount));
entry.times.usageCount += 1;

if (auth.credential_list.?.authData.flags.uv == 1) {
// publicKeyCredentialUserEntity MUST NOT be returned if user verification
// was not done by the authenticator in the original authenticatorGetAssertion call
const user_id = entry.getField("UserId", auth.callbacks.millis());
if (user_id) |uid| {
// User identifiable information (name, DisplayName, icon)
// inside the publicKeyCredentialUserEntity MUST NOT be returned
// if user verification is not done by the authenticator
user = .{ .id = uid, .name = null, .displayName = null };
} else {
std.log.warn(
"UserId field missing for id: {s}",
.{std.fmt.fmtSliceHexUpper(id.raw[0..])},
);
}
}
} else {
std.log.warn(
"Unable to load credential with id: {s}",
.{std.fmt.fmtSliceHexUpper(id.raw[0..])},
);
return fido.ctap.StatusCodes.ctap1_err_other;
}

// select algorithm based on credential
const algorithm = id.getAlg();
var alg: ?fido.ctap.crypto.SigAlg = null;
for (auth.algorithms) |_alg| blk: {
if (algorithm == _alg.alg) {
alg = _alg;
break :blk;
}
}

if (alg == null) {
std.log.err("Unknown algorithm for credential with id: {s}", .{std.fmt.fmtSliceHexLower(&id.raw)});
return fido.ctap.StatusCodes.ctap1_err_other;
}

const seed = id.deriveSeed(ms);
const key_pair = if (alg.?.create_det(
&seed,
auth.allocator,
)) |kp| kp else return fido.ctap.StatusCodes.ctap1_err_other;
defer {
auth.allocator.free(key_pair.cose_public_key);
auth.allocator.free(key_pair.raw_private_key);
}

// Sign the data
var authData = std.ArrayList(u8).init(auth.allocator);
defer authData.deinit();
try auth.credential_list.?.authData.encode(authData.writer());

const sig = if (alg.?.sign(
key_pair.raw_private_key,
&.{ authData.items, &auth.credential_list.?.clientDataHash },
auth.allocator,
)) |signature| signature else {
std.log.err("signature creation failed for credential with id: {s}", .{std.fmt.fmtSliceHexLower(&id.raw)});
return fido.ctap.StatusCodes.ctap1_err_other;
};
defer auth.allocator.free(sig);

const gar = fido.ctap.response.GetAssertion{
.credential = .{
.type = .@"public-key",
.id = &id.raw,
},
.authData = authData.items,
.signature = sig,
.user = user,
};

try auth.callbacks.persist();

try cbor.stringify(gar, .{ .allocator = auth.allocator }, out);

auth.credential_list.?.time_stamp = auth.callbacks.millis();

return .ctap1_err_success;
}
1 change: 1 addition & 0 deletions lib/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ pub const ctap = struct {
pub const authenticatorGetInfo = @import("ctap/commands/authenticator/get_info.zig").authenticatorGetInfo;
pub const authenticatorMakeCredential = @import("ctap/commands/authenticator/authenticatorMakeCredential.zig").authenticatorMakeCredential;
pub const authenticatorGetAssertion = @import("ctap/commands/authenticator/authenticatorGetAssertion.zig").authenticatorGetAssertion;
pub const authenticatorGetNextAssertion = @import("ctap/commands/authenticator/authenticatorGetNextAssertion.zig").authenticatorGetNextAssertion;
pub const authenticatorClientPin = @import("ctap/commands/authenticator/authenticatorClientPin.zig").authenticatorClientPin;
pub const authenticatorSelection = @import("ctap/commands/authenticator/authenticatorSelection.zig").authenticatorSelection;
pub const authenticatorCredentialManagement = @import("ctap/commands/authenticator/authenticatorCredentialManagement.zig").authenticatorCredentialManagement;
Expand Down
1 change: 1 addition & 0 deletions platform-auth/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ pub fn main() !void {
}

try authenticator.init();
defer authenticator.deinit();
// --------------------------------------------------------

notify.g_main_loop_run(loop);
Expand Down

0 comments on commit 1743f64

Please sign in to comment.