diff --git a/Duplicati/Agent/Program.cs b/Duplicati/Agent/Program.cs index c881b5a5c..274db8646 100644 --- a/Duplicati/Agent/Program.cs +++ b/Duplicati/Agent/Program.cs @@ -127,7 +127,7 @@ public static class Program KeepRemoteConnection.Start( settings.ServerUrl, settings.JWT, - ConvertCertificates(settings.ServerCertificates), + settings.ServerCertificates, cts.Token, ReKey, (m) => OnMessage(m, agentConfig) @@ -159,7 +159,7 @@ public static class Program if (!string.IsNullOrWhiteSpace(keydata.JWT) && settings.JWT != keydata.JWT) settings = settings with { JWT = keydata.JWT }; if (keydata.ServerCertificates.Any()) - settings = settings with { ServerCertificates = ConvertCertificates(keydata.ServerCertificates).Concat(settings.ServerCertificates).DistinctBy(x => x.MachineId).ToArray() }; + settings = settings with { ServerCertificates = (keydata.ServerCertificates ?? []).Concat(settings.ServerCertificates).DistinctBy(x => x.Identifier).ToArray() }; if (!string.IsNullOrWhiteSpace(keydata.LocalEncryptionKey) && settings.SettingsEncryptionKey != keydata.LocalEncryptionKey) { @@ -173,11 +173,6 @@ public static class Program return Task.CompletedTask; } - private static IEnumerable ConvertCertificates(IEnumerable certificates) - => (certificates ?? []).Select(cert => new ServerCertificate(cert.MachineId, cert.PublicKey, cert.Obtained, cert.Expiry)).ToArray(); - private static IEnumerable ConvertCertificates(IEnumerable certificates) - => (certificates ?? []).Select(cert => new Settings.ServerCertificate(cert.MachineId, cert.PublicKey, cert.Obtained, cert.Expiry)).ToArray(); - private static async Task RunServer(string[] args, CancellationToken cancellationToken) { cancellationToken.Register(() => @@ -234,7 +229,7 @@ public static class Program JWT = claimedClientData.JWT, ServerUrl = claimedClientData.ServerUrl, SettingsEncryptionKey = claimedClientData.LocalEncryptionKey, - ServerCertificates = ConvertCertificates(claimedClientData.ServerCertificates) + ServerCertificates = claimedClientData.ServerCertificates }; settings.Save(); diff --git a/Duplicati/Agent/Settings.cs b/Duplicati/Agent/Settings.cs index de4734edd..d994b5bca 100644 --- a/Duplicati/Agent/Settings.cs +++ b/Duplicati/Agent/Settings.cs @@ -23,6 +23,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Duplicati.Library.Encryption; using Duplicati.Library.Main; +using Duplicati.Library.RemoteControl; namespace Duplicati.Agent; @@ -39,24 +40,9 @@ public sealed record Settings( string JWT, string ServerUrl, string? SettingsEncryptionKey, - IEnumerable ServerCertificates + IEnumerable ServerCertificates ) { - /// - /// The persisted version of a server certificate. - /// This is the data that is serialized to the file. - /// - /// The machine the key belongs to - /// The public key - /// The date the key was obtained - /// The expiry date of the key - public sealed record ServerCertificate( - string MachineId, - string PublicKey, - DateTimeOffset Obtained, - DateTimeOffset Expiry - ); - /// /// Gets the default settings file path /// @@ -80,7 +66,7 @@ public sealed record Settings( { path ??= DefaultSettingsFile; var tmp = JsonSerializer.Deserialize(File.ReadAllText(path)) - ?? new Settings(path, string.Empty, string.Empty, string.Empty, Array.Empty()); + ?? new Settings(path, string.Empty, string.Empty, string.Empty, Array.Empty()); return tmp with { diff --git a/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs b/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs index afcc95aea..b812ae013 100644 --- a/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs +++ b/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs @@ -102,7 +102,7 @@ public class KeepRemoteConnection : IDisposable /// The JWT token to use /// The server key to use /// The token to cancel the connection - private KeepRemoteConnection(string serverUrl, string JWT, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) + private KeepRemoteConnection(string serverUrl, string JWT, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) { _client = new Websocket.Client.WebsocketClient(new Uri(serverUrl)); _client.ReconnectTimeout = TimeSpan.FromSeconds(30); @@ -126,7 +126,7 @@ public class KeepRemoteConnection : IDisposable try { var envelope = EnvelopedMessage.ForceParse(msg.Text); - var machineKey = serverKeys.FirstOrDefault(x => x.MachineId == envelope.From && x.Expiry > DateTimeOffset.Now)?.PublicKey; + var machineKey = serverKeys.FirstOrDefault(x => x.Identifier == envelope.From && x.Expiry > DateTimeOffset.Now)?.PublicKey; envelope.ValidateSignature(machineKey); if (_state == ConnectionState.Connected) @@ -214,7 +214,7 @@ public class KeepRemoteConnection : IDisposable /// The callback to call when rekeying /// The callback to call when a message is received /// - public static Task Start(string serverUrl, string JWT, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) + public static Task Start(string serverUrl, string JWT, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) => Task.Run(async () => { using var connection = new KeepRemoteConnection(serverUrl, JWT, serverKeys, cancellationToken, onReKey, onMessage); @@ -290,7 +290,7 @@ public class KeepRemoteConnection : IDisposable /// The callback to call when a message is received /// The token to cancel the connection /// The connection object - public static KeepRemoteConnection CreateRemoteListener(string serverUrl, string JWT, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) + public static KeepRemoteConnection CreateRemoteListener(string serverUrl, string JWT, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) => new KeepRemoteConnection(serverUrl, JWT, serverKeys, cancellationToken, onReKey, onMessage); /// diff --git a/Duplicati/Library/RemoteControl/SharedTypes.cs b/Duplicati/Library/RemoteControl/SharedTypes.cs index 47008fe37..4e53f6d89 100644 --- a/Duplicati/Library/RemoteControl/SharedTypes.cs +++ b/Duplicati/Library/RemoteControl/SharedTypes.cs @@ -47,21 +47,21 @@ public sealed record RegisterClientData( public sealed record ClaimedClientData( string JWT, string ServerUrl, - IEnumerable ServerCertificates, + IEnumerable ServerCertificates, string? LocalEncryptionKey ); -// TODO: Replace with a standard certificate type - /// -/// The server certificate for a machine +/// The server certificate for a machine. +/// This data is serialized to various files, and chosen instead of X509 certificates. +/// If we find a non-complex certificate format, we should switch to that. /// -/// The machine identifier the key is valid for +/// The machine identifier the key is valid for /// The certificate public key /// The date the certificate was obtained /// The expiry date of the certificate -public sealed record ServerCertificate( - string MachineId, +public sealed record MiniServerCertificate( + string Identifier, string PublicKey, DateTimeOffset Obtained, DateTimeOffset Expiry diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backups.cs b/Duplicati/WebserverCore/Endpoints/V1/Backups.cs index 6eb05a5c7..4efd17711 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backups.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backups.cs @@ -1,5 +1,3 @@ -using System.Text.Json; -using Duplicati.Library.Encryption; using Duplicati.Library.RestAPI; using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs b/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs index 2177a167f..c42a5637a 100644 --- a/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs +++ b/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs @@ -43,16 +43,7 @@ public sealed record RemoteControlConfig /// /// The trusted server certificates. /// - public required IEnumerable ServerCertificates { get; init; } - - /// - /// A server certificate. - /// - /// The machine identifier - /// The machine public key - /// The expiry date of the certificate - /// The date the certificate was obtained - public sealed record CertificateKey(string MachineId, string PublicKey, DateTimeOffset Expiry, DateTimeOffset Obtained); + public required IEnumerable ServerCertificates { get; init; } } /// @@ -116,8 +107,7 @@ public class RemoteControllerRegistrationService(Connection connection, IHttpCli connection.ApplicationSettings.RemoteControlConfig = JsonSerializer.Serialize(new RemoteControlConfig { Token = claimData.JWT, - ServerCertificates = (claimData.ServerCertificates ?? Enumerable.Empty()) - .Select(x => new RemoteControlConfig.CertificateKey(x.MachineId, x.PublicKey, x.Expiry, x.Obtained)), + ServerCertificates = claimData.ServerCertificates, ServerUrl = claimData.ServerUrl }); diff --git a/Duplicati/WebserverCore/Services/RemoteControllerService.cs b/Duplicati/WebserverCore/Services/RemoteControllerService.cs index a93d5b8a1..6b780f99d 100644 --- a/Duplicati/WebserverCore/Services/RemoteControllerService.cs +++ b/Duplicati/WebserverCore/Services/RemoteControllerService.cs @@ -79,7 +79,7 @@ public class RemoteControllerService(Connection connection, IHttpClientFactory h _keepRemoteConnection = KeepRemoteConnection.CreateRemoteListener( config.ServerUrl, config.Token, - config.ServerCertificates.Select(x => new ServerCertificate(x.MachineId, x.PublicKey, x.Expiry, x.Obtained)).ToArray(), + config.ServerCertificates, CancellationToken.None, ReKey, OnMessage @@ -96,7 +96,7 @@ public class RemoteControllerService(Connection connection, IHttpClientFactory h connection.ApplicationSettings.RemoteControlConfig = JsonConvert.SerializeObject(new RemoteControlConfig { Token = data.JWT, - ServerCertificates = data.ServerCertificates.Select(x => new RemoteControlConfig.CertificateKey(x.MachineId, x.PublicKey, x.Expiry, x.Obtained)), + ServerCertificates = data.ServerCertificates, ServerUrl = data.ServerUrl });