diff --git a/Iceshrimp.Backend/Core/Federation/Cryptography/HttpSignature.cs b/Iceshrimp.Backend/Core/Federation/Cryptography/HttpSignature.cs index 9e3aa4c6..07fb937b 100644 --- a/Iceshrimp.Backend/Core/Federation/Cryptography/HttpSignature.cs +++ b/Iceshrimp.Backend/Core/Federation/Cryptography/HttpSignature.cs @@ -7,39 +7,44 @@ using Microsoft.Extensions.Primitives; namespace Iceshrimp.Backend.Core.Federation.Cryptography; public static class HttpSignature { - public static async Task Verify(HttpRequest request, HttpSignatureHeader signature, - IEnumerable requiredHeaders, string key) { + public static Task Verify(HttpRequest request, HttpSignatureHeader signature, + IEnumerable requiredHeaders, string key) { if (!requiredHeaders.All(signature.Headers.Contains)) throw new ConstraintException("Request is missing required headers"); - //TODO: verify date header exists and is set to something the last 12 hours - var signingString = GenerateSigningString(signature.Headers, request.Method, request.Path, request.Headers); - //TODO: does this break for requests without a body? - var digest = await SHA256.HashDataAsync(request.BodyReader.AsStream()); - - //TODO: this definitely breaks if there's no body - //TODO: check for the SHA256= prefix instead of blindly removing the first 8 chars - if (Convert.ToBase64String(digest) != request.Headers["digest"].ToString().Remove(0, 8)) - throw new ConstraintException("Request digest mismatch"); - - var rsa = RSA.Create(); - rsa.ImportFromPem(key); - return rsa.VerifyData(Encoding.UTF8.GetBytes(signingString), signature.Signature, - HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + return VerifySignature(key, signingString, signature, request.Headers, request.Body); } //TODO: make this share code with the the regular Verify function - public static bool VerifySign(this HttpRequestMessage request, string key) { + public static Task Verify(this HttpRequestMessage request, string key) { var signatureHeader = request.Headers.GetValues("Signature").First(); var signature = Parse(signatureHeader); var signingString = GenerateSigningString(signature.Headers, request.Method.Method, request.RequestUri!.AbsolutePath, request.Headers.ToHeaderDictionary()); + return VerifySignature(key, signingString, signature, request.Headers.ToHeaderDictionary(), + request.Content?.ReadAsStream()); + } + + private static async Task VerifySignature(string key, string signingString, HttpSignatureHeader signature, + IHeaderDictionary headers, Stream? body) { + if (!headers.TryGetValue("date", out var date)) throw new Exception("Date header is missing"); + if (DateTime.Now - DateTime.Parse(date!) > TimeSpan.FromHours(12)) throw new Exception("Signature too old"); + + //TODO: does this break for requests without a body? + if (body != null) { + var digest = await SHA256.HashDataAsync(body); + + //TODO: check for the SHA256= prefix instead of blindly removing the first 8 chars + if (Convert.ToBase64String(digest) != headers["digest"].ToString().Remove(0, 8)) + throw new ConstraintException("Request digest mismatch"); + } + var rsa = RSA.Create(); rsa.ImportFromPem(key); return rsa.VerifyData(Encoding.UTF8.GetBytes(signingString), signature.Signature, diff --git a/Iceshrimp.Backend/Core/Services/HttpRequestService.cs b/Iceshrimp.Backend/Core/Services/HttpRequestService.cs index 86eda702..ce60f859 100644 --- a/Iceshrimp.Backend/Core/Services/HttpRequestService.cs +++ b/Iceshrimp.Backend/Core/Services/HttpRequestService.cs @@ -42,11 +42,10 @@ public class HttpRequestService(IOptions options) { return GenerateRequest(url, HttpMethod.Post, body, contentType); } - public HttpRequestMessage GetSigned(string url, IEnumerable? accept, User user, UserKeypair keypair) { - var msg = Get(url, accept).Sign(["(request-target)", "date", "host", "accept"], keypair.PrivateKey, + public HttpRequestMessage GetSigned(string url, IEnumerable? accept, User user, + UserKeypair keypair) { + return Get(url, accept).Sign(["(request-target)", "date", "host", "accept"], keypair.PrivateKey, $"https://{options.Value.WebDomain}/users/{user.Id}#main-key"); - if (!msg.VerifySign(keypair.PublicKey)) throw new Exception("Failed to sign request"); - return msg; } public async Task PostSigned(string url, string body, string contentType, User user,