diff --git a/src/Intune.Commander.Core/Extensions/ServiceCollectionExtensions.cs b/src/Intune.Commander.Core/Extensions/ServiceCollectionExtensions.cs index b064a775..61b56bf9 100644 --- a/src/Intune.Commander.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Intune.Commander.Core/Extensions/ServiceCollectionExtensions.cs @@ -21,15 +21,29 @@ public static IServiceCollection AddIntuneManagerCore(this IServiceCollection se // One-time migration: copy DataProtection key XML files from the legacy // location to the new one so existing encrypted profiles remain readable. - if (Directory.Exists(legacyKeysPath) && !Directory.Exists(keysPath)) + var legacyKeyFiles = Directory.Exists(legacyKeysPath) + ? Directory.GetFiles(legacyKeysPath, "*.xml") + : Array.Empty(); + + var newKeyDirectoryHasKeys = Directory.Exists(keysPath) + && Directory.GetFiles(keysPath, "*.xml").Length > 0; + + if (legacyKeyFiles.Length > 0 && !newKeyDirectoryHasKeys) { - try + Directory.CreateDirectory(keysPath); + + foreach (var file in legacyKeyFiles) { - Directory.CreateDirectory(keysPath); - foreach (var file in Directory.GetFiles(legacyKeysPath, "*.xml")) - File.Copy(file, Path.Combine(keysPath, Path.GetFileName(file)), overwrite: false); + var destinationPath = Path.Combine(keysPath, Path.GetFileName(file)); + if (File.Exists(destinationPath)) + continue; + + try + { + File.Copy(file, destinationPath, overwrite: false); + } + catch { /* best-effort — continue copying remaining keys */ } } - catch { /* best-effort — new keys will be generated if copy fails */ } } services.AddDataProtection() diff --git a/src/Intune.Commander.Core/Services/ProfileEncryptionService.cs b/src/Intune.Commander.Core/Services/ProfileEncryptionService.cs index c1041177..c7c5ed80 100644 --- a/src/Intune.Commander.Core/Services/ProfileEncryptionService.cs +++ b/src/Intune.Commander.Core/Services/ProfileEncryptionService.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using Microsoft.AspNetCore.DataProtection; namespace Intune.Commander.Core.Services; @@ -40,7 +41,7 @@ public string Decrypt(string cipherText) { return _protector.Unprotect(cipherText); } - catch + catch (CryptographicException) { // Fall back to legacy purpose — will throw if both fail return _legacyProtector.Unprotect(cipherText); diff --git a/tests/Intune.Commander.Core.Tests/Services/ProfileEncryptionServiceTests.cs b/tests/Intune.Commander.Core.Tests/Services/ProfileEncryptionServiceTests.cs index 3adc2749..edd8a8d0 100644 --- a/tests/Intune.Commander.Core.Tests/Services/ProfileEncryptionServiceTests.cs +++ b/tests/Intune.Commander.Core.Tests/Services/ProfileEncryptionServiceTests.cs @@ -70,7 +70,7 @@ public void Decrypt_LegacyPurpose_SucceedsViaFallback() services.AddDataProtection() .SetApplicationName("IntuneManager-Tests") .PersistKeysToFileSystem(new DirectoryInfo(_keysDir)); - var sp = services.BuildServiceProvider(); + using var sp = services.BuildServiceProvider(); var provider = sp.GetRequiredService(); // Encrypt directly under the legacy purpose (bypassing ProfileEncryptionService.Encrypt) @@ -83,7 +83,6 @@ public void Decrypt_LegacyPurpose_SucceedsViaFallback() var decrypted = _encryption.Decrypt(legacyEncrypted); Assert.Equal(plainText, decrypted); - sp.Dispose(); } }