ArmoredInputStream.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Text;
  7. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  8. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities.IO;
  9. namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Bcpg
  10. {
  11. /**
  12. * reader for Base64 armored objects - read the headers and then start returning
  13. * bytes when the data is reached. An IOException is thrown if the CRC check
  14. * is detected and fails.
  15. * <p>
  16. * By default a missing CRC will not cause an exception. To force CRC detection use:
  17. * <pre>
  18. * ArmoredInputStream aIn = ...
  19. *
  20. * aIn.setDetectMissingCRC(true);
  21. * </pre>
  22. * </p>
  23. */
  24. public class ArmoredInputStream
  25. : BaseInputStream
  26. {
  27. /*
  28. * set up the decoding table.
  29. */
  30. private readonly static byte[] decodingTable;
  31. static ArmoredInputStream()
  32. {
  33. decodingTable = new byte[128];
  34. Arrays.Fill(decodingTable, 0xff);
  35. for (int i = 'A'; i <= 'Z'; i++)
  36. {
  37. decodingTable[i] = (byte)(i - 'A');
  38. }
  39. for (int i = 'a'; i <= 'z'; i++)
  40. {
  41. decodingTable[i] = (byte)(i - 'a' + 26);
  42. }
  43. for (int i = '0'; i <= '9'; i++)
  44. {
  45. decodingTable[i] = (byte)(i - '0' + 52);
  46. }
  47. decodingTable['+'] = 62;
  48. decodingTable['/'] = 63;
  49. }
  50. /**
  51. * decode the base 64 encoded input data.
  52. *
  53. * @return the offset the data starts in out.
  54. */
  55. private static int Decode(int in0, int in1, int in2, int in3, int[] result)
  56. {
  57. if (in3 < 0)
  58. throw new EndOfStreamException("unexpected end of file in armored stream.");
  59. int b1, b2, b3, b4;
  60. if (in2 == '=')
  61. {
  62. b1 = decodingTable[in0];
  63. b2 = decodingTable[in1];
  64. if ((b1 | b2) >= 128)
  65. throw new IOException("invalid armor");
  66. result[2] = ((b1 << 2) | (b2 >> 4)) & 0xff;
  67. return 2;
  68. }
  69. else if (in3 == '=')
  70. {
  71. b1 = decodingTable[in0];
  72. b2 = decodingTable[in1];
  73. b3 = decodingTable[in2];
  74. if ((b1 | b2 | b3) >= 128)
  75. throw new IOException("invalid armor");
  76. result[1] = ((b1 << 2) | (b2 >> 4)) & 0xff;
  77. result[2] = ((b2 << 4) | (b3 >> 2)) & 0xff;
  78. return 1;
  79. }
  80. else
  81. {
  82. b1 = decodingTable[in0];
  83. b2 = decodingTable[in1];
  84. b3 = decodingTable[in2];
  85. b4 = decodingTable[in3];
  86. if ((b1 | b2 | b3 | b4) >= 128)
  87. throw new IOException("invalid armor");
  88. result[0] = ((b1 << 2) | (b2 >> 4)) & 0xff;
  89. result[1] = ((b2 << 4) | (b3 >> 2)) & 0xff;
  90. result[2] = ((b3 << 6) | b4) & 0xff;
  91. return 0;
  92. }
  93. }
  94. /*
  95. * Ignore missing CRC checksums.
  96. * https://tests.sequoia-pgp.org/#ASCII_Armor suggests that missing CRC sums do not invalidate the message.
  97. */
  98. private bool detectMissingChecksum = false;
  99. Stream input;
  100. bool start = true;
  101. int[] outBuf = new int[3];
  102. int bufPtr = 3;
  103. Crc24 crc = new Crc24();
  104. bool crcFound = false;
  105. bool hasHeaders = true;
  106. string header = null;
  107. bool newLineFound = false;
  108. bool clearText = false;
  109. bool restart = false;
  110. IList<string> headerList = new List<string>();
  111. int lastC = 0;
  112. bool isEndOfStream;
  113. /**
  114. * Create a stream for reading a PGP armoured message, parsing up to a header
  115. * and then reading the data that follows.
  116. *
  117. * @param input
  118. */
  119. public ArmoredInputStream(Stream input)
  120. : this(input, true)
  121. {
  122. }
  123. /**
  124. * Create an armoured input stream which will assume the data starts
  125. * straight away, or parse for headers first depending on the value of
  126. * hasHeaders.
  127. *
  128. * @param input
  129. * @param hasHeaders true if headers are to be looked for, false otherwise.
  130. */
  131. public ArmoredInputStream(Stream input, bool hasHeaders)
  132. {
  133. this.input = input;
  134. this.hasHeaders = hasHeaders;
  135. if (hasHeaders)
  136. {
  137. ParseHeaders();
  138. }
  139. start = false;
  140. }
  141. private bool ParseHeaders()
  142. {
  143. header = null;
  144. int c;
  145. int last = 0;
  146. bool headerFound = false;
  147. headerList = new List<string>();
  148. //
  149. // if restart we already have a header
  150. //
  151. if (restart)
  152. {
  153. headerFound = true;
  154. }
  155. else
  156. {
  157. while ((c = input.ReadByte()) >= 0)
  158. {
  159. if (c == '-' && (last == 0 || last == '\n' || last == '\r'))
  160. {
  161. headerFound = true;
  162. break;
  163. }
  164. last = c;
  165. }
  166. }
  167. if (headerFound)
  168. {
  169. StringBuilder buf = new StringBuilder("-");
  170. bool eolReached = false;
  171. bool crLf = false;
  172. if (restart) // we've had to look ahead two '-'
  173. {
  174. buf.Append('-');
  175. }
  176. while ((c = input.ReadByte()) >= 0)
  177. {
  178. if (last == '\r' && c == '\n')
  179. {
  180. crLf = true;
  181. }
  182. if (eolReached && (last != '\r' && c == '\n'))
  183. {
  184. break;
  185. }
  186. if (eolReached && c == '\r')
  187. {
  188. break;
  189. }
  190. if (c == '\r' || (last != '\r' && c == '\n'))
  191. {
  192. string line = buf.ToString();
  193. if (line.Trim().Length < 1)
  194. break;
  195. if (headerList.Count > 0 && line.IndexOf(':') < 0)
  196. throw new IOException("invalid armor header");
  197. headerList.Add(line);
  198. buf.Length = 0;
  199. }
  200. if (c != '\n' && c != '\r')
  201. {
  202. buf.Append((char)c);
  203. eolReached = false;
  204. }
  205. else
  206. {
  207. if (c == '\r' || (last != '\r' && c == '\n'))
  208. {
  209. eolReached = true;
  210. }
  211. }
  212. last = c;
  213. }
  214. if (crLf)
  215. {
  216. input.ReadByte(); // skip last \n
  217. }
  218. }
  219. if (headerList.Count > 0)
  220. {
  221. header = (string)headerList[0];
  222. }
  223. clearText = "-----BEGIN PGP SIGNED MESSAGE-----".Equals(header);
  224. newLineFound = true;
  225. return headerFound;
  226. }
  227. /**
  228. * @return true if we are inside the clear text section of a PGP
  229. * signed message.
  230. */
  231. public bool IsClearText()
  232. {
  233. return clearText;
  234. }
  235. /**
  236. * @return true if the stream is actually at end of file.
  237. */
  238. public bool IsEndOfStream()
  239. {
  240. return isEndOfStream;
  241. }
  242. /**
  243. * Return the armor header line (if there is one)
  244. * @return the armor header line, null if none present.
  245. */
  246. public string GetArmorHeaderLine()
  247. {
  248. return header;
  249. }
  250. /**
  251. * Return the armor headers (the lines after the armor header line),
  252. * @return an array of armor headers, null if there aren't any.
  253. */
  254. public string[] GetArmorHeaders()
  255. {
  256. if (headerList.Count <= 1)
  257. return null;
  258. string[] hdrs = new string[headerList.Count - 1];
  259. for (int i = 0; i != hdrs.Length; i++)
  260. {
  261. hdrs[i] = (string)headerList[i + 1];
  262. }
  263. return hdrs;
  264. }
  265. private int ReadIgnoreSpace()
  266. {
  267. int c;
  268. do
  269. {
  270. c = input.ReadByte();
  271. }
  272. while (c == ' ' || c == '\t' || c == '\f' || c == '\u000B') ; // \u000B ~ \v
  273. if (c >= 128)
  274. throw new IOException("invalid armor");
  275. return c;
  276. }
  277. public override int Read(byte[] buffer, int offset, int count)
  278. {
  279. Streams.ValidateBufferArguments(buffer, offset, count);
  280. /*
  281. * TODO Currently can't return partial data when exception thrown (breaking test case), so we don't inherit
  282. * the base class implementation. Probably the reason is that throws don't mark this instance as 'failed'.
  283. */
  284. int pos = 0;
  285. while (pos < count)
  286. {
  287. int b = ReadByte();
  288. if (b < 0)
  289. break;
  290. buffer[offset + pos++] = (byte)b;
  291. }
  292. return pos;
  293. }
  294. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  295. public override int Read(Span<byte> buffer)
  296. {
  297. /*
  298. * TODO Currently can't return partial data when exception thrown (breaking test case), so we don't inherit
  299. * the base class implementation. Probably the reason is that throws don't mark this instance as 'failed'.
  300. */
  301. int pos = 0;
  302. while (pos < buffer.Length)
  303. {
  304. int b = ReadByte();
  305. if (b < 0)
  306. break;
  307. buffer[pos++] = (byte)b;
  308. }
  309. return pos;
  310. }
  311. #endif
  312. public override int ReadByte()
  313. {
  314. if (start)
  315. {
  316. if (hasHeaders)
  317. {
  318. ParseHeaders();
  319. }
  320. crc.Reset();
  321. start = false;
  322. }
  323. int c;
  324. if (clearText)
  325. {
  326. c = input.ReadByte();
  327. if (c == '\r' || (c == '\n' && lastC != '\r'))
  328. {
  329. newLineFound = true;
  330. }
  331. else if (newLineFound && c == '-')
  332. {
  333. c = input.ReadByte();
  334. if (c == '-') // a header, not dash escaped
  335. {
  336. clearText = false;
  337. start = true;
  338. restart = true;
  339. }
  340. else // a space - must be a dash escape
  341. {
  342. c = input.ReadByte();
  343. }
  344. newLineFound = false;
  345. }
  346. else
  347. {
  348. if (c != '\n' && lastC != '\r')
  349. {
  350. newLineFound = false;
  351. }
  352. }
  353. lastC = c;
  354. if (c < 0)
  355. {
  356. isEndOfStream = true;
  357. }
  358. return c;
  359. }
  360. if (bufPtr > 2 || crcFound)
  361. {
  362. c = ReadIgnoreSpace();
  363. if (c == '\r' || c == '\n')
  364. {
  365. c = ReadIgnoreSpace();
  366. while (c == '\n' || c == '\r')
  367. {
  368. c = ReadIgnoreSpace();
  369. }
  370. if (c < 0) // EOF
  371. {
  372. isEndOfStream = true;
  373. return -1;
  374. }
  375. if (c == '=') // crc reached
  376. {
  377. bufPtr = Decode(ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
  378. if (bufPtr == 0)
  379. {
  380. int i = ((outBuf[0] & 0xff) << 16)
  381. | ((outBuf[1] & 0xff) << 8)
  382. | (outBuf[2] & 0xff);
  383. crcFound = true;
  384. if (i != crc.Value)
  385. {
  386. throw new IOException("crc check failed in armored message.");
  387. }
  388. return ReadByte();
  389. }
  390. else
  391. {
  392. if (detectMissingChecksum)
  393. {
  394. throw new IOException("no crc found in armored message");
  395. }
  396. }
  397. }
  398. else if (c == '-') // end of record reached
  399. {
  400. while ((c = input.ReadByte()) >= 0)
  401. {
  402. if (c == '\n' || c == '\r')
  403. {
  404. break;
  405. }
  406. }
  407. if (!crcFound && detectMissingChecksum)
  408. {
  409. throw new IOException("crc check not found");
  410. }
  411. crcFound = false;
  412. start = true;
  413. bufPtr = 3;
  414. if (c < 0)
  415. {
  416. isEndOfStream = true;
  417. }
  418. return -1;
  419. }
  420. else // data
  421. {
  422. bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
  423. }
  424. }
  425. else
  426. {
  427. if (c >= 0)
  428. {
  429. bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
  430. }
  431. else
  432. {
  433. isEndOfStream = true;
  434. return -1;
  435. }
  436. }
  437. }
  438. c = outBuf[bufPtr++];
  439. crc.Update(c);
  440. return c;
  441. }
  442. protected override void Dispose(bool disposing)
  443. {
  444. if (disposing)
  445. {
  446. input.Dispose();
  447. }
  448. base.Dispose(disposing);
  449. }
  450. /**
  451. * Change how the stream should react if it encounters missing CRC checksum.
  452. * The default value is false (ignore missing CRC checksums). If the behavior is set to true,
  453. * an {@link IOException} will be thrown if a missing CRC checksum is encountered.
  454. *
  455. * @param detectMissing ignore missing CRC sums
  456. */
  457. public virtual void SetDetectMissingCrc(bool detectMissing)
  458. {
  459. this.detectMissingChecksum = detectMissing;
  460. }
  461. }
  462. }
  463. #pragma warning restore
  464. #endif