ArmoredInputStream.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using System.Collections;
  5. using System.IO;
  6. using System.Text;
  7. using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  8. using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.IO;
  9. namespace BestHTTP.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 headerList = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
  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 = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
  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 ReadByte()
  278. {
  279. if (start)
  280. {
  281. if (hasHeaders)
  282. {
  283. ParseHeaders();
  284. }
  285. crc.Reset();
  286. start = false;
  287. }
  288. int c;
  289. if (clearText)
  290. {
  291. c = input.ReadByte();
  292. if (c == '\r' || (c == '\n' && lastC != '\r'))
  293. {
  294. newLineFound = true;
  295. }
  296. else if (newLineFound && c == '-')
  297. {
  298. c = input.ReadByte();
  299. if (c == '-') // a header, not dash escaped
  300. {
  301. clearText = false;
  302. start = true;
  303. restart = true;
  304. }
  305. else // a space - must be a dash escape
  306. {
  307. c = input.ReadByte();
  308. }
  309. newLineFound = false;
  310. }
  311. else
  312. {
  313. if (c != '\n' && lastC != '\r')
  314. {
  315. newLineFound = false;
  316. }
  317. }
  318. lastC = c;
  319. if (c < 0)
  320. {
  321. isEndOfStream = true;
  322. }
  323. return c;
  324. }
  325. if (bufPtr > 2 || crcFound)
  326. {
  327. c = ReadIgnoreSpace();
  328. if (c == '\r' || c == '\n')
  329. {
  330. c = ReadIgnoreSpace();
  331. while (c == '\n' || c == '\r')
  332. {
  333. c = ReadIgnoreSpace();
  334. }
  335. if (c < 0) // EOF
  336. {
  337. isEndOfStream = true;
  338. return -1;
  339. }
  340. if (c == '=') // crc reached
  341. {
  342. bufPtr = Decode(ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
  343. if (bufPtr == 0)
  344. {
  345. int i = ((outBuf[0] & 0xff) << 16)
  346. | ((outBuf[1] & 0xff) << 8)
  347. | (outBuf[2] & 0xff);
  348. crcFound = true;
  349. if (i != crc.Value)
  350. {
  351. throw new IOException("crc check failed in armored message.");
  352. }
  353. return ReadByte();
  354. }
  355. else
  356. {
  357. if (detectMissingChecksum)
  358. {
  359. throw new IOException("no crc found in armored message");
  360. }
  361. }
  362. }
  363. else if (c == '-') // end of record reached
  364. {
  365. while ((c = input.ReadByte()) >= 0)
  366. {
  367. if (c == '\n' || c == '\r')
  368. {
  369. break;
  370. }
  371. }
  372. if (!crcFound && detectMissingChecksum)
  373. {
  374. throw new IOException("crc check not found");
  375. }
  376. crcFound = false;
  377. start = true;
  378. bufPtr = 3;
  379. if (c < 0)
  380. {
  381. isEndOfStream = true;
  382. }
  383. return -1;
  384. }
  385. else // data
  386. {
  387. bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
  388. }
  389. }
  390. else
  391. {
  392. if (c >= 0)
  393. {
  394. bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
  395. }
  396. else
  397. {
  398. isEndOfStream = true;
  399. return -1;
  400. }
  401. }
  402. }
  403. c = outBuf[bufPtr++];
  404. crc.Update(c);
  405. return c;
  406. }
  407. /**
  408. * Reads up to <code>len</code> bytes of data from the input stream into
  409. * an array of bytes. An attempt is made to read as many as
  410. * <code>len</code> bytes, but a smaller number may be read.
  411. * The number of bytes actually read is returned as an integer.
  412. *
  413. * The first byte read is stored into element <code>b[off]</code>, the
  414. * next one into <code>b[off+1]</code>, and so on. The number of bytes read
  415. * is, at most, equal to <code>len</code>.
  416. *
  417. * NOTE: We need to override the custom behavior of Java's {@link InputStream#read(byte[], int, int)},
  418. * as the upstream method silently swallows {@link IOException IOExceptions}.
  419. * This would cause CRC checksum errors to go unnoticed.
  420. *
  421. * @see <a href="https://github.com/bcgit/bc-java/issues/998">Related BC bug report</a>
  422. * @param b byte array
  423. * @param off offset at which we start writing data to the array
  424. * @param len number of bytes we write into the array
  425. * @return total number of bytes read into the buffer
  426. *
  427. * @throws IOException if an exception happens AT ANY POINT
  428. */
  429. public override int Read(byte[] b, int off, int len)
  430. {
  431. CheckIndexSize(b.Length, off, len);
  432. int pos = 0;
  433. while (pos < len)
  434. {
  435. int c = ReadByte();
  436. if (c < 0)
  437. break;
  438. b[off + pos++] = (byte)c;
  439. }
  440. return pos;
  441. }
  442. private void CheckIndexSize(int size, int off, int len)
  443. {
  444. if (off < 0 || len < 0)
  445. throw new IndexOutOfRangeException("Offset and length cannot be negative.");
  446. if (off > size - len)
  447. throw new IndexOutOfRangeException("Invalid offset and length.");
  448. }
  449. #if PORTABLE || NETFX_CORE
  450. protected override void Dispose(bool disposing)
  451. {
  452. if (disposing)
  453. {
  454. BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(input);
  455. }
  456. base.Dispose(disposing);
  457. }
  458. #else
  459. public override void Close()
  460. {
  461. BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(input);
  462. base.Close();
  463. }
  464. #endif
  465. /**
  466. * Change how the stream should react if it encounters missing CRC checksum.
  467. * The default value is false (ignore missing CRC checksums). If the behavior is set to true,
  468. * an {@link IOException} will be thrown if a missing CRC checksum is encountered.
  469. *
  470. * @param detectMissing ignore missing CRC sums
  471. */
  472. public virtual void SetDetectMissingCrc(bool detectMissing)
  473. {
  474. this.detectMissingChecksum = detectMissing;
  475. }
  476. }
  477. }
  478. #pragma warning restore
  479. #endif