Skip to content

Commit 67e6af4

Browse files
rzikmCopilot
andauthored
Validate base64 in NegotiateAuthentication.GetOutgoingBlob(string) (#128691)
The `string`-based `NegotiateAuthentication.GetOutgoingBlob` overload decoded the incoming blob with `Convert.FromBase64String`, which throws `FormatException` on malformed input. Callers that hand off untrusted client tokens (e.g. HTTP servers parsing `Authorization: Negotiate <blob>`) had no way to get the documented `InvalidToken` status code for that case without wrapping the call in a try/catch. This change switches to `Convert.TryFromBase64String` and returns `NegotiateAuthenticationStatusCode.InvalidToken` with a `null` outgoing blob when decoding fails, matching the contract on the other failure paths. The decode buffer is rented from `ArrayPool<byte>` and sliced to the actual decoded length, then passed to the existing `ReadOnlySpan<byte>` overload so we avoid both the throw and the extra allocation on the happy path. Tests cover a few representative malformed inputs (non-base64 characters, wrong length, misplaced padding). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6684fb2 commit 67e6af4

2 files changed

Lines changed: 43 additions & 10 deletions

File tree

src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -253,20 +253,39 @@ public IIdentity RemoteIdentity
253253
/// </remarks>
254254
public string? GetOutgoingBlob(string? incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
255255
{
256-
byte[]? decodedIncomingBlob = null;
257-
if (!string.IsNullOrEmpty(incomingBlob))
256+
byte[]? rentedBuffer = null;
257+
try
258258
{
259-
decodedIncomingBlob = Convert.FromBase64String(incomingBlob);
260-
}
261-
byte[]? decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, out statusCode);
259+
ReadOnlySpan<byte> decodedIncomingBlob = default;
260+
if (!string.IsNullOrEmpty(incomingBlob))
261+
{
262+
rentedBuffer = ArrayPool<byte>.Shared.Rent((incomingBlob.Length / 4) * 3);
263+
if (!Convert.TryFromBase64String(incomingBlob, rentedBuffer, out int decodedLength))
264+
{
265+
statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
266+
return null;
267+
}
268+
269+
decodedIncomingBlob = rentedBuffer.AsSpan(0, decodedLength);
270+
}
271+
272+
byte[]? decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, out statusCode);
262273

263-
string? outgoingBlob = null;
264-
if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0)
274+
string? outgoingBlob = null;
275+
if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0)
276+
{
277+
outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob);
278+
}
279+
280+
return outgoingBlob;
281+
}
282+
finally
265283
{
266-
outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob);
284+
if (rentedBuffer is not null)
285+
{
286+
ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
287+
}
267288
}
268-
269-
return outgoingBlob;
270289
}
271290

272291
/// <summary>

src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ public void Package_Unsupported()
8585
Assert.Equal(NegotiateAuthenticationStatusCode.Unsupported, statusCode);
8686
}
8787

88+
[Theory]
89+
[InlineData("!!!!")]
90+
[InlineData("AAA")]
91+
[InlineData("AAAAA")]
92+
[InlineData("AA=A")]
93+
public void GetOutgoingBlob_InvalidBase64_ReturnsInvalidToken(string incomingBlob)
94+
{
95+
NegotiateAuthenticationClientOptions clientOptions = new NegotiateAuthenticationClientOptions { Package = "Negotiate", Credential = s_testCredentialRight, TargetName = "HTTP/foo" };
96+
NegotiateAuthentication negotiateAuthentication = new NegotiateAuthentication(clientOptions);
97+
string? outgoingBlob = negotiateAuthentication.GetOutgoingBlob(incomingBlob, out NegotiateAuthenticationStatusCode statusCode);
98+
Assert.Null(outgoingBlob);
99+
Assert.Equal(NegotiateAuthenticationStatusCode.InvalidToken, statusCode);
100+
}
101+
88102
[ConditionalFact(typeof(NegotiateAuthenticationTests), nameof(IsNtlmAvailable))]
89103
public void Package_Supported_NTLM()
90104
{

0 commit comments

Comments
 (0)