namespace RelayClient.Crypto; /// /// 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. /// public static class KeyStorage { /// Returns (and creates if needed) the per-app keys directory. private static string GetKeyFolder() { var folder = Path.Combine(FileSystem.AppDataDirectory, "keys"); Directory.CreateDirectory(folder); return folder; } /// Writes the base64 RSA private key to disk. Used at first-launch after GenerateRsaKeyPair. public static void SavePrivateKey(string username, string privateKey) { File.WriteAllText(Path.Combine(GetKeyFolder(), $"{username}.private.key"), privateKey); } /// Writes the base64 RSA public key to disk. Sent to the server via WsAction.RegisterKey. public static void SavePublicKey(string username, string publicKey) { File.WriteAllText(Path.Combine(GetKeyFolder(), $"{username}.public.key"), publicKey); } /// Reads the user's RSA private key. Used by TryDecryptAndParseContent on every inbound message. public static string LoadPrivateKey(string username) { return File.ReadAllText(Path.Combine(GetKeyFolder(), $"{username}.private.key")); } /// Reads the user's RSA public key. Used during the boot handshake to send to the server. public static string LoadPublicKey(string username) { return File.ReadAllText(Path.Combine(GetKeyFolder(), $"{username}.public.key")); } /// True if BOTH halves of the user's keypair already exist on disk. False means we need to generate. public static bool HasKeys(string username) { return File.Exists(Path.Combine(GetKeyFolder(), $"{username}.private.key")) && File.Exists(Path.Combine(GetKeyFolder(), $"{username}.public.key")); } }