-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Interop Layer for supporting xplat NegotiateStream #5500
Changes from all commits
c4a4aa7
b1d2989
d3eb606
f7cfe62
d1ce84d
3642040
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,125 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license 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.NetSecurity.GssFlags inFlags, | ||
| byte[] buffer, | ||
| out byte[] outputBuffer, | ||
| out uint outFlags) | ||
| { | ||
| outputBuffer = null; | ||
| outFlags = 0; | ||
|
|
||
| if (context == null) | ||
| { | ||
| context = new SafeGssContextHandle(); | ||
| } | ||
|
|
||
| SafeGssBufferHandle outputToken; | ||
| NetSecurity.Status status; | ||
| NetSecurity.Status minorStatus; | ||
| int outputLength; | ||
| status = NetSecurity.InitSecContext(out minorStatus, | ||
| credential, | ||
| ref context, | ||
| isNtlm, | ||
| targetName, | ||
| (uint)inFlags, | ||
| buffer, | ||
| (buffer == null) ? 0 : buffer.Length, | ||
| out outputToken, | ||
| out outputLength, | ||
| out outFlags); | ||
|
|
||
| using (outputToken) | ||
| { | ||
| if ((status != NetSecurity.Status.GSS_S_COMPLETE) && (status != NetSecurity.Status.GSS_S_CONTINUE_NEEDED)) | ||
| { | ||
| throw new NetSecurity.GssApiException(SR.net_context_establishment_failed, status, minorStatus); | ||
| } | ||
|
|
||
| outputBuffer = new byte[outputLength]; | ||
| outputToken.Copy(outputBuffer, 0); | ||
| } | ||
| return status == NetSecurity.Status.GSS_S_COMPLETE; | ||
| } | ||
|
|
||
| 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"); | ||
|
|
||
| SafeGssBufferHandle outputToken; | ||
| int msgLength; | ||
| NetSecurity.Status minorStatus; | ||
| NetSecurity.Status status = NetSecurity.Wrap(out minorStatus, context, encrypt, buffer, offset, count, out outputToken, out msgLength); | ||
| using (outputToken) | ||
| { | ||
| if (status != NetSecurity.Status.GSS_S_COMPLETE) | ||
| { | ||
| throw new NetSecurity.GssApiException(SR.net_context_wrap_failed, status, minorStatus); | ||
| } | ||
|
|
||
| byte[] outputBuffer = new byte[msgLength]; | ||
| outputToken.Copy(outputBuffer, 0); | ||
| return outputBuffer; | ||
| } | ||
| } | ||
|
|
||
| internal static int Decrypt( | ||
| SafeGssContextHandle context, | ||
| byte[] buffer, | ||
| int offset, | ||
| int count) | ||
| { | ||
|
Contributor
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. It's not clear to me what this does; does it decrypt the buffer in place? Encrypt seems to clearly move data from one buffer to another; why is Decrypt so different? I'm probably just missing context here...maybe comments would be helpful.
Contributor
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.. I agree comments would help. This does in-place decryption because the Interop layer is currently scoped to the exact requirements of System.Net.Security.NegotiateStream. The logic of NegotiateStream is to allocate a buffer for Encrypt and do in-place decryption for Decrypt |
||
| 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"); | ||
|
|
||
| SafeGssBufferHandle outputToken; | ||
| int msgLength; | ||
| NetSecurity.Status minorStatus; | ||
| NetSecurity.Status status = NetSecurity.Unwrap(out minorStatus, context, buffer, offset, count, out outputToken, out msgLength); | ||
|
|
||
| using (outputToken) | ||
| { | ||
| if (status != NetSecurity.Status.GSS_S_COMPLETE) | ||
| { | ||
| throw new NetSecurity.GssApiException(SR.net_context_unwrap_failed, status, minorStatus); | ||
| } | ||
|
|
||
| int length = msgLength - offset; | ||
| if (msgLength > length) | ||
| { | ||
| throw new NetSecurity.GssApiException(SR.Format(SR.net_context_buffer_too_small, msgLength, length)); | ||
| } | ||
|
|
||
| outputToken.Copy(buffer, offset); | ||
| } | ||
|
|
||
| return msgLength; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
| using Microsoft.Win32.SafeHandles; | ||
|
|
||
| internal static partial class Interop | ||
| { | ||
| internal static partial class NetSecurity | ||
| { | ||
| internal sealed class GssApiException : Exception | ||
| { | ||
| private Status _minorStatus; | ||
|
|
||
| public Status MinorStatus | ||
| { | ||
| get { return _minorStatus; } | ||
| } | ||
|
|
||
| public GssApiException(string message) : base(message) | ||
| { | ||
| } | ||
|
|
||
| public GssApiException(Status majorStatus, Status minorStatus) | ||
| : this(SR.Format(SR.net_gssapi_operation_failed, majorStatus, minorStatus), majorStatus, minorStatus) | ||
| { | ||
| } | ||
|
|
||
| public GssApiException(string message, Status majorStatus, Status minorStatus) | ||
| : this(message) | ||
| { | ||
| HResult = (int)majorStatus; | ||
| _minorStatus = minorStatus; | ||
| } | ||
|
|
||
| public static void AssertOrThrowIfError(string message, Status majorStatus, Status minorStatus) | ||
| { | ||
| if (majorStatus != Status.GSS_S_COMPLETE) | ||
| { | ||
| GssApiException ex = new GssApiException(majorStatus, minorStatus); | ||
| throw ex; | ||
| } | ||
| } | ||
|
|
||
| #if DEBUG | ||
| public override string ToString() | ||
| { | ||
| return Message + "\n GSSAPI status: " + GetGssApiDisplayStatus((Status)HResult, _minorStatus); | ||
| } | ||
|
|
||
| private static string GetGssApiDisplayStatus(Status majorStatus, Status minorStatus) | ||
| { | ||
| KeyValuePair<Status, bool>[] statusArr = { new KeyValuePair<Status, bool>(majorStatus, false), new KeyValuePair<Status, bool>(minorStatus, true) }; | ||
| int length = statusArr.Length; | ||
| string[] msgStrings = new string[length]; | ||
|
|
||
| for (int i = 0; i < length; i++) | ||
| { | ||
| SafeGssBufferHandle msgBuffer; | ||
| Interop.NetSecurity.Status minStat; | ||
| int statusLength; | ||
| if (Status.GSS_S_COMPLETE != DisplayStatus(out minStat, statusArr[i].Key, statusArr[i].Value, out msgBuffer, out statusLength)) | ||
| { | ||
| msgStrings[i] = "Unknown Error"; | ||
| continue; | ||
| } | ||
| using (msgBuffer) | ||
| { | ||
| byte[] statusBytes = new byte[statusLength]; | ||
| msgBuffer.Copy(statusBytes, 0); | ||
| var encoding = new System.Text.ASCIIEncoding(); | ||
| msgStrings[i] = encoding.GetString(statusBytes); | ||
| } | ||
| } | ||
|
|
||
| return msgStrings[0] + " (" + msgStrings[1] + ") \nStatus: " + majorStatus.ToString("x8") + " (" + minorStatus.ToString("x8") + ")"; | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license 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 NetSecurity | ||
| { | ||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_ReleaseBuffer")] | ||
| internal static extern Status ReleaseBuffer( | ||
| out Status minorStatus, | ||
| IntPtr bufferHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_CopyBuffer")] | ||
| internal static extern Status CopyBuffer( | ||
| SafeGssBufferHandle handle, | ||
| byte[] buffer, | ||
| int capacity, | ||
| int offset); | ||
|
Contributor
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. It is better to pass the capacity also and add an assert in unmanaged code to verify there is enough to copy gss_buffer_desc.length bytes |
||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_DisplayStatus")] | ||
| internal static extern Status DisplayStatus( | ||
| out Status minorStatus, | ||
| Status statusValue, | ||
| bool isGssMechCode, | ||
| out SafeGssBufferHandle bufferHandle, | ||
| out int statusLength); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurity_ImportUserName")] | ||
| internal static extern Status ImportUserName( | ||
| out Status minorStatus, | ||
| string inputName, | ||
| int inputNameLen, | ||
| out SafeGssNameHandle outputName); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurity_ImportPrincipalName")] | ||
| internal static extern Status ImportPrincipalName( | ||
| out Status minorStatus, | ||
| string inputName, | ||
| int inputNameLen, | ||
| out SafeGssNameHandle outputName); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_ReleaseName")] | ||
| internal static extern Status ReleaseName( | ||
| out Status minorStatus, | ||
| ref IntPtr inputName); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_AcquireCredSpNego")] | ||
| internal static extern Status AcquireCredSpNego( | ||
| out Status minorStatus, | ||
| SafeGssNameHandle desiredName, | ||
| bool isInitiate, | ||
| out SafeGssCredHandle outputCredHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurity_AcquireCredWithPassword")] | ||
| internal static extern Status AcquireCredWithPassword( | ||
| out Status minorStatus, | ||
| SafeGssNameHandle desiredName, | ||
| string password, | ||
| int passwordLen, | ||
| bool isInitiate, | ||
| out SafeGssCredHandle outputCredHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_ReleaseCred")] | ||
| internal static extern Status ReleaseCred( | ||
| out Status minorStatus, | ||
| ref IntPtr credHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_InitSecContext")] | ||
| internal static extern Status InitSecContext( | ||
| out Status minorStatus, | ||
| SafeGssCredHandle initiatorCredHandle, | ||
| ref SafeGssContextHandle contextHandle, | ||
| bool isNtlm, | ||
| SafeGssNameHandle targetName, | ||
| uint reqFlags, | ||
| byte[] inputBytes, | ||
| int inputLength, | ||
| out SafeGssBufferHandle outputToken, | ||
| out int outputLength, | ||
| out uint retFlags); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_DeleteSecContext")] | ||
| internal static extern Status DeleteSecContext( | ||
| out Status minorStatus, | ||
| ref IntPtr contextHandle); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_Wrap")] | ||
| internal static extern Status Wrap( | ||
| out Status minorStatus, | ||
| SafeGssContextHandle contextHandle, | ||
| bool isEncrypt, | ||
| byte[] inputBytes, | ||
| int offset, | ||
| int count, | ||
| out SafeGssBufferHandle outputMessageBuffer, | ||
| out int outMsgLength); | ||
|
|
||
| [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_Unwrap")] | ||
| internal static extern Status Unwrap( | ||
| out Status minorStatus, | ||
| SafeGssContextHandle contextHandle, | ||
| byte[] inputBytes, | ||
| int offset, | ||
| int count, | ||
| out SafeGssBufferHandle outputMessageBuffer, | ||
| out int outMsgLength); | ||
|
|
||
| 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 think I'd rather see this either a) always allocate the handle (maybe rename to CreateSecurityContext) or b) always initialize an existing handle (rename to InitializeSecurityContext?). Having this be dual-purpose like this seems like it will lead to confusion.
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.
CreateSecurityContext is not appropriate because the way this is going to be consumed is similar to the SSPI model in Windows. There will be several calls to InitializeSecurityContext (or Establish.. in our case) and the first call allocates the context. So either we need 2 methods or let the caller handle context creation. I think instead of changing the logic in this PR, we should wait till the PR for the consuming code is sent (expected in a few days). That would help in arriving at the right pattern for this.