Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4ac2501
WIP: Add implementation of NegotiateAuthentication
filipnavara Jun 3, 2022
c7a6bce
WIP: Update error code mapping
filipnavara Jun 4, 2022
5d5c61f
Spanify input of GetOutgoingBlob
filipnavara Jun 4, 2022
d0d1bba
Update comments
filipnavara Jun 4, 2022
982e7c3
Move NegotiateStreamPal.Encrypt/Decrypt to shared sources. Unix imple…
filipnavara Jun 4, 2022
cbd24a2
Revert accidental change
filipnavara Jun 4, 2022
9d4d557
Build fixes.
filipnavara Jun 4, 2022
c371515
Fix error handling condition
filipnavara Jun 4, 2022
3306c67
Update error mapping based on HttpListener usage.
filipnavara Jun 4, 2022
065d87c
WIP: HttpListener test
filipnavara Jun 7, 2022
447020a
Move workaround from HttpListener to low-level SSPI code
filipnavara Jun 7, 2022
bed08fa
Fix build
filipnavara Jun 14, 2022
318ca06
Clean up
filipnavara Jun 14, 2022
5474811
Revert "WIP: HttpListener test"
filipnavara Jun 14, 2022
4cdb8af
Convert System.Net.Http.FunctionalTests to use NegotiateAuthenticatio…
filipnavara Jun 14, 2022
34e47c4
Dispose the identity along NegotiateAuthentication
filipnavara Jun 14, 2022
73ac446
Modify unit tests to use the new API
filipnavara Jun 14, 2022
7e85f10
Add exceptions for invalid inputs/states
filipnavara Jun 14, 2022
7c870bd
Remove tvOS unsupported marker, managed NTLM is used on tvOS
filipnavara Jun 16, 2022
1bd0cde
Apply suggestions from code review
filipnavara Jun 14, 2022
e93f073
Fix typo
filipnavara Jun 14, 2022
5aae44d
Remove reference equality checks from IsNTLM/IsKerberos
filipnavara Jun 16, 2022
b746590
Remove NTAuthentication.AssociatedName to make it more obvious which …
filipnavara Jun 16, 2022
dd7dd9a
Add comment
filipnavara Jun 16, 2022
39a43b2
Add more tests, handle unsupported protocols
filipnavara Jun 16, 2022
9a4ccdf
Handle NotSupportedException from NTAuthentication constructor
filipnavara Jun 16, 2022
e86ce79
Add workaround for linker issue
filipnavara Jun 17, 2022
ffed0be
Apply suggestions from code review
filipnavara Jun 20, 2022
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
Prev Previous commit
Next Next commit
Spanify input of GetOutgoingBlob
  • Loading branch information
filipnavara authored Jun 16, 2022
commit 5d5c61f10c8e651d0bf9019d3f6149f75f1794a9
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,21 @@ internal static partial Status ReleaseCred(
ref IntPtr credHandle);

[LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContext")]
internal static partial Status InitSecContext(
private static partial Status InitSecContext(
out Status minorStatus,
SafeGssCredHandle initiatorCredHandle,
ref SafeGssContextHandle contextHandle,
[MarshalAs(UnmanagedType.Bool)] bool isNtlmOnly,
SafeGssNameHandle? targetName,
uint reqFlags,
byte[]? inputBytes,
ref byte inputBytes,
int inputLength,
ref GssBuffer token,
out uint retFlags,
[MarshalAs(UnmanagedType.Bool)] out bool isNtlmUsed);

[LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContextEx")]
internal static partial Status InitSecContext(
private static partial Status InitSecContext(
out Status minorStatus,
SafeGssCredHandle initiatorCredHandle,
ref SafeGssContextHandle contextHandle,
Expand All @@ -96,23 +96,99 @@ internal static partial Status InitSecContext(
int cbtSize,
SafeGssNameHandle? targetName,
uint reqFlags,
byte[]? inputBytes,
ref byte inputBytes,
int inputLength,
ref GssBuffer token,
out uint retFlags,
[MarshalAs(UnmanagedType.Bool)] out bool isNtlmUsed);

internal static Status InitSecContext(
out Status minorStatus,
SafeGssCredHandle initiatorCredHandle,
ref SafeGssContextHandle contextHandle,
bool isNtlmOnly,
SafeGssNameHandle? targetName,
uint reqFlags,
ReadOnlySpan<byte> inputBytes,
ref GssBuffer token,
out uint retFlags,
out bool isNtlmUsed)
{
return InitSecContext(
out minorStatus,
initiatorCredHandle,
ref contextHandle,
isNtlmOnly,
targetName,
reqFlags,
ref MemoryMarshal.GetReference(inputBytes),
inputBytes.Length,
ref token,
out retFlags,
out isNtlmUsed);
}

internal static Status InitSecContext(
out Status minorStatus,
SafeGssCredHandle initiatorCredHandle,
ref SafeGssContextHandle contextHandle,
bool isNtlmOnly,
IntPtr cbt,
int cbtSize,
SafeGssNameHandle? targetName,
uint reqFlags,
ReadOnlySpan<byte> inputBytes,
ref GssBuffer token,
out uint retFlags,
out bool isNtlmUsed)
{
return InitSecContext(
out minorStatus,
initiatorCredHandle,
ref contextHandle,
isNtlmOnly,
cbt,
cbtSize,
targetName,
reqFlags,
ref MemoryMarshal.GetReference(inputBytes),
inputBytes.Length,
ref token,
out retFlags,
out isNtlmUsed);
}

[LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_AcceptSecContext")]
internal static partial Status AcceptSecContext(
private static partial Status AcceptSecContext(
out Status minorStatus,
SafeGssCredHandle acceptorCredHandle,
ref SafeGssContextHandle acceptContextHandle,
byte[]? inputBytes,
ref byte inputBytes,
int inputLength,
ref GssBuffer token,
out uint retFlags,
[MarshalAs(UnmanagedType.Bool)] out bool isNtlmUsed);

internal static Status AcceptSecContext(
out Status minorStatus,
SafeGssCredHandle acceptorCredHandle,
ref SafeGssContextHandle acceptContextHandle,
ReadOnlySpan<byte> inputBytes,
ref GssBuffer token,
out uint retFlags,
out bool isNtlmUsed)
{
return AcceptSecContext(
out minorStatus,
acceptorCredHandle,
ref acceptContextHandle,
ref MemoryMarshal.GetReference(inputBytes),
inputBytes.Length,
ref token,
out retFlags,
out isNtlmUsed);
}

[LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_DeleteSecContext")]
internal static partial Status DeleteSecContext(
out Status minorStatus,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,16 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref

internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError)
{
return GetOutgoingBlob(incomingBlob, throwOnError, out _);
return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out _);
}

// Accepts an incoming binary security blob and returns an outgoing binary security blob.
internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out statusCode);
}

internal byte[]? GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
byte[]? result = new byte[_tokenSize];

Expand Down
65 changes: 22 additions & 43 deletions src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,18 +330,23 @@ internal void CloseContext()

internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError)
{
return GetOutgoingBlob(incomingBlob, throwOnError, out _);
return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out _);
}

// Accepts an incoming binary security blob and returns an outgoing binary security blob.
internal unsafe byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out statusCode);
}

internal unsafe byte[]? GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
byte[]? outgoingBlob;

// TODO: Logging, validation
if (_negotiateMessage == null)
{
Debug.Assert(incomingBlob == null);
Debug.Assert(incomingBlob.IsEmpty);

_negotiateMessage = new byte[sizeof(NegotiateMessage)];
CreateNtlmNegotiateMessage(_negotiateMessage);
Expand All @@ -351,7 +356,7 @@ internal void CloseContext()
}
else
{
Debug.Assert(incomingBlob != null);
Debug.Assert(!incomingBlob.IsEmpty);

if (!_isSpNego)
{
Expand Down Expand Up @@ -638,23 +643,22 @@ private static byte[] DeriveKey(ReadOnlySpan<byte> exportedSessionKey, ReadOnlyS
}

// This gets decoded byte blob and returns response in binary form.
private unsafe byte[]? ProcessChallenge(byte[] blob, out SecurityStatusPal statusCode)
private unsafe byte[]? ProcessChallenge(ReadOnlySpan<byte> blob, out SecurityStatusPal statusCode)
{
// TODO: Validate size and offsets

ReadOnlySpan<byte> asBytes = new ReadOnlySpan<byte>(blob);
ref readonly ChallengeMessage challengeMessage = ref MemoryMarshal.AsRef<ChallengeMessage>(asBytes.Slice(0, sizeof(ChallengeMessage)));
ref readonly ChallengeMessage challengeMessage = ref MemoryMarshal.AsRef<ChallengeMessage>(blob.Slice(0, sizeof(ChallengeMessage)));

// Verify message type and signature
if (challengeMessage.Header.MessageType != MessageType.Challenge ||
!NtlmHeader.SequenceEqual(asBytes.Slice(0, NtlmHeader.Length)))
!NtlmHeader.SequenceEqual(blob.Slice(0, NtlmHeader.Length)))
{
statusCode = SecurityStatusPalInvalidToken;
return null;
}

Flags flags = BitConverter.IsLittleEndian ? challengeMessage.Flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)challengeMessage.Flags);
ReadOnlySpan<byte> targetName = GetField(challengeMessage.TargetName, asBytes);
ReadOnlySpan<byte> targetName = GetField(challengeMessage.TargetName, blob);

// Only NTLMv2 with MIC is supported
//
Expand All @@ -666,7 +670,7 @@ private static byte[] DeriveKey(ReadOnlySpan<byte> exportedSessionKey, ReadOnlyS
return null;
}

ReadOnlySpan<byte> targetInfo = GetField(challengeMessage.TargetInfo, asBytes);
ReadOnlySpan<byte> targetInfo = GetField(challengeMessage.TargetInfo, blob);
byte[] targetInfoBuffer = ProcessTargetInfo(targetInfo, out DateTime time, out bool hasNbNames);

// If NTLM v2 authentication is used and the CHALLENGE_MESSAGE does not contain both
Expand Down Expand Up @@ -717,7 +721,7 @@ private static byte[] DeriveKey(ReadOnlySpan<byte> exportedSessionKey, ReadOnlyS
payloadOffset += ChallengeResponseLength;

// Create NTLM2 response
ReadOnlySpan<byte> serverChallenge = asBytes.Slice(24, 8);
ReadOnlySpan<byte> serverChallenge = blob.Slice(24, 8);
makeNtlm2ChallengeResponse(time, ntlm2hash, serverChallenge, clientChallenge, targetInfoBuffer, ref response.NtChallengeResponse, payload, ref payloadOffset);
Debug.Assert(payloadOffset == sizeof(AuthenticateMessage) + ChallengeResponseLength + sizeof(NtChallengeResponse) + targetInfoBuffer.Length);

Expand Down Expand Up @@ -872,7 +876,7 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
return writer.Encode();
}

private unsafe byte[]? ProcessSpNegoChallenge(byte[] challenge, out SecurityStatusPal statusCode)
private unsafe byte[]? ProcessSpNegoChallenge(ReadOnlySpan<byte> challenge, out SecurityStatusPal statusCode)
{
NegState state = NegState.Unknown;
string? mech = null;
Expand All @@ -881,8 +885,8 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti

try
{
AsnReader reader = new AsnReader(challenge, AsnEncodingRules.DER);
AsnReader challengeReader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp));
AsnValueReader reader = new AsnValueReader(challenge, AsnEncodingRules.DER);
AsnValueReader challengeReader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp));
reader.ThrowIfNotEmpty();

// NegTokenResp ::= SEQUENCE {
Expand All @@ -904,28 +908,28 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti

if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState)))
{
AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState));
AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState));
state = valueReader.ReadEnumeratedValue<NegState>();
valueReader.ThrowIfNotEmpty();
}

if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech)))
{
AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech));
AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech));
mech = valueReader.ReadObjectIdentifier();
valueReader.ThrowIfNotEmpty();
}

if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken)))
{
AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken));
AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken));
blob = valueReader.ReadOctetString();
valueReader.ThrowIfNotEmpty();
}

if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC)))
{
AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC));
AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC));
mechListMIC = valueReader.ReadOctetString();
valueReader.ThrowIfNotEmpty();
}
Expand All @@ -934,13 +938,8 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
}
catch (AsnContentException)
{
<<<<<<< HEAD
statusCode = SecurityStatusPalInvalidToken;
return null;
=======
statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.IllegalMessage, e);
return Array.Empty<byte>();
>>>>>>> WIP: Add implementation of NegotiateAuthentication
}

if (blob?.Length > 0)
Expand All @@ -949,14 +948,9 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
// message with the challenge blob.
if (!NtlmOid.Equals(mech))
{
<<<<<<< HEAD
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Server requested unknown mechanism {mech}");
statusCode = SecurityStatusPalPackageNotFound;
return null;
=======
statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.PackageNotFound);
return Array.Empty<byte>();
>>>>>>> WIP: Add implementation of NegotiateAuthentication
}

// Process decoded NTLM blob.
Expand Down Expand Up @@ -987,11 +981,7 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
}
}

<<<<<<< HEAD
statusCode = state == NegState.RequestMic ? SecurityStatusPalContinueNeeded : SecurityStatusPalOk;
=======
statusCode = SecurityStatusPalContinueNeeded;
>>>>>>> WIP: Add implementation of NegotiateAuthentication
return writer.Encode();
}
}
Expand All @@ -1000,7 +990,6 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
{
if (_spnegoMechList == null || state != NegState.AcceptCompleted)
{
<<<<<<< HEAD
statusCode = SecurityStatusPalInternalError;
return null;
}
Expand All @@ -1009,15 +998,10 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
{
statusCode = SecurityStatusPalMessageAltered;
return null;
=======
statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.MessageAltered);
return Array.Empty<byte>();
>>>>>>> WIP: Add implementation of NegotiateAuthentication
}
}

IsCompleted = state == NegState.AcceptCompleted || state == NegState.Reject;
<<<<<<< HEAD
statusCode = state switch {
NegState.AcceptCompleted => SecurityStatusPalOk,
NegState.AcceptIncomplete => SecurityStatusPalContinueNeeded,
Expand All @@ -1026,11 +1010,6 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
};

return null;
=======

statusCode = IsCompleted ? SecurityStatusPalOk : new SecurityStatusPal(SecurityStatusPalErrorCode.LogonDenied);
return Array.Empty<byte>();
>>>>>>> WIP: Add implementation of NegotiateAuthentication
}

#pragma warning disable CA1822
Expand Down
Loading