up
This commit is contained in:
344
Assets/BestHTTP/SecureProtocol/crypto/tls/RecordStream.cs
Normal file
344
Assets/BestHTTP/SecureProtocol/crypto/tls/RecordStream.cs
Normal file
@@ -0,0 +1,344 @@
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace Org.BouncyCastle.Crypto.Tls
|
||||
{
|
||||
/// <summary>An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3.</summary>
|
||||
internal class RecordStream
|
||||
{
|
||||
private const int DEFAULT_PLAINTEXT_LIMIT = (1 << 14);
|
||||
|
||||
internal const int TLS_HEADER_SIZE = 5;
|
||||
internal const int TLS_HEADER_TYPE_OFFSET = 0;
|
||||
internal const int TLS_HEADER_VERSION_OFFSET = 1;
|
||||
internal const int TLS_HEADER_LENGTH_OFFSET = 3;
|
||||
|
||||
private TlsProtocol mHandler;
|
||||
private Stream mInput;
|
||||
private Stream mOutput;
|
||||
private TlsCompression mPendingCompression = null, mReadCompression = null, mWriteCompression = null;
|
||||
private TlsCipher mPendingCipher = null, mReadCipher = null, mWriteCipher = null;
|
||||
private long mReadSeqNo = 0, mWriteSeqNo = 0;
|
||||
private MemoryStream mBuffer = new MemoryStream();
|
||||
|
||||
private TlsHandshakeHash mHandshakeHash = null;
|
||||
|
||||
private ProtocolVersion mReadVersion = null, mWriteVersion = null;
|
||||
private bool mRestrictReadVersion = true;
|
||||
|
||||
private int mPlaintextLimit, mCompressedLimit, mCiphertextLimit;
|
||||
|
||||
internal RecordStream(TlsProtocol handler, Stream input, Stream output)
|
||||
{
|
||||
this.mHandler = handler;
|
||||
this.mInput = input;
|
||||
this.mOutput = output;
|
||||
this.mReadCompression = new TlsNullCompression();
|
||||
this.mWriteCompression = this.mReadCompression;
|
||||
}
|
||||
|
||||
internal virtual void Init(TlsContext context)
|
||||
{
|
||||
this.mReadCipher = new TlsNullCipher(context);
|
||||
this.mWriteCipher = this.mReadCipher;
|
||||
this.mHandshakeHash = new DeferredHash();
|
||||
this.mHandshakeHash.Init(context);
|
||||
|
||||
SetPlaintextLimit(DEFAULT_PLAINTEXT_LIMIT);
|
||||
}
|
||||
|
||||
internal virtual int GetPlaintextLimit()
|
||||
{
|
||||
return mPlaintextLimit;
|
||||
}
|
||||
|
||||
internal virtual void SetPlaintextLimit(int plaintextLimit)
|
||||
{
|
||||
this.mPlaintextLimit = plaintextLimit;
|
||||
this.mCompressedLimit = this.mPlaintextLimit + 1024;
|
||||
this.mCiphertextLimit = this.mCompressedLimit + 1024;
|
||||
}
|
||||
|
||||
internal virtual ProtocolVersion ReadVersion
|
||||
{
|
||||
get { return mReadVersion; }
|
||||
set { this.mReadVersion = value; }
|
||||
}
|
||||
|
||||
internal virtual void SetWriteVersion(ProtocolVersion writeVersion)
|
||||
{
|
||||
this.mWriteVersion = writeVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 5246 E.1. "Earlier versions of the TLS specification were not fully clear on what the
|
||||
* record layer version number (TLSPlaintext.version) should contain when sending ClientHello
|
||||
* (i.e., before it is known which version of the protocol will be employed). Thus, TLS servers
|
||||
* compliant with this specification MUST accept any value {03,XX} as the record layer version
|
||||
* number for ClientHello."
|
||||
*/
|
||||
internal virtual void SetRestrictReadVersion(bool enabled)
|
||||
{
|
||||
this.mRestrictReadVersion = enabled;
|
||||
}
|
||||
|
||||
internal virtual void SetPendingConnectionState(TlsCompression tlsCompression, TlsCipher tlsCipher)
|
||||
{
|
||||
this.mPendingCompression = tlsCompression;
|
||||
this.mPendingCipher = tlsCipher;
|
||||
}
|
||||
|
||||
internal virtual void SentWriteCipherSpec()
|
||||
{
|
||||
if (mPendingCompression == null || mPendingCipher == null)
|
||||
throw new TlsFatalAlert(AlertDescription.handshake_failure);
|
||||
|
||||
this.mWriteCompression = this.mPendingCompression;
|
||||
this.mWriteCipher = this.mPendingCipher;
|
||||
this.mWriteSeqNo = 0;
|
||||
}
|
||||
|
||||
internal virtual void ReceivedReadCipherSpec()
|
||||
{
|
||||
if (mPendingCompression == null || mPendingCipher == null)
|
||||
throw new TlsFatalAlert(AlertDescription.handshake_failure);
|
||||
|
||||
this.mReadCompression = this.mPendingCompression;
|
||||
this.mReadCipher = this.mPendingCipher;
|
||||
this.mReadSeqNo = 0;
|
||||
}
|
||||
|
||||
internal virtual void FinaliseHandshake()
|
||||
{
|
||||
if (mReadCompression != mPendingCompression || mWriteCompression != mPendingCompression
|
||||
|| mReadCipher != mPendingCipher || mWriteCipher != mPendingCipher)
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.handshake_failure);
|
||||
}
|
||||
this.mPendingCompression = null;
|
||||
this.mPendingCipher = null;
|
||||
}
|
||||
|
||||
internal virtual bool ReadRecord()
|
||||
{
|
||||
byte[] recordHeader = TlsUtilities.ReadAllOrNothing(TLS_HEADER_SIZE, mInput);
|
||||
if (recordHeader == null)
|
||||
return false;
|
||||
|
||||
byte type = TlsUtilities.ReadUint8(recordHeader, TLS_HEADER_TYPE_OFFSET);
|
||||
|
||||
/*
|
||||
* RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an
|
||||
* unexpected_message alert.
|
||||
*/
|
||||
CheckType(type, AlertDescription.unexpected_message);
|
||||
|
||||
if (!mRestrictReadVersion)
|
||||
{
|
||||
int version = TlsUtilities.ReadVersionRaw(recordHeader, TLS_HEADER_VERSION_OFFSET);
|
||||
if ((version & 0xffffff00) != 0x0300)
|
||||
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProtocolVersion version = TlsUtilities.ReadVersion(recordHeader, TLS_HEADER_VERSION_OFFSET);
|
||||
if (mReadVersion == null)
|
||||
{
|
||||
mReadVersion = version;
|
||||
}
|
||||
else if (!version.Equals(mReadVersion))
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
|
||||
}
|
||||
}
|
||||
|
||||
int length = TlsUtilities.ReadUint16(recordHeader, TLS_HEADER_LENGTH_OFFSET);
|
||||
byte[] plaintext = DecodeAndVerify(type, mInput, length);
|
||||
mHandler.ProcessRecord(type, plaintext, 0, plaintext.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal virtual byte[] DecodeAndVerify(byte type, Stream input, int len)
|
||||
{
|
||||
CheckLength(len, mCiphertextLimit, AlertDescription.record_overflow);
|
||||
|
||||
byte[] buf = TlsUtilities.ReadFully(len, input);
|
||||
byte[] decoded = mReadCipher.DecodeCiphertext(mReadSeqNo++, type, buf, 0, buf.Length);
|
||||
|
||||
CheckLength(decoded.Length, mCompressedLimit, AlertDescription.record_overflow);
|
||||
|
||||
/*
|
||||
* TODO RFC5264 6.2.2. Implementation note: Decompression functions are responsible for
|
||||
* ensuring that messages cannot cause internal buffer overflows.
|
||||
*/
|
||||
Stream cOut = mReadCompression.Decompress(mBuffer);
|
||||
if (cOut != mBuffer)
|
||||
{
|
||||
cOut.Write(decoded, 0, decoded.Length);
|
||||
cOut.Flush();
|
||||
decoded = GetBufferContents();
|
||||
}
|
||||
|
||||
/*
|
||||
* RFC 5264 6.2.2. If the decompression function encounters a TLSCompressed.fragment that
|
||||
* would decompress to a length in excess of 2^14 bytes, it should report a fatal
|
||||
* decompression failure error.
|
||||
*/
|
||||
CheckLength(decoded.Length, mPlaintextLimit, AlertDescription.decompression_failure);
|
||||
|
||||
/*
|
||||
* RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
|
||||
* or ChangeCipherSpec content types.
|
||||
*/
|
||||
if (decoded.Length < 1 && type != ContentType.application_data)
|
||||
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
internal virtual void WriteRecord(byte type, byte[] plaintext, int plaintextOffset, int plaintextLength)
|
||||
{
|
||||
// Never send anything until a valid ClientHello has been received
|
||||
if (mWriteVersion == null)
|
||||
return;
|
||||
|
||||
/*
|
||||
* RFC 5264 6. Implementations MUST NOT send record types not defined in this document
|
||||
* unless negotiated by some extension.
|
||||
*/
|
||||
CheckType(type, AlertDescription.internal_error);
|
||||
|
||||
/*
|
||||
* RFC 5264 6.2.1 The length should not exceed 2^14.
|
||||
*/
|
||||
CheckLength(plaintextLength, mPlaintextLimit, AlertDescription.internal_error);
|
||||
|
||||
/*
|
||||
* RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
|
||||
* or ChangeCipherSpec content types.
|
||||
*/
|
||||
if (plaintextLength < 1 && type != ContentType.application_data)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
if (type == ContentType.handshake)
|
||||
{
|
||||
UpdateHandshakeData(plaintext, plaintextOffset, plaintextLength);
|
||||
}
|
||||
|
||||
Stream cOut = mWriteCompression.Compress(mBuffer);
|
||||
|
||||
byte[] ciphertext;
|
||||
if (cOut == mBuffer)
|
||||
{
|
||||
ciphertext = mWriteCipher.EncodePlaintext(mWriteSeqNo++, type, plaintext, plaintextOffset, plaintextLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
cOut.Write(plaintext, plaintextOffset, plaintextLength);
|
||||
cOut.Flush();
|
||||
byte[] compressed = GetBufferContents();
|
||||
|
||||
/*
|
||||
* RFC5264 6.2.2. Compression must be lossless and may not increase the content length
|
||||
* by more than 1024 bytes.
|
||||
*/
|
||||
CheckLength(compressed.Length, plaintextLength + 1024, AlertDescription.internal_error);
|
||||
|
||||
ciphertext = mWriteCipher.EncodePlaintext(mWriteSeqNo++, type, compressed, 0, compressed.Length);
|
||||
}
|
||||
|
||||
/*
|
||||
* RFC 5264 6.2.3. The length may not exceed 2^14 + 2048.
|
||||
*/
|
||||
CheckLength(ciphertext.Length, mCiphertextLimit, AlertDescription.internal_error);
|
||||
|
||||
byte[] record = new byte[ciphertext.Length + TLS_HEADER_SIZE];
|
||||
TlsUtilities.WriteUint8(type, record, TLS_HEADER_TYPE_OFFSET);
|
||||
TlsUtilities.WriteVersion(mWriteVersion, record, TLS_HEADER_VERSION_OFFSET);
|
||||
TlsUtilities.WriteUint16(ciphertext.Length, record, TLS_HEADER_LENGTH_OFFSET);
|
||||
Array.Copy(ciphertext, 0, record, TLS_HEADER_SIZE, ciphertext.Length);
|
||||
mOutput.Write(record, 0, record.Length);
|
||||
mOutput.Flush();
|
||||
}
|
||||
|
||||
internal virtual void NotifyHelloComplete()
|
||||
{
|
||||
this.mHandshakeHash = mHandshakeHash.NotifyPrfDetermined();
|
||||
}
|
||||
|
||||
internal virtual TlsHandshakeHash HandshakeHash
|
||||
{
|
||||
get { return mHandshakeHash; }
|
||||
}
|
||||
|
||||
internal virtual TlsHandshakeHash PrepareToFinish()
|
||||
{
|
||||
TlsHandshakeHash result = mHandshakeHash;
|
||||
this.mHandshakeHash = mHandshakeHash.StopTracking();
|
||||
return result;
|
||||
}
|
||||
|
||||
internal virtual void UpdateHandshakeData(byte[] message, int offset, int len)
|
||||
{
|
||||
mHandshakeHash.BlockUpdate(message, offset, len);
|
||||
}
|
||||
|
||||
internal virtual void SafeClose()
|
||||
{
|
||||
try
|
||||
{
|
||||
Platform.Dispose(mInput);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Platform.Dispose(mOutput);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void Flush()
|
||||
{
|
||||
mOutput.Flush();
|
||||
}
|
||||
|
||||
private byte[] GetBufferContents()
|
||||
{
|
||||
byte[] contents = mBuffer.ToArray();
|
||||
mBuffer.SetLength(0);
|
||||
return contents;
|
||||
}
|
||||
|
||||
private static void CheckType(byte type, byte alertDescription)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ContentType.application_data:
|
||||
case ContentType.alert:
|
||||
case ContentType.change_cipher_spec:
|
||||
case ContentType.handshake:
|
||||
case ContentType.heartbeat:
|
||||
break;
|
||||
default:
|
||||
throw new TlsFatalAlert(alertDescription);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckLength(int length, int limit, byte alertDescription)
|
||||
{
|
||||
if (length > limit)
|
||||
throw new TlsFatalAlert(alertDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user