about summary refs log tree commit diff
path: root/LibMatrix.Federation
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-07-16 19:00:05 +0200
committerRory& <root@rory.gay>2025-07-16 19:00:05 +0200
commitc2613aab129c8d1d5aba3b7ed02609059a826c84 (patch)
treeaeb7a3610cae4c15f013ef7181b379f3b53f5534 /LibMatrix.Federation
parentFix dependency path for federationtest (diff)
downloadLibMatrix-master.tar.xz
Federation tuff HEAD master
Diffstat (limited to 'LibMatrix.Federation')
-rw-r--r--LibMatrix.Federation/AuthenticatedFederationClient.cs26
-rw-r--r--LibMatrix.Federation/Extensions/Ed25519Extensions.cs2
-rw-r--r--LibMatrix.Federation/Extensions/ObjectExtensions.cs31
-rw-r--r--LibMatrix.Federation/Extensions/SignedObjectExtensions.cs37
-rw-r--r--LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs20
-rw-r--r--LibMatrix.Federation/Utilities/JsonSigning.cs108
-rw-r--r--LibMatrix.Federation/XMatrixAuthorizationScheme.cs1
7 files changed, 105 insertions, 120 deletions
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/Utilities/JsonSigning.cs b/LibMatrix.Federation/Utilities/JsonSigning.cs deleted file mode 100644
index c727cde..0000000 --- a/LibMatrix.Federation/Utilities/JsonSigning.cs +++ /dev/null
@@ -1,108 +0,0 @@ -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.Homeservers; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math.EC.Rfc8032; - -namespace LibMatrix.Federation.Utilities; - -public static class JsonSigning { } - -[JsonConverter(typeof(SignedObjectConverterFactory))] -public class SignedObject<T> { - [JsonPropertyName("signatures")] - 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)); - set => Signatures = value.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (string)key.Key, key => key.Value)); - } - - [JsonExtensionData] - public required JsonObject Content { get; set; } - - [JsonIgnore] - public T TypedContent { - get => Content.Deserialize<T>() ?? throw new JsonException("Failed to deserialize TypedContent from Content."); - set => Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value)) ?? new JsonObject(); - } -} - -// 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); - if (jsonObject == null) { - throw new JsonException("Failed to deserialize SignedObject, JSON object is null."); - } - - var signatures = jsonObject["signatures"] ?? throw new JsonException("Failed to find 'signatures' property in JSON object."); - jsonObject.Remove("signatures"); - - var signedObject = new SignedObject<T> { - Content = jsonObject, - Signatures = signatures.Deserialize<Dictionary<string, Dictionary<string, string>>>() - ?? throw new JsonException("Failed to deserialize 'signatures' property into Dictionary<string, Dictionary<string, string>>.") - }; - - return signedObject; - } - - public override void Write(Utf8JsonWriter writer, SignedObject<T> value, JsonSerializerOptions options) { - var targetObj = value.Content.DeepClone(); - targetObj["signatures"] = value.Signatures.ToJsonNode(); - JsonSerializer.Serialize(writer, targetObj, options); - } -} - -internal class SignedObjectConverterFactory : JsonConverterFactory { - public override bool CanConvert(Type typeToConvert) { - if (!typeToConvert.IsGenericType) return false; - return typeToConvert.GetGenericTypeDefinition() == typeof(SignedObject<>); - } - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { - var wrappedType = typeToConvert.GetGenericArguments()[0]; - 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.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;