RecordStream.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  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 Best.HTTP.Shared.PlatformSupport.Memory;
  7. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
  8. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  9. namespace Best.HTTP.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. Best.HTTP.Shared.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. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  211. WriteRecord(contentType, plaintext.AsSpan(plaintextOffset, plaintextLength));
  212. #else
  213. // Never send anything until a valid ClientHello has been received
  214. if (m_writeVersion == null)
  215. return;
  216. /*
  217. * RFC 5246 6.2.1 The length should not exceed 2^14.
  218. */
  219. CheckLength(plaintextLength, m_plaintextLimit, AlertDescription.internal_error);
  220. /*
  221. * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
  222. * or ChangeCipherSpec content types.
  223. */
  224. if (plaintextLength < 1 && contentType != ContentType.application_data)
  225. throw new TlsFatalAlert(AlertDescription.internal_error);
  226. long seqNo = m_writeSeqNo.NextValue(AlertDescription.internal_error);
  227. ProtocolVersion recordVersion = m_writeVersion;
  228. TlsEncodeResult encoded = m_writeCipher.EncodePlaintext(seqNo, contentType, recordVersion,
  229. RecordFormat.FragmentOffset, plaintext, plaintextOffset, plaintextLength);
  230. int ciphertextLength = encoded.len - RecordFormat.FragmentOffset;
  231. TlsUtilities.CheckUint16(ciphertextLength);
  232. TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + RecordFormat.TypeOffset);
  233. TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + RecordFormat.VersionOffset);
  234. TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + RecordFormat.LengthOffset);
  235. // TODO[tls-port] Can we support interrupted IO on .NET?
  236. try
  237. {
  238. m_output.Write(encoded.buf, encoded.off, encoded.len);
  239. }
  240. //catch (InterruptedIOException e)
  241. //{
  242. // throw new TlsFatalAlert(AlertDescription.internal_error, e);
  243. //}
  244. finally
  245. {
  246. if (encoded.fromBufferPool)
  247. BufferPool.Release(encoded.buf);
  248. }
  249. m_output.Flush();
  250. #endif
  251. }
  252. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  253. /// <exception cref="IOException"/>
  254. internal void WriteRecord(short contentType, ReadOnlySpan<byte> plaintext)
  255. {
  256. // Never send anything until a valid ClientHello has been received
  257. if (m_writeVersion == null)
  258. return;
  259. /*
  260. * RFC 5246 6.2.1 The length should not exceed 2^14.
  261. */
  262. CheckLength(plaintext.Length, m_plaintextLimit, AlertDescription.internal_error);
  263. /*
  264. * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
  265. * or ChangeCipherSpec content types.
  266. */
  267. if (plaintext.Length < 1 && contentType != ContentType.application_data)
  268. throw new TlsFatalAlert(AlertDescription.internal_error);
  269. long seqNo=m_writeSeqNo.NextValue(AlertDescription.internal_error);
  270. ProtocolVersion recordVersion = m_writeVersion;
  271. TlsEncodeResult encoded = m_writeCipher.EncodePlaintext(seqNo, contentType, recordVersion,
  272. RecordFormat.FragmentOffset, plaintext);
  273. int ciphertextLength = encoded.len - RecordFormat.FragmentOffset;
  274. TlsUtilities.CheckUint16(ciphertextLength);
  275. TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + RecordFormat.TypeOffset);
  276. TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + RecordFormat.VersionOffset);
  277. TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + RecordFormat.LengthOffset);
  278. // TODO[tls-port] Can we support interrupted IO on .NET?
  279. //try
  280. //{
  281. m_output.Write(encoded.buf, encoded.off, encoded.len);
  282. //}
  283. //catch (InterruptedIOException e)
  284. //{
  285. // throw new TlsFatalAlert(AlertDescription.internal_error, e);
  286. //}
  287. m_output.Flush();
  288. }
  289. #endif
  290. /// <exception cref="IOException"/>
  291. internal void Close()
  292. {
  293. m_inputRecord.Reset();
  294. IOException io = null;
  295. try
  296. {
  297. m_input.Dispose();
  298. }
  299. catch (IOException e)
  300. {
  301. io = e;
  302. }
  303. try
  304. {
  305. m_output.Dispose();
  306. }
  307. catch (IOException e)
  308. {
  309. if (io == null)
  310. {
  311. io = e;
  312. }
  313. else
  314. {
  315. // TODO[tls] Available from JDK 7
  316. //io.addSuppressed(e);
  317. }
  318. }
  319. if (io != null)
  320. throw io;
  321. }
  322. /// <exception cref="IOException"/>
  323. private void CheckChangeCipherSpec(byte[] buf, int off, int len)
  324. {
  325. if (1 != len || (byte)ChangeCipherSpec.change_cipher_spec != buf[off])
  326. {
  327. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  328. "Malformed " + ContentType.GetText(ContentType.change_cipher_spec));
  329. }
  330. }
  331. /// <exception cref="IOException"/>
  332. private short CheckRecordType(byte[] buf, int off)
  333. {
  334. short recordType = TlsUtilities.ReadUint8(buf, off);
  335. if (null != m_readCipherDeferred && recordType == ContentType.application_data)
  336. {
  337. this.m_readCipher = m_readCipherDeferred;
  338. this.m_readCipherDeferred = null;
  339. this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(m_plaintextLimit);
  340. m_readSeqNo.Reset();
  341. }
  342. else if (m_readCipher.UsesOpaqueRecordType)
  343. {
  344. if (ContentType.application_data != recordType)
  345. {
  346. if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
  347. {
  348. // See RFC 8446 D.4.
  349. }
  350. else
  351. {
  352. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  353. "Opaque " + ContentType.GetText(recordType));
  354. }
  355. }
  356. }
  357. else
  358. {
  359. switch (recordType)
  360. {
  361. case ContentType.application_data:
  362. {
  363. if (!m_handler.IsApplicationDataReady)
  364. {
  365. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  366. "Not ready for " + ContentType.GetText(ContentType.application_data));
  367. }
  368. break;
  369. }
  370. case ContentType.alert:
  371. case ContentType.change_cipher_spec:
  372. case ContentType.handshake:
  373. // case ContentType.heartbeat:
  374. break;
  375. default:
  376. throw new TlsFatalAlert(AlertDescription.unexpected_message,
  377. "Unsupported " + ContentType.GetText(recordType));
  378. }
  379. }
  380. return recordType;
  381. }
  382. /// <exception cref="IOException"/>
  383. private static void CheckLength(int length, int limit, short alertDescription)
  384. {
  385. if (length > limit)
  386. throw new TlsFatalAlert(alertDescription);
  387. }
  388. private sealed class Record
  389. {
  390. private readonly byte[] m_header = new byte[RecordFormat.FragmentOffset];
  391. internal volatile byte[] m_buf;
  392. internal volatile int m_pos;
  393. internal Record()
  394. {
  395. this.m_buf = m_header;
  396. this.m_pos = 0;
  397. }
  398. /// <exception cref="IOException"/>
  399. internal void FillTo(Stream input, int length)
  400. {
  401. while (m_pos < length)
  402. {
  403. // TODO[tls-port] Can we support interrupted IO on .NET?
  404. //try
  405. //{
  406. int numRead = input.Read(m_buf, m_pos, length - m_pos);
  407. if (numRead < 1)
  408. break;
  409. m_pos += numRead;
  410. //}
  411. //catch (InterruptedIOException e)
  412. //{
  413. // /*
  414. // * Although modifying the bytesTransferred doesn't seem ideal, it's the simplest
  415. // * way to make sure we don't break client code that depends on the exact type,
  416. // * e.g. in Apache's httpcomponents-core-4.4.9, BHttpConnectionBase.isStale
  417. // * depends on the exception type being SocketTimeoutException (or a subclass).
  418. // *
  419. // * We can set to 0 here because the only relevant callstack (via
  420. // * TlsProtocol.readApplicationData) only ever processes one non-empty record (so
  421. // * interruption after partial output cannot occur).
  422. // */
  423. // m_pos += e.bytesTransferred;
  424. // e.bytesTransferred = 0;
  425. // throw e;
  426. //}
  427. }
  428. }
  429. /// <exception cref="IOException"/>
  430. internal void ReadFragment(Stream input, int fragmentLength)
  431. {
  432. int recordLength = RecordFormat.FragmentOffset + fragmentLength;
  433. Resize(recordLength);
  434. FillTo(input, recordLength);
  435. if (m_pos < recordLength)
  436. throw new EndOfStreamException();
  437. }
  438. /// <exception cref="IOException"/>
  439. internal bool ReadHeader(Stream input)
  440. {
  441. FillTo(input, RecordFormat.FragmentOffset);
  442. if (m_pos == 0)
  443. return false;
  444. if (m_pos < RecordFormat.FragmentOffset)
  445. throw new EndOfStreamException();
  446. return true;
  447. }
  448. internal void Reset()
  449. {
  450. if (m_buf != m_header)
  451. BufferPool.Release(m_buf);
  452. m_buf = m_header;
  453. m_pos = 0;
  454. }
  455. private void Resize(int length)
  456. {
  457. if (m_buf.Length < length)
  458. {
  459. //byte[] tmp = new byte[length];
  460. //Array.Copy(m_buf, 0, tmp, 0, m_pos);
  461. //m_buf = tmp;
  462. byte[] tmp = BufferPool.Get(length, true);
  463. Array.Copy(m_buf, 0, tmp, 0, m_pos);
  464. if (m_buf != m_header)
  465. BufferPool.Release(m_buf);
  466. m_buf = tmp;
  467. }
  468. }
  469. }
  470. private sealed class SequenceNumber
  471. {
  472. private long m_value = 0L;
  473. private bool m_exhausted = false;
  474. internal long CurrentValue
  475. {
  476. get { lock (this) return m_value; }
  477. }
  478. /// <exception cref="TlsFatalAlert"/>
  479. internal long NextValue(short alertDescription)
  480. {
  481. lock (this)
  482. {
  483. if (m_exhausted)
  484. throw new TlsFatalAlert(alertDescription, "Sequence numbers exhausted");
  485. long result = m_value;
  486. if (++m_value == 0L)
  487. {
  488. this.m_exhausted = true;
  489. }
  490. return result;
  491. }
  492. }
  493. internal void Reset()
  494. {
  495. lock (this)
  496. {
  497. this.m_value = 0L;
  498. this.m_exhausted = false;
  499. }
  500. }
  501. }
  502. }
  503. }
  504. #pragma warning restore
  505. #endif