using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security.Cryptography.X509Certificates; using System.Runtime.InteropServices; using System.Net; using System.IO; using System.Net.Sockets; using System.Diagnostics; using System.Security; namespace Intel.Management.Wsman { /// /// Implements an SChannel Client /// public class SchannelClient : IDisposable { SECURITY_HANDLE _hCredHandle;//handle to acquired Credential (FreeCredentialsHandle) SECURITY_HANDLE _hContext; //handle to the context (DeleteSecurityContext) UserCredentials _creds; // user credentials static List _credCache; // credential Cache static object _credLock = new object(); //thread lock for cached credentials //package info uint _cbMaxToken = 0; string _packageName; string _target; //name of server or SPN to verify TcpClient _tcpClient; static TraceSource _trace = new TraceSource("Intel.Management.Wsman.Schannel"); public SchannelClient(string packageName) { _trace.TraceInformation("Request Schannel package for {0}",packageName); // store package info in a structure (static) IntPtr pkgPtr; int status = QuerySecurityPackageInfo(packageName, out pkgPtr); if (status == 0) { SecPkgInfo pkgInfo = SecPkgInfo.FromIntPtr(pkgPtr); _cbMaxToken = pkgInfo.cbMaxToken; _packageName = pkgInfo.Name; status = FreeContextBuffer(pkgPtr); } // create a credential cache lock (_credLock) { if (_credCache == null) _credCache = new List(); } } public string Target { get { return _target; } set { _target = value; } } public X509Certificate2 GetSessionCerticate() { return null; } public bool GetSessionInfo(out string SessionId) { SessionId = string.Empty; return false; } // acquire credentials for a user public int AcquireCredentials(string user, string domain, SecureString password) { WindowsCredentials winCreds = new WindowsCredentials(); winCreds.UserName = user; winCreds.DomainName = domain; winCreds.Password = password; _creds = winCreds; int status = 0; lock (_credLock) { long expirers; status = AcquireCredentials(out expirers); } return status; } /// /// Acquire credentials for MTLS handshake /// /// Certificate Credentials public void AcquireCredentials(X509Certificate2 certificate) { AcquireCredentials(certificate, true, true, false); } /// /// Acquire credentials for TLS handshake /// public void AcquireCredentials(X509Certificate2 certificate, bool verifyCa, bool verifyHost, bool sentRoot) { _creds = new CertificateCredentials(); if (certificate != null) { //_trace.TraceInformation("Acquire Credentials for certificate {0}", certificate.Subject); ((CertificateCredentials)_creds).Certificate = certificate; ((CertificateCredentials)_creds).SkipDefaultCreds(); if (sentRoot) ((CertificateCredentials)_creds).SendRoot(); if (!verifyCa) ((CertificateCredentials)_creds).SkipCACheck(); if (!verifyHost) ((CertificateCredentials)_creds).SkipCNCheck(); } else { _trace.TraceInformation("Acquire Credentials (no certificates specified)"); } lock (_credLock) { long expirers; int status = AcquireCredentials(out expirers); } } private int AcquireCredentials(out long expiers) { // assume the creds are locked when this is called //CacheItem removeItem = null; // find exising item in cache existing cred foreach (CacheItem cacheItem in _credCache) { if (cacheItem.Credentials.CompareTo(_creds)) { _hCredHandle.HighPart=cacheItem.HighPart; _hCredHandle.LowPart=cacheItem.LowPart; //get a copy of the connection flags expiers = cacheItem.Expiers; _trace.TraceInformation("Found cached credential"); return 0; } // end cached handle not freed }//end for expiers = 0; int status = AcquireCredentialsHandle(null, _packageName, 2, //2=SECPKG_CRED_OUTBOUND IntPtr.Zero, // _creds, //handle to schannel creds (client certificates) IntPtr.Zero, IntPtr.Zero, ref _hCredHandle, //acquired cred Handle ref expiers); if (status == 0) { _trace.TraceInformation("Credential Handle {0}", this.GetHandleMessage(ref _hCredHandle)); _trace.TraceInformation("Acquired Credentials added to cache"); _credCache.Add(new CacheItem(_hCredHandle.HighPart, _hCredHandle.LowPart, _creds, expiers)); } else { _trace.TraceInformation("Acquire Credentials returned {0}", GetSchannelMessage(status)); throw new WsmanConnectionException(GetSchannelMessage(status)); } return status; } #region IDisposableMembers private bool _disposed = false; public void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _creds?.Dispose(); _creds = null; } _disposed = true; } public void Dispose() { CloseContext(); Dispose(true); GC.SuppressFinalize(this); } ~SchannelClient() { Dispose(false); } #endregion public string GetToken(string prevToken) { string result = null; //inFlags = Delegate=1, MutualAuth=2, ReplayDetect=4, SequenceDetect=8, Confidentiality=16, Connection=2048 //allocate memory=256 uint flags = 1 | 2 | 4 | 8 | 16 | 2048 | 256; long expiers = 0; uint contextAttr = 0; int status = 0; ChannelOutputBuffer outBuff = new ChannelOutputBuffer(); InputBuffer inputBuff = new InputBuffer(); byte[] token = null; if (prevToken != null) { int pos = prevToken.LastIndexOf(" "); string tokenString = prevToken.Substring(pos + 1); if (string.Compare(tokenString,"Negotiate",true)!=0) { token = Convert.FromBase64String(tokenString); } } if (token==null) { CloseContext(); status = InitializeSecurityContextInit( ref _hCredHandle, IntPtr.Zero, _target, flags, //Delegate=1, MutualAuth=2, ReplayDetect=4, SequenceDetect=8, Confidentiality=16, Connection=2048, 0, 16, //SECURITY_NATIVE_DREP=16 IntPtr.Zero, 0, ref _hContext, outBuff, ref contextAttr, ref expiers); } else { inputBuff = new InputBuffer(1); inputBuff[0].BufferType=BufferType.Token; inputBuff[0].SetBuffer(token, 0, token.Length); status = InitializeSecurityContextExchange( ref _hCredHandle, IntPtr.Zero, _target, flags, //Delegate=1, MutualAuth=2, ReplayDetect=4, SequenceDetect=8, Confidentiality=16, Connection=2048, 0, 16, //SECURITY_NATIVE_DREP=16 inputBuff, 0, ref _hContext, outBuff, ref contextAttr, ref expiers); } _trace.TraceInformation("InitializeSecurityContext({0}) returned {1}", _target, GetSchannelMessage(status)); _trace.TraceInformation("New Context is {0}", _hContext.ToString()); if (status >= 0) { result = _packageName + " " + Convert.ToBase64String(outBuff[0].Data, 0, outBuff[0].Data.Length); } return result; } public void CloseContext() { if (!_hContext.LowPart.Equals(IntPtr.Zero) || !_hContext.HighPart.Equals(IntPtr.Zero)) { int status = DeleteSecurityContext(ref _hContext); if (status == 0) { _hContext.HighPart = IntPtr.Zero; _hContext.LowPart = IntPtr.Zero; } } } public void CloseNotify() { long expiers = 0; uint contextAttrs = 0; InputBuffer buffer = new InputBuffer(1); buffer[0].BufferType=BufferType.Token; byte[] data = BitConverter.GetBytes((int)1); buffer[0].SetBuffer(data, 0, data.Length); int status=ApplyControlToken(ref _hContext,buffer); _trace.TraceInformation("ApplyControlToken for Shutdown retured {0}", GetSchannelMessage(status)); ChannelOutputBuffer outBuff = new ChannelOutputBuffer(); InputBuffer inBuff = new InputBuffer(); status = InitializeSecurityContext( ref _hCredHandle, ref _hContext, _target, 49436, //ISC_REQ_SEQUENCE_DETECT|ISC_REQ_REPLAY_DETECT|ISC_REQ_CONFIDENTIALITY|ISC_RET_EXTENDED_ERROR |ISC_REQ_ALLOCATE_MEMORY |ISC_REQ_STREAM; 0, 16, //SECURITY_NATIVE_DREP inBuff, 0, IntPtr.Zero, outBuff, ref contextAttrs, ref expiers); _trace.TraceInformation("InitializeSecurityContext for shutdown retured {0}", GetSchannelMessage(status)); if (status == 0) SendBuffer(outBuff[0]); _tcpClient = null; CloseContext(); } public void WriteMessage(string message) { //SECPKG_ATTR_STREAM_SIZES=4 StreamSizeAttribute sizes = new StreamSizeAttribute(); int status = QueryContextAttributes(ref _hContext, 4, sizes); _trace.TraceInformation("QueryContextAttributes for SECPKG_ATTR_STREAM_SIZES returned {0}", GetSchannelMessage(status)); if (status!=0) throw new IOException(GetSchannelMessage(status)); byte[] dataString = Encoding.ASCII.GetBytes(message); byte[] data = new byte[sizes.Header + dataString.Length + sizes.Trailer]; //copy the data into the buffer Array.Copy(dataString,0,data,sizes.Header,dataString.Length); InputBuffer input = new InputBuffer(4); input[0].BufferType = BufferType.StreamHeader; input[0].SetBuffer(data, 0, sizes.Header); input[1].BufferType = BufferType.Data; input[1].SetBuffer(data, sizes.Header, dataString.Length); input[2].BufferType = BufferType.StreamTrailer; input[2].SetBuffer(data, sizes.Header + dataString.Length,sizes.Trailer); input[3].BufferType = BufferType.Empty; status = EncryptMessage(ref _hContext, 0,input, 0); _trace.TraceInformation("EncryptMessage returned {0}", GetSchannelMessage(status)); //send the data buffer int total = input[0].Length + input[1].Length + input[2].Length; if (status == 0) _tcpClient.GetStream().Write(data, 0, total); else throw new IOException(GetSchannelMessage(status)); _trace.TraceInformation("Encrypted message sent {0} bytes", total); } private int ExpectingData(byte[] data) { //we have 5 bytes if (data.Length >= 5) { int messageType = (int)data[0]; int majorVersion = (int)data[1]; int minorVersion = (int)data[2]; byte[] sizeArray = { data[4], data[3],0 ,0 }; int len= BitConverter.ToInt32(sizeArray, 0); } return 5; } public int ReadMessage(byte[] data,int offset,int buffLen) { const int SEC_E_OK = 0; //const int SEC_E_INCOMPLETE_MESSAGE = -2146893032; const int SEC_I_RENEGOTIATE = 590625; int status; InputBuffer input = new InputBuffer(4); _trace.TraceInformation("Reading data from server"); byte[] tlsData = ReadRecord(); int copyLen = 0; _trace.TraceInformation("Recieved {0} encrypted bytes from server", tlsData.Length); input[0].BufferType = BufferType.Data; input[0].SetBuffer(tlsData, 0, tlsData.Length); input[1].BufferType = BufferType.Empty; input[1].SetBuffer(null, 0, 0); input[2].BufferType = BufferType.Empty; input[2].SetBuffer(null, 0, 0); input[3].BufferType = BufferType.Empty; input[3].SetBuffer(null, 0, 0); status = DecryptMessage(ref _hContext, input, 0, IntPtr.Zero); _trace.TraceInformation("DecryptMessage returned {0}", GetSchannelMessage(status)); if (status >= 0) { if (status == SEC_I_RENEGOTIATE) { throw new IOException(); } for (int i = 0; i < input.Count; i++) { if (input[i].BufferType == BufferType.Data) { copyLen = input[i].Length; if (copyLen > buffLen) { copyLen = buffLen; //store left over for next read } //result = new byte[input[i].Length]; Array.Copy(input[i].Data, input[i].Offset, data, 0, copyLen); } } //for input[i] }// if status>0 return copyLen; } protected void SendBuffer(SecurityBuffer buffer) { if (buffer.Data != null && buffer.Length > 0) { _tcpClient.GetStream().Write(buffer.Data, buffer.Offset, buffer.Length); } } public byte[] ReadRecord() { // for SSL Record layer potocol verying header is at least 5 bytes byte[] data = new byte[5]; int offset=0; int br=0; do { br = _tcpClient.GetStream().Read(data, offset, data.Length-offset); offset += br; if (br == 0 && offset > 0) throw new IOException("Server Unexpecity closed connection"); if (br==0 && offset==0) return new byte[0]; } while (offset < data.Length); //we have 5 byte int messageType = (int)data[0]; int majorVersion = (int)data[1]; int minorVersion = (int)data[2]; byte[] sizeArray = { data[4], data[3], 0, 0 }; int len = BitConverter.ToInt32(sizeArray, 0); byte[] temp = data; data = new byte[len + data.Length]; Array.Copy(temp, 0, data, 0, temp.Length); offset = 5; br = 0; do { br = _tcpClient.GetStream().Read(data, offset, data.Length - offset); if (br == 0) throw new IOException("Server Unexpetily disconnected"); offset += br; } while (offset < data.Length); return data; } private void PerformClientLoop(bool doInitialRead) { bool doRead = doInitialRead; int offset = 0; long exp = 0; uint contextAttrs; byte[] data = new byte[_cbMaxToken]; //read from the server SecurityHandle nullHandle = new NullSecuirtyHandle(); const int SEC_E_OK = 0; const int SEC_I_CONTINUE_NEEDED = 590610; const int SEC_E_INCOMPLETE_MESSAGE = -2146893032; const int SEC_I_INCOMPLETE_CREDENTIALS = 590624; const int ISC_RET_EXTENDED_ERROR = 16384; int status = SEC_I_CONTINUE_NEEDED; while (status == SEC_I_CONTINUE_NEEDED || status == SEC_I_INCOMPLETE_CREDENTIALS || status == SEC_E_INCOMPLETE_MESSAGE) { if (doRead) { offset += _tcpClient.GetStream().Read(data, offset, data.Length - offset); if (offset == 0) { status = SEC_E_INCOMPLETE_MESSAGE; break; } //ExpectingData(data); } doRead = true; // // Set up the input buffers. Buffer 0 is used to pass in data // received from the server. Schannel will consume some or all // of this. Leftover data (if any) will be placed in buffer 1 and // given a buffer type of SECBUFFER_EXTRA. // contextAttrs = 0; exp = 0; ChannelOutputBuffer outBuff = new ChannelOutputBuffer(); InputBuffer inBuff = new InputBuffer(2); inBuff[0].BufferType = BufferType.Token; inBuff[0].SetBuffer(data, 0, offset); inBuff[1].BufferType = BufferType.Empty; status = InitializeSecurityContext( ref _hCredHandle, ref _hContext, _target, 49436, //ISC_REQ_SEQUENCE_DETECT|ISC_REQ_REPLAY_DETECT|ISC_REQ_CONFIDENTIALITY|ISC_RET_EXTENDED_ERROR |ISC_REQ_ALLOCATE_MEMORY |ISC_REQ_STREAM; 0, 16, //SECURITY_NATIVE_DREP inBuff, 0, IntPtr.Zero, outBuff, ref contextAttrs, ref exp); _trace.TraceInformation("InitializeSecurityContext returned {0}", GetSchannelMessage(status)); // If InitializeSecurityContext was successful (or if the error was // one of the special extended ones), send the contends of the output // buffer to the server. // if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED || (status < 0 && (contextAttrs & ISC_RET_EXTENDED_ERROR) > 0)) { //if (outBuff[0].Length>0) ExpectingData(outBuff[0].Data); SendBuffer(outBuff[0]); } // // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE, // then we need to read more data from the server and try again. // if (status == SEC_E_INCOMPLETE_MESSAGE) { continue; } if (status == SEC_I_INCOMPLETE_CREDENTIALS) { //we have a problem with the credentials } //copy extra to offset data int extraIndex = -1; for (int i = 0; i < inBuff.Count; i++) { if (inBuff[i].BufferType == BufferType.Extra) { extraIndex = i; break; } } if (extraIndex>=0) { offset = 0; Array.Copy(inBuff[extraIndex].Data, 0, data, offset, inBuff[extraIndex].Length); offset = inBuff[extraIndex].Length; } else { offset = 0; } if (status == SEC_E_OK) { // If the "extra" buffer contains data, this is encrypted application // protocol layer stuff. It needs to be saved. The application layer // will later decrypt it with DecryptMessage. // //value extra buffer if (offset > 0) throw new InvalidDataException(); break; } }//End while if (status != SEC_E_OK) { //ToDO Fix //_hContext.Close(); throw new Exception(GetSchannelMessage(status)); } } public void PerformHandshake(TcpClient tcpClient) { long expiers = 0; uint contextAttr = 0; int status = 0; _tcpClient = tcpClient; SecurityHandle nullHandle = new NullSecuirtyHandle(); //TODO: release previous context if (_target == null) _target = _tcpClient.Client.RemoteEndPoint.ToString(); ChannelOutputBuffer outBuff = new ChannelOutputBuffer(); InputBuffer inputBuff = new InputBuffer(); status = InitializeSecurityContextInit( ref _hCredHandle, IntPtr.Zero, _target, 49436, //ISC_REQ_SEQUENCE_DETECT|ISC_REQ_REPLAY_DETECT|ISC_REQ_CONFIDENTIALITY|ISC_RET_EXTENDED_ERROR |ISC_REQ_ALLOCATE_MEMORY |ISC_REQ_STREAM; 0, 16, //SECURITY_NATIVE_DREP=16 IntPtr.Zero, 0, ref _hContext, outBuff, ref contextAttr, ref expiers); _trace.TraceInformation("InitializeSecurityContext({0}) returned {1}", _target, GetSchannelMessage(status)); _trace.TraceInformation("New Context is {0}", this.GetHandleMessage(ref _hCredHandle)); // check the buffer if (status >= 0) SendBuffer(outBuff[0]);//send the buffer if (status > 0) PerformClientLoop(true); } public uint SChannelRequestFlags { get { //ISC_REQ_SEQUENCE_DETECT | //ISC_REQ_REPLAY_DETECT | //ISC_REQ_CONFIDENTIALITY | //ISC_RET_EXTENDED_ERROR | //ISC_REQ_ALLOCATE_MEMORY | //ISC_REQ_STREAM; return 49436; } } private string GetHandleMessage(ref SECURITY_HANDLE handle) { return handle.HighPart + "," + handle.LowPart; } public string GetSchannelMessage(int status) { string result=string.Empty; switch ((uint)status) { case 0x00000000: result = "Completed successfully."; break; case 0x80090300: result="Not enough memory is available to complete this request"; break; case 0x80090301: result="The handle specified is invalid"; break; case 0x80090302: result="The function requested is not supported"; break; case 0x80090303: result="The specified target is unknown or unreachable"; break; case 0x80090304: result="The Local Security Authority cannot be contacted"; break; case 0x80090305: result="The requested security package does not exist"; break; case 0x80090306: result="The caller is not the owner of the desired credentials"; break; case 0x80090307: result="The security package failed to initialize, and cannot be installed"; break; case 0x80090308: result="The token supplied to the function is invalid"; break; case 0x80090309: result="The security package is not able to marshall the logon buffer, so the logon attempt has failed"; break; case 0x8009030A: result="The per-message Quality of Protection is not supported by the security package"; break; case 0x8009030B: result="The security context does not allow impersonation of the client"; break; case 0x8009030C: result="The logon attempt failed"; break; case 0x8009030D: result="The credentials supplied to the package were not recognized"; break; case 0x8009030E: result="No credentials are available in the security package"; break; case 0x8009030F: result="The message supplied for verification has been altered"; break; case 0x80090310: result="The message supplied for verification is out of sequence"; break; case 0x80090311: result="No authority could be contacted for authentication."; break; case 0x00090312: result="The function completed successfully, but must be called again to complete the context"; break; case 0x00090313: result="The function completed successfully, but CompleteToken must be called"; break; case 0x00090314: result="The function completed successfully, but both CompleteToken and this function must be called to complete the context"; break; case 0x00090315: result="The logon was completed, but no network authority was available. The logon was made using locally known information"; break; case 0x80090316: result="The requested security package does not exist"; break; case 0x80090317: result="The context has expired and can no longer be used."; break; case 0x80090318: result="The supplied message is incomplete. The signature was not verified."; break; case 0x00090320: result="The credentials supplied were not complete, and could not be verified. Additional information can be returned from the context."; break; case 0x80090321: result="The buffers supplied to a function was too small."; break; case 0x00090321: result = "The context data must be renegotiated with the peer."; break; case 0x80090322: result = "The target principal name is incorrect"; break; case 0x80090325: result = "The certificate chain was issued by an authority that is not trusted."; break; case 0x80090326: result = "The message recieved was unexpected or badly formatted."; break; case 0x80090327: result = "The certificate recieved from the remote server has not validated correctly"; break; case 0x80090329: result = "The specified data could not be encrypted"; break; case 0x80090330: result = "The specified data could not be decrypted."; break; default: result = "SChannel Error " + status.ToString("X8"); break; } return result; } /// /// Security provider for TLS handshake /// public static string SchannelPackageName { get { return "Microsoft Unified Security Protocol Provider"; } } /// /// TraceSource for Schannel Tracing /// public static TraceSource TraceSource { get { return _trace; } } /// /// Schannel Buffer type /// protected enum BufferType : int { Empty=0, Data=1, Token=2, Params=3, Missing=4, Extra=5, StreamTrailer=6, StreamHeader=7, Negotiate=8, Padding=9, Stream=10, } /// /// An Schannel Security buffer /// /// protected class SecurityBuffer { //Schannal data int _bufferSize; BufferType _bufferType; byte[] _data; // internal data int _offset; public SecurityBuffer() { } public BufferType BufferType { get { return _bufferType;} set { _bufferType=value;} } public byte[] Data { get { return _data; } } public int Length { get {return _bufferSize; } } public int Offset { get { return _offset; } } public void SetBuffer(byte[] data,int offset, int len) { _data=data; _offset=offset; _bufferSize=len; } } /// /// A description of schannel SecurityBuffers /// /// InputBuffer,ChannelOutputBuffer inherit this protected class SecurityBufferDescription { SecurityBuffer[] _buffers; protected SecurityBufferDescription() { _buffers = new SecurityBuffer[0]; } protected SecurityBufferDescription(int size) { _buffers = new SecurityBuffer[size]; for (int i = 0; i < size; i++) _buffers[i] = new SecurityBuffer(); } public SecurityBuffer this[int index] { get { return _buffers[index]; } } public int Count { get { return _buffers.Length; } } } /// /// An Schannel Input buffer /// /// A buffer size of 0 will result in a null value being marshaled protected class InputBuffer : SecurityBufferDescription { public InputBuffer() : base(0) { } public InputBuffer(int size) : base(size) { } } /// /// An Schannel Allocated output buffer /// protected class ChannelOutputBuffer : SecurityBufferDescription { public ChannelOutputBuffer() : base(1) { } } /// /// Base definition of a Security Handle /// protected class SecurityHandle { protected SecurityHandle() { } } /// /// Represents a Null Security Handle /// protected class NullSecuirtyHandle : SecurityHandle { public NullSecuirtyHandle() : base() { } } /// /// Base Security Handle with a HI/Low pointers /// protected class HiLoHandle : SecurityHandle { SECURITY_HANDLE _handle; protected HiLoHandle() { _handle.HighPart = IntPtr.Zero; _handle.LowPart = IntPtr.Zero; } public IntPtr HighPart { get { return _handle.HighPart; } set { _handle.HighPart = value; } } public IntPtr LowPart { get { return _handle.LowPart; } set { _handle.LowPart = value; } } } /// /// A Security Credential Passed to AcquireCredentialsHandle() /// protected class UserCredentials : IDisposable { public virtual IntPtr MarshalToPtr() { return IntPtr.Zero; } public virtual bool CompareTo(UserCredentials creds) { return false; } #region IDisposableMembers private bool _disposed = false; protected virtual void Dispose(bool disposing) { _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~UserCredentials() { Dispose(false); } #endregion }//End UserCredentials /// /// A Null Credential Passed to AcquireCredentialsHandle() /// protected class NullCredential : UserCredentials { }// End NullCredential /// /// A Windows User Credential to /// protected class WindowsCredentials : UserCredentials { string _user; string _domain; SecureString _password; public WindowsCredentials() : base() { } public string UserName { get { return _user; } set { _user = value; } } public string DomainName { get { return _domain; } set { _domain = value; } } public SecureString Password { get { return _password; } set { _password?.Dispose(); _password = value ?? new SecureString(); } } public override IntPtr MarshalToPtr() { IntPtr ptr; SEC_WINNT_AUTH_IDENTITY identity = new SEC_WINNT_AUTH_IDENTITY(); identity.Flags = 2;//Unicode identity.User = _user; if (_user == null) identity.UserLength = 0; else identity.UserLength = _user.Length; identity.Domain = _domain; if (_domain == null) identity.DomainLength = 0; else identity.DomainLength = _domain.Length; identity.Password = _password?.Copy(); if (_password == null) identity.PasswordLength = 0; else identity.PasswordLength = _password.Length; ptr = Marshal.AllocHGlobal(Marshal.SizeOf(identity)); //old struct has nothing to dispose so (true) means nothing Marshal.StructureToPtr(identity, ptr, false); return ptr; } public override bool CompareTo(UserCredentials creds) { if (creds is WindowsCredentials) { WindowsCredentials winCreds = (WindowsCredentials)creds; if (winCreds.DomainName == null || winCreds.UserName == null) { return UserName == null && DomainName == null; } if (winCreds.UserName.Equals(UserName) && winCreds.DomainName.Equals(DomainName)) { return true; } } return base.CompareTo(creds); } #region IDisposableMembers private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (_disposed) return; base.Dispose(disposing); if (disposing) { _password?.Dispose(); } _disposed = true; } #endregion } protected class CertificateCredentials : UserCredentials { X509Certificate2 _cert; uint _flags; public CertificateCredentials() : base() { } public override IntPtr MarshalToPtr() { IntPtr ptr = IntPtr.Zero; if (_cert != null || _flags != 0) { SCHANNEL_CRED cred = new SCHANNEL_CRED(); cred.dwVersion = 4;//SCHANNEL_CRED_VERSION=4 cred.dwFlags = _flags; //manual verification //cred.dwFlags = 262168; cred.cCreds = 0; if (_cert != null) cred.cCreds = 1; ptr = Marshal.AllocHGlobal(Marshal.SizeOf(cred) + (IntPtr.Size * (int)cred.cCreds)); if (cred.cCreds > 0) { cred.paCred = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(cred)); } Marshal.StructureToPtr(cred, ptr, false); if (cred.cCreds > 0) { Marshal.WriteIntPtr(cred.paCred, _cert.Handle); IntPtr tt = Marshal.ReadIntPtr(cred.paCred); } } return ptr; } public X509Certificate2 Certificate { get { return _cert; } set { _cert = value; } } public void SendRoot() { //SCH_CRED_NO_DEFAULT_CREDS=0x00000010; //SCH_SEND_ROOT_CERT=0x00040000 _flags = _flags |0x00040000 | 0x00000010; } public void SkipCNCheck() { //SCH_CRED_NO_DEFAULT_CREDS=0x00000010; //SCH_CRED_NO_SERVERNAME_CHECK = 0x00000004; _flags = _flags |0x00000004 | 0x00000010; } public void SkipCACheck() { //SCH_CRED_NO_DEFAULT_CREDS=0x00000010; //SCH_CRED_MANUAL_CRED_VALIDATION=0x00000008 _flags = _flags |0x00000008 | 0x00000010; } public void SkipDefaultCreds() { //SCH_CRED_NO_DEFAULT_CREDS=0x00000010; //SCH_CRED_MANUAL_CRED_VALIDATION=0x00000008 _flags = 0x00000010; } public override bool CompareTo(UserCredentials creds) { bool certMatch = false; bool flagMatch = false; if (creds is CertificateCredentials) { CertificateCredentials certCreds = (CertificateCredentials)creds; if (certCreds.Certificate != null && _cert != null) { certMatch = certCreds.Certificate.Thumbprint.Equals(_cert.Thumbprint); } else { certMatch = certCreds.Certificate == null && _cert == null; } flagMatch = certCreds._flags ==_flags; } return certMatch && flagMatch; } } /// /// A Cached Credential Item /// protected class CacheItem { IntPtr _highPtr; IntPtr _lowPtr; UserCredentials _credentials; long _expires; public CacheItem(IntPtr hiPtr,IntPtr lowPtr, UserCredentials auth, long expires) { _highPtr = hiPtr; _lowPtr = lowPtr; _credentials = auth; _expires = expires; } public UserCredentials Credentials { get { return _credentials; } } public IntPtr HighPart { get { return _highPtr; } } public IntPtr LowPart { get { return _lowPtr; } } public long Expiers { get { return _expires; } } } /// /// A Security Context Attribute /// protected class SecurityAttribute { protected SecurityAttribute() : base() { } public virtual void FreeNavtive(IntPtr ptr) { Marshal.FreeHGlobal(ptr); } public virtual IntPtr MarshalToNavtive() { return IntPtr.Zero; } public virtual void MarshalFromNavtive(IntPtr ptr) { } } /// /// A Security Context Attribute /// protected class StreamSizeAttribute : SecurityAttribute { SecPkgContext_StreamSizes _attribute; public StreamSizeAttribute() : base() { } public override IntPtr MarshalToNavtive() { IntPtr result = Marshal.AllocHGlobal(Marshal.SizeOf(_attribute)); return result; } public override void MarshalFromNavtive(IntPtr ptr) { _attribute = (SecPkgContext_StreamSizes)Marshal.PtrToStructure(ptr, typeof(SecPkgContext_StreamSizes)); } public int Header { get { return (int)_attribute.cbHeader; } } public int Trailer { get { return (int)_attribute.cbTrailer; } } public int MaximumMessage { get { return (int)_attribute.cbMaximumMessage; } } } /// /// Custom Marshaler for Schannel API Structures /// /// MashalCookie is used to select Marshaler for the specific type protected class ChannelMarshaler : ICustomMarshaler { object _objectToMashal; // the managed object being marshaled public ChannelMarshaler() { } public int GetNativeDataSize() { return -1;//never gets called? } public void CleanUpManagedData(object managedObject) { // nothing to clean up } public void CleanUpNativeData(IntPtr ptr) { if (!ptr.Equals(IntPtr.Zero)) Marshal.FreeCoTaskMem(ptr); } public IntPtr MarshalManagedToNative(object managedObject) { IntPtr result=IntPtr.Zero; if (managedObject is NullSecuirtyHandle) { _objectToMashal = managedObject; } else if (managedObject is HiLoHandle) { _objectToMashal = managedObject; int x = Marshal.SizeOf(typeof(SECURITY_HANDLE)); x.ToString(); result = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(SECURITY_HANDLE))); Marshal.WriteIntPtr(result, 0, ((HiLoHandle)managedObject).LowPart); Marshal.WriteIntPtr(result, IntPtr.Size, ((HiLoHandle)managedObject).HighPart); } return result; } public object MarshalNativeToManaged(IntPtr ptr) { if (_objectToMashal is NullSecuirtyHandle) { } else if (_objectToMashal is HiLoHandle) { ((HiLoHandle)_objectToMashal).LowPart = Marshal.ReadIntPtr(ptr, 0 ); ((HiLoHandle)_objectToMashal).HighPart = Marshal.ReadIntPtr(ptr, IntPtr.Size ); } return _objectToMashal; } public static ICustomMarshaler GetInstance(string cookie) { ICustomMarshaler result=null; switch (cookie) { case "Input": result = new InputBufferMarshaler(); break; case "Output": result = new OutputBufferMarshaler(); break; case "Handle": result = new ChannelMarshaler(); break; case "Attribute": result = new AttributeMarshaler(); break; case "Credential": result = new CredentialMarshaler(); break; default: throw new ArgumentException("Unknown Schannel Marshal Cookie"); } return result; } } /// /// Attribute Marshaler for Schannel API Structures /// /// MashalCookie is used to select Marshaler for the specific type protected class AttributeMarshaler : ICustomMarshaler { SecurityAttribute _objectToMarshal; public AttributeMarshaler() { } public int GetNativeDataSize() { return -1;//never gets called? } public void CleanUpManagedData(object managedObject) { _objectToMarshal = null; } public void CleanUpNativeData(IntPtr ptr) { _objectToMarshal.FreeNavtive(ptr); } public IntPtr MarshalManagedToNative(object managedObject) { _objectToMarshal = (SecurityAttribute)managedObject; return _objectToMarshal.MarshalToNavtive(); } public object MarshalNativeToManaged(IntPtr ptr) { _objectToMarshal.MarshalFromNavtive(ptr); return _objectToMarshal; } } /// /// Credential Marshaler for Schannel API Structures /// /// MashalCookie is used to select Marshaler for the specific type protected class CredentialMarshaler : ICustomMarshaler { public CredentialMarshaler() { } public int GetNativeDataSize() { return -1;//never gets called? } public void CleanUpManagedData(object managedObject) { } public void CleanUpNativeData(IntPtr ptr) { //_objectToMarshal.FreeNavtive(ptr); if (!ptr.Equals(IntPtr.Zero)) Marshal.FreeHGlobal(ptr); } public IntPtr MarshalManagedToNative(object managedObject) { UserCredentials userCreds = (UserCredentials)managedObject; return userCreds.MarshalToPtr(); } public object MarshalNativeToManaged(IntPtr ptr) { //_objectToMarshal.MarshalFromNavtive(ptr); //return _objectToMarshal; return null; } } /// /// Mashaler for Schannel Allocated OuputBuffers /// internal class OutputBufferMarshaler : ICustomMarshaler { ChannelOutputBuffer _objectToMarshal; public OutputBufferMarshaler() { } public int GetNativeDataSize() { return -1;//never gets called? } public void CleanUpManagedData(object managedObject) { // nothing to clean up } public void CleanUpNativeData(IntPtr ptr) { Marshal.FreeHGlobal(ptr); } public IntPtr MarshalManagedToNative(object managedObject) { IntPtr result = IntPtr.Zero; _objectToMarshal = (ChannelOutputBuffer)managedObject; // alocate enough memory result = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SecBufferDesc)) + Marshal.SizeOf(typeof(SecBuffer))); //write the description Marshal.WriteInt32(result, 0, 0); //write SECBUFFER_VERSION Marshal.WriteInt32(result, sizeof(uint), 1); //write buffer count //create an array pointer to a buffer IntPtr pBuffer = new IntPtr(result.ToInt64() + Marshal.SizeOf(typeof(SecBufferDesc))); Marshal.WriteInt32(pBuffer, 0, 0); //write buffer size Marshal.WriteInt32(pBuffer, sizeof(uint), (int)BufferType.Token); //write buffer type Marshal.WriteIntPtr(pBuffer, sizeof(uint) + sizeof(uint), IntPtr.Zero); //write pointer //write the array pointer Marshal.WriteIntPtr(result, sizeof(uint) + sizeof(uint), pBuffer); //write buffer count return result; } public object MarshalNativeToManaged(IntPtr ptr) { //read the description int buffSize = Marshal.ReadInt32(ptr, sizeof(uint)); //read buffer count IntPtr pBuffers = Marshal.ReadIntPtr(ptr, sizeof(uint) + sizeof(uint)); //read buffer int cb = Marshal.ReadInt32(pBuffers, 0); //read buffer size IntPtr pCtxData = Marshal.ReadIntPtr(pBuffers, sizeof(uint) + sizeof(uint)); //read buffer ChannelOutputBuffer result = _objectToMarshal; if (result==null) result = new ChannelOutputBuffer(); if (!pCtxData.Equals(IntPtr.Zero) && cb > 0) { byte[] data = new byte[cb]; Marshal.Copy(pCtxData, data, 0, data.Length); int status=FreeContextBuffer(pCtxData); result[0].SetBuffer(data,0,cb); } return result; } } /// /// Mashaler for Schannel InputBuffers /// internal class InputBufferMarshaler : ICustomMarshaler { InputBuffer _objectToMarshal; public InputBufferMarshaler() { } public int GetNativeDataSize() { return -1; //never gets called? } public void CleanUpManagedData(object managedObject) { } public void CleanUpNativeData(IntPtr ptr) { if (!ptr.Equals(IntPtr.Zero)) Marshal.FreeHGlobal(ptr); } public IntPtr MarshalManagedToNative(object managedObject) { IntPtr result = IntPtr.Zero; _objectToMarshal = (InputBuffer)managedObject; if (_objectToMarshal.Count>0) { //calc total size int total = Marshal.SizeOf(typeof(SecBufferDesc)); total += Marshal.SizeOf(typeof(SecBuffer))*_objectToMarshal.Count; int dataLen = 0; //get size of all buffers for (int i=0;i<_objectToMarshal.Count;i++) dataLen+=_objectToMarshal[i].Length; total += dataLen; result = Marshal.AllocHGlobal(total); for (int i = 0; i < total; i++) Marshal.WriteByte(result, i, (byte)0); //write the description Marshal.WriteInt32(result, 0, 0); //write SECBUFFER_VERSION Marshal.WriteInt32(result, sizeof(uint), _objectToMarshal.Count); //write buffer count //create an array pointer to a buffer IntPtr pBuffer = new IntPtr(result.ToInt64() + Marshal.SizeOf(typeof(SecBufferDesc))); Marshal.WriteIntPtr(result, sizeof(uint)+sizeof(uint), pBuffer); //write buffer int arrayOffset =0; int dataOffet = Marshal.SizeOf(typeof(SecBufferDesc))+(Marshal.SizeOf(typeof(SecBuffer))*_objectToMarshal.Count); byte[] baseData = null; for (int i=0;i<_objectToMarshal.Count;i++) { //write buffer size Marshal.WriteInt32(pBuffer, arrayOffset,_objectToMarshal[i].Length); //write buffer type Marshal.WriteInt32(pBuffer, arrayOffset +sizeof(uint),(int)_objectToMarshal[i].BufferType); if (i == 0) baseData = _objectToMarshal[i].Data; //write buffer Pointer if (_objectToMarshal[i].Length>0) { IntPtr dataPtr = new IntPtr(result.ToInt64()+dataOffet); //only copy the data if its not null (otherwise its uninitalized data) Marshal.Copy( baseData, _objectToMarshal[i].Offset, dataPtr, _objectToMarshal[i].Length); //write pointer Marshal.WriteIntPtr(pBuffer, arrayOffset+ sizeof(uint) + sizeof(uint), dataPtr); } else Marshal.WriteIntPtr(pBuffer,arrayOffset+ sizeof(uint) + sizeof(uint), IntPtr.Zero); arrayOffset+=Marshal.SizeOf(typeof(SecBuffer)); dataOffet+=_objectToMarshal[i].Length; } } return result; } public object MarshalNativeToManaged(IntPtr ptr) { //read the description int buffNums = Marshal.ReadInt32(ptr, sizeof(uint)); //read buffer count IntPtr pBuffers = Marshal.ReadIntPtr(ptr, sizeof(uint) + sizeof(uint)); //read buffer InputBuffer result = _objectToMarshal; int arrayOffset =0; long baseLong=0; byte[] baseData = null; int prevLen=0; IntPtr prevBuffer=IntPtr.Zero; for (int i = 0; i < buffNums; i++) { //read buffer size int buffSize = Marshal.ReadInt32(pBuffers, arrayOffset); BufferType buffType = (BufferType)Marshal.ReadInt32(pBuffers, arrayOffset +sizeof(uint)); IntPtr buffPtr = Marshal.ReadIntPtr(pBuffers, arrayOffset+ sizeof(uint) + sizeof(uint)); _objectToMarshal[i].BufferType = buffType; // the buffer is the managed data and Navtive data if (buffType == BufferType.Extra) { int of = prevLen - buffSize; baseLong = prevBuffer.ToInt64()+of; buffPtr = new IntPtr( baseLong); } if (i == 0) { baseLong = buffPtr.ToInt64(); baseData = _objectToMarshal[i].Data; prevLen = buffSize; prevBuffer = buffPtr; } try { _objectToMarshal[i].SetBuffer(baseData, (int)(buffPtr.ToInt64() - baseLong), buffSize);//Conversion from long to int } catch (System.OverflowException e) { throw new System.OverflowException(e.ToString()); } if (buffSize > 0 && !buffPtr.Equals(IntPtr.Zero)) { Marshal.Copy(buffPtr, baseData, _objectToMarshal[i].Offset, buffSize); } _objectToMarshal[i].BufferType = buffType; // update the buffer type (should be token ot Extra) arrayOffset += Marshal.SizeOf(typeof(SecBuffer)); } return result; } } [DllImport("secur32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int AcquireCredentialsHandle( string pszPrincipal, string pszPackage, int fCredentialUse, IntPtr PAuthenticationID, [In, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Credential")] UserCredentials pAuthData, IntPtr pGetKeyFn, IntPtr pvGetKeyArgument, ref SECURITY_HANDLE phCredential, ref long ptsExpiry); [DllImport("secur32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int InitializeSecurityContext( ref SECURITY_HANDLE phCredential, ref SECURITY_HANDLE phContext, string pszTargetName, uint fContextReq, uint Reserved1, uint TargetDataRep, [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Input")] SecurityBufferDescription pInput, uint Reserved2, IntPtr hNewContext, [In,Out,MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Output")] SecurityBufferDescription pOutput, ref uint pfContextAttr, ref long ptsExpiry); [DllImport("secur32.dll", EntryPoint = "InitializeSecurityContext", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int InitializeSecurityContextInit( ref SECURITY_HANDLE phCredential, IntPtr phContext, //this is null on input string pszTargetName, uint fContextReq, uint Reserved1, uint TargetDataRep, IntPtr pInput, //this is null on init call uint Reserved2, ref SECURITY_HANDLE hNewContext, // this is output [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Output")] SecurityBufferDescription pOutput, ref uint pfContextAttr, ref long ptsExpiry); [DllImport("secur32.dll", EntryPoint = "InitializeSecurityContext", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int InitializeSecurityContextExchange( ref SECURITY_HANDLE phCredential, IntPtr pContext, //this is null on exhange string pszTargetName, uint fContextReq, uint Reserved1, uint TargetDataRep, [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Input")] SecurityBufferDescription pInput, uint Reserved2, ref SECURITY_HANDLE hNewContext, // this is output [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Output")] SecurityBufferDescription pOutput, ref uint pfContextAttr, ref long ptsExpiry); [DllImport("secur32.dll", SetLastError = true)] private static extern int EncryptMessage( ref SECURITY_HANDLE hContext, uint fQoP, [In,Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Input")] SecurityBufferDescription buffer, ulong seqNum); [DllImport("secur32.dll", SetLastError = true)] private static extern int DecryptMessage( ref SECURITY_HANDLE hContext, [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Input")] SecurityBufferDescription buffer, ulong seqNum, IntPtr fQoP ); [DllImport("secur32.dll", SetLastError = true)] private static extern int ApplyControlToken( ref SECURITY_HANDLE hContext, [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Input")] SecurityBufferDescription buffer); [DllImport("secur32.dll", SetLastError = true)] private static extern int FreeContextBuffer(IntPtr ptrToBuffer); [DllImport("secur32.dll", SetLastError = true)] private static extern int DeleteSecurityContext( ref SECURITY_HANDLE hContext); [DllImport("secur32.dll", SetLastError = true)] private static extern int FreeCredentialsHandle( ref SECURITY_HANDLE phCredHandle); [DllImport("secur32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int QuerySecurityPackageInfo( string packageName, out IntPtr packageInfo); [DllImport("secur32.dll", SetLastError = true)] private static extern int QueryContextAttributes( ref SECURITY_HANDLE hContext, uint ulAttribute, [In,Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ChannelMarshaler), MarshalCookie = "Attribute")] SecurityAttribute buffer); [StructLayout(LayoutKind.Sequential)] private struct SecPkgContext_StreamSizes { public uint cbHeader; public uint cbTrailer; public uint cbMaximumMessage; public uint cBuffers; public uint cbBlockSize; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct SEC_WINNT_AUTH_IDENTITY { public string User; public int UserLength; public string Domain; public int DomainLength; public SecureString Password; public int PasswordLength; public int Flags;//UNICODE=2,ASNI=1 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct SecPkgInfo { public uint fCapabilities; public ushort wVersion; public ushort wRPCID; public uint cbMaxToken; public string Name; public string Comment; public static SecPkgInfo FromIntPtr(IntPtr ptr) { return (SecPkgInfo)Marshal.PtrToStructure(ptr, typeof(SecPkgInfo)); } } [StructLayout(LayoutKind.Sequential)] private struct SCHANNEL_CRED { public uint dwVersion; public uint cCreds; public IntPtr paCred; public IntPtr hRootStore; public uint cMappers; public IntPtr aphMappers; public uint cSupportedAlgs; public IntPtr palgSupportedAlgs; public uint grbitEnabledProtocols; public uint dwMinimumCipherStrength; public uint dwMaximumCipherStrength; public uint dwSessionLifespan; public uint dwFlags; public uint dwCredFormat; } [StructLayout(LayoutKind.Sequential)] private struct SECURITY_HANDLE { public IntPtr LowPart; public IntPtr HighPart; } [StructLayout(LayoutKind.Sequential)] private struct SecBufferDesc { public uint ulVersion;// Version number SECBUFFER_VERSION=0 public uint cBuffers;// Number of buffers public IntPtr pBuffers; } [StructLayout(LayoutKind.Sequential)] private struct SecBuffer { public uint cbBuffer; public uint BufferType; public IntPtr pvBuffer; public static IntPtr Create(int size) { SecBuffer outBuffer = new SecBuffer(); IntPtr result = Marshal.AllocHGlobal(Marshal.SizeOf(outBuffer)*size); //zero fill for (int i = 0; i < (Marshal.SizeOf(outBuffer) * size); i++) Marshal.WriteByte(result, i, 0); return result; } } } //end class }//end namespace