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.
388 lines
12 KiB
388 lines
12 KiB
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) |
|
#pragma warning disable |
|
using System; |
|
|
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Digests; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Security; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; |
|
|
|
namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Encodings |
|
{ |
|
/** |
|
* this does your basic Pkcs 1 v1.5 padding - whether or not you should be using this |
|
* depends on your application - see Pkcs1 Version 2 for details. |
|
*/ |
|
public class Pkcs1Encoding |
|
: IAsymmetricBlockCipher |
|
{ |
|
/** |
|
* some providers fail to include the leading zero in PKCS1 encoded blocks. If you need to |
|
* work with one of these set the system property BestHTTP.SecureProtocol.Org.BouncyCastle.Pkcs1.Strict to false. |
|
*/ |
|
public const string StrictLengthEnabledProperty = "BestHTTP.SecureProtocol.Org.BouncyCastle.Pkcs1.Strict"; |
|
|
|
private const int HeaderLength = 10; |
|
|
|
/** |
|
* The same effect can be achieved by setting the static property directly |
|
* <p> |
|
* The static property is checked during construction of the encoding object, it is set to |
|
* true by default. |
|
* </p> |
|
*/ |
|
public static bool StrictLengthEnabled |
|
{ |
|
get { return strictLengthEnabled[0]; } |
|
set { strictLengthEnabled[0] = value; } |
|
} |
|
|
|
private static readonly bool[] strictLengthEnabled; |
|
|
|
static Pkcs1Encoding() |
|
{ |
|
string strictProperty = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.GetEnvironmentVariable(StrictLengthEnabledProperty); |
|
|
|
strictLengthEnabled = new bool[]{ strictProperty == null || BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.EqualsIgnoreCase("true", strictProperty) }; |
|
} |
|
|
|
|
|
private SecureRandom random; |
|
private IAsymmetricBlockCipher engine; |
|
private bool forEncryption; |
|
private bool forPrivateKey; |
|
private bool useStrictLength; |
|
private int pLen = -1; |
|
private byte[] fallback = null; |
|
private byte[] blockBuffer = null; |
|
|
|
/** |
|
* Basic constructor. |
|
* |
|
* @param cipher |
|
*/ |
|
public Pkcs1Encoding( |
|
IAsymmetricBlockCipher cipher) |
|
{ |
|
this.engine = cipher; |
|
this.useStrictLength = StrictLengthEnabled; |
|
} |
|
|
|
/** |
|
* Constructor for decryption with a fixed plaintext length. |
|
* |
|
* @param cipher The cipher to use for cryptographic operation. |
|
* @param pLen Length of the expected plaintext. |
|
*/ |
|
public Pkcs1Encoding(IAsymmetricBlockCipher cipher, int pLen) |
|
{ |
|
this.engine = cipher; |
|
this.useStrictLength = StrictLengthEnabled; |
|
this.pLen = pLen; |
|
} |
|
|
|
/** |
|
* Constructor for decryption with a fixed plaintext length and a fallback |
|
* value that is returned, if the padding is incorrect. |
|
* |
|
* @param cipher |
|
* The cipher to use for cryptographic operation. |
|
* @param fallback |
|
* The fallback value, we don't to a arraycopy here. |
|
*/ |
|
public Pkcs1Encoding(IAsymmetricBlockCipher cipher, byte[] fallback) |
|
{ |
|
this.engine = cipher; |
|
this.useStrictLength = StrictLengthEnabled; |
|
this.fallback = fallback; |
|
this.pLen = fallback.Length; |
|
} |
|
|
|
public IAsymmetricBlockCipher GetUnderlyingCipher() |
|
{ |
|
return engine; |
|
} |
|
|
|
public string AlgorithmName |
|
{ |
|
get { return engine.AlgorithmName + "/PKCS1Padding"; } |
|
} |
|
|
|
public void Init(bool forEncryption, ICipherParameters parameters) |
|
{ |
|
AsymmetricKeyParameter kParam; |
|
if (parameters is ParametersWithRandom) |
|
{ |
|
ParametersWithRandom rParam = (ParametersWithRandom)parameters; |
|
|
|
this.random = rParam.Random; |
|
kParam = (AsymmetricKeyParameter)rParam.Parameters; |
|
} |
|
else |
|
{ |
|
this.random = new SecureRandom(); |
|
kParam = (AsymmetricKeyParameter)parameters; |
|
} |
|
|
|
engine.Init(forEncryption, parameters); |
|
|
|
this.forPrivateKey = kParam.IsPrivate; |
|
this.forEncryption = forEncryption; |
|
this.blockBuffer = new byte[engine.GetOutputBlockSize()]; |
|
|
|
if (pLen > 0 && fallback == null && random == null) |
|
throw new ArgumentException("encoder requires random"); |
|
} |
|
|
|
public int GetInputBlockSize() |
|
{ |
|
int baseBlockSize = engine.GetInputBlockSize(); |
|
|
|
return forEncryption |
|
? baseBlockSize - HeaderLength |
|
: baseBlockSize; |
|
} |
|
|
|
public int GetOutputBlockSize() |
|
{ |
|
int baseBlockSize = engine.GetOutputBlockSize(); |
|
|
|
return forEncryption |
|
? baseBlockSize |
|
: baseBlockSize - HeaderLength; |
|
} |
|
|
|
public byte[] ProcessBlock( |
|
byte[] input, |
|
int inOff, |
|
int length) |
|
{ |
|
return forEncryption |
|
? EncodeBlock(input, inOff, length) |
|
: DecodeBlock(input, inOff, length); |
|
} |
|
|
|
private byte[] EncodeBlock( |
|
byte[] input, |
|
int inOff, |
|
int inLen) |
|
{ |
|
if (inLen > GetInputBlockSize()) |
|
throw new ArgumentException("input data too large", "inLen"); |
|
|
|
byte[] block = new byte[engine.GetInputBlockSize()]; |
|
|
|
if (forPrivateKey) |
|
{ |
|
block[0] = 0x01; // type code 1 |
|
|
|
for (int i = 1; i != block.Length - inLen - 1; i++) |
|
{ |
|
block[i] = (byte)0xFF; |
|
} |
|
} |
|
else |
|
{ |
|
random.NextBytes(block); // random fill |
|
|
|
block[0] = 0x02; // type code 2 |
|
|
|
// |
|
// a zero byte marks the end of the padding, so all |
|
// the pad bytes must be non-zero. |
|
// |
|
for (int i = 1; i != block.Length - inLen - 1; i++) |
|
{ |
|
while (block[i] == 0) |
|
{ |
|
block[i] = (byte)random.NextInt(); |
|
} |
|
} |
|
} |
|
|
|
block[block.Length - inLen - 1] = 0x00; // mark the end of the padding |
|
Array.Copy(input, inOff, block, block.Length - inLen, inLen); |
|
|
|
return engine.ProcessBlock(block, 0, block.Length); |
|
} |
|
|
|
/** |
|
* Checks if the argument is a correctly PKCS#1.5 encoded Plaintext |
|
* for encryption. |
|
* |
|
* @param encoded The Plaintext. |
|
* @param pLen Expected length of the plaintext. |
|
* @return Either 0, if the encoding is correct, or -1, if it is incorrect. |
|
*/ |
|
private static int CheckPkcs1Encoding(byte[] encoded, int pLen) |
|
{ |
|
int correct = 0; |
|
/* |
|
* Check if the first two bytes are 0 2 |
|
*/ |
|
correct |= (encoded[0] ^ 2); |
|
|
|
/* |
|
* Now the padding check, check for no 0 byte in the padding |
|
*/ |
|
int plen = encoded.Length - ( |
|
pLen /* Length of the PMS */ |
|
+ 1 /* Final 0-byte before PMS */ |
|
); |
|
|
|
for (int i = 1; i < plen; i++) |
|
{ |
|
int tmp = encoded[i]; |
|
tmp |= tmp >> 1; |
|
tmp |= tmp >> 2; |
|
tmp |= tmp >> 4; |
|
correct |= (tmp & 1) - 1; |
|
} |
|
|
|
/* |
|
* Make sure the padding ends with a 0 byte. |
|
*/ |
|
correct |= encoded[encoded.Length - (pLen + 1)]; |
|
|
|
/* |
|
* Return 0 or 1, depending on the result. |
|
*/ |
|
correct |= correct >> 1; |
|
correct |= correct >> 2; |
|
correct |= correct >> 4; |
|
return ~((correct & 1) - 1); |
|
} |
|
|
|
/** |
|
* Decode PKCS#1.5 encoding, and return a random value if the padding is not correct. |
|
* |
|
* @param in The encrypted block. |
|
* @param inOff Offset in the encrypted block. |
|
* @param inLen Length of the encrypted block. |
|
* @param pLen Length of the desired output. |
|
* @return The plaintext without padding, or a random value if the padding was incorrect. |
|
* @throws InvalidCipherTextException |
|
*/ |
|
private byte[] DecodeBlockOrRandom(byte[] input, int inOff, int inLen) |
|
{ |
|
if (!forPrivateKey) |
|
throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing"); |
|
|
|
byte[] block = engine.ProcessBlock(input, inOff, inLen); |
|
byte[] random; |
|
if (this.fallback == null) |
|
{ |
|
random = new byte[this.pLen]; |
|
this.random.NextBytes(random); |
|
} |
|
else |
|
{ |
|
random = fallback; |
|
} |
|
|
|
byte[] data = (useStrictLength & (block.Length != engine.GetOutputBlockSize())) ? blockBuffer : block; |
|
|
|
/* |
|
* Check the padding. |
|
*/ |
|
int correct = CheckPkcs1Encoding(data, this.pLen); |
|
|
|
/* |
|
* Now, to a constant time constant memory copy of the decrypted value |
|
* or the random value, depending on the validity of the padding. |
|
*/ |
|
byte[] result = new byte[this.pLen]; |
|
for (int i = 0; i < this.pLen; i++) |
|
{ |
|
result[i] = (byte)((data[i + (data.Length - pLen)] & (~correct)) | (random[i] & correct)); |
|
} |
|
|
|
Arrays.Fill(data, 0); |
|
|
|
return result; |
|
} |
|
|
|
/** |
|
* @exception InvalidCipherTextException if the decrypted block is not in Pkcs1 format. |
|
*/ |
|
private byte[] DecodeBlock( |
|
byte[] input, |
|
int inOff, |
|
int inLen) |
|
{ |
|
/* |
|
* If the length of the expected plaintext is known, we use a constant-time decryption. |
|
* If the decryption fails, we return a random value. |
|
*/ |
|
if (this.pLen != -1) |
|
{ |
|
return this.DecodeBlockOrRandom(input, inOff, inLen); |
|
} |
|
|
|
byte[] block = engine.ProcessBlock(input, inOff, inLen); |
|
bool incorrectLength = (useStrictLength & (block.Length != engine.GetOutputBlockSize())); |
|
|
|
byte[] data; |
|
if (block.Length < GetOutputBlockSize()) |
|
{ |
|
data = blockBuffer; |
|
} |
|
else |
|
{ |
|
data = block; |
|
} |
|
|
|
byte expectedType = (byte)(forPrivateKey ? 2 : 1); |
|
byte type = data[0]; |
|
|
|
bool badType = (type != expectedType); |
|
|
|
// |
|
// find and extract the message block. |
|
// |
|
int start = FindStart(type, data); |
|
|
|
start++; // data should start at the next byte |
|
|
|
if (badType | (start < HeaderLength)) |
|
{ |
|
Arrays.Fill(data, 0); |
|
throw new InvalidCipherTextException("block incorrect"); |
|
} |
|
|
|
// if we get this far, it's likely to be a genuine encoding error |
|
if (incorrectLength) |
|
{ |
|
Arrays.Fill(data, 0); |
|
throw new InvalidCipherTextException("block incorrect size"); |
|
} |
|
|
|
byte[] result = new byte[data.Length - start]; |
|
|
|
Array.Copy(data, start, result, 0, result.Length); |
|
|
|
return result; |
|
} |
|
|
|
private int FindStart(byte type, byte[] block) |
|
{ |
|
int start = -1; |
|
bool padErr = false; |
|
|
|
for (int i = 1; i != block.Length; i++) |
|
{ |
|
byte pad = block[i]; |
|
|
|
if (pad == 0 & start < 0) |
|
{ |
|
start = i; |
|
} |
|
padErr |= ((type == 1) & (start < 0) & (pad != (byte)0xff)); |
|
} |
|
|
|
return padErr ? -1 : start; |
|
} |
|
} |
|
} |
|
#pragma warning restore |
|
#endif
|
|
|