diff --git a/server/sockeye.csproj b/server/sockeye.csproj index a271fc9..ae93a5c 100644 --- a/server/sockeye.csproj +++ b/server/sockeye.csproj @@ -69,6 +69,8 @@ - + + + \ No newline at end of file diff --git a/sockeye.tests/ResignTests.cs b/sockeye.tests/ResignTests.cs new file mode 100644 index 0000000..983b0eb --- /dev/null +++ b/sockeye.tests/ResignTests.cs @@ -0,0 +1,102 @@ +namespace sockeye.tests; + +/* +Extract key fixture data, note that pgroup / format is 1 for v7 product family keys and not 1 for any v8 family keys +mkdir C:\data\sockeye-fixtures +$env:PGCLIENTENCODING = "UTF8" +.\psql -U postgres -d sockeye -At -o C:\data\sockeye-fixtures\resign.json -c " + SELECT json_agg(t ORDER BY t.id) FROM ( + SELECT id, + pgroup AS format, + key + FROM alicense + WHERE key IS NOT NULL + ) t;" + + + +*/ +// public class UnitTest1 +// { +// [Fact] +// public void Test1() +// { + +// } +// } + + +using System.Text.Json; + +public record ResignRow(long Id, int Format, string Key); + +public static class ResignFixture +{ + static readonly Lazy> _rows = new(Load); + public static IReadOnlyDictionary Rows => _rows.Value; + + static Dictionary Load() + { + var path = "C:\\data\\sockeye-fixtures\\resign.json";//Environment.GetEnvironmentVariable("SOCKEYE_RESIGN_FIXTURE"); + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + throw new InvalidOperationException( + "Set SOCKEYE_RESIGN_FIXTURE to the exported alicense JSON file."); + + var rows = JsonSerializer.Deserialize>( + File.ReadAllText(path), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + return rows.ToDictionary(r => r.Id); + } +} + +public class ResignTests +{ + public static IEnumerable RowIds() => + ResignFixture.Rows.Keys.Select(id => new object[] { id }); + + [Theory] + [MemberData(nameof(RowIds))] + public void Stored_licence_resigns_identically(long id) + { + var row = ResignFixture.Rows[id]; + var keyText = Between(row.Key, "[KEY", "KEY]"); + var expected = StripWs(Between(row.Key, "[SIGNATURE", "SIGNATURE]")); + + var actual = row.Format == 1 + ? Sockeye.Biz.KeyFactory.Sign(keyText) // your extracted internal methods + : Sockeye.Biz.RavenKeyFactory.Sign(keyText); + //test sign returns whole key with signature need to re-extract signature only + actual = StripWs(Between(actual, "[SIGNATURE", "SIGNATURE]")); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Fixture_covers_both_formats() + { + Assert.Contains(ResignFixture.Rows.Values, r => r.Format == 1); + Assert.Contains(ResignFixture.Rows.Values, r => r.Format == 2); + Assert.Contains(ResignFixture.Rows.Values, r => r.Format == 3); + } + + [Fact] + public void Tampered_payload_does_not_match() + { + var row = ResignFixture.Rows.Values.First(r => r.Format == 1); + var keyText = Between(row.Key, "[KEY", "KEY]") + "x"; + var expected = StripWs(Between(row.Key, "[SIGNATURE", "SIGNATURE]")); + Assert.NotEqual(expected, StripWs(Between(Sockeye.Biz.KeyFactory.Sign(keyText), "[SIGNATURE", "SIGNATURE]"))); + } + + static string Between(string s, string start, string end) + { + int a = s.IndexOf(start, StringComparison.Ordinal); + int b = s.IndexOf(end, StringComparison.Ordinal); + if (a < 0 || b < 0 || b < a) + throw new FormatException($"Markers {start}/{end} not found"); + return s.Substring(a + start.Length, b - a - start.Length); + } + + static string StripWs(string s) => string.Concat(s.Where(c => !char.IsWhiteSpace(c))); +} \ No newline at end of file diff --git a/sockeye.tests/sockeye.tests.csproj b/sockeye.tests/sockeye.tests.csproj new file mode 100644 index 0000000..e84c3e3 --- /dev/null +++ b/sockeye.tests/sockeye.tests.csproj @@ -0,0 +1,21 @@ + + + net6.0 + enable + disable + false + + + + + + + + + + + + + + + \ No newline at end of file