2016-08-23 13:20:38 +00:00
|
|
|
|
//
|
|
|
|
|
// MobileAuthenticatedStream.cs
|
|
|
|
|
//
|
|
|
|
|
// Author:
|
|
|
|
|
// Martin Baulig <martin.baulig@xamarin.com>
|
|
|
|
|
//
|
|
|
|
|
// Copyright (c) 2015 Xamarin, Inc.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#if SECURITY_DEP
|
|
|
|
|
#if MONO_SECURITY_ALIAS
|
|
|
|
|
extern alias MonoSecurity;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if MONO_SECURITY_ALIAS
|
|
|
|
|
using MSI = MonoSecurity::Mono.Security.Interface;
|
|
|
|
|
#else
|
|
|
|
|
using MSI = Mono.Security.Interface;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Security;
|
|
|
|
|
using System.Globalization;
|
2017-08-21 15:34:15 +00:00
|
|
|
|
using System.Security.Authentication;
|
2017-01-19 14:22:10 +00:00
|
|
|
|
using System.Runtime.ExceptionServices;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
|
|
|
|
|
|
using SD = System.Diagnostics;
|
|
|
|
|
using SSA = System.Security.Authentication;
|
|
|
|
|
using SslProtocols = System.Security.Authentication.SslProtocols;
|
|
|
|
|
|
|
|
|
|
namespace Mono.Net.Security
|
|
|
|
|
{
|
|
|
|
|
abstract class MobileAuthenticatedStream : AuthenticatedStream, MSI.IMonoSslStream
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
/*
|
|
|
|
|
* This is intentionally called `xobileTlsContext'. It is a "dangerous" object
|
|
|
|
|
* that must not be touched outside the `ioLock' and we need to be very careful
|
|
|
|
|
* where we access it.
|
|
|
|
|
*/
|
2016-08-23 13:20:38 +00:00
|
|
|
|
MobileTlsContext xobileTlsContext;
|
2017-08-21 15:34:15 +00:00
|
|
|
|
ExceptionDispatchInfo lastException;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
AsyncProtocolRequest asyncHandshakeRequest;
|
|
|
|
|
AsyncProtocolRequest asyncReadRequest;
|
|
|
|
|
AsyncProtocolRequest asyncWriteRequest;
|
|
|
|
|
BufferOffsetSize2 readBuffer;
|
|
|
|
|
BufferOffsetSize2 writeBuffer;
|
|
|
|
|
|
|
|
|
|
object ioLock = new object ();
|
|
|
|
|
int closeRequested;
|
2017-08-21 15:34:15 +00:00
|
|
|
|
bool shutdown;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
static int uniqueNameInteger = 123;
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public MobileAuthenticatedStream (Stream innerStream, bool leaveInnerStreamOpen, SslStream owner,
|
|
|
|
|
MSI.MonoTlsSettings settings, MSI.MonoTlsProvider provider)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
: base (innerStream, leaveInnerStreamOpen)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
SslStream = owner;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
Settings = settings;
|
|
|
|
|
Provider = provider;
|
|
|
|
|
|
|
|
|
|
readBuffer = new BufferOffsetSize2 (16834);
|
|
|
|
|
writeBuffer = new BufferOffsetSize2 (16384);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public SslStream SslStream {
|
2016-08-23 13:20:38 +00:00
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public MSI.MonoTlsSettings Settings {
|
2016-08-23 13:20:38 +00:00
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public MSI.MonoTlsProvider Provider {
|
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal bool HasContext {
|
|
|
|
|
get { return xobileTlsContext != null; }
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal void CheckThrow (bool authSuccessCheck, bool shutdownCheck = false)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
|
|
|
|
if (lastException != null)
|
2017-08-21 15:34:15 +00:00
|
|
|
|
lastException.Throw ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
if (authSuccessCheck && !IsAuthenticated)
|
2017-08-21 15:34:15 +00:00
|
|
|
|
throw new InvalidOperationException (SR.net_auth_noauth);
|
|
|
|
|
if (shutdownCheck && shutdown)
|
|
|
|
|
throw new InvalidOperationException (SR.net_ssl_io_already_shutdown);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal static Exception GetSSPIException (Exception e)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
if (e is OperationCanceledException || e is IOException || e is ObjectDisposedException || e is AuthenticationException)
|
|
|
|
|
return e;
|
|
|
|
|
return new AuthenticationException (SR.net_auth_SSPI, e);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal static Exception GetIOException (Exception e, string message)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
if (e is OperationCanceledException || e is IOException || e is ObjectDisposedException || e is AuthenticationException)
|
|
|
|
|
return e;
|
|
|
|
|
return new IOException (message, e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal ExceptionDispatchInfo SetException (Exception e)
|
|
|
|
|
{
|
|
|
|
|
var info = ExceptionDispatchInfo.Capture (e);
|
|
|
|
|
var old = Interlocked.CompareExchange (ref lastException, info, null);
|
|
|
|
|
return old ?? info;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SslProtocols DefaultProtocols {
|
|
|
|
|
get { return SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; }
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
enum OperationType {
|
|
|
|
|
Read,
|
|
|
|
|
Write,
|
|
|
|
|
Shutdown
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-23 13:20:38 +00:00
|
|
|
|
public void AuthenticateAsClient (string targetHost)
|
|
|
|
|
{
|
|
|
|
|
AuthenticateAsClient (targetHost, new X509CertificateCollection (), DefaultProtocols, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void AuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var task = ProcessAuthentication (true, false, targetHost, enabledSslProtocols, null, clientCertificates, false);
|
|
|
|
|
task.Wait ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IAsyncResult BeginAuthenticateAsClient (string targetHost, AsyncCallback asyncCallback, object asyncState)
|
|
|
|
|
{
|
|
|
|
|
return BeginAuthenticateAsClient (targetHost, new X509CertificateCollection (), DefaultProtocols, false, asyncCallback, asyncState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IAsyncResult BeginAuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var task = ProcessAuthentication (false, false, targetHost, enabledSslProtocols, null, clientCertificates, false);
|
|
|
|
|
return TaskToApm.Begin (task, asyncCallback, asyncState);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void EndAuthenticateAsClient (IAsyncResult asyncResult)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
TaskToApm.End (asyncResult);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void AuthenticateAsServer (X509Certificate serverCertificate)
|
|
|
|
|
{
|
|
|
|
|
AuthenticateAsServer (serverCertificate, false, DefaultProtocols, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void AuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var task = ProcessAuthentication (true, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired);
|
|
|
|
|
task.Wait ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, AsyncCallback asyncCallback, object asyncState)
|
|
|
|
|
{
|
|
|
|
|
return BeginAuthenticateAsServer (serverCertificate, false, DefaultProtocols, false, asyncCallback, asyncState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var task = ProcessAuthentication (false, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired);
|
|
|
|
|
return TaskToApm.Begin (task, asyncCallback, asyncState);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void EndAuthenticateAsServer (IAsyncResult asyncResult)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
TaskToApm.End (asyncResult);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task AuthenticateAsClientAsync (string targetHost)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
return ProcessAuthentication (false, false, targetHost, DefaultProtocols, null, null, false);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task AuthenticateAsClientAsync (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
return ProcessAuthentication (false, false, targetHost, enabledSslProtocols, null, clientCertificates, false);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task AuthenticateAsServerAsync (X509Certificate serverCertificate)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
return AuthenticateAsServerAsync (serverCertificate, false, DefaultProtocols, false);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task AuthenticateAsServerAsync (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
return ProcessAuthentication (false, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task ShutdownAsync ()
|
|
|
|
|
{
|
|
|
|
|
Debug ("ShutdownAsync");
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* SSLClose() is a little bit tricky as it might attempt to send a close_notify alert
|
|
|
|
|
* and thus call our write callback.
|
|
|
|
|
*
|
|
|
|
|
* It is also not thread-safe with SSLRead() or SSLWrite(), so we need to take the I/O lock here.
|
|
|
|
|
*/
|
|
|
|
|
var asyncRequest = new AsyncShutdownRequest (this);
|
|
|
|
|
var task = StartOperation (OperationType.Shutdown, asyncRequest, CancellationToken.None);
|
|
|
|
|
return task;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AuthenticatedStream AuthenticatedStream {
|
|
|
|
|
get { return this; }
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
async Task ProcessAuthentication (
|
|
|
|
|
bool runSynchronously, bool serverMode, string targetHost, SslProtocols enabledProtocols,
|
|
|
|
|
X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool clientCertRequired)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
if (serverMode) {
|
|
|
|
|
if (serverCertificate == null)
|
|
|
|
|
throw new ArgumentException (nameof (serverCertificate));
|
|
|
|
|
} else {
|
|
|
|
|
if (targetHost == null)
|
|
|
|
|
throw new ArgumentException (nameof (targetHost));
|
|
|
|
|
if (targetHost.Length == 0)
|
|
|
|
|
targetHost = "?" + Interlocked.Increment (ref uniqueNameInteger).ToString (NumberFormatInfo.InvariantInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lastException != null)
|
|
|
|
|
lastException.Throw ();
|
|
|
|
|
|
|
|
|
|
var asyncRequest = new AsyncHandshakeRequest (this, runSynchronously);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
if (Interlocked.CompareExchange (ref asyncHandshakeRequest, asyncRequest, null) != null)
|
|
|
|
|
throw new InvalidOperationException ("Invalid nested call.");
|
2017-08-21 15:34:15 +00:00
|
|
|
|
// Make sure no other async requests can be started during the handshake.
|
|
|
|
|
if (Interlocked.CompareExchange (ref asyncReadRequest, asyncRequest, null) != null)
|
|
|
|
|
throw new InvalidOperationException ("Invalid nested call.");
|
|
|
|
|
if (Interlocked.CompareExchange (ref asyncWriteRequest, asyncRequest, null) != null)
|
|
|
|
|
throw new InvalidOperationException ("Invalid nested call.");
|
|
|
|
|
|
|
|
|
|
AsyncProtocolResult result;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
try {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
lock (ioLock) {
|
|
|
|
|
if (xobileTlsContext != null)
|
|
|
|
|
throw new InvalidOperationException ();
|
|
|
|
|
readBuffer.Reset ();
|
|
|
|
|
writeBuffer.Reset ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
xobileTlsContext = CreateContext (
|
|
|
|
|
serverMode, targetHost, enabledProtocols, serverCertificate,
|
|
|
|
|
clientCertificates, clientCertRequired);
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
try {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
result = await asyncRequest.StartOperation (CancellationToken.None).ConfigureAwait (false);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
} catch (Exception ex) {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
result = new AsyncProtocolResult (SetException (GetSSPIException (ex)));
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
} finally {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
lock (ioLock) {
|
2016-08-23 13:20:38 +00:00
|
|
|
|
readBuffer.Reset ();
|
|
|
|
|
writeBuffer.Reset ();
|
2017-08-21 15:34:15 +00:00
|
|
|
|
asyncWriteRequest = null;
|
|
|
|
|
asyncReadRequest = null;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
asyncHandshakeRequest = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
if (result.Error != null)
|
|
|
|
|
result.Error.Throw ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected abstract MobileTlsContext CreateContext (
|
2017-08-21 15:34:15 +00:00
|
|
|
|
bool serverMode, string targetHost, SSA.SslProtocols enabledProtocols,
|
|
|
|
|
X509Certificate serverCertificate, X509CertificateCollection clientCertificates,
|
|
|
|
|
bool askForClientCert);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
public override IAsyncResult BeginRead (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var asyncRequest = new AsyncReadRequest (this, false, buffer, offset, count);
|
|
|
|
|
var task = StartOperation (OperationType.Read, asyncRequest, CancellationToken.None);
|
|
|
|
|
return TaskToApm.Begin (task, asyncCallback, asyncState);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int EndRead (IAsyncResult asyncResult)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
return TaskToApm.End<int> (asyncResult);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IAsyncResult BeginWrite (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var asyncRequest = new AsyncWriteRequest (this, false, buffer, offset, count);
|
|
|
|
|
var task = StartOperation (OperationType.Write, asyncRequest, CancellationToken.None);
|
|
|
|
|
return TaskToApm.Begin (task, asyncCallback, asyncState);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void EndWrite (IAsyncResult asyncResult)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
TaskToApm.End (asyncResult);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int Read (byte[] buffer, int offset, int count)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var asyncRequest = new AsyncReadRequest (this, true, buffer, offset, count);
|
|
|
|
|
var task = StartOperation (OperationType.Read, asyncRequest, CancellationToken.None);
|
|
|
|
|
return task.Result;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write (byte[] buffer)
|
|
|
|
|
{
|
|
|
|
|
Write (buffer, 0, buffer.Length);
|
|
|
|
|
}
|
2017-08-21 15:34:15 +00:00
|
|
|
|
|
2016-08-23 13:20:38 +00:00
|
|
|
|
public override void Write (byte[] buffer, int offset, int count)
|
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var asyncRequest = new AsyncWriteRequest (this, true, buffer, offset, count);
|
|
|
|
|
var task = StartOperation (OperationType.Write, asyncRequest, CancellationToken.None);
|
|
|
|
|
task.Wait ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public override Task<int> ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var asyncRequest = new AsyncReadRequest (this, false, buffer, offset, count);
|
|
|
|
|
return StartOperation (OperationType.Read, asyncRequest, cancellationToken);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var asyncRequest = new AsyncWriteRequest (this, false, buffer, offset, count);
|
|
|
|
|
return StartOperation (OperationType.Write, asyncRequest, cancellationToken);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
async Task<int> StartOperation (OperationType type, AsyncProtocolRequest asyncRequest, CancellationToken cancellationToken)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
CheckThrow (true, type != OperationType.Read);
|
|
|
|
|
Debug ("StartOperationAsync: {0} {1}", asyncRequest, type);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
if (type == OperationType.Read) {
|
|
|
|
|
if (Interlocked.CompareExchange (ref asyncReadRequest, asyncRequest, null) != null)
|
|
|
|
|
throw new InvalidOperationException ("Invalid nested call.");
|
|
|
|
|
} else {
|
|
|
|
|
if (Interlocked.CompareExchange (ref asyncWriteRequest, asyncRequest, null) != null)
|
|
|
|
|
throw new InvalidOperationException ("Invalid nested call.");
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
AsyncProtocolResult result;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
try {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
lock (ioLock) {
|
|
|
|
|
if (type == OperationType.Read)
|
|
|
|
|
readBuffer.Reset ();
|
|
|
|
|
else
|
|
|
|
|
writeBuffer.Reset ();
|
|
|
|
|
}
|
|
|
|
|
result = await asyncRequest.StartOperation (cancellationToken).ConfigureAwait (false);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
} catch (Exception e) {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var info = SetException (GetIOException (e, asyncRequest.Name + " failed"));
|
|
|
|
|
result = new AsyncProtocolResult (info);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
} finally {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
lock (ioLock) {
|
|
|
|
|
if (type == OperationType.Read) {
|
|
|
|
|
readBuffer.Reset ();
|
|
|
|
|
asyncReadRequest = null;
|
|
|
|
|
} else {
|
|
|
|
|
writeBuffer.Reset ();
|
|
|
|
|
asyncWriteRequest = null;
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-21 15:34:15 +00:00
|
|
|
|
|
|
|
|
|
if (result.Error != null)
|
|
|
|
|
result.Error.Throw ();
|
|
|
|
|
return result.UserResult;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int nextId;
|
|
|
|
|
internal readonly int ID = ++nextId;
|
|
|
|
|
|
2017-10-19 20:04:20 +00:00
|
|
|
|
[SD.Conditional ("MONO_TLS_DEBUG")]
|
2016-08-23 13:20:38 +00:00
|
|
|
|
protected internal void Debug (string message, params object[] args)
|
|
|
|
|
{
|
2017-10-19 20:04:20 +00:00
|
|
|
|
MonoTlsProviderFactory.Debug ("MobileAuthenticatedStream({0}): {1}", ID, string.Format (message, args));
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#region Called back from native code via SslConnection
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Called from within SSLRead() and SSLHandshake(). We only access tha managed byte[] here.
|
|
|
|
|
*/
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal int InternalRead (byte[] buffer, int offset, int size, out bool outWantMore)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
|
|
|
|
try {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
Debug ("InternalRead: {0} {1} {2} {3} {4}", offset, size,
|
|
|
|
|
asyncHandshakeRequest != null ? "handshake" : "",
|
|
|
|
|
asyncReadRequest != null ? "async" : "",
|
|
|
|
|
readBuffer != null ? readBuffer.ToString () : "");
|
2016-08-23 13:20:38 +00:00
|
|
|
|
var asyncRequest = asyncHandshakeRequest ?? asyncReadRequest;
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var (ret, wantMore) = InternalRead (asyncRequest, readBuffer, buffer, offset, size);
|
|
|
|
|
outWantMore = wantMore;
|
|
|
|
|
return ret;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
Debug ("InternalRead failed: {0}", ex);
|
2017-08-21 15:34:15 +00:00
|
|
|
|
SetException (GetIOException (ex, "InternalRead() failed"));
|
|
|
|
|
outWantMore = false;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
(int, bool) InternalRead (AsyncProtocolRequest asyncRequest, BufferOffsetSize internalBuffer, byte[] buffer, int offset, int size)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
|
|
|
|
if (asyncRequest == null)
|
|
|
|
|
throw new InvalidOperationException ();
|
|
|
|
|
|
|
|
|
|
Debug ("InternalRead: {0} {1} {2}", internalBuffer, offset, size);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* One of Apple's native functions wants to read 'size' bytes of data.
|
|
|
|
|
*
|
|
|
|
|
* First, we check whether we already have enough in the internal buffer.
|
|
|
|
|
*
|
|
|
|
|
* If the internal buffer is empty (it will be the first time we're called), we save
|
|
|
|
|
* the amount of bytes that were requested and return 'SslStatus.WouldBlock' to our
|
|
|
|
|
* native caller. This native function will then return this code to managed code,
|
|
|
|
|
* where we read the requested amount of data into the internal buffer, then call the
|
|
|
|
|
* native function again.
|
|
|
|
|
*/
|
|
|
|
|
if (internalBuffer.Size == 0 && !internalBuffer.Complete) {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
Debug ("InternalRead #1: {0} {1} {2}", internalBuffer.Offset, internalBuffer.TotalBytes, size);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
internalBuffer.Offset = internalBuffer.Size = 0;
|
|
|
|
|
asyncRequest.RequestRead (size);
|
2017-08-21 15:34:15 +00:00
|
|
|
|
return (0, true);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The second time we're called, the native buffer will contain the exact amount of data that the
|
|
|
|
|
* previous call requested from us, so we should be able to return it all here. However, just in
|
|
|
|
|
* case that Apple's native function changed its mind, we can also return less.
|
|
|
|
|
*
|
|
|
|
|
* In either case, if we have any data buffered, then we return as much of it as possible - if the
|
|
|
|
|
* native code isn't satisfied, then it will call us again to request more.
|
|
|
|
|
*/
|
|
|
|
|
var len = System.Math.Min (internalBuffer.Size, size);
|
|
|
|
|
Buffer.BlockCopy (internalBuffer.Buffer, internalBuffer.Offset, buffer, offset, len);
|
|
|
|
|
internalBuffer.Offset += len;
|
|
|
|
|
internalBuffer.Size -= len;
|
2017-08-21 15:34:15 +00:00
|
|
|
|
return (len, !internalBuffer.Complete && len < size);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We may get called from SSLWrite(), SSLHandshake() or SSLClose().
|
|
|
|
|
*/
|
|
|
|
|
internal bool InternalWrite (byte[] buffer, int offset, int size)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
Debug ("InternalWrite: {0} {1}", offset, size);
|
|
|
|
|
var asyncRequest = asyncHandshakeRequest ?? asyncWriteRequest;
|
|
|
|
|
return InternalWrite (asyncRequest, writeBuffer, buffer, offset, size);
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
Debug ("InternalWrite failed: {0}", ex);
|
2017-08-21 15:34:15 +00:00
|
|
|
|
SetException (GetIOException (ex, "InternalWrite() failed"));
|
2016-08-23 13:20:38 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool InternalWrite (AsyncProtocolRequest asyncRequest, BufferOffsetSize2 internalBuffer, byte[] buffer, int offset, int size)
|
|
|
|
|
{
|
|
|
|
|
Debug ("InternalWrite: {0} {1} {2} {3}", asyncRequest != null, internalBuffer, offset, size);
|
|
|
|
|
|
|
|
|
|
if (asyncRequest == null) {
|
|
|
|
|
/*
|
|
|
|
|
* The only situation where 'asyncRequest' could possibly be 'null' is when we're called
|
|
|
|
|
* from within SSLClose() - which might attempt to send the close_notity notification.
|
|
|
|
|
* Since this notification message is very small, it should definitely fit into our internal
|
|
|
|
|
* buffer, so we just save it in there and after SSLClose() returns, the final call to
|
|
|
|
|
* InternalFlush() - just before closing the underlying stream - will send it out.
|
|
|
|
|
*/
|
|
|
|
|
if (lastException != null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (Interlocked.Exchange (ref closeRequested, 1) == 0)
|
|
|
|
|
internalBuffer.Reset ();
|
|
|
|
|
else if (internalBuffer.Remaining == 0)
|
|
|
|
|
throw new InvalidOperationException ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Normal write - can be either SSLWrite() or SSLHandshake().
|
|
|
|
|
*
|
|
|
|
|
* It is important that we always accept all the data and queue it.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
internalBuffer.AppendData (buffer, offset, size);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Calling 'asyncRequest.RequestWrite()' here ensures that ProcessWrite() is called next
|
|
|
|
|
* time we regain control from native code.
|
|
|
|
|
*
|
|
|
|
|
* During the handshake, the native code won't actually realize (unless if attempts to send
|
|
|
|
|
* so much that the write buffer gets full) that we only buffered the data.
|
|
|
|
|
*
|
|
|
|
|
* However, it doesn't matter because it will either return with a completed handshake
|
|
|
|
|
* (and doesn't care whether the remote actually received the data) or it will expect more
|
|
|
|
|
* data from the remote and request a read. In either case, we regain control in managed
|
|
|
|
|
* code and can flush out the data.
|
|
|
|
|
*
|
|
|
|
|
* Note that a calling RequestWrite() followed by RequestRead() will first flush the write
|
|
|
|
|
* queue once we return to managed code - before attempting to read anything.
|
|
|
|
|
*/
|
|
|
|
|
if (asyncRequest != null)
|
|
|
|
|
asyncRequest.RequestWrite ();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#endregion
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#region Inner Stream
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Read / write data from the inner stream; we're only called from managed code and only manipulate
|
|
|
|
|
* the internal buffers.
|
|
|
|
|
*/
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal async Task<int> InnerRead (bool sync, int requestedSize, CancellationToken cancellationToken)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
cancellationToken.ThrowIfCancellationRequested ();
|
2017-10-19 20:04:20 +00:00
|
|
|
|
Debug ("InnerRead: {0} {1} {2} {3} {4}", sync, readBuffer.Offset, readBuffer.Size, readBuffer.Remaining, requestedSize);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
var len = System.Math.Min (readBuffer.Remaining, requestedSize);
|
|
|
|
|
if (len == 0)
|
|
|
|
|
throw new InvalidOperationException ();
|
2017-08-21 15:34:15 +00:00
|
|
|
|
|
|
|
|
|
Task<int> task;
|
|
|
|
|
if (sync)
|
|
|
|
|
task = Task.Run (() => InnerStream.Read (readBuffer.Buffer, readBuffer.EndOffset, len));
|
|
|
|
|
else
|
|
|
|
|
task = InnerStream.ReadAsync (readBuffer.Buffer, readBuffer.EndOffset, len, cancellationToken);
|
|
|
|
|
|
|
|
|
|
var ret = await task.ConfigureAwait (false);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
Debug ("InnerRead done: {0} {1} - {2}", readBuffer.Remaining, len, ret);
|
|
|
|
|
|
|
|
|
|
if (ret >= 0) {
|
|
|
|
|
readBuffer.Size += ret;
|
|
|
|
|
readBuffer.TotalBytes += ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ret == 0) {
|
|
|
|
|
readBuffer.Complete = true;
|
|
|
|
|
Debug ("InnerRead - end of stream!");
|
|
|
|
|
/*
|
|
|
|
|
* Try to distinguish between a graceful close - first Read() returned 0 - and
|
|
|
|
|
* the remote prematurely closing the connection without sending us all data.
|
|
|
|
|
*/
|
|
|
|
|
if (readBuffer.TotalBytes > 0)
|
|
|
|
|
ret = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug ("InnerRead done: {0} - {1} {2}", readBuffer, len, ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal async Task InnerWrite (bool sync, CancellationToken cancellationToken)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
cancellationToken.ThrowIfCancellationRequested ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
Debug ("InnerWrite: {0} {1}", writeBuffer.Offset, writeBuffer.Size);
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
if (writeBuffer.Size == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Task task;
|
|
|
|
|
if (sync)
|
|
|
|
|
task = Task.Run (() => InnerStream.Write (writeBuffer.Buffer, writeBuffer.Offset, writeBuffer.Size));
|
|
|
|
|
else
|
|
|
|
|
task = InnerStream.WriteAsync (writeBuffer.Buffer, writeBuffer.Offset, writeBuffer.Size);
|
|
|
|
|
|
|
|
|
|
await task.ConfigureAwait (false);
|
|
|
|
|
|
|
|
|
|
writeBuffer.TotalBytes += writeBuffer.Size;
|
|
|
|
|
writeBuffer.Offset = writeBuffer.Size = 0;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#endregion
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#region Main async I/O loop
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal AsyncOperationStatus ProcessHandshake (AsyncOperationStatus status)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
|
|
|
|
Debug ("ProcessHandshake: {0}", status);
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
lock (ioLock) {
|
|
|
|
|
/*
|
|
|
|
|
* The first time we're called (AsyncOperationStatus.Initialize), we need to setup the SslContext and
|
|
|
|
|
* start the handshake.
|
|
|
|
|
*/
|
|
|
|
|
if (status == AsyncOperationStatus.Initialize) {
|
|
|
|
|
xobileTlsContext.StartHandshake ();
|
|
|
|
|
return AsyncOperationStatus.Continue;
|
|
|
|
|
} else if (status == AsyncOperationStatus.ReadDone) {
|
|
|
|
|
throw new IOException (SR.net_auth_eof);
|
|
|
|
|
} else if (status != AsyncOperationStatus.Continue) {
|
|
|
|
|
throw new InvalidOperationException ();
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
/*
|
2017-08-21 15:34:15 +00:00
|
|
|
|
* SSLHandshake() will return repeatedly with 'SslStatus.WouldBlock', we then need
|
|
|
|
|
* to take care of I/O and call it again.
|
|
|
|
|
*/
|
2017-10-19 20:04:20 +00:00
|
|
|
|
var newStatus = AsyncOperationStatus.Continue;
|
2017-08-21 15:34:15 +00:00
|
|
|
|
if (xobileTlsContext.ProcessHandshake ()) {
|
|
|
|
|
xobileTlsContext.FinishHandshake ();
|
2017-10-19 20:04:20 +00:00
|
|
|
|
newStatus = AsyncOperationStatus.Complete;
|
2017-08-21 15:34:15 +00:00
|
|
|
|
}
|
2017-10-19 20:04:20 +00:00
|
|
|
|
|
|
|
|
|
if (lastException != null)
|
|
|
|
|
lastException.Throw ();
|
|
|
|
|
|
|
|
|
|
return newStatus;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal (int, bool) ProcessRead (BufferOffsetSize userBuffer)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
|
|
|
|
lock (ioLock) {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
// This operates on the internal buffer and will never block.
|
2017-10-19 20:04:20 +00:00
|
|
|
|
var ret = xobileTlsContext.Read (userBuffer.Buffer, userBuffer.Offset, userBuffer.Size);
|
|
|
|
|
if (lastException != null)
|
|
|
|
|
lastException.Throw ();
|
|
|
|
|
return ret;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal (int, bool) ProcessWrite (BufferOffsetSize userBuffer)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
|
|
|
|
lock (ioLock) {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
// This operates on the internal buffer and will never block.
|
2017-10-19 20:04:20 +00:00
|
|
|
|
var ret = xobileTlsContext.Write (userBuffer.Buffer, userBuffer.Offset, userBuffer.Size);
|
|
|
|
|
if (lastException != null)
|
|
|
|
|
lastException.Throw ();
|
|
|
|
|
return ret;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
internal AsyncOperationStatus ProcessShutdown (AsyncOperationStatus status)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2017-08-21 15:34:15 +00:00
|
|
|
|
Debug ("ProcessShutdown: {0}", status);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
lock (ioLock) {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
xobileTlsContext.Shutdown ();
|
|
|
|
|
shutdown = true;
|
|
|
|
|
return AsyncOperationStatus.Complete;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#endregion
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
|
|
|
|
public override bool IsServer {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
get {
|
|
|
|
|
CheckThrow (false);
|
|
|
|
|
return xobileTlsContext != null && xobileTlsContext.IsServer;
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool IsAuthenticated {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
get {
|
|
|
|
|
lock (ioLock) {
|
|
|
|
|
// Don't use CheckThrow(), we want to return false if we're not authenticated.
|
|
|
|
|
return xobileTlsContext != null && lastException == null && xobileTlsContext.IsAuthenticated;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool IsMutuallyAuthenticated {
|
|
|
|
|
get {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
lock (ioLock) {
|
|
|
|
|
// Don't use CheckThrow() here.
|
|
|
|
|
if (!IsAuthenticated)
|
|
|
|
|
return false;
|
|
|
|
|
if ((xobileTlsContext.IsServer ? xobileTlsContext.LocalServerCertificate : xobileTlsContext.LocalClientCertificate) == null)
|
|
|
|
|
return false;
|
|
|
|
|
return xobileTlsContext.IsRemoteCertificateAvailable;
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Dispose (bool disposing)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
lock (ioLock) {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
Debug ("Dispose: {0}", xobileTlsContext != null);
|
|
|
|
|
lastException = ExceptionDispatchInfo.Capture (new ObjectDisposedException ("MobileAuthenticatedStream"));
|
2016-08-23 13:20:38 +00:00
|
|
|
|
if (xobileTlsContext != null) {
|
|
|
|
|
xobileTlsContext.Dispose ();
|
|
|
|
|
xobileTlsContext = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
base.Dispose (disposing);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Flush ()
|
|
|
|
|
{
|
2017-10-19 20:04:20 +00:00
|
|
|
|
InnerStream.Flush ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public SslProtocols SslProtocol {
|
|
|
|
|
get {
|
|
|
|
|
lock (ioLock) {
|
|
|
|
|
CheckThrow (true);
|
|
|
|
|
return (SslProtocols)xobileTlsContext.NegotiatedProtocol;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public X509Certificate RemoteCertificate {
|
|
|
|
|
get {
|
|
|
|
|
lock (ioLock) {
|
|
|
|
|
CheckThrow (true);
|
|
|
|
|
return xobileTlsContext.RemoteCertificate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public X509Certificate LocalCertificate {
|
|
|
|
|
get {
|
|
|
|
|
lock (ioLock) {
|
|
|
|
|
CheckThrow (true);
|
|
|
|
|
return InternalLocalCertificate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public X509Certificate InternalLocalCertificate {
|
|
|
|
|
get {
|
|
|
|
|
lock (ioLock) {
|
|
|
|
|
CheckThrow (false);
|
|
|
|
|
if (xobileTlsContext == null)
|
|
|
|
|
return null;
|
|
|
|
|
return xobileTlsContext.IsServer ? xobileTlsContext.LocalServerCertificate : xobileTlsContext.LocalClientCertificate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public MSI.MonoTlsConnectionInfo GetConnectionInfo ()
|
|
|
|
|
{
|
|
|
|
|
lock (ioLock) {
|
|
|
|
|
CheckThrow (true);
|
|
|
|
|
return xobileTlsContext.ConnectionInfo;
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// 'xobileTlsContext' must not be accessed below this point.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
public override long Seek (long offset, SeekOrigin origin)
|
|
|
|
|
{
|
|
|
|
|
throw new NotSupportedException ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void SetLength (long value)
|
|
|
|
|
{
|
|
|
|
|
InnerStream.SetLength (value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public TransportContext TransportContext {
|
|
|
|
|
get { throw new NotSupportedException (); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool CanRead {
|
|
|
|
|
get { return IsAuthenticated && InnerStream.CanRead; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool CanTimeout {
|
|
|
|
|
get { return InnerStream.CanTimeout; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool CanWrite {
|
2017-08-21 15:34:15 +00:00
|
|
|
|
get { return IsAuthenticated & InnerStream.CanWrite && !shutdown; }
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool CanSeek {
|
|
|
|
|
get { return false; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override long Length {
|
|
|
|
|
get { return InnerStream.Length; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override long Position {
|
|
|
|
|
get { return InnerStream.Position; }
|
|
|
|
|
set { throw new NotSupportedException (); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool IsEncrypted {
|
|
|
|
|
get { return IsAuthenticated; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool IsSigned {
|
|
|
|
|
get { return IsAuthenticated; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int ReadTimeout {
|
|
|
|
|
get { return InnerStream.ReadTimeout; }
|
|
|
|
|
set { InnerStream.ReadTimeout = value; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int WriteTimeout {
|
|
|
|
|
get { return InnerStream.WriteTimeout; }
|
|
|
|
|
set { InnerStream.WriteTimeout = value; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SSA.CipherAlgorithmType CipherAlgorithm {
|
|
|
|
|
get {
|
|
|
|
|
CheckThrow (true);
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var info = GetConnectionInfo ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
if (info == null)
|
|
|
|
|
return SSA.CipherAlgorithmType.None;
|
|
|
|
|
switch (info.CipherAlgorithmType) {
|
|
|
|
|
case MSI.CipherAlgorithmType.Aes128:
|
|
|
|
|
case MSI.CipherAlgorithmType.AesGcm128:
|
|
|
|
|
return SSA.CipherAlgorithmType.Aes128;
|
|
|
|
|
case MSI.CipherAlgorithmType.Aes256:
|
|
|
|
|
case MSI.CipherAlgorithmType.AesGcm256:
|
|
|
|
|
return SSA.CipherAlgorithmType.Aes256;
|
|
|
|
|
default:
|
|
|
|
|
return SSA.CipherAlgorithmType.None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SSA.HashAlgorithmType HashAlgorithm {
|
|
|
|
|
get {
|
|
|
|
|
CheckThrow (true);
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var info = GetConnectionInfo ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
if (info == null)
|
|
|
|
|
return SSA.HashAlgorithmType.None;
|
|
|
|
|
switch (info.HashAlgorithmType) {
|
|
|
|
|
case MSI.HashAlgorithmType.Md5:
|
|
|
|
|
case MSI.HashAlgorithmType.Md5Sha1:
|
|
|
|
|
return SSA.HashAlgorithmType.Md5;
|
|
|
|
|
case MSI.HashAlgorithmType.Sha1:
|
|
|
|
|
case MSI.HashAlgorithmType.Sha224:
|
|
|
|
|
case MSI.HashAlgorithmType.Sha256:
|
|
|
|
|
case MSI.HashAlgorithmType.Sha384:
|
|
|
|
|
case MSI.HashAlgorithmType.Sha512:
|
|
|
|
|
return SSA.HashAlgorithmType.Sha1;
|
|
|
|
|
default:
|
|
|
|
|
return SSA.HashAlgorithmType.None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SSA.ExchangeAlgorithmType KeyExchangeAlgorithm {
|
|
|
|
|
get {
|
|
|
|
|
CheckThrow (true);
|
2017-08-21 15:34:15 +00:00
|
|
|
|
var info = GetConnectionInfo ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
if (info == null)
|
|
|
|
|
return SSA.ExchangeAlgorithmType.None;
|
|
|
|
|
switch (info.ExchangeAlgorithmType) {
|
|
|
|
|
case MSI.ExchangeAlgorithmType.Rsa:
|
|
|
|
|
return SSA.ExchangeAlgorithmType.RsaSign;
|
|
|
|
|
case MSI.ExchangeAlgorithmType.Dhe:
|
|
|
|
|
case MSI.ExchangeAlgorithmType.EcDhe:
|
|
|
|
|
return SSA.ExchangeAlgorithmType.DiffieHellman;
|
|
|
|
|
default:
|
|
|
|
|
return SSA.ExchangeAlgorithmType.None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#region Need to Implement
|
2016-08-23 13:20:38 +00:00
|
|
|
|
public int CipherStrength {
|
|
|
|
|
get {
|
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public int HashStrength {
|
|
|
|
|
get {
|
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public int KeyExchangeStrength {
|
|
|
|
|
get {
|
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public bool CheckCertRevocationStatus {
|
|
|
|
|
get {
|
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
#endregion
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|