using JdeScoping.Infrastructure.Security; using JdeScoping.Infrastructure.Tests.Helpers; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; using System.Security.Cryptography; namespace JdeScoping.Infrastructure.Tests.Security; public class SecureStoreRsaKeyServiceTests : IDisposable { private readonly InMemorySecureStore _secureStore; public SecureStoreRsaKeyServiceTests() { _secureStore = new InMemorySecureStore(); } public void Dispose() { _secureStore.Dispose(); } private SecureStoreRsaKeyService CreateService() { return new SecureStoreRsaKeyService( _secureStore, NullLogger.Instance); } [Fact] public void Constructor_WhenNoKeyInStore_GeneratesNewKeyAndStoresIt() { // Arrange - ensure no key exists _secureStore.Contains(SecureStoreRsaKeyService.RsaPrivateKeyName).ShouldBeFalse(); // Act using var service = CreateService(); // Assert - key should now be stored _secureStore.Contains(SecureStoreRsaKeyService.RsaPrivateKeyName).ShouldBeTrue(); var storedKey = _secureStore.Get(SecureStoreRsaKeyService.RsaPrivateKeyName); storedKey.ShouldStartWith("-----BEGIN RSA PRIVATE KEY-----"); } [Fact] public void Constructor_WhenKeyExistsInStore_LoadsExistingKey() { // Arrange - pre-store a key using var originalRsa = RSA.Create(2048); var originalPem = originalRsa.ExportRSAPrivateKeyPem(); var originalPublicKey = originalRsa.ExportSubjectPublicKeyInfoPem(); _secureStore.Set(SecureStoreRsaKeyService.RsaPrivateKeyName, originalPem); // Act using var service = CreateService(); // Assert - should load the same key var loadedPublicKey = service.GetPublicKeyPem(); loadedPublicKey.ShouldBe(originalPublicKey); } [Fact] public void GetPublicKeyPem_ReturnsValidPemFormat() { // Arrange using var service = CreateService(); // Act var pem = service.GetPublicKeyPem(); // Assert pem.ShouldStartWith("-----BEGIN PUBLIC KEY-----"); pem.ShouldEndWith("-----END PUBLIC KEY-----"); } [Fact] public void Decrypt_WithValidCiphertext_ReturnsPlaintext() { // Arrange using var service = CreateService(); var plaintext = "Hello, World!"u8.ToArray(); // Encrypt using public key (simulating what client does) using var rsa = RSA.Create(); rsa.ImportFromPem(service.GetPublicKeyPem()); var ciphertext = rsa.Encrypt(plaintext, RSAEncryptionPadding.OaepSHA256); // Act var decrypted = service.Decrypt(ciphertext); // Assert decrypted.ShouldBe(plaintext); } [Fact] public void Decrypt_WithInvalidCiphertext_ThrowsCryptographicException() { // Arrange using var service = CreateService(); var invalidCiphertext = new byte[] { 1, 2, 3, 4, 5 }; // Act & Assert Should.Throw(() => service.Decrypt(invalidCiphertext)); } [Fact] public void MultipleInstances_WithSameStore_UseSameKey() { // Arrange & Act using var service1 = CreateService(); var publicKey1 = service1.GetPublicKeyPem(); // Create second instance with same store using var service2 = new SecureStoreRsaKeyService( _secureStore, NullLogger.Instance); var publicKey2 = service2.GetPublicKeyPem(); // Assert publicKey2.ShouldBe(publicKey1); } [Fact] public void EncryptDecrypt_RoundTrip_Succeeds() { // Arrange using var service = CreateService(); var originalMessage = "Sensitive password data 123!"u8.ToArray(); // Act - encrypt with public key using var clientRsa = RSA.Create(); clientRsa.ImportFromPem(service.GetPublicKeyPem()); var encrypted = clientRsa.Encrypt(originalMessage, RSAEncryptionPadding.OaepSHA256); // Act - decrypt with service (which has private key) var decrypted = service.Decrypt(encrypted); // Assert decrypted.ShouldBe(originalMessage); } [Fact] public void Operations_AfterDispose_ThrowObjectDisposedException() { // Arrange var service = CreateService(); service.Dispose(); // Act & Assert Should.Throw(() => service.GetPublicKeyPem()); Should.Throw(() => service.Decrypt(Array.Empty())); } }