Summary Update.
This commit is contained in:
@@ -3,8 +3,14 @@ using System.Text;
|
||||
|
||||
namespace RelayClient.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Client-side mirror of RelayServer.Services.Crypto.E2EeHelper. Identical algorithms +
|
||||
/// key formats so blobs round-trip cleanly between server and client.
|
||||
/// See the server class for full algorithm details.
|
||||
/// </summary>
|
||||
public static class E2EeHelper
|
||||
{
|
||||
/// <summary>Generates a fresh RSA-2048 keypair. Called once per user on first launch and persisted via KeyStorage.</summary>
|
||||
public static (string publicKey, string privateKey) GenerateRsaKeyPair()
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
@@ -15,6 +21,11 @@ public static class E2EeHelper
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid encrypts a plaintext string for a specific recipient: fresh AES-256 key encrypts
|
||||
/// the payload (AES-GCM), then RSA-OAEP-SHA256 wraps the AES key with the recipient's
|
||||
/// public key. Returns base64-encoded fields ready to ship in a SocketEncryptedMessage.
|
||||
/// </summary>
|
||||
public static EncryptedPayload EncryptForRecipient(string plainText, string recipientPublicKeyBase64)
|
||||
{
|
||||
byte[] aesKey = RandomNumberGenerator.GetBytes(32);
|
||||
@@ -44,6 +55,11 @@ public static class E2EeHelper
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverse of EncryptForRecipient: RSA-decrypt the AES key with the recipient's private
|
||||
/// key, then AES-GCM-decrypt the ciphertext. Throws on tampered/corrupt payloads
|
||||
/// (auth tag mismatch). Returns the original UTF-8 plaintext string.
|
||||
/// </summary>
|
||||
public static string DecryptForRecipient(EncryptedPayload payload, string recipientPrivateKeyBase64)
|
||||
{
|
||||
byte[] aesKey;
|
||||
@@ -69,6 +85,7 @@ public static class E2EeHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The 4-tuple ciphertext bundle. Same shape on both client and server; matches SocketEncryptedMessage's encrypted fields.</summary>
|
||||
public class EncryptedPayload
|
||||
{
|
||||
public required string CipherText { get; set; }
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
namespace RelayClient.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Per-user RSA keypair persistence. Keys live as base64-encoded files in
|
||||
/// {AppData}/keys/{username}.{public|private}.key
|
||||
///
|
||||
/// Plaintext on disk. For now this is fine because the only attack model is "someone else
|
||||
/// has access to your filesystem" — at which point everything is compromised. A future
|
||||
/// enhancement could encrypt the private key with a passphrase derived from the user's
|
||||
/// password, similar to how SSH/PGP do it.
|
||||
/// </summary>
|
||||
public static class KeyStorage
|
||||
{
|
||||
/// <summary>Returns (and creates if needed) the per-app keys directory.</summary>
|
||||
private static string GetKeyFolder()
|
||||
{
|
||||
var folder = Path.Combine(FileSystem.AppDataDirectory, "keys");
|
||||
@@ -9,29 +19,34 @@ public static class KeyStorage
|
||||
return folder;
|
||||
}
|
||||
|
||||
/// <summary>Writes the base64 RSA private key to disk. Used at first-launch after GenerateRsaKeyPair.</summary>
|
||||
public static void SavePrivateKey(string username, string privateKey)
|
||||
{
|
||||
File.WriteAllText(Path.Combine(GetKeyFolder(), $"{username}.private.key"), privateKey);
|
||||
}
|
||||
|
||||
/// <summary>Writes the base64 RSA public key to disk. Sent to the server via WsAction.RegisterKey.</summary>
|
||||
public static void SavePublicKey(string username, string publicKey)
|
||||
{
|
||||
File.WriteAllText(Path.Combine(GetKeyFolder(), $"{username}.public.key"), publicKey);
|
||||
}
|
||||
|
||||
/// <summary>Reads the user's RSA private key. Used by TryDecryptAndParseContent on every inbound message.</summary>
|
||||
public static string LoadPrivateKey(string username)
|
||||
{
|
||||
return File.ReadAllText(Path.Combine(GetKeyFolder(), $"{username}.private.key"));
|
||||
}
|
||||
|
||||
/// <summary>Reads the user's RSA public key. Used during the boot handshake to send to the server.</summary>
|
||||
public static string LoadPublicKey(string username)
|
||||
{
|
||||
return File.ReadAllText(Path.Combine(GetKeyFolder(), $"{username}.public.key"));
|
||||
}
|
||||
|
||||
/// <summary>True if BOTH halves of the user's keypair already exist on disk. False means we need to generate.</summary>
|
||||
public static bool HasKeys(string username)
|
||||
{
|
||||
return File.Exists(Path.Combine(GetKeyFolder(), $"{username}.private.key")) &&
|
||||
File.Exists(Path.Combine(GetKeyFolder(), $"{username}.public.key"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user