Skip to content
Snippets Groups Projects
Commit 3d6bc2a2 authored by Klaus Fischer's avatar Klaus Fischer
Browse files

Problems with Callback validation HMAC

parent a659f90c
No related branches found
No related tags found
1 merge request!6Routing Api
......@@ -10,6 +10,7 @@ using FitConnect.Models;
using FitConnect.Models.v1.Api;
using FitConnect.Services.Models.v1.Submission;
using IdentityModel;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using NJsonSchema;
......@@ -201,20 +202,27 @@ public class Subscriber : FitConnectClient,
Logger?.LogInformation("Submission completed {status}", result);
}
public static bool VerifyCallback(WebRequest request) {
var timestamp = long.Parse(request.Headers["callback-timestamp"] ?? "0");
public static bool VerifyCallback(string callbackSecret, HttpRequest request) {
if (!request.Headers.ContainsKey("callback-timestamp"))
throw new ArgumentException("Missing callback-timestamp header");
var timeStampString = request.Headers["callback-timestamp"].ToString();
var timestamp = long.Parse(timeStampString);
if (timestamp < DateTime.Now.AddMinutes(-5).ToEpochTime())
throw new ArgumentException("Request is too old");
var secret = request.Headers["callback-authentication"] ?? "";
using var requestStream = request.GetRequestStream();
var content = new StreamReader(requestStream).ReadToEnd();
var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(secret)).ComputeHash(
Encoding.UTF8.GetBytes(request.Headers["callback-timestamp"] + "." + content));
var authentication = request.Headers["callback-authentication"];
using var requestStream = request.Body;
var content = new StreamReader(requestStream).ReadToEnd().Trim();
var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(callbackSecret))
.ComputeHash(Encoding.UTF8.GetBytes($"{timeStampString}.{content}"));
var hmacString = Convert.ToHexString(hmac);
if (hmacString != secret)
if (hmacString != authentication)
throw new ArgumentException("Request is not authentic");
return true;
}
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using Autofac;
using FluentAssertions;
using IdentityModel;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using MockContainer;
using Moq;
using NUnit.Framework;
......@@ -11,7 +17,8 @@ namespace IntegrationTests;
[TestFixture]
public class CallbackTest {
private WebRequest Request;
private HttpRequest Request;
private string _callbackSecret = "";
[SetUp]
public void Setup() {
......@@ -21,13 +28,13 @@ public class CallbackTest {
//
// {"type":"https://schema.fitko.de/fit-connect/submission-api/callbacks/new-submissions","submissionIds":["f39ab143-d91a-474a-b69f-b00f1a1873c2"]}
var request = HttpWebRequest.Create(
"https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect");
request.Headers.Add("callback-authentication",
"798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df");
request.Headers.Add("callback-timestamp", DateTime.Now.ToEpochTime().ToString());
request.Method = "POST";
request.ContentType = "application/json";
// HttpRequest request = null!;
// request.Headers
// request.Headers.Add("callback-authentication",
// "798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df");
// request.Headers.Add("callback-timestamp", DateTime.Now.ToEpochTime().ToString());
// request.Method = "POST";
// request.ContentType = "application/json";
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream);
......@@ -37,24 +44,34 @@ public class CallbackTest {
streamWriter.Flush();
memoryStream.Position = 0;
// Request = new DefaultHttpRequest(new DefaultHttpContext()) {
// Body = new StreamBody(memoryStream)
// };
var headers = new HeaderDictionary(new Dictionary<string, StringValues>() {
{ "callback-timestamp", DateTime.Now.ToEpochTime().ToString() }, {
"callback-authentication",
"798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df"
}
});
var mock =
new Mock<HttpWebRequest>(
"https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect");
new Mock<HttpRequest>();
mock.Setup(w => w.ContentType).Returns("application/json");
mock.Setup(w => w.Headers).Returns(request.Headers);
mock.Setup(w => w.Headers).Returns(headers);
mock.Setup(w => w.Method).Returns("POST");
mock.Setup(w => w.GetRequestStream()).Returns(memoryStream);
mock.Setup(w => w.RequestUri)
.Returns(new Uri(
"https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect"));
mock.Setup(w => w.Body).Returns(memoryStream);
Request = mock.Object;
_callbackSecret = MockContainer.Container.Create().Resolve<MockSettings>().CallbackSecret;
}
[Test]
public void ValidRequest() {
// Assert
FitConnect.Subscriber.VerifyCallback(Request).Should().Be(true);
FitConnect.Subscriber.VerifyCallback(_callbackSecret,Request).Should().Be(true);
}
[Test]
......@@ -64,7 +81,7 @@ public class CallbackTest {
// Atc
// Assert
Assert.Throws<ArgumentException>(() => { FitConnect.Subscriber.VerifyCallback(Request); })
Assert.Throws<ArgumentException>(() => { FitConnect.Subscriber.VerifyCallback(_callbackSecret,Request); })
.Message.Should().Be("Request is too old");
}
......@@ -76,7 +93,7 @@ public class CallbackTest {
// Atc
// Assert
Assert.Throws<ArgumentException>(() => { FitConnect.Subscriber.VerifyCallback(Request); })
Assert.Throws<ArgumentException>(() => { FitConnect.Subscriber.VerifyCallback(_callbackSecret,Request); })
.Message.Should().Be("Request is not authentic");
}
}
using System.Text.Encodings.Web;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using Autofac;
......@@ -88,6 +88,7 @@ public static class Container {
senderClientId, senderClientSecret,
subscriberClientId, subscriberClientSecret,
destinationId, leikaKey, callbackSecret))
callbackSecret))
.As<MockSettings>();
builder.Register(c => new KeySet {
PrivateKeyDecryption = privateKeyDecryption,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment