summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-11-01 21:23:33 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-11-01 21:23:33 +0700
commit944ced70e1e2f889eafe6b169a9c69396ae8cbee (patch)
tree788f0a9a7b6c18885ea9443deec14b6370076a4e
parentMore Span-based Stream methods (diff)
downloadBouncyCastle.NET-ed25519-944ced70e1e2f889eafe6b169a9c69396ae8cbee.tar.xz
Span-based TlsStream methods
-rw-r--r--crypto/src/tls/RecordStream.cs51
-rw-r--r--crypto/src/tls/TlsProtocol.cs141
-rw-r--r--crypto/src/tls/TlsStream.cs14
-rw-r--r--crypto/src/tls/crypto/TlsCipher.cs5
-rw-r--r--crypto/src/tls/crypto/TlsNullNullCipher.cs10
-rw-r--r--crypto/src/tls/crypto/impl/TlsAeadCipher.cs80
-rw-r--r--crypto/src/tls/crypto/impl/TlsBlockCipher.cs76
-rw-r--r--crypto/src/tls/crypto/impl/TlsNullCipher.cs12
-rw-r--r--crypto/src/tls/crypto/impl/TlsSuiteHmac.cs26
-rw-r--r--crypto/src/tls/crypto/impl/TlsSuiteMac.cs4
10 files changed, 419 insertions, 0 deletions
diff --git a/crypto/src/tls/RecordStream.cs b/crypto/src/tls/RecordStream.cs
index a97d34698..a5926d05b 100644
--- a/crypto/src/tls/RecordStream.cs
+++ b/crypto/src/tls/RecordStream.cs
@@ -258,6 +258,9 @@ namespace Org.BouncyCastle.Tls
         /// <exception cref="IOException"/>
         internal void WriteRecord(short contentType, byte[] plaintext, int plaintextOffset, int plaintextLength)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            WriteRecord(contentType, plaintext.AsSpan(plaintextOffset, plaintextLength));
+#else
             // Never send anything until a valid ClientHello has been received
             if (m_writeVersion == null)
                 return;
@@ -298,8 +301,56 @@ namespace Org.BouncyCastle.Tls
             //}
 
             m_output.Flush();
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        internal void WriteRecord(short contentType, ReadOnlySpan<byte> plaintext)
+        {
+            // Never send anything until a valid ClientHello has been received
+            if (m_writeVersion == null)
+                return;
+
+            /*
+             * RFC 5246 6.2.1 The length should not exceed 2^14.
+             */
+            CheckLength(plaintext.Length, m_plaintextLimit, AlertDescription.internal_error);
+
+            /*
+             * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (plaintext.Length < 1 && contentType != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            long seqNo = m_writeSeqNo.NextValue(AlertDescription.internal_error);
+            ProtocolVersion recordVersion = m_writeVersion;
+
+            TlsEncodeResult encoded = m_writeCipher.EncodePlaintext(seqNo, contentType, recordVersion,
+                RecordFormat.FragmentOffset, plaintext);
+
+            int ciphertextLength = encoded.len - RecordFormat.FragmentOffset;
+            TlsUtilities.CheckUint16(ciphertextLength);
+
+            TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + RecordFormat.TypeOffset);
+            TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + RecordFormat.VersionOffset);
+            TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + RecordFormat.LengthOffset);
+
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //try
+            //{
+                m_output.Write(encoded.buf, encoded.off, encoded.len);
+            //}
+            //catch (InterruptedIOException e)
+            //{
+            //    throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            //}
+
+            m_output.Flush();
+        }
+#endif
+
         /// <exception cref="IOException"/>
         internal void Close()
         {
diff --git a/crypto/src/tls/TlsProtocol.cs b/crypto/src/tls/TlsProtocol.cs
index 3461e9b58..437a51447 100644
--- a/crypto/src/tls/TlsProtocol.cs
+++ b/crypto/src/tls/TlsProtocol.cs
@@ -707,6 +707,9 @@ namespace Org.BouncyCastle.Tls
         {
             Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ReadApplicationData(buffer.AsSpan(offset, count));
+#else
             if (!m_appDataReady)
                 throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
 
@@ -733,8 +736,42 @@ namespace Org.BouncyCastle.Tls
                 m_applicationDataQueue.RemoveData(buffer, offset, count, 0);
             }
             return count;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ReadApplicationData(Span<byte> buffer)
+        {
+            if (!m_appDataReady)
+                throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
+
+            while (m_applicationDataQueue.Available < 1)
+            {
+                if (this.m_closed)
+                {
+                    if (this.m_failedWithError)
+                        throw new IOException("Cannot read application data on failed TLS connection");
+
+                    return 0;
+                }
+
+                /*
+                 * NOTE: Only called more than once when empty records are received, so no special
+                 * InterruptedIOException handling is necessary.
+                 */
+                SafeReadRecord();
+            }
+
+            int count = buffer.Length;
+            if (count > 0)
+            {
+                count = System.Math.Min(count, m_applicationDataQueue.Available);
+                m_applicationDataQueue.RemoveData(buffer[..count], 0);
+            }
+            return count;
+        }
+#endif
+
         /// <exception cref="IOException"/>
         protected virtual RecordPreview SafePreviewRecordHeader(byte[] recordHeader)
         {
@@ -850,6 +887,32 @@ namespace Org.BouncyCastle.Tls
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        protected virtual void SafeWriteRecord(short type, ReadOnlySpan<byte> buffer)
+        {
+            try
+            {
+                m_recordStream.WriteRecord(type, buffer);
+            }
+            catch (TlsFatalAlert e)
+            {
+                HandleException(e.AlertDescription, "Failed to write record", e);
+                throw e;
+            }
+            catch (IOException e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to write record", e);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to write record", e);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+#endif
+
         /// <summary>Write some application data.</summary>
         /// <remarks>
         /// Fragmentation is handled internally. Usable in both blocking/non-blocking modes.<br/><br/>
@@ -869,6 +932,9 @@ namespace Org.BouncyCastle.Tls
         {
             Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            WriteApplicationData(buffer.AsSpan(offset, count));
+#else
             if (!m_appDataReady)
                 throw new InvalidOperationException(
                     "Cannot write application data until initial handshake completed.");
@@ -938,7 +1004,82 @@ namespace Org.BouncyCastle.Tls
                     count -= toWrite;
                 }
             }
+#endif
+        }
+
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void WriteApplicationData(ReadOnlySpan<byte> buffer)
+        {
+            if (!m_appDataReady)
+                throw new InvalidOperationException(
+                    "Cannot write application data until initial handshake completed.");
+
+            lock (m_recordWriteLock)
+            {
+                while (!buffer.IsEmpty)
+                {
+                    if (m_closed)
+                        throw new IOException("Cannot write application data on closed/failed TLS connection");
+
+                    /*
+                     * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
+                     * potentially useful as a traffic analysis countermeasure.
+                     * 
+                     * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting.
+                     */
+                    if (m_appDataSplitEnabled)
+                    {
+                        /*
+                         * Protect against known IV attack!
+                         * 
+                         * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
+                         */
+                        switch (m_appDataSplitMode)
+                        {
+                        case ADS_MODE_0_N_FIRSTONLY:
+                        {
+                            this.m_appDataSplitEnabled = false;
+                            SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
+                            break;
+                        }
+                        case ADS_MODE_0_N:
+                        {
+                            SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
+                            break;
+                        }
+                        case ADS_MODE_1_Nsub1:
+                        default:
+                        {
+                            if (buffer.Length > 1)
+                            {
+                                SafeWriteRecord(ContentType.application_data, buffer[..1]);
+                                buffer = buffer[1..];
+                            }
+                            break;
+                        }
+                        }
+                    }
+                    else if (m_keyUpdateEnabled)
+                    {
+                        if (m_keyUpdatePendingSend)
+                        {
+                            Send13KeyUpdate(false);
+                        }
+                        else if (m_recordStream.NeedsKeyUpdate())
+                        {
+                            Send13KeyUpdate(true);
+                        }
+                    }
+
+                    // Fragment data according to the current fragment limit.
+                    int toWrite = System.Math.Min(buffer.Length, m_recordStream.PlaintextLimit);
+                    SafeWriteRecord(ContentType.application_data, buffer[..toWrite]);
+                    buffer = buffer[toWrite..];
+                }
+            }
         }
+#endif
 
         public virtual int AppDataSplitMode
         {
diff --git a/crypto/src/tls/TlsStream.cs b/crypto/src/tls/TlsStream.cs
index 01b990799..5c07da2bf 100644
--- a/crypto/src/tls/TlsStream.cs
+++ b/crypto/src/tls/TlsStream.cs
@@ -58,6 +58,13 @@ namespace Org.BouncyCastle.Tls
             return m_handler.ReadApplicationData(buffer, offset, count);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            return m_handler.ReadApplicationData(buffer);
+        }
+#endif
+
         public override int ReadByte()
         {
             byte[] buf = new byte[1];
@@ -80,6 +87,13 @@ namespace Org.BouncyCastle.Tls
             m_handler.WriteApplicationData(buffer, offset, count);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_handler.WriteApplicationData(buffer);
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             m_handler.WriteApplicationData(new byte[]{ value }, 0, 1);
diff --git a/crypto/src/tls/crypto/TlsCipher.cs b/crypto/src/tls/crypto/TlsCipher.cs
index 4c2147bf7..53a8141fd 100644
--- a/crypto/src/tls/crypto/TlsCipher.cs
+++ b/crypto/src/tls/crypto/TlsCipher.cs
@@ -38,6 +38,11 @@ namespace Org.BouncyCastle.Tls.Crypto
         TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
             int headerAllocation, byte[] plaintext, int offset, int len);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext);
+#endif
+
         /// <summary>Decode the passed in ciphertext using the current bulk cipher.</summary>
         /// <param name="seqNo">sequence number of the message represented by ciphertext.</param>
         /// <param name="recordType">content type used in the record for this message.</param>
diff --git a/crypto/src/tls/crypto/TlsNullNullCipher.cs b/crypto/src/tls/crypto/TlsNullNullCipher.cs
index 082dff358..13fe092f7 100644
--- a/crypto/src/tls/crypto/TlsNullNullCipher.cs
+++ b/crypto/src/tls/crypto/TlsNullNullCipher.cs
@@ -31,6 +31,16 @@ namespace Org.BouncyCastle.Tls.Crypto
             return new TlsEncodeResult(result, 0, result.Length, contentType);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            byte[] result = new byte[headerAllocation + plaintext.Length];
+            plaintext.CopyTo(result.AsSpan(headerAllocation));
+            return new TlsEncodeResult(result, 0, result.Length, contentType);
+        }
+#endif
+
         public TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int offset, int len)
         {
diff --git a/crypto/src/tls/crypto/impl/TlsAeadCipher.cs b/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
index 046e6883f..a53e1e835 100644
--- a/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
+++ b/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
@@ -161,6 +161,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
             int headerAllocation, byte[] plaintext, int plaintextOffset, int plaintextLength)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodePlaintext(seqNo, contentType, recordVersion, headerAllocation,
+                plaintext.AsSpan(plaintextOffset, plaintextLength));
+#else
             byte[] nonce = new byte[m_encryptNonce.Length + m_record_iv_length];
 
             switch (m_nonceMode)
@@ -229,7 +233,83 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             }
 
             return new TlsEncodeResult(output, 0, output.Length, recordType);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            byte[] nonce = new byte[m_encryptNonce.Length + m_record_iv_length];
+
+            switch (m_nonceMode)
+            {
+            case NONCE_RFC5288:
+                Array.Copy(m_encryptNonce, 0, nonce, 0, m_encryptNonce.Length);
+                // RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number.
+                TlsUtilities.WriteUint64(seqNo, nonce, m_encryptNonce.Length);
+                break;
+            case NONCE_RFC7905:
+                TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8);
+                for (int i = 0; i < m_encryptNonce.Length; ++i)
+                {
+                    nonce[i] ^= m_encryptNonce[i];
+                }
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            int extraLength = m_isTlsV13 ? 1 : 0;
+
+            // TODO[tls13] If we support adding padding to TLSInnerPlaintext, this will need review
+            int encryptionLength = m_encryptCipher.GetOutputSize(plaintext.Length + extraLength);
+            int ciphertextLength = m_record_iv_length + encryptionLength;
+
+            byte[] output = new byte[headerAllocation + ciphertextLength];
+            int outputPos = headerAllocation;
+
+            if (m_record_iv_length != 0)
+            {
+                Array.Copy(nonce, nonce.Length - m_record_iv_length, output, outputPos, m_record_iv_length);
+                outputPos += m_record_iv_length;
+            }
+
+            short recordType = m_isTlsV13 ? ContentType.application_data : contentType;
+
+            byte[] additionalData = GetAdditionalData(seqNo, recordType, recordVersion, ciphertextLength,
+                plaintext.Length);
+
+            try
+            {
+                plaintext.CopyTo(output.AsSpan(outputPos));
+                if (m_isTlsV13)
+                {
+                    output[outputPos + plaintext.Length] = (byte)contentType;
+                }
+
+                m_encryptCipher.Init(nonce, m_macSize, additionalData);
+                outputPos += m_encryptCipher.DoFinal(output, outputPos, plaintext.Length + extraLength, output,
+                    outputPos);
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+
+            if (outputPos != output.Length)
+            {
+                // NOTE: The additional data mechanism for AEAD ciphers requires exact output size prediction.
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            return new TlsEncodeResult(output, 0, output.Length, recordType);
         }
+#endif
 
         public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int ciphertextOffset, int ciphertextLength)
diff --git a/crypto/src/tls/crypto/impl/TlsBlockCipher.cs b/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
index ed9d68649..64a73bfea 100644
--- a/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
+++ b/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
@@ -199,6 +199,9 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
             int headerAllocation, byte[] plaintext, int offset, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodePlaintext(seqNo, contentType, recordVersion, headerAllocation, plaintext.AsSpan(offset, len));
+#else
             int blockSize = m_encryptCipher.GetBlockSize();
             int macSize = m_writeMac.Size;
 
@@ -264,7 +267,80 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
                 throw new TlsFatalAlert(AlertDescription.internal_error);
 
             return new TlsEncodeResult(outBuf, 0, outBuf.Length, contentType);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            int blockSize = m_encryptCipher.GetBlockSize();
+            int macSize = m_writeMac.Size;
+
+            int enc_input_length = plaintext.Length;
+            if (!m_encryptThenMac)
+            {
+                enc_input_length += macSize;
+            }
+
+            int padding_length = blockSize - (enc_input_length % blockSize);
+            if (m_useExtraPadding)
+            {
+                // Add a random number of extra blocks worth of padding
+                int maxExtraPadBlocks = (256 - padding_length) / blockSize;
+                int actualExtraPadBlocks = ChooseExtraPadBlocks(maxExtraPadBlocks);
+                padding_length += actualExtraPadBlocks * blockSize;
+            }
+
+            int totalSize = plaintext.Length + macSize + padding_length;
+            if (m_useExplicitIV)
+            {
+                totalSize += blockSize;
+            }
+
+            byte[] outBuf = new byte[headerAllocation + totalSize];
+            int outOff = headerAllocation;
+
+            if (m_useExplicitIV)
+            {
+                // Technically the explicit IV will be the encryption of this nonce
+                byte[] explicitIV = m_cryptoParams.NonceGenerator.GenerateNonce(blockSize);
+                Array.Copy(explicitIV, 0, outBuf, outOff, blockSize);
+                outOff += blockSize;
+            }
+
+            plaintext.CopyTo(outBuf.AsSpan(outOff));
+            outOff += plaintext.Length;
+
+            if (!m_encryptThenMac)
+            {
+                byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext);
+                mac.CopyTo(outBuf.AsSpan(outOff));
+                outOff += mac.Length;
+            }
+
+            byte padByte = (byte)(padding_length - 1);
+            for (int i = 0; i < padding_length; ++i)
+            {
+                outBuf[outOff++] = padByte;
+            }
+
+            m_encryptCipher.DoFinal(outBuf, headerAllocation, outOff - headerAllocation, outBuf, headerAllocation);
+
+            if (m_encryptThenMac)
+            {
+                byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, outBuf, headerAllocation,
+                    outOff - headerAllocation);
+                Array.Copy(mac, 0, outBuf, outOff, mac.Length);
+                outOff += mac.Length;
+            }
+
+            if (outOff != outBuf.Length)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return new TlsEncodeResult(outBuf, 0, outBuf.Length, contentType);
         }
+#endif
 
         public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int offset, int len)
diff --git a/crypto/src/tls/crypto/impl/TlsNullCipher.cs b/crypto/src/tls/crypto/impl/TlsNullCipher.cs
index 5b6b5663a..9bb08110a 100644
--- a/crypto/src/tls/crypto/impl/TlsNullCipher.cs
+++ b/crypto/src/tls/crypto/impl/TlsNullCipher.cs
@@ -81,6 +81,18 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             return new TlsEncodeResult(ciphertext, 0, ciphertext.Length, contentType);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext);
+            byte[] ciphertext = new byte[headerAllocation + plaintext.Length + mac.Length];
+            plaintext.CopyTo(ciphertext.AsSpan(headerAllocation));
+            mac.CopyTo(ciphertext.AsSpan(headerAllocation + plaintext.Length));
+            return new TlsEncodeResult(ciphertext, 0, ciphertext.Length, contentType);
+        }
+#endif
+
         public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int offset, int len)
         {
diff --git a/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs b/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
index 9f43f4382..b4edde760 100644
--- a/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
+++ b/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
@@ -55,6 +55,9 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
 
         public virtual byte[] CalculateMac(long seqNo, short type, byte[] msg, int msgOff, int msgLen)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return CalculateMac(seqNo, type, msg.AsSpan(msgOff, msgLen));
+#else
             ProtocolVersion serverVersion = m_cryptoParams.ServerVersion;
             bool isSsl = serverVersion.IsSsl;
 
@@ -71,8 +74,31 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             m_mac.Update(msg, msgOff, msgLen);
 
             return Truncate(m_mac.CalculateMac());
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual byte[] CalculateMac(long seqNo, short type, ReadOnlySpan<byte> message)
+        {
+            ProtocolVersion serverVersion = m_cryptoParams.ServerVersion;
+            bool isSsl = serverVersion.IsSsl;
+
+            byte[] macHeader = new byte[isSsl ? 11 : 13];
+            TlsUtilities.WriteUint64(seqNo, macHeader, 0);
+            TlsUtilities.WriteUint8(type, macHeader, 8);
+            if (!isSsl)
+            {
+                TlsUtilities.WriteVersion(serverVersion, macHeader, 9);
+            }
+            TlsUtilities.WriteUint16(message.Length, macHeader, macHeader.Length - 2);
+
+            m_mac.Update(macHeader);
+            m_mac.Update(message);
+
+            return Truncate(m_mac.CalculateMac());
+        }
+#endif
+
         public virtual byte[] CalculateMacConstantTime(long seqNo, short type, byte[] msg, int msgOff, int msgLen,
             int fullLength, byte[] dummyData)
         {
diff --git a/crypto/src/tls/crypto/impl/TlsSuiteMac.cs b/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
index 6e4942928..1a28eba81 100644
--- a/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
+++ b/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
@@ -18,6 +18,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         /// <returns>A new byte array containing the MAC value.</returns>
         byte[] CalculateMac(long seqNo, short type, byte[] message, int offset, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        byte[] CalculateMac(long seqNo, short type, ReadOnlySpan<byte> message);
+#endif
+
         /// <summary>Constant time calculation of the MAC for some given data with a given expected length.</summary>
         /// <param name="seqNo">The sequence number of the record.</param>
         /// <param name="type">The content type of the message.</param>