Refactor http signature validation
This commit is contained in:
parent
3fba5550d5
commit
5f088ba66f
2 changed files with 25 additions and 21 deletions
|
@ -7,39 +7,44 @@ using Microsoft.Extensions.Primitives;
|
|||
namespace Iceshrimp.Backend.Core.Federation.Cryptography;
|
||||
|
||||
public static class HttpSignature {
|
||||
public static async Task<bool> Verify(HttpRequest request, HttpSignatureHeader signature,
|
||||
IEnumerable<string> requiredHeaders, string key) {
|
||||
public static Task<bool> Verify(HttpRequest request, HttpSignatureHeader signature,
|
||||
IEnumerable<string> 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<bool> 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<bool> 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,
|
||||
|
|
|
@ -42,11 +42,10 @@ public class HttpRequestService(IOptions<Config.InstanceSection> options) {
|
|||
return GenerateRequest(url, HttpMethod.Post, body, contentType);
|
||||
}
|
||||
|
||||
public HttpRequestMessage GetSigned(string url, IEnumerable<string>? accept, User user, UserKeypair keypair) {
|
||||
var msg = Get(url, accept).Sign(["(request-target)", "date", "host", "accept"], keypair.PrivateKey,
|
||||
public HttpRequestMessage GetSigned(string url, IEnumerable<string>? 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<HttpRequestMessage> PostSigned(string url, string body, string contentType, User user,
|
||||
|
|
Loading…
Add table
Reference in a new issue