diff --git a/Duplicati/CommandLine/SecretTool/Program.cs b/Duplicati/CommandLine/SecretTool/Program.cs
index 482080b1b..9974aa84b 100644
--- a/Duplicati/CommandLine/SecretTool/Program.cs
+++ b/Duplicati/CommandLine/SecretTool/Program.cs
@@ -102,9 +102,8 @@ public static class Program
/// The exit code.
private static async Task RunTest(string secretUrl, string[] secrets)
{
- var secretProvider = SecretProviderLoader.CreateInstance(secretUrl);
- await secretProvider.InitializeAsync(new Uri(secretUrl), CancellationToken.None);
- var result = await secretProvider.ResolveSecretsAsync(secrets, CancellationToken.None);
+ var secretProvider = await SecretProviderLoader.CreateInstanceAsync(secretUrl, true, CancellationToken.None).ConfigureAwait(false);
+ var result = await secretProvider.ResolveSecretsAsync(secrets, CancellationToken.None).ConfigureAwait(false);
Console.WriteLine("NOTE: Secret values are not displayed for security reasons");
Console.WriteLine("Secrets:");
@@ -140,7 +139,7 @@ public static class Program
{
if (string.IsNullOrWhiteSpace(secretUrl))
{
- var defaultProvider = await SecretProviderLoader.GetDefaultSecretProviderForOperatingSystem(CancellationToken.None);
+ var defaultProvider = await SecretProviderLoader.GetDefaultSecretProviderForOperatingSystem(true, CancellationToken.None);
if (defaultProvider == null)
throw new UserInformationException("No working default secret provider found", "NoDefaultSecretProvider");
@@ -204,12 +203,11 @@ public static class Program
throw new UserInformationException("Secret values do not match", "SecretMismatch");
}
- var secretProvider = SecretProviderLoader.CreateInstance(secretUrl);
- await secretProvider.InitializeAsync(new Uri(secretUrl), CancellationToken.None);
+ var secretProvider = await SecretProviderLoader.CreateInstanceAsync(secretUrl, true, CancellationToken.None).ConfigureAwait(false);
await secretProvider.SetSecretAsync(key, value, overwrite, CancellationToken.None);
// Verify that the secret was stored correctly
- var result = await secretProvider.ResolveSecretsAsync([key], CancellationToken.None);
+ var result = await secretProvider.ResolveSecretsAsync([key], CancellationToken.None).ConfigureAwait(false);
if (!result.ContainsKey(key) || result[key] != value)
throw new UserInformationException("Failed to verify that the secret was stored correctly", "SecretVerificationFailed");
Console.WriteLine($"Secret '{key}' stored.");
diff --git a/Duplicati/CommandLine/ServerUtil/Settings.cs b/Duplicati/CommandLine/ServerUtil/Settings.cs
index a09e85ad4..4726cb0a4 100644
--- a/Duplicati/CommandLine/ServerUtil/Settings.cs
+++ b/Duplicati/CommandLine/ServerUtil/Settings.cs
@@ -99,8 +99,6 @@ public sealed record Settings(
ISecretProvider? secretInstance = null;
if (!string.IsNullOrWhiteSpace(secretProvider))
{
- var secretProviderInstance = SecretProviderLoader.CreateInstance(secretProvider);
-
// Map into expected structure
var opts = new Dictionary
{
diff --git a/Duplicati/Library/DynamicLoader/SecretProviderLoader.cs b/Duplicati/Library/DynamicLoader/SecretProviderLoader.cs
index 703d1ea91..37fdee6c7 100644
--- a/Duplicati/Library/DynamicLoader/SecretProviderLoader.cs
+++ b/Duplicati/Library/DynamicLoader/SecretProviderLoader.cs
@@ -117,8 +117,10 @@ public class SecretProviderLoader
/// Creates an instance of a secret provider
///
/// The configuration string
+ /// Whether to initialize the provider
+ /// The cancellation token
/// The secret provider instance
- public static ISecretProvider CreateInstance(string config)
+ public static async Task CreateInstanceAsync(string config, bool initialize, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(config))
throw new ArgumentNullException(nameof(config));
@@ -145,6 +147,15 @@ public class SecretProviderLoader
config = result;
}
+ if (string.Equals(config, "default://", StringComparison.OrdinalIgnoreCase))
+ {
+ var defaultProvider = await GetDefaultSecretProviderForOperatingSystem(initialize, cancellationToken).ConfigureAwait(false);
+ if (defaultProvider == null)
+ throw new InvalidOperationException("No default secret provider is available for this system");
+
+ return defaultProvider;
+ }
+
var uri = new Uri(config);
var key = uri.Scheme;
@@ -154,6 +165,9 @@ public class SecretProviderLoader
if (Activator.CreateInstance(providerType.GetType()) is not ISecretProvider provider)
throw new InvalidOperationException($"Failed to create an instance of {providerType}");
+ if (initialize)
+ await provider.InitializeAsync(uri, cancellationToken).ConfigureAwait(false);
+
return provider;
}
@@ -161,37 +175,34 @@ public class SecretProviderLoader
/// Gets the default secret provider for the current operating system
///
/// The secret provider or null if none is available
- public static async Task GetDefaultSecretProviderForOperatingSystem(CancellationToken cancellationToken)
+ public static async Task GetDefaultSecretProviderForOperatingSystem(bool initialize, CancellationToken cancellationToken)
{
if (OperatingSystem.IsWindows())
{
var res = new WindowsCredentialManagerProvider();
- await res.InitializeAsync(new Uri("wincred://"), cancellationToken);
+ if (initialize)
+ await res.InitializeAsync(new Uri("wincred://"), cancellationToken).ConfigureAwait(false);
return res;
}
if (OperatingSystem.IsMacOS())
{
var res = new MacOSKeyChainProvider();
- await res.InitializeAsync(new Uri("keychain://"), cancellationToken);
+ if (initialize)
+ await res.InitializeAsync(new Uri("keychain://"), cancellationToken).ConfigureAwait(false);
return res;
}
if (OperatingSystem.IsLinux())
{
- ISecretProvider tmp = new LibSecretLinuxProvider();
- if (await tmp.IsSupported(cancellationToken))
+ var res = new LibSecretLinuxProvider();
+ if (await res.IsSupported(cancellationToken).ConfigureAwait(false))
{
- var res = new LibSecretLinuxProvider();
- await res.InitializeAsync(new Uri("libsecret://"), cancellationToken);
- if (await res.DoesCollectionExist(cancellationToken))
+ if (!initialize)
return res;
- }
- tmp = new UnixPassProvider();
- if (await tmp.IsSupported(cancellationToken))
- {
- await tmp.InitializeAsync(new Uri("pass://"), cancellationToken);
- return tmp;
+ await res.InitializeAsync(new Uri("libsecret://"), cancellationToken).ConfigureAwait(false);
+ if (await res.DoesCollectionExist(cancellationToken).ConfigureAwait(false))
+ return res;
}
}
diff --git a/Duplicati/Library/Main/SecretProviderHelper.cs b/Duplicati/Library/Main/SecretProviderHelper.cs
index ca6eb7ead..417812aba 100644
--- a/Duplicati/Library/Main/SecretProviderHelper.cs
+++ b/Duplicati/Library/Main/SecretProviderHelper.cs
@@ -71,25 +71,6 @@ public static class SecretProviderHelper
Persistent
}
- ///
- /// Creates an instance of a secret provider with caching enabled
- ///
- /// The configuration string
- /// The caching level
- /// The folder to persist the cache to
- /// The salt to use for hashing
- /// The pattern to use for matching
- /// The cancellation token
- /// The secret provider instance
- public static async Task CreateInstanceAsync(string config, CachingLevel cachingLevel, string persistedFolder, string salt, string pattern, CancellationToken cancelToken)
- {
- var provider = SecretProviderLoader.CreateInstance(config);
- var sp = WrapWithCache(config, provider, cachingLevel, persistedFolder, salt, pattern);
- await sp.InitializeAsync(new System.Uri(config), cancelToken).ConfigureAwait(false);
-
- return sp;
- }
-
///
/// Wraps a secret provider with caching
///
@@ -118,14 +99,14 @@ public static class SecretProviderHelper
var providerConfig = options.GetValueOrDefault("secret-provider");
if (!string.IsNullOrWhiteSpace(providerConfig))
{
- var provider = SecretProviderLoader.CreateInstance(providerConfig);
+ var provider = await SecretProviderLoader.CreateInstanceAsync(providerConfig, true, cancellationToken).ConfigureAwait(false);
if (provider?.IsSetSupported == true)
return provider;
}
try
{
- return await SecretProviderLoader.GetDefaultSecretProviderForOperatingSystem(cancellationToken).ConfigureAwait(false);
+ return await SecretProviderLoader.GetDefaultSecretProviderForOperatingSystem(true, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -161,7 +142,7 @@ public static class SecretProviderHelper
}
else
{
- var newProvider = SecretProviderLoader.CreateInstance(provider);
+ var newProvider = await SecretProviderLoader.CreateInstanceAsync(provider, false, cancellationToken);
// Weak salt, but semi-static
string salt;
diff --git a/proprietary/LicenseChecker/LicenseChecker.cs b/proprietary/LicenseChecker/LicenseChecker.cs
index f20f8bb42..2d05d89cc 100644
--- a/proprietary/LicenseChecker/LicenseChecker.cs
+++ b/proprietary/LicenseChecker/LicenseChecker.cs
@@ -72,9 +72,9 @@ public static class LicenseChecker
if (string.IsNullOrWhiteSpace(licenseKey))
throw new ArgumentException("License key cannot be null or empty", nameof(licenseKey));
- if (licenseKey.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
+ if (licenseKey.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
- var filePath = licenseKey[5..];
+ var filePath = licenseKey[7..];
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("License file path cannot be null or empty", nameof(licenseKey));
if (!File.Exists(filePath))
@@ -104,6 +104,9 @@ public static class LicenseChecker
}
else
{
+ if (licenseKey.StartsWith("jwt:", StringComparison.OrdinalIgnoreCase))
+ licenseKey = licenseKey[4..];
+
using var httpClient = new HttpClient();
using var content = JsonContent.Create(new LicenseRequestData(licenseKey));
using var response = await httpClient.PostAsync(ServerUrl, content, cancellationToken);
diff --git a/proprietary/LicenseChecker/LicenseHelper.cs b/proprietary/LicenseChecker/LicenseHelper.cs
index 2244654a9..3128e27d1 100644
--- a/proprietary/LicenseChecker/LicenseHelper.cs
+++ b/proprietary/LicenseChecker/LicenseHelper.cs
@@ -7,6 +7,7 @@ namespace Duplicati.Proprietary.LicenseChecker;
public static class LicenseHelper
{
+ private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(typeof(LicenseHelper));
private static readonly object _licenseLock = new();
private static LicenseData? _cachedLicenseData;
private static string? _remoteClientLicenseKey;
@@ -59,8 +60,23 @@ public static class LicenseHelper
// Check for a license file in the installation directory (highest priority)
var keyfilepath = Path.Combine(UpdaterManager.INSTALLATIONDIR, "license.key");
if (File.Exists(keyfilepath))
+ {
+ // Fallback to assume we have a direct license key
key = $"file://{keyfilepath}";
+ try
+ {
+ // Probe if the file has base64 encoded data
+ var content = File.ReadAllText(keyfilepath);
+ if (content.TrimStart().StartsWith("base64:", StringComparison.OrdinalIgnoreCase) || content.TrimStart().StartsWith("jwt:", StringComparison.OrdinalIgnoreCase))
+ key = content;
+ }
+ catch (Exception ex)
+ {
+ Library.Logging.Log.WriteVerboseMessage(LOGTAG, "FailedToLoadLicenseFile", ex, "Failed to open the existing license file, check permissions");
+ }
+ }
+
// Check for a license key in the environment variables
if (string.IsNullOrWhiteSpace(key))
key = Environment.GetEnvironmentVariable("DUPLICATI_LICENSE_KEY");