1 Commits
master ... 0.1

Author SHA1 Message Date
antiduh
5463beab4a Marked the code at the 0.1 release. 2014-07-08 19:08:38 +00:00
81 changed files with 907 additions and 1428 deletions

2
.gitignore vendored
View File

@@ -1,2 +0,0 @@
.vs
/_ReSharper.Caches

View File

@@ -1,4 +1,4 @@
Copyright (c) 2019, Kevin Thompson
Copyright (c) 2014, Kevin Thompson
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,8 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 16.0.28527.54
MinimumVisualStudioVersion = 10.0.40219.1
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "TestClient\TestClient.csproj", "{E93FBF1A-5198-44D6-BDF0-880D17F2B81A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProtocol", "TestProtocol\TestProtocol.csproj", "{9BFD94E1-D9FB-44D7-A6E7-8BAC2620E535}"

2
NSspi/.gitignore vendored
View File

@@ -1,2 +0,0 @@
bin
obj

View File

@@ -1,4 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
@@ -8,7 +13,7 @@ namespace NSspi
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>
@@ -32,7 +37,8 @@ namespace NSspi
buffer[position + 0] = (byte)( value >> 24 );
buffer[position + 1] = (byte)( value >> 16 );
buffer[position + 2] = (byte)( value >> 8 );
buffer[position + 3] = (byte)( value );
buffer[position + 3] = (byte)( value);
}
/// <summary>
@@ -71,4 +77,4 @@ namespace NSspi
return value;
}
}
}
}

View File

@@ -1,4 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NSspi.Buffers;
using NSspi.Credentials;
@@ -9,7 +14,7 @@ namespace NSspi.Contexts
/// with the server and to encrypt, decrypt, sign and verify messages to and from the server.
/// </summary>
/// <remarks>
/// A client and server establish a shared security context by exchanging authentication tokens. Once
/// A client and server establish a shared security context by exchanging authentication tokens. Once
/// the shared context is established, the client and server can pass messages to each other, encrypted,
/// signed, etc, using the established parameters of the shared context.
/// </remarks>
@@ -28,7 +33,7 @@ namespace NSspi.Contexts
/// <param name="requestedAttribs">Requested attributes that describe the desired properties of the
/// context once it is established. If a context cannot be established that satisfies the indicated
/// properties, the context initialization is aborted.</param>
public ClientContext( Credential cred, string serverPrinc, ContextAttrib requestedAttribs )
public ClientContext( ClientCredential cred, string serverPrinc, ContextAttrib requestedAttribs )
: base( cred )
{
this.serverPrinc = serverPrinc;
@@ -42,11 +47,11 @@ namespace NSspi.Contexts
/// This method is performed iteratively to start, continue, and end the authentication cycle with the
/// server. Each stage works by acquiring a token from one side, presenting it to the other side
/// which in turn may generate a new token.
///
///
/// The cycle typically starts and ends with the client. On the first invocation on the client,
/// no server token exists, and null is provided in its place. The client returns its status, providing
/// its output token for the server. The server accepts the clients token as input and provides a
/// token as output to send back to the client. This cycle continues until the server and client
/// its output token for the server. The server accepts the clients token as input and provides a
/// token as output to send back to the client. This cycle continues until the server and client
/// both indicate, typically, a SecurityStatus of 'OK'.
/// </remarks>
/// <param name="serverToken">The most recently received token from the server, or null if beginning
@@ -55,7 +60,7 @@ namespace NSspi.Contexts
/// <returns>A status message indicating the progression of the authentication cycle.
/// A status of 'OK' indicates that the cycle is complete, from the client's perspective. If the outToken
/// is not null, it must be sent to the server.
/// A status of 'Continue' indicates that the output token should be sent to the server and
/// A status of 'Continue' indicates that the output token should be sent to the server and
/// a response should be anticipated.</returns>
public SecurityStatus Init( byte[] serverToken, out byte[] outToken )
{
@@ -68,7 +73,7 @@ namespace NSspi.Contexts
SecureBuffer serverBuffer;
SecureBufferAdapter serverAdapter;
if( this.Disposed )
{
throw new ObjectDisposedException( "ClientContext" );
@@ -81,24 +86,24 @@ namespace NSspi.Contexts
{
throw new InvalidOperationException( "Must provide the server's response when continuing the init process." );
}
// The security package tells us how big its biggest token will be. We'll allocate a buffer
// that size, and it'll tell us how much it used.
outTokenBuffer = new SecureBuffer(
new byte[this.Credential.PackageInfo.MaxTokenLength],
BufferType.Token
outTokenBuffer = new SecureBuffer(
new byte[ this.Credential.PackageInfo.MaxTokenLength ],
BufferType.Token
);
serverBuffer = null;
if( serverToken != null )
if ( serverToken != null )
{
serverBuffer = new SecureBuffer( serverToken, BufferType.Token );
}
// Some notes on handles and invoking InitializeSecurityContext
// - The first time around, the phContext parameter (the 'old' handle) is a null pointer to what
// would be an RawSspiHandle, to indicate this is the first time it's being called.
// The phNewContext is a pointer (reference) to an RawSspiHandle struct of where to write the
// - The first time around, the phContext parameter (the 'old' handle) is a null pointer to what
// would be an RawSspiHandle, to indicate this is the first time it's being called.
// The phNewContext is a pointer (reference) to an RawSspiHandle struct of where to write the
// new handle's values.
// - The next time you invoke ISC, it takes a pointer to the handle it gave you last time in phContext,
// and takes a pointer to where it should write the new handle's values in phNewContext.
@@ -106,13 +111,13 @@ namespace NSspi.Contexts
// "On the second call, phNewContext can be the same as the handle specified in the phContext
// parameter."
// It will overwrite the handle you gave it with the new handle value.
// - All handle structures themselves are actually *two* pointer variables, eg, 64 bits on 32-bit
// - All handle structures themselves are actually *two* pointer variables, eg, 64 bits on 32-bit
// Windows, 128 bits on 64-bit Windows.
// - So in the end, on a 64-bit machine, we're passing a 64-bit value (the pointer to the struct) that
// points to 128 bits of memory (the struct itself) for where to write the handle numbers.
using( outAdapter = new SecureBufferAdapter( outTokenBuffer ) )
using ( outAdapter = new SecureBufferAdapter( outTokenBuffer ) )
{
if( this.ContextHandle.IsInvalid )
if ( this.ContextHandle.IsInvalid )
{
status = ContextNativeMethods.InitializeSecurityContext_1(
ref this.Credential.Handle.rawHandle,
@@ -131,7 +136,7 @@ namespace NSspi.Contexts
}
else
{
using( serverAdapter = new SecureBufferAdapter( serverBuffer ) )
using ( serverAdapter = new SecureBufferAdapter( serverBuffer ) )
{
status = ContextNativeMethods.InitializeSecurityContext_2(
ref this.Credential.Handle.rawHandle,
@@ -174,4 +179,4 @@ namespace NSspi.Contexts
return status;
}
}
}
}

View File

@@ -1,19 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using NSspi.Buffers;
using NSspi.Contexts;
using NSspi.Credentials;
namespace NSspi.Contexts
{
/// <summary>
/// Represents a security context and provides common functionality required for all security
/// Represents a security context and provides common functionality required for all security
/// contexts.
/// </summary>
/// <remarks>
/// This class is abstract and has a protected constructor and Initialize method. The exact
/// initialization implementation is provided by a subclasses, which may perform initialization
/// This class is abstract and has a protected constructor and Initialize method. The exact
/// initialization implementation is provided by a subclasses, which may perform initialization
/// in a variety of manners.
/// </remarks>
public abstract class Context : IDisposable
@@ -83,11 +87,6 @@ namespace NSspi.Contexts
/// </summary>
public bool Disposed { get; private set; }
/// <summary>
/// Constant for wrapping only...no encryption
/// </summary>
protected uint KERB_WRAP_NO_ENCRYPT = 0x80000001;
/// <summary>
/// Marks the context as having completed the initialization process, ie, exchanging of authentication tokens.
/// </summary>
@@ -123,105 +122,6 @@ namespace NSspi.Contexts
this.Disposed = true;
}
/// <summary>
/// Returns the identity of the remote entity.
/// </summary>
/// <returns></returns>
public IIdentity GetRemoteIdentity()
{
IIdentity result = null;
using( var tokenHandle = GetContextToken() )
{
bool gotRef = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
tokenHandle.DangerousAddRef( ref gotRef );
}
catch( Exception )
{
if( gotRef )
{
tokenHandle.DangerousRelease();
gotRef = false;
}
throw;
}
finally
{
try
{
result = new WindowsIdentity(
tokenHandle.DangerousGetHandle(),
this.Credential.SecurityPackage
);
}
finally
{
// Make sure we release the handle, even if the allocation for
// WindowsIdentity fails.
tokenHandle.DangerousRelease();
}
}
}
return result;
}
private SafeTokenHandle GetContextToken()
{
bool gotRef = false;
SecurityStatus status = SecurityStatus.InternalError;
SafeTokenHandle token;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
this.ContextHandle.DangerousAddRef( ref gotRef );
}
catch( Exception )
{
if( gotRef )
{
this.ContextHandle.DangerousRelease();
gotRef = false;
}
throw;
}
finally
{
if( gotRef )
{
try
{
status = ContextNativeMethods.QuerySecurityContextToken(
ref this.ContextHandle.rawHandle,
out token
);
}
finally
{
this.ContextHandle.DangerousRelease();
}
}
else
{
token = null;
}
}
if( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to query context token.", status );
}
return token;
}
/// <summary>
/// Encrypts the byte array using the context's session key.
/// </summary>
@@ -235,9 +135,8 @@ namespace NSspi.Contexts
/// - The padding buffer.
/// </remarks>
/// <param name="input">The raw message to encrypt.</param>
/// <param name="wrapOnly">Only wrap the message, no encryption</param>
/// <returns>The packed and encrypted message.</returns>
public byte[] Encrypt( byte[] input, bool wrapOnly = false )
public byte[] Encrypt( byte[] input )
{
// The message is encrypted in place in the buffer we provide to Win32 EncryptMessage
SecPkgContext_Sizes sizes;
@@ -262,14 +161,9 @@ namespace NSspi.Contexts
using( adapter = new SecureBufferAdapter( new[] { trailerBuffer, dataBuffer, paddingBuffer } ) )
{
uint qualityOfProtection = 0u;
if (wrapOnly)
{
qualityOfProtection = KERB_WRAP_NO_ENCRYPT;
}
status = ContextNativeMethods.SafeEncryptMessage(
this.ContextHandle,
qualityOfProtection,
0,
adapter,
0
);
@@ -306,7 +200,7 @@ namespace NSspi.Contexts
Array.Copy( paddingBuffer.Buffer, 0, result, position, paddingBuffer.Length );
position += paddingBuffer.Length;
return result;
}
@@ -323,9 +217,8 @@ namespace NSspi.Contexts
/// - The padding buffer.
/// </remarks>
/// <param name="input">The packed and encrypted data.</param>
/// <param name="unwrapOnly">Only wrap the message, no encryption</param>
/// <returns>The original plaintext message.</returns>
public byte[] Decrypt( byte[] input, bool unwrapOnly = false)
public byte[] Decrypt( byte[] input )
{
SecPkgContext_Sizes sizes;
@@ -364,7 +257,7 @@ namespace NSspi.Contexts
paddingLength = ByteWriter.ReadInt16_BE( input, position );
position += 2;
if( trailerLength + dataLength + paddingLength + 2 + 4 + 2 > input.Length )
if ( trailerLength + dataLength + paddingLength + 2 + 4 + 2 > input.Length )
{
throw new ArgumentException( "The buffer contains invalid data - the embedded length data does not add up." );
}
@@ -385,7 +278,7 @@ namespace NSspi.Contexts
{
throw new ArgumentException( "Input is missing data - it is not long enough to contain a fully encrypted message" );
}
if( dataBuffer.Length <= remaining )
{
Array.Copy( input, position, dataBuffer.Buffer, 0, dataBuffer.Length );
@@ -402,17 +295,13 @@ namespace NSspi.Contexts
Array.Copy( input, position, paddingBuffer.Buffer, 0, paddingBuffer.Length );
}
// else there was no padding.
using( adapter = new SecureBufferAdapter( new[] { trailerBuffer, dataBuffer, paddingBuffer } ) )
using( adapter = new SecureBufferAdapter( new [] { trailerBuffer, dataBuffer, paddingBuffer } ) )
{
uint qualityOfProtection = 0u;
if (unwrapOnly)
{
qualityOfProtection = KERB_WRAP_NO_ENCRYPT;
}
status = ContextNativeMethods.SafeDecryptMessage(
this.ContextHandle,
qualityOfProtection,
0,
adapter,
0
);
@@ -459,7 +348,7 @@ namespace NSspi.Contexts
Array.Copy( message, dataBuffer.Buffer, message.Length );
using( adapter = new SecureBufferAdapter( new[] { dataBuffer, signatureBuffer } ) )
using ( adapter = new SecureBufferAdapter( new[] { dataBuffer, signatureBuffer } ) )
{
status = ContextNativeMethods.SafeMakeSignature(
this.ContextHandle,
@@ -469,7 +358,7 @@ namespace NSspi.Contexts
);
}
if( status != SecurityStatus.OK )
if ( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to create message signature.", status );
}
@@ -477,7 +366,7 @@ namespace NSspi.Contexts
byte[] outMessage;
int position = 0;
// Enough room for
// Enough room for
// - original message length (4 bytes)
// - signature length (2 bytes)
// - original message
@@ -500,35 +389,6 @@ namespace NSspi.Contexts
return outMessage;
}
/// <summary>
/// Returns the Session Key from a context or null on failure.
/// </summary>
/// <remarks>
/// Session keys are sometimes needed for other purposes or HMAC functions. This function
/// will run QueryAttribute to get the session key struct, and read and return the key from
/// that struct.
/// </remarks>
/// <returns>byte[] with the session key data or null on failure</returns>
public byte[] QuerySessionKey()
{
SecurityStatus status;
byte[] SessionKey = null;
status = ContextNativeMethods.SafeQueryContextAttribute(
this.ContextHandle,
ContextQueryAttrib.SessionKey,
ref SessionKey
);
if( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to query session key.", status );
}
return SessionKey;
}
/// <summary>
/// Verifies the signature of a signed message
/// </summary>
@@ -554,8 +414,8 @@ namespace NSspi.Contexts
CheckLifecycle();
sizes = QueryBufferSizes();
if( signedMessage.Length < 2 + 4 + sizes.MaxSignature )
if ( signedMessage.Length < 2 + 4 + sizes.MaxSignature )
{
throw new ArgumentException( "Input message is too small to possibly fit a valid message" );
}
@@ -570,7 +430,7 @@ namespace NSspi.Contexts
sigLen = ByteWriter.ReadInt16_BE( signedMessage, position );
position += 2;
if( messageLen + sigLen + 2 + 4 > signedMessage.Length )
if ( messageLen + sigLen + 2 + 4 > signedMessage.Length )
{
throw new ArgumentException( "The buffer contains invalid data - the embedded length data does not add up." );
}
@@ -583,7 +443,7 @@ namespace NSspi.Contexts
Array.Copy( signedMessage, position, signatureBuffer.Buffer, 0, sigLen );
position += sigLen;
using( adapter = new SecureBufferAdapter( new[] { dataBuffer, signatureBuffer } ) )
using ( adapter = new SecureBufferAdapter( new[] { dataBuffer, signatureBuffer } ) )
{
status = ContextNativeMethods.SafeVerifySignature(
this.ContextHandle,
@@ -593,12 +453,12 @@ namespace NSspi.Contexts
);
}
if( status == SecurityStatus.OK )
if ( status == SecurityStatus.OK )
{
origMessage = dataBuffer.Buffer;
return true;
}
else if( status == SecurityStatus.MessageAltered ||
else if ( status == SecurityStatus.MessageAltered ||
status == SecurityStatus.OutOfSequence )
{
origMessage = null;
@@ -625,9 +485,9 @@ namespace NSspi.Contexts
{
this.ContextHandle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
this.ContextHandle.DangerousRelease();
gotRef = false;
@@ -637,7 +497,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.QueryContextAttributes_Sizes(
ref this.ContextHandle.rawHandle,
@@ -661,7 +521,7 @@ namespace NSspi.Contexts
/// </summary>
/// <param name="attrib">The string-valued attribute to query.</param>
/// <returns></returns>
private string QueryContextString( ContextQueryAttrib attrib )
private string QueryContextString(ContextQueryAttrib attrib)
{
SecPkgContext_String stringAttrib;
SecurityStatus status = SecurityStatus.InternalError;
@@ -680,9 +540,9 @@ namespace NSspi.Contexts
{
this.ContextHandle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
this.ContextHandle.DangerousRelease();
gotRef = false;
@@ -691,7 +551,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.QueryContextAttributes_String(
ref this.ContextHandle.rawHandle,
@@ -701,14 +561,14 @@ namespace NSspi.Contexts
this.ContextHandle.DangerousRelease();
if( status == SecurityStatus.OK )
if ( status == SecurityStatus.OK )
{
result = Marshal.PtrToStringUni( stringAttrib.StringResult );
ContextNativeMethods.FreeContextBuffer( stringAttrib.StringResult );
}
}
}
if( status == SecurityStatus.Unsupported )
{
return null;
@@ -722,7 +582,7 @@ namespace NSspi.Contexts
}
/// <summary>
/// Verifies that the object's lifecycle (initialization / disposition) state is suitable for using the
/// Verifies that the object's lifecycle (initialization / disposition) state is suitable for using the
/// object.
/// </summary>
private void CheckLifecycle()
@@ -737,4 +597,4 @@ namespace NSspi.Contexts
}
}
}
}
}

View File

@@ -1,25 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Contexts
{
/// <summary>
/// Defines options for creating a security context via win32 InitializeSecurityContext
/// Defines options for creating a security context via win32 InitializeSecurityContext
/// (used by clients) and AcceptSecurityContext (used by servers).
/// Required attribute flags are specified when creating the context. InitializeSecurityContext
/// and AcceptSecurityContext returns a value indicating what final attributes the created context
/// and AcceptSecurityContext returns a value indicating what final attributes the created context
/// actually has.
/// </summary>
[Flags]
public enum ContextAttrib : int
public enum ContextAttrib : int
{
/// <summary>
/// No additional attributes are provided.
/// </summary>
Zero = 0,
/// <summary>
/// The server can use the context to authenticate to other servers as the client. The
/// MutualAuth flag must be set for this flag to work. Valid for Kerberos. Ignore this flag for
/// MutualAuth flag must be set for this flag to work. Valid for Kerberos. Ignore this flag for
/// constrained delegation.
/// </summary>
Delegate = 0x00000001,
@@ -32,16 +36,17 @@ namespace NSspi.Contexts
/// </summary>
MutualAuth = 0x00000002,
/// <summary>
/// Detect replayed messages that have been encoded by using the EncryptMessage or MakeSignature
/// Detect replayed messages that have been encoded by using the EncryptMessage or MakeSignature
/// message support functionality.
/// </summary>
ReplayDetect = 0x00000004,
/// <summary>
/// Detect messages received out of sequence when using the message support functionality.
/// This flag implies all of the conditions specified by the Integrity flag - out-of-order sequence
/// detection can only be trusted if the integrity of any underlying sequence detection mechanism
/// Detect messages received out of sequence when using the message support functionality.
/// This flag implies all of the conditions specified by the Integrity flag - out-of-order sequence
/// detection can only be trusted if the integrity of any underlying sequence detection mechanism
/// in transmitted data can be trusted.
/// </summary>
SequenceDetect = 0x00000008,
@@ -50,12 +55,12 @@ namespace NSspi.Contexts
// Confidentiality is supported for NTLM with Microsoft
// Windows NT version 4.0, SP4 and later and with the
// Kerberos protocol in Microsoft Windows 2000 and later.
/// <summary>
/// The context must protect data while in transit. Encrypt messages by using the EncryptMessage function.
/// </summary>
Confidentiality = 0x00000010,
/// <summary>
/// A new session key must be negotiated.
/// This value is supported only by the Kerberos security package.
@@ -63,13 +68,13 @@ namespace NSspi.Contexts
UseSessionKey = 0x00000020,
/// <summary>
/// The security package allocates output buffers for you. Buffers allocated by the security package have
/// The security package allocates output buffers for you. Buffers allocated by the security package have
/// to be released by the context memory management functions.
/// </summary>
AllocateMemory = 0x00000100,
/// <summary>
/// The security context will not handle formatting messages. This value is the default for the Kerberos,
/// The security context will not handle formatting messages. This value is the default for the Kerberos,
/// Negotiate, and NTLM security packages.
/// </summary>
Connection = 0x00000800,
@@ -79,7 +84,7 @@ namespace NSspi.Contexts
/// </summary>
/// <remarks>
/// A client specifies InitExtendedError in InitializeSecurityContext
/// and the server specifies AcceptExtendedError in AcceptSecurityContext.
/// and the server specifies AcceptExtendedError in AcceptSecurityContext.
/// </remarks>
InitExtendedError = 0x00004000,
@@ -88,7 +93,7 @@ namespace NSspi.Contexts
/// </summary>
/// <remarks>
/// A client specifies InitExtendedError in InitializeSecurityContext
/// and the server specifies AcceptExtendedError in AcceptSecurityContext.
/// and the server specifies AcceptExtendedError in AcceptSecurityContext.
/// </remarks>
AcceptExtendedError = 0x00008000,
@@ -107,7 +112,7 @@ namespace NSspi.Contexts
/// Replayed and out-of-sequence messages will not be detected with the setting of this attribute.
/// Set ReplayDetect and SequenceDetect also if these behaviors are desired.
/// </summary>
InitIntegrity = 0x00010000,
InitIntegrity = 0x00010000,
/// <summary>
/// Sign messages and verify signatures by using the EncryptMessage and MakeSignature functions.
@@ -116,16 +121,7 @@ namespace NSspi.Contexts
/// </summary>
AcceptIntegrity = 0x00020000,
/// <summary>
/// Set by a client; indicates the context can only impersonate with limited privileges,
/// allowing the server only to identify the client when impersonating.
/// </summary>
InitIdentify = 0x00020000,
/// <summary>
/// Set by a server; indicates the context can only impersonate with limited privileges,
/// allowing the server only to identify the client when impersonating.
/// </summary>
AcceptIdentify = 0x00080000,
/// <summary>
@@ -138,4 +134,4 @@ namespace NSspi.Contexts
/// </summary>
InitUseSuppliedCreds = 0x00000080,
}
}
}

View File

@@ -1,8 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NSspi.Buffers;
using NSspi.Contexts;
namespace NSspi.Contexts
{
@@ -23,7 +28,7 @@ namespace NSspi.Contexts
_Out_ PULONG pfContextAttr,
_Out_opt_ PTimeStamp ptsTimeStamp
);
SECURITY_STATUS SEC_Entry InitializeSecurityContext(
_In_opt_ PCredHandle phCredential, // [in] handle to the credentials
_In_opt_ PCtxtHandle phContext, // [in/out] handle of partially formed context. Always NULL the first time through
@@ -40,7 +45,7 @@ namespace NSspi.Contexts
);
*/
[DllImport( "Secur32.dll", EntryPoint = "AcceptSecurityContext", CharSet = CharSet.Unicode )]
[DllImport( "Secur32.dll", EntryPoint = "AcceptSecurityContext",CharSet = CharSet.Unicode )]
internal static extern SecurityStatus AcceptSecurityContext_1(
ref RawSspiHandle credHandle,
IntPtr oldContextHandle,
@@ -53,6 +58,7 @@ namespace NSspi.Contexts
ref TimeStamp expiry
);
[DllImport( "Secur32.dll", EntryPoint = "AcceptSecurityContext", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus AcceptSecurityContext_2(
ref RawSspiHandle credHandle,
@@ -66,6 +72,7 @@ namespace NSspi.Contexts
ref TimeStamp expiry
);
[DllImport( "Secur32.dll", EntryPoint = "InitializeSecurityContext", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus InitializeSecurityContext_1(
ref RawSspiHandle credentialHandle,
@@ -82,6 +89,7 @@ namespace NSspi.Contexts
ref TimeStamp expiry
);
[DllImport( "Secur32.dll", EntryPoint = "InitializeSecurityContext", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus InitializeSecurityContext_2(
ref RawSspiHandle credentialHandle,
@@ -98,17 +106,18 @@ namespace NSspi.Contexts
ref TimeStamp expiry
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "DeleteSecurityContext", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus DeleteSecurityContext( ref RawSspiHandle contextHandle );
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "Secur32.dll", EntryPoint = "EncryptMessage", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus EncryptMessage(
ref RawSspiHandle contextHandle,
uint qualityOfProtection,
int qualityOfProtection,
IntPtr bufferDescriptor,
uint sequenceNumber
int sequenceNumber
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
@@ -116,17 +125,17 @@ namespace NSspi.Contexts
internal static extern SecurityStatus DecryptMessage(
ref RawSspiHandle contextHandle,
IntPtr bufferDescriptor,
uint sequenceNumber,
uint qualityOfProtection
int sequenceNumber,
int qualityOfProtection
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "Secur32.dll", EntryPoint = "MakeSignature", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus MakeSignature(
ref RawSspiHandle contextHandle,
uint qualityOfProtection,
int qualityOfProtection,
IntPtr bufferDescriptor,
uint sequenceNumber
int sequenceNumber
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
@@ -134,8 +143,8 @@ namespace NSspi.Contexts
internal static extern SecurityStatus VerifySignature(
ref RawSspiHandle contextHandle,
IntPtr bufferDescriptor,
uint sequenceNumber,
uint qualityOfProtection
int sequenceNumber,
int qualityOfProtection
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
@@ -146,7 +155,7 @@ namespace NSspi.Contexts
ref SecPkgContext_Sizes sizes
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success)]
[DllImport( "Secur32.dll", EntryPoint = "QueryContextAttributes", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus QueryContextAttributes_String(
ref RawSspiHandle contextHandle,
@@ -154,18 +163,11 @@ namespace NSspi.Contexts
ref SecPkgContext_String names
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "QueryContextAttributes", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus QueryContextAttributes(
ref RawSspiHandle contextHandle,
ContextQueryAttrib attrib,
IntPtr attribute
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "FreeContextBuffer", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus FreeContextBuffer( IntPtr handle );
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "ImpersonateSecurityContext", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus ImpersonateSecurityContext( ref RawSspiHandle contextHandle );
@@ -174,78 +176,8 @@ namespace NSspi.Contexts
[DllImport( "Secur32.dll", EntryPoint = "RevertSecurityContext", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus RevertSecurityContext( ref RawSspiHandle contextHandle );
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "QuerySecurityContextToken", SetLastError = true )]
internal static extern SecurityStatus QuerySecurityContextToken( ref RawSspiHandle contextHandle, [Out] out SafeTokenHandle handle );
[StructLayout( LayoutKind.Sequential )]
private class KeyStruct
{
public int size;
public IntPtr data;
}
internal static SecurityStatus SafeQueryContextAttribute(
SafeContextHandle handle,
ContextQueryAttrib attribute,
ref byte[] buffer
)
{
bool gotRef = false;
SecurityStatus status = SecurityStatus.InternalError;
RuntimeHelpers.PrepareConstrainedRegions();
int pointerSize = System.Environment.Is64BitOperatingSystem ? 8 : 4; //NOTE: update this when 128 bit processors exist
IntPtr alloc_buffer = Marshal.AllocHGlobal( sizeof( uint ) + pointerSize ); //NOTE: this is at most 4 + sizeof(void*) bytes
//see struct SecPkgContext_SessionKey
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa380096(v=vs.85).aspx
try
{
handle.DangerousAddRef( ref gotRef );
}
catch( Exception )
{
if( gotRef )
{
handle.DangerousRelease();
gotRef = false;
buffer = null;
}
throw;
}
finally
{
if( gotRef )
{
status = ContextNativeMethods.QueryContextAttributes(
ref handle.rawHandle,
attribute,
alloc_buffer
);
if( status == SecurityStatus.OK )
{
KeyStruct key = new KeyStruct();
Marshal.PtrToStructure( alloc_buffer, key ); // fit to the proper size, read a byte[]
byte[] sizedBuffer = new byte[key.size];
for( int i = 0; i < key.size; i++ )
sizedBuffer[i] = Marshal.ReadByte( key.data, i );
buffer = sizedBuffer;
}
handle.DangerousRelease();
}
}
Marshal.FreeHGlobal( alloc_buffer );
return status;
}
/// <summary>
/// Safely invokes the native EncryptMessage function, making sure that handle ref counting is
/// Safely invokes the native EncryptMessage function, making sure that handle ref counting is
/// performed in a proper CER.
/// </summary>
/// <param name="handle"></param>
@@ -255,9 +187,9 @@ namespace NSspi.Contexts
/// <returns></returns>
internal static SecurityStatus SafeEncryptMessage(
SafeContextHandle handle,
uint qualityOfProtection,
int qualityOfProtection,
SecureBufferAdapter bufferAdapter,
uint sequenceNumber )
int sequenceNumber )
{
SecurityStatus status = SecurityStatus.InternalError;
bool gotRef = false;
@@ -267,9 +199,9 @@ namespace NSspi.Contexts
{
handle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
handle.DangerousRelease();
gotRef = false;
@@ -279,7 +211,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.EncryptMessage(
ref handle.rawHandle,
@@ -296,7 +228,7 @@ namespace NSspi.Contexts
}
/// <summary>
/// Safely invokes the native DecryptMessage function, making sure that handle ref counting is
/// Safely invokes the native DecryptMessage function, making sure that handle ref counting is
/// performed in a proper CER.
/// </summary>
/// <param name="handle"></param>
@@ -304,11 +236,11 @@ namespace NSspi.Contexts
/// <param name="bufferAdapter"></param>
/// <param name="sequenceNumber"></param>
/// <returns></returns>
internal static SecurityStatus SafeDecryptMessage(
SafeContextHandle handle,
uint qualityOfProtection,
SecureBufferAdapter bufferAdapter,
uint sequenceNumber )
internal static SecurityStatus SafeDecryptMessage(
SafeContextHandle handle,
int qualityOfProtection,
SecureBufferAdapter bufferAdapter,
int sequenceNumber )
{
SecurityStatus status = SecurityStatus.InvalidHandle;
bool gotRef = false;
@@ -347,7 +279,7 @@ namespace NSspi.Contexts
}
/// <summary>
/// Safely invokes the native MakeSignature function, making sure that handle ref counting is
/// Safely invokes the native MakeSignature function, making sure that handle ref counting is
/// performed in a proper CER.
/// </summary>
/// <param name="handle"></param>
@@ -357,9 +289,9 @@ namespace NSspi.Contexts
/// <returns></returns>
internal static SecurityStatus SafeMakeSignature(
SafeContextHandle handle,
uint qualityOfProtection,
int qualityOfProtection,
SecureBufferAdapter adapter,
uint sequenceNumber )
int sequenceNumber )
{
bool gotRef = false;
SecurityStatus status = SecurityStatus.InternalError;
@@ -369,9 +301,9 @@ namespace NSspi.Contexts
{
handle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
handle.DangerousRelease();
gotRef = false;
@@ -381,7 +313,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.MakeSignature(
ref handle.rawHandle,
@@ -398,7 +330,7 @@ namespace NSspi.Contexts
}
/// <summary>
/// Safely invokes the native VerifySignature function, making sure that handle ref counting is
/// Safely invokes the native VerifySignature function, making sure that handle ref counting is
/// performed in a proper CER.
/// </summary>
/// <param name="handle"></param>
@@ -408,9 +340,9 @@ namespace NSspi.Contexts
/// <returns></returns>
internal static SecurityStatus SafeVerifySignature(
SafeContextHandle handle,
uint qualityOfProtection,
int qualityOfProtection,
SecureBufferAdapter adapter,
uint sequenceNumber )
int sequenceNumber )
{
bool gotRef = false;
SecurityStatus status = SecurityStatus.InternalError;
@@ -420,9 +352,9 @@ namespace NSspi.Contexts
{
handle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
handle.DangerousRelease();
gotRef = false;
@@ -432,7 +364,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.VerifySignature(
ref handle.rawHandle,
@@ -448,4 +380,4 @@ namespace NSspi.Contexts
return status;
}
}
}
}

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Contexts
{
@@ -23,4 +27,4 @@ namespace NSspi.Contexts
{
public IntPtr StringResult;
}
}
}

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Contexts
{
@@ -32,15 +36,5 @@ namespace NSspi.Contexts
/// Results for a query of this type are stored in a Win32 SecPkgContext_Authority structure.
/// </remarks>
Authority = 6,
/// <summary>
/// Queries the context for it's neogtiated SessionKey
/// </summary>
/// <remarks>
/// Results for a query of this type are stored in a Win32 SecPkgContext_SessionKey structure
/// </remarks>
SessionKey = 9,
AccessToken = 13, //not implemented yet but this would be cool
}
}
}

View File

@@ -1,47 +1,45 @@
using System;
using System.Security.Principal;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Contexts
{
/// <summary>
/// Represents impersonation performed on a server on behalf of a client.
/// Represents impersonation performed on a server on behalf of a client.
/// </summary>
/// <remarks>
/// The handle controls the lifetime of impersonation, and will revert the impersonation
/// if it is disposed, or if it is finalized ie by being leaked and garbage collected.
///
///
/// If the handle is accidentally leaked while operations are performed on behalf of the user,
/// impersonation may be reverted at any arbitrary time, perhaps during those operations.
/// This may lead to operations being performed in the security context of the server,
/// This may lead to operations being performed in the security context of the server,
/// potentially leading to security vulnerabilities.
/// </remarks>
public class ImpersonationHandle : IDisposable
{
private readonly ServerContext server;
private bool disposed;
private ServerContext server;
/// <summary>
/// Initializes a new instance of the ImpersonationHandle. Does not perform impersonation.
/// </summary>
/// <param name="server">The server context that is performing impersonation.</param>
internal ImpersonationHandle( ServerContext server )
internal ImpersonationHandle(ServerContext server)
{
this.server = server;
this.disposed = false;
}
/// <summary>
/// Finalizes the ImpersonationHandle by reverting the impersonation.
/// </summary>
~ImpersonationHandle()
{
Dispose( false );
}
/// <summary>
/// Reverts impersonation.
/// Reverts the impersonation.
/// </summary>
public void Dispose()
{
@@ -49,28 +47,13 @@ namespace NSspi.Contexts
GC.SuppressFinalize( this );
}
/// <summary>
/// Reverts impersonation.
/// </summary>
/// <param name="disposing">True if being disposed, false if being finalized.</param>
private void Dispose( bool disposing )
protected virtual void Dispose( bool disposing )
{
// This implements a variant of the typical dispose pattern. Always try to revert
// impersonation, even if finalizing. Don't do anything if we're already reverted.
if( this.disposed == false )
if( disposing && this.disposed == false && this.server != null && this.server.Disposed == false )
{
this.disposed = true;
// Just in case the reference is being pulled out from under us, pull a stable copy
// of the reference while we're null-checking.
var serverCopy = this.server;
if( serverCopy != null && serverCopy.Disposed == false )
{
serverCopy.RevertImpersonate();
}
this.server.RevertImpersonate();
}
}
}
}
}

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Contexts
{
@@ -8,17 +12,10 @@ namespace NSspi.Contexts
/// </summary>
public class SafeContextHandle : SafeSspiHandle
{
/// <summary>
/// Initializes a new instance of the <see cref="SafeContextHandle"/> class.
/// </summary>
public SafeContextHandle()
: base()
{ }
/// <summary>
/// Releases the safe context handle.
/// </summary>
/// <returns></returns>
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
protected override bool ReleaseHandle()
{
@@ -31,4 +28,4 @@ namespace NSspi.Contexts
return status == SecurityStatus.OK;
}
}
}
}

View File

@@ -1,27 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace NSspi.Contexts
{
public class SafeTokenHandle : SafeHandle
{
public SafeTokenHandle() : base( IntPtr.Zero, true )
{
}
public override bool IsInvalid
{
get
{
return handle == IntPtr.Zero || handle == new IntPtr( -1 );
}
}
protected override bool ReleaseHandle()
{
NativeMethods.CloseHandle( this.handle );
return true;
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Principal;
using System.Threading;
using System.Text;
using System.Threading.Tasks;
using NSspi.Buffers;
using NSspi.Credentials;
@@ -12,31 +14,24 @@ namespace NSspi.Contexts
/// </summary>
public class ServerContext : Context
{
private readonly ContextAttrib requestedAttribs;
private readonly bool impersonationSetsThreadPrinciple;
private ContextAttrib requestedAttribs;
private ContextAttrib finalAttribs;
private bool impersonating;
/// <summary>
/// Performs basic initialization of a new instance of the ServerContext class. The
/// ServerContext is not ready for message manipulation until a security context has been
/// established with a client.
/// Performs basic initialization of a new instance of the ServerContext class. The ServerContext
/// is not ready for message manipulation until a security context has been established with a client.
/// </summary>
/// <param name="cred"></param>
/// <param name="requestedAttribs"></param>
/// <param name="impersonationSetsThreadPrinciple">
/// If true, the `Thread.CurrentPrinciple` property will be modified by successful impersonation.
/// </param>
public ServerContext( Credential cred, ContextAttrib requestedAttribs, bool impersonationSetsThreadPrinciple = false ) : base( cred )
public ServerContext(ServerCredential cred, ContextAttrib requestedAttribs) : base ( cred )
{
this.requestedAttribs = requestedAttribs;
this.impersonationSetsThreadPrinciple = impersonationSetsThreadPrinciple;
this.finalAttribs = ContextAttrib.Zero;
this.impersonating = false;
this.SupportsImpersonate = this.Credential.PackageInfo.Capabilities.HasFlag( SecPkgCapability.Impersonation );
}
@@ -55,11 +50,11 @@ namespace NSspi.Contexts
/// This method is performed iteratively to continue and end the authentication cycle with the
/// client. Each stage works by acquiring a token from one side, presenting it to the other side
/// which in turn may generate a new token.
///
///
/// The cycle typically starts and ends with the client. On the first invocation on the client,
/// no server token exists, and null is provided in its place. The client returns its status, providing
/// its output token for the server. The server accepts the clients token as input and provides a
/// token as output to send back to the client. This cycle continues until the server and client
/// its output token for the server. The server accepts the clients token as input and provides a
/// token as output to send back to the client. This cycle continues until the server and client
/// both indicate, typically, a SecurityStatus of 'OK'.
/// </remarks>
/// <param name="clientToken">The most recently received token from the client.</param>
@@ -68,7 +63,7 @@ namespace NSspi.Contexts
/// <returns>A status message indicating the progression of the authentication cycle.
/// A status of 'OK' indicates that the cycle is complete, from the servers's perspective. If the nextToken
/// is not null, it must be sent to the client.
/// A status of 'Continue' indicates that the output token should be sent to the client and
/// A status of 'Continue' indicates that the output token should be sent to the client and
/// a response should be anticipated.</returns>
public SecurityStatus AcceptToken( byte[] clientToken, out byte[] nextToken )
{
@@ -87,21 +82,21 @@ namespace NSspi.Contexts
}
else if( this.Initialized )
{
throw new InvalidOperationException(
throw new InvalidOperationException(
"Attempted to continue initialization of a ServerContext after initialization had completed."
);
}
clientBuffer = new SecureBuffer( clientToken, BufferType.Token );
outBuffer = new SecureBuffer(
new byte[this.Credential.PackageInfo.MaxTokenLength],
BufferType.Token
outBuffer = new SecureBuffer(
new byte[ this.Credential.PackageInfo.MaxTokenLength ],
BufferType.Token
);
using( clientAdapter = new SecureBufferAdapter( clientBuffer ) )
using ( clientAdapter = new SecureBufferAdapter( clientBuffer ) )
{
using( outAdapter = new SecureBufferAdapter( outBuffer ) )
using ( outAdapter = new SecureBufferAdapter( outBuffer ) )
{
if( this.ContextHandle.IsInvalid )
{
@@ -130,15 +125,19 @@ namespace NSspi.Contexts
ref this.finalAttribs,
ref rawExpiry
);
}
}
}
if( status == SecurityStatus.OK )
if ( status == SecurityStatus.OK )
{
nextToken = null;
base.Initialize( rawExpiry.ToDateTime() );
if( outBuffer.Length != 0 )
if ( outBuffer.Length != 0 )
{
nextToken = new byte[outBuffer.Length];
Array.Copy( outBuffer.Buffer, nextToken, nextToken.Length );
@@ -148,7 +147,7 @@ namespace NSspi.Contexts
nextToken = null;
}
}
else if( status == SecurityStatus.ContinueNeeded )
else if ( status == SecurityStatus.ContinueNeeded )
{
nextToken = new byte[outBuffer.Length];
Array.Copy( outBuffer.Buffer, nextToken, nextToken.Length );
@@ -162,17 +161,17 @@ namespace NSspi.Contexts
}
/// <summary>
/// Changes the current thread's security context to impersonate the user of the client.
/// Changes the current thread's security context to impersonate the user of the client.
/// </summary>
/// <remarks>
/// Requires that the security package provided with the server's credentials, as well as the
/// Requires that the security package provided with the server's credentials, as well as the
/// client's credentials, support impersonation.
///
/// Currently, only one thread may initiate impersonation per security context. Impersonation may
///
/// Currently, only one thread may initiate impersonation per security context. Impersonation may
/// follow threads created by the initial impersonation thread, however.
/// </remarks>
/// <returns>A handle to capture the lifetime of the impersonation. Dispose the handle to revert
/// impersonation. If the handle is leaked, the impersonation will automatically revert at a
/// impersonation. If the handle is leaked, the impersonation will automatically revert at a
/// non-deterministic time when the handle is finalized by the Garbage Collector.</returns>
public ImpersonationHandle ImpersonateClient()
{
@@ -184,20 +183,14 @@ namespace NSspi.Contexts
{
throw new ObjectDisposedException( "ServerContext" );
}
else if( this.Initialized == false )
{
throw new InvalidOperationException(
"The server context has not been completely initialized."
);
}
else if( impersonating )
{
throw new InvalidOperationException( "Cannot impersonate again while already impersonating." );
}
else if( this.SupportsImpersonate == false )
{
throw new InvalidOperationException(
"The ServerContext is using a security package that does not support impersonation."
throw new InvalidOperationException(
"The ServerContext is using a security package that does not support impersonation."
);
}
@@ -227,7 +220,7 @@ namespace NSspi.Contexts
this.ContextHandle.DangerousRelease();
this.impersonating = status == SecurityStatus.OK;
this.impersonating = true;
}
}
@@ -243,12 +236,7 @@ namespace NSspi.Contexts
{
throw new SSPIException( "Failed to impersonate the client", status );
}
if( this.impersonating && this.impersonationSetsThreadPrinciple )
{
Thread.CurrentPrincipal = new WindowsPrincipal( (WindowsIdentity)GetRemoteIdentity() );
}
return handle;
}
@@ -294,10 +282,6 @@ namespace NSspi.Contexts
}
}
/// <summary>
/// Releases all resources associted with the ServerContext.
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose( bool disposing )
{
// We were disposed while impersonating. This means that the consumer that is currently holding
@@ -316,4 +300,4 @@ namespace NSspi.Contexts
base.Dispose( disposing );
}
}
}
}

View File

@@ -1,55 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace NSspi.Credentials
{
/// <summary>
/// Provides authentication data in native method calls.
/// </summary>
/// <remarks>
/// Implements the 'SEC_WINNT_AUTH_IDENTITY' structure. See:
///
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa380131(v=vs.85).aspx
/// </remarks>
[StructLayout( LayoutKind.Sequential )]
internal struct NativeAuthData
{
public NativeAuthData( string domain, string username, string password, NativeAuthDataFlag flag )
{
this.Domain = domain;
this.DomainLength = domain.Length;
this.User = username;
this.UserLength = username.Length;
this.Password = password;
this.PasswordLength = password.Length;
this.Flags = flag;
}
[MarshalAs( UnmanagedType.LPWStr )]
public string User;
public int UserLength;
[MarshalAs( UnmanagedType.LPWStr )]
public string Domain;
public int DomainLength;
[MarshalAs( UnmanagedType.LPWStr )]
public string Password;
public int PasswordLength;
public NativeAuthDataFlag Flags;
}
internal enum NativeAuthDataFlag : int
{
Ansi = 1,
Unicode = 2
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Credentials
{
/// <summary>
/// Represents the credentials of the user running the current process, for use as an SSPI client.
/// </summary>
public class ClientCredential : CurrentCredential
{
/// <summary>
/// Initializes a new instance of the ClientCredential class.
/// </summary>
/// <param name="package">The security package to acquire the credential handle from.</param>
public ClientCredential( string package )
: base( package, CredentialUse.Outbound )
{
}
}
}

View File

@@ -1,20 +0,0 @@
using System;
namespace NSspi.Credentials
{
/// <summary>
/// Represents a handle to the credentials of the user running the current process, to be used to
/// authenticate as a client.
/// </summary>
public class ClientCurrentCredential : CurrentCredential
{
/// <summary>
/// Initializes a new instance of the ClientCurrentCredential class.
/// </summary>
/// <param name="package">The security package to acquire the credential handle from.</param>
public ClientCurrentCredential( string package )
: base( package, CredentialUse.Outbound )
{
}
}
}

View File

@@ -1,6 +1,13 @@
using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NSspi.Credentials;
namespace NSspi.Credentials
{
@@ -9,16 +16,16 @@ namespace NSspi.Credentials
/// </summary>
public class Credential : IDisposable
{
/// <summary>
/// The name of the security package that controls the credential.
/// </summary>
private readonly string securityPackage;
/// <summary>
/// Whether the Credential has been disposed.
/// </summary>
private bool disposed;
/// <summary>
/// The name of the security package that controls the credential.
/// </summary>
private string securityPackage;
/// <summary>
/// A safe handle to the credential's handle.
/// </summary>
@@ -35,13 +42,14 @@ namespace NSspi.Credentials
/// <param name="package">The security package to acquire the credential from.</param>
public Credential( string package )
{
this.disposed = false;
this.securityPackage = package;
this.disposed = false;
this.expiry = DateTime.MinValue;
this.PackageInfo = PackageSupport.GetPackageCapabilities( this.SecurityPackage );
}
/// <summary>
/// Gets metadata for the security package associated with the credential.
/// </summary>
@@ -143,7 +151,7 @@ namespace NSspi.Credentials
protected set
{
CheckLifecycle();
this.expiry = value;
}
}
@@ -177,15 +185,11 @@ namespace NSspi.Credentials
GC.SuppressFinalize( this );
}
/// <summary>
/// Releases all resources associted with the credential.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose( bool disposing )
{
if( this.disposed == false )
if ( this.disposed == false )
{
if( disposing )
if ( disposing )
{
this.safeCredHandle.Dispose();
}
@@ -202,4 +206,4 @@ namespace NSspi.Credentials
}
}
}
}
}

View File

@@ -1,12 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NSspi.Credentials;
namespace NSspi.Credentials
{
internal static class CredentialNativeMethods
{
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport( "Secur32.dll", EntryPoint = "AcquireCredentialsHandle", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus AcquireCredentialsHandle(
string principleName,
@@ -20,27 +25,13 @@ namespace NSspi.Credentials
ref TimeStamp expiry
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "Secur32.dll", EntryPoint = "AcquireCredentialsHandle", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus AcquireCredentialsHandle_AuthData(
string principleName,
string packageName,
CredentialUse credentialUse,
IntPtr loginId,
ref NativeAuthData authData,
IntPtr getKeyFunc,
IntPtr getKeyData,
ref RawSspiHandle credentialHandle,
ref TimeStamp expiry
);
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "FreeCredentialsHandle", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus FreeCredentialsHandle(
ref RawSspiHandle credentialHandle
);
/// <summary>
/// The overload of the QueryCredentialsAttribute method that is used for querying the name attribute.
/// In this call, it takes a void* to a structure that contains a wide char pointer. The wide character
@@ -58,4 +49,4 @@ namespace NSspi.Credentials
ref QueryNameAttribCarrier name
);
}
}
}

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Credentials
{
@@ -12,4 +16,4 @@ namespace NSspi.Credentials
/// </summary>
Names = 1,
}
}
}

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Credentials
{
@@ -25,4 +29,4 @@ namespace NSspi.Credentials
/// </summary>
Both = 3,
}
}
}

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Credentials
{
@@ -34,7 +38,7 @@ namespace NSspi.Credentials
this.Handle = new SafeCredentialHandle();
// The finally clause is the actual constrained region. The VM pre-allocates any stack space,
// performs any allocations it needs to prepare methods for execution, and postpones any
// performs any allocations it needs to prepare methods for execution, and postpones any
// instances of the 'uncatchable' exceptions (ThreadAbort, StackOverflow, OutOfMemory).
RuntimeHelpers.PrepareConstrainedRegions();
try { }
@@ -53,12 +57,13 @@ namespace NSspi.Credentials
);
}
if( status != SecurityStatus.OK )
if ( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to call AcquireCredentialHandle", status );
}
this.Expiry = rawExpiry.ToDateTime();
}
}
}
}

View File

@@ -1,78 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
namespace NSspi.Credentials
{
/// <summary>
/// Represents credentials acquired by providing a username, password, and domain.
/// </summary>
public class PasswordCredential : Credential
{
/// <summary>
/// Initializes a new instance of the PasswordCredential class.
/// </summary>
/// <remarks>
/// It is possible to acquire a valid handle to credentials that do not provide a valid
/// username-password combination. The username and password are not validation until the
/// authentication cycle begins.
/// </remarks>
/// <param name="domain">The domain to authenticate to.</param>
/// <param name="username">The username of the user to authenticate as.</param>
/// <param name="password">The user's password.</param>
/// <param name="secPackage">The SSPI security package to create credentials for.</param>
/// <param name="use">
/// Specify inbound when acquiring credentials for a server; outbound for a client.
/// </param>
public PasswordCredential( string domain, string username, string password, string secPackage, CredentialUse use )
: base( secPackage )
{
NativeAuthData authData = new NativeAuthData( domain, username, password, NativeAuthDataFlag.Unicode );
Init( authData, secPackage, use );
}
private void Init( NativeAuthData authData, string secPackage, CredentialUse use )
{
string packageName;
TimeStamp rawExpiry = new TimeStamp();
SecurityStatus status = SecurityStatus.InternalError;
// -- Package --
// Copy off for the call, since this.SecurityPackage is a property.
packageName = this.SecurityPackage;
this.Handle = new SafeCredentialHandle();
// The finally clause is the actual constrained region. The VM pre-allocates any stack space,
// performs any allocations it needs to prepare methods for execution, and postpones any
// instances of the 'uncatchable' exceptions (ThreadAbort, StackOverflow, OutOfMemory).
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
status = CredentialNativeMethods.AcquireCredentialsHandle_AuthData(
null,
packageName,
use,
IntPtr.Zero,
ref authData,
IntPtr.Zero,
IntPtr.Zero,
ref this.Handle.rawHandle,
ref rawExpiry
);
}
if( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to call AcquireCredentialHandle", status );
}
this.Expiry = rawExpiry.ToDateTime();
}
}
}

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Credentials
{
@@ -10,9 +14,9 @@ namespace NSspi.Credentials
internal struct QueryNameAttribCarrier
{
/// <summary>
/// A pointer to a null-terminated ascii-encoded 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

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Credentials
{
@@ -8,17 +12,10 @@ namespace NSspi.Credentials
/// </summary>
public class SafeCredentialHandle : SafeSspiHandle
{
/// <summary>
/// Initializes a new instance of the <see cref="SafeCredentialHandle"/> class.
/// </summary>
public SafeCredentialHandle()
: base()
{ }
/// <summary>
/// Releases the resources held by the credential handle.
/// </summary>
/// <returns></returns>
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
protected override bool ReleaseHandle()
{
@@ -31,4 +28,5 @@ namespace NSspi.Credentials
return status == SecurityStatus.OK;
}
}
}
}

View File

@@ -1,21 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Credentials
{
/// <summary>
/// Represents a handle to the credentials of the user running the current process, to be used to
/// authenticate as a server.
/// Represents the credentials of the user running the current process, for use as an SSPI server.
/// </summary>
public class ServerCurrentCredential : CurrentCredential
public class ServerCredential : CurrentCredential
{
/// <summary>
/// Initializes a new instance of the ServerCredential class, acquiring credentials from
/// 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 ServerCurrentCredential( string package )
public ServerCredential( string package )
: base( package, CredentialUse.Inbound )
{
}
}
}
}

View File

@@ -1,82 +0,0 @@
using System;
using System.Reflection;
namespace NSspi
{
/// <summary>
/// Tags an enumeration member with a string that can be programmatically accessed.
/// </summary>
[AttributeUsage( AttributeTargets.Field )]
public class EnumStringAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="EnumStringAttribute"/> class.
/// </summary>
/// <param name="text">The string to associate with the enumeration member.</param>
public EnumStringAttribute( string text )
{
this.Text = text;
}
/// <summary>
/// Gets the string associated with the enumeration member.
/// </summary>
public string Text { get; private set; }
}
/// <summary>
/// Converts betwen enumeration members and the strings associated to the members through the
/// <see cref="EnumStringAttribute"/> type.
/// </summary>
public class EnumMgr
{
/// <summary>
/// Gets the text associated with the given enumeration member through a <see cref="EnumStringAttribute"/>.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string ToText( Enum value )
{
FieldInfo field = value.GetType().GetField( value.ToString() );
EnumStringAttribute[] attribs = (EnumStringAttribute[])field.GetCustomAttributes( typeof( EnumStringAttribute ), false );
if( attribs == null || attribs.Length == 0 )
{
return null;
}
else
{
return attribs[0].Text;
}
}
/// <summary>
/// Returns the enumeration member that is tagged with the given text using the <see cref="EnumStringAttribute"/> type.
/// </summary>
/// <typeparam name="T">The enumeration type to inspect.</typeparam>
/// <param name="text"></param>
/// <returns></returns>
public static T FromText<T>( string text )
{
FieldInfo[] fields = typeof( T ).GetFields();
EnumStringAttribute[] attribs;
foreach( FieldInfo field in fields )
{
attribs = (EnumStringAttribute[])field.GetCustomAttributes( typeof( EnumStringAttribute ), false );
foreach( EnumStringAttribute attrib in attribs )
{
if( attrib.Text == text )
{
return (T)field.GetValue( null );
}
}
}
throw new ArgumentException( "Could not find a matching enumeration value for the text '" + text + "'." );
}
}
}

View File

@@ -1,31 +1,95 @@
<Project Sdk="Microsoft.NET.Sdk">
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net40</TargetFrameworks>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4B4CD933-BF62-4F92-B8FA-6771758C5197}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NSspi</RootNamespace>
<AssemblyName>NSspi</AssemblyName>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Kevin Thompson</Authors>
<PackageReleaseNotes>Adds support for accessing the remote identity.</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/antiduh/nsspi</PackageProjectUrl>
<Version>0.3.1.0</Version>
<SignAssembly>true</SignAssembly>
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>nsspi key.snk</AssemblyOriginatorKeyFile>
<PackageLicenseFile>License.txt</PackageLicenseFile>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\NSspi.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<StartupObject>
</StartupObject>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Security.Principal.Windows" Version="4.5.1" />
</ItemGroup>
<ItemGroup>
<None Include="..\License.txt">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.DirectoryServices" />
<Reference Include="System.DirectoryServices.AccountManagement" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ByteWriter.cs" />
<Compile Include="Contexts\ClientContext.cs" />
<Compile Include="Contexts\Context.cs" />
<Compile Include="Contexts\ContextAttrib.cs" />
<Compile Include="Contexts\ContextNativeMethods.cs" />
<Compile Include="Contexts\ContextQueries.cs" />
<Compile Include="Contexts\ContextQueryAttrib.cs" />
<Compile Include="Contexts\ImpersonationHandle.cs" />
<Compile Include="Contexts\SafeContextHandle.cs" />
<Compile Include="Credentials\CurrentCredential.cs" />
<Compile Include="SecPkgInfo.cs" />
<Compile Include="Contexts\ServerContext.cs" />
<Compile Include="Credentials\ClientCredential.cs" />
<Compile Include="Credentials\Credential.cs" />
<Compile Include="Credentials\CredentialNativeMethods.cs" />
<Compile Include="Credentials\CredentialQueryAttrib.cs" />
<Compile Include="Credentials\CredentialUse.cs" />
<Compile Include="Credentials\QueryNameSupport.cs" />
<Compile Include="Credentials\SafeCredentialHandle.cs" />
<Compile Include="Credentials\ServerCredential.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="PackageSupport.cs" />
<Compile Include="PackageNames.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SecureBuffer\SecureBuffer.cs" />
<Compile Include="SecureBuffer\SecureBufferAdapter.cs" />
<Compile Include="SecureBuffer\SecureBufferDataRep.cs" />
<Compile Include="SecureBuffer\SecureBufferDesc.cs" />
<Compile Include="SecureBuffer\SecureBufferType.cs" />
<Compile Include="SecurityStatus.cs" />
<Compile Include="SSPIException.cs" />
<Compile Include="SspiHandle.cs" />
<Compile Include="TimeStamp.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -1,15 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NSspi.Contexts;
namespace NSspi
{
internal static class NativeMethods
{
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success)]
[DllImport( "Secur32.dll", EntryPoint = "FreeContextBuffer", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus FreeContextBuffer( IntPtr buffer );
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "QuerySecurityPackageInfo", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus QuerySecurityPackageInfo( string packageName, ref IntPtr pkgInfo );
@@ -18,8 +24,5 @@ namespace NSspi
[DllImport( "Secur32.dll", EntryPoint = "EnumerateSecurityPackages", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus EnumerateSecurityPackages( ref int numPackages, ref IntPtr pkgInfoArry );
[DllImport( "Kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true )]
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
internal static extern bool CloseHandle( IntPtr handle );
}
}
}

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
@@ -22,4 +26,4 @@ namespace NSspi
/// </summary>
public const string Ntlm = "NTLM";
}
}
}

View File

@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
@@ -20,7 +24,7 @@ namespace NSspi
SecurityStatus status = SecurityStatus.InternalError;
IntPtr rawInfoPtr;
rawInfoPtr = new IntPtr();
info = new SecPkgInfo();
@@ -31,11 +35,11 @@ namespace NSspi
{
status = NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr );
if( rawInfoPtr != IntPtr.Zero )
if ( rawInfoPtr != IntPtr.Zero )
{
try
{
if( status == SecurityStatus.OK )
if ( status == SecurityStatus.OK )
{
// This performs allocations as it makes room for the strings contained in the SecPkgInfo class.
Marshal.PtrToStructure( rawInfoPtr, info );
@@ -67,7 +71,7 @@ namespace NSspi
IntPtr pkgArrayPtr;
IntPtr pkgPtr;
int numPackages = 0;
int pkgSize = Marshal.SizeOf( typeof( SecPkgInfo ) );
int pkgSize = Marshal.SizeOf( typeof(SecPkgInfo) );
pkgArrayPtr = new IntPtr();
@@ -86,7 +90,7 @@ namespace NSspi
// Bwooop Bwooop Alocation Alert
// 1) We allocate the array
// 2) We allocate the individual elements in the array (they're class objects).
// 3) We allocate the strings in the individual elements in the array when we
// 3) We allocate the strings in the individual elements in the array when we
// call Marshal.PtrToStructure()
packages = new SecPkgInfo[numPackages];
@@ -95,7 +99,7 @@ namespace NSspi
{
packages[i] = new SecPkgInfo();
}
for( int i = 0; i < numPackages; i++ )
{
pkgPtr = IntPtr.Add( pkgArrayPtr, i * pkgSize );
@@ -119,4 +123,4 @@ namespace NSspi
return packages;
}
}
}
}

View File

@@ -1,9 +1,36 @@
using System.Runtime.InteropServices;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("NSspi")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Kevin Thompson")]
[assembly: AssemblyProduct("NSspi")]
[assembly: AssemblyCopyright("Copyright © Kevin Thompson 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid( "9abf710c-c646-42aa-8183-76bfa141a07b" )]
[assembly: Guid("9abf710c-c646-42aa-8183-76bfa141a07b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
@@ -22,7 +26,7 @@ namespace NSspi
this.message = message;
this.errorCode = errorCode;
}
/// <summary>
/// Initializes a new instance of the SSPIException class from serialization data.
/// </summary>
@@ -31,7 +35,7 @@ namespace NSspi
protected SSPIException( SerializationInfo info, StreamingContext context )
: base( info, context )
{
this.message = info.GetString( "message" );
this.message = info.GetString( "messsage" );
this.errorCode = (SecurityStatus)info.GetUInt32( "errorCode" );
}
@@ -66,13 +70,8 @@ namespace NSspi
{
get
{
return string.Format(
"{0}. Error Code = '0x{1:X}' - \"{2}\".",
this.message,
this.errorCode,
EnumMgr.ToText( this.errorCode )
);
return string.Format( "{0}. Error Code = '{1:X}'.", this.message, this.errorCode );
}
}
}
}
}

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
@@ -43,55 +47,55 @@ namespace NSspi
}
/// <summary>
/// Describes the capabilities of a security package.
/// Describes the capabilities of a security package.
/// </summary>
[Flags]
public enum SecPkgCapability : uint
public enum SecPkgCapability : uint
{
/// <summary>
/// Whether the package supports generating messages with integrity information. Required for MakeSignature and VerifySignature.
/// </summary>
Integrity = 0x1,
Integrity = 0x1,
/// <summary>
/// Whether the package supports generating encrypted messages. Required for EncryptMessage and DecryptMessage.
/// </summary>
Privacy = 0x2,
Privacy = 0x2,
/// <summary>
/// Whether the package uses any other buffer information than token buffers.
/// </summary>
TokenOnly = 0x4,
TokenOnly = 0x4,
/// <summary>
/// Whether the package supports datagram-style authentication.
/// </summary>
Datagram = 0x8,
Datagram = 0x8,
/// <summary>
/// Whether the package supports creating contexts with connection semantics
/// </summary>
Connection = 0x10,
Connection = 0x10,
/// <summary>
/// Multiple legs are neccessary for authentication.
/// </summary>
MultiLeg = 0x20,
MultiLeg = 0x20,
/// <summary>
/// Server authentication is not supported.
/// </summary>
ClientOnly = 0x40,
ClientOnly = 0x40,
/// <summary>
/// Supports extended error handling facilities.
/// </summary>
ExtendedError = 0x80,
ExtendedError = 0x80,
/// <summary>
/// Supports client impersonation on the server.
/// </summary>
Impersonation = 0x100,
Impersonation = 0x100,
/// <summary>
/// Understands Windows princple and target names.
@@ -101,45 +105,45 @@ namespace NSspi
/// <summary>
/// Supports stream semantics
/// </summary>
Stream = 0x400,
Stream = 0x400,
/// <summary>
/// Package may be used by the Negiotiate meta-package.
/// </summary>
Negotiable = 0x800,
Negotiable = 0x800,
/// <summary>
/// Compatible with GSS.
/// </summary>
GssCompatible = 0x1000,
GssCompatible = 0x1000,
/// <summary>
/// Supports LsaLogonUser
/// </summary>
Logon = 0x2000,
Logon = 0x2000,
/// <summary>
/// Token buffers are in Ascii format.
/// </summary>
AsciiBuffers = 0x4000,
AsciiBuffers = 0x4000,
/// <summary>
/// Supports separating large tokens into multiple buffers.
/// </summary>
Fragment = 0x8000,
Fragment = 0x8000,
/// <summary>
/// Supports mutual authentication between a client and server.
/// </summary>
MutualAuth = 0x10000,
MutualAuth = 0x10000,
/// <summary>
/// Supports credential delegation from the server to a third context.
/// </summary>
Delegation = 0x20000,
Delegation = 0x20000,
/// <summary>
/// Supports calling EncryptMessage with the read-only-checksum flag, which protects data only
/// Supports calling EncryptMessage with the read-only-checksum flag, which protects data only
/// with a checksum and does not encrypt it.
/// </summary>
ReadOnlyChecksum = 0x40000,
@@ -153,11 +157,11 @@ namespace NSspi
/// <summary>
/// Extends the negotiate package; only one such package may be registered at any time.
/// </summary>
ExtendsNego = 0x00100000,
ExtendsNego = 0x00100000,
/// <summary>
/// This package is negotiated by the package of type ExtendsNego.
/// </summary>
Negotiable2 = 0x00200000,
Negotiable2 = 0x00200000,
}
}
}

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Buffers
{
@@ -33,10 +37,10 @@ namespace NSspi.Buffers
/// </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
/// 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>
@@ -46,7 +50,7 @@ namespace NSspi.Buffers
/// 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
/// <param name="type">The type or purpose of the buffer, for purposes of
/// invoking the native API.</param>
public SecureBuffer( byte[] buffer, BufferType type )
{
@@ -71,4 +75,4 @@ namespace NSspi.Buffers
/// </summary>
public int Length { get; internal set; }
}
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Buffers
{
@@ -10,52 +13,52 @@ namespace NSspi.Buffers
/// </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
///
/// 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
/// * 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.
///
/// 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
/// |-- 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.
///
/// 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
@@ -117,7 +120,7 @@ namespace NSspi.Buffers
this.bufferHandles = new GCHandle[this.buffers.Count];
this.bufferCarrier = new SecureBufferInternal[this.buffers.Count];
for( int i = 0; i < this.buffers.Count; i++ )
for ( int i = 0; i < this.buffers.Count; i++ )
{
this.bufferHandles[i] = GCHandle.Alloc( this.buffers[i].Buffer, GCHandleType.Pinned );
@@ -153,7 +156,7 @@ namespace NSspi.Buffers
{
get
{
if( this.disposed )
if ( this.disposed )
{
throw new ObjectDisposedException( "Cannot use SecureBufferListHandle after it has been disposed" );
}
@@ -181,11 +184,11 @@ namespace NSspi.Buffers
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
private void Dispose( bool disposing )
{
if( this.disposed == true ) { return; }
if ( this.disposed == true ) { return; }
if( disposing )
if ( disposing )
{
// When this class is actually being used for its original purpose - to convey buffers
// When this class is actually being used for its original purpose - to convey buffers
// back and forth to SSPI calls - we need to copy the potentially modified structure members
// back to our caller's buffer.
for( int i = 0; i < this.buffers.Count; i++ )
@@ -215,4 +218,4 @@ namespace NSspi.Buffers
this.disposed = true;
}
}
}
}

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Buffers
{
@@ -18,4 +22,4 @@ namespace NSspi.Buffers
/// </summary>
Network = 0x00
}
}
}

View File

@@ -1,5 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Buffers
{
@@ -7,7 +12,7 @@ namespace NSspi.Buffers
/// Represents the native layout of the secure buffer descriptor that is provided directly
/// to native API calls.
/// </summary>
[StructLayout( LayoutKind.Sequential )]
[StructLayout( LayoutKind.Sequential)]
internal struct SecureBufferDescInternal
{
/// <summary>
@@ -30,4 +35,4 @@ namespace NSspi.Buffers
/// </summary>
public const int ApiVersion = 0;
}
}
}

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi.Buffers
{
@@ -34,7 +38,7 @@ namespace NSspi.Buffers
/// <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
/// 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,
@@ -53,7 +57,7 @@ namespace NSspi.Buffers
Stream = 0x0A,
ChannelBindings = 0x0E,
TargetHost = 0x10,
ReadOnlyFlag = unchecked((int)0x80000000),
ReadOnlyFlag = unchecked( (int)0x80000000 ),
ReadOnlyWithChecksum = 0x10000000
}
}
}

View File

@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
/*
// From winerror.h
// From winerror.h
#define SEC_E_OK ((HRESULT)0x00000000L)
#define SEC_E_INSUFFICIENT_MEMORY _HRESULT_TYPEDEF_(0x80090300L)
#define SEC_E_INVALID_HANDLE _HRESULT_TYPEDEF_(0x80090301L)
@@ -26,158 +30,56 @@ namespace NSspi
/// <summary>
/// The request completed successfully
/// </summary>
[EnumString( "No error" )]
OK = 0x00000000,
OK = 0x00000000,
/// <summary>
/// The token returned by the context needs to be provided to the cooperating party
/// to continue construction of the context.
/// </summary>
[EnumString( "Authentication cycle needs to continue" )]
ContinueNeeded = 0x00090312,
ContinueNeeded = 0x00090312,
/// <summary>
/// Occurs after a client calls InitializeSecurityContext to indicate that the client
/// must call CompleteAuthToken.
/// </summary>
[EnumString( "Authentication cycle needs to perform a 'complete'." )]
CompleteNeeded = 0x00090313,
CompleteNeeded = 0x00090313,
/// <summary>
/// Occurs after a client calls InitializeSecurityContext to indicate that the client
/// must call CompleteAuthToken and pass the result to the server.
/// </summary>
[EnumString( "Authentication cycle needs to perform a 'complete' and then continue." )]
CompAndContinue = 0x00090314,
CompAndContinue = 0x00090314,
/// <summary>
/// An attempt to use the context was performed after the context's expiration time elapsed.
/// </summary>
[EnumString( "The security context was used after its expiration time passed." )]
ContextExpired = 0x00090317,
ContextExpired = 0x00090317,
CredentialsNeeded = 0x00090320,
Renegotiate = 0x00090321,
/// <summary>
/// The credentials supplied to the security context were not fully initialized.
/// </summary>
[EnumString( "The credentials supplied to the security context were not fully initialized." )]
CredentialsNeeded = 0x00090320,
/// <summary>
/// The context data must be re-negotiated with the peer.
/// </summary>
[EnumString( "The context data must be re-negotiated with the peer." )]
Renegotiate = 0x00090321,
// -------------- Errors --------------
/// <summary>
/// The SSPI operation failed due to insufficient memory resources.
/// </summary>
[EnumString( "Not enough memory." )]
OutOfMemory = 0x80090300,
/// <summary>
/// The handle provided to the API was invalid.
/// </summary>
[EnumString( "The handle provided to the API was invalid." )]
InvalidHandle = 0x80090301,
/// <summary>
/// The attempted operation is not supported.
/// </summary>
[EnumString( "The attempted operation is not supported." )]
Unsupported = 0x80090302,
/// <summary>
/// The specified principle is not known in the authentication system.
/// </summary>
[EnumString( "The specified principle is not known in the authentication system." )]
TargetUnknown = 0x80090303,
/// <summary>
/// An internal error occurred
/// </summary>
[EnumString( "An internal error occurred." )]
InternalError = 0x80090304,
// Errors
OutOfMemory = 0x80090300,
InvalidHandle = 0x80090301,
Unsupported = 0x80090302,
TargetUnknown = 0x80090303,
InternalError = 0x80090304,
/// <summary>
/// No security provider package was found with the given name.
/// </summary>
[EnumString( "The requested security package was not found." )]
PackageNotFound = 0x80090305,
PackageNotFound = 0x80090305,
/// <summary>
/// Cannot use the provided credentials, the caller is not the owner of the credentials.
/// </summary>
[EnumString( "The caller is not the owner of the desired credentials." )]
NotOwner = 0x80090306,
/// <summary>
/// The requested security package failed to initalize, and thus cannot be used.
/// </summary>
[EnumString( "The requested security package failed to initalize, and thus cannot be used." )]
CannotInstall = 0x80090307,
/// <summary>
/// A token was provided that contained incorrect or corrupted data.
/// </summary>
[EnumString( "The provided authentication token is invalid or corrupted." )]
InvalidToken = 0x80090308,
/// <summary>
/// The security package is not able to marshall the logon buffer, so the logon attempt has failed
/// </summary>
[EnumString( "The security package is not able to marshall the logon buffer, so the logon attempt has failed." )]
CannotPack = 0x80090309,
/// <summary>
/// The per-message Quality of Protection is not supported by the security package.
/// </summary>
[EnumString( "The per-message Quality of Protection is not supported by the security package." )]
QopNotSupported = 0x8009030A,
/// <summary>
/// Impersonation is not supported.
/// </summary>
[EnumString( "Impersonation is not supported with the current security package." )]
NoImpersonation = 0x8009030B,
/// <summary>
/// The logon was denied, perhaps because the provided credentials were incorrect.
/// </summary>
[EnumString( "The logon was denied, perhaps because the provided credentials were incorrect." )]
LogonDenied = 0x8009030C,
/// <summary>
/// The credentials provided are not recognized by the selected security package.
/// </summary>
[EnumString( "The credentials provided are not recognized by the selected security package." )]
UnknownCredentials = 0x8009030D,
/// <summary>
/// No credentials are available in the selected security package.
/// </summary>
[EnumString( "No credentials are available in the selected security package." )]
NoCredentials = 0x8009030E,
/// <summary>
/// A message that was provided to the Decrypt or VerifySignature functions was altered after
/// it was created.
/// </summary>
[EnumString( "A message that was provided to the Decrypt or VerifySignature functions was altered " +
"after it was created." )]
MessageAltered = 0x8009030F,
/// <summary>
/// A message was received out of the expected order.
/// </summary>
[EnumString( "A message was received out of the expected order." )]
OutOfSequence = 0x80090310,
/// <summary>
/// The current security package cannot contact an authenticating authority.
/// </summary>
[EnumString( "The current security package cannot contact an authenticating authority." )]
NotOwner = 0x80090306,
CannotInstall = 0x80090307,
InvalidToken = 0x80090308,
CannotPack = 0x80090309,
QopNotSupported = 0x8009030A,
NoImpersonation = 0x8009030B,
LogonDenied = 0x8009030C,
UnknownCredentials = 0x8009030D,
NoCredentials = 0x8009030E,
MessageAltered = 0x8009030F,
OutOfSequence = 0x80090310,
NoAuthenticatingAuthority = 0x80090311,
/// <summary>
@@ -186,93 +88,25 @@ namespace NSspi
/// <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.
/// 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>
[EnumString( "The buffer provided to an SSPI API call contained a message that was not complete." )]
IncompleteMessage = 0x80090318,
/// <summary>
/// The credentials supplied were not complete, and could not be verified. The context could not be initialized.
/// </summary>
[EnumString( "The credentials supplied were not complete, and could not be verified. The context could not be initialized." )]
IncompleteMessage = 0x80090318,
IncompleteCredentials = 0x80090320,
/// <summary>
/// The buffers supplied to a security function were too small.
/// </summary>
[EnumString( "The buffers supplied to a security function were too small." )]
BufferNotEnough = 0x80090321,
/// <summary>
/// The target principal name is incorrect.
/// </summary>
[EnumString( "The target principal name is incorrect." )]
WrongPrincipal = 0x80090322,
/// <summary>
/// The clocks on the client and server machines are skewed.
/// </summary>
[EnumString( "The clocks on the client and server machines are skewed." )]
TimeSkew = 0x80090324,
/// <summary>
/// The certificate chain was issued by an authority that is not trusted.
/// </summary>
[EnumString( "The certificate chain was issued by an authority that is not trusted." )]
UntrustedRoot = 0x80090325,
/// <summary>
/// The message received was unexpected or badly formatted.
/// </summary>
[EnumString( "The message received was unexpected or badly formatted." )]
IllegalMessage = 0x80090326,
/// <summary>
/// An unknown error occurred while processing the certificate.
/// </summary>
[EnumString( "An unknown error occurred while processing the certificate." )]
CertUnknown = 0x80090327,
/// <summary>
/// The received certificate has expired.
/// </summary>
[EnumString( "The received certificate has expired." )]
CertExpired = 0x80090328,
/// <summary>
/// The client and server cannot communicate, because they do not possess a common algorithm.
/// </summary>
[EnumString( "The client and server cannot communicate, because they do not possess a common algorithm." )]
AlgorithmMismatch = 0x80090331,
/// <summary>
/// The security context could not be established due to a failure in the requested quality
/// of service (e.g. mutual authentication or delegation).
/// </summary>
[EnumString( "The security context could not be established due to a failure in the requested " +
"quality of service (e.g. mutual authentication or delegation)." )]
SecurityQosFailed = 0x80090332,
/// <summary>
/// Smartcard logon is required and was not used.
/// </summary>
[EnumString( "Smartcard logon is required and was not used." )]
BufferNotEnough = 0x80090321,
WrongPrincipal = 0x80090322,
TimeSkew = 0x80090324,
UntrustedRoot = 0x80090325,
IllegalMessage = 0x80090326,
CertUnknown = 0x80090327,
CertExpired = 0x80090328,
AlgorithmMismatch = 0x80090331,
SecurityQosFailed = 0x80090332,
SmartcardLogonRequired = 0x8009033E,
/// <summary>
/// An unsupported preauthentication mechanism was presented to the Kerberos package.
/// </summary>
[EnumString( "An unsupported preauthentication mechanism was presented to the Kerberos package." )]
UnsupportedPreauth = 0x80090343,
/// <summary>
/// Client's supplied SSPI channel bindings were incorrect.
/// </summary>
[EnumString( "Client's supplied SSPI channel bindings were incorrect." )]
BadBinding = 0x80090346
UnsupportedPreauth = 0x80090343,
BadBinding = 0x80090346
}
/// <summary>
@@ -290,4 +124,5 @@ namespace NSspi
return (uint)status > 0x80000000u;
}
}
}
}

View File

@@ -1,25 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NSspi.Contexts;
namespace NSspi
{
/// <summary>
/// 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.
/// 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:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380495(v=vs.85).aspx
///
///
/// 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 performed a CER
/// that employs handle reference counting across the native API invocation.
/// </remarks>
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
[StructLayout( LayoutKind.Sequential, Pack = 1 ) ]
internal struct RawSspiHandle
{
private IntPtr lowPart;
@@ -40,7 +46,7 @@ namespace NSspi
/// <remarks>
/// This method is executed in a CER during handle release.
/// </remarks>
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success)]
public void SetInvalid()
{
this.lowPart = IntPtr.Zero;
@@ -55,27 +61,17 @@ namespace NSspi
{
internal RawSspiHandle rawHandle;
/// <summary>
/// Initializes a new instance of the <see cref="SafeSspiHandle"/> class.
/// </summary>
protected SafeSspiHandle()
: base( IntPtr.Zero, true )
{
this.rawHandle = new RawSspiHandle();
}
/// <summary>
/// Gets whether the handle is invalid.
/// </summary>
public override bool IsInvalid
{
get { return IsClosed || this.rawHandle.IsZero(); }
get { return IsClosed || this.rawHandle.IsZero(); }
}
/// <summary>
/// Marks the handle as no longer being in use.
/// </summary>
/// <returns></returns>
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
protected override bool ReleaseHandle()
{
@@ -83,4 +79,4 @@ namespace NSspi
return true;
}
}
}
}

View File

@@ -1,18 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
/// <summary>
/// Represents a Windows API Timestamp structure, which stores time in units of 100 nanosecond
/// 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
{
/// <summary>
/// Returns the calendar date and time corresponding a zero timestamp.
/// </summary>
public static readonly DateTime Epoch = new DateTime( 1601, 1, 1, 0, 0, 0, DateTimeKind.Utc );
/// <summary>
@@ -28,13 +29,13 @@ namespace NSspi
/// <returns></returns>
public DateTime ToDateTime()
{
ulong test = (ulong)this.time + (ulong)( Epoch.Ticks );
ulong test = (ulong)this.time + (ulong)(Epoch.Ticks);
// Sometimes the value returned is massive, eg, 0x7fffff154e84ffff, which is a value
// 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 )
if ( test > (ulong)DateTime.MaxValue.Ticks )
{
return DateTime.MaxValue;
}
@@ -44,4 +45,4 @@ namespace NSspi
}
}
}
}
}

Binary file not shown.

View File

@@ -1,2 +0,0 @@
bin
obj

View File

@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NsspiDemo</RootNamespace>
<AssemblyName>NsspiDemo</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
@@ -22,7 +22,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -32,7 +31,6 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View File

@@ -16,10 +16,10 @@ namespace NSspi
private static void CredTest( string packageName )
{
ClientCurrentCredential clientCred = null;
ClientCredential clientCred = null;
ClientContext client = null;
ServerCurrentCredential serverCred = null;
ServerCredential serverCred = null;
ServerContext server = null;
byte[] clientToken;
@@ -30,23 +30,24 @@ namespace NSspi
try
{
clientCred = new ClientCurrentCredential( packageName );
serverCred = new ServerCurrentCredential( packageName );
clientCred = new ClientCredential( packageName );
serverCred = new ServerCredential( packageName );
Console.Out.WriteLine( clientCred.PrincipleName );
client = new ClientContext(
clientCred,
serverCred.PrincipleName,
ContextAttrib.MutualAuth |
client = new ClientContext(
clientCred,
serverCred.PrincipleName,
ContextAttrib.MutualAuth |
ContextAttrib.InitIdentify |
ContextAttrib.Confidentiality |
ContextAttrib.ReplayDetect |
ContextAttrib.SequenceDetect |
ContextAttrib.SequenceDetect |
ContextAttrib.Connection |
ContextAttrib.Delegate
);
server = new ServerContext(
serverCred,
ContextAttrib.MutualAuth |
@@ -63,17 +64,18 @@ namespace NSspi
clientStatus = client.Init( serverToken, out clientToken );
while( true )
while ( true )
{
serverStatus = server.AcceptToken( clientToken, out serverToken );
if( serverStatus != SecurityStatus.ContinueNeeded && clientStatus != SecurityStatus.ContinueNeeded ) { break; }
if ( serverStatus != SecurityStatus.ContinueNeeded && clientStatus != SecurityStatus.ContinueNeeded ) { break; }
clientStatus = client.Init( serverToken, out clientToken );
if( serverStatus != SecurityStatus.ContinueNeeded && clientStatus != SecurityStatus.ContinueNeeded ) { break; }
if ( serverStatus != SecurityStatus.ContinueNeeded && clientStatus != SecurityStatus.ContinueNeeded ) { break; }
}
Console.Out.WriteLine( "Server authority: " + server.AuthorityName );
Console.Out.WriteLine( "Server context user: " + server.ContextUserName );
@@ -100,7 +102,7 @@ namespace NSspi
throw new Exception();
}
for( int i = 0; i < plainText.Length; i++ )
for( int i= 0; i < plainText.Length; i++ )
{
if( plainText[i] != roundTripPlaintext[i] )
{
@@ -115,23 +117,25 @@ namespace NSspi
throw new Exception();
}
using( server.ImpersonateClient() )
{
}
cipherText = client.MakeSignature( plainText );
bool goodSig = server.VerifySignature( cipherText, out roundTripPlaintext );
if( goodSig == false ||
if ( goodSig == false ||
roundTripPlaintext.Length != plainText.Length )
{
throw new Exception();
}
for( int i = 0; i < plainText.Length; i++ )
for ( int i = 0; i < plainText.Length; i++ )
{
if( plainText[i] != roundTripPlaintext[i] )
if ( plainText[i] != roundTripPlaintext[i] )
{
throw new Exception();
}
@@ -141,12 +145,12 @@ namespace NSspi
}
finally
{
if( server != null )
if ( server != null )
{
server.Dispose();
}
if( client != null )
if ( client != null )
{
client.Dispose();
}
@@ -156,11 +160,11 @@ namespace NSspi
clientCred.Dispose();
}
if( serverCred != null )
if ( serverCred != null )
{
serverCred.Dispose();
}
}
}
}
}
}

View File

@@ -1,7 +1,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle( "NsspiDemo" )]
@@ -13,8 +14,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]
@@ -24,12 +25,12 @@ using System.Runtime.InteropServices;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion( "1.0.0.0" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -19,7 +19,7 @@ namespace NsspiDemo.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -12,7 +12,7 @@ namespace NsspiDemo.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

70
Readme.txt Normal file
View File

@@ -0,0 +1,70 @@
This projects provides a C# / .Net interface to the Windows Integrated Authentication API,
better known as SSPI (Security Service Provider Interface).
The project is provided as a .Net 4.0 assembly, but can just as easily be upgraded to .Net 4.5
or later. The solution file can be opened by Visual Studio 2010 SP1, Visual Studio 2012, or
later Visual Studio editions.
The SSPI API provides an interface for real authentication protocols, such as Kerberos or
NTLM, to be invoked transparently by client and server code in order to perform authentication
and message manipulation. These authentication protocols are better known as 'security packages'.
The SSPI API exposes these packages using a common API, and so a program may invoke one or the
other with only minor changes in implementation. SSPI also supports the 'negotiate' 'meta'
package, that allows a client and server to decide dynamically which real security provider to
use, and then itself provides a passthrough interface to the real package.
==== Usage ====
Typically, a client acquires some form of a credential, either from the currently logged on
user's security context, by acquiring a username and password from the user, or by some other
means. The server acquires a credential in a similar manner. Each uses their credentials to
identify themselves to each other.
A client and a server each start with uninitialized security contexts. They exchange negotiation
and authentication tokens to perform authentication, and if all succeeds, they create a shared
security context in the form of a client's context and a server's context. The effectively shared
context agrees on the security package to use (kerberos, NTLM), and what parameters to use
for message passing. Every new client that authenticates with a server creates a new security
context specific to that client-server pairing.
From the software perspective, a client security context initializes itself by exchanging
authentication tokens with a server; the server initializes itself by exchanging authentication
tokens with the client.
This API provides raw access to the authentication tokens created during the negotiation and
authentication process. In this manner, any application can integrate SSPI-based authentication
by deciding for themselves how to integrate the tokens into their application protocol.
The project is broken up into 3 chunks:
* The NSspi library, which provides safe, managed access to the SSPI API.
* NsspiDemo, a quick demo program to show how to exercise the features of NSspi locally.
* UI demo programs TestClient and TestServer (that have a common dependency on TestProtocol) that
may be run on separate machines, that show how one might integrate SSPI into a custom
application.
==== More information ====
If you would like to understand the SSPI API, feel free to browse the following references:
MSDN documentation on the SSPI API
http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731(v=vs.85).aspx
MSDN article on SSPI along with a sample Managed C++ SSPI library and UI client/servers.
http://msdn.microsoft.com/en-us/library/ms973911.aspx
Relevant StackOverflow questions:
"Client-server authentication - using SSPI?"
- http://stackoverflow.com/questions/17241365/
"Validate Windows Identity Token"
- http://stackoverflow.com/questions/11238141/
"How to deal with allocations in constrained execution regions?"
- http://stackoverflow.com/questions/24442209/
"AcquireCredentialsHandle returns massive expiration time"
- http://stackoverflow.com/questions/24478056/

View File

@@ -1,2 +0,0 @@
bin
obj

View File

@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

View File

@@ -148,7 +148,6 @@
this.sendTextbox.Location = new System.Drawing.Point(6, 19);
this.sendTextbox.Multiline = true;
this.sendTextbox.Name = "sendTextbox";
this.sendTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.sendTextbox.Size = new System.Drawing.Size(302, 298);
this.sendTextbox.TabIndex = 7;
//
@@ -191,7 +190,6 @@
this.receiveTextbox.Location = new System.Drawing.Point(3, 16);
this.receiveTextbox.Multiline = true;
this.receiveTextbox.Name = "receiveTextbox";
this.receiveTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.receiveTextbox.Size = new System.Drawing.Size(308, 338);
this.receiveTextbox.TabIndex = 10;
//
@@ -233,7 +231,7 @@
this.disconnectButton.Text = "Disconnect";
this.disconnectButton.UseVisualStyleBackColor = true;
//
// ClientForm
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
@@ -246,8 +244,8 @@
this.Controls.Add(this.serverTextBox);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Name = "ClientForm";
this.Text = "Client - SSPI Demo";
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.portNumeric)).EndInit();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();

View File

@@ -1,6 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using NSspi;
using NSspi.Contexts;
@@ -14,7 +20,7 @@ namespace TestClient
public partial class ClientForm : Form
{
private ClientContext context;
private ClientCurrentCredential cred;
private ClientCredential cred;
private CustomConnection connection;
@@ -42,16 +48,16 @@ namespace TestClient
this.FormClosing += Form1_FormClosing;
// --- SSPI ---
this.cred = new ClientCurrentCredential( PackageNames.Negotiate );
this.cred = new ClientCredential( PackageNames.Negotiate );
this.context = new ClientContext(
cred,
"",
ContextAttrib.InitIntegrity |
ContextAttrib.ReplayDetect |
ContextAttrib.SequenceDetect |
ContextAttrib.MutualAuth |
ContextAttrib.Delegate |
ContextAttrib.InitIntegrity |
ContextAttrib.ReplayDetect |
ContextAttrib.SequenceDetect |
ContextAttrib.MutualAuth |
ContextAttrib.Delegate |
ContextAttrib.Confidentiality
);
@@ -101,6 +107,7 @@ namespace TestClient
throw;
}
}
}
}
@@ -109,6 +116,7 @@ namespace TestClient
this.connection.Stop();
}
private void encryptButton_Click( object sender, EventArgs e )
{
byte[] plaintext;
@@ -141,7 +149,7 @@ namespace TestClient
private void connection_Received( Message message )
{
this.Invoke( (Action)delegate ()
this.Invoke( (Action)delegate()
{
if( message.Operation == ProtocolOp.ServerToken )
{
@@ -172,11 +180,11 @@ namespace TestClient
this.initializing = false;
this.lastServerToken = null;
this.BeginInvoke( (Action)delegate ()
this.BeginInvoke( (Action)delegate()
{
this.context.Dispose();
this.context = new ClientContext(
this.cred,
this.context = new ClientContext(
this.cred,
"",
ContextAttrib.InitIntegrity |
ContextAttrib.ReplayDetect |
@@ -187,9 +195,9 @@ namespace TestClient
);
UpdateButtons();
} );
});
}
private void DoInit()
{
SecurityStatus status;
@@ -245,4 +253,4 @@ namespace TestClient
this.signButton.Enabled = this.connected;
}
}
}
}

View File

@@ -1,19 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestClient
{
internal static class Program
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main()
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault( false );
Application.Run( new ClientForm() );
}
}
}
}

View File

@@ -1,7 +1,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle( "TestClient" )]
@@ -13,8 +14,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]
@@ -24,11 +25,11 @@ using System.Runtime.InteropServices;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion( "1.0.0.0" )]

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -19,7 +19,7 @@ namespace TestClient.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -12,7 +12,7 @@ namespace TestClient.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestClient</RootNamespace>
<AssemblyName>TestClient</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
@@ -22,7 +22,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -32,7 +31,6 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View File

@@ -1,2 +0,0 @@
bin
obj

View File

@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

View File

@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using NSspi;
@@ -9,7 +13,7 @@ namespace TestProtocol
public class CustomConnection
{
private Thread receiveThread;
private Socket socket;
private bool running;
@@ -20,7 +24,7 @@ namespace TestProtocol
}
public delegate void ReceivedAction( Message message );
public event ReceivedAction Received;
public event Action Disconnected;
@@ -29,7 +33,7 @@ namespace TestProtocol
{
if( this.running )
{
throw new InvalidOperationException( "Already running" );
throw new InvalidOperationException("Already running");
}
this.socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
@@ -61,7 +65,7 @@ namespace TestProtocol
throw new InvalidOperationException( "Not connected" );
}
byte[] outBuffer = new byte[message.Data.Length + 8];
byte[] outBuffer = new byte[ message.Data.Length + 8 ];
int position = 0;
ByteWriter.WriteInt32_BE( (int)message.Operation, outBuffer, position );
@@ -93,7 +97,10 @@ namespace TestProtocol
try
{
this.Disconnected?.Invoke();
if( this.Disconnected != null )
{
this.Disconnected();
}
}
catch
{ }
@@ -105,10 +112,7 @@ namespace TestProtocol
byte[] readBuffer = new byte[65536];
ProtocolOp operation;
int messageLength;
int remaining;
int chunkLength;
int position;
int length;
while( this.running )
{
@@ -117,42 +121,29 @@ namespace TestProtocol
// |--4 bytes--|--4 bytes--|---N--|
// Every command is a TLV - | Operation | Length | Data |
// Read the operation.
this.socket.Receive( readBuffer, 4, SocketFlags.None );
// Check if we popped out of a receive call after we were shut down.
if( this.running == false ) { break; }
operation = (ProtocolOp)ByteWriter.ReadInt32_BE( readBuffer, 0 );
// Read the length
// Read the length
this.socket.Receive( readBuffer, 4, SocketFlags.None );
messageLength = ByteWriter.ReadInt32_BE( readBuffer, 0 );
if( readBuffer.Length < messageLength )
{
readBuffer = new byte[messageLength];
}
length = ByteWriter.ReadInt32_BE( readBuffer, 0 );
// Read the data
// Keep in mind that Socket.Receive may return less data than asked for.
remaining = messageLength;
chunkLength = 0;
position = 0;
while( remaining > 0 )
{
chunkLength = this.socket.Receive( readBuffer, position, remaining, SocketFlags.None );
remaining -= chunkLength;
position += chunkLength;
}
this.socket.Receive( readBuffer, length, SocketFlags.None );
}
catch( SocketException e )
{
if( e.SocketErrorCode == SocketError.ConnectionAborted ||
e.SocketErrorCode == SocketError.Interrupted ||
e.SocketErrorCode == SocketError.OperationAborted ||
e.SocketErrorCode == SocketError.Shutdown ||
e.SocketErrorCode == SocketError.ConnectionReset )
e.SocketErrorCode == SocketError.Shutdown )
{
// Shutting down.
break;
@@ -167,18 +158,19 @@ namespace TestProtocol
if( this.Received != null )
{
byte[] dataCopy = new byte[messageLength];
Array.Copy( readBuffer, 0, dataCopy, 0, messageLength );
byte[] dataCopy = new byte[length];
Array.Copy( readBuffer, 0, dataCopy, 0, length );
Message message = new Message( operation, dataCopy );
try
{
this.Received( message );
}
catch( Exception )
catch( Exception e )
{ }
}
}
}
}
}
}

View File

@@ -1,12 +1,22 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
using NSspi;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestProtocol
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using NSspi;
public class CustomServer
{
private Thread receiveThread;
@@ -139,9 +149,7 @@ namespace TestProtocol
byte[] readBuffer = new byte[65536];
ProtocolOp operation;
int messageLength;
int position;
int remaining;
int length;
while( this.running )
{
@@ -149,7 +157,7 @@ namespace TestProtocol
{
// |--4 bytes--|--4 bytes--|---N--|
// Every command is a TLV - | Operation | Length | Data |
int chunkLength;
// Read the operation.
this.readSocket.Receive( readBuffer, 4, SocketFlags.None );
@@ -159,26 +167,13 @@ namespace TestProtocol
operation = (ProtocolOp)ByteWriter.ReadInt32_BE( readBuffer, 0 );
// Read the length
// Read the length
this.readSocket.Receive( readBuffer, 4, SocketFlags.None );
messageLength = ByteWriter.ReadInt32_BE( readBuffer, 0 );
if( readBuffer.Length < messageLength )
{
readBuffer = new byte[messageLength];
}
length = ByteWriter.ReadInt32_BE( readBuffer, 0 );
// Read the data
// Keep in mind that Socket.Receive may return less data than asked for.
remaining = messageLength;
chunkLength = 0;
position = 0;
while( remaining > 0 )
{
chunkLength = this.readSocket.Receive( readBuffer, position, remaining, SocketFlags.None );
remaining -= chunkLength;
position += chunkLength;
}
this.readSocket.Receive( readBuffer, length, SocketFlags.None );
}
catch( SocketException e )
{
@@ -201,8 +196,8 @@ namespace TestProtocol
if( this.Received != null )
{
byte[] dataCopy = new byte[messageLength];
Array.Copy( readBuffer, 0, dataCopy, 0, messageLength );
byte[] dataCopy = new byte[length];
Array.Copy( readBuffer, 0, dataCopy, 0, length );
Message message = new Message( operation, dataCopy );
try
@@ -212,6 +207,7 @@ namespace TestProtocol
catch( Exception )
{ }
}
}
try
@@ -224,4 +220,5 @@ namespace TestProtocol
catch { }
}
}
}
}

View File

@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestProtocol
{
public class Message
{
public Message( ProtocolOp op, byte[] data )
public Message(ProtocolOp op, byte[] data)
{
this.Operation = op;
this.Data = data;
@@ -14,4 +18,4 @@ namespace TestProtocol
public byte[] Data { get; private set; }
}
}
}

View File

@@ -1,7 +1,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle( "TestProtocol" )]
@@ -13,8 +14,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]
@@ -24,12 +25,12 @@ using System.Runtime.InteropServices;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion( "1.0.0.0" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -19,7 +19,7 @@ namespace TestProtocol.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -12,7 +12,7 @@ namespace TestProtocol.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestProtocol
{
@@ -10,4 +14,4 @@ namespace TestProtocol
EncryptedMessage = 3,
SignedMessage = 4,
}
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestProtocol</RootNamespace>
<AssemblyName>TestProtocol</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
@@ -22,7 +22,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -32,7 +31,6 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<StartupObject />

View File

@@ -1,2 +0,0 @@
bin
obj

View File

@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

View File

@@ -1,19 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestServer
{
internal static class Program
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main()
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault( false );
Application.Run( new ServerForm() );
}
}
}
}

View File

@@ -1,7 +1,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle( "TestServer" )]
@@ -13,8 +14,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]
@@ -24,12 +25,12 @@ using System.Runtime.InteropServices;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion( "1.0.0.0" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -19,7 +19,7 @@ namespace TestServer.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -12,7 +12,7 @@ namespace TestServer.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

View File

@@ -38,13 +38,13 @@
this.label2 = new System.Windows.Forms.Label();
this.serverUsernameTextbox = new System.Windows.Forms.TextBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.impersonateButton = new System.Windows.Forms.Button();
this.signButton = new System.Windows.Forms.Button();
this.encryptButton = new System.Windows.Forms.Button();
this.sendTextbox = new System.Windows.Forms.TextBox();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.receivedTextbox = new System.Windows.Forms.TextBox();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.impersonateButton = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.portNumeric)).BeginInit();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
@@ -168,16 +168,6 @@
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Send a message to the client";
//
// impersonateButton
//
this.impersonateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.impersonateButton.Location = new System.Drawing.Point(262, 356);
this.impersonateButton.Name = "impersonateButton";
this.impersonateButton.Size = new System.Drawing.Size(116, 23);
this.impersonateButton.TabIndex = 4;
this.impersonateButton.Text = "Test impersonation";
this.impersonateButton.UseVisualStyleBackColor = true;
//
// signButton
//
this.signButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
@@ -206,7 +196,6 @@
this.sendTextbox.Location = new System.Drawing.Point(6, 19);
this.sendTextbox.Multiline = true;
this.sendTextbox.Name = "sendTextbox";
this.sendTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.sendTextbox.Size = new System.Drawing.Size(400, 323);
this.sendTextbox.TabIndex = 0;
//
@@ -227,7 +216,6 @@
this.receivedTextbox.Location = new System.Drawing.Point(3, 16);
this.receivedTextbox.Multiline = true;
this.receivedTextbox.Name = "receivedTextbox";
this.receivedTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.receivedTextbox.Size = new System.Drawing.Size(407, 370);
this.receivedTextbox.TabIndex = 0;
//
@@ -248,6 +236,15 @@
this.tableLayoutPanel1.Size = new System.Drawing.Size(838, 395);
this.tableLayoutPanel1.TabIndex = 7;
//
// impersonateButton
//
this.impersonateButton.Location = new System.Drawing.Point(262, 350);
this.impersonateButton.Name = "impersonateButton";
this.impersonateButton.Size = new System.Drawing.Size(116, 23);
this.impersonateButton.TabIndex = 4;
this.impersonateButton.Text = "Test impersonation";
this.impersonateButton.UseVisualStyleBackColor = true;
//
// ServerForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);

View File

@@ -1,21 +1,25 @@
using System;
using System.IO;
using System.Security.Principal;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using NSspi;
using NSspi.Contexts;
using NSspi.Credentials;
using TestProtocol;
namespace TestServer
{
using System.IO;
using NSspi;
using NSspi.Contexts;
using NSspi.Credentials;
using Message = TestProtocol.Message;
public partial class ServerForm : Form
{
private ServerCurrentCredential serverCred;
private ServerCredential serverCred;
private ServerContext serverContext;
@@ -31,17 +35,16 @@ namespace TestServer
{
InitializeComponent();
this.serverCred = new ServerCurrentCredential( PackageNames.Negotiate );
this.serverCred = new ServerCredential( PackageNames.Negotiate );
this.serverContext = new ServerContext(
this.serverContext = new ServerContext(
serverCred,
ContextAttrib.AcceptIntegrity |
ContextAttrib.ReplayDetect |
ContextAttrib.SequenceDetect |
ContextAttrib.MutualAuth |
ContextAttrib.Delegate |
ContextAttrib.Confidentiality,
true
ContextAttrib.AcceptIntegrity |
ContextAttrib.ReplayDetect |
ContextAttrib.SequenceDetect |
ContextAttrib.MutualAuth |
ContextAttrib.Delegate |
ContextAttrib.Confidentiality
);
this.server = new CustomServer();
@@ -126,15 +129,11 @@ namespace TestServer
{
MessageBox.Show( "Starting impersonation: " + Environment.UserName );
var directory = Environment.GetFolderPath( Environment.SpecialFolder.DesktopDirectory );
Directory.CreateDirectory( directory );
FileStream stream = File.Create( directory + @"\test.txt" );
FileStream stream = File.Create( Environment.GetFolderPath( Environment.SpecialFolder.DesktopDirectory) + @"\test.txt" );
StreamWriter writer = new StreamWriter( stream, Encoding.UTF8 );
writer.WriteLine( "Hello world." );
writer.Close();
stream.Close();
}
@@ -151,6 +150,7 @@ namespace TestServer
this.signButton.Enabled = this.connected;
}
private void server_Received( Message message )
{
if( message.Operation == ProtocolOp.ClientToken )
@@ -171,38 +171,13 @@ namespace TestServer
}
}
private void InitComplete()
{
UpdateButtons();
this.clientUsernameTextBox.Text = serverContext.ContextUserName;
var builder = new StringBuilder();
var remoteId = this.serverContext.GetRemoteIdentity();
builder.AppendLine( "Client identity information:" );
builder.AppendLine( " - Name: " + remoteId.Name );
var windowsId = remoteId as WindowsIdentity;
if( windowsId != null )
{
builder.AppendLine( " - User SID: " + windowsId.User.Value );
foreach( var claim in windowsId.Claims )
{
builder.AppendLine( " - " + claim.ToString() );
}
}
this.receivedTextbox.AppendText( builder.ToString() );
}
private void server_Disconnected()
{
this.running = true;
this.running = false;
this.initializing = true;
this.connected = false;
this.serverContext.Dispose();
this.serverContext = new ServerContext(
serverCred,
@@ -214,13 +189,14 @@ namespace TestServer
ContextAttrib.Confidentiality
);
this.BeginInvoke( (Action)delegate ()
this.BeginInvoke( (Action)delegate()
{
UpdateButtons();
this.clientUsernameTextBox.Text = "";
} );
});
}
private void HandleInit( Message message )
{
byte[] nextToken;
@@ -242,33 +218,38 @@ namespace TestServer
this.initializing = false;
this.connected = true;
this.Invoke( (Action)InitComplete );
this.Invoke( (Action)delegate()
{
UpdateButtons();
this.clientUsernameTextBox.Text = serverContext.ContextUserName;
} );
}
}
else
{
this.Invoke( (Action)delegate ()
this.Invoke( (Action)delegate()
{
MessageBox.Show( "Failed to accept token from client. Sspi error code: " + status );
} );
}
}
}
private void HandleEncrypted( Message message )
{
this.Invoke( (Action)delegate ()
this.Invoke( (Action)delegate()
{
byte[] plainText = this.serverContext.Decrypt( message.Data );
string text = Encoding.UTF8.GetString( plainText );
this.receivedTextbox.Text += "Received encrypted message from client:\r\n" + text + "\r\n";
} );
}
private void HandleSigned( Message message )
{
this.Invoke( (Action)delegate ()
this.Invoke( (Action)delegate()
{
byte[] plainText;
@@ -287,10 +268,11 @@ namespace TestServer
private void HandleUnknown( Message message )
{
this.Invoke( (Action)delegate ()
this.Invoke( (Action)delegate()
{
MessageBox.Show( "Received unexpected message from server. Message type: " + message.Operation );
} );
}
}
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestServer</RootNamespace>
<AssemblyName>TestServer</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
@@ -22,7 +22,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -32,7 +31,6 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View File

@@ -1,59 +0,0 @@
## Downloads ##
The latest release of NSspi is v0.3.1, released 5-Aug-2019.
Version 0.3.1 adds support to obtain an IIdentity/WindowsPrinciple representing the remote connection. This is useful for servers that wish to query the properties on the principle, such as claims.
* [Source](https://github.com/antiduh/nsspi/archive/0.3.1.zip)
* [Nuget package](https://www.nuget.org/packages/NSspi)
You can also browse the list of [releases](https://github.com/antiduh/nsspi/releases).
## Introduction ##
This project provides a C# / .Net interface to the Windows Integrated Authentication API, better known as SSPI (Security Service Provider Interface). This allows a custom client / server system to authenticate users using their existing logon credentials. This allows a developer to provide Single-Sign-On in their application.
## Overview ##
The API provides raw access to authentication tokens so that authentication can be easily integrated into any networking system - you can send the tokens over a socket, a remoting interface, or heck even a serial port if you want; they're just bytes. Clients and servers may exchange encrypted and signed messages, and the server can perform client impersonation.
The project is provided as a .Net 4.0 assembly, but can just as easily be upgraded to .Net 4.5 or later. The solution file can be opened by Visual Studio 2010 SP1, Visual Studio 2012, or later Visual Studio editions.
The SSPI API provides an interface for real authentication protocols, such as Kerberos or NTLM, to be invoked transparently by client and server code in order to perform authentication and message manipulation. These authentication protocols are better known as 'security packages'.
The SSPI API exposes these packages using a common API, and so a program may invoke one or the other with only minor changes in implementation. SSPI also supports the 'negotiate' 'meta' package, that allows a client and server to decide dynamically which real security provider to use, and then itself provides a passthrough interface to the real package.
## Usage ##
Typically, a client acquires some form of a credential, either from the currently logged on user's security context, by acquiring a username and password from the user, or by some other means. The server acquires a credential in a similar manner. Each uses their credentials to identify themselves to each other.
A client and a server each start with uninitialized security contexts. They exchange negotiation and authentication tokens to perform authentication, and if all succeeds, they create a shared security context in the form of a client's context and a server's context. The effectively shared context agrees on the security package to use (kerberos, NTLM), and what parameters to use for message passing. Every new client that authenticates with a server creates a new security context specific to that client-server pairing.
From the software perspective, a client security context initializes itself by exchanging authentication tokens with a server; the server initializes itself by exchanging authentication tokens with the client.
This API provides raw access to the authentication tokens created during the negotiation and authentication process. In this manner, any application can integrate SSPI-based authentication by deciding for themselves how to integrate the tokens into their application protocol.
The project is broken up into 3 chunks:
* The NSspi library, which provides safe, managed access to the SSPI API.
* NsspiDemo, a quick demo program to show how to exercise the features of NSspi locally.
* UI demo programs TestClient and TestServer (that have a common dependency on TestProtocol) that
may be run on separate machines, that show how one might integrate SSPI into a custom
application.
## More information ##
If you would like to understand the SSPI API, feel free to browse the following references:
MSDN documentation on the SSPI API:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731(v=vs.85).aspx](http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731\(v=vs.85\).aspx)
MSDN article on SSPI along with a sample Managed C++ SSPI library and UI client/servers.<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [http://msdn.microsoft.com/en-us/library/ms973911.aspx](http://msdn.microsoft.com/en-us/library/ms973911.aspx)
Relevant StackOverflow questions:
* [Client-server authentication - using SSPI?](http://stackoverflow.com/questions/17241365/)
* [Validate Windows Identity Token](http://stackoverflow.com/questions/11238141/)
* [How to deal with allocations in constrained execution regions?](http://stackoverflow.com/questions/24442209/)
* [AcquireCredentialsHandle returns massive expiration time](http://stackoverflow.com/questions/24478056/)