using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; using AttackTracerNinjaCli.Models; namespace AttackTracerNinjaCli.Transport; internal sealed class N8nUploadClient { public UploadResult UploadJson( string webhookUrl, string machineName, string clientVersion, string payloadJson, string sharedSecret, int timeoutSeconds) { string timestamp = DateTimeOffset.UtcNow.ToString("O"); string nonce = Guid.NewGuid().ToString("N"); string payloadHash = ComputeSha256(payloadJson); string signature = ComputeSignature(machineName, timestamp, nonce, clientVersion, payloadHash, sharedSecret); using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds)) }; using var request = new HttpRequestMessage(HttpMethod.Post, webhookUrl) { Content = new StringContent(payloadJson, Encoding.UTF8, "application/json") }; request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Add("X-ATN-Device", machineName); request.Headers.Add("X-ATN-Timestamp", timestamp); request.Headers.Add("X-ATN-Nonce", nonce); request.Headers.Add("X-ATN-Version", clientVersion); request.Headers.Add("X-ATN-Payload-SHA256", payloadHash); request.Headers.Add("X-ATN-Signature", signature); using HttpResponseMessage response = httpClient.Send(request); string responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); return new UploadResult { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode, Message = string.IsNullOrWhiteSpace(responseText) ? response.ReasonPhrase ?? string.Empty : responseText, Nonce = nonce, PayloadSha256 = payloadHash }; } private static string ComputeSha256(string value) { byte[] bytes = Encoding.UTF8.GetBytes(value); byte[] hash = SHA256.HashData(bytes); return Convert.ToHexString(hash).ToLowerInvariant(); } private static string ComputeSignature(string machineName, string timestamp, string nonce, string version, string payloadHash, string sharedSecret) { string canonical = string.Join("\n", machineName, timestamp, nonce, version, payloadHash); byte[] secretBytes = Encoding.UTF8.GetBytes(sharedSecret); byte[] canonicalBytes = Encoding.UTF8.GetBytes(canonical); using var hmac = new HMACSHA256(secretBytes); byte[] hash = hmac.ComputeHash(canonicalBytes); return Convert.ToHexString(hash).ToLowerInvariant(); } }