|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Security
{
public enum EncryptionPolicy
{
// Prohibit null ciphers (current system defaults)
RequireEncryption = 0,
// Add null ciphers to current system defaults
[System.ObsoleteAttribute(Obsoletions.EncryptionPolicyMessage, DiagnosticId = Obsoletions.EncryptionPolicyDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
AllowNoEncryption,
// Request null ciphers only
[System.ObsoleteAttribute(Obsoletions.EncryptionPolicyMessage, DiagnosticId = Obsoletions.EncryptionPolicyDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
NoEncryption
}
// A user delegate used to verify remote SSL certificate.
public delegate bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors);
// A user delegate used to select local SSL certificate.
public delegate X509Certificate LocalCertificateSelectionCallback(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate? remoteCertificate, string[] acceptableIssuers);
public delegate X509Certificate ServerCertificateSelectionCallback(object sender, string? hostName);
public delegate ValueTask<SslServerAuthenticationOptions> ServerOptionsSelectionCallback(SslStream stream, SslClientHelloInfo clientHelloInfo, object? state, CancellationToken cancellationToken);
public partial class SslStream : AuthenticatedStream
{
/// <summary>Set as the _exception when the instance is disposed.</summary>
private static readonly ExceptionDispatchInfo s_disposedSentinel = ExceptionDispatchInfo.Capture(new ObjectDisposedException(nameof(SslStream), (string?)null));
private ExceptionDispatchInfo? _exception;
private bool _shutdown;
private bool _handshakeCompleted;
// FrameOverhead = 5 byte header + HMAC trailer + padding (if block cipher)
// HMAC: 32 bytes for SHA-256 or 20 bytes for SHA-1 or 16 bytes for the MD5
private const int FrameOverhead = 64;
private const int InitialHandshakeBufferSize = 4096 + FrameOverhead; // try to fit at least 4K ServerCertificate
private const int ReadBufferSize = 4096 * 4 + FrameOverhead; // We read in 16K chunks + headers.
private SslBuffer _buffer = new();
// internal buffer for storing incoming data. Wrapper around ArrayBuffer which adds
// separation between decrypted and still encrypted part of the active region.
// - Encrypted: Contains incoming TLS frames, the last such frame may be incomplete
// - Decrypted: Contains decrypted data from *one* TLS frame which have not been read by the user yet.
private struct SslBuffer
{
private ArrayBuffer _buffer;
private int _decryptedLength;
// padding between decrypted part of the active memory and following undecrypted TLS frame.
private int _decryptedPadding;
// Indicates whether the _buffer currently holds a rented buffer.
private bool _isValid;
public SslBuffer()
{
_buffer = new ArrayBuffer(initialSize: 0, usePool: true);
_decryptedLength = 0;
_decryptedPadding = 0;
_isValid = false;
}
public bool IsValid => _isValid;
public Span<byte> DecryptedSpan => _buffer.ActiveSpan.Slice(0, _decryptedLength);
public ReadOnlySpan<byte> DecryptedReadOnlySpanSliced(int length)
{
Debug.Assert(length <= DecryptedLength);
return _buffer.ActiveSpan.Slice(0, length);
}
public int DecryptedLength => _decryptedLength;
public int ActiveLength => _buffer.ActiveLength;
public Span<byte> EncryptedSpanSliced(int length) => _buffer.ActiveSpan.Slice(_decryptedLength + _decryptedPadding, length);
public ReadOnlySpan<byte> EncryptedReadOnlySpan => _buffer.ActiveSpan.Slice(_decryptedLength + _decryptedPadding);
public int EncryptedLength => _buffer.ActiveLength - _decryptedPadding - _decryptedLength;
public Memory<byte> AvailableMemory => _buffer.AvailableMemory;
public int AvailableLength => _buffer.AvailableLength;
public int Capacity => _buffer.Capacity;
public void Commit(int byteCount) => _buffer.Commit(byteCount);
public void EnsureAvailableSpace(int byteCount)
{
_isValid = true;
_buffer.EnsureAvailableSpace(byteCount);
}
public void Discard(int byteCount)
{
Debug.Assert(byteCount <= _decryptedLength, "byteCount <= _decryptedBytes");
_buffer.Discard(byteCount);
_decryptedLength -= byteCount;
// if drained all decrypted data, discard also the tail of the frame so that only
// encrypted part of the active memory of the _buffer remains
if (_decryptedLength == 0)
{
_buffer.Discard(_decryptedPadding);
_decryptedPadding = 0;
}
}
public void DiscardEncrypted(int byteCount)
{
// should be called only during handshake -> no pending decrypted data
Debug.Assert(_decryptedLength == 0, "_decryptedBytes == 0");
Debug.Assert(_decryptedPadding == 0, "_encryptedOffset == 0");
_buffer.Discard(byteCount);
}
public void OnDecrypted(int decryptedOffset, int decryptedCount, int frameSize)
{
Debug.Assert(_decryptedLength == 0, "_decryptedBytes == 0");
Debug.Assert(_decryptedPadding == 0, "_encryptedOffset == 0");
if (decryptedCount > 0)
{
// discard padding before decrypted contents
_buffer.Discard(decryptedOffset);
_decryptedPadding = frameSize - decryptedOffset - decryptedCount;
_decryptedLength = decryptedCount;
}
else
{
// No user data available, discard entire frame
_buffer.Discard(frameSize);
}
}
public void ReturnBuffer()
{
_buffer.ClearAndReturnBuffer();
_decryptedLength = 0;
_decryptedPadding = 0;
_isValid = false;
}
}
private enum NestedState
{
StreamNotInUse = 0,
StreamInUse = 1,
StreamDisposed = 2,
}
private NestedState _nestedWrite;
private NestedState _nestedRead;
private PoolingPointerMemoryManager? _readPointerMemoryManager;
private PoolingPointerMemoryManager? _writePointerMemoryManager;
public SslStream(Stream innerStream)
: this(innerStream, false, null, null)
{
}
public SslStream(Stream innerStream, bool leaveInnerStreamOpen)
: this(innerStream, leaveInnerStreamOpen, null, null, EncryptionPolicy.RequireEncryption)
{
}
public SslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback? userCertificateValidationCallback)
: this(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, null, EncryptionPolicy.RequireEncryption)
{
}
public SslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback? userCertificateValidationCallback,
LocalCertificateSelectionCallback? userCertificateSelectionCallback)
: this(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback, EncryptionPolicy.RequireEncryption)
{
}
public SslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback? userCertificateValidationCallback,
LocalCertificateSelectionCallback? userCertificateSelectionCallback, EncryptionPolicy encryptionPolicy)
: base(innerStream, leaveInnerStreamOpen)
{
#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
if (encryptionPolicy != EncryptionPolicy.RequireEncryption && encryptionPolicy != EncryptionPolicy.AllowNoEncryption && encryptionPolicy != EncryptionPolicy.NoEncryption)
{
throw new ArgumentException(SR.Format(SR.net_invalid_enum, "EncryptionPolicy"), nameof(encryptionPolicy));
}
#pragma warning restore SYSLIB0040
_sslAuthenticationOptions.EncryptionPolicy = encryptionPolicy;
_sslAuthenticationOptions.CertValidationDelegate = userCertificateValidationCallback;
_sslAuthenticationOptions.CertSelectionDelegate = userCertificateSelectionCallback;
#if TARGET_ANDROID
_sslAuthenticationOptions.SslStreamProxy = new SslStream.JavaProxy(sslStream: this);
#endif
if (NetEventSource.Log.IsEnabled()) NetEventSource.Log.SslStreamCtor(this, innerStream);
}
//
// Client side auth.
//
public virtual IAsyncResult BeginAuthenticateAsClient(string targetHost, AsyncCallback? asyncCallback, object? asyncState)
{
return BeginAuthenticateAsClient(targetHost, null, SecurityProtocol.SystemDefaultSecurityProtocols, false,
asyncCallback, asyncState);
}
public virtual IAsyncResult BeginAuthenticateAsClient(string targetHost, X509CertificateCollection? clientCertificates,
bool checkCertificateRevocation, AsyncCallback? asyncCallback, object? asyncState)
{
return BeginAuthenticateAsClient(targetHost, clientCertificates, SecurityProtocol.SystemDefaultSecurityProtocols, checkCertificateRevocation, asyncCallback, asyncState);
}
public virtual IAsyncResult BeginAuthenticateAsClient(string targetHost, X509CertificateCollection? clientCertificates,
SslProtocols enabledSslProtocols, bool checkCertificateRevocation,
AsyncCallback? asyncCallback, object? asyncState)
{
SslClientAuthenticationOptions options = new SslClientAuthenticationOptions
{
TargetHost = targetHost,
ClientCertificates = clientCertificates,
EnabledSslProtocols = enabledSslProtocols,
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
EncryptionPolicy = _sslAuthenticationOptions.EncryptionPolicy,
};
return BeginAuthenticateAsClient(options, CancellationToken.None, asyncCallback, asyncState);
}
internal IAsyncResult BeginAuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken, AsyncCallback? asyncCallback, object? asyncState) =>
TaskToAsyncResult.Begin(AuthenticateAsClientAsync(sslClientAuthenticationOptions, cancellationToken)!, asyncCallback, asyncState);
public virtual void EndAuthenticateAsClient(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult);
//
// Server side auth.
//
public virtual IAsyncResult BeginAuthenticateAsServer(X509Certificate serverCertificate, AsyncCallback? asyncCallback, object? asyncState)
{
return BeginAuthenticateAsServer(serverCertificate, false, SecurityProtocol.SystemDefaultSecurityProtocols, false,
asyncCallback,
asyncState);
}
public virtual IAsyncResult BeginAuthenticateAsServer(X509Certificate serverCertificate, bool clientCertificateRequired,
bool checkCertificateRevocation, AsyncCallback? asyncCallback, object? asyncState)
{
return BeginAuthenticateAsServer(serverCertificate, clientCertificateRequired, SecurityProtocol.SystemDefaultSecurityProtocols, checkCertificateRevocation, asyncCallback, asyncState);
}
public virtual IAsyncResult BeginAuthenticateAsServer(X509Certificate serverCertificate, bool clientCertificateRequired,
SslProtocols enabledSslProtocols, bool checkCertificateRevocation,
AsyncCallback? asyncCallback,
object? asyncState)
{
SslServerAuthenticationOptions options = new SslServerAuthenticationOptions
{
ServerCertificate = serverCertificate,
ClientCertificateRequired = clientCertificateRequired,
EnabledSslProtocols = enabledSslProtocols,
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
EncryptionPolicy = _sslAuthenticationOptions.EncryptionPolicy,
};
return BeginAuthenticateAsServer(options, CancellationToken.None, asyncCallback, asyncState);
}
private IAsyncResult BeginAuthenticateAsServer(SslServerAuthenticationOptions sslServerAuthenticationOptions, CancellationToken cancellationToken, AsyncCallback? asyncCallback, object? asyncState) =>
TaskToAsyncResult.Begin(AuthenticateAsServerAsync(sslServerAuthenticationOptions, cancellationToken)!, asyncCallback, asyncState);
public virtual void EndAuthenticateAsServer(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult);
internal IAsyncResult BeginShutdown(AsyncCallback? asyncCallback, object? asyncState) => TaskToAsyncResult.Begin(ShutdownAsync(), asyncCallback, asyncState);
internal static void EndShutdown(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult);
public TransportContext TransportContext => new SslStreamContext(this);
#region Synchronous methods
public virtual void AuthenticateAsClient(string targetHost)
{
AuthenticateAsClient(targetHost, null, SecurityProtocol.SystemDefaultSecurityProtocols, false);
}
public virtual void AuthenticateAsClient(string targetHost, X509CertificateCollection? clientCertificates, bool checkCertificateRevocation)
{
AuthenticateAsClient(targetHost, clientCertificates, SecurityProtocol.SystemDefaultSecurityProtocols, checkCertificateRevocation);
}
public virtual void AuthenticateAsClient(string targetHost, X509CertificateCollection? clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
{
SslClientAuthenticationOptions options = new SslClientAuthenticationOptions
{
TargetHost = targetHost,
ClientCertificates = clientCertificates,
EnabledSslProtocols = enabledSslProtocols,
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
EncryptionPolicy = _sslAuthenticationOptions.EncryptionPolicy,
};
AuthenticateAsClient(options);
}
public void AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions)
{
ArgumentNullException.ThrowIfNull(sslClientAuthenticationOptions);
ThrowIfExceptional();
_sslAuthenticationOptions.UpdateOptions(sslClientAuthenticationOptions);
ProcessAuthenticationAsync().GetAwaiter().GetResult();
}
public virtual void AuthenticateAsServer(X509Certificate serverCertificate)
{
AuthenticateAsServer(serverCertificate, false, SecurityProtocol.SystemDefaultSecurityProtocols, false);
}
public virtual void AuthenticateAsServer(X509Certificate serverCertificate, bool clientCertificateRequired, bool checkCertificateRevocation)
{
AuthenticateAsServer(serverCertificate, clientCertificateRequired, SecurityProtocol.SystemDefaultSecurityProtocols, checkCertificateRevocation);
}
public virtual void AuthenticateAsServer(X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
{
SslServerAuthenticationOptions options = new SslServerAuthenticationOptions
{
ServerCertificate = serverCertificate,
ClientCertificateRequired = clientCertificateRequired,
EnabledSslProtocols = enabledSslProtocols,
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
EncryptionPolicy = _sslAuthenticationOptions.EncryptionPolicy,
};
AuthenticateAsServer(options);
}
public void AuthenticateAsServer(SslServerAuthenticationOptions sslServerAuthenticationOptions)
{
ArgumentNullException.ThrowIfNull(sslServerAuthenticationOptions);
_sslAuthenticationOptions.UpdateOptions(sslServerAuthenticationOptions);
ProcessAuthenticationAsync().GetAwaiter().GetResult();
}
#endregion
#region Task-based async public methods
public virtual Task AuthenticateAsClientAsync(string targetHost) => AuthenticateAsClientAsync(targetHost, null, false);
public virtual Task AuthenticateAsClientAsync(string targetHost, X509CertificateCollection? clientCertificates, bool checkCertificateRevocation) => AuthenticateAsClientAsync(targetHost, clientCertificates, SecurityProtocol.SystemDefaultSecurityProtocols, checkCertificateRevocation);
public virtual Task AuthenticateAsClientAsync(string targetHost, X509CertificateCollection? clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
{
SslClientAuthenticationOptions options = new SslClientAuthenticationOptions()
{
TargetHost = targetHost,
ClientCertificates = clientCertificates,
EnabledSslProtocols = enabledSslProtocols,
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
EncryptionPolicy = _sslAuthenticationOptions.EncryptionPolicy,
};
return AuthenticateAsClientAsync(options);
}
public Task AuthenticateAsClientAsync(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(sslClientAuthenticationOptions);
ThrowIfExceptional();
_sslAuthenticationOptions.UpdateOptions(sslClientAuthenticationOptions);
return ProcessAuthenticationAsync(isAsync: true, cancellationToken);
}
public virtual Task AuthenticateAsServerAsync(X509Certificate serverCertificate) =>
AuthenticateAsServerAsync(serverCertificate, false, SecurityProtocol.SystemDefaultSecurityProtocols, false);
public virtual Task AuthenticateAsServerAsync(X509Certificate serverCertificate, bool clientCertificateRequired, bool checkCertificateRevocation)
{
SslServerAuthenticationOptions options = new SslServerAuthenticationOptions
{
ServerCertificate = serverCertificate,
ClientCertificateRequired = clientCertificateRequired,
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
EncryptionPolicy = _sslAuthenticationOptions.EncryptionPolicy,
};
return AuthenticateAsServerAsync(options);
}
public virtual Task AuthenticateAsServerAsync(X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
{
SslServerAuthenticationOptions options = new SslServerAuthenticationOptions
{
ServerCertificate = serverCertificate,
ClientCertificateRequired = clientCertificateRequired,
EnabledSslProtocols = enabledSslProtocols,
CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
EncryptionPolicy = _sslAuthenticationOptions.EncryptionPolicy,
};
return AuthenticateAsServerAsync(options);
}
public Task AuthenticateAsServerAsync(SslServerAuthenticationOptions sslServerAuthenticationOptions, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(sslServerAuthenticationOptions);
_sslAuthenticationOptions.UpdateOptions(sslServerAuthenticationOptions);
return ProcessAuthenticationAsync(isAsync: true, cancellationToken);
}
public Task AuthenticateAsServerAsync(ServerOptionsSelectionCallback optionsCallback, object? state, CancellationToken cancellationToken = default)
{
_sslAuthenticationOptions.UpdateOptions(optionsCallback, state);
return ProcessAuthenticationAsync(isAsync: true, cancellationToken);
}
public virtual Task ShutdownAsync()
{
ThrowIfExceptionalOrNotAuthenticatedOrShutdown();
ProtocolToken token = CreateShutdownToken();
_shutdown = true;
if (token.Size > 0 && token.Payload != null)
{
return InnerStream.WriteAsync(new ReadOnlyMemory<byte>(token.Payload, 0, token.Size), default).AsTask();
}
return Task.CompletedTask;
}
#endregion
public override bool IsAuthenticated => IsValidContext && _exception == null && _handshakeCompleted;
public override bool IsMutuallyAuthenticated
{
get
{
return
IsAuthenticated &&
(IsServer ? LocalServerCertificate : LocalClientCertificate) != null &&
IsRemoteCertificateAvailable; /* does not work: Context.IsMutualAuthFlag;*/
}
}
public override bool IsEncrypted => IsAuthenticated;
public override bool IsSigned => IsAuthenticated;
public override bool IsServer => _sslAuthenticationOptions.IsServer;
public virtual SslProtocols SslProtocol
{
get
{
ThrowIfExceptionalOrNotHandshake();
return GetSslProtocolInternal();
}
}
// Skips the ThrowIfExceptionalOrNotHandshake() check
internal SslProtocols GetSslProtocolInternal()
{
if (_connectionInfo.Protocol == 0)
{
return SslProtocols.None;
}
SslProtocols proto = (SslProtocols)_connectionInfo.Protocol;
SslProtocols ret = SslProtocols.None;
#pragma warning disable 0618 // Ssl2, Ssl3 are deprecated.
// Restore client/server bits so the result maps exactly on published constants.
if ((proto & SslProtocols.Ssl2) != 0)
{
ret |= SslProtocols.Ssl2;
}
if ((proto & SslProtocols.Ssl3) != 0)
{
ret |= SslProtocols.Ssl3;
}
#pragma warning restore
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
if ((proto & SslProtocols.Tls) != 0)
{
ret |= SslProtocols.Tls;
}
if ((proto & SslProtocols.Tls11) != 0)
{
ret |= SslProtocols.Tls11;
}
#pragma warning restore SYSLIB0039
if ((proto & SslProtocols.Tls12) != 0)
{
ret |= SslProtocols.Tls12;
}
if ((proto & SslProtocols.Tls13) != 0)
{
ret |= SslProtocols.Tls13;
}
return ret;
}
public virtual bool CheckCertRevocationStatus => _sslAuthenticationOptions.CertificateRevocationCheckMode != X509RevocationMode.NoCheck;
//
// This will return selected local cert for both client/server streams
//
public virtual X509Certificate? LocalCertificate
{
get
{
ThrowIfExceptionalOrNotAuthenticated();
return IsServer ? LocalServerCertificate : LocalClientCertificate;
}
}
public virtual X509Certificate? RemoteCertificate
{
get
{
ThrowIfExceptionalOrNotAuthenticated();
_remoteCertificateExposed = true;
return _remoteCertificate;
}
}
public SslApplicationProtocol NegotiatedApplicationProtocol
{
get
{
ThrowIfExceptionalOrNotHandshake();
return _connectionInfo.ApplicationProtocol != null ? new SslApplicationProtocol(_connectionInfo.ApplicationProtocol, false) : default;
}
}
[CLSCompliant(false)]
public virtual TlsCipherSuite NegotiatedCipherSuite
{
get
{
ThrowIfExceptionalOrNotHandshake();
return _connectionInfo.TlsCipherSuite;
}
}
[Obsolete(Obsoletions.TlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.TlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public virtual CipherAlgorithmType CipherAlgorithm
{
get
{
ThrowIfExceptionalOrNotHandshake();
return (CipherAlgorithmType)_connectionInfo.DataCipherAlg;
}
}
[Obsolete(Obsoletions.TlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.TlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public virtual int CipherStrength
{
get
{
ThrowIfExceptionalOrNotHandshake();
return _connectionInfo.DataKeySize;
}
}
[Obsolete(Obsoletions.TlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.TlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public virtual HashAlgorithmType HashAlgorithm
{
get
{
ThrowIfExceptionalOrNotHandshake();
return (HashAlgorithmType)_connectionInfo.DataHashAlg;
}
}
[Obsolete(Obsoletions.TlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.TlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public virtual int HashStrength
{
get
{
ThrowIfExceptionalOrNotHandshake();
return _connectionInfo.DataHashKeySize;
}
}
[Obsolete(Obsoletions.TlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.TlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public virtual ExchangeAlgorithmType KeyExchangeAlgorithm
{
get
{
ThrowIfExceptionalOrNotHandshake();
return (ExchangeAlgorithmType)_connectionInfo.KeyExchangeAlg;
}
}
[Obsolete(Obsoletions.TlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.TlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public virtual int KeyExchangeStrength
{
get
{
ThrowIfExceptionalOrNotHandshake();
return _connectionInfo.KeyExchKeySize;
}
}
public string TargetHostName
{
get
{
return _sslAuthenticationOptions.TargetHost;
}
}
//
// Stream contract implementation.
//
public override bool CanSeek => false;
public override bool CanRead => IsAuthenticated && InnerStream.CanRead;
public override bool CanTimeout => InnerStream.CanTimeout;
public override bool CanWrite => IsAuthenticated && InnerStream.CanWrite && !_shutdown;
public override int ReadTimeout
{
get => InnerStream.ReadTimeout;
set => InnerStream.ReadTimeout = value;
}
public override int WriteTimeout
{
get => InnerStream.WriteTimeout;
set => InnerStream.WriteTimeout = value;
}
public override long Length => InnerStream.Length;
public override long Position
{
get => InnerStream.Position;
set => throw new NotSupportedException(SR.net_noseek);
}
public override void SetLength(long value) => InnerStream.SetLength(value);
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(SR.net_noseek);
public override void Flush() => InnerStream.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) => InnerStream.FlushAsync(cancellationToken);
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("windows")]
[SupportedOSPlatform("freebsd")]
public virtual Task NegotiateClientCertificateAsync(CancellationToken cancellationToken = default)
{
ThrowIfExceptionalOrNotAuthenticated();
if (RemoteCertificate != null)
{
throw new InvalidOperationException(SR.net_ssl_certificate_exist);
}
return RenegotiateAsync<AsyncReadWriteAdapter>(cancellationToken);
}
protected override void Dispose(bool disposing)
{
try
{
CloseInternal();
}
finally
{
base.Dispose(disposing);
}
}
public override async ValueTask DisposeAsync()
{
try
{
CloseInternal();
}
finally
{
await base.DisposeAsync().ConfigureAwait(false);
}
}
private static unsafe PoolingPointerMemoryManager RentPointerMemoryManager(ref PoolingPointerMemoryManager? field, byte* pointer, int length)
{
// we get null when called for the first-time, or concurrent read or write operation
var manager = Interlocked.Exchange(ref field, null) ?? new PoolingPointerMemoryManager();
manager.Reset(pointer, length);
return manager;
}
private static unsafe void ReturnPointerMemoryManager(ref PoolingPointerMemoryManager? field, PoolingPointerMemoryManager manager)
{
manager.Reset(null, 0);
field = manager;
}
public override int ReadByte()
{
ThrowIfExceptionalOrNotAuthenticated();
if (Interlocked.Exchange(ref _nestedRead, NestedState.StreamInUse) == NestedState.StreamInUse)
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read"));
}
// If there's any data in the buffer, take one byte, and we're done.
try
{
if (_buffer.DecryptedLength > 0)
{
int b = _buffer.DecryptedSpan[0];
_buffer.Discard(1);
ReturnReadBufferIfEmpty();
return b;
}
}
finally
{
// Regardless of whether we were able to read a byte from the buffer,
// reset the read tracking. If we weren't able to read a byte, the
// subsequent call to Read will set the flag again.
_nestedRead = NestedState.StreamNotInUse;
}
// Otherwise, fall back to reading a byte via Read, the same way Stream.ReadByte does.
// This allocation is unfortunate but should be relatively rare, as it'll only occur once
// per buffer fill internally by Read.
byte oneByte = default;
int bytesRead = Read(new Span<byte>(ref oneByte));
Debug.Assert(bytesRead == 0 || bytesRead == 1);
return bytesRead == 1 ? oneByte : -1;
}
public override unsafe int Read(Span<byte> buffer)
{
ThrowIfExceptionalOrNotAuthenticated();
fixed (byte* ptr = &MemoryMarshal.GetReference(buffer))
{
PoolingPointerMemoryManager memoryManager = RentPointerMemoryManager(ref _readPointerMemoryManager, ptr, buffer.Length);
try
{
ValueTask<int> vt = ReadAsyncInternal<SyncReadWriteAdapter>(memoryManager.Memory, default(CancellationToken));
Debug.Assert(vt.IsCompleted, "Sync operation must have completed synchronously");
return vt.GetAwaiter().GetResult();
}
finally
{
ReturnPointerMemoryManager(ref _readPointerMemoryManager, memoryManager);
}
}
}
public override int Read(byte[] buffer, int offset, int count)
{
ThrowIfExceptionalOrNotAuthenticated();
ValidateBufferArguments(buffer, offset, count);
ValueTask<int> vt = ReadAsyncInternal<SyncReadWriteAdapter>(new Memory<byte>(buffer, offset, count), default(CancellationToken));
Debug.Assert(vt.IsCompleted, "Sync operation must have completed synchronously");
return vt.GetAwaiter().GetResult();
}
public override void WriteByte(byte value) => Write(new ReadOnlySpan<byte>(ref value));
public override unsafe void Write(ReadOnlySpan<byte> buffer)
{
ThrowIfExceptionalOrNotAuthenticated();
fixed (byte* ptr = &MemoryMarshal.GetReference(buffer))
{
PoolingPointerMemoryManager memoryManager = RentPointerMemoryManager(ref _writePointerMemoryManager, ptr, buffer.Length);
try
{
ValueTask vt = WriteAsyncInternal<SyncReadWriteAdapter>(memoryManager.Memory, default(CancellationToken));
Debug.Assert(vt.IsCompleted, "Sync operation must have completed synchronously");
vt.GetAwaiter().GetResult();
}
finally
{
ReturnPointerMemoryManager(ref _writePointerMemoryManager, memoryManager);
}
}
}
public void Write(byte[] buffer) => Write(buffer, 0, buffer.Length);
public override void Write(byte[] buffer, int offset, int count)
{
ThrowIfExceptionalOrNotAuthenticated();
ValidateBufferArguments(buffer, offset, count);
ValueTask vt = WriteAsyncInternal<SyncReadWriteAdapter>(new ReadOnlyMemory<byte>(buffer, offset, count), default(CancellationToken));
Debug.Assert(vt.IsCompleted, "Sync operation must have completed synchronously");
vt.GetAwaiter().GetResult();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState)
{
ThrowIfExceptionalOrNotAuthenticated();
return TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), asyncCallback, asyncState);
}
public override int EndRead(IAsyncResult asyncResult)
{
ThrowIfExceptionalOrNotAuthenticated();
return TaskToAsyncResult.End<int>(asyncResult);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState)
{
ThrowIfExceptionalOrNotAuthenticated();
return TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), asyncCallback, asyncState);
}
public override void EndWrite(IAsyncResult asyncResult)
{
ThrowIfExceptionalOrNotAuthenticated();
TaskToAsyncResult.End(asyncResult);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ThrowIfExceptionalOrNotAuthenticated();
ValidateBufferArguments(buffer, offset, count);
return WriteAsyncInternal<AsyncReadWriteAdapter>(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
ThrowIfExceptionalOrNotAuthenticated();
return WriteAsyncInternal<AsyncReadWriteAdapter>(buffer, cancellationToken);
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ThrowIfExceptionalOrNotAuthenticated();
ValidateBufferArguments(buffer, offset, count);
return ReadAsyncInternal<AsyncReadWriteAdapter>(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
ThrowIfExceptionalOrNotAuthenticated();
return ReadAsyncInternal<AsyncReadWriteAdapter>(buffer, cancellationToken);
}
private void ThrowIfExceptional()
{
ExceptionDispatchInfo? e = _exception;
if (e != null)
{
ThrowExceptional(e);
}
// Local function to make the check method more inline friendly.
void ThrowExceptional(ExceptionDispatchInfo e)
{
// If the stored exception just indicates disposal, throw a new ODE rather than the stored one,
// so as to not continually build onto the shared exception's stack.
ObjectDisposedException.ThrowIf(ReferenceEquals(e, s_disposedSentinel), this);
// Throw the stored exception.
e.Throw();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfExceptionalOrNotAuthenticated()
{
ThrowIfExceptional();
if (!IsAuthenticated)
{
ThrowNotAuthenticated();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfExceptionalOrNotHandshake()
{
ThrowIfExceptional();
if (!IsAuthenticated)
{
ThrowNotAuthenticated();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfExceptionalOrNotAuthenticatedOrShutdown()
{
ThrowIfExceptional();
if (!IsAuthenticated)
{
ThrowNotAuthenticated();
}
if (_shutdown)
{
ThrowAlreadyShutdown();
}
// Local function to make the check method more inline friendly.
static void ThrowAlreadyShutdown()
{
throw new InvalidOperationException(SR.net_ssl_io_already_shutdown);
}
}
// Static non-returning throw method to make the check methods more inline friendly.
private static void ThrowNotAuthenticated()
{
throw new InvalidOperationException(SR.net_auth_noauth);
}
// (non-generic) copy of the PointerMemoryManager<T> which supports resetting the stored
// pointer to allow pooling its instances instead of allocating a new one per Read/Write call.
// The memory ponted to by the intenal poiner is assumed to be externally pinned (or naive memory).
internal sealed unsafe class PoolingPointerMemoryManager : MemoryManager<byte>
{
private byte* _pointer;
private int _length;
protected override void Dispose(bool disposing)
{
}
public void Reset(byte* pointer, int length)
{
_pointer = pointer;
_length = length;
}
public override Span<byte> GetSpan()
{
return new Span<byte>(_pointer, _length);
}
public override MemoryHandle Pin(int elementIndex = 0)
{
// memory assumed to be pinned already
return new MemoryHandle(_pointer + elementIndex, default, null);
}
public override void Unpin()
{
// nop
}
}
}
}
|