using System.Text; using System.Text.Json; using VoiceCat.Crypto; namespace VoiceCat.Tests; public sealed class PasswordTests { [Fact] public void VerifiesLibsodiumHashesWithoutPasswordNormalization() { var hasher = new PasswordHasher(); using var fixture = JsonDocument.Parse(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "cpp-passwords.json"))); foreach (var item in fixture.RootElement.GetProperty("hashes").EnumerateArray()) { string encodedPassword = item.GetProperty("passwordBase64").GetString()!; string password = Encoding.UTF8.GetString(Convert.FromBase64String(encodedPassword.PadRight((encodedPassword.Length + 3) / 4 * 4, '='))); string hash = item.GetProperty("hash").GetString()!; Assert.True(hasher.Verify(password, hash)); Assert.False(hasher.Verify(password + "!", hash)); } } [Fact] public void FreshHashesUseRandomSaltAndNativePhcFormat() { var hasher = new PasswordHasher(); string first = hasher.Hash("hello"); string second = hasher.Hash("hello"); Assert.NotEqual(first, second); Assert.StartsWith("$argon2id$v=19$m=65536,t=2,p=1$", first); Assert.True(hasher.Verify("hello", first)); Assert.False(hasher.Verify("wrong", first)); } [Theory] [InlineData("$argon2id$v=19$m=999999999,t=2,p=1$c2FsdA$aGFzaA")] [InlineData("$argon2id$v=19$m=65536,t=99999,p=1$c2FsdA$aGFzaA")] [InlineData("$argon2id$v=16$m=65536,t=2,p=1$c2FsdA$aGFzaA")] [InlineData("$argon2id$v=19$m=65536,t=2,p=1$!!!$!!!")] public void MalformedOrExcessiveHashesFailClosed(string hash) => Assert.False(new PasswordHasher().Verify("hello", hash)); }