1 Commits
0.2.1 ... 0.1

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

1
.gitignore vendored
View File

@@ -1 +0,0 @@
.vs

View File

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

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
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
{
@@ -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>

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;
@@ -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;
@@ -85,12 +90,12 @@ namespace NSspi.Contexts
// 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],
new byte[ this.Credential.PackageInfo.MaxTokenLength ],
BufferType.Token
);
serverBuffer = null;
if( serverToken != null )
if ( serverToken != null )
{
serverBuffer = new SecureBuffer( serverToken, BufferType.Token );
}
@@ -110,9 +115,9 @@ namespace NSspi.Contexts
// 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,

View File

@@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using NSspi.Buffers;
using NSspi.Contexts;
using NSspi.Credentials;
namespace NSspi.Contexts
@@ -252,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." );
}
@@ -291,7 +296,8 @@ namespace NSspi.Contexts
}
// else there was no padding.
using( adapter = new SecureBufferAdapter( new[] { trailerBuffer, dataBuffer, paddingBuffer } ) )
using( adapter = new SecureBufferAdapter( new [] { trailerBuffer, dataBuffer, paddingBuffer } ) )
{
status = ContextNativeMethods.SafeDecryptMessage(
this.ContextHandle,
@@ -342,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,
@@ -352,7 +358,7 @@ namespace NSspi.Contexts
);
}
if( status != SecurityStatus.OK )
if ( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to create message signature.", status );
}
@@ -383,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>
@@ -438,7 +415,7 @@ namespace NSspi.Contexts
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" );
}
@@ -453,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." );
}
@@ -466,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,
@@ -476,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;
@@ -508,9 +485,9 @@ namespace NSspi.Contexts
{
this.ContextHandle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
this.ContextHandle.DangerousRelease();
gotRef = false;
@@ -520,7 +497,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.QueryContextAttributes_Sizes(
ref this.ContextHandle.rawHandle,
@@ -544,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;
@@ -563,9 +540,9 @@ namespace NSspi.Contexts
{
this.ContextHandle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
this.ContextHandle.DangerousRelease();
gotRef = false;
@@ -574,7 +551,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.QueryContextAttributes_String(
ref this.ContextHandle.rawHandle,
@@ -584,7 +561,7 @@ namespace NSspi.Contexts
this.ContextHandle.DangerousRelease();
if( status == SecurityStatus.OK )
if ( status == SecurityStatus.OK )
{
result = Marshal.PtrToStringUni( stringAttrib.StringResult );
ContextNativeMethods.FreeContextBuffer( stringAttrib.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,6 +36,7 @@ namespace NSspi.Contexts
/// </summary>
MutualAuth = 0x00000002,
/// <summary>
/// Detect replayed messages that have been encoded by using the EncryptMessage or MakeSignature
/// message support functionality.

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
{
@@ -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,11 +106,12 @@ 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,
@@ -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,72 +176,6 @@ namespace NSspi.Contexts
[DllImport( "Secur32.dll", EntryPoint = "RevertSecurityContext", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus RevertSecurityContext( ref RawSspiHandle contextHandle );
[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
/// performed in a proper CER.
@@ -263,9 +199,9 @@ namespace NSspi.Contexts
{
handle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
handle.DangerousRelease();
gotRef = false;
@@ -275,7 +211,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.EncryptMessage(
ref handle.rawHandle,
@@ -365,9 +301,9 @@ namespace NSspi.Contexts
{
handle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
handle.DangerousRelease();
gotRef = false;
@@ -377,7 +313,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.MakeSignature(
ref handle.rawHandle,
@@ -416,9 +352,9 @@ namespace NSspi.Contexts
{
handle.DangerousAddRef( ref gotRef );
}
catch( Exception )
catch ( Exception )
{
if( gotRef )
if ( gotRef )
{
handle.DangerousRelease();
gotRef = false;
@@ -428,7 +364,7 @@ namespace NSspi.Contexts
}
finally
{
if( gotRef )
if ( gotRef )
{
status = ContextNativeMethods.VerifySignature(
ref handle.rawHandle,

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
{

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,6 +1,8 @@
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
{
@@ -25,7 +27,7 @@ namespace NSspi.Contexts
/// 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;
@@ -52,5 +54,6 @@ namespace NSspi.Contexts
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
{

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;
@@ -16,25 +18,19 @@ namespace NSspi.Contexts
private ContextAttrib finalAttribs;
private bool impersonating;
private bool impersonationSetsThreadPrinciple;
/// <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.finalAttribs = ContextAttrib.Zero;
this.impersonating = false;
this.impersonationSetsThreadPrinciple = impersonationSetsThreadPrinciple;
this.SupportsImpersonate = this.Credential.PackageInfo.Capabilities.HasFlag( SecPkgCapability.Impersonation );
}
@@ -94,13 +90,13 @@ namespace NSspi.Contexts
clientBuffer = new SecureBuffer( clientToken, BufferType.Token );
outBuffer = new SecureBuffer(
new byte[this.Credential.PackageInfo.MaxTokenLength],
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 )
{
@@ -129,17 +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 );
@@ -149,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 );
@@ -185,12 +183,6 @@ 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." );
@@ -228,7 +220,7 @@ namespace NSspi.Contexts
this.ContextHandle.DangerousRelease();
this.impersonating = status == SecurityStatus.OK;
this.impersonating = true;
}
}
@@ -245,11 +237,6 @@ namespace NSspi.Contexts
throw new SSPIException( "Failed to impersonate the client", status );
}
if( this.impersonating && this.impersonationSetsThreadPrinciple )
{
SetThreadPrinciple();
}
return handle;
}
@@ -312,15 +299,5 @@ namespace NSspi.Contexts
base.Dispose( disposing );
}
/// <summary>
/// Set the current thread security context to the impersonated identity.
/// </summary>
private void SetThreadPrinciple()
{
Thread.CurrentPrincipal = new WindowsPrincipal(
WindowsIdentity.GetCurrent( TokenAccessLevels.AllAccess )
);
}
}
}

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
{
@@ -180,9 +187,9 @@ namespace NSspi.Credentials
protected virtual void Dispose( bool disposing )
{
if( this.disposed == false )
if ( this.disposed == false )
{
if( disposing )
if ( disposing )
{
this.safeCredHandle.Dispose();
}

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

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
{

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
{

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
{
@@ -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
{

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
{
@@ -24,4 +28,5 @@ namespace NSspi.Credentials
return status == SecurityStatus.OK;
}
}
}

View File

@@ -1,19 +1,23 @@
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
/// 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,57 +0,0 @@
using System;
using System.Reflection;
namespace NSspi
{
[AttributeUsage( AttributeTargets.Field )]
public class EnumStringAttribute : Attribute
{
public EnumStringAttribute( string text )
{
this.Text = text;
}
public string Text { get; private set; }
}
public class EnumMgr
{
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;
}
}
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

@@ -23,7 +23,6 @@
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Debug\NSspi.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -43,7 +42,11 @@
<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" />
@@ -55,20 +58,17 @@
<Compile Include="Contexts\ContextQueryAttrib.cs" />
<Compile Include="Contexts\ImpersonationHandle.cs" />
<Compile Include="Contexts\SafeContextHandle.cs" />
<Compile Include="Credentials\AuthData.cs" />
<Compile Include="Credentials\ClientCurrentCredential.cs" />
<Compile Include="Credentials\CurrentCredential.cs" />
<Compile Include="Credentials\PasswordCredential.cs" />
<Compile Include="Credentials\ServerCurrentCredential.cs" />
<Compile Include="EnumMgr.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" />
@@ -83,9 +83,7 @@
<Compile Include="SspiHandle.cs" />
<Compile Include="TimeStamp.cs" />
</ItemGroup>
<ItemGroup>
<None Include="NSspi.nuspec" />
</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.

View File

@@ -1,18 +0,0 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>NSspi</id>
<version>0.2.1.0</version>
<authors>Kevin Thompson</authors>
<owners>Kevin Thompson</owners>
<projectUrl>https://github.com/antiduh/nsspi</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>
A C#/.Net interface to the Win32 SSPI authentication API,
better known as Windows Integrated Authentication.
</description>
<language>C#</language>
<releaseNotes>Adds support for username/password credentials, but introduces a minor change in the interface that breaks existing code.</releaseNotes>
<copyright>Copyright 2018</copyright>
</metadata>
</package>

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 );
@@ -17,5 +23,6 @@ namespace NSspi
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
[DllImport( "Secur32.dll", EntryPoint = "EnumerateSecurityPackages", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus EnumerateSecurityPackages( ref int numPackages, ref IntPtr pkgInfoArry );
}
}

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{

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
{
@@ -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();

View File

@@ -1,25 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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 2018" )]
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
[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:
//
@@ -31,5 +32,5 @@ using System.Runtime.InteropServices;
// 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( "0.2.1.0" )]
[assembly: AssemblyFileVersion( "0.2.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
{
@@ -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,12 +70,7 @@ 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
{

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
{

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
{
@@ -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,9 +184,9 @@ 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
// back and forth to SSPI calls - we need to copy the potentially modified structure members

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
{

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>

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
{
@@ -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,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NSspi
{
@@ -26,99 +30,56 @@ namespace NSspi
/// <summary>
/// The request completed successfully
/// </summary>
[EnumString( "No error" )]
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,
/// <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,
/// <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,
/// <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,
[EnumString( "The credentials supplied to the security context were not fully initialized." )]
CredentialsNeeded = 0x00090320,
[EnumString( "The context data must be re-negotiated with the peer" )]
Renegotiate = 0x00090321,
// Errors
[EnumString( "Not enough memory." )]
OutOfMemory = 0x80090300,
[EnumString( "The handle provided to the API was invalid." )]
InvalidHandle = 0x80090301,
[EnumString( "The attempted operation is not supported" )]
Unsupported = 0x80090302,
[EnumString( "The specified principle is not known in the authentication system." )]
TargetUnknown = 0x80090303,
[EnumString( "An internal error occurred" )]
InternalError = 0x80090304,
/// <summary>
/// No security provider package was found with the given name.
/// </summary>
[EnumString( "The requested security package was not found." )]
PackageNotFound = 0x80090305,
NotOwner = 0x80090306,
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,
CannotPack = 0x80090309,
QopNotSupported = 0x8009030A,
/// <summary>
/// Impersonation is not supported.
/// </summary>
[EnumString( "Impersonation is not supported with the current security package." )]
NoImpersonation = 0x8009030B,
[EnumString( "The logon was denied, perhaps because the provided credentials were incorrect." )]
LogonDenied = 0x8009030C,
[EnumString( "The credentials provided are not recognized by the selected security package." )]
UnknownCredentials = 0x8009030D,
[EnumString( "No credentials are available in the selected security package." )]
NoCredentials = 0x8009030E,
[EnumString( "A message that was provided to the Decrypt or VerifySignature functions was altered " +
"after it was created." )]
MessageAltered = 0x8009030F,
[EnumString( "A message was received out of the expected order." )]
OutOfSequence = 0x80090310,
[EnumString( "The current security package cannot contact an authenticating authority." )]
NoAuthenticatingAuthority = 0x80090311,
/// <summary>
@@ -133,7 +94,6 @@ namespace NSspi
/// type 'extra'.
/// </remarks>
IncompleteMessage = 0x80090318,
IncompleteCredentials = 0x80090320,
BufferNotEnough = 0x80090321,
WrongPrincipal = 0x80090322,
@@ -164,4 +124,5 @@ namespace NSspi
return (uint)status > 0x80000000u;
}
}
}

View File

@@ -1,6 +1,12 @@
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
{
@@ -19,7 +25,7 @@ namespace NSspi
/// 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;

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
{
@@ -25,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
// 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;
}

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.0" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

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,8 +30,8 @@ namespace NSspi
try
{
clientCred = new ClientCurrentCredential( packageName );
serverCred = new ServerCurrentCredential( packageName );
clientCred = new ClientCredential( packageName );
serverCred = new ServerCredential( packageName );
Console.Out.WriteLine( clientCred.PrincipleName );
@@ -47,6 +47,7 @@ namespace NSspi
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,7 +160,7 @@ namespace NSspi
clientCred.Dispose();
}
if( serverCred != null )
if ( serverCred != null )
{
serverCred.Dispose();
}

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following

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.0" />
<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,7 +48,7 @@ namespace TestClient
this.FormClosing += Form1_FormClosing;
// --- SSPI ---
this.cred = new ClientCurrentCredential( PackageNames.Negotiate );
this.cred = new ClientCredential( PackageNames.Negotiate );
this.context = new ClientContext(
cred,
@@ -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,7 +180,7 @@ namespace TestClient
this.initializing = false;
this.lastServerToken = null;
this.BeginInvoke( (Action)delegate ()
this.BeginInvoke( (Action)delegate()
{
this.context.Dispose();
this.context = new ClientContext(
@@ -187,7 +195,7 @@ namespace TestClient
);
UpdateButtons();
} );
});
}
private void DoInit()

View File

@@ -1,15 +1,18 @@
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 );

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following

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.0" />
<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;
@@ -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 );
@@ -108,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 )
{
@@ -120,6 +121,7 @@ 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 );
@@ -130,32 +132,18 @@ namespace TestProtocol
// 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;
@@ -170,8 +158,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
@@ -181,6 +169,7 @@ namespace TestProtocol
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 );
@@ -161,24 +169,11 @@ namespace TestProtocol
// 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;

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestProtocol
{

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.0" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

View File

@@ -1,15 +1,18 @@
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 );

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following

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,15 +168,6 @@
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Send a message to the client";
//
// 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;
//
// signButton
//
this.signButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
@@ -205,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;
//
@@ -226,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;
//
@@ -247,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,5 +1,11 @@
using System;
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 TestProtocol;
@@ -13,7 +19,7 @@ namespace TestServer
public partial class ServerForm : Form
{
private ServerCurrentCredential serverCred;
private ServerCredential serverCred;
private ServerContext serverContext;
@@ -29,7 +35,7 @@ namespace TestServer
{
InitializeComponent();
this.serverCred = new ServerCurrentCredential( PackageNames.Negotiate );
this.serverCred = new ServerCredential( PackageNames.Negotiate );
this.serverContext = new ServerContext(
serverCred,
@@ -123,7 +129,7 @@ namespace TestServer
{
MessageBox.Show( "Starting impersonation: " + Environment.UserName );
FileStream stream = File.Create( Environment.GetFolderPath( Environment.SpecialFolder.DesktopDirectory ) + @"\test.txt" );
FileStream stream = File.Create( Environment.GetFolderPath( Environment.SpecialFolder.DesktopDirectory) + @"\test.txt" );
StreamWriter writer = new StreamWriter( stream, Encoding.UTF8 );
writer.WriteLine( "Hello world." );
@@ -144,6 +150,7 @@ namespace TestServer
this.signButton.Enabled = this.connected;
}
private void server_Received( Message message )
{
if( message.Operation == ProtocolOp.ClientToken )
@@ -166,10 +173,11 @@ namespace TestServer
private void server_Disconnected()
{
this.running = true;
this.running = false;
this.initializing = true;
this.connected = false;
this.serverContext.Dispose();
this.serverContext = new ServerContext(
serverCred,
@@ -181,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;
@@ -209,7 +218,7 @@ namespace TestServer
this.initializing = false;
this.connected = true;
this.Invoke( (Action)delegate ()
this.Invoke( (Action)delegate()
{
UpdateButtons();
this.clientUsernameTextBox.Text = serverContext.ContextUserName;
@@ -218,17 +227,18 @@ namespace TestServer
}
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 );
@@ -239,7 +249,7 @@ namespace TestServer
private void HandleSigned( Message message )
{
this.Invoke( (Action)delegate ()
this.Invoke( (Action)delegate()
{
byte[] plainText;
@@ -258,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,61 +0,0 @@
## Downloads ##
The latest release of NSspi is v0.2.0.
Version 0.2.0 adds the ability to authenticate using provided username/password credentials.
**Please note** that v0.2.0 introduces a small change in the design that breaks backwards compatibility with previous verisons.
* [Source](https://github.com/antiduh/nsspi/archive/0.2.0.zip)
* [Binaries](https://github.com/antiduh/nsspi/releases/download/0.2.0/nsspi-0.2.0-bin.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 projects 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/)