Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
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 @@ -9,6 +9,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,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();
Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Contributor Author

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_context are such that we need to keep calling it with output from previous call. The very first time, we will call it with null.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Contributor Author

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?

}

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of true indicates handshake is done. While, false indicates that it is in progress. This is a part of the handshake between the server and client.

The error case has already been handled in line#53 above .

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Interop.GssApi code count == 0 is valid

I'm confused. This code is Interop.GssAPI. But this assert will fail if count == 0. What am I missing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops.. sorry. I updated the comment. I meant, it is invalid.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was intentional. The corresponding type on the native side is size_t. It seemed better to throw here in the managed code than assert it in the native code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 net_context_buffer_too_small. Currently it is for buffers provided by the user (where we indicate the difference in the lengths).
But please note that in general, this cannot happen: we get two types of buffers from the native layer - one which are tokens during handshake establishment and two, the encrypted/decrypted messages. The former have a small, fixed size. The size of the latter is determined by the size of the input that we give. Since we cannot give as input any buffer of length greater than Int32.MaxValue, we cannot reasonably see this error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok. I thought we are not supposed to mutate the field of a struct.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is true for any any struct, Right? I made sure that this struct inherits IDisposable so that user is warned of the issue. Is there anything else I need to do here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

majorStatus?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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