// This will initiate renegotiation or PHA for Tls1.3 private async Task RenegotiateAsync <TIOAdapter>(CancellationToken cancellationToken) where TIOAdapter : IReadWriteAdapter { if (Interlocked.Exchange(ref _nestedAuth, 1) == 1) { throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "authenticate")); } if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read")); } if (Interlocked.Exchange(ref _nestedWrite, 1) == 1) { _nestedRead = 0; throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "write")); } try { if (_buffer.ActiveLength > 0) { throw new InvalidOperationException(SR.net_ssl_renegotiate_buffer); } _sslAuthenticationOptions !.RemoteCertRequired = true; _isRenego = true; SecurityStatusPal status = Renegotiate(out byte[]? nextmsg); if (nextmsg is { Length : > 0 }) { await TIOAdapter.WriteAsync(InnerStream, nextmsg, 0, nextmsg.Length, cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); } if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { if (status.ErrorCode == SecurityStatusPalErrorCode.NoRenegotiation) { // Peer does not want to renegotiate. That should keep session usable. return; } throw SslStreamPal.GetException(status); } _buffer.EnsureAvailableSpace(InitialHandshakeBufferSize); ProtocolToken message; do { message = await ReceiveBlobAsync <TIOAdapter>(cancellationToken).ConfigureAwait(false); if (message.Size > 0) { await TIOAdapter.WriteAsync(InnerStream, message.Payload !, 0, message.Size, cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); } }while (message.Status.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded); CompleteHandshake(_sslAuthenticationOptions !); }
private async ValueTask <int> ReadAsyncInternal <TIOAdapter>(TIOAdapter adapter, Memory <byte> buffer) where TIOAdapter : ISslIOAdapter { if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); } try { while (true) { int copyBytes; if (_decryptedBytesCount != 0) { copyBytes = CopyDecryptedData(buffer); return(copyBytes); } copyBytes = await adapter.ReadLockAsync(buffer).ConfigureAwait(false); if (copyBytes > 0) { return(copyBytes); } ResetReadBuffer(); int readBytes = await FillBufferAsync(adapter, SecureChannel.ReadHeaderSize).ConfigureAwait(false); if (readBytes == 0) { return(0); } int payloadBytes = GetFrameSize(new ReadOnlySpan <byte>(_internalBuffer, _internalOffset, readBytes)); if (payloadBytes < 0) { throw new IOException(SR.net_frame_read_size); } readBytes = await FillBufferAsync(adapter, payloadBytes).ConfigureAwait(false); Debug.Assert(readBytes >= 0); if (readBytes == 0) { throw new IOException(SR.net_io_eof); } // At this point, readBytes contains the size of the header plus body. // Set _decrytpedBytesOffset/Count to the current frame we have (including header) // DecryptData will decrypt in-place and modify these to point to the actual decrypted data, which may be smaller. _decryptedBytesOffset = _internalOffset; _decryptedBytesCount = payloadBytes; SecurityStatusPal status = DecryptData(); // Treat the bytes we just decrypted as consumed // Note, we won't do another buffer read until the decrypted bytes are processed ConsumeBufferedBytes(readBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { byte[] extraBuffer = null; if (_decryptedBytesCount != 0) { extraBuffer = new byte[_decryptedBytesCount]; Buffer.BlockCopy(_internalBuffer, _decryptedBytesOffset, extraBuffer, 0, _decryptedBytesCount); _decryptedBytesCount = 0; } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"***Processing an error Status = {status}"); } if (status.ErrorCode == SecurityStatusPalErrorCode.Renegotiate) { if (!_sslAuthenticationOptions.AllowRenegotiation) { if (NetEventSource.IsEnabled) { NetEventSource.Fail(this, "Renegotiation was requested but it is disallowed"); } throw new IOException(SR.net_ssl_io_renego); } await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false); // Loop on read. continue; } if (status.ErrorCode == SecurityStatusPalErrorCode.ContextExpired) { return(0); } throw new IOException(SR.net_io_decrypt, SslStreamPal.GetException(status)); } } } catch (Exception e) { if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested)) { throw; } throw new IOException(SR.net_io_read, e); } finally { _nestedRead = 0; } }
internal Exception GetException() { // If it's not done, then there's got to be an error, even if it's // a Handshake message up, and we only have a Warning message. return(this.Done ? null : SslStreamPal.GetException(Status)); }
private ValueTask WriteSingleChunk <TIOAdapter>(TIOAdapter writeAdapter, ReadOnlyMemory <byte> buffer) where TIOAdapter : struct, ISslIOAdapter { // Request a write IO slot. Task ioSlot = writeAdapter.WriteLockAsync(); if (!ioSlot.IsCompletedSuccessfully) { // Operation is async and has been queued, return. return(WaitForWriteIOSlot(writeAdapter, ioSlot, buffer)); } byte[] rentedBuffer = ArrayPool <byte> .Shared.Rent(buffer.Length + FrameOverhead); byte[] outBuffer = rentedBuffer; SecurityStatusPal status = EncryptData(buffer, ref outBuffer, out int encryptedBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { // Re-handshake status is not supported. ArrayPool <byte> .Shared.Return(rentedBuffer); return(new ValueTask(Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.net_io_encrypt, SslStreamPal.GetException(status)))))); } ValueTask t = writeAdapter.WriteAsync(outBuffer, 0, encryptedBytes); if (t.IsCompletedSuccessfully) { ArrayPool <byte> .Shared.Return(rentedBuffer); FinishWrite(); return(t); } else { return(CompleteAsync(t, rentedBuffer)); } async ValueTask WaitForWriteIOSlot(TIOAdapter wAdapter, Task lockTask, ReadOnlyMemory <byte> buff) { await lockTask.ConfigureAwait(false); await WriteSingleChunk(wAdapter, buff).ConfigureAwait(false); } async ValueTask CompleteAsync(ValueTask writeTask, byte[] bufferToReturn) { try { await writeTask.ConfigureAwait(false); } finally { ArrayPool <byte> .Shared.Return(bufferToReturn); FinishWrite(); } } }
private async ValueTask <int> ReadAsyncInternal <TIOAdapter>(TIOAdapter adapter, Memory <byte> buffer) where TIOAdapter : ISslIOAdapter { if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); } try { while (true) { if (_decryptedBytesCount != 0) { return(CopyDecryptedData(buffer)); } int copyBytes = await adapter.ReadLockAsync(buffer).ConfigureAwait(false); if (copyBytes > 0) { return(copyBytes); } ResetReadBuffer(); // Read the next frame header. if (_internalBufferCount < SecureChannel.ReadHeaderSize) { // We don't have enough bytes buffered, so issue an initial read to try to get enough. This is // done in this method both to better consolidate error handling logic (the first read is the special // case that needs to differentiate reading 0 from > 0, and everything else needs to throw if it // doesn't read enough), and to minimize the chances that in the common case the FillBufferAsync // helper needs to yield and allocate a state machine. int readBytes = await adapter.ReadAsync(_internalBuffer.AsMemory(_internalBufferCount)).ConfigureAwait(false); if (readBytes == 0) { return(0); } _internalBufferCount += readBytes; if (_internalBufferCount < SecureChannel.ReadHeaderSize) { await FillBufferAsync(adapter, SecureChannel.ReadHeaderSize).ConfigureAwait(false); } } Debug.Assert(_internalBufferCount >= SecureChannel.ReadHeaderSize); // Parse the frame header to determine the payload size (which includes the header size). int payloadBytes = GetFrameSize(_internalBuffer.AsSpan(_internalOffset)); if (payloadBytes < 0) { throw new IOException(SR.net_frame_read_size); } // Read in the rest of the payload if we don't have it. if (_internalBufferCount < payloadBytes) { await FillBufferAsync(adapter, payloadBytes).ConfigureAwait(false); } // Set _decrytpedBytesOffset/Count to the current frame we have (including header) // DecryptData will decrypt in-place and modify these to point to the actual decrypted data, which may be smaller. _decryptedBytesOffset = _internalOffset; _decryptedBytesCount = payloadBytes; SecurityStatusPal status = DecryptData(); // Treat the bytes we just decrypted as consumed // Note, we won't do another buffer read until the decrypted bytes are processed ConsumeBufferedBytes(payloadBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { byte[] extraBuffer = null; if (_decryptedBytesCount != 0) { extraBuffer = new byte[_decryptedBytesCount]; Buffer.BlockCopy(_internalBuffer, _decryptedBytesOffset, extraBuffer, 0, _decryptedBytesCount); _decryptedBytesCount = 0; } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"***Processing an error Status = {status}"); } if (status.ErrorCode == SecurityStatusPalErrorCode.Renegotiate) { if (!_sslAuthenticationOptions.AllowRenegotiation) { if (NetEventSource.IsEnabled) { NetEventSource.Fail(this, "Renegotiation was requested but it is disallowed"); } throw new IOException(SR.net_ssl_io_renego); } await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false); // Loop on read. continue; } if (status.ErrorCode == SecurityStatusPalErrorCode.ContextExpired) { return(0); } throw new IOException(SR.net_io_decrypt, SslStreamPal.GetException(status)); } } } catch (Exception e) { if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested)) { throw; } throw new IOException(SR.net_io_read, e); } finally { _nestedRead = 0; } }