summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crypto/src/crypto/io/CipherStream.cs150
-rw-r--r--crypto/src/crypto/io/DigestSink.cs18
-rw-r--r--crypto/src/crypto/io/DigestStream.cs85
-rw-r--r--crypto/src/crypto/io/MacSink.cs18
-rw-r--r--crypto/src/crypto/io/MacStream.cs85
-rw-r--r--crypto/src/crypto/io/SignerSink.cs18
-rw-r--r--crypto/src/crypto/io/SignerStream.cs85
-rw-r--r--crypto/src/tls/TlsStream.cs2
-rw-r--r--crypto/src/util/io/BaseInputStream.cs4
-rw-r--r--crypto/src/util/io/BaseOutputStream.cs3
-rw-r--r--crypto/src/util/io/FilterStream.cs2
-rw-r--r--crypto/src/util/io/PushbackStream.cs2
-rw-r--r--crypto/src/util/io/Streams.cs50
13 files changed, 355 insertions, 167 deletions
diff --git a/crypto/src/crypto/io/CipherStream.cs b/crypto/src/crypto/io/CipherStream.cs
index a06f74190..0735d32cc 100644
--- a/crypto/src/crypto/io/CipherStream.cs
+++ b/crypto/src/crypto/io/CipherStream.cs
@@ -1,12 +1,11 @@
 using System;
 using System.Diagnostics;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
@@ -41,20 +40,11 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public IBufferedCipher WriteCipher => m_writeCipher;
 
-        public override bool CanRead
-        {
-            get { return m_stream.CanRead; }
-        }
+        public override bool CanRead => m_stream.CanRead;
 
-        public override bool CanSeek
-        {
-            get { return false; }
-        }
+        public override bool CanSeek => false;
 
-        public override bool CanWrite
-        {
-            get { return m_stream.CanWrite; }
-        }
+        public override bool CanWrite => m_stream.CanWrite;
 
 #if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void CopyTo(Stream destination, int bufferSize)
@@ -114,12 +104,12 @@ namespace Org.BouncyCastle.Crypto.IO
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override int Read(Span<byte> buffer)
         {
-            if (buffer.IsEmpty)
-                return 0;
-
             if (m_readCipher == null)
                 return m_stream.Read(buffer);
 
+            if (buffer.IsEmpty)
+                return 0;
+
             int num = 0;
             while (num < buffer.Length)
             {
@@ -159,15 +149,9 @@ namespace Org.BouncyCastle.Crypto.IO
             return m_readBuf[m_readBufPos++];
         }
 
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            throw new NotSupportedException();
-        }
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 
-        public override void SetLength(long length)
-        {
-            throw new NotSupportedException();
-        }
+        public override void SetLength(long length) => throw new NotSupportedException();
 
         public override void Write(byte[] buffer, int offset, int count)
         {
@@ -179,33 +163,55 @@ namespace Org.BouncyCastle.Crypto.IO
 
             Streams.ValidateBufferArguments(buffer, offset, count);
 
-            if (count < 1)
-                return;
-
-            int outputSize = m_writeCipher.GetUpdateOutputSize(count);
-
-            byte[] output = null;
-            if (outputSize > 0)
+            if (count > 0)
             {
-                output = new byte[outputSize];
-            }
+                int outputSize = m_writeCipher.GetUpdateOutputSize(count);
+
+                byte[] output = new byte[outputSize];
 
-            try
-            {
                 int length = m_writeCipher.ProcessBytes(buffer, offset, count, output, 0);
                 if (length > 0)
                 {
-                    m_stream.Write(output, 0, length);
+                    try
+                    {
+                        m_stream.Write(output, 0, length);
+                    }
+                    finally
+                    {
+                        Array.Clear(output, 0, output.Length);
+                    }
                 }
             }
-            finally
+        }
+
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (m_writeCipher == null)
+                return m_stream.WriteAsync(buffer, offset, count, cancellationToken);
+
+            Streams.ValidateBufferArguments(buffer, offset, count);
+
+            if (count > 0)
             {
-                if (output != null)
+                if (cancellationToken.IsCancellationRequested)
+                    return Task.FromCanceled(cancellationToken);
+
+                int outputSize = m_writeCipher.GetUpdateOutputSize(count);
+
+                byte[] output = new byte[outputSize];
+
+                int length = m_writeCipher.ProcessBytes(buffer, offset, count, output, 0);
+                if (length > 0)
                 {
-                    Array.Clear(output, 0, output.Length);
+                    var writeTask = m_stream.WriteAsync(output, 0, length, cancellationToken);
+                    return Streams.WriteAsyncCompletion(writeTask, localBuffer: output);
                 }
             }
+
+            return Task.CompletedTask;
         }
+#endif
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void Write(ReadOnlySpan<byte> buffer)
@@ -216,32 +222,52 @@ namespace Org.BouncyCastle.Crypto.IO
                 return;
             }
 
-            if (buffer.IsEmpty)
-                return;
-
-            int outputSize = m_writeCipher.GetUpdateOutputSize(buffer.Length);
+            if (!buffer.IsEmpty)
+            {
+                int outputSize = m_writeCipher.GetUpdateOutputSize(buffer.Length);
 
-            Span<byte> output = outputSize <= Streams.DefaultBufferSize
-                ? stackalloc byte[outputSize]
-                : new byte[outputSize];
+                Span<byte> output = outputSize <= Streams.DefaultBufferSize
+                    ? stackalloc byte[outputSize]
+                    : new byte[outputSize];
 
-            try
-            {
                 int length = m_writeCipher.ProcessBytes(buffer, output);
                 if (length > 0)
                 {
-                    m_stream.Write(output[..length]);
+                    try
+                    {
+                        m_stream.Write(output[..length]);
+                    }
+                    finally
+                    {
+                        output.Fill(0x00);
+                    }
                 }
             }
-            finally
-            {
-                output.Fill(0x00);
-            }
         }
 
         public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
         {
-            return Streams.WriteAsync(WriteDestination, buffer, cancellationToken);
+            if (m_writeCipher == null)
+                return m_stream.WriteAsync(buffer, cancellationToken);
+
+            if (!buffer.IsEmpty)
+            {
+                if (cancellationToken.IsCancellationRequested)
+                    return ValueTask.FromCanceled(cancellationToken);
+
+                int outputSize = m_writeCipher.GetUpdateOutputSize(buffer.Length);
+
+                byte[] output = new byte[outputSize];
+
+                int length = m_writeCipher.ProcessBytes(buffer.Span, output.AsSpan());
+                if (length > 0)
+                {
+                    var writeTask = m_stream.WriteAsync(output.AsMemory(0, length), cancellationToken);
+                    return Streams.WriteAsyncCompletion(writeTask, localBuffer: output);
+                }
+            }
+
+            return ValueTask.CompletedTask;
         }
 #endif
 
@@ -253,10 +279,17 @@ namespace Org.BouncyCastle.Crypto.IO
                 return;
             }
 
-            byte[] data = m_writeCipher.ProcessByte(value);
-            if (data != null)
+            byte[] output = m_writeCipher.ProcessByte(value);
+            if (output != null)
             {
-                m_stream.Write(data, 0, data.Length);
+                try
+                {
+                    m_stream.Write(output, 0, output.Length);
+                }
+                finally
+                {
+                    Array.Clear(output, 0, output.Length);
+                }
             }
         }
 
@@ -337,6 +370,5 @@ namespace Org.BouncyCastle.Crypto.IO
         }
 
         private Stream ReadSource => m_readCipher == null ? m_stream : this;
-        private Stream WriteDestination => m_writeCipher == null ? m_stream : this;
     }
 }
diff --git a/crypto/src/crypto/io/DigestSink.cs b/crypto/src/crypto/io/DigestSink.cs
index 283bda28b..4e0e7d54a 100644
--- a/crypto/src/crypto/io/DigestSink.cs
+++ b/crypto/src/crypto/io/DigestSink.cs
@@ -1,4 +1,8 @@
 using System;
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
+using System.Threading.Tasks;
+#endif
 
 using Org.BouncyCastle.Utilities.IO;
 
@@ -11,7 +15,7 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public DigestSink(IDigest digest)
         {
-            m_digest = digest;
+            m_digest = digest ?? throw new ArgumentNullException(nameof(digest));
         }
 
         public IDigest Digest => m_digest;
@@ -26,6 +30,13 @@ namespace Org.BouncyCastle.Crypto.IO
             }
         }
 
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            return Streams.WriteAsyncDirect(this, buffer, offset, count, cancellationToken);
+        }
+#endif
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void Write(ReadOnlySpan<byte> buffer)
         {
@@ -34,6 +45,11 @@ namespace Org.BouncyCastle.Crypto.IO
                 m_digest.BlockUpdate(buffer);
             }
         }
+
+        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            return Streams.WriteAsyncDirect(this, buffer, cancellationToken);
+        }
 #endif
 
         public override void WriteByte(byte value)
diff --git a/crypto/src/crypto/io/DigestStream.cs b/crypto/src/crypto/io/DigestStream.cs
index 7508acb0f..0773c5215 100644
--- a/crypto/src/crypto/io/DigestStream.cs
+++ b/crypto/src/crypto/io/DigestStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
@@ -27,20 +27,11 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public IDigest WriteDigest => m_writeDigest;
 
-        public override bool CanRead
-        {
-            get { return m_stream.CanRead; }
-        }
+        public override bool CanRead => m_stream.CanRead;
 
-        public override bool CanSeek
-        {
-            get { return false; }
-        }
+        public override bool CanSeek => false;
 
-        public override bool CanWrite
-        {
-            get { return m_stream.CanWrite; }
-        }
+        public override bool CanWrite => m_stream.CanWrite;
 
 #if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void CopyTo(Stream destination, int bufferSize)
@@ -115,51 +106,84 @@ namespace Org.BouncyCastle.Crypto.IO
             return b;
         }
 
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            throw new NotSupportedException();
-        }
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 
-        public override void SetLength(long length)
-        {
-            throw new NotSupportedException();
-        }
+        public override void SetLength(long length) => throw new NotSupportedException();
 
         public override void Write(byte[] buffer, int offset, int count)
         {
+            if (m_writeDigest != null)
+            {
+                Streams.ValidateBufferArguments(buffer, offset, count);
+
+                if (count > 0)
+                {
+                    m_writeDigest.BlockUpdate(buffer, offset, count);
+                }
+            }
+
             m_stream.Write(buffer, offset, count);
+        }
 
-            if (m_writeDigest != null && count > 0)
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (m_writeDigest != null)
             {
-                m_writeDigest.BlockUpdate(buffer, offset, count);
+                Streams.ValidateBufferArguments(buffer, offset, count);
+
+                if (count > 0)
+                {
+                    if (cancellationToken.IsCancellationRequested)
+                        return Task.FromCanceled(cancellationToken);
+
+                    m_writeDigest.BlockUpdate(buffer, offset, count);
+                }
             }
+
+            return m_stream.WriteAsync(buffer, offset, count, cancellationToken);
         }
+#endif
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void Write(ReadOnlySpan<byte> buffer)
         {
-            m_stream.Write(buffer);
-
-            if (m_writeDigest != null && !buffer.IsEmpty)
+            if (m_writeDigest != null)
             {
-                m_writeDigest.BlockUpdate(buffer);
+                if (!buffer.IsEmpty)
+                {
+                    m_writeDigest.BlockUpdate(buffer);
+                }
             }
+
+            m_stream.Write(buffer);
         }
 
         public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
         {
-            return Streams.WriteAsync(WriteDestination, buffer, cancellationToken);
+            if (m_writeDigest != null)
+            {
+                if (!buffer.IsEmpty)
+                {
+                    if (cancellationToken.IsCancellationRequested)
+                        return ValueTask.FromCanceled(cancellationToken);
+
+                    m_writeDigest.BlockUpdate(buffer.Span);
+                }
+            }
+
+            return m_stream.WriteAsync(buffer, cancellationToken);
         }
 #endif
 
         public override void WriteByte(byte value)
         {
-            m_stream.WriteByte(value);
-
             if (m_writeDigest != null)
             {
                 m_writeDigest.Update(value);
             }
+
+            m_stream.WriteByte(value);
         }
 
         protected override void Dispose(bool disposing)
@@ -172,6 +196,5 @@ namespace Org.BouncyCastle.Crypto.IO
         }
 
         private Stream ReadSource => m_readDigest == null ? m_stream : this;
-        private Stream WriteDestination => m_writeDigest == null ? m_stream : this;
     }
 }
diff --git a/crypto/src/crypto/io/MacSink.cs b/crypto/src/crypto/io/MacSink.cs
index cc5d93b37..f98a9dbe9 100644
--- a/crypto/src/crypto/io/MacSink.cs
+++ b/crypto/src/crypto/io/MacSink.cs
@@ -1,4 +1,8 @@
 using System;
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
+using System.Threading.Tasks;
+#endif
 
 using Org.BouncyCastle.Utilities.IO;
 
@@ -11,7 +15,7 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public MacSink(IMac mac)
         {
-            m_mac = mac;
+            m_mac = mac ?? throw new ArgumentNullException(nameof(mac));
         }
 
         public IMac Mac => m_mac;
@@ -26,6 +30,13 @@ namespace Org.BouncyCastle.Crypto.IO
             }
         }
 
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            return Streams.WriteAsyncDirect(this, buffer, offset, count, cancellationToken);
+        }
+#endif
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void Write(ReadOnlySpan<byte> buffer)
         {
@@ -34,6 +45,11 @@ namespace Org.BouncyCastle.Crypto.IO
                 m_mac.BlockUpdate(buffer);
             }
         }
+
+        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            return Streams.WriteAsyncDirect(this, buffer, cancellationToken);
+        }
 #endif
 
         public override void WriteByte(byte value)
diff --git a/crypto/src/crypto/io/MacStream.cs b/crypto/src/crypto/io/MacStream.cs
index 5e1c00f40..d896e5e27 100644
--- a/crypto/src/crypto/io/MacStream.cs
+++ b/crypto/src/crypto/io/MacStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
@@ -27,20 +27,11 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public IMac WriteMac => m_writeMac;
 
-        public override bool CanRead
-        {
-            get { return m_stream.CanRead; }
-        }
+        public override bool CanRead => m_stream.CanRead;
 
-        public override bool CanSeek
-        {
-            get { return false; }
-        }
+        public override bool CanSeek => false;
 
-        public override bool CanWrite
-        {
-            get { return m_stream.CanWrite; }
-        }
+        public override bool CanWrite => m_stream.CanWrite;
 
 #if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void CopyTo(Stream destination, int bufferSize)
@@ -115,51 +106,84 @@ namespace Org.BouncyCastle.Crypto.IO
             return b;
         }
 
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            throw new NotSupportedException();
-        }
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 
-        public override void SetLength(long length)
-        {
-            throw new NotSupportedException();
-        }
+        public override void SetLength(long length) => throw new NotSupportedException();
 
         public override void Write(byte[] buffer, int offset, int count)
         {
+            if (m_writeMac != null)
+            {
+                Streams.ValidateBufferArguments(buffer, offset, count);
+
+                if (count > 0)
+                {
+                    m_writeMac.BlockUpdate(buffer, offset, count);
+                }
+            }
+
             m_stream.Write(buffer, offset, count);
+        }
 
-            if (m_writeMac != null && count > 0)
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (m_writeMac != null)
             {
-                m_writeMac.BlockUpdate(buffer, offset, count);
+                Streams.ValidateBufferArguments(buffer, offset, count);
+
+                if (count > 0)
+                {
+                    if (cancellationToken.IsCancellationRequested)
+                        return Task.FromCanceled(cancellationToken);
+
+                    m_writeMac.BlockUpdate(buffer, offset, count);
+                }
             }
+
+            return m_stream.WriteAsync(buffer, offset, count, cancellationToken);
         }
+#endif
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void Write(ReadOnlySpan<byte> buffer)
         {
-            m_stream.Write(buffer);
-
-            if (m_writeMac != null && !buffer.IsEmpty)
+            if (m_writeMac != null)
             {
-                m_writeMac.BlockUpdate(buffer);
+                if (!buffer.IsEmpty)
+                {
+                    m_writeMac.BlockUpdate(buffer);
+                }
             }
+
+            m_stream.Write(buffer);
         }
 
         public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
         {
-            return Streams.WriteAsync(WriteDestination, buffer, cancellationToken);
+            if (m_writeMac != null)
+            {
+                if (!buffer.IsEmpty)
+                {
+                    if (cancellationToken.IsCancellationRequested)
+                        return ValueTask.FromCanceled(cancellationToken);
+
+                    m_writeMac.BlockUpdate(buffer.Span);
+                }
+            }
+
+            return m_stream.WriteAsync(buffer, cancellationToken);
         }
 #endif
 
         public override void WriteByte(byte value)
         {
-            m_stream.WriteByte(value);
-
             if (m_writeMac != null)
             {
                 m_writeMac.Update(value);
             }
+
+            m_stream.WriteByte(value);
         }
 
         protected override void Dispose(bool disposing)
@@ -172,6 +196,5 @@ namespace Org.BouncyCastle.Crypto.IO
         }
 
         private Stream ReadSource => m_readMac == null ? m_stream : this;
-        private Stream WriteDestination => m_writeMac == null ? m_stream : this;
     }
 }
diff --git a/crypto/src/crypto/io/SignerSink.cs b/crypto/src/crypto/io/SignerSink.cs
index aaae59966..575b9763b 100644
--- a/crypto/src/crypto/io/SignerSink.cs
+++ b/crypto/src/crypto/io/SignerSink.cs
@@ -1,4 +1,8 @@
 using System;
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
+using System.Threading.Tasks;
+#endif
 
 using Org.BouncyCastle.Utilities.IO;
 
@@ -11,7 +15,7 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public SignerSink(ISigner signer)
 		{
-            m_signer = signer;
+            m_signer = signer ?? throw new ArgumentNullException(nameof(signer));
 		}
 
 		public ISigner Signer => m_signer;
@@ -26,6 +30,13 @@ namespace Org.BouncyCastle.Crypto.IO
 			}
 		}
 
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            return Streams.WriteAsyncDirect(this, buffer, offset, count, cancellationToken);
+        }
+#endif
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
 		public override void Write(ReadOnlySpan<byte> buffer)
 		{
@@ -34,6 +45,11 @@ namespace Org.BouncyCastle.Crypto.IO
 				m_signer.BlockUpdate(buffer);
 			}
 		}
+
+        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            return Streams.WriteAsyncDirect(this, buffer, cancellationToken);
+        }
 #endif
 
 		public override void WriteByte(byte value)
diff --git a/crypto/src/crypto/io/SignerStream.cs b/crypto/src/crypto/io/SignerStream.cs
index 24bfd305e..ce8ddbab0 100644
--- a/crypto/src/crypto/io/SignerStream.cs
+++ b/crypto/src/crypto/io/SignerStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
@@ -27,20 +27,11 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public ISigner WriteSigner => m_writeSigner;
 
-        public override bool CanRead
-        {
-            get { return m_stream.CanRead; }
-        }
+        public override bool CanRead => m_stream.CanRead;
 
-        public override bool CanSeek
-        {
-            get { return false; }
-        }
+        public override bool CanSeek => false;
 
-        public override bool CanWrite
-        {
-            get { return m_stream.CanWrite; }
-        }
+        public override bool CanWrite => m_stream.CanWrite;
 
 #if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void CopyTo(Stream destination, int bufferSize)
@@ -115,51 +106,84 @@ namespace Org.BouncyCastle.Crypto.IO
             return b;
         }
 
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            throw new NotSupportedException();
-        }
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 
-        public override void SetLength(long length)
-        {
-            throw new NotSupportedException();
-        }
+        public override void SetLength(long length) => throw new NotSupportedException();
 
         public override void Write(byte[] buffer, int offset, int count)
         {
+            if (m_writeSigner != null)
+            {
+                Streams.ValidateBufferArguments(buffer, offset, count);
+
+                if (count > 0)
+                {
+                    m_writeSigner.BlockUpdate(buffer, offset, count);
+                }
+            }
+
             m_stream.Write(buffer, offset, count);
+        }
 
-            if (m_writeSigner != null && count > 0)
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (m_writeSigner != null)
             {
-                m_writeSigner.BlockUpdate(buffer, offset, count);
+                Streams.ValidateBufferArguments(buffer, offset, count);
+
+                if (count > 0)
+                {
+                    if (cancellationToken.IsCancellationRequested)
+                        return Task.FromCanceled(cancellationToken);
+
+                    m_writeSigner.BlockUpdate(buffer, offset, count);
+                }
             }
+
+            return m_stream.WriteAsync(buffer, offset, count, cancellationToken);
         }
+#endif
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override void Write(ReadOnlySpan<byte> buffer)
         {
-            m_stream.Write(buffer);
-
-            if (m_writeSigner != null && !buffer.IsEmpty)
+            if (m_writeSigner != null)
             {
-                m_writeSigner.BlockUpdate(buffer);
+                if (!buffer.IsEmpty)
+                {
+                    m_writeSigner.BlockUpdate(buffer);
+                }
             }
+
+            m_stream.Write(buffer);
         }
 
         public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
         {
-            return Streams.WriteAsync(WriteDestination, buffer, cancellationToken);
+            if (m_writeSigner != null)
+            {
+                if (!buffer.IsEmpty)
+                {
+                    if (cancellationToken.IsCancellationRequested)
+                        return ValueTask.FromCanceled(cancellationToken);
+
+                    m_writeSigner.BlockUpdate(buffer.Span);
+                }
+            }
+
+            return m_stream.WriteAsync(buffer, cancellationToken);
         }
 #endif
 
         public override void WriteByte(byte value)
         {
-            m_stream.WriteByte(value);
-
             if (m_writeSigner != null)
             {
                 m_writeSigner.Update(value);
             }
+
+            m_stream.WriteByte(value);
         }
 
         protected override void Dispose(bool disposing)
@@ -172,6 +196,5 @@ namespace Org.BouncyCastle.Crypto.IO
         }
 
         private Stream ReadSource => m_readSigner == null ? m_stream : this;
-        private Stream WriteDestination => m_writeSigner == null ? m_stream : this;
     }
 }
diff --git a/crypto/src/tls/TlsStream.cs b/crypto/src/tls/TlsStream.cs
index e6afd1869..af2ab3e11 100644
--- a/crypto/src/tls/TlsStream.cs
+++ b/crypto/src/tls/TlsStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
diff --git a/crypto/src/util/io/BaseInputStream.cs b/crypto/src/util/io/BaseInputStream.cs
index 438583c75..4551f2ef7 100644
--- a/crypto/src/util/io/BaseInputStream.cs
+++ b/crypto/src/util/io/BaseInputStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
@@ -102,5 +102,7 @@ namespace Org.BouncyCastle.Utilities.IO
             throw new NotSupportedException();
         }
 #endif
+
+        // TODO[api] WriteByte
     }
 }
diff --git a/crypto/src/util/io/BaseOutputStream.cs b/crypto/src/util/io/BaseOutputStream.cs
index 72234b817..9c8ddc811 100644
--- a/crypto/src/util/io/BaseOutputStream.cs
+++ b/crypto/src/util/io/BaseOutputStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
@@ -43,6 +43,7 @@ namespace Org.BouncyCastle.Utilities.IO
             throw new NotSupportedException();
         }
 #endif
+        // TODO[api] ReadByte
         public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
         public sealed override void SetLength(long value) { throw new NotSupportedException(); }
 
diff --git a/crypto/src/util/io/FilterStream.cs b/crypto/src/util/io/FilterStream.cs
index 8fb6d9df4..80518c6d1 100644
--- a/crypto/src/util/io/FilterStream.cs
+++ b/crypto/src/util/io/FilterStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
diff --git a/crypto/src/util/io/PushbackStream.cs b/crypto/src/util/io/PushbackStream.cs
index 452019805..739972b4e 100644
--- a/crypto/src/util/io/PushbackStream.cs
+++ b/crypto/src/util/io/PushbackStream.cs
@@ -1,7 +1,7 @@
 using System;
 using System.IO;
-using System.Threading;
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
diff --git a/crypto/src/util/io/Streams.cs b/crypto/src/util/io/Streams.cs
index a1a2ea5d1..1c5210907 100644
--- a/crypto/src/util/io/Streams.cs
+++ b/crypto/src/util/io/Streams.cs
@@ -1,8 +1,10 @@
 using System;
 using System.IO;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
 using System.Runtime.InteropServices;
-using System.Threading;
+#endif
 #if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+using System.Threading;
 using System.Threading.Tasks;
 #endif
 
@@ -156,10 +158,10 @@ namespace Org.BouncyCastle.Utilities.IO
 
             byte[] sharedBuffer = new byte[buffer.Length];
 			var readTask = source.ReadAsync(sharedBuffer, 0, buffer.Length, cancellationToken);
-            return FinishReadAsync(readTask, sharedBuffer, buffer);
+            return ReadAsyncCompletion(readTask, sharedBuffer, buffer);
         }
 
-        private static async ValueTask<int> FinishReadAsync(Task<int> readTask, byte[] localBuffer,
+        internal static async ValueTask<int> ReadAsyncCompletion(Task<int> readTask, byte[] localBuffer,
 			Memory<byte> localDestination)
         {
             try
@@ -170,7 +172,7 @@ namespace Org.BouncyCastle.Utilities.IO
             }
             finally
             {
-                Array.Fill<byte>(localBuffer, 0x00);
+                Array.Clear(localBuffer, 0, localBuffer.Length);
             }
         }
 #endif
@@ -220,6 +222,30 @@ namespace Org.BouncyCastle.Utilities.IO
 				throw new ArgumentOutOfRangeException("count");
 		}
 
+#if NETCOREAPP1_0_OR_GREATER || NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER
+        internal static async Task WriteAsyncCompletion(Task writeTask, byte[] localBuffer)
+        {
+            try
+            {
+                await writeTask.ConfigureAwait(false);
+            }
+            finally
+            {
+                Array.Clear(localBuffer, 0, localBuffer.Length);
+            }
+        }
+
+        internal static Task WriteAsyncDirect(Stream destination, byte[] buffer, int offset, int count,
+            CancellationToken cancellationToken)
+        {
+            if (cancellationToken.IsCancellationRequested)
+                return Task.FromCanceled(cancellationToken);
+
+            destination.Write(buffer, offset, count);
+            return Task.CompletedTask;
+        }
+#endif
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public static ValueTask WriteAsync(Stream destination, ReadOnlyMemory<byte> buffer,
             CancellationToken cancellationToken = default)
@@ -232,10 +258,10 @@ namespace Org.BouncyCastle.Utilities.IO
 
             byte[] sharedBuffer = buffer.ToArray();
             var writeTask = destination.WriteAsync(sharedBuffer, 0, buffer.Length, cancellationToken);
-            return new ValueTask(FinishWriteAsync(writeTask, sharedBuffer));
+            return new ValueTask(WriteAsyncCompletion(writeTask, sharedBuffer));
         }
 
-        private static async Task FinishWriteAsync(Task writeTask, byte[] localBuffer)
+        internal static async ValueTask WriteAsyncCompletion(ValueTask writeTask, byte[] localBuffer)
         {
             try
             {
@@ -243,9 +269,19 @@ namespace Org.BouncyCastle.Utilities.IO
             }
             finally
             {
-                Array.Fill<byte>(localBuffer, 0x00);
+                Array.Clear(localBuffer, 0, localBuffer.Length);
             }
         }
+
+        internal static ValueTask WriteAsyncDirect(Stream destination, ReadOnlyMemory<byte> buffer,
+            CancellationToken cancellationToken = default)
+        {
+            if (cancellationToken.IsCancellationRequested)
+                return ValueTask.FromCanceled(cancellationToken);
+
+            destination.Write(buffer.Span);
+            return ValueTask.CompletedTask;
+        }
 #endif
 
         /// <exception cref="IOException"></exception>