ec4c8fab87
Move configuration options from Core/DataAccess/DataSync/ExcelIO to dedicated Options folders within each project for better organization. Update all references and tests accordingly.
135 lines
3.7 KiB
Markdown
135 lines
3.7 KiB
Markdown
# Encrypted Login Design
|
|
|
|
## Overview
|
|
|
|
Consolidate login models into Core project and implement RSA-OAEP encryption for login credentials between Blazor WASM client and API.
|
|
|
|
## Decisions
|
|
|
|
| Decision | Choice | Rationale |
|
|
|----------|--------|-----------|
|
|
| Encryption algorithm | RSA-OAEP (SHA-256) | Simple direct encryption, suitable for small payloads |
|
|
| Key size | 2048-bit | Industry standard, ~20 year security horizon |
|
|
| Key distribution | Fetch at runtime | Allows key rotation without client redeployment |
|
|
| Key storage | Auto-generate, persist to file | Self-bootstrapping, no manual key management |
|
|
| Key expiration | None | Internal enterprise app, manual rotation if needed |
|
|
|
|
## New Models (Core)
|
|
|
|
All in `JdeScoping.Core/Models/Auth/`:
|
|
|
|
```csharp
|
|
// LoginModel.cs - shared form/request model
|
|
public class LoginModel
|
|
{
|
|
[Required(ErrorMessage = "Username is required")]
|
|
public string Username { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Password is required")]
|
|
public string Password { get; set; } = string.Empty;
|
|
}
|
|
|
|
// LoginResultModel.cs - API response (unencrypted)
|
|
public record LoginResultModel(
|
|
bool Success,
|
|
string? ErrorMessage,
|
|
UserInfo? User);
|
|
|
|
// EncryptedLoginRequest.cs - encrypted payload wrapper
|
|
public record EncryptedLoginRequest(string EncryptedData);
|
|
|
|
// PublicKeyResponse.cs - public key endpoint response
|
|
public record PublicKeyResponse(string PublicKeyPem);
|
|
```
|
|
|
|
## RSA Key Service (Infrastructure)
|
|
|
|
**Interface:** `JdeScoping.Core/Interfaces/IRsaKeyService.cs`
|
|
```csharp
|
|
public interface IRsaKeyService
|
|
{
|
|
string GetPublicKeyPem();
|
|
byte[] Decrypt(byte[] ciphertext);
|
|
}
|
|
```
|
|
|
|
**Implementation:** `JdeScoping.Infrastructure/Security/RsaKeyService.cs`
|
|
- Auto-generates 2048-bit RSA key on first startup if not exists
|
|
- Persists private key to configurable file path
|
|
- Exports public key as PEM format
|
|
- Decrypts using RSA-OAEP with SHA-256 padding
|
|
|
|
## API Changes
|
|
|
|
**New endpoint:**
|
|
```
|
|
GET /api/auth/public-key → PublicKeyResponse
|
|
```
|
|
|
|
**Modified endpoint:**
|
|
```
|
|
POST /api/auth/login
|
|
Body: EncryptedLoginRequest { EncryptedData: base64 }
|
|
Response: LoginResultModel
|
|
```
|
|
|
|
Flow:
|
|
1. Receive base64-encoded encrypted data
|
|
2. Decode and decrypt with RSA private key
|
|
3. Deserialize JSON to LoginModel
|
|
4. Authenticate via IAuthService
|
|
5. Return LoginResultModel (unencrypted)
|
|
|
|
## Client Changes
|
|
|
|
**New service:** `ICryptoService` / `CryptoService`
|
|
- Uses `Blazor.SubtleCrypto` NuGet package
|
|
- Fetches and caches server public key
|
|
- Encrypts LoginModel JSON with RSA-OAEP
|
|
- Returns base64-encoded ciphertext
|
|
|
|
**Modified AuthService:**
|
|
- Injects ICryptoService
|
|
- Encrypts LoginModel before sending
|
|
- Sends EncryptedLoginRequest to API
|
|
|
|
## Files to Delete
|
|
|
|
- `JdeScoping.Api/Models/LoginRequest.cs`
|
|
- `JdeScoping.Client/Models/LoginModel.cs`
|
|
|
|
## Dependencies
|
|
|
|
**JdeScoping.Client.csproj:**
|
|
```xml
|
|
<PackageReference Include="Blazor.SubtleCrypto" Version="..." />
|
|
```
|
|
|
|
## DI Registration
|
|
|
|
**API/Host:**
|
|
```csharp
|
|
services.AddSingleton<IRsaKeyService>(sp =>
|
|
new RsaKeyService(Path.Combine(appDataPath, "rsa-key.bin")));
|
|
```
|
|
|
|
**Client:**
|
|
```csharp
|
|
services.AddScoped<ICryptoService, CryptoService>();
|
|
```
|
|
|
|
## Test Coverage
|
|
|
|
### RsaKeyServiceTests (Api.Tests)
|
|
- GetPublicKeyPem returns valid PEM format
|
|
- Decrypt with valid ciphertext returns plaintext
|
|
- Constructor loads existing key from file
|
|
- Constructor generates and persists new key if missing
|
|
|
|
### CryptoServiceTests (Client.Tests)
|
|
- EncryptLoginAsync returns valid base64
|
|
- EncryptLoginAsync caches public key (single HTTP call)
|
|
|
|
### Integration (optional)
|
|
- Full roundtrip: encrypt on client → decrypt on API → authenticate
|