diff --git a/src/main.zig b/src/main.zig index d106b91..7cd0361 100644 --- a/src/main.zig +++ b/src/main.zig @@ -76,7 +76,7 @@ const Args = struct { config_location: []const u8, }; -const OtpAuthUrl = struct { name: []const u8, secretEncoded: []const u8, url: []const u8, period: u32, digits: u4 }; +const OtpAuthUrl = struct { name: []const u8, secretEncoded: []const u8, url: []const u8, period: u32, digits: u4, algorithm: []const u8 }; const Authenticator = struct { url: OtpAuthUrl, @@ -199,6 +199,7 @@ fn parseOtpAuthUrl(allocator: Allocator, url: []const u8) !OtpAuthUrl { .url = url, .period = if (params.get("period") != null) try std.fmt.parseInt(u32, params.get("period").?, 10) else 30, .digits = if (params.get("digits") != null) try std.fmt.parseInt(u4, params.get("digits").?, 10) else 6, + .algorithm = if (params.get("algorithm") != null) params.get("algorithm").? else "SHA1", }; } @@ -373,14 +374,14 @@ test "zero padding" { } test "authenticator generate code with 6 digits" { - const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "TestAuthenticator", .secretEncoded = "HJ5PX4IFQKD37HFNXLJYBAVD5G6NMMUOKUCDXJ4XTLUUBWSXRXEN5SR4MLAZA5M2", .url = "not needed", .period = 30, .digits = 6 } }; + const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "TestAuthenticator", .secretEncoded = "HJ5PX4IFQKD37HFNXLJYBAVD5G6NMMUOKUCDXJ4XTLUUBWSXRXEN5SR4MLAZA5M2", .url = "not needed", .period = 30, .digits = 6, .algorithm = "SHA1" } }; 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 authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "TestAuthenticator", .secretEncoded = "HJ5PX4IFQKD37HFNXLJYBAVD5G6NMMUOKUCDXJ4XTLUUBWSXRXEN5SR4MLAZA5M2", .url = "not needed", .period = 60, .digits = 10, .algorithm = "SHA1" } }; const code = try authenticator.code(std.testing.allocator, _closure_return_1725695340); defer std.testing.allocator.free(code); @@ -390,17 +391,17 @@ test "authenticator generate code with 10 digits and zero padding" { test "testcases from https://www.rfc-editor.org/rfc/rfc6238#appendix-A" { const secretForSha1 = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"; // plain: "12345678901234567890" hex: "3132333435363738393031323334353637383930" - try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8 }, _closure_return_59, "94287082"); + try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_59, "94287082"); - try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8 }, _closure_return_1111111109, "07081804"); + try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_1111111109, "07081804"); - try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8 }, _closure_return_1111111111, "14050471"); + try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_1111111111, "14050471"); - try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8 }, _closure_return_1234567890, "89005924"); + try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_1234567890, "89005924"); - try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8 }, _closure_return_2000000000, "69279037"); + try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_2000000000, "69279037"); - try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8 }, _closure_return_20000000000, "65353130"); + try testTOTP(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_20000000000, "65353130"); } fn testTOTP(otpAuthUrl: OtpAuthUrl, timeFunc: *const fn () i64, expected: []const u8) !void { @@ -440,26 +441,26 @@ fn _closure_return_20000000000() i64 { } test "secret is not Base32 - invalid padding" { - const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "H", .url = "", .period = 60, .digits = 6 } }; + const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "H", .url = "", .period = 60, .digits = 6, .algorithm = "SHA1" } }; const actualError = authenticator.code(std.testing.allocator, _closure_return_1725695340); try std.testing.expectError(Base32Error.InvalidPadding, actualError); } test "secret is not Base32 - invalid padding, input has too many bytes" { - const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "HAX", .url = "", .period = 60, .digits = 6 } }; + const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "HAX", .url = "", .period = 60, .digits = 6, .algorithm = "SHA1" } }; const actualError = authenticator.code(std.testing.allocator, _closure_return_1725695340); try std.testing.expectError(Base32Error.InvalidPadding, actualError); } test "secret is not Base32 - invalid padding, trailing bits are not 0" { - const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "HX", .url = "", .period = 60, .digits = 6 } }; + const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "HX", .url = "", .period = 60, .digits = 6, .algorithm = "SHA1" } }; const actualError = authenticator.code(std.testing.allocator, _closure_return_1725695340); try std.testing.expectError(Base32Error.InvalidPadding, actualError); } test "secret is not Base32 - invalid character" { - const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "0", .url = "", .period = 60, .digits = 6 } }; + const authenticator = Authenticator{ .url = OtpAuthUrl{ .name = "", .secretEncoded = "0", .url = "", .period = 60, .digits = 6, .algorithm = "SHA1" } }; const actualError = authenticator.code(std.testing.allocator, _closure_return_1725695340); try std.testing.expectError(Base32Error.InvalidCharacter, actualError);