From a926906e914feef568a9fc259cc2f018e622433c Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sat, 7 Sep 2024 10:42:39 +0200 Subject: [PATCH] finalize computation of the code --- src/main.zig | 90 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/src/main.zig b/src/main.zig index 1df9783..9a3b622 100644 --- a/src/main.zig +++ b/src/main.zig @@ -18,18 +18,17 @@ pub fn main() !void { const args = try std.process.argsAlloc(allocator); - std.log.debug("arguments: {s}\n", .{args}); const arg: Args = try parseArgs(allocator, args); //print("parsed Args: {?any}\n", arg); if (arg.list) { const names = try executeGetList(allocator, arg.config_location); for (0..names.items.len) |i| { - std.debug.print("{s}\n", .{names.items[i]}); + try std.io.getStdOut().writer().print("{s}\n", .{names.items[i]}); } } else if (arg.show != null) { const authenticator = try getAuthenticator(allocator, arg.show.?, arg.config_location); - debug("number for {s}", .{try authenticator.code()}); + try std.io.getStdOut().writer().print("{s}\n", .{try authenticator.code(allocator, std.time.timestamp)}); } else { printHelp(); } @@ -63,20 +62,67 @@ const OtpAuthUrl = struct { name: []const u8, secretEncoded: []const u8, url: [] const Authenticator = struct { url: OtpAuthUrl, - pub fn code(self: Authenticator) ![]const u8 { + pub fn code(self: Authenticator, allocator: Allocator, timeFunc: *const fn () i64) ![]const u8 { if (self.url.name.len > 0) {} - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); const secret = try Base32.decodeU8(allocator, self.url.secretEncoded); + defer allocator.free(secret); if (false) { debug("secret: {s}\n", .{secret}); } - return "code"; + const intervalNumber = @divTrunc(timeFunc(), self.url.period); + + var intervalAsU8Array: [8]u8 = undefined; + std.mem.writePackedInt(i64, intervalAsU8Array[0..], 0, intervalNumber, .big); + //debug("interval packed: {X}\n", .{intervalAsU8Array}); + + //debug("intervalNumber: {d}\n", .{intervalNumber}); + + var out: [std.crypto.auth.hmac.HmacSha1.mac_length]u8 = undefined; + std.crypto.auth.hmac.HmacSha1.create(out[0..], &intervalAsU8Array, secret); + //debug("hmac: {X}\n", .{out}); + + // take the 4 least significant bits of the hash and use them as byte offset + const leastSignificantByte = out[std.crypto.auth.hmac.HmacSha1.mac_length - 1]; + const byteIndex = leastSignificantByte & 0b1111; + //debug("index: {d}\n", .{byteIndex}); + + const x: [4]u8 = [4]u8{ out[byteIndex], out[byteIndex + 1], out[byteIndex + 2], out[byteIndex + 3] }; + + const tokenBase = std.mem.readInt(i32, &x, .big) & 0x7fffffff; + //debug("tokenBase: {d}\n", .{tokenBase}); + const token = @mod(tokenBase, (std.math.pow(i64, 10, self.url.digits))); + + const result = try zeroPad(allocator, self.url.digits, token); + //debug("code as 0-padded string: {s} ({d})\n", .{ result, token }); + return result; } }; +fn zeroPad(allocator: Allocator, digits: u4, x: anytype) ![]u8 { + const result = try allocator.alloc(u8, digits); + + const s = try std.fmt.allocPrint(allocator, "{d}", .{x}); + defer allocator.free(s); + + var i: usize = 0; + var j: usize = 0; + + while (i < digits) { + if (j < s.len) { + result[@as(usize, digits) - i - 1] = s[s.len - j - 1]; + } else { + result[@as(usize, digits) - i - 1] = '0'; + } + + j += 1; + i += 1; + } + + return result; +} + const ArgumentError = error{ InvalidOtpAuthUrl, UnknownParameter, MissingConfigLocation, AuthenticatorNotFound }; fn parseArgs(allocator: Allocator, args: []const []const u8) !Args { @@ -169,7 +215,7 @@ fn configLocation(allocator: Allocator) ![]const u8 { if (xdg_config_home) |base| { const config_location = try std.mem.concat(allocator, u8, &[_][]const u8{ base, "/zig-totp" }); - debug("config_location: {s}", .{config_location}); + //debug("config_location: {s}", .{config_location}); return config_location; } @@ -177,7 +223,7 @@ fn configLocation(allocator: Allocator) ![]const u8 { const base = home orelse unreachable; const config_location = try std.mem.concat(allocator, u8, &[_][]const u8{ base, "/.zig-totp" }); - debug("config_location: {s}", .{config_location}); + //debug("config_location: {s}", .{config_location}); return config_location; } @@ -262,3 +308,29 @@ test "parse oth pauth url" { try std.testing.expectEqual(31, actual.period); try std.testing.expectEqual(7, actual.digits); } + +test "zero padding" { + const actual = try zeroPad(std.testing.allocator, 6, 123); + defer std.testing.allocator.free(actual); + + try std.testing.expectEqualStrings("000123", actual); +} + +test "authenticator generate code with 6 digits" { + const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "TestAuthenticator", .secretEncoded = "HJ5PX4IFQKD37HFNXLJYBAVD5G6NMMUOKUCDXJ4XTLUUBWSXRXEN5SR4MLAZA5M2", .url = "not needed", .period = 30, .digits = 6 } }; + + const code = try authenticator.code(std.testing.allocator, _closure_return_1725695340); + defer std.testing.allocator.free(code); + try std.testing.expectEqualStrings("218139", code); +} +test "authenticator generate code with 10 digits and zero padding" { + const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "TestAuthenticator", .secretEncoded = "HJ5PX4IFQKD37HFNXLJYBAVD5G6NMMUOKUCDXJ4XTLUUBWSXRXEN5SR4MLAZA5M2", .url = "not needed", .period = 60, .digits = 10 } }; + + const code = try authenticator.code(std.testing.allocator, _closure_return_1725695340); + defer std.testing.allocator.free(code); + try std.testing.expectEqualStrings("0844221464", code); +} + +fn _closure_return_1725695340() i64 { + return 1725695340; +}