RecordStream.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using BestHTTP.PlatformSupport.Memory;
  7. using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
  8. using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  9. namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Tls
  10. {
  11. /// <summary>An implementation of the TLS 1.0/1.1/1.2 record layer.</summary>
  12. internal sealed class RecordStream
  13. {
  14. private const int DefaultPlaintextLimit = (1 << 14);
  15. private readonly Record m_inputRecord = new Record();
  16. private readonly SequenceNumber m_readSeqNo = new SequenceNumber(), m_writeSeqNo = new SequenceNumber();
  17. private readonly TlsProtocol m_handler;
  18. private readonly Stream m_input;
  19. private readonly Stream m_output;
  20. private TlsCipher m_pendingCipher = null;
  21. private TlsCipher m_readCipher = TlsNullNullCipher.Instance;
  22. private TlsCipher m_readCipherDeferred = null;
  23. private TlsCipher m_writeCipher = TlsNullNullCipher.Instance;
  24. private ProtocolVersion m_writeVersion = null;
  25. private int m_plaintextLimit = DefaultPlaintextLimit;
  26. private int m_ciphertextLimit = DefaultPlaintextLimit;
  27. private bool m_ignoreChangeCipherSpec = false;
  28. internal RecordStream(TlsProtocol handler, Stream input, Stream output)
  29. {
  30. this.m_handler = handler;
  31. this.m_input = input;
  32. this.m_output = output;
  33. }
  34. internal int PlaintextLimit
  35. {
  36. get { return m_plaintextLimit; }
  37. }
  38. internal void SetPlaintextLimit(int plaintextLimit)
  39. {
  40. this.m_plaintextLimit = plaintextLimit;
  41. this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(plaintextLimit);
  42. }
  43. internal void SetWriteVersion(ProtocolVersion writeVersion)
  44. {
  45. this.m_writeVersion = writeVersion;
  46. }
  47. internal void SetIgnoreChangeCipherSpec(bool ignoreChangeCipherSpec)
  48. {
  49. this.m_ignoreChangeCipherSpec = ignoreChangeCipherSpec;
  50. }
  51. internal void SetPendingCipher(TlsCipher tlsCipher)
  52. {
  53. this.m_pendingCipher = tlsCipher;
  54. }
  55. /// <exception cref="IOException"/>
  56. internal void NotifyChangeCipherSpecReceived()
  57. {
  58. if (m_pendingCipher == null)
  59. throw new TlsFatalAlert(AlertDescription.unexpected_message, "No pending cipher");
  60. EnablePendingCipherRead(false);
  61. }
  62. /// <exception cref="IOException"/>
  63. internal void EnablePendingCipherRead(bool deferred)
  64. {
  65. if (m_pendingCipher == null)
  66. throw new TlsFatalAlert(AlertDescription.internal_error);
  67. if (m_readCipherDeferred != null)
  68. throw new TlsFatalAlert(AlertDescription.internal_error);
  69. if (deferred)
  70. {
  71. this.m_readCipherDeferred = m_pendingCipher;
  72. }
  73. else
  74. {
  75. this.m_readCipher = m_pendingCipher;
  76. this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(m_plaintextLimit);
  77. m_readSeqNo.Reset();
  78. }
  79. }
  80. /// <exception cref="IOException"/>
  81. internal void EnablePendingCipherWrite()
  82. {
  83. if (m_pendingCipher == null)
  84. throw new TlsFatalAlert(AlertDescription.internal_error);
  85. this.m_writeCipher = this.m_pendingCipher;
  86. m_writeSeqNo.Reset();
  87. }
  88. /// <exception cref="IOException"/>
  89. internal void FinaliseHandshake()
  90. {
  91. if (m_readCipher != m_pendingCipher || m_writeCipher != m_pendingCipher)
  92. throw new TlsFatalAlert(AlertDescription.handshake_failure);
  93. this.m_pendingCipher = null;
  94. }
  95. internal bool NeedsKeyUpdate()
  96. {
  97. return m_writeSeqNo.CurrentValue >= (1L << 20);
  98. }
  99. /// <exception cref="IOException"/>
  100. internal void NotifyKeyUpdateReceived()
  101. {
  102. m_readCipher.RekeyDecoder();
  103. m_readSeqNo.Reset();
  104. }
  105. /// <exception cref="IOException"/>
  106. internal void NotifyKeyUpdateSent()
  107. {
  108. m_writeCipher.RekeyEncoder();
  109. m_writeSeqNo.Reset();
  110. }
  111. /// <exception cref="IOException"/>
  112. internal RecordPreview PreviewRecordHeader(byte[] recordHeader)
  113. {
  114. short recordType = CheckRecordType(recordHeader, RecordFormat.TypeOffset);
  115. //ProtocolVersion recordVersion = TlsUtilities.ReadVersion(recordHeader, RecordFormat.VersionOffset);
  116. int length = TlsUtilities.ReadUint16(recordHeader, RecordFormat.LengthOffset);
  117. CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
  118. int recordSize = RecordFormat.FragmentOffset + length;
  119. int applicationDataLimit = 0;
  120. // NOTE: For TLS 1.3, this only MIGHT be application data
  121. if (ContentType.application_data == recordType && m_handler.IsApplicationDataReady)
  122. {
  123. applicationDataLimit = System.Math.Max(0, System.Math.Min(m_plaintextLimit,
  124. m_readCipher.GetPlaintextLimit(length)));
  125. }
  126. return new RecordPreview(recordSize, applicationDataLimit);
  127. }
  128. internal RecordPreview PreviewOutputRecord(int contentLength)
  129. {
  130. int contentLimit = System.Math.Max(0, System.Math.Min(m_plaintextLimit, contentLength));
  131. int recordSize = PreviewOutputRecordSize(contentLimit);
  132. return new RecordPreview(recordSize, contentLimit);
  133. }
  134. internal int PreviewOutputRecordSize(int contentLength)
  135. {
  136. Debug.Assert(contentLength <= m_plaintextLimit);
  137. return RecordFormat.FragmentOffset + m_writeCipher.GetCiphertextEncodeLimit(contentLength, m_plaintextLimit);
  138. }
  139. /// <exception cref="IOException"/>
  140. internal bool ReadFullRecord(byte[] input, int inputOff, int inputLen)
  141. {
  142. if (inputLen < RecordFormat.FragmentOffset)
  143. return false;
  144. int length = TlsUtilities.ReadUint16(input, inputOff + RecordFormat.LengthOffset);
  145. if (inputLen != (RecordFormat.FragmentOffset + length))
  146. return false;
  147. short recordType = CheckRecordType(input, inputOff + RecordFormat.TypeOffset);
  148. ProtocolVersion recordVersion = TlsUtilities.ReadVersion(input, inputOff + RecordFormat.VersionOffset);
  149. CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
  150. if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
  151. {
  152. CheckChangeCipherSpec(input, inputOff + RecordFormat.FragmentOffset, length);
  153. return true;
  154. }
  155. TlsDecodeResult decoded = DecodeAndVerify(recordType, recordVersion, input,
  156. inputOff + RecordFormat.FragmentOffset, length);
  157. m_handler.ProcessRecord(decoded.contentType, decoded.buf, decoded.off, decoded.len);
  158. return true;
  159. }
  160. /// <exception cref="IOException"/>
  161. internal bool ReadRecord()
  162. {
  163. if (!m_inputRecord.ReadHeader(m_input))
  164. return false;
  165. short recordType = CheckRecordType(m_inputRecord.m_buf, RecordFormat.TypeOffset);
  166. ProtocolVersion recordVersion = TlsUtilities.ReadVersion(m_inputRecord.m_buf, RecordFormat.VersionOffset);
  167. int length = TlsUtilities.ReadUint16(m_inputRecord.m_buf, RecordFormat.LengthOffset);
  168. CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
  169. m_inputRecord.ReadFragment(m_input, length);
  170. TlsDecodeResult decoded;
  171. try
  172. {
  173. if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
  174. {
  175. CheckChangeCipherSpec(m_inputRecord.m_buf, RecordFormat.FragmentOffset, length);
  176. return true;
  177. }
  178. decoded = DecodeAndVerify(recordType, recordVersion, m_inputRecord.m_buf, RecordFormat.FragmentOffset,
  179. length);
  180. // with aead/cha-cha in and out buffer is the same
  181. m_handler.ProcessRecord(decoded.contentType, decoded.buf, decoded.off, decoded.len);
  182. if (decoded.fromBufferPool)
  183. BestHTTP.PlatformSupport.Memory.BufferPool.Release(decoded.buf);
  184. }
  185. finally
  186. {
  187. m_inputRecord.Reset();
  188. }
  189. return true;
  190. }
  191. /// <exception cref="IOException"/>
  192. internal TlsDecodeResult DecodeAndVerify(short recordType, ProtocolVersion recordVersion, byte[] ciphertext,
  193. int off, int len)
  194. {
  195. long seqNo = m_readSeqNo.NextValue(AlertDescription.unexpected_message);
  196. TlsDecodeResult decoded = m_readCipher.DecodeCiphertext(seqNo, recordType, recordVersion, ciphertext, off,
  197. len);
  198. CheckLength(decoded.len, m_plaintextLimit, AlertDescription.record_overflow);
  199. /*
  200. * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
  201. * or ChangeCipherSpec content types.
  202. */
  203. if (decoded.len < 1 && decoded.contentType != ContentType.application_data)
  204. throw new TlsFatalAlert(AlertDescription.illegal_parameter);
  205. return decoded;
  206. }
  207. /// <exception cref="IOException"/>
  208. internal void WriteRecord(short contentType, byte[] plaintext, int plaintextOffset, int plaintextLength)
  209. {
  210. // Never send anything until a valid ClientHello has been received
  211. if (m_writeVersion == null)
  212. return;
  213. /*
  214. * RFC 5246 6.2.1 The length should not exceed 2^14.
  215. */
  216. CheckLength(plaintextLength, m_plaintextLimit, AlertDescription.internal_error);
  217. /*
  218. * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
  219. * or ChangeCipherSpec content types.
  220. */
  221. if (plaintextLength < 1 && contentType != ContentType.application_data)
  222. throw new TlsFatalAlert(AlertDescription.internal_error);
  223. long seqNo = m_writeSeqNo.NextValue(AlertDescription.internal_error);
  224. ProtocolVersion recordVersion = m_writeVersion;
  225. TlsEncodeResult encoded = m_writeCipher.EncodePlaintext(seqNo, contentType, recordVersion,
  226. RecordFormat.FragmentOffset, plaintext, plaintextOffset, plaintextLength);
  227. int ciphertextLength = encoded.len - RecordFormat.FragmentOffset;
  228. TlsUtilities.CheckUint16(ciphertextLength);
  229. TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + RecordFormat.TypeOffset);
  230. TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + RecordFormat.VersionOffset);
  231. TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + RecordFormat.LengthOffset);
  232. // TODO[tls-port] Can we support interrupted IO on .NET?
  233. //try
  234. //{
  235. m_output.Write(encoded.buf, encoded.off, encoded.len);
  236. //}
  237. //catch (InterruptedIOException e)
  238. //{
  239. // throw new TlsFatalAlert(AlertDescription.internal_error, e);
  240. //}
  241. m_output.Flush();
  242. }
  243. /// <exception cref="IOException"/>
  244. internal void Close()
  245. {
  246. m_inputRecord.Reset();
  247. IOException io = null;
  248. try
  249. {
  250. BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(m_input);
  251. }
  252. catch (IOException e)
  253. {
  254. io = e;
  255. }
  256. try
  257. {
  258. BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(m_output);
  259. }
  260. catch (IOException e)
  261. {
  262. if (io == null)
  263. {
  264. io = e;
  265. }
  266. else
  267. {
  268. // TODO[tls] Available from JDK 7
  269. //io.addSuppressed(e);
  270. }
  271. }
  272. if (io != null)
  273. throw io;
  274. }
  275. /// <exception cref="IOException"/>
  276. private void CheckChangeCipherSpec(byte[] buf, int off, int len)
  277. {
  278. if (1 != len || (byte)ChangeCipherSpec.change_cipher_spec != buf[off])
  279. {
  280. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  281. "Malformed " + ContentType.GetText(ContentType.change_cipher_spec));
  282. }
  283. }
  284. /// <exception cref="IOException"/>
  285. private short CheckRecordType(byte[] buf, int off)
  286. {
  287. short recordType = TlsUtilities.ReadUint8(buf, off);
  288. if (null != m_readCipherDeferred && recordType == ContentType.application_data)
  289. {
  290. this.m_readCipher = m_readCipherDeferred;
  291. this.m_readCipherDeferred = null;
  292. this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(m_plaintextLimit);
  293. m_readSeqNo.Reset();
  294. }
  295. else if (m_readCipher.UsesOpaqueRecordType)
  296. {
  297. if (ContentType.application_data != recordType)
  298. {
  299. if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
  300. {
  301. // See RFC 8446 D.4.
  302. }
  303. else
  304. {
  305. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  306. "Opaque " + ContentType.GetText(recordType));
  307. }
  308. }
  309. }
  310. else
  311. {
  312. switch (recordType)
  313. {
  314. case ContentType.application_data:
  315. {
  316. if (!m_handler.IsApplicationDataReady)
  317. {
  318. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  319. "Not ready for " + ContentType.GetText(ContentType.application_data));
  320. }
  321. break;
  322. }
  323. case ContentType.alert:
  324. case ContentType.change_cipher_spec:
  325. case ContentType.handshake:
  326. // case ContentType.heartbeat:
  327. break;
  328. default:
  329. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  330. "Unsupported " + ContentType.GetText(recordType));
  331. }
  332. }
  333. return recordType;
  334. }
  335. /// <exception cref="IOException"/>
  336. private static void CheckLength(int length, int limit, short alertDescription)
  337. {
  338. if (length > limit)
  339. throw new TlsFatalAlert(alertDescription);
  340. }
  341. private sealed class Record
  342. {
  343. private readonly byte[] m_header = new byte[RecordFormat.FragmentOffset];
  344. internal volatile byte[] m_buf;
  345. internal volatile int m_pos;
  346. internal Record()
  347. {
  348. this.m_buf = m_header;
  349. this.m_pos = 0;
  350. }
  351. /// <exception cref="IOException"/>
  352. internal void FillTo(Stream input, int length)
  353. {
  354. while (m_pos < length)
  355. {
  356. // TODO[tls-port] Can we support interrupted IO on .NET?
  357. //try
  358. //{
  359. int numRead = input.Read(m_buf, m_pos, length - m_pos);
  360. if (numRead < 1)
  361. break;
  362. m_pos += numRead;
  363. //}
  364. //catch (InterruptedIOException e)
  365. //{
  366. // /*
  367. // * Although modifying the bytesTransferred doesn't seem ideal, it's the simplest
  368. // * way to make sure we don't break client code that depends on the exact type,
  369. // * e.g. in Apache's httpcomponents-core-4.4.9, BHttpConnectionBase.isStale
  370. // * depends on the exception type being SocketTimeoutException (or a subclass).
  371. // *
  372. // * We can set to 0 here because the only relevant callstack (via
  373. // * TlsProtocol.readApplicationData) only ever processes one non-empty record (so
  374. // * interruption after partial output cannot occur).
  375. // */
  376. // m_pos += e.bytesTransferred;
  377. // e.bytesTransferred = 0;
  378. // throw e;
  379. //}
  380. }
  381. }
  382. /// <exception cref="IOException"/>
  383. internal void ReadFragment(Stream input, int fragmentLength)
  384. {
  385. int recordLength = RecordFormat.FragmentOffset + fragmentLength;
  386. Resize(recordLength);
  387. FillTo(input, recordLength);
  388. if (m_pos < recordLength)
  389. throw new EndOfStreamException();
  390. }
  391. /// <exception cref="IOException"/>
  392. internal bool ReadHeader(Stream input)
  393. {
  394. FillTo(input, RecordFormat.FragmentOffset);
  395. if (m_pos == 0)
  396. return false;
  397. if (m_pos < RecordFormat.FragmentOffset)
  398. throw new EndOfStreamException();
  399. return true;
  400. }
  401. internal void Reset()
  402. {
  403. if (m_buf != m_header)
  404. BufferPool.Release(m_buf);
  405. m_buf = m_header;
  406. m_pos = 0;
  407. }
  408. private void Resize(int length)
  409. {
  410. if (m_buf.Length < length)
  411. {
  412. //byte[] tmp = new byte[length];
  413. //Array.Copy(m_buf, 0, tmp, 0, m_pos);
  414. //m_buf = tmp;
  415. byte[] tmp = BufferPool.Get(length, true);
  416. Array.Copy(m_buf, 0, tmp, 0, m_pos);
  417. m_buf = tmp;
  418. }
  419. }
  420. }
  421. private sealed class SequenceNumber
  422. {
  423. private long m_value = 0L;
  424. private bool m_exhausted = false;
  425. internal long CurrentValue
  426. {
  427. get { lock (this) return m_value; }
  428. }
  429. /// <exception cref="TlsFatalAlert"/>
  430. internal long NextValue(short alertDescription)
  431. {
  432. lock (this)
  433. {
  434. if (m_exhausted)
  435. throw new TlsFatalAlert(alertDescription, "Sequence numbers exhausted");
  436. long result = m_value;
  437. if (++m_value == 0L)
  438. {
  439. this.m_exhausted = true;
  440. }
  441. return result;
  442. }
  443. }
  444. internal void Reset()
  445. {
  446. lock (this)
  447. {
  448. this.m_value = 0L;
  449. this.m_exhausted = false;
  450. }
  451. }
  452. }
  453. }
  454. }
  455. #pragma warning restore
  456. #endif