diff --git a/ArcaneLibs b/ArcaneLibs
-Subproject 7cbf235cceb82dc711622314c49edfc7e2614dc
+Subproject 549f13d0be4a5a386cd027242acce752b0c674d
diff --git a/LibMatrix.Federation/AuthenticatedFederationClient.cs b/LibMatrix.Federation/AuthenticatedFederationClient.cs
index 6f8d44b..15dc88f 100644
--- a/LibMatrix.Federation/AuthenticatedFederationClient.cs
+++ b/LibMatrix.Federation/AuthenticatedFederationClient.cs
@@ -1,20 +1,24 @@
-using LibMatrix.Homeservers;
+using LibMatrix.Abstractions;
+using LibMatrix.Federation.Extensions;
+using LibMatrix.Homeservers;
namespace LibMatrix.Federation;
-public class AuthenticatedFederationClient : FederationClient {
+public class AuthenticatedFederationClient(string federationEndpoint, AuthenticatedFederationClient.AuthenticatedFederationConfiguration config, string? proxy = null) : FederationClient(federationEndpoint, proxy) {
public class AuthenticatedFederationConfiguration {
-
- }
- public AuthenticatedFederationClient(string federationEndpoint, AuthenticatedFederationConfiguration config, string? proxy = null) : base(federationEndpoint, proxy)
- {
-
+ public required VersionedHomeserverPrivateKey PrivateKey { get; set; }
+ public required string OriginServerName { get; set; }
}
- // public async Task<UserDeviceListResponse> GetUserDevicesAsync(string userId) {
- // var response = await GetAsync<UserDeviceListResponse>($"/_matrix/federation/v1/user/devices/{userId}", accessToken);
- // return response;
- // }
+ public async Task<UserDeviceListResponse> GetUserDevicesAsync(string userId) {
+ var response = await HttpClient.SendAsync(new XMatrixAuthorizationScheme.XMatrixRequestSignature() {
+ OriginServerName = config.OriginServerName,
+ DestinationServerName = userId.Split(':', 2)[1],
+ Method = "GET",
+ Uri = $"/_matrix/federation/v1/user/devices/{userId}",
+ }.ToSignedHttpRequestMessage(config.PrivateKey));
+ return response;
+ }
}
\ No newline at end of file
diff --git a/LibMatrix.Federation/Extensions/Ed25519Extensions.cs b/LibMatrix.Federation/Extensions/Ed25519Extensions.cs
index 69baf58..e5a9e5d 100644
--- a/LibMatrix.Federation/Extensions/Ed25519Extensions.cs
+++ b/LibMatrix.Federation/Extensions/Ed25519Extensions.cs
@@ -1,3 +1,4 @@
+using LibMatrix.Abstractions;
using LibMatrix.FederationTest.Utilities;
using Org.BouncyCastle.Crypto.Parameters;
@@ -5,4 +6,5 @@ namespace LibMatrix.Federation.Extensions;
public static class Ed25519Extensions {
public static string ToUnpaddedBase64(this Ed25519PublicKeyParameters key) => UnpaddedBase64.Encode(key.GetEncoded());
+ public static Ed25519PrivateKeyParameters GetPrivateEd25519Key(this VersionedHomeserverPrivateKey key) => new(UnpaddedBase64.Decode(key.PrivateKey), 0);
}
\ No newline at end of file
diff --git a/LibMatrix.Federation/Extensions/ObjectExtensions.cs b/LibMatrix.Federation/Extensions/ObjectExtensions.cs
new file mode 100644
index 0000000..d20385d
--- /dev/null
+++ b/LibMatrix.Federation/Extensions/ObjectExtensions.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using LibMatrix.Abstractions;
+using LibMatrix.Extensions;
+using LibMatrix.FederationTest.Utilities;
+using LibMatrix.Responses.Federation;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math.EC.Rfc8032;
+
+namespace LibMatrix.Federation.Extensions;
+public static class ObjectExtensions {
+ public static SignedObject<T> Sign<T>(this T content, string serverName, string keyName, Ed25519PrivateKeyParameters key) {
+ SignedObject<T> signedObject = new() {
+ Signatures = [],
+ Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(content)) ?? new JsonObject(),
+ };
+
+ var contentBytes = CanonicalJsonSerializer.SerializeToUtf8Bytes(signedObject.Content);
+ var signature = new byte[Ed25519.SignatureSize];
+ key.Sign(Ed25519.Algorithm.Ed25519, null, contentBytes, 0, contentBytes.Length, signature, 0);
+
+ if (!signedObject.Signatures.ContainsKey(serverName))
+ signedObject.Signatures[serverName] = new Dictionary<string, string>();
+
+ signedObject.Signatures[serverName][keyName] = UnpaddedBase64.Encode(signature);
+ return signedObject;
+ }
+
+ public static SignedObject<T> Sign<T>(this T content, VersionedHomeserverPrivateKey privateKey)
+ => Sign(content, privateKey.ServerName, privateKey.KeyId, privateKey.GetPrivateEd25519Key());
+}
\ No newline at end of file
diff --git a/LibMatrix.Federation/Extensions/SignedObjectExtensions.cs b/LibMatrix.Federation/Extensions/SignedObjectExtensions.cs
new file mode 100644
index 0000000..eb1376e
--- /dev/null
+++ b/LibMatrix.Federation/Extensions/SignedObjectExtensions.cs
@@ -0,0 +1,37 @@
+using LibMatrix.Extensions;
+using LibMatrix.FederationTest.Utilities;
+using LibMatrix.Responses.Federation;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math.EC.Rfc8032;
+
+namespace LibMatrix.Federation.Extensions;
+public static class SignedObjectExtensions {
+ public static SignedObject<T> Sign<T>(this SignedObject<T> content, string serverName, string keyName, Ed25519PrivateKeyParameters key) {
+ var signResult = content.Content.Sign(serverName, keyName, key);
+ var signedObject = new SignedObject<T> {
+ Signatures = content.Signatures,
+ Content = signResult.Content
+ };
+
+ if (!signedObject.Signatures.ContainsKey(serverName))
+ signedObject.Signatures[serverName] = new Dictionary<string, string>();
+
+ signedObject.Signatures[serverName][keyName] = signResult.Signatures[serverName][keyName];
+ return signedObject;
+ }
+
+ public static bool ValidateSignature<T>(this SignedObject<T> content, string serverName, string keyName, Ed25519PublicKeyParameters key) {
+ if (!content.Signatures.TryGetValue(serverName, out var serverSignatures))
+ return false;
+
+ if (!serverSignatures.TryGetValue(keyName, out var signatureBase64))
+ return false;
+
+ var signature = UnpaddedBase64.Decode(signatureBase64);
+ if (signature.Length != Ed25519.SignatureSize)
+ return false;
+
+ var contentBytes = CanonicalJsonSerializer.SerializeToUtf8Bytes(content.Content);
+ return Ed25519.Verify(signature, 0, key.GetEncoded(), 0, contentBytes, 0, contentBytes.Length);
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs b/LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs
new file mode 100644
index 0000000..792264a
--- /dev/null
+++ b/LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs
@@ -0,0 +1,20 @@
+using System.Net.Http.Json;
+using LibMatrix.Abstractions;
+
+namespace LibMatrix.Federation.Extensions;
+
+public static class XMatrixAuthorizationSchemeExtensions {
+ public static HttpRequestMessage ToSignedHttpRequestMessage(this XMatrixAuthorizationScheme.XMatrixRequestSignature requestSignature, VersionedHomeserverPrivateKey privateKey) {
+ var signature = requestSignature.Sign(privateKey);
+ var requestMessage = new HttpRequestMessage {
+ Method = new HttpMethod(requestSignature.Method),
+ RequestUri = new Uri(requestSignature.Uri, UriKind.Relative)
+ };
+
+ if (requestSignature.Content != null) {
+ requestMessage.Content = JsonContent.Create(requestSignature.Content);
+ }
+
+ return requestMessage;
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix.Federation/XMatrixAuthorizationScheme.cs b/LibMatrix.Federation/XMatrixAuthorizationScheme.cs
index fc402b7..45899b8 100644
--- a/LibMatrix.Federation/XMatrixAuthorizationScheme.cs
+++ b/LibMatrix.Federation/XMatrixAuthorizationScheme.cs
@@ -1,4 +1,3 @@
-using System.Net;
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
diff --git a/LibMatrix/Abstractions/VersionedKeyId.cs b/LibMatrix/Abstractions/VersionedKeyId.cs
new file mode 100644
index 0000000..0a6d651
--- /dev/null
+++ b/LibMatrix/Abstractions/VersionedKeyId.cs
@@ -0,0 +1,24 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibMatrix.Abstractions;
+
+[DebuggerDisplay("{Algorithm}:{KeyId}")]
+[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
+public class VersionedKeyId {
+ public required string Algorithm { get; set; }
+ public required string KeyId { get; set; }
+
+ public static implicit operator VersionedKeyId(string key) {
+ var parts = key.Split(':', 2);
+ if (parts.Length != 2) throw new ArgumentException("Invalid key format. Expected 'algorithm:keyId'.", nameof(key));
+ return new VersionedKeyId { Algorithm = parts[0], KeyId = parts[1] };
+ }
+
+ public static implicit operator string(VersionedKeyId key) => $"{key.Algorithm}:{key.KeyId}";
+ public static implicit operator (string, string)(VersionedKeyId key) => (key.Algorithm, key.KeyId);
+ public static implicit operator VersionedKeyId((string algorithm, string keyId) key) => (key.algorithm, key.keyId);
+
+ public override bool Equals(object? obj) => obj is VersionedKeyId other && Algorithm == other.Algorithm && KeyId == other.KeyId;
+ public override int GetHashCode() => HashCode.Combine(Algorithm, KeyId);
+}
\ No newline at end of file
diff --git a/LibMatrix/Abstractions/VersionedPublicKey.cs b/LibMatrix/Abstractions/VersionedPublicKey.cs
new file mode 100644
index 0000000..ad6747d
--- /dev/null
+++ b/LibMatrix/Abstractions/VersionedPublicKey.cs
@@ -0,0 +1,16 @@
+namespace LibMatrix.Abstractions;
+
+public class VersionedPublicKey {
+ public required VersionedKeyId KeyId { get; set; }
+ public required string PublicKey { get; set; }
+}
+
+public class VersionedPrivateKey : VersionedPublicKey {
+ public required string PrivateKey { get; set; }
+}
+public class VersionedHomeserverPublicKey : VersionedPublicKey {
+ public required string ServerName { get; set; }
+}
+public class VersionedHomeserverPrivateKey : VersionedPrivateKey {
+ public required string ServerName { get; set; }
+}
\ No newline at end of file
diff --git a/LibMatrix/Homeservers/FederationClient.cs b/LibMatrix/Homeservers/FederationClient.cs
index a2cb12d..9760e20 100644
--- a/LibMatrix/Homeservers/FederationClient.cs
+++ b/LibMatrix/Homeservers/FederationClient.cs
@@ -1,7 +1,6 @@
-using System.Text.Json.Serialization;
using LibMatrix.Extensions;
+using LibMatrix.Responses.Federation;
using LibMatrix.Services;
-using Microsoft.VisualBasic.CompilerServices;
namespace LibMatrix.Homeservers;
@@ -18,81 +17,7 @@ public class FederationClient {
public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; }
public async Task<ServerVersionResponse> GetServerVersionAsync() => await HttpClient.GetFromJsonAsync<ServerVersionResponse>("/_matrix/federation/v1/version");
- public async Task<ServerKeysResponse> GetServerKeysAsync() => await HttpClient.GetFromJsonAsync<ServerKeysResponse>("/_matrix/key/v2/server");
+ public async Task<SignedObject<ServerKeysResponse>> GetServerKeysAsync() => await HttpClient.GetFromJsonAsync<SignedObject<ServerKeysResponse>>("/_matrix/key/v2/server");
}
-public class ServerKeysResponse {
- [JsonPropertyName("server_name")]
- public string ServerName { get; set; }
- [JsonPropertyName("valid_until_ts")]
- public ulong ValidUntilTs { get; set; }
-
- [JsonIgnore]
- public DateTime ValidUntil {
- get => DateTimeOffset.FromUnixTimeMilliseconds((long)ValidUntilTs).DateTime;
- set => ValidUntilTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds();
- }
-
- [JsonPropertyName("verify_keys")]
- public Dictionary<string, CurrentVerifyKey> VerifyKeys { get; set; } = new();
-
- [JsonIgnore]
- public Dictionary<VersionedKeyId, CurrentVerifyKey> VerifyKeysById {
- get => VerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value);
- set => VerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value);
- }
-
- [JsonPropertyName("old_verify_keys")]
- public Dictionary<string, ExpiredVerifyKey> OldVerifyKeys { get; set; } = new();
-
- [JsonIgnore]
- public Dictionary<VersionedKeyId, ExpiredVerifyKey> OldVerifyKeysById {
- get => OldVerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value);
- set => OldVerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value);
- }
-
- public class VersionedKeyId {
- public required string Algorithm { get; set; }
- public required string KeyId { get; set; }
-
- public static implicit operator VersionedKeyId(string key) {
- var parts = key.Split(':', 2);
- if (parts.Length != 2) throw new ArgumentException("Invalid key format. Expected 'algorithm:keyId'.", nameof(key));
- return new VersionedKeyId { Algorithm = parts[0], KeyId = parts[1] };
- }
-
- public static implicit operator string(VersionedKeyId key) => $"{key.Algorithm}:{key.KeyId}";
- public static implicit operator (string, string)(VersionedKeyId key) => (key.Algorithm, key.KeyId);
- public static implicit operator VersionedKeyId((string algorithm, string keyId) key) => (key.algorithm, key.keyId);
- }
-
- public class CurrentVerifyKey {
- [JsonPropertyName("key")]
- public string Key { get; set; }
- }
-
- public class ExpiredVerifyKey : CurrentVerifyKey {
- [JsonPropertyName("expired_ts")]
- public ulong ExpiredTs { get; set; }
-
- [JsonIgnore]
- public DateTime Expired {
- get => DateTimeOffset.FromUnixTimeMilliseconds((long)ExpiredTs).DateTime;
- set => ExpiredTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds();
- }
- }
-}
-
-public class ServerVersionResponse {
- [JsonPropertyName("server")]
- public required ServerInfo Server { get; set; }
-
- public class ServerInfo {
- [JsonPropertyName("name")]
- public string Name { get; set; }
-
- [JsonPropertyName("version")]
- public string Version { get; set; }
- }
-}
\ No newline at end of file
diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj
index 62bb48f..23d4eb8 100644
--- a/LibMatrix/LibMatrix.csproj
+++ b/LibMatrix/LibMatrix.csproj
@@ -18,8 +18,8 @@
</ItemGroup>
<ItemGroup>
- <!-- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250313-104848" Condition="'$(Configuration)' == 'Release'" />-->
- <!-- <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>-->
+ <!-- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250313-104848" Condition="'$(Configuration)' == 'Release'" />-->
+ <!-- <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>-->
<ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj"/>
</ItemGroup>
diff --git a/LibMatrix/Responses/Federation/ServerKeysResponse.cs b/LibMatrix/Responses/Federation/ServerKeysResponse.cs
new file mode 100644
index 0000000..cb62e34
--- /dev/null
+++ b/LibMatrix/Responses/Federation/ServerKeysResponse.cs
@@ -0,0 +1,55 @@
+using System.Diagnostics;
+using System.Text.Json.Serialization;
+using LibMatrix.Abstractions;
+
+namespace LibMatrix.Responses.Federation;
+
+public class ServerKeysResponse {
+ [JsonPropertyName("server_name")]
+ public string ServerName { get; set; }
+
+ [JsonPropertyName("valid_until_ts")]
+ public ulong ValidUntilTs { get; set; }
+
+ [JsonIgnore]
+ public DateTime ValidUntil {
+ get => DateTimeOffset.FromUnixTimeMilliseconds((long)ValidUntilTs).DateTime;
+ set => ValidUntilTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds();
+ }
+
+ [JsonPropertyName("verify_keys")]
+ public Dictionary<string, CurrentVerifyKey> VerifyKeys { get; set; } = new();
+
+ [JsonIgnore]
+ public Dictionary<VersionedKeyId, CurrentVerifyKey> VerifyKeysById {
+ get => VerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value);
+ set => VerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value);
+ }
+
+ [JsonPropertyName("old_verify_keys")]
+ public Dictionary<string, ExpiredVerifyKey> OldVerifyKeys { get; set; } = new();
+
+ [JsonIgnore]
+ public Dictionary<VersionedKeyId, ExpiredVerifyKey> OldVerifyKeysById {
+ get => OldVerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value);
+ set => OldVerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value);
+ }
+
+ [DebuggerDisplay("{Key}")]
+ public class CurrentVerifyKey {
+ [JsonPropertyName("key")]
+ public string Key { get; set; }
+ }
+
+ [DebuggerDisplay("{Key} (expired {Expired})")]
+ public class ExpiredVerifyKey : CurrentVerifyKey {
+ [JsonPropertyName("expired_ts")]
+ public ulong ExpiredTs { get; set; }
+
+ [JsonIgnore]
+ public DateTime Expired {
+ get => DateTimeOffset.FromUnixTimeMilliseconds((long)ExpiredTs).DateTime;
+ set => ExpiredTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds();
+ }
+ }
+}
diff --git a/LibMatrix/Responses/Federation/ServerVersionResponse.cs b/LibMatrix/Responses/Federation/ServerVersionResponse.cs
new file mode 100644
index 0000000..b09bdd0
--- /dev/null
+++ b/LibMatrix/Responses/Federation/ServerVersionResponse.cs
@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+
+namespace LibMatrix.Responses.Federation;
+
+public class ServerVersionResponse {
+ [JsonPropertyName("server")]
+ public required ServerInfo Server { get; set; }
+
+ public class ServerInfo {
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("version")]
+ public string Version { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix.Federation/Utilities/JsonSigning.cs b/LibMatrix/Responses/Federation/SignedObject.cs
index c727cde..3f6ffd6 100644
--- a/LibMatrix.Federation/Utilities/JsonSigning.cs
+++ b/LibMatrix/Responses/Federation/SignedObject.cs
@@ -1,17 +1,11 @@
-using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using ArcaneLibs.Extensions;
-using LibMatrix.Extensions;
-using LibMatrix.FederationTest.Utilities;
+using LibMatrix.Abstractions;
using LibMatrix.Homeservers;
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Math.EC.Rfc8032;
-namespace LibMatrix.Federation.Utilities;
-
-public static class JsonSigning { }
+namespace LibMatrix.Responses.Federation;
[JsonConverter(typeof(SignedObjectConverterFactory))]
public class SignedObject<T> {
@@ -19,8 +13,8 @@ public class SignedObject<T> {
public Dictionary<string, Dictionary<string, string>> Signatures { get; set; } = new();
[JsonIgnore]
- public Dictionary<string, Dictionary<ServerKeysResponse.VersionedKeyId, string>> VerifyKeysById {
- get => Signatures.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (ServerKeysResponse.VersionedKeyId)key.Key, key => key.Value));
+ public Dictionary<string, Dictionary<VersionedKeyId, string>> SignaturesById {
+ get => Signatures.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value));
set => Signatures = value.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (string)key.Key, key => key.Value));
}
@@ -34,7 +28,6 @@ public class SignedObject<T> {
}
}
-// Content needs to be merged at toplevel
public class SignedObjectConverter<T> : JsonConverter<SignedObject<T>> {
public override SignedObject<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
var jsonObject = JsonSerializer.Deserialize<JsonObject>(ref reader, options);
@@ -72,37 +65,4 @@ internal class SignedObjectConverterFactory : JsonConverterFactory {
var converter = (JsonConverter)Activator.CreateInstance(typeof(SignedObjectConverter<>).MakeGenericType(wrappedType))!;
return converter;
}
-}
-
-public static class ObjectExtensions {
- public static SignedObject<T> Sign<T>(this SignedObject<T> content, string serverName, string keyName, Ed25519PrivateKeyParameters key) {
- var signResult = Sign(content.Content, serverName, keyName, key);
- var signedObject = new SignedObject<T> {
- Signatures = content.Signatures,
- Content = signResult.Content
- };
-
- if (!signedObject.Signatures.ContainsKey(serverName))
- signedObject.Signatures[serverName] = new Dictionary<string, string>();
-
- signedObject.Signatures[serverName][keyName] = signResult.Signatures[serverName][keyName];
- return signedObject;
- }
-
- public static SignedObject<T> Sign<T>(this T content, string serverName, string keyName, Ed25519PrivateKeyParameters key) {
- SignedObject<T> signedObject = new() {
- Signatures = [],
- Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(content)) ?? new JsonObject(),
- };
-
- var contentBytes = CanonicalJsonSerializer.SerializeToUtf8Bytes(signedObject.Content);
- var signature = new byte[Ed25519.SignatureSize];
- key.Sign(Ed25519.Algorithm.Ed25519, null, contentBytes, 0, contentBytes.Length, signature, 0);
-
- if (!signedObject.Signatures.ContainsKey(serverName))
- signedObject.Signatures[serverName] = new Dictionary<string, string>();
-
- signedObject.Signatures[serverName][keyName] = UnpaddedBase64.Encode(signature);
- return signedObject;
- }
}
\ No newline at end of file
diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs
index 36bc828..c984c34 100644
--- a/LibMatrix/Services/HomeserverProviderService.cs
+++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -2,6 +2,7 @@ using System.Net.Http.Json;
using ArcaneLibs.Collections;
using LibMatrix.Homeservers;
using LibMatrix.Responses;
+using LibMatrix.Responses.Federation;
using Microsoft.Extensions.Logging;
namespace LibMatrix.Services;
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<object> PingRemoteServer(string serverName) {
+ Dictionary<string, object> 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<KeyValuePair<string, object>> PingRemoteServers([FromBody] List<string>? serverNames) {
+ Dictionary<string, object> 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<string, object>(serverName, responseMessage[serverName]);
+ // await Response.Body.FlushAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs
index 33d0b99..6516415 100644
--- a/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs
@@ -1,10 +1,11 @@
+using LibMatrix.Abstractions;
using LibMatrix.Federation.Extensions;
-using LibMatrix.Federation.Utilities;
using LibMatrix.FederationTest.Services;
using LibMatrix.Homeservers;
+using LibMatrix.Responses.Federation;
using Microsoft.AspNetCore.Mvc;
-namespace LibMatrix.FederationTest.Controllers;
+namespace LibMatrix.FederationTest.Controllers.Spec;
[ApiController]
[Route("_matrix/key/v2/")]
@@ -32,7 +33,7 @@ public class FederationKeysController(FederationTestConfiguration config, Federa
}
}
}
- }.Sign(config.ServerName, new ServerKeysResponse.VersionedKeyId() { Algorithm = "ed25519", KeyId = "0" }, keys.privateKey);
+ }.Sign(config.ServerName, new VersionedKeyId() { Algorithm = "ed25519", KeyId = "0" }, keys.privateKey);
}
_serverKeyCacheLock.Release();
diff --git a/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs
index 2c3aaa3..d146cfd 100644
--- a/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs
@@ -1,7 +1,8 @@
using LibMatrix.Homeservers;
+using LibMatrix.Responses.Federation;
using Microsoft.AspNetCore.Mvc;
-namespace LibMatrix.FederationTest.Controllers;
+namespace LibMatrix.FederationTest.Controllers.Spec;
[ApiController]
[Route("_matrix/federation/v1/")]
diff --git a/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs
index 28fca8d..b91868c 100644
--- a/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs
@@ -1,7 +1,7 @@
using LibMatrix.Services.WellKnownResolver.WellKnownResolvers;
using Microsoft.AspNetCore.Mvc;
-namespace LibMatrix.FederationTest.Controllers;
+namespace LibMatrix.FederationTest.Controllers.Spec;
[ApiController]
[Route(".well-known/")]
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/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<FederationTestConfiguration>();
builder.Services.AddSingleton<FederationKeyStore>();
-
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<PrivateKeyCollection>()
+ }
+
+ 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);
}
diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs
index 93e4b4f..495f9e3 100644
--- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs
+++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using LibMatrix.Homeservers;
using LibMatrix.Responses;
+using LibMatrix.Responses.Federation;
using Microsoft.AspNetCore.Mvc;
namespace LibMatrix.HomeserverEmulator.Controllers;
|