Release v3.3: end-to-end encrypted audio, plus cue and reliability fixes
Headline: all audio is now encrypted (AES-256-GCM), keyed by a per-profile password. Mandatory — v3.3 only interoperates with v3.3+. Encryption - RemSoundCrypto (Core): PBKDF2 key derivation, AES-GCM encrypt/decrypt (low-alloc, into-span), password fingerprint, light on-disk obfuscation. - Wire: SenderLane encrypts the audio payload (PCM split across parts when the +28 overhead crosses MTU); AudioReceiver/StreamSession decrypt via a shared single-thread AudioDecryptor. Fingerprint piggybacks on the Format packet (offset 36, backward-compatible) so a peer can detect a password mismatch. - Profile.Password (scrambled), carried through BuildCurrentProfile; MainForm derives + pushes the key/fingerprint to sender + receiver (RecomputeAudioCrypto). - UX: ask-for-password on profile create; File -> Change this profile's password (ProfilePasswordDialog); Options -> Profile passwords (manager); a gate that prompts before streaming without a password; and a clear "passwords don't match" / "peer needs to update" message driven by the fingerprint. Cue fixes - CuePlayer (NAudio) replaces System.Media.SoundPlayer, which silently failed on the 96 kHz/24-bit cue WAVs (and any custom file) — cues now play reliably, resampled to 48 kHz/16-bit. Also fixes the Preferences preview button. - Connect/disconnect cues now audio-gated with hysteresis: connected when audio flows OR heartbeat healthy; lost only when audio stops AND heartbeat unreachable. Kills false disconnects and the receive-only "no cues" case. - Honest cue logging (played / muted / not loaded). Smaller - Endpoint stickiness: keep the audio target pinned to the heartbeat-proven address instead of chasing a multi-homed peer's other (unreachable) address. - "Online/offline" label now audio+heartbeat aware, not discovery-only. - "Show what's new after each update" preference (on by default). Docs: About v3.3 block, RELEASE_NOTES, README (encryption as a headline), manual section 12 "Passwords and encryption" (+ renumber), MANUAL.md regenerated. Version 3.2.0 -> 3.3.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cdcac859c4
commit
959720f54d
@@ -0,0 +1,75 @@
|
||||
namespace RemSound.App;
|
||||
|
||||
/// <summary>
|
||||
/// Small dialog for viewing and changing the active profile's encryption password. Shows the
|
||||
/// current password in a PLAIN (readable) edit box, not a masked one, on purpose: this audience
|
||||
/// uses a screen reader, and a masked password field reads as a run of bullets, which is
|
||||
/// useless. Letting NVDA read the actual characters is far more usable, and the security model
|
||||
/// already accepts that the password is recoverable from the profile file anyway.
|
||||
///
|
||||
/// Returns the new password on OK (which may be empty — that clears the password), or null on
|
||||
/// Cancel. The result is trimmed so an invisible trailing space can't cause a maddening
|
||||
/// "we typed the same password but it won't connect" mismatch between two peers. 2026-05-31.
|
||||
/// </summary>
|
||||
internal static class ProfilePasswordDialog
|
||||
{
|
||||
public static string? Show(IWin32Window owner, string profileTitle, string currentPassword)
|
||||
{
|
||||
using var dialog = new Form
|
||||
{
|
||||
Text = "Change profile password",
|
||||
StartPosition = FormStartPosition.CenterParent,
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||
MinimizeBox = false,
|
||||
MaximizeBox = false,
|
||||
ShowInTaskbar = false,
|
||||
ClientSize = new Size(460, 150),
|
||||
AccessibleName = "Change profile password",
|
||||
};
|
||||
|
||||
var label = new Label
|
||||
{
|
||||
Text = $"Password for profile “{profileTitle}”:",
|
||||
AutoSize = true,
|
||||
};
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Text = currentPassword,
|
||||
Dock = DockStyle.Top,
|
||||
Width = 400,
|
||||
AccessibleName = $"Password for profile {profileTitle}",
|
||||
};
|
||||
var hint = new Label
|
||||
{
|
||||
Text = "Both you and the person you're connecting to must use the same password.",
|
||||
AutoSize = true,
|
||||
};
|
||||
|
||||
var okButton = new Button { Text = "OK", AutoSize = true, DialogResult = DialogResult.OK };
|
||||
var cancelButton = new Button { Text = "Cancel", AutoSize = true, DialogResult = DialogResult.Cancel };
|
||||
textBox.KeyDown += (_, args) =>
|
||||
{
|
||||
if (args.KeyCode == Keys.Enter)
|
||||
{
|
||||
dialog.DialogResult = DialogResult.OK;
|
||||
dialog.Close();
|
||||
args.Handled = true;
|
||||
args.SuppressKeyPress = true;
|
||||
}
|
||||
};
|
||||
|
||||
var panel = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(12), RowCount = 4, ColumnCount = 1 };
|
||||
panel.Controls.Add(label, 0, 0);
|
||||
panel.Controls.Add(textBox, 0, 1);
|
||||
panel.Controls.Add(hint, 0, 2);
|
||||
var buttons = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.RightToLeft, Dock = DockStyle.Fill };
|
||||
buttons.Controls.Add(okButton);
|
||||
buttons.Controls.Add(cancelButton);
|
||||
panel.Controls.Add(buttons, 0, 3);
|
||||
dialog.Controls.Add(panel);
|
||||
dialog.AcceptButton = okButton;
|
||||
dialog.CancelButton = cancelButton;
|
||||
|
||||
return dialog.ShowDialog(owner) == DialogResult.OK ? textBox.Text.Trim() : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user