Updated comments.

This commit is contained in:
antiduh
2014-07-03 03:24:16 +00:00
parent 3611034f82
commit 9636bb44b5
14 changed files with 326 additions and 35 deletions

View File

@@ -7,16 +7,31 @@ using System.Threading.Tasks;
namespace NSspi
{
/// <summary>
/// Reads and writes value types to byte arrays with explicit endianness.
/// </summary>
public static class ByteWriter
{
// Big endian: Most significant byte at lowest address in memory.
/// <summary>
/// Writes a 2-byte signed integer to the buffer in big-endian format.
/// </summary>
/// <param name="value">The value to write to the buffer.</param>
/// <param name="buffer">The buffer to write to.</param>
/// <param name="position">The index of the first byte to write to.</param>
public static void WriteInt16_BE( Int16 value, byte[] buffer, int position )
{
buffer[position + 0] = (byte)( value >> 8 );
buffer[position + 1] = (byte)( value );
}
/// <summary>
/// Writes a 4-byte signed integer to the buffer in big-endian format.
/// </summary>
/// <param name="value">The value to write to the buffer.</param>
/// <param name="buffer">The buffer to write to.</param>
/// <param name="position">The index of the first byte to write to.</param>
public static void WriteInt32_BE( Int32 value, byte[] buffer, int position )
{
buffer[position + 0] = (byte)( value >> 24 );
@@ -26,6 +41,13 @@ namespace NSspi
}
/// <summary>
/// Reads a 2-byte signed integer that is stored in the buffer in big-endian format.
/// The returned value is in the native endianness.
/// </summary>
/// <param name="buffer">The buffer to read.</param>
/// <param name="position">The index of the first byte to read.</param>
/// <returns></returns>
public static Int16 ReadInt16_BE( byte[] buffer, int position )
{
Int16 value;
@@ -36,6 +58,13 @@ namespace NSspi
return value;
}
/// <summary>
/// Reads a 4-byte signed integer that is stored in the buffer in big-endian format.
/// The returned value is in the native endianness.
/// </summary>
/// <param name="buffer">The buffer to read.</param>
/// <param name="position">The index of the first byte to read.</param>
/// <returns></returns>
public static Int32 ReadInt32_BE( byte[] buffer, int position )
{
Int32 value;
@@ -47,6 +76,5 @@ namespace NSspi
return value;
}
}
}

View File

@@ -14,7 +14,7 @@ namespace NSspi.Credentials.Credentials
internal struct QueryNameAttribCarrier
{
/// <summary>
/// A pointer to a null-terminated ascii c-string containing the principle name
/// A pointer to a null-terminated ascii-encoded containing the principle name
/// associated with a credential
/// </summary>
public IntPtr Name;

View File

@@ -12,6 +12,11 @@ namespace NSspi.Credentials
/// </summary>
public class ServerCredential : CurrentCredential
{
/// <summary>
/// Initializes a new instance of the ServerCredential class, acquiring credentials from
/// the current thread's security context.
/// </summary>
/// <param name="package">The name of the security package to obtain credentials from.</param>
public ServerCredential( string package )
: base( package, CredentialUse.Inbound )
{

View File

@@ -11,20 +11,6 @@ namespace NSspi
{
internal static class NativeMethods
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374713(v=vs.85).aspx
// The REMSSPI sample:
// A C++ pure client/server example:
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380536(v=vs.85).aspx
/*
SECURITY_STATUS SEC_Entry FreeContextBuffer(
_In_ PVOID pvContextBuffer
);
*/
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success)]
[DllImport( "Secur32.dll", EntryPoint = "FreeContextBuffer", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus FreeContextBuffer( IntPtr buffer );

View File

@@ -6,12 +6,24 @@ using System.Threading.Tasks;
namespace NSspi
{
/// <summary>
/// Provides canonical names for security pacakges.
/// </summary>
public static class PackageNames
{
/// <summary>
/// Indicates the Negotiate security package.
/// </summary>
public const string Negotiate = "Negotiate";
/// <summary>
/// Indicates the Kerberos security package.
/// </summary>
public const string Kerberos = "Kerberos";
/// <summary>
/// Indicates the NTLM security package.
/// </summary>
public const string Ntlm = "NTLM";
}
}

View File

@@ -8,8 +8,16 @@ using System.Threading.Tasks;
namespace NSspi
{
/// <summary>
/// Queries information about security packages.
/// </summary>
public static class PackageSupport
{
/// <summary>
/// Returns the properties of the named package.
/// </summary>
/// <param name="packageName">The name of the package.</param>
/// <returns></returns>
public static SecPkgInfo GetPackageCapabilities( string packageName )
{
SecPkgInfo info;
@@ -52,6 +60,10 @@ namespace NSspi
return info;
}
/// <summary>
/// Returns a list of all known security package providers and their properties.
/// </summary>
/// <returns></returns>
public static SecPkgInfo[] EnumeratePackages()
{
SecurityStatus status = SecurityStatus.InternalError;

View File

@@ -7,19 +7,51 @@ using System.Threading.Tasks;
namespace NSspi.Buffers
{
/// <summary>
/// Represents a native SecureBuffer structure, which is used for communicating
/// buffers to the native APIs.
/// </summary>
[StructLayout( LayoutKind.Sequential )]
internal struct SecureBufferInternal
{
/// <summary>
/// When provided to the native API, the total number of bytes available in the buffer.
/// On return from the native API, the number of bytes that were filled or used by the
/// native API.
/// </summary>
public int Count;
/// <summary>
/// The type or purpose of the buffer.
/// </summary>
public BufferType Type;
// A pointer to a byte[]
/// <summary>
/// An pointer to a pinned byte[] buffer.
/// </summary>
public IntPtr Buffer;
}
/// <summary>
/// Stores buffers to provide tokens and data to the native SSPI APIs.
/// </summary>
/// <remarks>The buffer is translated into a SecureBufferInternal for the actual call.
/// To keep the call setup code simple, and to centralize the buffer pinning code,
/// this class stores and returns buffers as regular byte arrays. The buffer
/// pinning support code in SecureBufferAdapter handles conversion to SecureBufferInternal
/// for pass to the managed api, as well as pinning relevant chunks of memory.
///
/// Furthermore, the native API may not use the entire buffer, and so a mechanism
/// is needed to communicate the usage of the buffer separate from the length
/// of the buffer.</remarks>
internal class SecureBuffer
{
/// <summary>
/// Initializes a new instance of the SecureBuffer class.
/// </summary>
/// <param name="buffer">The buffer to wrap.</param>
/// <param name="type">The type or purpose of the buffer, for purposes of
/// invoking the native API.</param>
public SecureBuffer( byte[] buffer, BufferType type )
{
this.Buffer = buffer;
@@ -27,10 +59,20 @@ namespace NSspi.Buffers
this.Length = this.Buffer.Length;
}
/// <summary>
/// The type or purposes of the API, for invoking the native API.
/// </summary>
public BufferType Type { get; set; }
/// <summary>
/// The buffer to provide to the native API.
/// </summary>
public byte[] Buffer { get; set; }
/// <summary>
/// The number of elements that were actually filled or used by the native API,
/// which may be less than the total length of the buffer.
/// </summary>
public int Length { get; internal set; }
}
}

View File

@@ -8,26 +8,109 @@ using System.Threading.Tasks;
namespace NSspi.Buffers
{
/// <summary>
/// Prepares SecureBuffers for providing them to native API calls.
/// </summary>
/// <remarks>
/// The native APIs consume lists of buffers, with each buffer indicating its type or purpose.
///
/// The buffers themselves are simple byte arrays, and the native APIs consume arrays of buffers.
///
/// Since winapi calling convention, perhaps as an extension of C calling convention, does not
/// provide a standard convention means of communicating the length of any array, custom structures
/// must be created to carry the buffer length and usage.
///
/// Not only does the API need to know how long each buffer is, and how long the array of buffers is,
/// it needs to communicate back how much of each buffer was filled; we may provide it a token buffer
/// that is 12288 bytes long, but it might only use 125 bytes of that, which we need a way of knowing.
///
/// As a result of this, the API requires byte arrays to be carried in structs that are natively known as
/// SecureBuffers (known as SecureBufferInternal in this project), and then arrays of SecureBuffers are
/// carried in a SecureBufferDescriptor structure.
///
/// As such, this class has to do a significant amount of marshaling work just to get the buffers back and
/// forth to the native APIs.
/// * We have to pin all buffers
/// * We have to pin the array of buffers
/// * We have to obtain IntPtr handles to each of the buffers and to the array of buffers.
/// * Since we provide EasyToUse SecureBuffer classes from the rest of the project, but we
/// provide SecureBufferInternal structures from the native API, we have to copy back values
/// from the SecureBufferInternal structs to our SecureBuffer class.
///
/// To make this class easy to use, it accepts either one or many buffers as its constructor; and
/// implements IDisposable to know when to marshal values back from the unmanaged structures and to
/// release pinned handles.
///
/// Additionally, in case the adapter is leaked without disposing, the adapter implements a Critical
/// Finalizer, to ensure that the GCHandles are released, else we will permanently pin handles.
///
/// The typical flow is to take one or many buffers; create and fill the neccessary unmanaged structures;
/// pin memory; acquire the IntPtr handles; let the caller access the top-level IntPtr representing
/// the SecureBufferDescriptor, to provide to the native APIs; wait for the caller to invoke the native
/// API; wait for the caller to invoke our Dispose; marshal back any data from the native structures
/// (buffer write counts); release all GCHandles to unpin memory.
///
/// The total descriptor structure is as follows:
/// |-- Descriptor handle
/// |-- Array of buffers
/// |-- Buffer 1
/// |-- Buffer 2
/// ...
/// |-- Buffer N.
///
/// Each object in that structure must be pinned and passed as an IntPtr to the native APIs.
/// All this to pass what boils down to a List of byte arrays..
/// </remarks>
internal sealed class SecureBufferAdapter : CriticalFinalizerObject, IDisposable
{
/// <summary>
/// Whether the adapter has already been disposed.
/// </summary>
private bool disposed;
/// <summary>
/// The list of mananged SecureBuffers the caller provided to us.
/// </summary>
private IList<SecureBuffer> buffers;
/// <summary>
/// The top level handle representing the entire descriptor.
/// </summary>
private GCHandle descriptorHandle;
private GCHandle[] bufferHandles;
private SecureBufferDescInternal descriptor;
private SecureBufferInternal[] bufferCarrier;
/// <summary>
/// The handle representing the array of buffers.
/// </summary>
private GCHandle bufferCarrierHandle;
/// <summary>
/// The handles representing each actual buffer.
/// </summary>
private GCHandle[] bufferHandles;
/// <summary>
/// The native buffer descriptor
/// </summary>
private SecureBufferDescInternal descriptor;
/// <summary>
/// An array of the native buffers.
/// </summary>
private SecureBufferInternal[] bufferCarrier;
/// <summary>
/// Initializes a SecureBufferAdapter to carry a single buffer to the native api.
/// </summary>
/// <param name="buffer"></param>
public SecureBufferAdapter( SecureBuffer buffer )
: this( new[] { buffer } )
{
}
/// <summary>
/// Initializes the SecureBufferAdapter to carry a list of buffers to the native api.
/// </summary>
/// <param name="buffers"></param>
public SecureBufferAdapter( IList<SecureBuffer> buffers ) : base()
{
this.buffers = buffers;
@@ -66,6 +149,9 @@ namespace NSspi.Buffers
Dispose( false );
}
/// <summary>
/// Gets the top-level pointer to the secure buffer descriptor to pass to the native API.
/// </summary>
public IntPtr Handle
{
get
@@ -79,12 +165,22 @@ namespace NSspi.Buffers
}
}
/// <summary>
/// Completes any buffer passing marshaling and releases all resources associated with the adapter.
/// </summary>
public void Dispose()
{
this.Dispose( true );
GC.SuppressFinalize( this );
}
/// <summary>
/// Completes any buffer passing marshaling and releases all resources associated with the adapter.
/// This may be called by the finalizer, or by the regular Dispose method. In the case of the finalizer,
/// we've been leaked and there's no point in attempting to marshal back data from the native structures,
/// nor should we anyway since they may be gone.
/// </summary>
/// <param name="disposing">Whether Dispose is being called.</param>
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
private void Dispose( bool disposing )
{
@@ -121,6 +217,5 @@ namespace NSspi.Buffers
this.disposed = true;
}
}
}

View File

@@ -6,13 +6,20 @@ using System.Threading.Tasks;
namespace NSspi.Buffers
{
/// <summary>
/// Describes how a buffer's opaque internals should be stored, with regards to byte ordering.
/// </summary>
internal enum SecureBufferDataRep : int
{
/*
#define SECURITY_NATIVE_DREP 0x00000010
#define SECURITY_NETWORK_DREP 0x00000000
*/
Nativee = 0x10,
/// <summary>
/// Buffers internals are to be stored in the machine native byte order, which will change depending on
/// what machine generated the buffer.
/// </summary>
Native = 0x10,
/// <summary>
/// Buffers are stored in network byte ordering, that is, big endian format.
/// </summary>
Network = 0x00
}
}

View File

@@ -8,15 +8,31 @@ using System.Threading.Tasks;
namespace NSspi.Buffers
{
/// <summary>
/// Represents the native layout of the secure buffer descriptor that is provided directly
/// to native API calls.
/// </summary>
[StructLayout( LayoutKind.Sequential)]
internal struct SecureBufferDescInternal
{
/// <summary>
/// The buffer structure version.
/// </summary>
public int Version;
/// <summary>
/// The number of buffers represented by this descriptor.
/// </summary>
public int NumBuffers;
// A pointer to a SecureBuffer[]
/// <summary>
/// A pointer to a array of buffers, where each buffer is a byte[].
/// </summary>
public IntPtr Buffers;
/// <summary>
/// Indicates the buffer structure version supported by this structure. Always 0.
/// </summary>
public const int ApiVersion = 0;
}
}

View File

@@ -6,16 +6,53 @@ using System.Threading.Tasks;
namespace NSspi.Buffers
{
/// <summary>
/// Describes the type and purpose of a secure buffer passed to the native API.
/// </summary>
internal enum BufferType : int
{
/// <summary>
/// The buffer is empty.
/// </summary>
Empty = 0x00,
/// <summary>
/// The buffer contains message data. Message data can be plaintext or cipher text data.
/// </summary>
Data = 0x01,
/// <summary>
/// The buffer contains opaque authentication token data.
/// </summary>
Token = 0x02,
/// <summary>
/// The buffer contains parameters specific to the security package.
/// </summary>
Parameters = 0x03,
/// <summary>
/// The buffer placeholder indicating that some data is missing.
/// </summary>
Missing = 0x04,
/// <summary>
/// The buffer passed to an API call contained more data than was necessary for completing the action,
/// such as the case when a streaming-mode connection that does not preserve message bounders, such as TCP
/// is used as the transport. The extra data is returned back to the caller in a buffer of this type.
/// </summary>
Extra = 0x05,
/// <summary>
/// The buffer contains a security data trailer, such as a message signature or marker, or framing data.
/// </summary>
Trailer = 0x06,
/// <summary>
/// The buffer contains a security data header, such as a message signature, marker, or framing data.
/// </summary>
Header = 0x07,
Padding = 0x09,
Stream = 0x0A,
ChannelBindings = 0x0E,

View File

@@ -60,7 +60,12 @@ namespace NSspi
Unsupported = 0x80090302,
TargetUnknown = 0x80090303,
InternalError = 0x80090304,
/// <summary>
/// No security provider package was found with the given name.
/// </summary>
PackageNotFound = 0x80090305,
NotOwner = 0x80090306,
CannotInstall = 0x80090307,
InvalidToken = 0x80090308,
@@ -73,6 +78,18 @@ namespace NSspi
MessageAltered = 0x8009030F,
OutOfSequence = 0x80090310,
NoAuthenticatingAuthority = 0x80090311,
/// <summary>
/// The buffer provided to an SSPI API call contained a message that was not complete.
/// </summary>
/// <remarks>
/// This occurs regularly with SSPI contexts that exchange data using a streaming context,
/// where the data returned from the streaming communications channel, such as a TCP socket,
/// did not contain the complete message.
/// Similarly, a streaming channel may return too much data, in which case the API function
/// will indicate success, but will save off the extra, unrelated data in a buffer of
/// type 'extra'.
/// </remarks>
IncompleteMessage = 0x80090318,
IncompleteCredentials = 0x80090320,
BufferNotEnough = 0x80090321,
@@ -89,8 +106,16 @@ namespace NSspi
BadBinding = 0x80090346
}
/// <summary>
/// Provides extension methods for the SecurityStatus enumeration.
/// </summary>
public static class SecurityStatusExtensions
{
/// <summary>
/// Returns whether or not the status represents an error.
/// </summary>
/// <param name="status"></param>
/// <returns>True if the status represents an error condition.</returns>
public static bool IsError( this SecurityStatus status )
{
return (uint)status > 0x80000000u;

View File

@@ -11,8 +11,9 @@ using NSspi.Contexts;
namespace NSspi
{
/// <summary>
/// Represents any SSPI handle created for credential handles, context handles, and security package
/// handles. Any SSPI handle is always the size of two native pointers.
/// Represents the raw structure for any handle created for the SSPI API, for example, credential
/// handles, context handles, and security package handles. Any SSPI handle is always the size
/// of two native pointers.
/// </summary>
/// <remarks>
/// The documentation for SSPI handles can be found here:
@@ -21,8 +22,8 @@ namespace NSspi
/// This class is not reference safe - if used directly, or referenced directly, it may be leaked,
/// or subject to finalizer races, or any of the hundred of things SafeHandles were designed to fix.
/// Do not directly use this class - use only though SafeHandle wrapper objects. Any reference needed
/// to this handle for performing work (InitializeSecurityContext, eg), should be done through
/// a second class SafeSspiHandleReference so that reference counting is properly executed.
/// to this handle for performing work (InitializeSecurityContext, eg) should be performed a CER
/// that employs handle reference counting across the native API invocation.
/// </remarks>
[StructLayout( LayoutKind.Sequential, Pack = 1 ) ]
internal struct RawSspiHandle
@@ -30,12 +31,21 @@ namespace NSspi
private IntPtr lowPart;
private IntPtr highPart;
/// <summary>
/// Returns whether or not the handle is set to the default, empty value.
/// </summary>
/// <returns></returns>
public bool IsZero()
{
return this.lowPart == IntPtr.Zero && this.highPart == IntPtr.Zero;
}
// This guy has to be executed in a CER.
/// <summary>
/// Sets the handle to an invalid value.
/// </summary>
/// <remarks>
/// This method is executed in a CER during handle release.
/// </remarks>
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success)]
public void SetInvalid()
{
@@ -44,7 +54,9 @@ namespace NSspi
}
}
/// <summary>
/// Safely encapsulates a raw handle used in the SSPI api.
/// </summary>
public abstract class SafeSspiHandle : SafeHandle
{
internal RawSspiHandle rawHandle;

View File

@@ -7,19 +7,33 @@ using System.Threading.Tasks;
namespace NSspi
{
/// <summary>
/// Represents a Windows API Timestamp structure, which stores time in units of 100 nanosecond
/// ticks, counting from January 1st, year 1601 at 00:00 UTC. Time is stored as a 64-bit value.
/// </summary>
[StructLayout( LayoutKind.Sequential )]
public struct TimeStamp
{
public static readonly DateTime Epoch = new DateTime( 1601, 1, 1, 0, 0, 0, DateTimeKind.Utc );
/// <summary>
/// Stores the time value. Infinite times are often represented as values near, but not exactly
/// at the maximum signed 64-bit 2's complement value.
/// </summary>
private long time;
/// <summary>
/// Converts the TimeStamp to an equivalant DateTime object. If the TimeStamp represents
/// a value larger than DateTime.MaxValue, then DateTime.MaxValue is returned.
/// </summary>
/// <returns></returns>
public DateTime ToDateTime()
{
ulong test = (ulong)this.time + (ulong)(Epoch.Ticks);
// Sometimes the value returned is massive, eg, 0x7fffff154e84ffff, which is a value
// somewhere in the year 30848. This would overflow DateTime, since it peaks at 31-Dec-9999.
// It turns out that this value corresponds to a TimeStamp's maximum value, reduced by my local timezone
// http://stackoverflow.com/questions/24478056/
if ( test > (ulong)DateTime.MaxValue.Ticks )
{