123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
- #pragma warning disable
- using System;
- using System.Diagnostics;
- using System.IO;
- using BestHTTP.PlatformSupport.Memory;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
- namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Tls
- {
- /// <summary>An implementation of the TLS 1.0/1.1/1.2 record layer.</summary>
- internal sealed class RecordStream
- {
- private const int DefaultPlaintextLimit = (1 << 14);
- private readonly Record m_inputRecord = new Record();
- private readonly SequenceNumber m_readSeqNo = new SequenceNumber(), m_writeSeqNo = new SequenceNumber();
- private readonly TlsProtocol m_handler;
- private readonly Stream m_input;
- private readonly Stream m_output;
- private TlsCipher m_pendingCipher = null;
- private TlsCipher m_readCipher = TlsNullNullCipher.Instance;
- private TlsCipher m_readCipherDeferred = null;
- private TlsCipher m_writeCipher = TlsNullNullCipher.Instance;
- private ProtocolVersion m_writeVersion = null;
- private int m_plaintextLimit = DefaultPlaintextLimit;
- private int m_ciphertextLimit = DefaultPlaintextLimit;
- private bool m_ignoreChangeCipherSpec = false;
- internal RecordStream(TlsProtocol handler, Stream input, Stream output)
- {
- this.m_handler = handler;
- this.m_input = input;
- this.m_output = output;
- }
- internal int PlaintextLimit
- {
- get { return m_plaintextLimit; }
- }
- internal void SetPlaintextLimit(int plaintextLimit)
- {
- this.m_plaintextLimit = plaintextLimit;
- this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(plaintextLimit);
- }
- internal void SetWriteVersion(ProtocolVersion writeVersion)
- {
- this.m_writeVersion = writeVersion;
- }
- internal void SetIgnoreChangeCipherSpec(bool ignoreChangeCipherSpec)
- {
- this.m_ignoreChangeCipherSpec = ignoreChangeCipherSpec;
- }
- internal void SetPendingCipher(TlsCipher tlsCipher)
- {
- this.m_pendingCipher = tlsCipher;
- }
- /// <exception cref="IOException"/>
- internal void NotifyChangeCipherSpecReceived()
- {
- if (m_pendingCipher == null)
- throw new TlsFatalAlert(AlertDescription.unexpected_message, "No pending cipher");
- EnablePendingCipherRead(false);
- }
- /// <exception cref="IOException"/>
- internal void EnablePendingCipherRead(bool deferred)
- {
- if (m_pendingCipher == null)
- throw new TlsFatalAlert(AlertDescription.internal_error);
- if (m_readCipherDeferred != null)
- throw new TlsFatalAlert(AlertDescription.internal_error);
- if (deferred)
- {
- this.m_readCipherDeferred = m_pendingCipher;
- }
- else
- {
- this.m_readCipher = m_pendingCipher;
- this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(m_plaintextLimit);
- m_readSeqNo.Reset();
- }
- }
- /// <exception cref="IOException"/>
- internal void EnablePendingCipherWrite()
- {
- if (m_pendingCipher == null)
- throw new TlsFatalAlert(AlertDescription.internal_error);
- this.m_writeCipher = this.m_pendingCipher;
- m_writeSeqNo.Reset();
- }
- /// <exception cref="IOException"/>
- internal void FinaliseHandshake()
- {
- if (m_readCipher != m_pendingCipher || m_writeCipher != m_pendingCipher)
- throw new TlsFatalAlert(AlertDescription.handshake_failure);
- this.m_pendingCipher = null;
- }
- internal bool NeedsKeyUpdate()
- {
- return m_writeSeqNo.CurrentValue >= (1L << 20);
- }
- /// <exception cref="IOException"/>
- internal void NotifyKeyUpdateReceived()
- {
- m_readCipher.RekeyDecoder();
- m_readSeqNo.Reset();
- }
- /// <exception cref="IOException"/>
- internal void NotifyKeyUpdateSent()
- {
- m_writeCipher.RekeyEncoder();
- m_writeSeqNo.Reset();
- }
- /// <exception cref="IOException"/>
- internal RecordPreview PreviewRecordHeader(byte[] recordHeader)
- {
- short recordType = CheckRecordType(recordHeader, RecordFormat.TypeOffset);
- //ProtocolVersion recordVersion = TlsUtilities.ReadVersion(recordHeader, RecordFormat.VersionOffset);
- int length = TlsUtilities.ReadUint16(recordHeader, RecordFormat.LengthOffset);
- CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
- int recordSize = RecordFormat.FragmentOffset + length;
- int applicationDataLimit = 0;
- // NOTE: For TLS 1.3, this only MIGHT be application data
- if (ContentType.application_data == recordType && m_handler.IsApplicationDataReady)
- {
- applicationDataLimit = System.Math.Max(0, System.Math.Min(m_plaintextLimit,
- m_readCipher.GetPlaintextLimit(length)));
- }
- return new RecordPreview(recordSize, applicationDataLimit);
- }
- internal RecordPreview PreviewOutputRecord(int contentLength)
- {
- int contentLimit = System.Math.Max(0, System.Math.Min(m_plaintextLimit, contentLength));
- int recordSize = PreviewOutputRecordSize(contentLimit);
- return new RecordPreview(recordSize, contentLimit);
- }
- internal int PreviewOutputRecordSize(int contentLength)
- {
- Debug.Assert(contentLength <= m_plaintextLimit);
- return RecordFormat.FragmentOffset + m_writeCipher.GetCiphertextEncodeLimit(contentLength, m_plaintextLimit);
- }
- /// <exception cref="IOException"/>
- internal bool ReadFullRecord(byte[] input, int inputOff, int inputLen)
- {
- if (inputLen < RecordFormat.FragmentOffset)
- return false;
- int length = TlsUtilities.ReadUint16(input, inputOff + RecordFormat.LengthOffset);
- if (inputLen != (RecordFormat.FragmentOffset + length))
- return false;
- short recordType = CheckRecordType(input, inputOff + RecordFormat.TypeOffset);
- ProtocolVersion recordVersion = TlsUtilities.ReadVersion(input, inputOff + RecordFormat.VersionOffset);
- CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
- if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
- {
- CheckChangeCipherSpec(input, inputOff + RecordFormat.FragmentOffset, length);
- return true;
- }
- TlsDecodeResult decoded = DecodeAndVerify(recordType, recordVersion, input,
- inputOff + RecordFormat.FragmentOffset, length);
- m_handler.ProcessRecord(decoded.contentType, decoded.buf, decoded.off, decoded.len);
- return true;
- }
- /// <exception cref="IOException"/>
- internal bool ReadRecord()
- {
- if (!m_inputRecord.ReadHeader(m_input))
- return false;
- short recordType = CheckRecordType(m_inputRecord.m_buf, RecordFormat.TypeOffset);
- ProtocolVersion recordVersion = TlsUtilities.ReadVersion(m_inputRecord.m_buf, RecordFormat.VersionOffset);
- int length = TlsUtilities.ReadUint16(m_inputRecord.m_buf, RecordFormat.LengthOffset);
- CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
- m_inputRecord.ReadFragment(m_input, length);
- TlsDecodeResult decoded;
- try
- {
- if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
- {
- CheckChangeCipherSpec(m_inputRecord.m_buf, RecordFormat.FragmentOffset, length);
- return true;
- }
- decoded = DecodeAndVerify(recordType, recordVersion, m_inputRecord.m_buf, RecordFormat.FragmentOffset,
- length);
- // with aead/cha-cha in and out buffer is the same
- m_handler.ProcessRecord(decoded.contentType, decoded.buf, decoded.off, decoded.len);
- if (decoded.fromBufferPool)
- BestHTTP.PlatformSupport.Memory.BufferPool.Release(decoded.buf);
- }
- finally
- {
- m_inputRecord.Reset();
- }
- return true;
- }
- /// <exception cref="IOException"/>
- internal TlsDecodeResult DecodeAndVerify(short recordType, ProtocolVersion recordVersion, byte[] ciphertext,
- int off, int len)
- {
- long seqNo = m_readSeqNo.NextValue(AlertDescription.unexpected_message);
- TlsDecodeResult decoded = m_readCipher.DecodeCiphertext(seqNo, recordType, recordVersion, ciphertext, off,
- len);
- CheckLength(decoded.len, m_plaintextLimit, AlertDescription.record_overflow);
- /*
- * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
- * or ChangeCipherSpec content types.
- */
- if (decoded.len < 1 && decoded.contentType != ContentType.application_data)
- throw new TlsFatalAlert(AlertDescription.illegal_parameter);
- return decoded;
- }
- /// <exception cref="IOException"/>
- internal void WriteRecord(short contentType, byte[] plaintext, int plaintextOffset, int plaintextLength)
- {
- // 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(plaintextLength, 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 (plaintextLength < 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, plaintextOffset, plaintextLength);
- 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();
- }
- /// <exception cref="IOException"/>
- internal void Close()
- {
- m_inputRecord.Reset();
- IOException io = null;
- try
- {
- BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(m_input);
- }
- catch (IOException e)
- {
- io = e;
- }
- try
- {
- BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(m_output);
- }
- catch (IOException e)
- {
- if (io == null)
- {
- io = e;
- }
- else
- {
- // TODO[tls] Available from JDK 7
- //io.addSuppressed(e);
- }
- }
- if (io != null)
- throw io;
- }
- /// <exception cref="IOException"/>
- private void CheckChangeCipherSpec(byte[] buf, int off, int len)
- {
- if (1 != len || (byte)ChangeCipherSpec.change_cipher_spec != buf[off])
- {
- throw new TlsFatalAlert(AlertDescription.unexpected_message,
- "Malformed " + ContentType.GetText(ContentType.change_cipher_spec));
- }
- }
- /// <exception cref="IOException"/>
- private short CheckRecordType(byte[] buf, int off)
- {
- short recordType = TlsUtilities.ReadUint8(buf, off);
- if (null != m_readCipherDeferred && recordType == ContentType.application_data)
- {
- this.m_readCipher = m_readCipherDeferred;
- this.m_readCipherDeferred = null;
- this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(m_plaintextLimit);
- m_readSeqNo.Reset();
- }
- else if (m_readCipher.UsesOpaqueRecordType)
- {
- if (ContentType.application_data != recordType)
- {
- if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
- {
- // See RFC 8446 D.4.
- }
- else
- {
- throw new TlsFatalAlert(AlertDescription.unexpected_message,
- "Opaque " + ContentType.GetText(recordType));
- }
- }
- }
- else
- {
- switch (recordType)
- {
- case ContentType.application_data:
- {
- if (!m_handler.IsApplicationDataReady)
- {
- throw new TlsFatalAlert(AlertDescription.unexpected_message,
- "Not ready for " + ContentType.GetText(ContentType.application_data));
- }
- break;
- }
- case ContentType.alert:
- case ContentType.change_cipher_spec:
- case ContentType.handshake:
- // case ContentType.heartbeat:
- break;
- default:
- throw new TlsFatalAlert(AlertDescription.unexpected_message,
- "Unsupported " + ContentType.GetText(recordType));
- }
- }
- return recordType;
- }
- /// <exception cref="IOException"/>
- private static void CheckLength(int length, int limit, short alertDescription)
- {
- if (length > limit)
- throw new TlsFatalAlert(alertDescription);
- }
- private sealed class Record
- {
- private readonly byte[] m_header = new byte[RecordFormat.FragmentOffset];
- internal volatile byte[] m_buf;
- internal volatile int m_pos;
- internal Record()
- {
- this.m_buf = m_header;
- this.m_pos = 0;
- }
- /// <exception cref="IOException"/>
- internal void FillTo(Stream input, int length)
- {
- while (m_pos < length)
- {
- // TODO[tls-port] Can we support interrupted IO on .NET?
- //try
- //{
- int numRead = input.Read(m_buf, m_pos, length - m_pos);
- if (numRead < 1)
- break;
- m_pos += numRead;
- //}
- //catch (InterruptedIOException e)
- //{
- // /*
- // * Although modifying the bytesTransferred doesn't seem ideal, it's the simplest
- // * way to make sure we don't break client code that depends on the exact type,
- // * e.g. in Apache's httpcomponents-core-4.4.9, BHttpConnectionBase.isStale
- // * depends on the exception type being SocketTimeoutException (or a subclass).
- // *
- // * We can set to 0 here because the only relevant callstack (via
- // * TlsProtocol.readApplicationData) only ever processes one non-empty record (so
- // * interruption after partial output cannot occur).
- // */
- // m_pos += e.bytesTransferred;
- // e.bytesTransferred = 0;
- // throw e;
- //}
- }
- }
- /// <exception cref="IOException"/>
- internal void ReadFragment(Stream input, int fragmentLength)
- {
- int recordLength = RecordFormat.FragmentOffset + fragmentLength;
- Resize(recordLength);
- FillTo(input, recordLength);
- if (m_pos < recordLength)
- throw new EndOfStreamException();
- }
- /// <exception cref="IOException"/>
- internal bool ReadHeader(Stream input)
- {
- FillTo(input, RecordFormat.FragmentOffset);
- if (m_pos == 0)
- return false;
- if (m_pos < RecordFormat.FragmentOffset)
- throw new EndOfStreamException();
- return true;
- }
- internal void Reset()
- {
- if (m_buf != m_header)
- BufferPool.Release(m_buf);
- m_buf = m_header;
- m_pos = 0;
- }
- private void Resize(int length)
- {
- if (m_buf.Length < length)
- {
- //byte[] tmp = new byte[length];
- //Array.Copy(m_buf, 0, tmp, 0, m_pos);
- //m_buf = tmp;
- byte[] tmp = BufferPool.Get(length, true);
- Array.Copy(m_buf, 0, tmp, 0, m_pos);
- m_buf = tmp;
- }
- }
- }
- private sealed class SequenceNumber
- {
- private long m_value = 0L;
- private bool m_exhausted = false;
- internal long CurrentValue
- {
- get { lock (this) return m_value; }
- }
- /// <exception cref="TlsFatalAlert"/>
- internal long NextValue(short alertDescription)
- {
- lock (this)
- {
- if (m_exhausted)
- throw new TlsFatalAlert(alertDescription, "Sequence numbers exhausted");
- long result = m_value;
- if (++m_value == 0L)
- {
- this.m_exhausted = true;
- }
- return result;
- }
- }
- internal void Reset()
- {
- lock (this)
- {
- this.m_value = 0L;
- this.m_exhausted = false;
- }
- }
- }
- }
- }
- #pragma warning restore
- #endif
|