/ GUNRPG.Tests / AuthoritySecurityTests.cs
AuthoritySecurityTests.cs
  1  using GUNRPG.Security;
  2  
  3  namespace GUNRPG.Tests;
  4  
  5  public sealed class AuthoritySecurityTests
  6  {
  7      private static readonly DateTimeOffset ReferenceNow = new(2026, 03, 15, 04, 00, 00, TimeSpan.Zero);
  8  
  9      [Fact]
 10      public void VerifyServerCertificate_ReturnsTrue_ForRootSignedUnexpiredCertificate()
 11      {
 12          var rootPrivateKey = CertificateIssuer.GeneratePrivateKey();
 13          var certificateIssuer = new CertificateIssuer(rootPrivateKey);
 14          var authorityRoot = new AuthorityRoot(certificateIssuer.RootPublicKey);
 15          var serverId = Guid.NewGuid();
 16          var serverPublicKey = ServerIdentity.GetPublicKey(ServerIdentity.GeneratePrivateKey());
 17          var issuedAt = ReferenceNow.AddMinutes(-5);
 18          var validUntil = ReferenceNow.AddMinutes(30);
 19  
 20          var certificate = certificateIssuer.IssueServerCertificate(serverId, serverPublicKey, issuedAt, validUntil);
 21  
 22          Assert.True(authorityRoot.VerifyServerCertificate(certificate, ReferenceNow));
 23      }
 24  
 25      [Fact]
 26      public void VerifyServerCertificate_ReturnsFalse_ForExpiredCertificate()
 27      {
 28          var rootPrivateKey = CertificateIssuer.GeneratePrivateKey();
 29          var certificateIssuer = new CertificateIssuer(rootPrivateKey);
 30          var authorityRoot = new AuthorityRoot(certificateIssuer.RootPublicKey);
 31          var serverId = Guid.NewGuid();
 32          var serverPublicKey = ServerIdentity.GetPublicKey(ServerIdentity.GeneratePrivateKey());
 33  
 34          var certificate = certificateIssuer.IssueServerCertificate(
 35              serverId,
 36              serverPublicKey,
 37              ReferenceNow.AddHours(-2),
 38              ReferenceNow.AddHours(-1));
 39  
 40          Assert.False(authorityRoot.VerifyServerCertificate(certificate, ReferenceNow));
 41      }
 42  
 43      [Fact]
 44      public void VerifyRunSignature_ReturnsTrue_ForValidSignedValidation()
 45      {
 46          var serverIdentity = CreateServerIdentity(out var authorityRoot);
 47          var verifier = new SignatureVerifier(authorityRoot);
 48  
 49          var validation = serverIdentity.SignRunValidation(Guid.NewGuid(), Guid.NewGuid(), CreateHash(1));
 50  
 51          Assert.True(verifier.VerifyRunSignature(validation, serverIdentity.Certificate, ReferenceNow));
 52      }
 53  
 54      [Fact]
 55      public void VerifyRunSignature_ReturnsFalse_WhenFinalStateHashIsTampered()
 56      {
 57          var serverIdentity = CreateServerIdentity(out var authorityRoot);
 58          var verifier = new SignatureVerifier(authorityRoot);
 59          var validation = serverIdentity.SignRunValidation(Guid.NewGuid(), Guid.NewGuid(), CreateHash(10));
 60          var tampered = new RunValidationSignature(
 61              validation.RunId,
 62              validation.PlayerId,
 63              CreateHash(11),
 64              validation.ServerId,
 65              validation.Signature);
 66  
 67          Assert.False(verifier.VerifyRunSignature(tampered, serverIdentity.Certificate, ReferenceNow));
 68      }
 69  
 70      [Fact]
 71      public void VerifyRunSignature_ReturnsFalse_WhenServerIdDoesNotMatchCertificate()
 72      {
 73          var serverIdentity = CreateServerIdentity(out var authorityRoot);
 74          var verifier = new SignatureVerifier(authorityRoot);
 75          var validation = serverIdentity.SignRunValidation(Guid.NewGuid(), Guid.NewGuid(), CreateHash(7));
 76          var mismatched = new RunValidationSignature(
 77              validation.RunId,
 78              validation.PlayerId,
 79              validation.FinalStateHash,
 80              Guid.NewGuid(),
 81              validation.Signature);
 82  
 83          Assert.False(verifier.VerifyRunSignature(mismatched, serverIdentity.Certificate, ReferenceNow));
 84      }
 85  
 86      [Fact]
 87      public void VerifySignedRunValidation_Succeeds_WhenCertificateAndSignatureValid()
 88      {
 89          var serverIdentity = CreateServerIdentity(out var authorityRoot);
 90          var verifier = new SignatureVerifier(authorityRoot);
 91  
 92          var signedValidation = serverIdentity.SignSignedRunValidation(Guid.NewGuid(), Guid.NewGuid(), CreateHash(11));
 93  
 94          Assert.True(verifier.Verify(signedValidation, ReferenceNow));
 95      }
 96  
 97      [Fact]
 98      public void SignRunValidation_Throws_WhenFinalStateHashIsNotSha256Length()
 99      {
100          var serverIdentity = CreateServerIdentity(out _);
101  
102          Assert.Throws<ArgumentException>(() =>
103              serverIdentity.SignRunValidation(Guid.NewGuid(), Guid.NewGuid(), [1, 2, 3, 4]));
104      }
105  
106      private static ServerIdentity CreateServerIdentity(out AuthorityRoot authorityRoot)
107      {
108          var rootPrivateKey = CertificateIssuer.GeneratePrivateKey();
109          var certificateIssuer = new CertificateIssuer(rootPrivateKey);
110          authorityRoot = new AuthorityRoot(certificateIssuer.RootPublicKey);
111  
112          var serverPrivateKey = ServerIdentity.GeneratePrivateKey();
113          var certificate = certificateIssuer.IssueServerCertificate(
114              Guid.NewGuid(),
115              ServerIdentity.GetPublicKey(serverPrivateKey),
116              ReferenceNow.AddMinutes(-5),
117              ReferenceNow.AddMinutes(30));
118  
119          return new ServerIdentity(certificate, serverPrivateKey);
120      }
121  
122      private static byte[] CreateHash(byte seed)
123      {
124          var value = new byte[32];
125          for (var i = 0; i < value.Length; i++)
126          {
127              value[i] = (byte)(seed + i);
128          }
129  
130          return value;
131      }
132  }