diff --git a/agent-cs/AgentWorker.cs b/agent-cs/AgentWorker.cs
index 9a4149f..80bc9ab 100644
--- a/agent-cs/AgentWorker.cs
+++ b/agent-cs/AgentWorker.cs
@@ -6,7 +6,7 @@ namespace ITNexusAgent;
public class AgentWorker
{
- private const string Version = "2.8.0";
+ private const string Version = "2.9.0";
private const string DataDir = @"C:\ProgramData\IT Nexus Agent";
private const string ConfigPath = @"C:\ProgramData\IT Nexus Agent\config.json";
private const string StatusPath = @"C:\ProgramData\IT Nexus Agent\status.json";
@@ -44,10 +44,11 @@ public class AgentWorker
// Idempotent (Server liefert bestehenden Key erneut) — daher bei jedem Start sicher aufrufbar.
var hostname = SystemInfoService.GetHostname();
var enrolledKey = await _api.EnrollAsync(hostname);
- if (!string.IsNullOrEmpty(enrolledKey) && enrolledKey != _config.AgentKey)
+ var configWasUnencrypted = !File.ReadAllText(ConfigPath).Contains("dpapi:");
+ if (!string.IsNullOrEmpty(enrolledKey) && (enrolledKey != _config.AgentKey || configWasUnencrypted))
{
_config.AgentKey = enrolledKey;
- _config.Save(ConfigPath);
+ _config.Save(ConfigPath); // schreibt agent_key jetzt DPAPI-verschlüsselt statt im Klartext
_api.UpdateKey(enrolledKey);
Log("ENROLL: Per-Device-Key erhalten und gespeichert");
}
diff --git a/agent-cs/IT-Nexus-Agent.csproj b/agent-cs/IT-Nexus-Agent.csproj
index c19d560..53e3981 100644
--- a/agent-cs/IT-Nexus-Agent.csproj
+++ b/agent-cs/IT-Nexus-Agent.csproj
@@ -7,8 +7,8 @@
true
IT-Nexus-Agent
ITNexusAgent
- 2.8.0
- 2.8.0.0
+ 2.9.0
+ 2.9.0.0
enable
enable
false
@@ -31,6 +31,7 @@
+
diff --git a/agent-cs/Models/Config.cs b/agent-cs/Models/Config.cs
index a148509..a13282c 100644
--- a/agent-cs/Models/Config.cs
+++ b/agent-cs/Models/Config.cs
@@ -1,25 +1,69 @@
using Newtonsoft.Json;
using System.IO;
+using System.Security.Cryptography;
+using System.Text;
namespace ITNexusAgent.Models;
public class AgentConfig
{
- [JsonProperty("server_url")]
+ private const string ProtectedPrefix = "dpapi:";
+
+ [JsonIgnore]
public string ServerUrl { get; set; } = "";
- [JsonProperty("agent_key")]
+ // Im Speicher immer Klartext — nur auf der Platte (config.json) liegt der verschlüsselte Wert.
+ [JsonIgnore]
public string AgentKey { get; set; } = "";
+ private class RawConfig
+ {
+ [JsonProperty("server_url")] public string ServerUrl { get; set; } = "";
+ [JsonProperty("agent_key")] public string AgentKey { get; set; } = "";
+ }
+
public static AgentConfig Load(string path)
{
var json = File.ReadAllText(path);
- return JsonConvert.DeserializeObject(json)
+ var raw = JsonConvert.DeserializeObject(json)
?? throw new Exception("Ungültige config.json");
+
+ return new AgentConfig
+ {
+ ServerUrl = raw.ServerUrl,
+ AgentKey = Unprotect(raw.AgentKey),
+ };
}
+ // Verschlüsselt den Key per Windows DPAPI (LocalMachine-Scope) bevor er auf die Platte geschrieben
+ // wird — ein Klartext-Auslesen von config.json bringt einem Angreifer dann nichts mehr, da der Wert
+ // nur vom SYSTEM-Konto auf genau diesem Rechner wieder entschlüsselt werden kann.
public void Save(string path)
{
- File.WriteAllText(path, JsonConvert.SerializeObject(this, Formatting.Indented));
+ var raw = new RawConfig { ServerUrl = ServerUrl, AgentKey = Protect(AgentKey) };
+ File.WriteAllText(path, JsonConvert.SerializeObject(raw, Formatting.Indented));
+ }
+
+ private static string Protect(string plaintext)
+ {
+ if (string.IsNullOrEmpty(plaintext)) return plaintext;
+ var bytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(plaintext), null, DataProtectionScope.LocalMachine);
+ return ProtectedPrefix + Convert.ToBase64String(bytes);
+ }
+
+ // Erkennt das alte Klartext-Format (z.B. frisch aus dem Installer-Template) und lässt es unverändert
+ // durch — wird beim nächsten Save() automatisch verschlüsselt persistiert.
+ private static string Unprotect(string stored)
+ {
+ if (string.IsNullOrEmpty(stored) || !stored.StartsWith(ProtectedPrefix)) return stored;
+ try
+ {
+ var bytes = ProtectedData.Unprotect(Convert.FromBase64String(stored[ProtectedPrefix.Length..]), null, DataProtectionScope.LocalMachine);
+ return Encoding.UTF8.GetString(bytes);
+ }
+ catch
+ {
+ return stored; // Korrupt/falsche Maschine → unverändert zurückgeben statt Crash
+ }
}
}
diff --git a/agent-cs/setup.iss b/agent-cs/setup.iss
index ff2fbe0..db406c7 100644
--- a/agent-cs/setup.iss
+++ b/agent-cs/setup.iss
@@ -1,5 +1,5 @@
#define MyAppName "IT Nexus Agent"
-#define MyAppVersion "2.8.0"
+#define MyAppVersion "2.9.0"
#define MyAppPublisher "Cereda Systems GmbH"
#define MyAppURL "https://it-nexus.cereda-systems.de"
#define MyAppExeName "IT-Nexus-Agent.exe"
diff --git a/frontend/src/pages/PatchManagementPage.jsx b/frontend/src/pages/PatchManagementPage.jsx
index 6627acf..87bb778 100644
--- a/frontend/src/pages/PatchManagementPage.jsx
+++ b/frontend/src/pages/PatchManagementPage.jsx
@@ -5,7 +5,7 @@ import { useAuth } from '../context/AuthContext';
const ANN_TYPES = { maintenance: { icon: '🔧', label: 'Wartung', color: '#f59e0b' }, warning: { icon: '⚠️', label: 'Warnung', color: '#ef4444' }, info: { icon: 'ℹ️', label: 'Info', color: '#6366f1' } };
-const LATEST_AGENT_VERSION = '2.8.0';
+const LATEST_AGENT_VERSION = '2.9.0';
const SEVERITY_LABELS = { critical: 'Kritisch', important: 'Wichtig', moderate: 'Moderat', low: 'Niedrig', all: 'Alle' };
const SEVERITY_COLORS = { critical: '#EF4444', important: '#F59E0B', moderate: '#3B82F6', low: '#6B7280', all: '#0D9488' };
const COMMAND_LABELS = { check_updates: '🔍 Update-Scan', install_updates: '⬇️ Installation', reboot: '🔄 Neustart', upgrade_win11: '🪟 Win 11 Upgrade', update_agent: '⬆️ Agent-Update' };