-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Introducing native shims for libgssapi in order to support NegotiateStream #5774
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
| using System.Text; | ||
| using Microsoft.Win32.SafeHandles; | ||
|
|
||
| internal static partial class Interop | ||
| { | ||
| internal static class GssApi | ||
| { | ||
| internal static bool EstablishSecurityContext( | ||
| ref SafeGssContextHandle context, | ||
| SafeGssCredHandle credential, | ||
| bool isNtlm, | ||
| SafeGssNameHandle targetName, | ||
| Interop.NetSecurityNative.GssFlags inFlags, | ||
| byte[] buffer, | ||
| out byte[] outputBuffer, | ||
| out uint outFlags) | ||
| { | ||
| 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. | ||
| if (context == null) | ||
| { | ||
| context = new SafeGssContextHandle(); | ||
| } | ||
|
|
||
| Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); | ||
| Interop.NetSecurityNative.Status status; | ||
|
|
||
| try | ||
| { | ||
| Interop.NetSecurityNative.Status minorStatus; | ||
| status = NetSecurityNative.InitSecContext(out minorStatus, | ||
| credential, | ||
| ref context, | ||
| isNtlm, | ||
| targetName, | ||
| (uint)inFlags, | ||
| buffer, | ||
| (buffer == null) ? 0 : buffer.Length, | ||
| ref token, | ||
| out outFlags); | ||
|
|
||
| if ((status != NetSecurityNative.Status.GSS_S_COMPLETE) && (status != NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) | ||
| { | ||
| throw new NetSecurityNative.GssApiException(status, minorStatus); | ||
| } | ||
|
|
||
| outputBuffer = token.ToByteArray(); | ||
| } | ||
| finally | ||
| { | ||
| token.Dispose(); | ||
| } | ||
|
|
||
| return status == NetSecurityNative.Status.GSS_S_COMPLETE; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this just return the status and let the caller compare it? I'm just wondering if this fails, if an exception is going to be generated or something, would the status be useful as part of that exception?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return value of The error case has already been handled in line#53 above .
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see, makes sense. |
||
| } | ||
|
|
||
| internal static byte[] Encrypt( | ||
| SafeGssContextHandle context, | ||
| bool encrypt, | ||
| byte[] buffer, | ||
| int offset, | ||
| int count) | ||
| { | ||
| Debug.Assert((buffer != null) && (buffer.Length > 0), "Invalid input buffer passed to Encrypt"); | ||
| Debug.Assert((offset >= 0) && (offset < buffer.Length), "Invalid input offset passed to Encrypt"); | ||
| Debug.Assert((count > 0) && (count <= (buffer.Length - offset)), "Invalid input count passed to Encrypt"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. count == 0 is invalid? If it can actually be 0, then both this and the first assert above need to be changed.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (and the second assert, since offset could be exactly buffer.Length in the case of a zero-count)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Interop.GssApi code count == 0 is invalid. We do not want to call the native layer with zero length.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm confused. This code is Interop.GssAPI. But this assert will fail if count == 0. What am I missing?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops.. sorry. I updated the comment. I meant, it is invalid.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. |
||
|
|
||
| Interop.NetSecurityNative.GssBuffer encryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); | ||
| try | ||
| { | ||
|
|
||
| NetSecurityNative.Status minorStatus; | ||
| NetSecurityNative.Status status = NetSecurityNative.Wrap(out minorStatus, context, encrypt, buffer, offset, count, ref encryptedBuffer); | ||
| if (status != NetSecurityNative.Status.GSS_S_COMPLETE) | ||
| { | ||
| throw new NetSecurityNative.GssApiException(status, minorStatus); | ||
| } | ||
|
|
||
| return encryptedBuffer.ToByteArray(); | ||
| } | ||
| finally | ||
| { | ||
| encryptedBuffer.Dispose(); | ||
| } | ||
| } | ||
|
|
||
| internal static int Decrypt( | ||
| SafeGssContextHandle context, | ||
| byte[] buffer, | ||
| int offset, | ||
| int count) | ||
| { | ||
| Debug.Assert((buffer != null) && (buffer.Length > 0), "Invalid input buffer passed to Decrypt"); | ||
| Debug.Assert((offset >= 0) && (offset <= buffer.Length), "Invalid input offset passed to Decrypt"); | ||
| Debug.Assert((count > 0) && (count <= (buffer.Length - offset)), "Invalid input count passed to Decrypt"); | ||
|
|
||
| Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); | ||
| try | ||
| { | ||
| NetSecurityNative.Status minorStatus; | ||
| NetSecurityNative.Status status = NetSecurityNative.Unwrap(out minorStatus, context, buffer, offset, count, ref decryptedBuffer); | ||
| if (status != NetSecurityNative.Status.GSS_S_COMPLETE) | ||
| { | ||
| throw new NetSecurityNative.GssApiException(status, minorStatus); | ||
| } | ||
|
|
||
| return decryptedBuffer.Copy(buffer, offset); | ||
| } | ||
| finally | ||
| { | ||
| decryptedBuffer.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| internal static partial class Interop | ||
| { | ||
| internal static partial class NetSecurityNative | ||
| { | ||
| internal sealed class GssApiException : Exception | ||
| { | ||
| private readonly Status _minorStatus; | ||
|
|
||
| public Status MinorStatus | ||
| { | ||
| get { return _minorStatus; } | ||
| } | ||
|
|
||
| public GssApiException(string message) : base(message) | ||
| { | ||
| } | ||
|
|
||
| public GssApiException(Status majorStatus, Status minorStatus) | ||
| : base(GetGssApiDisplayStatus(majorStatus, minorStatus)) | ||
| { | ||
| HResult = (int)majorStatus; | ||
| _minorStatus = minorStatus; | ||
| } | ||
|
|
||
| private static string GetGssApiDisplayStatus(Status majorStatus, Status minorStatus) | ||
| { | ||
| string majorError = GetGssApiDisplayStatus(majorStatus, false); | ||
| string minorError = GetGssApiDisplayStatus(minorStatus, true); | ||
|
|
||
| return (majorError != null && minorError != null) ? | ||
| SR.Format(SR.net_gssapi_operation_failed_detailed, majorError, minorError) : | ||
| SR.Format(SR.net_gssapi_operation_failed, majorStatus.ToString("x8"), minorStatus.ToString("x8")); | ||
| } | ||
|
|
||
| private static string GetGssApiDisplayStatus(Status status, bool isMinor) | ||
| { | ||
| GssBuffer displayBuffer = default(GssBuffer); | ||
| Interop.NetSecurityNative.Status minStat; | ||
|
|
||
| try | ||
| { | ||
| Interop.NetSecurityNative.Status displayCallStatus = DisplayStatus(out minStat, status, isMinor, ref displayBuffer); | ||
| return (Status.GSS_S_COMPLETE != displayCallStatus) ? null : Marshal.PtrToStringAnsi(displayBuffer.data); | ||
| } | ||
| finally | ||
| { | ||
| displayBuffer.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
| using Microsoft.Win32.SafeHandles; | ||
|
|
||
| internal static partial class Interop | ||
| { | ||
| internal static partial class NetSecurityNative | ||
| { | ||
| [StructLayout(LayoutKind.Sequential)] | ||
| internal unsafe struct GssBuffer : IDisposable | ||
| { | ||
| internal UInt64 length; | ||
| internal IntPtr data; | ||
|
|
||
| internal int Copy(byte[] destination, int offset) | ||
| { | ||
| Debug.Assert(destination != null, "target destination cannot be null"); | ||
| Debug.Assert(offset >= 0 && offset < destination.Length, "invalid offset " + offset); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically offset could be exactly destination.Length, if this.length was 0. |
||
|
|
||
| if (data == IntPtr.Zero || length == 0) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| int bufferLength = Convert.ToInt32(length); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will throw if length is greater than Int32.MaxValue; is that intentional? I assume so, just verifying.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it was intentional. The corresponding type on the native side is
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, this is better, though see my comment below about whether it'd be even better to handle it with the same exception below. |
||
| int available = destination.Length - offset; // amount of space in the given buffer | ||
| if (bufferLength > available) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than throwing above, should it not throw and just let this case of "net_context_buffer_too_small" handle it? That would seem to be the more descriptive error, rather than a generic overflow exception.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. I need to introduce a new error message here, though. Right? Or modify
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's not possible to get this, then the Convert with the overflow exception is fine. |
||
| { | ||
| throw new NetSecurityNative.GssApiException(SR.Format(SR.net_context_buffer_too_small, bufferLength, available)); | ||
| } | ||
|
|
||
| Marshal.Copy(data, destination, offset, bufferLength); | ||
| return bufferLength; | ||
| } | ||
|
|
||
| internal byte[] ToByteArray() | ||
| { | ||
| if (data == IntPtr.Zero || length == 0) | ||
| { | ||
| return Array.Empty<byte>(); | ||
| } | ||
|
|
||
| int bufferLength = Convert.ToInt32(length); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question as earlier about whether the overflow exception here is intentional. |
||
| byte[] destination = new byte[bufferLength]; | ||
| Marshal.Copy(data, destination, 0, bufferLength); | ||
| return destination; | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| if (data != IntPtr.Zero) | ||
| { | ||
| Interop.NetSecurityNative.ReleaseGssBuffer(data, length); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set data to IntPtr.Zero here so that calling Dispose multiple times is still safe.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ok. I thought we are not supposed to mutate the field of a struct.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can. Though it won't provide perfect protection against a double dispose, as two copies of the struct will be distinct. Won't hurt, though.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is true for any any
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. My point is that nulling out the field won't hurt, it could help, but it won't definitively prevent double-free. |
||
| data = IntPtr.Zero; | ||
| } | ||
|
|
||
| length = 0; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
| using System.Text; | ||
| using Microsoft.Win32.SafeHandles; | ||
|
|
||
| internal static partial class Interop | ||
| { | ||
| internal static partial class NetSecurityNative | ||
| { | ||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ReleaseGssBuffer")] | ||
| internal static extern void ReleaseGssBuffer( | ||
| IntPtr bufferPtr, | ||
| UInt64 length); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_DisplayStatus")] | ||
| internal static extern Status DisplayStatus( | ||
| out Status minorStatus, | ||
| Status statusValue, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. majorStatus?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This value can be either a major status or a minor status. A single API for both major status and the minor status.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Ok, thanks. |
||
| bool isGssMechCode, | ||
| ref GssBuffer buffer); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurityNative_ImportUserName")] | ||
| internal static extern Status ImportUserName( | ||
| out Status minorStatus, | ||
| string inputName, | ||
| int inputNameLen, | ||
| out SafeGssNameHandle outputName); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurityNative_ImportPrincipalName")] | ||
| internal static extern Status ImportPrincipalName( | ||
| out Status minorStatus, | ||
| string inputName, | ||
| int inputNameLen, | ||
| out SafeGssNameHandle outputName); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ReleaseName")] | ||
| internal static extern Status ReleaseName( | ||
| out Status minorStatus, | ||
| ref IntPtr inputName); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitiateCredSpNego")] | ||
| internal static extern Status InitiateCredSpNego( | ||
| out Status minorStatus, | ||
| SafeGssNameHandle desiredName, | ||
| out SafeGssCredHandle outputCredHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurityNative_InitiateCredWithPassword")] | ||
| internal static extern Status InitiateCredWithPassword( | ||
| out Status minorStatus, | ||
| SafeGssNameHandle desiredName, | ||
| string password, | ||
| int passwordLen, | ||
| out SafeGssCredHandle outputCredHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ReleaseCred")] | ||
| internal static extern Status ReleaseCred( | ||
| out Status minorStatus, | ||
| ref IntPtr credHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContext")] | ||
| internal static extern Status InitSecContext( | ||
| out Status minorStatus, | ||
| SafeGssCredHandle initiatorCredHandle, | ||
| ref SafeGssContextHandle contextHandle, | ||
| bool isNtlm, | ||
| SafeGssNameHandle targetName, | ||
| uint reqFlags, | ||
| byte[] inputBytes, | ||
| int inputLength, | ||
| ref GssBuffer token, | ||
| out uint retFlags); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_DeleteSecContext")] | ||
| internal static extern Status DeleteSecContext( | ||
| out Status minorStatus, | ||
| ref IntPtr contextHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_Wrap")] | ||
| internal static extern Status Wrap( | ||
| out Status minorStatus, | ||
| SafeGssContextHandle contextHandle, | ||
| bool isEncrypt, | ||
| byte[] inputBytes, | ||
| int offset, | ||
| int count, | ||
| ref GssBuffer outBuffer); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_Unwrap")] | ||
| internal static extern Status Unwrap( | ||
| out Status minorStatus, | ||
| SafeGssContextHandle contextHandle, | ||
| byte[] inputBytes, | ||
| int offset, | ||
| int count, | ||
| ref GssBuffer outBuffer); | ||
|
|
||
| internal enum Status : uint | ||
| { | ||
| GSS_S_COMPLETE = 0, | ||
| GSS_S_CONTINUE_NEEDED = 1 | ||
| } | ||
|
|
||
| [Flags] | ||
| internal enum GssFlags : uint | ||
| { | ||
| GSS_C_DELEG_FLAG = 0x1, | ||
| GSS_C_MUTUAL_FLAG = 0x2, | ||
| GSS_C_REPLAY_FLAG = 0x4, | ||
| GSS_C_SEQUENCE_FLAG = 0x8, | ||
| GSS_C_CONF_FLAG = 0x10, | ||
| GSS_C_INTEG_FLAG = 0x20, | ||
| GSS_C_ANON_FLAG = 0x40, | ||
| GSS_C_PROT_READY_FLAG = 0x80, | ||
| GSS_C_TRANS_FLAG = 0x100, | ||
| GSS_C_DCE_STYLE = 0x1000, | ||
| GSS_C_IDENTIFY_FLAG = 0x2000, | ||
| GSS_C_EXTENDED_ERROR_FLAG = 0x4000, | ||
| GSS_C_DELEG_POLICY_FLAG = 0x8000 | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I seem to remember asking about this before, but maybe I didn't. In what situation will context be null? In what situation won't it be null? It seems odd we'd have an API like this that's used in both situations... is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The semantics of the underlying
gss_init_sec_contextare such that we need to keep calling it with output from previous call. The very first time, we will call it with null.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Can you add a comment explaining that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please check if the explanation is clear enough?