123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
- #pragma warning disable
- using System;
- using System.Collections;
- using System.IO;
- using System.Text;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.IO;
- namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Bcpg
- {
- /**
- * reader for Base64 armored objects - read the headers and then start returning
- * bytes when the data is reached. An IOException is thrown if the CRC check
- * is detected and fails.
- * <p>
- * By default a missing CRC will not cause an exception. To force CRC detection use:
- * <pre>
- * ArmoredInputStream aIn = ...
- *
- * aIn.setDetectMissingCRC(true);
- * </pre>
- * </p>
- */
- public class ArmoredInputStream
- : BaseInputStream
- {
- /*
- * set up the decoding table.
- */
- private readonly static byte[] decodingTable;
- static ArmoredInputStream()
- {
- decodingTable = new byte[128];
- Arrays.Fill(decodingTable, 0xff);
- for (int i = 'A'; i <= 'Z'; i++)
- {
- decodingTable[i] = (byte)(i - 'A');
- }
- for (int i = 'a'; i <= 'z'; i++)
- {
- decodingTable[i] = (byte)(i - 'a' + 26);
- }
- for (int i = '0'; i <= '9'; i++)
- {
- decodingTable[i] = (byte)(i - '0' + 52);
- }
- decodingTable['+'] = 62;
- decodingTable['/'] = 63;
- }
- /**
- * decode the base 64 encoded input data.
- *
- * @return the offset the data starts in out.
- */
- private static int Decode(int in0, int in1, int in2, int in3, int[] result)
- {
- if (in3 < 0)
- throw new EndOfStreamException("unexpected end of file in armored stream.");
- int b1, b2, b3, b4;
- if (in2 == '=')
- {
- b1 = decodingTable[in0];
- b2 = decodingTable[in1];
- if ((b1 | b2) >= 128)
- throw new IOException("invalid armor");
- result[2] = ((b1 << 2) | (b2 >> 4)) & 0xff;
- return 2;
- }
- else if (in3 == '=')
- {
- b1 = decodingTable[in0];
- b2 = decodingTable[in1];
- b3 = decodingTable[in2];
- if ((b1 | b2 | b3) >= 128)
- throw new IOException("invalid armor");
- result[1] = ((b1 << 2) | (b2 >> 4)) & 0xff;
- result[2] = ((b2 << 4) | (b3 >> 2)) & 0xff;
- return 1;
- }
- else
- {
- b1 = decodingTable[in0];
- b2 = decodingTable[in1];
- b3 = decodingTable[in2];
- b4 = decodingTable[in3];
- if ((b1 | b2 | b3 | b4) >= 128)
- throw new IOException("invalid armor");
- result[0] = ((b1 << 2) | (b2 >> 4)) & 0xff;
- result[1] = ((b2 << 4) | (b3 >> 2)) & 0xff;
- result[2] = ((b3 << 6) | b4) & 0xff;
- return 0;
- }
- }
- /*
- * Ignore missing CRC checksums.
- * https://tests.sequoia-pgp.org/#ASCII_Armor suggests that missing CRC sums do not invalidate the message.
- */
- private bool detectMissingChecksum = false;
- Stream input;
- bool start = true;
- int[] outBuf = new int[3];
- int bufPtr = 3;
- Crc24 crc = new Crc24();
- bool crcFound = false;
- bool hasHeaders = true;
- string header = null;
- bool newLineFound = false;
- bool clearText = false;
- bool restart = false;
- IList headerList = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
- int lastC = 0;
- bool isEndOfStream;
- /**
- * Create a stream for reading a PGP armoured message, parsing up to a header
- * and then reading the data that follows.
- *
- * @param input
- */
- public ArmoredInputStream(Stream input)
- : this(input, true)
- {
- }
- /**
- * Create an armoured input stream which will assume the data starts
- * straight away, or parse for headers first depending on the value of
- * hasHeaders.
- *
- * @param input
- * @param hasHeaders true if headers are to be looked for, false otherwise.
- */
- public ArmoredInputStream(Stream input, bool hasHeaders)
- {
- this.input = input;
- this.hasHeaders = hasHeaders;
- if (hasHeaders)
- {
- ParseHeaders();
- }
- start = false;
- }
- private bool ParseHeaders()
- {
- header = null;
- int c;
- int last = 0;
- bool headerFound = false;
- headerList = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
- //
- // if restart we already have a header
- //
- if (restart)
- {
- headerFound = true;
- }
- else
- {
- while ((c = input.ReadByte()) >= 0)
- {
- if (c == '-' && (last == 0 || last == '\n' || last == '\r'))
- {
- headerFound = true;
- break;
- }
- last = c;
- }
- }
- if (headerFound)
- {
- StringBuilder buf = new StringBuilder("-");
- bool eolReached = false;
- bool crLf = false;
- if (restart) // we've had to look ahead two '-'
- {
- buf.Append('-');
- }
- while ((c = input.ReadByte()) >= 0)
- {
- if (last == '\r' && c == '\n')
- {
- crLf = true;
- }
- if (eolReached && (last != '\r' && c == '\n'))
- {
- break;
- }
- if (eolReached && c == '\r')
- {
- break;
- }
- if (c == '\r' || (last != '\r' && c == '\n'))
- {
- string line = buf.ToString();
- if (line.Trim().Length < 1)
- break;
- if (headerList.Count > 0 && line.IndexOf(':') < 0)
- throw new IOException("invalid armor header");
- headerList.Add(line);
- buf.Length = 0;
- }
- if (c != '\n' && c != '\r')
- {
- buf.Append((char)c);
- eolReached = false;
- }
- else
- {
- if (c == '\r' || (last != '\r' && c == '\n'))
- {
- eolReached = true;
- }
- }
- last = c;
- }
- if (crLf)
- {
- input.ReadByte(); // skip last \n
- }
- }
- if (headerList.Count > 0)
- {
- header = (string)headerList[0];
- }
- clearText = "-----BEGIN PGP SIGNED MESSAGE-----".Equals(header);
- newLineFound = true;
- return headerFound;
- }
- /**
- * @return true if we are inside the clear text section of a PGP
- * signed message.
- */
- public bool IsClearText()
- {
- return clearText;
- }
- /**
- * @return true if the stream is actually at end of file.
- */
- public bool IsEndOfStream()
- {
- return isEndOfStream;
- }
- /**
- * Return the armor header line (if there is one)
- * @return the armor header line, null if none present.
- */
- public string GetArmorHeaderLine()
- {
- return header;
- }
- /**
- * Return the armor headers (the lines after the armor header line),
- * @return an array of armor headers, null if there aren't any.
- */
- public string[] GetArmorHeaders()
- {
- if (headerList.Count <= 1)
- return null;
- string[] hdrs = new string[headerList.Count - 1];
- for (int i = 0; i != hdrs.Length; i++)
- {
- hdrs[i] = (string)headerList[i + 1];
- }
- return hdrs;
- }
- private int ReadIgnoreSpace()
- {
- int c;
- do
- {
- c = input.ReadByte();
- }
- while (c == ' ' || c == '\t' || c == '\f' || c == '\u000B') ; // \u000B ~ \v
- if (c >= 128)
- throw new IOException("invalid armor");
- return c;
- }
- public override int ReadByte()
- {
- if (start)
- {
- if (hasHeaders)
- {
- ParseHeaders();
- }
- crc.Reset();
- start = false;
- }
- int c;
- if (clearText)
- {
- c = input.ReadByte();
- if (c == '\r' || (c == '\n' && lastC != '\r'))
- {
- newLineFound = true;
- }
- else if (newLineFound && c == '-')
- {
- c = input.ReadByte();
- if (c == '-') // a header, not dash escaped
- {
- clearText = false;
- start = true;
- restart = true;
- }
- else // a space - must be a dash escape
- {
- c = input.ReadByte();
- }
- newLineFound = false;
- }
- else
- {
- if (c != '\n' && lastC != '\r')
- {
- newLineFound = false;
- }
- }
-
- lastC = c;
- if (c < 0)
- {
- isEndOfStream = true;
- }
-
- return c;
- }
- if (bufPtr > 2 || crcFound)
- {
- c = ReadIgnoreSpace();
-
- if (c == '\r' || c == '\n')
- {
- c = ReadIgnoreSpace();
-
- while (c == '\n' || c == '\r')
- {
- c = ReadIgnoreSpace();
- }
- if (c < 0) // EOF
- {
- isEndOfStream = true;
- return -1;
- }
- if (c == '=') // crc reached
- {
- bufPtr = Decode(ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
- if (bufPtr == 0)
- {
- int i = ((outBuf[0] & 0xff) << 16)
- | ((outBuf[1] & 0xff) << 8)
- | (outBuf[2] & 0xff);
- crcFound = true;
- if (i != crc.Value)
- {
- throw new IOException("crc check failed in armored message.");
- }
- return ReadByte();
- }
- else
- {
- if (detectMissingChecksum)
- {
- throw new IOException("no crc found in armored message");
- }
- }
- }
- else if (c == '-') // end of record reached
- {
- while ((c = input.ReadByte()) >= 0)
- {
- if (c == '\n' || c == '\r')
- {
- break;
- }
- }
- if (!crcFound && detectMissingChecksum)
- {
- throw new IOException("crc check not found");
- }
- crcFound = false;
- start = true;
- bufPtr = 3;
- if (c < 0)
- {
- isEndOfStream = true;
- }
- return -1;
- }
- else // data
- {
- bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
- }
- }
- else
- {
- if (c >= 0)
- {
- bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
- }
- else
- {
- isEndOfStream = true;
- return -1;
- }
- }
- }
- c = outBuf[bufPtr++];
- crc.Update(c);
- return c;
- }
- /**
- * Reads up to <code>len</code> bytes of data from the input stream into
- * an array of bytes. An attempt is made to read as many as
- * <code>len</code> bytes, but a smaller number may be read.
- * The number of bytes actually read is returned as an integer.
- *
- * The first byte read is stored into element <code>b[off]</code>, the
- * next one into <code>b[off+1]</code>, and so on. The number of bytes read
- * is, at most, equal to <code>len</code>.
- *
- * NOTE: We need to override the custom behavior of Java's {@link InputStream#read(byte[], int, int)},
- * as the upstream method silently swallows {@link IOException IOExceptions}.
- * This would cause CRC checksum errors to go unnoticed.
- *
- * @see <a href="https://github.com/bcgit/bc-java/issues/998">Related BC bug report</a>
- * @param b byte array
- * @param off offset at which we start writing data to the array
- * @param len number of bytes we write into the array
- * @return total number of bytes read into the buffer
- *
- * @throws IOException if an exception happens AT ANY POINT
- */
- public override int Read(byte[] b, int off, int len)
- {
- CheckIndexSize(b.Length, off, len);
- int pos = 0;
- while (pos < len)
- {
- int c = ReadByte();
- if (c < 0)
- break;
- b[off + pos++] = (byte)c;
- }
- return pos;
- }
- private void CheckIndexSize(int size, int off, int len)
- {
- if (off < 0 || len < 0)
- throw new IndexOutOfRangeException("Offset and length cannot be negative.");
- if (off > size - len)
- throw new IndexOutOfRangeException("Invalid offset and length.");
- }
- #if PORTABLE || NETFX_CORE
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(input);
- }
- base.Dispose(disposing);
- }
- #else
- public override void Close()
- {
- BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(input);
- base.Close();
- }
- #endif
- /**
- * Change how the stream should react if it encounters missing CRC checksum.
- * The default value is false (ignore missing CRC checksums). If the behavior is set to true,
- * an {@link IOException} will be thrown if a missing CRC checksum is encountered.
- *
- * @param detectMissing ignore missing CRC sums
- */
- public virtual void SetDetectMissingCrc(bool detectMissing)
- {
- this.detectMissingChecksum = detectMissing;
- }
- }
- }
- #pragma warning restore
- #endif
|