You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
545 lines
16 KiB
545 lines
16 KiB
#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
|
|
|