Iceshrimp.NET/Iceshrimp.Backend/Core/Middleware/AuthorizedFetchMiddleware.cs
2024-01-24 04:11:56 +01:00

57 lines
No EOL
2.4 KiB
C#

using System.Net;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Federation.ActivityPub;
using Iceshrimp.Backend.Core.Federation.Cryptography;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Middleware;
public class AuthorizedFetchMiddleware(RequestDelegate next) {
public async Task InvokeAsync(HttpContext context, IOptionsSnapshot<Config.SecuritySection> config,
DatabaseContext db, UserResolver userResolver,
ILogger<AuthorizedFetchMiddleware> logger) {
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<AuthorizedFetchAttribute>();
if (attribute != null && config.Value.AuthorizedFetch) {
var request = context.Request;
if (!request.Headers.TryGetValue("signature", out var sigHeader))
throw new CustomException(HttpStatusCode.Unauthorized, "Request is missing the signature header",
logger);
var sig = HttpSignature.Parse(sigHeader.ToString());
// First, we check if we already have the key
var key = await db.UserPublickeys.FirstOrDefaultAsync(p => p.KeyId == sig.KeyId);
// If we don't, we need to try to fetch it
if (key == null) {
var user = await userResolver.Resolve(sig.KeyId);
key = await db.UserPublickeys.FirstOrDefaultAsync(p => p.UserId == user.Id);
}
// If we still don't have the key, something went wrong and we need to throw an exception
if (key == null) throw new CustomException("Failed to fetch key of signature user", logger);
//TODO: re-fetch key once if signature validation fails, to properly support key rotation
List<string> headers = request.Body.Length > 0 || attribute.ForceBody
? ["(request-target)", "digest", "host", "date"]
: ["(request-target)", "host", "date"];
var verified = await HttpSignature.Verify(context.Request, sig, headers, key.KeyPem);
logger.LogDebug("HttpSignature.Verify returned {result} for key {keyId}", verified, sig.KeyId);
if (!verified)
throw new CustomException(HttpStatusCode.Forbidden, "Request signature validation failed", logger);
}
await next(context);
}
}
public class AuthorizedFetchAttribute(bool forceBody = false) : Attribute {
public bool ForceBody { get; } = forceBody;
}