diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 27e12e60bbe0d3..5ec1ee9cbb69bc 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -85,146 +85,6 @@ private static int GssUnwrap( } } - private static bool GssInitSecurityContext( - ref SafeGssContextHandle? context, - SafeGssCredHandle credential, - bool isNtlm, - ChannelBinding? channelBinding, - SafeGssNameHandle? targetName, - Interop.NetSecurityNative.GssFlags inFlags, - ReadOnlySpan buffer, - out byte[]? outputBuffer, - out uint outFlags, - out bool isNtlmUsed) - { - outputBuffer = null; - outFlags = 0; - - // EstablishSecurityContext is called multiple times in a session. - // In each call, we need to pass the context handle from the previous call. - // For the first call, the context handle will be null. - bool newContext = false; - if (context == null) - { - newContext = true; - context = new SafeGssContextHandle(); - } - - Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); - Interop.NetSecurityNative.Status status; - - try - { - Interop.NetSecurityNative.Status minorStatus; - - if (channelBinding != null) - { - // If a TLS channel binding token (cbt) is available then get the pointer - // to the application specific data. - int appDataOffset = Marshal.SizeOf(); - Debug.Assert(appDataOffset < channelBinding.Size); - IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset; - int cbtAppDataSize = channelBinding.Size - appDataOffset; - status = Interop.NetSecurityNative.InitSecContext(out minorStatus, - credential, - ref context, - isNtlm, - cbtAppData, - cbtAppDataSize, - targetName, - (uint)inFlags, - buffer, - ref token, - out outFlags, - out isNtlmUsed); - } - else - { - status = Interop.NetSecurityNative.InitSecContext(out minorStatus, - credential, - ref context, - isNtlm, - targetName, - (uint)inFlags, - buffer, - ref token, - out outFlags, - out isNtlmUsed); - } - - if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) && - (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) - { - if (newContext) - { - context.Dispose(); - context = null; - } - throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); - } - - outputBuffer = token.ToByteArray(); - } - finally - { - token.Dispose(); - } - - return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; - } - - private static bool GssAcceptSecurityContext( - ref SafeGssContextHandle? context, - SafeGssCredHandle credential, - ReadOnlySpan buffer, - out byte[] outputBuffer, - out uint outFlags, - out bool isNtlmUsed) - { - Debug.Assert(credential != null); - - bool newContext = false; - if (context == null) - { - newContext = true; - context = new SafeGssContextHandle(); - } - - Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); - Interop.NetSecurityNative.Status status; - - try - { - Interop.NetSecurityNative.Status minorStatus; - status = Interop.NetSecurityNative.AcceptSecContext(out minorStatus, - credential, - ref context, - buffer, - ref token, - out outFlags, - out isNtlmUsed); - - if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) && - (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) - { - if (newContext) - { - context.Dispose(); - context = null; - } - throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); - } - - outputBuffer = token.ToByteArray(); - } - finally - { - token.Dispose(); - } - - return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; - } - private static string GssGetUser( ref SafeGssContextHandle? context) { @@ -287,27 +147,70 @@ private static SecurityStatusPal EstablishSecurityContext( context = new SafeDeleteNegoContext(credential, targetName!); } + Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); + Interop.NetSecurityNative.Status status; + Interop.NetSecurityNative.Status minorStatus; SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)context; + SafeGssContextHandle contextHandle = negoContext.GssContext; try { Interop.NetSecurityNative.GssFlags inputFlags = ContextFlagsAdapterPal.GetInteropFromContextFlagsPal(inFlags, isServer: false); uint outputFlags; bool isNtlmUsed; - SafeGssContextHandle? contextHandle = negoContext.GssContext; - bool done = GssInitSecurityContext( - ref contextHandle, - credential.GssCredential, - isNtlmOnly, - channelBinding, - negoContext.TargetName, - inputFlags, - incomingBlob, - out resultBuffer, - out outputFlags, - out isNtlmUsed); - - if (done) + + if (channelBinding != null) + { + // If a TLS channel binding token (cbt) is available then get the pointer + // to the application specific data. + int appDataOffset = Marshal.SizeOf(); + Debug.Assert(appDataOffset < channelBinding.Size); + IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset; + int cbtAppDataSize = channelBinding.Size - appDataOffset; + status = Interop.NetSecurityNative.InitSecContext(out minorStatus, + credential.GssCredential, + ref contextHandle, + isNtlmOnly, + cbtAppData, + cbtAppDataSize, + negoContext.TargetName, + (uint)inputFlags, + incomingBlob, + ref token, + out outputFlags, + out isNtlmUsed); + } + else + { + status = Interop.NetSecurityNative.InitSecContext(out minorStatus, + credential.GssCredential, + ref contextHandle, + isNtlmOnly, + negoContext.TargetName, + (uint)inputFlags, + incomingBlob, + ref token, + out outputFlags, + out isNtlmUsed); + } + + if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) && + (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) + { + if (negoContext.GssContext.IsInvalid) + { + context.Dispose(); + } + + Interop.NetSecurityNative.GssApiException gex = new Interop.NetSecurityNative.GssApiException(status, minorStatus); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex); + resultBuffer = Array.Empty(); + return new SecurityStatusPal(GetErrorCode(gex), gex); + } + + resultBuffer = token.ToByteArray(); + + if (status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE) { if (NetEventSource.Log.IsEnabled()) { @@ -322,17 +225,9 @@ private static SecurityStatusPal EstablishSecurityContext( Debug.Assert(resultBuffer != null, "Unexpected null buffer returned by GssApi"); outFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop( (Interop.NetSecurityNative.GssFlags)outputFlags, isServer: false); - Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); - - // Save the inner context handle for further calls to NetSecurity - Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); - if (null == negoContext.GssContext) - { - negoContext.SetGssContext(contextHandle!); - } - SecurityStatusPalErrorCode errorCode = done ? - (negoContext.IsNtlmUsed && resultBuffer.Length > 0 ? SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.CompleteNeeded) : + SecurityStatusPalErrorCode errorCode = status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE ? + SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.ContinueNeeded; return new SecurityStatusPal(errorCode); } @@ -341,6 +236,22 @@ private static SecurityStatusPal EstablishSecurityContext( if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, ex); return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, ex); } + finally + { + token.Dispose(); + + // Save the inner context handle for further calls to NetSecurity + // + // For the first call `negoContext.GssContext` is invalid and we expect the + // inital handle to be returned from InitSecContext. For any subsequent + // call the handle should stay the same or it can be destroyed by the native + // InitSecContext call. + Debug.Assert( + negoContext.GssContext == contextHandle || + negoContext.GssContext.IsInvalid || + contextHandle.IsInvalid); + negoContext.SetGssContext(contextHandle); + } } internal static SecurityStatusPal InitializeSecurityContext( @@ -388,33 +299,44 @@ internal static SecurityStatusPal AcceptSecurityContext( securityContext ??= new SafeDeleteNegoContext((SafeFreeNegoCredentials)credentialsHandle!); SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext; + SafeGssContextHandle contextHandle = negoContext.GssContext; + Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); try { - SafeGssContextHandle? contextHandle = negoContext.GssContext; - bool done = GssAcceptSecurityContext( - ref contextHandle, - negoContext.AcceptorCredential, - incomingBlob, - out resultBlob, - out uint outputFlags, - out bool isNtlmUsed); - - Debug.Assert(resultBlob != null, "Unexpected null buffer returned by GssApi"); - Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); + Interop.NetSecurityNative.Status status; + Interop.NetSecurityNative.Status minorStatus; + status = Interop.NetSecurityNative.AcceptSecContext(out minorStatus, + negoContext.AcceptorCredential, + ref contextHandle, + incomingBlob, + ref token, + out uint outputFlags, + out bool isNtlmUsed); - // Save the inner context handle for further calls to NetSecurity - Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); - if (null == negoContext.GssContext) + if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) && + (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) { - negoContext.SetGssContext(contextHandle!); + if (negoContext.GssContext.IsInvalid) + { + contextHandle.Dispose(); + } + + Interop.NetSecurityNative.GssApiException gex = new Interop.NetSecurityNative.GssApiException(status, minorStatus); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex); + resultBlobLength = 0; + return new SecurityStatusPal(GetErrorCode(gex), gex); } + resultBlob = token.ToByteArray(); + + Debug.Assert(resultBlob != null, "Unexpected null buffer returned by GssApi"); + contextFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop( (Interop.NetSecurityNative.GssFlags)outputFlags, isServer: true); resultBlobLength = resultBlob.Length; SecurityStatusPalErrorCode errorCode; - if (done) + if (status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE) { if (NetEventSource.Log.IsEnabled()) { @@ -423,7 +345,7 @@ internal static SecurityStatusPal AcceptSecurityContext( } negoContext.SetAuthenticationPackage(isNtlmUsed); - errorCode = (isNtlmUsed && resultBlob.Length > 0) ? SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.CompleteNeeded; + errorCode = SecurityStatusPalErrorCode.OK; } else { @@ -432,18 +354,28 @@ internal static SecurityStatusPal AcceptSecurityContext( return new SecurityStatusPal(errorCode); } - catch (Interop.NetSecurityNative.GssApiException gex) - { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex); - resultBlobLength = 0; - return new SecurityStatusPal(GetErrorCode(gex), gex); - } catch (Exception ex) { if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, ex); resultBlobLength = 0; return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, ex); } + finally + { + token.Dispose(); + + // Save the inner context handle for further calls to NetSecurity + // + // For the first call `negoContext.GssContext` is invalid and we expect the + // inital handle to be returned from AcceptSecContext. For any subsequent + // call the handle should stay the same or it can be destroyed by the native + // AcceptSecContext call. + Debug.Assert( + negoContext.GssContext == contextHandle || + negoContext.GssContext.IsInvalid || + contextHandle.IsInvalid); + negoContext.SetGssContext(contextHandle); + } } // https://www.gnu.org/software/gss/reference/gss.pdf (page 25) diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs index 123a28ace0f498..c3316ee343538b 100644 --- a/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs +++ b/src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs @@ -13,7 +13,7 @@ internal sealed class SafeDeleteNegoContext : SafeDeleteContext { private SafeGssCredHandle? _acceptorCredential; private SafeGssNameHandle? _targetName; - private SafeGssContextHandle? _context; + private SafeGssContextHandle _context; private bool _isNtlmUsed; public SafeGssCredHandle AcceptorCredential @@ -36,7 +36,7 @@ public bool IsNtlmUsed get { return _isNtlmUsed; } } - public SafeGssContextHandle? GssContext + public SafeGssContextHandle GssContext { get { return _context; } } @@ -45,6 +45,7 @@ public SafeDeleteNegoContext(SafeFreeNegoCredentials credential) : base(credential) { Debug.Assert((null != credential), "Null credential in SafeDeleteNegoContext"); + _context = new SafeGssContextHandle(); } public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetName) @@ -53,6 +54,7 @@ public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetNa try { _targetName = SafeGssNameHandle.CreateTarget(targetName); + _context = new SafeGssContextHandle(); } catch { @@ -63,7 +65,6 @@ public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetNa public void SetGssContext(SafeGssContextHandle context) { - Debug.Assert(context != null && !context.IsInvalid, "Invalid context passed to SafeDeleteNegoContext"); _context = context; } @@ -76,11 +77,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - if (null != _context) - { - _context.Dispose(); - _context = null; - } + _context.Dispose(); if (_targetName != null) {