From c2613aab129c8d1d5aba3b7ed02609059a826c84 Mon Sep 17 00:00:00 2001 From: Rory& Date: Wed, 16 Jul 2025 19:00:05 +0200 Subject: Federation tuff --- .../Controllers/FederationKeysController.cs | 41 ----------- .../Controllers/FederationVersionController.cs | 18 ----- .../Controllers/RemoteServerPingController.cs | 79 ++++++++++++++++++++++ .../Controllers/Spec/FederationKeysController.cs | 42 ++++++++++++ .../Spec/FederationVersionController.cs | 19 ++++++ .../Controllers/Spec/WellKnownController.cs | 19 ++++++ .../Controllers/TestController.cs | 6 +- .../Controllers/WellKnownController.cs | 19 ------ Utilities/LibMatrix.FederationTest/Program.cs | 14 ++-- .../Services/FederationKeyStore.cs | 25 ++++++- .../Utilities/Ed25519Utils.cs | 2 +- 11 files changed, 197 insertions(+), 87 deletions(-) delete mode 100644 Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs delete mode 100644 Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs create mode 100644 Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs create mode 100644 Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs create mode 100644 Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs create mode 100644 Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs delete mode 100644 Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs (limited to 'Utilities/LibMatrix.FederationTest') diff --git a/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs b/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs deleted file mode 100644 index 33d0b99..0000000 --- a/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs +++ /dev/null @@ -1,41 +0,0 @@ -using LibMatrix.Federation.Extensions; -using LibMatrix.Federation.Utilities; -using LibMatrix.FederationTest.Services; -using LibMatrix.Homeservers; -using Microsoft.AspNetCore.Mvc; - -namespace LibMatrix.FederationTest.Controllers; - -[ApiController] -[Route("_matrix/key/v2/")] -public class FederationKeysController(FederationTestConfiguration config, FederationKeyStore keyStore) { - static FederationKeysController() { - Console.WriteLine("INFO | FederationKeysController initialized."); - } - - private static SignedObject? _cachedServerKeysResponse; - private static SemaphoreSlim _serverKeyCacheLock = new SemaphoreSlim(1, 1); - - [HttpGet("server")] - public async Task> GetServerKeys() { - await _serverKeyCacheLock.WaitAsync(); - if (_cachedServerKeysResponse == null || _cachedServerKeysResponse.TypedContent.ValidUntil < DateTime.Now + TimeSpan.FromSeconds(30)) { - var keys = keyStore.GetCurrentSigningKey(); - _cachedServerKeysResponse = new ServerKeysResponse() { - ValidUntil = DateTime.Now + TimeSpan.FromMinutes(1), - ServerName = config.ServerName, - OldVerifyKeys = [], - VerifyKeysById = new() { - { - new() { Algorithm = "ed25519", KeyId = "0" }, new ServerKeysResponse.CurrentVerifyKey() { - Key = keys.publicKey.ToUnpaddedBase64(), - } - } - } - }.Sign(config.ServerName, new ServerKeysResponse.VersionedKeyId() { Algorithm = "ed25519", KeyId = "0" }, keys.privateKey); - } - _serverKeyCacheLock.Release(); - - return _cachedServerKeysResponse; - } -} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs b/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs deleted file mode 100644 index 2c3aaa3..0000000 --- a/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using LibMatrix.Homeservers; -using Microsoft.AspNetCore.Mvc; - -namespace LibMatrix.FederationTest.Controllers; - -[ApiController] -[Route("_matrix/federation/v1/")] -public class FederationVersionController : ControllerBase { - [HttpGet("version")] - public ServerVersionResponse GetVersion() { - return new ServerVersionResponse { - Server = new() { - Name = "LibMatrix.Federation", - Version = "0.0.0", - } - }; - } -} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs b/Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs new file mode 100644 index 0000000..8d3a5ea --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs @@ -0,0 +1,79 @@ +using LibMatrix.Federation; +using LibMatrix.Federation.Extensions; +using LibMatrix.FederationTest.Services; +using LibMatrix.FederationTest.Utilities; +using LibMatrix.Services; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers; + +[ApiController] +public class RemoteServerPingController(FederationTestConfiguration config, FederationKeyStore keyStore, HomeserverResolverService hsResolver) : ControllerBase { + [HttpGet] + [Route("/ping/{serverName}")] + public async Task PingRemoteServer(string serverName) { + Dictionary responseMessage = []; + var hsResolveResult = await hsResolver.ResolveHomeserverFromWellKnown(serverName, enableClient: false); + responseMessage["resolveResult"] = hsResolveResult; + + if (!string.IsNullOrWhiteSpace(hsResolveResult.Server)) { + try { + var ownKey = keyStore.GetCurrentSigningKey(); + var hs = new AuthenticatedFederationClient(hsResolveResult.Server, new() { + PrivateKey = , + OriginServerName = null + }); + var keys = await hs.GetServerKeysAsync(); + responseMessage["version"] = await hs.GetServerVersionAsync(); + responseMessage["keys"] = keys; + + responseMessage["keysAreValid"] = keys.SignaturesById[serverName].ToDictionary( + sig => (string)sig.Key, + sig => keys.ValidateSignature(serverName, sig.Key, Ed25519Utils.LoadPublicKeyFromEncoded(keys.TypedContent.VerifyKeysById[sig.Key].Key)) + ); + } + catch (Exception ex) { + responseMessage["error"] = new { + error = "Failed to connect to remote server", + message = ex.Message, + st = ex.StackTrace, + }; + return responseMessage; + } + } + + return responseMessage; + } + + [HttpPost] + [Route("/ping/")] + public async IAsyncEnumerable> PingRemoteServers([FromBody] List? serverNames) { + Dictionary responseMessage = []; + + if (serverNames == null || !serverNames.Any()) { + responseMessage["error"] = "No server names provided"; + yield return responseMessage.First(); + yield break; + } + + var results = serverNames!.Select(s => (s, PingRemoteServer(s))).ToList(); + foreach (var result in results) { + var (serverName, pingResult) = result; + try { + responseMessage[serverName] = await pingResult; + if (results.Where(x => !x.Item2.IsCompleted).Select(x => x.s).ToList() is { } servers and not { Count: 0 }) + Console.WriteLine($"INFO | Waiting for servers: {string.Join(", ", servers)}"); + } + catch (Exception ex) { + responseMessage[serverName] = new { + error = "Failed to ping remote server", + message = ex.Message, + st = ex.StackTrace, + }; + } + + yield return new KeyValuePair(serverName, responseMessage[serverName]); + // await Response.Body.FlushAsync(); + } + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs new file mode 100644 index 0000000..6516415 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs @@ -0,0 +1,42 @@ +using LibMatrix.Abstractions; +using LibMatrix.Federation.Extensions; +using LibMatrix.FederationTest.Services; +using LibMatrix.Homeservers; +using LibMatrix.Responses.Federation; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers.Spec; + +[ApiController] +[Route("_matrix/key/v2/")] +public class FederationKeysController(FederationTestConfiguration config, FederationKeyStore keyStore) { + static FederationKeysController() { + Console.WriteLine("INFO | FederationKeysController initialized."); + } + + private static SignedObject? _cachedServerKeysResponse; + private static SemaphoreSlim _serverKeyCacheLock = new SemaphoreSlim(1, 1); + + [HttpGet("server")] + public async Task> GetServerKeys() { + await _serverKeyCacheLock.WaitAsync(); + if (_cachedServerKeysResponse == null || _cachedServerKeysResponse.TypedContent.ValidUntil < DateTime.Now + TimeSpan.FromSeconds(30)) { + var keys = keyStore.GetCurrentSigningKey(); + _cachedServerKeysResponse = new ServerKeysResponse() { + ValidUntil = DateTime.Now + TimeSpan.FromMinutes(1), + ServerName = config.ServerName, + OldVerifyKeys = [], + VerifyKeysById = new() { + { + new() { Algorithm = "ed25519", KeyId = "0" }, new ServerKeysResponse.CurrentVerifyKey() { + Key = keys.publicKey.ToUnpaddedBase64(), + } + } + } + }.Sign(config.ServerName, new VersionedKeyId() { Algorithm = "ed25519", KeyId = "0" }, keys.privateKey); + } + _serverKeyCacheLock.Release(); + + return _cachedServerKeysResponse; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs new file mode 100644 index 0000000..d146cfd --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs @@ -0,0 +1,19 @@ +using LibMatrix.Homeservers; +using LibMatrix.Responses.Federation; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers.Spec; + +[ApiController] +[Route("_matrix/federation/v1/")] +public class FederationVersionController : ControllerBase { + [HttpGet("version")] + public ServerVersionResponse GetVersion() { + return new ServerVersionResponse { + Server = new() { + Name = "LibMatrix.Federation", + Version = "0.0.0", + } + }; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs new file mode 100644 index 0000000..b91868c --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs @@ -0,0 +1,19 @@ +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers.Spec; + +[ApiController] +[Route(".well-known/")] +public class WellKnownController(ILogger logger) : ControllerBase { + static WellKnownController() { + Console.WriteLine("INFO | WellKnownController initialized."); + } + [HttpGet("matrix/server")] + public ServerWellKnown GetMatrixServerWellKnown() { + // {Request.Headers["X-Forwarded-Proto"].FirstOrDefault(Request.Scheme)}:// + return new() { + Homeserver = $"{Request.Headers["X-Forwarded-Host"].FirstOrDefault(Request.Host.Host)}:{Request.Headers["X-Forwarded-Port"].FirstOrDefault("443")}", + }; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs b/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs index 4a6bc87..9c0981d 100644 --- a/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs +++ b/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs @@ -1,8 +1,8 @@ using System.Text.Json.Nodes; -using ArcaneLibs.Extensions; +using LibMatrix.Abstractions; using LibMatrix.Extensions; using LibMatrix.Federation; -using LibMatrix.Federation.Utilities; +using LibMatrix.Federation.Extensions; using LibMatrix.FederationTest.Services; using LibMatrix.Homeservers; using Microsoft.AspNetCore.Mvc; @@ -21,7 +21,7 @@ public class TestController(FederationTestConfiguration config, FederationKeySto BaseAddress = new Uri("https://matrix.rory.gay") }; - var keyId = new ServerKeysResponse.VersionedKeyId() { + var keyId = new VersionedKeyId() { Algorithm = "ed25519", KeyId = "0" }; diff --git a/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs b/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs deleted file mode 100644 index 28fca8d..0000000 --- a/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs +++ /dev/null @@ -1,19 +0,0 @@ -using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; -using Microsoft.AspNetCore.Mvc; - -namespace LibMatrix.FederationTest.Controllers; - -[ApiController] -[Route(".well-known/")] -public class WellKnownController(ILogger logger) : ControllerBase { - static WellKnownController() { - Console.WriteLine("INFO | WellKnownController initialized."); - } - [HttpGet("matrix/server")] - public ServerWellKnown GetMatrixServerWellKnown() { - // {Request.Headers["X-Forwarded-Proto"].FirstOrDefault(Request.Scheme)}:// - return new() { - Homeserver = $"{Request.Headers["X-Forwarded-Host"].FirstOrDefault(Request.Host.Host)}:{Request.Headers["X-Forwarded-Port"].FirstOrDefault("443")}", - }; - } -} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Program.cs b/Utilities/LibMatrix.FederationTest/Program.cs index adc809f..18d3421 100644 --- a/Utilities/LibMatrix.FederationTest/Program.cs +++ b/Utilities/LibMatrix.FederationTest/Program.cs @@ -1,10 +1,17 @@ +using System.Text.Json.Serialization; using LibMatrix.FederationTest.Services; +using LibMatrix.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. -builder.Services.AddControllers(); +builder.Services.AddControllers() + .AddJsonOptions(options => { + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.WriteIndented = true; + // options.JsonSerializerOptions.DefaultBufferSize = ; + }).AddMvcOptions(o => { o.SuppressOutputFormatterBuffering = true; }); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); builder.Services.AddHttpLogging(options => { @@ -14,10 +21,10 @@ builder.Services.AddHttpLogging(options => { options.RequestHeaders.Add("X-Forwarded-Port"); }); +builder.Services.AddRoryLibMatrixServices(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - var app = builder.Build(); // Configure the HTTP request pipeline. @@ -25,10 +32,9 @@ if (true || app.Environment.IsDevelopment()) { app.MapOpenApi(); } -app.UseAuthorization(); +// app.UseAuthorization(); app.MapControllers(); // app.UseHttpLogging(); - app.Run(); \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs b/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs index f24d14e..e916703 100644 --- a/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs +++ b/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs @@ -1,3 +1,5 @@ +using System.Text.Json; +using LibMatrix.Abstractions; using LibMatrix.FederationTest.Utilities; using Org.BouncyCastle.Crypto.Parameters; @@ -9,7 +11,28 @@ public class FederationKeyStore(FederationTestConfiguration config) { } private static (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) currentKeyPair = default; - public (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) GetCurrentSigningKey() { + + public class PrivateKeyCollection { + + public required VersionedHomeserverPrivateKey CurrentSigningKey { get; set; } + } + + public PrivateKeyCollection GetCurrentSigningKey() { + if(!Directory.Exists(config.KeyStorePath)) Directory.CreateDirectory(config.KeyStorePath); + var privateKeyPath = Path.Combine(config.KeyStorePath, "private-keys.json"); + + if (!File.Exists(privateKeyPath)) { + var keyPair = InternalGetSigningKey(); + var privateKey = new VersionedHomeserverPrivateKey { + PrivateKey = keyPair.privateKey.GetEncoded().ToUnpaddedBase64(), + }; + File.WriteAllText(privateKeyPath, privateKey.ToJson()); + } + + return JsonSerializer.Deserialize() + } + + private (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) InternalGetSigningKey() { if (currentKeyPair != default) { return currentKeyPair; } diff --git a/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs b/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs index bb57d51..7714fee 100644 --- a/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs +++ b/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs @@ -18,7 +18,7 @@ public class Ed25519Utils { } public static Ed25519PublicKeyParameters LoadPublicKeyFromEncoded(string key) { - var keyBytes = Convert.FromBase64String(key); + var keyBytes = UnpaddedBase64.Decode(key); return new Ed25519PublicKeyParameters(keyBytes, 0); } -- cgit 1.5.1