Skip to content

Commit 3af1fd3

Browse files
committed
Rewrite locking in DnsCookieSecretManager
1 parent 224475f commit 3af1fd3

1 file changed

Lines changed: 105 additions & 76 deletions

File tree

DnsServerCore/Dns/Security/DnsCookieSecretManager.cs

Lines changed: 105 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ You should have received a copy of the GNU General Public License
1818
*/
1919

2020
using System;
21-
using System.Collections.Generic;
2221
using System.IO;
2322
using System.Security.Cryptography;
2423
using System.Threading;
@@ -27,6 +26,19 @@ namespace DnsServerCore.Dns.Security
2726
{
2827
public class DnsCookieSecretManager
2928
{
29+
#region constants
30+
31+
private const int FileVersion = 1;
32+
33+
// Operational bounds; keep aligned with validator policy.
34+
private const int MinSecretLen = 16;
35+
private const int MaxSecretLen = 256;
36+
37+
// Default secret size (256-bit)
38+
private const int DefaultSecretLen = 32;
39+
40+
#endregion
41+
3042
#region variables
3143

3244
readonly string _secretFilePath;
@@ -42,98 +54,113 @@ public class DnsCookieSecretManager
4254

4355
public DnsCookieSecretManager(string secretFilePath)
4456
{
57+
if (string.IsNullOrWhiteSpace(secretFilePath))
58+
throw new ArgumentException("Secret file path must not be null or empty.", nameof(secretFilePath));
59+
4560
_secretFilePath = secretFilePath;
46-
Load();
61+
62+
lock (_lock)
63+
{
64+
LoadLocked();
65+
}
4766
}
4867

4968
#endregion
5069

5170
#region private
5271

53-
private void Load()
72+
private void LoadLocked()
5473
{
55-
lock (_lock)
74+
// Caller must hold _lock
75+
if (!File.Exists(_secretFilePath))
5676
{
57-
if (File.Exists(_secretFilePath))
58-
{
59-
try
60-
{
61-
byte[] data = File.ReadAllBytes(_secretFilePath);
62-
using (MemoryStream ms = new MemoryStream(data))
63-
using (BinaryReader br = new BinaryReader(ms))
64-
{
65-
int version = br.ReadInt32();
66-
if (version != 1)
67-
throw new InvalidDataException("Unsupported secret file version.");
68-
69-
_currentSecretCreated = new DateTime(br.ReadInt64(), DateTimeKind.Utc);
70-
71-
int currentLen = br.ReadInt32();
72-
if (currentLen < 8 || currentLen > 256)
73-
throw new InvalidDataException("Invalid current secret length.");
74-
75-
_currentSecret = br.ReadBytes(currentLen);
76-
77-
int previousLen = br.ReadInt32();
78-
if (previousLen < 8 || previousLen > 256)
79-
throw new InvalidDataException("Invalid previous secret length.");
80-
81-
_previousSecret = br.ReadBytes(previousLen);
82-
}
83-
}
84-
catch
85-
{
86-
// If loading fails, generate new secrets
87-
GenerateNewSecrets();
88-
}
89-
}
90-
else
77+
GenerateNewSecretsLocked();
78+
return;
79+
}
80+
81+
try
82+
{
83+
byte[] data = File.ReadAllBytes(_secretFilePath);
84+
using MemoryStream ms = new MemoryStream(data, writable: false);
85+
using BinaryReader br = new BinaryReader(ms);
86+
87+
int version = br.ReadInt32();
88+
if (version != FileVersion)
89+
throw new InvalidDataException("Unsupported secret file version.");
90+
91+
_currentSecretCreated = new DateTime(br.ReadInt64(), DateTimeKind.Utc);
92+
93+
int currentLen = br.ReadInt32();
94+
if (currentLen < MinSecretLen || currentLen > MaxSecretLen)
95+
throw new InvalidDataException("Invalid current secret length.");
96+
97+
byte[] current = br.ReadBytes(currentLen);
98+
if (current.Length != currentLen)
99+
throw new EndOfStreamException("Unexpected end of secret file (current secret).");
100+
101+
int previousLen = br.ReadInt32();
102+
byte[] previous = null;
103+
104+
if (previousLen != 0)
91105
{
92-
GenerateNewSecrets();
106+
if (previousLen < MinSecretLen || previousLen > MaxSecretLen)
107+
throw new InvalidDataException("Invalid previous secret length.");
108+
109+
previous = br.ReadBytes(previousLen);
110+
if (previous.Length != previousLen)
111+
throw new EndOfStreamException("Unexpected end of secret file (previous secret).");
93112
}
113+
114+
_currentSecret = current;
115+
_previousSecret = previous;
116+
}
117+
catch
118+
{
119+
GenerateNewSecretsLocked();
94120
}
95121
}
96122

97-
private void Save()
123+
private void SaveLocked()
98124
{
99-
lock (_lock)
125+
// Caller must hold _lock
126+
if (_currentSecret is null || _currentSecret.Length < MinSecretLen)
127+
throw new InvalidOperationException("Current secret is missing or too short.");
128+
129+
using MemoryStream ms = new MemoryStream();
130+
using (BinaryWriter bw = new BinaryWriter(ms))
100131
{
101-
using (MemoryStream ms = new MemoryStream())
132+
bw.Write(FileVersion);
133+
bw.Write(_currentSecretCreated.Ticks);
134+
135+
bw.Write(_currentSecret.Length);
136+
bw.Write(_currentSecret);
137+
138+
if (_previousSecret is { Length: >= MinSecretLen and <= MaxSecretLen })
139+
{
140+
bw.Write(_previousSecret.Length);
141+
bw.Write(_previousSecret);
142+
}
143+
else
102144
{
103-
using (BinaryWriter bw = new BinaryWriter(ms))
104-
{
105-
bw.Write(1); // version
106-
bw.Write(_currentSecretCreated.Ticks);
107-
108-
bw.Write(_currentSecret.Length);
109-
bw.Write(_currentSecret);
110-
111-
if (_previousSecret != null)
112-
{
113-
bw.Write(_previousSecret.Length);
114-
bw.Write(_previousSecret);
115-
}
116-
else
117-
{
118-
bw.Write(0);
119-
}
120-
}
121-
122-
string directory = Path.GetDirectoryName(_secretFilePath);
123-
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
124-
Directory.CreateDirectory(directory);
125-
126-
File.WriteAllBytes(_secretFilePath, ms.ToArray());
145+
bw.Write(0);
127146
}
128147
}
148+
149+
string directory = Path.GetDirectoryName(_secretFilePath);
150+
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
151+
Directory.CreateDirectory(directory);
152+
153+
File.WriteAllBytes(_secretFilePath, ms.ToArray());
129154
}
130155

131-
private void GenerateNewSecrets()
156+
private void GenerateNewSecretsLocked()
132157
{
133-
_currentSecret = RandomNumberGenerator.GetBytes(32);
158+
// Caller must hold _lock
159+
_currentSecret = RandomNumberGenerator.GetBytes(DefaultSecretLen);
134160
_currentSecretCreated = DateTime.UtcNow;
135161
_previousSecret = null;
136-
Save();
162+
163+
SaveLocked();
137164
}
138165

139166
#endregion
@@ -144,26 +171,28 @@ public void Rotate()
144171
{
145172
lock (_lock)
146173
{
147-
_previousSecret = _currentSecret != null ? (byte[])_currentSecret.Clone() : null;
148-
_currentSecret = RandomNumberGenerator.GetBytes(32);
174+
_previousSecret = _currentSecret is null ? null : (byte[])_currentSecret.Clone();
175+
_currentSecret = RandomNumberGenerator.GetBytes(DefaultSecretLen);
149176
_currentSecretCreated = DateTime.UtcNow;
150-
Save();
177+
178+
SaveLocked();
151179
}
152180
}
153181

154-
public ReadOnlySpan<byte> GetCurrentSecret()
182+
// Returning spans here is unsafe once the lock is released; return a clone instead.
183+
public byte[] GetCurrentSecret()
155184
{
156185
lock (_lock)
157186
{
158-
return new ReadOnlySpan<byte>(_currentSecret);
187+
return _currentSecret is null ? null : (byte[])_currentSecret.Clone();
159188
}
160189
}
161190

162-
public ReadOnlySpan<byte> GetPreviousSecret()
191+
public byte[] GetPreviousSecret()
163192
{
164193
lock (_lock)
165194
{
166-
return new ReadOnlySpan<byte>(_previousSecret);
195+
return _previousSecret is null ? null : (byte[])_previousSecret.Clone();
167196
}
168197
}
169198

0 commit comments

Comments
 (0)