diff --git a/NEW/src/JdeScoping.Client/Services/CryptoService.cs b/NEW/src/JdeScoping.Client/Services/CryptoService.cs
new file mode 100644
index 0000000..0c9125c
--- /dev/null
+++ b/NEW/src/JdeScoping.Client/Services/CryptoService.cs
@@ -0,0 +1,63 @@
+using System.Net.Http.Json;
+using System.Text.Json;
+using JdeScoping.Core.Models.Auth;
+using Microsoft.JSInterop;
+
+namespace JdeScoping.Client.Services;
+
+///
+/// Encrypts login credentials using Web Crypto API via JavaScript interop.
+/// Uses RSA-OAEP with SHA-256 to encrypt credentials before transmission.
+///
+public class CryptoService : ICryptoService
+{
+ private readonly HttpClient _httpClient;
+ private readonly IJSRuntime _jsRuntime;
+ private string? _cachedPublicKeyPem;
+ private readonly SemaphoreSlim _keyLock = new(1, 1);
+
+ public CryptoService(HttpClient httpClient, IJSRuntime jsRuntime)
+ {
+ _httpClient = httpClient;
+ _jsRuntime = jsRuntime;
+ }
+
+ ///
+ public async Task EncryptLoginAsync(LoginModel model)
+ {
+ var publicKeyPem = await GetOrFetchPublicKeyAsync();
+
+ var json = JsonSerializer.Serialize(model);
+
+ // Use JavaScript interop to encrypt with RSA-OAEP via browser's SubtleCrypto API
+ var encryptedBase64 = await _jsRuntime.InvokeAsync(
+ "jdeScopingInterop.rsaEncrypt",
+ publicKeyPem,
+ json);
+
+ return encryptedBase64;
+ }
+
+ private async Task GetOrFetchPublicKeyAsync()
+ {
+ if (_cachedPublicKeyPem is not null)
+ return _cachedPublicKeyPem;
+
+ await _keyLock.WaitAsync();
+ try
+ {
+ if (_cachedPublicKeyPem is not null)
+ return _cachedPublicKeyPem;
+
+ var response = await _httpClient.GetFromJsonAsync("api/auth/public-key")
+ ?? throw new InvalidOperationException("Failed to fetch public key from server");
+
+ _cachedPublicKeyPem = response.PublicKeyPem;
+ return _cachedPublicKeyPem;
+ }
+ finally
+ {
+ _keyLock.Release();
+ }
+ }
+}
diff --git a/NEW/src/JdeScoping.Client/Services/ICryptoService.cs b/NEW/src/JdeScoping.Client/Services/ICryptoService.cs
new file mode 100644
index 0000000..905b921
--- /dev/null
+++ b/NEW/src/JdeScoping.Client/Services/ICryptoService.cs
@@ -0,0 +1,16 @@
+using JdeScoping.Core.Models.Auth;
+
+namespace JdeScoping.Client.Services;
+
+///
+/// Service for encrypting data using server's RSA public key.
+///
+public interface ICryptoService
+{
+ ///
+ /// Encrypts login credentials for transmission to server.
+ ///
+ /// Login credentials to encrypt
+ /// Base64-encoded encrypted data
+ Task EncryptLoginAsync(LoginModel model);
+}
diff --git a/NEW/src/JdeScoping.Client/wwwroot/js/interop.js b/NEW/src/JdeScoping.Client/wwwroot/js/interop.js
index bcf2da6..0197695 100644
--- a/NEW/src/JdeScoping.Client/wwwroot/js/interop.js
+++ b/NEW/src/JdeScoping.Client/wwwroot/js/interop.js
@@ -66,5 +66,51 @@ window.jdeScopingInterop = {
// Remove value from sessionStorage
removeSessionStorage: function (key) {
sessionStorage.removeItem(key);
+ },
+
+ // RSA-OAEP encryption for login credentials
+ // Takes a PEM-encoded public key and plaintext, returns base64-encoded ciphertext
+ rsaEncrypt: async function (publicKeyPem, plaintext) {
+ // Extract base64 content from PEM format
+ const pemHeader = '-----BEGIN PUBLIC KEY-----';
+ const pemFooter = '-----END PUBLIC KEY-----';
+ const pemContents = publicKeyPem
+ .replace(pemHeader, '')
+ .replace(pemFooter, '')
+ .replace(/\s/g, '');
+
+ // Decode base64 to binary
+ const binaryDer = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0));
+
+ // Import the public key for RSA-OAEP encryption
+ const publicKey = await window.crypto.subtle.importKey(
+ 'spki',
+ binaryDer,
+ {
+ name: 'RSA-OAEP',
+ hash: 'SHA-256'
+ },
+ false,
+ ['encrypt']
+ );
+
+ // Encode plaintext to bytes
+ const encoder = new TextEncoder();
+ const plaintextBytes = encoder.encode(plaintext);
+
+ // Encrypt with RSA-OAEP
+ const ciphertext = await window.crypto.subtle.encrypt(
+ { name: 'RSA-OAEP' },
+ publicKey,
+ plaintextBytes
+ );
+
+ // Convert to base64 for transmission
+ const ciphertextArray = new Uint8Array(ciphertext);
+ let binary = '';
+ for (let i = 0; i < ciphertextArray.length; i++) {
+ binary += String.fromCharCode(ciphertextArray[i]);
+ }
+ return btoa(binary);
}
};