2136 lines
59 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security;
using Common.Utils;
namespace Intel.Management.Wsman
{
/// <summary>
/// Implements the Http Transport for Wsman requests
/// </summary>
public class HttpTransport : IDisposable
{
/// <summary>
/// TcpClient for send TLS Handshaking messages
/// </summary>
protected TcpClient _tcpClient;
/// <summary>
/// Authentication handshake to use
/// </summary>
protected ISecurityHandshake _authHandshake;
/// <summary>
/// Proxy Authentication handshake to use
/// </summary>
protected ISecurityHandshake _proxyHandshake;
/// <summary>
/// The Uri of the connection
/// </summary>
protected Uri _uri;
/// <summary>
/// The Network address type (IPV4/IPV6)
/// </summary>
protected AddressFamily _addressType;
/// <summary>
/// the maximum HTTP message/envolope Size
/// </summary>
protected int _maxEnvelopeSize = 153600;
/// <summary>
/// HttpProxy Object
/// </summary>
protected HttpProxy _proxy;
/// <summary>
/// Http Header trace
/// </summary>
protected static System.Diagnostics.TraceSource _trace = new System.Diagnostics.TraceSource("Intel.Management.Wsman.HttpTransport");
/// <summary>
/// Constructs the HTTP for the given URI and address type
/// </summary>
/// <param name="addressType"></param>
/// <param name="address"></param>
protected HttpTransport(AddressFamily addressType, Uri address)
{
_uri = address;
_addressType = addressType;
_proxy = null;
//System.Threading.Thread.
}
public static System.Diagnostics.TraceSource TraceSource
{
get { return _trace; }
}
/// <summary>
/// Gets the name of the protocol
/// </summary>
public virtual string Name
{
get
{
return "Unknown";
}
}
/// <summary>
/// Gets the underlying TCP client
/// </summary>
public TcpClient TcpClient
{
get { return _tcpClient; }
}
/// <summary>
/// Gets or sets the proxy to go through
/// </summary>
public HttpProxy Proxy
{
get { return _proxy; }
set
{
_proxy?.Dispose();
_proxy = value;
}
}
/// <summary>
/// The address of the protocol endpoint
/// </summary>
public Uri Address
{
get { return _uri; }
}
/// <summary>
/// Returns the request-uri for the http method
/// </summary>
public virtual string GetRequestUri()
{
return "/";
}
/// <summary>
/// The security handshake the protocol will use
/// </summary>
public ISecurityHandshake Authorization
{
get { return _authHandshake; }
set { _authHandshake = value; }
}
/// <summary>
/// Gets a flag indicating if the network socket is keeped open
/// </summary>
protected virtual bool KeepAlive
{
get { return true; }
}
/// <summary>
/// Closes the network socket
/// </summary>
public virtual void Close()
{
if (_tcpClient != null)
{
_tcpClient.GetStream().Close();
_tcpClient.Close();
_tcpClient = null;
}
}
public virtual string GetProxyAuthorization(string method, string url, ServerResponse response)
{
if (_proxy != null && _proxyHandshake == null)
{
_proxyHandshake = _proxy.GetAuthorization(method, url, response.ProxyChallange.ToLower());
if(_proxyHandshake != null)
{
_proxyHandshake.GetInitialToken();
_proxyHandshake.SetReturnToken(response.ProxyChallange);
}
}
if (_proxyHandshake != null)
return _proxyHandshake.GetNextToken();
return null;
}
/// <summary>
/// Reads a message from the network socket
/// </summary>
protected virtual int ReadMessage(byte[] data, int offset, int count)
{
return _tcpClient.GetStream().Read(data, offset, count);
}
/// <summary>
/// Sends the message over the network socket
/// </summary>
/// <param name="message">The message to send</param>
protected virtual void SendMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
_tcpClient.GetStream().Write(data, 0, data.Length);
}
/// <summary>
/// Sets the credentials for the transport
/// </summary>
///<param name="cert">A client certificate</param>
public virtual void SetCredentials(X509Certificate2 cert)
{
}
/// <summary>
/// Sets the certificate validation options
/// </summary>
///<param name="verifyServerCA">A client certificate</param>
///<param name="verifyHost">Value indicates if the certifcate CN name must match the http host name</param>
///<param name="sendRoot">Value indecates if the root certificate should be included in the client chain</param>
public virtual void SetCertificateValidationOptions(bool verifyServerCA, bool verifyHost, bool sendRoot)
{
}
/// <summary>
/// Initializes a new request
/// </summary>
public virtual void InitializeRequest()
{
if (_tcpClient == null)
{
_tcpClient = new TcpClient(_addressType);
if (_proxy != null)
_tcpClient.Connect(_proxy.Address.Host, _proxy.Address.Port);
else
_tcpClient.Connect(_uri.Host, _uri.Port);
}
}
public virtual void SendMessageHeaders(string headers)
{
_trace.TraceInformation(headers);
if (_tcpClient == null)
{
_tcpClient = new TcpClient(_addressType);
_tcpClient.Connect(_uri.Host, _uri.Port);
}
SendMessage(headers);
}
public virtual void SendMessageBody(string body)
{
SendMessage(body);
}
public virtual ServerResponse GetResponse(string method)
{
MemoryStream inStream;
ServerResponse response = null;
using (inStream = new MemoryStream())
{
StreamReader headerStream = null;
//will be all zeros
byte[] buffer = new byte[_tcpClient.ReceiveBufferSize];
//read http headers
do
{
int br = ReadMessage(buffer, 0, buffer.Length);
if (br == 0)
throw new WsmanConnectionException("Server unexpectedly disconnected");
inStream.Write(buffer, 0, br);
headerStream = GetHeaders(inStream);
} while (headerStream == null);
response = ParseHeaders(headerStream);
if (response != null)
{
if (method.Equals("CONNECT") && response.StatusCode >= 200 && response.StatusCode < 300)
{
//connect proxy 200s are not going to have a body so we are done
return response;
}
//done processing headers so swap buffer for reading body
int eoh;
try
{
eoh = GetEndOfMessage(inStream.GetBuffer(), 0, (int)inStream.Length);//Conversion from long to int
}
catch (System.OverflowException e)
{
throw new System.OverflowException(e.ToString());
}
MemoryStream temp;
using (temp = new MemoryStream())
{
try
{
temp.Write(inStream.GetBuffer(), eoh + 1, (int)inStream.Length - eoh - 1);//Conversion from long to int
}
catch (System.OverflowException e)
{
throw new System.OverflowException(e.ToString());
}
inStream.Close();
headerStream.Close();
inStream = temp;
inStream.Seek(0, SeekOrigin.Begin);
temp = null;
}
if (response != null && response.Chunked)
{
string line;
while (true)
{
line = ReadLine(buffer, inStream);
if (line == null)
throw new WsmanException("Invalid server response");
int pos = line.IndexOf(';');
if (pos > 0) line = line.Substring(0, pos);
int chunkLen = int.Parse(line, System.Globalization.NumberStyles.HexNumber);
if (chunkLen == 0) //last chunks
break;
if ((response.GetResponse().Length + chunkLen) > _maxEnvelopeSize)
throw new WsmanConnectionException("Response too big");
byte[] chunkData = new byte[chunkLen];
ReadChunk(chunkData, buffer, inStream);
response.GetResponse().Write(chunkData, 0, chunkData.Length);
}
//read until empty line
while (line != null && !line.Equals(string.Empty))
{
line = ReadLine(buffer, inStream);
}
try
{
String msg = Encoding.UTF8.GetString(
response.GetResponse().GetBuffer(),
0,
(int)response.GetResponse().Length);//Conversion from long to int
}
catch (System.OverflowException e)
{
throw new System.OverflowException(e.ToString());
}
}
else if (response != null && response.ContentLength >= 0)
{
if (response.ContentLength > _maxEnvelopeSize)
throw new WsmanConnectionException("Response too big");
int total = 0;
do
{
int br = ReadMessage(buffer, 0, buffer.Length);
if (br == 0)
throw new WsmanConnectionException("Server unexpectedly disconnected");
total += br;
response.GetResponse().Write(buffer, 0, br);
} while (total < response.ContentLength);
}
else //read until connection closed
{
int total = 0;
while (true)
{
int br = ReadMessage(buffer, 0, buffer.Length);
if (br == 0)
break;
total += br;
if (total > _maxEnvelopeSize)
throw new WsmanConnectionException("Response to big");
response.GetResponse().Write(buffer, 0, br);
}
}
//inStream.Close();
response.GetResponse().Seek(0, SeekOrigin.Begin);
if (response.Closed)
Close();
else if (!KeepAlive)
Close();
}
}
return response;
}
protected ServerResponse ParseHeaders(StreamReader reader)
{
string realm = string.Empty;
string server = string.Empty;
string contentType = string.Empty;
string wwwAuth = string.Empty;
string proxyAuth = string.Empty;
DateTime date = DateTime.UtcNow;
int contentLen = -1;
bool chunked = false;
bool closed = true;
string statusText = string.Empty;
int statusCode = 500;
string original = reader.ReadLine();
string line = original.ToLower();
while (line != null && !line.Equals(string.Empty))
{
_trace.TraceInformation(line);
if (line.StartsWith("http/1.1 ") || line.StartsWith("http/1.0"))
{
string[] status = original.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (status.Length > 1)
{
statusCode = int.Parse(status[1]);
}
if (status.Length > 2)
{
StringBuilder builder = new StringBuilder();
for (int i = 2; i < status.Length; i++)
{
if (i > 2) builder.Append(' ');
builder.Append(status[i]);
}
statusText = builder.ToString();
}
}
else if (line.StartsWith("www-authenticate: digest") && _authHandshake.Name.Equals("Digest"))
{
wwwAuth = original;
//_authHandshake.SetReturnToken(original);
}
else if (line.StartsWith("www-authenticate: negotiate") && _authHandshake.Name.Equals("Negotiate"))
{
wwwAuth = original;
//_authHandshake.SetReturnToken(original);
}
else if (line.StartsWith("www-authenticate: ") && _authHandshake.Name.Equals("Anonymous"))
{
wwwAuth = original;
//_authHandshake.SetReturnToken(original);
}
else if (line.StartsWith("connection: close"))
{
closed = true;
}
else if (line.StartsWith("connection: keep-alive"))
{
closed = false;
}
else if (line.StartsWith("server: "))
{
server = line.Substring("server: ".Length);
}
else if (line.StartsWith("content-length: "))
{
contentLen = int.Parse(line.Substring("content-length: ".Length));
}
else if (line.StartsWith("transfer-encoding: chunked"))
{
chunked = true;
}
else if (line.StartsWith("date"))
{
date = DateTime.Parse(original.Substring("Date: ".Length));
}
else if (line.StartsWith("proxy-authenticate: basic"))
{
if (string.IsNullOrEmpty(proxyAuth)) proxyAuth = original;
}
else if (line.StartsWith("proxy-authenticate: digest"))
{
proxyAuth = original;
}
else if (line.StartsWith("proxy-authenticate: negotiate"))
{
proxyAuth = original;
}
else if (line.StartsWith("proxy-connection: close"))
{
closed = true;
}
else if (line.StartsWith("proxy-connection: keep-alive"))
{
closed = false;
}
original = reader.ReadLine();
line = original.ToLower();
} //while headers
return new ServerResponse(new MemoryStream(),
contentType,
contentLen,
date,
wwwAuth,
proxyAuth,
chunked,
closed,
realm,
server,
statusText,
statusCode);
}
/// <summary>
/// Gets the End of a message (two new lines)
/// </summary>
/// <param name="data">The data to search</param>
/// <param name="index">The index to start the search</param>
/// <param name="len">The length of the data</param>
/// <returns>-1 if no end to the message, otherwise the index that marks the end</returns>
protected int GetEndOfMessage(byte[] data, int index, int len)
{
int eol = -1;
while (index >= 0 && index < len)
{
eol = index = Array.IndexOf(data, (byte)'\n', index, len - index);
if (index > 0 && index < len)
index = Array.IndexOf(data, (byte)'\n', index + 1, len - index - 1);
if (index == (eol + 2))
break;
eol = -1;
}
if (eol >= 0)
return index;
return -1;
}
protected StreamReader GetHeaders(MemoryStream inStream)
{
byte[] data = inStream.GetBuffer();
int len;
try
{
len = (int)inStream.Length;//Conversion from long to int
}
catch (System.OverflowException e)
{
throw new System.OverflowException(e.ToString());
}
int index = GetEndOfMessage(data, 0, len);
if (index > 0)
{
inStream.Seek(0, SeekOrigin.Begin);
return new StreamReader(inStream, Encoding.UTF8);
}
return null;//no headers found yet
}
protected byte ReadByte(byte[] buffer, MemoryStream instream)
{
int nible = instream.ReadByte();
if (nible < 0)
{
int br = ReadMessage(buffer, 0, buffer.Length);
instream.SetLength(0);
instream.Write(buffer, 0, br);
instream.Seek(0, SeekOrigin.Begin);
nible = instream.ReadByte();
}
return (byte)nible;
}
protected string ReadLine(byte[] buffer, MemoryStream instream)
{
List<byte> list = new List<byte>(8);
string result = null;
while (true)
{
byte nible = ReadByte(buffer, instream);
if (nible == (byte)'\n')
break;
if (result != null)
throw new WsmanException("Invalid server response");
if (nible != (byte)'\r' && nible != (byte)'\n')
list.Add((byte)nible);
else
result = Encoding.ASCII.GetString(list.ToArray());
if (nible == (byte)'\n')
break;
}//while nibling
return result;
}
protected void ReadChunk(byte[] chunkData, byte[] buffer, MemoryStream instream)
{
for (int i = 0; i < chunkData.Length; i++)
chunkData[i] = ReadByte(buffer, instream);
byte nible = ReadByte(buffer, instream);
if (nible == (byte)'\r') nible = ReadByte(buffer, instream);
if (nible != (byte)'\n')
throw new WsmanException("Invalid response from server");
}
#region IDisposable Members
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
Close();
_proxy?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}//End of HttpTransport
/// <summary>
/// Implements the Http Transport in plain Text
/// </summary>
public class PlainTextTransport : HttpTransport
{
public PlainTextTransport(AddressFamily family, Uri url)
: base(family, url)
{
}
public override string GetRequestUri()
{
if (_proxy != null)
return _uri.ToString();
return _uri.LocalPath;
}
public override string Name
{
get { return "PlainText"; }
}
protected override void SendMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
_tcpClient.GetStream().Write(data, 0, data.Length);
}
public void SendHeaders(string headers)
{
SendMessage(headers);
}
public void SendBody(string body)
{
SendMessage(body);
}
public StreamReader GetResponseReader()
{
return new StreamReader(_tcpClient.GetStream(), Encoding.UTF8);
}
}// end of PlainText reader
/// <summary>
/// Implements the Http Transport over TLS
/// </summary>
public class TlsTransport : PlainTextTransport, IDisposable
{
SchannelClient _schClient;
X509Certificate2 _clientCert;
bool _verifyCa;
bool _verifyCn;
bool _sendRoot;
bool _clearChannel;
public TlsTransport(AddressFamily family, Uri url)
: base(family, url)
{
_verifyCa = true;
_verifyCn = true;
_sendRoot = false;
_clearChannel = true;
}
private void Connect()
{
ISecurityHandshake proxy = null;
string proxyAuth = null;
int retry = 1;
while (retry > 0)
{
using (StringWriter writer = new StringWriter())
{
writer.Write("CONNECT ");
writer.Write(_uri.Authority);
writer.Write(" HTTP/1.1");
writer.WriteLine();
if (proxyAuth != null)
{
writer.Write("Proxy-Authorization: ");
writer.Write(proxyAuth);
writer.WriteLine();
}
writer.Write("Host: ");
writer.Write(_uri.Host);
writer.WriteLine();
writer.WriteLine();
SendMessageHeaders(writer.ToString());
}
ServerResponse response = GetResponse("CONNECT");
// we had a successful connect
if (response.StatusCode >= 200 && response.StatusCode < 300)
break;
//assume we got a 407
if (response.StatusCode != 407)
{
string text = "Proxy Error ";
if (!string.IsNullOrEmpty(response.StatusText))
text = response.StatusText;
throw new WsmanConnectionException(response.StatusText + "(" + text + ")");
}
proxyAuth = GetProxyAuthorization("CONNECT", "/", response);
if (proxy == null)
{
proxy = _proxyHandshake;
retry = _proxyHandshake.MaxAttempts;
}
else
{
proxy.SetReturnToken(response.ProxyChallange);
}
if (proxy.State != HandshakeState.ResponseNeeded)
retry = 0;
if (response.Closed)
{
_clearChannel = true;
base.Close();
}
base.InitializeRequest();
retry--;
}
base.InitializeRequest();
}
public override string GetRequestUri()
{
return _uri.LocalPath;
}
protected override bool KeepAlive
{
get
{
return true;
}
}
public override void Close()
{
_clearChannel = true;
_proxyHandshake = null;
if (_schClient != null && _tcpClient != null)
_schClient.CloseNotify();
base.Close();
}
public override string Name
{
get { return "TLS"; }
}
public override void SetCredentials(X509Certificate2 cert)
{
_clientCert = cert;
}
public X509Certificate2 GetCredentials()
{
return _clientCert;
}
public override void InitializeRequest()
{
base.InitializeRequest();
//send proxy connect if needed
if (_proxy != null && _clearChannel == true)
{
Connect();
}
//look for a cached channel
if (_schClient == null)
{
_schClient = new SchannelClient(SchannelClient.SchannelPackageName);
_schClient.Target = _uri.Host;
_schClient.AcquireCredentials(_clientCert, _verifyCa, _verifyCn, _sendRoot);
}
_schClient.PerformHandshake(_tcpClient);
_clearChannel = false;
}
public override void SetCertificateValidationOptions(bool verifyServerCA, bool verifyHost, bool sendRoot)
{
_verifyCa = verifyServerCA;
_verifyCn = verifyHost;
_sendRoot = sendRoot;
}
protected override void SendMessage(string message)
{
if (_clearChannel)
base.SendMessage(message);
else
_schClient.WriteMessage(message);
}
protected override int ReadMessage(byte[] data, int offset, int count)
{
if (_clearChannel)
return base.ReadMessage(data, offset, count);
int len = _schClient.ReadMessage(data, offset, count);
return len;
}
#region IDisposable Members
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_schClient?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}// end of TlsTransport
/// <summary>
/// The State of a security Handshake
/// </summary>
public enum HandshakeState
{
Uninitialized,
CredentialsAquired,
ResponseNeeded,
Complete,
Failed,
}
/// <summary>
/// Interface that represents a security Handshake (e.g. ,Digest,Negotiate)
/// </summary>
/// <remarks></remarks>
public interface ISecurityHandshake
{
/// <summary>
/// Gets the Initial Token of the handshake (starts the handshake)
/// </summary>
/// <returns>The first token (if any)</returns>
string GetInitialToken();
/// <summary>
/// Sets the return token from a handshake
/// </summary>
/// <param name="token">A security token returned from a handshake</param>
void SetReturnToken(string token);
/// <summary>
/// Gets the next token of the handshake(until complete)
/// </summary>
/// <returns>Next token to (if any)</returns>
string GetNextToken();
/// <summary>
/// Gets the number of Handshake attemps the protocol will use
/// </summary>
int MaxAttempts { get; }
/// <summary>
/// Gets the state of the Handshake process
/// </summary>
HandshakeState State { get; }
/// <summary>
/// Gets the name of the Protocol being implemented
/// </summary>
String Name { get; }
}
/// <summary>
/// Implements HTTP Basic Handshaking Protocol
/// </summary>
public class BasicAuthorization : ISecurityHandshake
{
string _wwwAuth;
HandshakeState _state; // the state of handshake
/// <summary>
/// Generates the initial security token(s)
/// </summary>
/// <returns>For Basic always returns null</returns>
public string GetInitialToken()
{
_state = HandshakeState.ResponseNeeded;
return null;
}
/// <summary>
/// Sets the security token returned from a server
/// </summary>
public void SetReturnToken(string token)
{
_state = HandshakeState.ResponseNeeded;
// there is no return token for Basic but to keep th loop we return response need
}
/// <summary>
/// Generates the initial security token(s)
/// </summary>
/// <returns>Returns Basic security token </returns>
public string GetNextToken()
{
return _wwwAuth;
}
/// <summary>
/// Gets the maximum number of attemps
/// </summary>
public int MaxAttempts
{
get { return 2; }
}
/// <summary>
/// Gets the state of the handshanking process
/// </summary>
public HandshakeState State
{
get { return _state; }
}
/// <summary>
/// Gets the name of the Prototol
/// </summary>
public string Name
{
get { return "Basic"; }
}
}
/// <summary>
/// Implements HTTP Digest Handshaking Protocol
/// </summary>
public class DigestAuthorization : ISecurityHandshake,IDisposable
{
#region Consts
private const string DEFAULT_USER = "admin";
private const string DEFAULT_PASSWORD = "admin";
private const string DEFAULT_URL = "/wsman";
private const string DEFAULT_METHOD = "POST";
#endregion
#region Private Fields
string _user;
SecureString _password;
string _url;
string _wwwAuth; //Digest Challenge (returned token)
string _nc;//nonce-count
string _ncnonce;//unique console token
string _a1;//MD5 handes A1
string _a2;//MD5 hashed A2
string _method;
HandshakeState _state; // the state of handshake
#endregion
/// <summary>
/// Creates a default digest object
/// </summary>
/// <param name="user">Digest user</param>
/// <param name="password">Digest password</param>
/// <remarks>Url defaults to /wsman</remarks>
public DigestAuthorization()
{
_user = DEFAULT_USER;
_password = DEFAULT_PASSWORD.ConvertToSecureString();
_url = DEFAULT_URL;
_method = DEFAULT_METHOD;
_state = HandshakeState.CredentialsAquired;
}
/// <summary>
/// Creates a Digest Authorization Handshake object
/// </summary>
/// <param name="user">Digest user</param>
/// <param name="password">Digest password</param>
/// <remarks>Url defaults to /wsman</remarks>
public DigestAuthorization(string user, SecureString password)
{
_user = user;
_password = password ?? new SecureString();
_url = DEFAULT_URL;
_method = DEFAULT_METHOD;
_state = HandshakeState.CredentialsAquired;
}
/// <summary>
/// Creates a Digest Authorization Handshake object
/// </summary>
/// <param name="user">Digest user</param>
/// <param name="password">Digest password</param>
/// <param name="url">Digest Url path</param>
/// <param name="method">Http Method</param>
public DigestAuthorization(string user, SecureString password, string url, string method)
{
_user = user;
_password = password ?? new SecureString();
_url = url;
_method = method;
_state = HandshakeState.CredentialsAquired;
}
/// <summary>
/// Gets a Digest Message Directive
/// </summary>
public string GetDirective(string message, string name)
{
int end = 0;
int pos = message.IndexOf(name);
string result = null;
if (pos > 0)
{
pos = pos + name.Length;
if (message.Substring(pos, 1).Equals("\""))
{
pos += 1;
end = message.IndexOf('\"', pos);
}
else
{
end = message.IndexOf(',', pos);
}
result = message.Substring(pos, end - pos);
}
return result;
}
public bool Stale
{
get
{
if (_wwwAuth != null)
{
const string STALE_TOKEN = "stale="; //optional
string staleValue = GetDirective(_wwwAuth, STALE_TOKEN);
if (staleValue != null && string.Compare(staleValue, "true") == 0)
{
return true;
}
}
return false;
}
}
public int MaxAttempts
{
get { return 2; }
}
/// <summary>
/// Gets or sets the HTTP uri to use in generating digest response
/// </summary>
public string Uri
{
get { return _url; }
set { _url = value; }
}
/// <summary>
/// Generates the initial security token(s)
/// </summary>
/// <returns>For Digest always returns null</returns>
public string GetInitialToken()
{
_wwwAuth = null;
_nc = "00000000";
_ncnonce = null;
_a1 = null;
_a2 = null;
GenerateCNonce();
_state = HandshakeState.ResponseNeeded;
return null;
}
/// <summary>
/// Sets the token returned from the server (WWW-Authenticate header)
/// </summary>
/// <param name="token">WWW-Authenticate header</param>
public void SetReturnToken(string token)
{
if (!string.IsNullOrEmpty(token) && _state == HandshakeState.ResponseNeeded)
_state = HandshakeState.ResponseNeeded;
_wwwAuth = token;
}
/// <summary>
/// Generates the next token to send
/// </summary>
/// <returns>Authorization token</returns>
public string GetNextToken()
{
if (string.IsNullOrEmpty(_wwwAuth))
{
_state = HandshakeState.Failed;
return null;
}
using (MD5 md5 = new MD5CryptoServiceProvider())
{
int nc = int.Parse(_nc);
nc++;
_nc = nc.ToString("D8");
// server directives
const string REALM_TOKEN = "realm=";
const string NONCE_TOKEN = "nonce=";
const string STALE_TOKEN = "stale="; //optional
const string QOP_TOKEN = "qop="; //quality of protection "auth"
const string OPAQUE_TOKEN = "opaque="; //optional
string realmValue = GetDirective(_wwwAuth, REALM_TOKEN);
string nonceValue = GetDirective(_wwwAuth, NONCE_TOKEN);
string staleValue = GetDirective(_wwwAuth, STALE_TOKEN);
string qopValue = GetDirective(_wwwAuth, QOP_TOKEN);
string opaqueValue = GetDirective(_wwwAuth, OPAQUE_TOKEN);
StringBuilder builder = null;
byte[] hash = null;
//calculate A1
if (_a1 == null)
{
builder = new StringBuilder();
builder.Append(_user);
builder.Append(':');
builder.Append(realmValue);
builder.Append(':');
builder.Append(_password.ConvertToString());
hash = md5.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
_a1 = BitConverter.ToString(hash);
_a1 = _a1.Replace("-", string.Empty).ToLower();
}
//calucate A2
if (_a2 == null)
{
builder = new StringBuilder();
builder.Append(_method);
builder.Append(':');
builder.Append(_url);
hash = md5.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
_a2 = BitConverter.ToString(hash);
_a2 = _a2.Replace("-", string.Empty).ToLower();
}
builder = new StringBuilder();
builder.Append(_a1);
builder.Append(':');
builder.Append(nonceValue);
builder.Append(':');
builder.Append(_nc);
builder.Append(':');
builder.Append(_ncnonce);
builder.Append(':');
builder.Append(qopValue);
builder.Append(':');
builder.Append(_a2);
hash = md5.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
string response = BitConverter.ToString(hash);
response = response.Replace("-", string.Empty).ToLower();
builder = new StringBuilder();
builder.Append("Digest ");
builder.Append("username=\"");
builder.Append(_user);
builder.Append("\",");
builder.Append(REALM_TOKEN);
builder.Append("\"");
builder.Append(realmValue);
builder.Append("\",");
builder.Append(NONCE_TOKEN);
builder.Append("\"");
builder.Append(nonceValue);
builder.Append("\",");
builder.Append("uri=\"");
builder.Append(_url);
builder.Append("\",");
builder.Append("algorithm=\"MD5\",");
builder.Append("cnonce=\"");
builder.Append(_ncnonce);
builder.Append("\",");
builder.Append("nc=");
builder.Append(_nc);
builder.Append(",");
builder.Append(QOP_TOKEN);
builder.Append("\"");
builder.Append(qopValue);
builder.Append("\",");
builder.Append("response=\"");
builder.Append(response);
builder.Append("\"");
if (opaqueValue != null)
{
builder.Append(",");
builder.Append(OPAQUE_TOKEN);
builder.Append("\"");
builder.Append(opaqueValue);
builder.Append("\"");
}
_state = HandshakeState.ResponseNeeded;
return builder.ToString();
}
}
/// <summary>
/// Gets the state of the handshaking process
/// </summary>
public HandshakeState State
{
get { return _state; }
}
/// <summary>
/// Gets the name of the Prototol
/// </summary>
public string Name
{
get { return "Digest"; }
}
/// <summary>
/// Generates a random Client Nonce using the secure RandomeNumber Generator
/// </summary>
private void GenerateCNonce()
{
if (_ncnonce == null)
{
byte[] rndData = new byte[16];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(rndData);
_ncnonce = BitConverter.ToString(rndData);
_ncnonce = _ncnonce.Replace("-", string.Empty).ToLower();
}
}
}
#region IDisposableMembers
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_password?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~DigestAuthorization()
{
Dispose(false);
}
#endregion
}// end DigestAuthorization
/// <summary>
/// Implements a Anonymous Authorization Handshake
/// </summary>
public class AnonymousAuthorization : ISecurityHandshake
{
List<string> _tokenList;
public AnonymousAuthorization()
{
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public string GetInitialToken()
{
return string.Empty;
}
public void SetReturnToken(string token)
{
if (_tokenList == null)
_tokenList = new List<string>();
_tokenList.Add(token);
}
public string GetNextToken()
{
return string.Empty;
}
public HandshakeState State
{
get { return HandshakeState.Complete; }
}
public int MaxAttempts
{
get { return 1; }
}
public string Name
{
get { return "Anonymous"; }
}
public string[] GetAuthorizations()
{
if (_tokenList == null)
return new string[0];
return _tokenList.ToArray();
}
}// end AnonymousAuthorziation
/// <summary>
/// Implements the Windnows Authorization (Negotiate) Handshake
/// </summary>
public class WindowsAuthorization : ISecurityHandshake, IDisposable
{
SchannelClient _schClient;
HandshakeState _state;
string _token;
public WindowsAuthorization(string user, string domain, SecureString password, string spn)
{
_state = HandshakeState.Uninitialized;
_schClient = new SchannelClient("Negotiate");
_schClient.Target = spn;
int status = _schClient.AcquireCredentials(user, domain, password);
if (status == 0)
_state = HandshakeState.CredentialsAquired;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
/// <summary>
/// Initializes the Handshake
/// </summary>
/// <returns>Initial token (if any)</returns>
public string GetInitialToken()
{
_state = HandshakeState.ResponseNeeded;
return _schClient.GetToken(_token);
}
public int MaxAttempts
{
get { return int.MaxValue; }
}
public void SetReturnToken(string token)
{
//if the previous token was empty and the new token is empty then Unauthorized
if (_state == HandshakeState.ResponseNeeded && string.Compare(token, "WWW-Authenticate: Negotiate", true) == 0)
_state = HandshakeState.Failed;
if (_state != HandshakeState.Failed && !string.IsNullOrEmpty(token))
_state = HandshakeState.ResponseNeeded;
else
_state = HandshakeState.Failed;
_token = token;
}
public string GetNextToken()
{
return _schClient.GetToken(_token);
}
/// <summary>
/// Gets the State of the HandShake
/// </summary>
public HandshakeState State
{
get { return _state; }
}
/// <summary>
/// Gets the name of the SecurityHandShake
/// </summary>
public string Name
{
get { return "Negotiate"; }
}
#region IDisposable Members
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_schClient?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}// end WindowsAuthorziation
/// <summary>
/// The Response returned from a server
/// </summary>
public class ServerResponse
{
MemoryStream _stream;
string _contentType;
int _contentLength;
DateTime _date;
string _wwwAuth;
string _proxyAuth;
string _realm;
string _server;
string _statusText;
int _statusCode;
bool _chunked;
bool _closed;
public ServerResponse(MemoryStream stream,
string contentType,
int contentLen,
DateTime date,
string wwwAuth,
string proxyAuth,
bool chunked,
bool closed,
string realm,
string server,
string statusText,
int statusCode)
{
_stream = stream;
_contentType = contentType;
_contentLength = contentLen;
_date = date;
_wwwAuth = wwwAuth;
_proxyAuth = proxyAuth;
_chunked = chunked;
_closed = closed;
_realm = realm;
_server = server;
_statusText = statusText;
_statusCode = statusCode;
}
public MemoryStream GetResponse()
{
return _stream;
}
public DateTime Date
{
get { return _date; }
}
public string Challange
{
get { return _wwwAuth; }
}
public string ProxyChallange
{
get { return _proxyAuth; }
}
public int StatusCode
{
get { return _statusCode; }
}
public string StatusText
{
get { return _statusText; }
}
public string ContentType
{
get { return _contentType; }
}
public string Server
{
get { return _server; }
}
public bool Chunked
{
get { return _chunked; }
}
public int ContentLength
{
get { return _contentLength; }
}
public bool Closed
{
get { return _closed; }
}
public System.Xml.XmlDocument GetXml()
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.Load(_stream);
_stream.Seek(0, SeekOrigin.Begin);
return doc;
}
public void Close()
{
_stream.Close();
}
}// End ServerResponse
/// <summary>
/// Manages Proxy Connections
/// </summary>
interface IMpsManager: IDisposable
{
/// <summary>
/// Gets the list of hosts that should be proxied
/// </summary>
/// <remarks>This is a configued list and not live from the mps</remarks>
System.Collections.Specialized.StringCollection Hosts { get; }
/// <summary>
/// Get or sets a value indicating whether the MPS manager is enabled
/// </summary>
bool Enabled { get; set; }
/// <summary>
/// Get value indicating when the proxy manager is active
/// </summary>
DateTime ValidUntil { get; set; }
/// <summary>
/// Get or sets the hostname and port of the http proxy
/// </summary>
string HttpProxy { get; set; }
/// <summary>
/// Get or sets the http proxy user (optional)
/// </summary>
string HttpUser { get; set; }
/// <summary>
/// Get or sets the http proxy password (optional)
/// </summary>
SecureString HttpPassword { get; set; }
/// <summary>
/// Get or sets the hostname and port of the redirection proxy
/// </summary>
string SocksProxy { get; set; }
/// <summary>
/// Get or sets the socks proxy password (optional)
/// </summary>
string SocksUser { get; set; }
/// <summary>
/// Get or sets the socks proxy password (optional)
/// </summary>
SecureString SocksPassword { get; set; }
/// <summary>
/// Checks to see if the host should be proxied
/// </summary>
/// <param name="host">the host name to check</param>
/// <returns>True if the host should be proxied</returns>
bool IsConnected(string host);
/// <summary>
/// Adds a host to the proxy list
/// </summary>
/// <param name="host">the host hame to add</param>
void AddHost(string host);
/// <summary>
/// Removes a host from the proxy list
/// </summary>
/// <param name="host">the host name to remove</param>
void RemoveHost(string host);
/// <summary>
/// Saves the settings so they can be used by other applications
/// </summary>
void Save();
/// <summary>
/// Reloads MPS Settings
/// </summary>
void ReLoad();
/// <summary>
/// Gets the list reported by the MPS
/// </summary>
/// <remarks>This is a live host list</remarks>
System.Collections.Specialized.StringCollection ReportedHosts { get; }
}
public class MpsManager : IMpsManager
{
public MpsManager()
{
}
public System.Collections.Specialized.StringCollection Hosts
{
get
{
if (Properties.Settings.Default.Hosts == null)
{
return new System.Collections.Specialized.StringCollection();
}
return Properties.Settings.Default.Hosts;
}
}
public bool Enabled
{
get
{
return Properties.Settings.Default.AutoProxyEnabled;
}
set
{
Properties.Settings.Default.AutoProxyEnabled = value;
}
}
public DateTime ValidUntil
{
get
{
return Properties.Settings.Default.ValidUntil;
}
set
{
Properties.Settings.Default.ValidUntil = value;
}
}
public string HttpProxy
{
get
{
return Properties.Settings.Default.HttpProxyUri;
}
set
{
Properties.Settings.Default.HttpProxyUri = value;
}
}
public string SocksProxy
{
get
{
return Properties.Settings.Default.SocksUri;
}
set
{
Properties.Settings.Default.SocksUri = value;
}
}
public string HttpUser
{
get
{
return Properties.Settings.Default.HttpProxyUser;
}
set
{
Properties.Settings.Default.HttpProxyUser = value;
}
}
public string SocksUser
{
get
{
return Properties.Settings.Default.SocksUser;
}
set
{
Properties.Settings.Default.SocksUser = value;
}
}
public SecureString HttpPassword
{
get
{
return Properties.Settings.Default.HttpProxyPassword;
}
set
{
Properties.Settings.Default.HttpProxyPassword = value;
}
}
public SecureString SocksPassword
{
get
{
return Properties.Settings.Default.SocksPassword;
}
set
{
Properties.Settings.Default.SocksPassword = value;
}
}
public bool IsConnected(string host)
{
if (Properties.Settings.Default.AutoProxyEnabled)
{
foreach (string entry in Properties.Settings.Default.Hosts)
{
if (string.Compare(entry, host, true) == 0)
{
return DateTime.Now.CompareTo(Properties.Settings.Default.ValidUntil) <= 0;
}
}
}
return false;
}
public void AddHost(string host)
{
bool bExists = false;
System.Collections.Specialized.StringCollection hosts = Properties.Settings.Default.Hosts;
if (hosts == null)
hosts = new System.Collections.Specialized.StringCollection();
if (DateTime.Now.CompareTo(Properties.Settings.Default.ValidUntil) > 0)
hosts.Clear();
foreach (string entry in hosts)
{
if (string.Compare(entry, host, true) == 0)
{
bExists = true;
break;
}
}
// the host was not already found in the list
if (!bExists)
hosts.Add(host);
DateTime setValue = DateTime.Now.AddHours(8);
if (setValue.CompareTo(Properties.Settings.Default.ValidUntil) > 0)
Properties.Settings.Default.ValidUntil = setValue;
Properties.Settings.Default.Hosts = hosts;
}
public void RemoveHost(string host)
{
if (Properties.Settings.Default.Hosts == null)
Properties.Settings.Default.Hosts = new System.Collections.Specialized.StringCollection();
foreach (string entry in Properties.Settings.Default.Hosts)
{
if (string.Compare(entry, host, true) == 0)
{
Properties.Settings.Default.Hosts.Remove(entry);
break;
}
}
}
public string DiscoveryAddress
{
get
{
return Properties.Settings.Default.DiscoveryUri;
}
set
{
Properties.Settings.Default.DiscoveryUri = value;
}
}
public void Save()
{
Properties.Settings.Default.Save();
}
public void ReLoad()
{
Properties.Settings.Default.Reload();
}
public System.Collections.Specialized.StringCollection ReportedHosts
{
get
{
throw new NotImplementedException();
}
}
#region IDisposableMembers
/// <summary>
/// implement IDisposable interface
/// </summary>
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
HttpPassword?.Dispose();
SocksPassword?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~MpsManager()
{
Dispose(false);
}
#endregion
}//end classs
/// <summary>
/// Defines connection options for an HTTP proxy
/// </summary>
public class HttpProxy : IDisposable
{
Uri _uri;
string _proxyUser;
SecureString _proxyPassword;
public HttpProxy()
{
}
public HttpProxy(string address)
{
if (address.ToLower().StartsWith("http://") == false)
address = "http://" + address;
_uri = new Uri(address);
_proxyUser = string.Empty;
_proxyPassword = new SecureString();
}
public HttpProxy(string address, string username, SecureString password)
{
if (address.ToLower().StartsWith("http://") == false)
address = "http://" + address;
_uri = new Uri(address);
_proxyUser = username;
_proxyPassword = password ?? new SecureString();
}
public ISecurityHandshake GetAuthorization(string method, string url, string challenge)
{
ISecurityHandshake result = null;
if (challenge.StartsWith("proxy-authenticate: digest"))
{
DigestAuthorization auth = new DigestAuthorization(_proxyUser, _proxyPassword.Copy(), url, method);
result = auth;
}
else if (challenge.StartsWith("proxy-authenticate: negotiate"))
{
//DigestAuthorization auth = new WindowsAuthorization(_proxyUser, _proxyPassword);
//result = auth;
}
return result;
}
public Uri Address
{
get { return _uri; }
}
public string Username
{
get { return _proxyUser; }
}
public SecureString Password
{
get { return _proxyPassword; }
}
#region IDisposableMembers
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_proxyPassword?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~HttpProxy()
{
Dispose(false);
}
#endregion
}
}//end namespace