Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@
**Vulnerability:** Intermediate key material (`secret` extracted from chaining key, and expanded `t*_plus` buffers) in `src/wireguard/crypto.zig`'s HKDF implementation (`kdf1`, `kdf2`, `kdf3`) was left on the stack without being zeroed. These buffers contain sensitive pseudorandom keys derived from the handshake chaining key.
**Learning:** Helper functions often create temporary buffers on the stack which persist until overwritten. Unlike the main handshake state struct, these are ephemeral but critical. Zig does not automatically zero stack variables on return.
**Prevention:** Always use `defer std.crypto.secureZero(u8, &var);` immediately after declaring any variable that will hold sensitive key material, especially in crypto primitives.

## 2025-05-24 - [CRITICAL] Timing Attack on Public Key Verification
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added sentinel entry is dated 2025-05-24, but the other recent entries in this file use dates in 2026 (e.g., 2026-02-24 on line 8 and 2026-05-24 on line 15). Given that the PR was created in March 2026, the year 2025 appears to be a typo that should be 2026. The date should reflect when the vulnerability was discovered and fixed.

Copilot uses AI. Check for mistakes.
**Vulnerability:** The functions `consumeInitiation` and `consumeInitiationFast` in `src/wireguard/noise.zig` used `std.mem.eql` to compare the decrypted static public key (`initiator_static`) with the expected remote public key (`remote_static`). This exposes a timing oracle, allowing an attacker to determine the expected public key byte-by-byte. While not as immediately catastrophic as leaking a private key, an attacker could abuse this to spoof initiations or gather information on network topology.
**Learning:** Public keys used as identifiers and matched during authentication protocols must be compared using constant-time functions. `std.mem.eql` terminates early on mismatch, revealing information via timing differences.
**Prevention:** Always use `std.crypto.timing_safe.eql` for comparing cryptographic identities and secrets during authentication flows. Update the review checklist to flag non-constant-time comparisons on keys, even public ones, during handshake validations.
6 changes: 4 additions & 2 deletions src/wireguard/noise.zig
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ pub const Handshake = struct {
crypto.mixHash(&hash, &msg.encrypted_static);

// Verify the decrypted static matches what we expect
if (!std.mem.eql(u8, &initiator_static, &self.remote_static)) {
// Security: Use constant-time comparison for cryptographic keys to prevent timing attacks.
if (!std.crypto.timing_safe.eql([32]u8, initiator_static, self.remote_static)) {
return error.UnknownPeer;
}

Expand Down Expand Up @@ -282,7 +283,8 @@ pub const Handshake = struct {
var hash = preamble.hash;

// Verify the decrypted static matches what we expect
if (!std.mem.eql(u8, &preamble.initiator_static, &self.remote_static)) {
// Security: Use constant-time comparison for cryptographic keys to prevent timing attacks.
if (!std.crypto.timing_safe.eql([32]u8, preamble.initiator_static, self.remote_static)) {
return error.UnknownPeer;
}

Expand Down