Fix for the secure connection

This commit is contained in:
sta
2013-07-19 17:29:58 +09:00
parent 49dc8800d3
commit 3e6c589953
11 changed files with 308 additions and 243 deletions

View File

@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
// Change them to the values specific to your project.
[assembly: AssemblyTitle("websocket-sharp")]
[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client & server")]
[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client and server")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("websocket-sharp.dll")]

View File

@@ -45,6 +45,7 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
@@ -282,6 +283,12 @@ namespace WebSocketSharp {
: null;
}
internal static TcpListenerWebSocketContext GetWebSocketContext(
this TcpClient client, bool secure, X509Certificate cert)
{
return new TcpListenerWebSocketContext(client, secure, cert);
}
// <summary>
// Determines whether the specified object is <see langword="null"/>.
// </summary>
@@ -532,60 +539,6 @@ namespace WebSocketSharp {
#region Public Methods
/// <summary>
/// Accepts a WebSocket connection by the <see cref="TcpListener"/>.
/// </summary>
/// <returns>
/// A <see cref="TcpListenerWebSocketContext"/> that contains a WebSocket connection.
/// </returns>
/// <param name="listener">
/// A <see cref="TcpListener"/> that provides a TCP connection to accept a WebSocket connection.
/// </param>
/// <param name="secure">
/// A <see cref="bool"/> that indicates a secure connection or not. (<c>true</c> indicates a secure connection.)
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="listener"/> is <see langword="null"/>.
/// </exception>
public static TcpListenerWebSocketContext AcceptWebSocket(this TcpListener listener, bool secure)
{
if (listener == null)
throw new ArgumentNullException("listener");
var client = listener.AcceptTcpClient();
return new TcpListenerWebSocketContext(client, secure);
}
/// <summary>
/// Accepts a WebSocket connection asynchronously by the <see cref="TcpListener"/>.
/// </summary>
/// <param name="listener">
/// A <see cref="TcpListener"/> that provides a TCP connection to accept a WebSocket connection.
/// </param>
/// <param name="secure">
/// A <see cref="bool"/> that indicates a secure connection or not. (<c>true</c> indicates a secure connection.)
/// </param>
/// <param name="completed">
/// An Action&lt;TcpListenerWebSocketContext&gt; delegate that contains the method(s) that is called when an asynchronous operation completes.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="listener"/> is <see langword="null"/>.
/// </exception>
public static void AcceptWebSocketAsync(this TcpListener listener, bool secure, Action<TcpListenerWebSocketContext> completed)
{
if (listener == null)
throw new ArgumentNullException("listener");
AsyncCallback callback = (ar) =>
{
var client = listener.EndAcceptTcpClient(ar);
var context = new TcpListenerWebSocketContext(client, secure);
completed(context);
};
listener.BeginAcceptTcpClient(callback, null);
}
/// <summary>
/// Determines whether the specified <see cref="string"/> contains any of characters
/// in the specified array of <see cref="char"/>.

View File

@@ -30,12 +30,13 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets {
/// <summary>
/// Provides access to the WebSocket connection request objects received by the <see cref="TcpListener"/> class.
/// Provides access to the WebSocket connection request objects received by the <see cref="TcpListener"/>.
/// </summary>
/// <remarks>
/// </remarks>
@@ -44,22 +45,22 @@ namespace WebSocketSharp.Net.WebSockets {
#region Private Fields
private CookieCollection _cookies;
private TcpClient _tcpClient;
private bool _isSecure;
private TcpClient _client;
private RequestHandshake _request;
private bool _secure;
private WsStream _stream;
private WebSocket _websocket;
private WsStream _wsStream;
#endregion
#region Internal Constructors
internal TcpListenerWebSocketContext(TcpClient tcpClient, bool secure)
internal TcpListenerWebSocketContext(TcpClient client, bool secure, X509Certificate cert)
{
_tcpClient = tcpClient;
_isSecure = secure;
_wsStream = WsStream.CreateServerStream(tcpClient, secure);
_request = RequestHandshake.Parse(_wsStream.ReadHandshake());
_client = client;
_secure = secure;
_stream = WsStream.CreateServerStream(client, secure, cert);
_request = RequestHandshake.Parse(_stream.ReadHandshake());
_websocket = new WebSocket(this);
}
@@ -69,7 +70,7 @@ namespace WebSocketSharp.Net.WebSockets {
internal WsStream Stream {
get {
return _wsStream;
return _stream;
}
}
@@ -85,7 +86,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value>
public override CookieCollection CookieCollection {
get {
if (_cookies.IsNull())
if (_cookies == null)
_cookies = _request.Cookies;
return _cookies;
@@ -139,7 +140,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value>
public override bool IsSecureConnection {
get {
return _isSecure;
return _secure;
}
}
@@ -260,7 +261,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value>
public virtual System.Net.IPEndPoint ServerEndPoint {
get {
return (System.Net.IPEndPoint)_tcpClient.Client.LocalEndPoint;
return (System.Net.IPEndPoint)_client.Client.LocalEndPoint;
}
}
@@ -287,7 +288,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value>
public virtual System.Net.IPEndPoint UserEndPoint {
get {
return (System.Net.IPEndPoint)_tcpClient.Client.RemoteEndPoint;
return (System.Net.IPEndPoint)_client.Client.RemoteEndPoint;
}
}
@@ -309,8 +310,8 @@ namespace WebSocketSharp.Net.WebSockets {
internal void Close()
{
_wsStream.Close();
_tcpClient.Close();
_stream.Close();
_client.Close();
}
#endregion

View File

@@ -30,6 +30,7 @@ using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using WebSocketSharp.Net.WebSockets;
@@ -45,15 +46,16 @@ namespace WebSocketSharp.Server {
#region Private Fields
private IPAddress _address;
private bool _listening;
private Logger _logger;
private int _port;
private Thread _receiveRequestThread;
private bool _secure;
private bool _selfHost;
private TcpListener _tcpListener;
private Uri _uri;
private IPAddress _address;
private X509Certificate2 _cert;
private bool _listening;
private Logger _logger;
private int _port;
private Thread _receiveRequestThread;
private bool _secure;
private bool _selfHost;
private TcpListener _listener;
private Uri _uri;
#endregion
@@ -63,7 +65,7 @@ namespace WebSocketSharp.Server {
/// Initializes a new instance of the <see cref="WebSocketServerBase"/> class.
/// </summary>
/// <remarks>
/// This constructor initializes a new instance of this class as non self host.
/// This constructor initializes a new instance of this class as non self hosted server.
/// </remarks>
protected WebSocketServerBase()
: this(new Logger())
@@ -75,7 +77,7 @@ namespace WebSocketSharp.Server {
/// with the specified <paramref name="logger"/>.
/// </summary>
/// <remarks>
/// This constructor initializes a new instance of this class as non self host.
/// This constructor initializes a new instance of this class as non self hosted server.
/// </remarks>
/// <param name="logger">
/// A <see cref="Logger"/> that provides the logging functions.
@@ -104,7 +106,7 @@ namespace WebSocketSharp.Server {
if (url.IsNull())
throw new ArgumentNullException("url");
Uri uri;
Uri uri;
string msg;
if (!tryCreateUri(url, out uri, out msg))
throw new ArgumentException(msg, "url");
@@ -208,6 +210,25 @@ namespace WebSocketSharp.Server {
}
}
/// <summary>
/// Gets or sets the certificate used to authenticate the server on the secure connection.
/// </summary>
/// <value>
/// A <see cref="X509Certificate2"/> used to authenticate the server.
/// </value>
public X509Certificate2 Certificate {
get {
return _cert;
}
set {
if (_listening)
return;
_cert = value;
}
}
/// <summary>
/// Gets a value indicating whether the server has been started.
/// </summary>
@@ -303,7 +324,7 @@ namespace WebSocketSharp.Server {
_listening = false;
_logger = new Logger();
_selfHost = true;
_tcpListener = new TcpListener(_address, _port);
_listener = new TcpListener(_address, _port);
}
private void init(Uri uri)
@@ -320,13 +341,13 @@ namespace WebSocketSharp.Server {
init();
}
private void processRequestAsync(TcpListenerWebSocketContext context)
private void processRequestAsync(TcpClient client)
{
WaitCallback callback = (state) =>
WaitCallback callback = state =>
{
try
{
AcceptWebSocket(context);
AcceptWebSocket(client.GetWebSocketContext(_secure, _cert));
}
catch (Exception ex)
{
@@ -344,11 +365,11 @@ namespace WebSocketSharp.Server {
{
try
{
processRequestAsync(_tcpListener.AcceptWebSocket(_secure));
processRequestAsync(_listener.AcceptTcpClient());
}
catch (SocketException)
{
// TcpListener has been stopped.
_logger.Info("TcpListener has been stopped.");
break;
}
catch (Exception ex)
@@ -368,7 +389,7 @@ namespace WebSocketSharp.Server {
_receiveRequestThread.Start();
}
private bool tryCreateUri(string uriString, out Uri result, out string message)
private static bool tryCreateUri(string uriString, out Uri result, out string message)
{
if (!uriString.TryCreateWebSocketUri(out result, out message))
return false;
@@ -422,7 +443,16 @@ namespace WebSocketSharp.Server {
if (!_selfHost || _listening)
return;
_tcpListener.Start();
if (_secure && _cert == null)
{
var msg = "Secure connection requires a server certificate.";
_logger.Error(msg);
error(msg);
return;
}
_listener.Start();
startReceiveRequestThread();
_listening = true;
}
@@ -435,7 +465,7 @@ namespace WebSocketSharp.Server {
if (!_selfHost || !_listening)
return;
_tcpListener.Stop();
_listener.Stop();
_receiveRequestThread.Join(5 * 1000);
_listening = false;
}

View File

@@ -38,6 +38,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
@@ -66,6 +67,8 @@ namespace WebSocketSharp {
#region Private Fields
private string _base64key;
private RemoteCertificateValidationCallback
_certValidationCallback;
private bool _client;
private Action _closeContext;
private CookieCollection _cookies;
@@ -162,9 +165,8 @@ namespace WebSocketSharp {
_uri = uri;
_protocols = protocols.ToString(", ");
_client = true;
_secure = uri.Scheme == "wss"
? true
: false;
_secure = uri.Scheme == "wss" ? true : false;
_base64key = createBase64Key();
}
/// <summary>
@@ -226,6 +228,12 @@ namespace WebSocketSharp {
}
}
internal bool IsOpened {
get {
return _readyState == WsState.OPEN || _readyState == WsState.CLOSING;
}
}
#endregion
#region Public Properties
@@ -243,8 +251,14 @@ namespace WebSocketSharp {
}
set {
if (isOpened(true))
if (IsOpened)
{
var msg = "The WebSocket connection has already been established.";
_logger.Error(msg);
error(msg);
return;
}
_compression = value;
}
@@ -363,19 +377,25 @@ namespace WebSocketSharp {
}
set {
if (isOpened(true))
return;
if (value.IsNullOrEmpty())
string msg = null;
if (IsOpened)
{
msg = "The WebSocket connection has already been established.";
}
else if (value.IsNullOrEmpty())
{
_origin = String.Empty;
return;
}
var origin = new Uri(value);
if (!origin.IsAbsoluteUri || origin.Segments.Length > 1)
else
{
var origin = new Uri(value);
if (!origin.IsAbsoluteUri || origin.Segments.Length > 1)
msg = "The syntax of value of Origin must be '<scheme>://<host>[:<port>]'.";
}
if (msg != null)
{
var msg = "The syntax of value of Origin must be '<scheme>://<host>[:<port>]'.";
_logger.Error(msg);
error(msg);
@@ -410,6 +430,27 @@ namespace WebSocketSharp {
}
}
/// <summary>
/// Gets or sets the callback used to validate the certificate supplied by the server.
/// </summary>
/// <remarks>
/// If the value of this property is <see langword="null"/>, the validation does nothing
/// with the server certificate, always returns valid.
/// </remarks>
/// <value>
/// A <see cref="RemoteCertificateValidationCallback"/> delegate that references the method(s)
/// used to validate the server certificate. The default is <see langword="null"/>.
/// </value>
public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
get {
return _certValidationCallback;
}
set {
_certValidationCallback = value;
}
}
/// <summary>
/// Gets the WebSocket URL to connect.
/// </summary>
@@ -777,7 +818,7 @@ namespace WebSocketSharp {
// As client
private bool doHandshake()
{
init();
setWsStream();
return processResponseHandshake(sendRequestHandshake());
}
@@ -799,24 +840,13 @@ namespace WebSocketSharp {
return CompressionMethod.NONE;
}
// As client
private void init()
{
_base64key = createBase64Key();
var host = _uri.DnsSafeHost;
var port = _uri.Port;
_tcpClient = new TcpClient(host, port);
_wsStream = WsStream.CreateClientStream(_tcpClient, host, _secure);
}
// As server
private void init(WebSocketContext context)
{
_context = context;
_uri = context.Path.ToUri();
_secure = context.IsSecureConnection;
_client = false;
_uri = context.Path.ToUri();
_secure = context.IsSecureConnection;
_client = false;
}
private static bool isCompressionExtension(string value)
@@ -832,21 +862,6 @@ namespace WebSocketSharp {
: false;
}
private bool isOpened(bool errorIfOpened)
{
if (_readyState != WsState.OPEN && _readyState != WsState.CLOSING)
return false;
if (errorIfOpened)
{
var msg = "The WebSocket connection has been established already.";
_logger.Error(msg);
error(msg);
}
return true;
}
// As server
private bool isValidHostHeader()
{
@@ -1351,6 +1366,15 @@ namespace WebSocketSharp {
send(res);
}
// As client
private void setWsStream()
{
var host = _uri.DnsSafeHost;
var port = _uri.Port;
_tcpClient = new TcpClient(host, port);
_wsStream = WsStream.CreateClientStream(_tcpClient, _secure, host, _certValidationCallback);
}
private void startReceiving()
{
_exitReceiving = new AutoResetEvent(false);
@@ -1480,8 +1504,14 @@ namespace WebSocketSharp {
/// </summary>
public void Connect()
{
if (isOpened(true))
if (IsOpened)
{
var msg = "The WebSocket connection has already been established.";
_logger.Error(msg);
error(msg);
return;
}
try
{
@@ -1690,12 +1720,14 @@ namespace WebSocketSharp {
/// </param>
public void SetCookie(Cookie cookie)
{
if (isOpened(true))
return;
var msg = IsOpened
? "The WebSocket connection has already been established."
: cookie == null
? "'cookie' must not be null."
: null;
if (cookie == null)
if (msg != null)
{
var msg = "'cookie' must not be null.";
_logger.Error(msg);
error(msg);
@@ -1723,24 +1755,28 @@ namespace WebSocketSharp {
/// </param>
public void SetCredentials(string userName, string password, bool preAuth)
{
if (isOpened(true))
return;
if (userName == null)
string msg = null;
if (IsOpened)
{
msg = "The WebSocket connection has already been established.";
}
else if (userName == null)
{
_credentials = null;
_preAuth = false;
return;
}
else
{
msg = userName.Length > 0 && (userName.Contains(':') || !userName.IsText())
? "'userName' contains an invalid character."
: !password.IsNullOrEmpty() && !password.IsText()
? "'password' contains an invalid character."
: null;
}
var msg = userName.Length > 0 && (userName.Contains(':') || !userName.IsText())
? "'userName' contains an invalid character."
: !password.IsNullOrEmpty() && !password.IsText()
? "'password' contains an invalid character."
: String.Empty;
if (msg.Length > 0)
if (msg != null)
{
_logger.Error(msg);
error(msg);

View File

@@ -28,7 +28,6 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
@@ -119,18 +118,20 @@ namespace WebSocketSharp {
#region Internal Methods
internal static WsStream CreateClientStream(TcpClient tcpClient, string host, bool secure)
internal static WsStream CreateClientStream(
TcpClient client,
bool secure,
string host,
System.Net.Security.RemoteCertificateValidationCallback validationCallback
)
{
var netStream = tcpClient.GetStream();
var netStream = client.GetStream();
if (secure)
{
System.Net.Security.RemoteCertificateValidationCallback callback = (sender, certificate, chain, sslPolicyErrors) =>
{
// FIXME: Always returns true
return true;
};
if (validationCallback == null)
validationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
var sslStream = new SslStream(netStream, false, callback);
var sslStream = new SslStream(netStream, false, validationCallback);
sslStream.AuthenticateAsClient(host);
return new WsStream(sslStream);
@@ -139,14 +140,13 @@ namespace WebSocketSharp {
return new WsStream(netStream);
}
internal static WsStream CreateServerStream(TcpClient tcpClient, bool secure)
internal static WsStream CreateServerStream(TcpClient client, bool secure, X509Certificate cert)
{
var netStream = tcpClient.GetStream();
var netStream = client.GetStream();
if (secure)
{
var sslStream = new SslStream(netStream, false);
var certPath = ConfigurationManager.AppSettings["ServerCertPath"];
sslStream.AuthenticateAsServer(new X509Certificate2(certPath));
sslStream.AuthenticateAsServer(cert);
return new WsStream(sslStream);
}