Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/Common/src/Interop/Unix/Interop.Libraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ private static partial class Libraries
// Shims
internal const string SystemNative = "System.Native";
internal const string HttpNative = "System.Net.Http.Native";
internal const string NetSecurityNative = "System.Net.Security.Native";
internal const string CryptoNative = "System.Security.Cryptography.Native";
internal const string GlobalizationNative = "System.Globalization.Native";
internal const string CompressionNative = "System.IO.Compression.Native";
Expand Down
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,
Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor

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.

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)
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
}
}
}
Loading