345 lines
13 KiB
C#
345 lines
13 KiB
C#
#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
|