mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-11-07 17:36:07 +00:00
Add support for creating replay mod capture files (#1246)
* Add test replay handler * Fix incorrect built raw packet * Fix incorrect built raw packet * Add filter * Add not working zip lib * Add dotNetZip lib and complete basic function * Update ReplayHandler.cs * Complete Replay handler Without client player handling * Complete replay mod - New ChatBot OnNetworkPacket event * Add auto-backup and command for Replay Mod * Add ReplayMod description to readme * Small naming changes, fix compile error on .NET4.0 * ReplayHandler slight optimizations Use Path.Combine to automatically use Windows '\' or Linux '/' Move re-usable common parts outside the Replay handler Small optimizations in building JSON strings Co-authored-by: ORelio <oreliogitantispam.l0gin@spamgourmet.com>
This commit is contained in:
parent
cd1badb9d6
commit
7e20e409a8
47 changed files with 32732 additions and 21 deletions
941
MinecraftClient/Protocol/Handlers/Compression/Zip/WinZipAes.cs
Normal file
941
MinecraftClient/Protocol/Handlers/Compression/Zip/WinZipAes.cs
Normal file
|
|
@ -0,0 +1,941 @@
|
|||
//#define Trace
|
||||
|
||||
// WinZipAes.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009-2011 Dino Chiesa.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2011-July-12 13:42:06>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines the classes for dealing with WinZip's AES encryption,
|
||||
// according to the specifications for the format available on WinZip's website.
|
||||
//
|
||||
// Created: January 2009
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
#if AESCRYPTO
|
||||
namespace Ionic.Zip
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a helper class supporting WinZip AES encryption.
|
||||
/// This class is intended for use only by the DotNetZip library.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Most uses of the DotNetZip library will not involve direct calls into
|
||||
/// the WinZipAesCrypto class. Instead, the WinZipAesCrypto class is
|
||||
/// instantiated and used by the ZipEntry() class when WinZip AES
|
||||
/// encryption or decryption on an entry is employed.
|
||||
/// </remarks>
|
||||
internal class WinZipAesCrypto
|
||||
{
|
||||
internal byte[] _Salt;
|
||||
internal byte[] _providedPv;
|
||||
internal byte[] _generatedPv;
|
||||
internal int _KeyStrengthInBits;
|
||||
private byte[] _MacInitializationVector;
|
||||
private byte[] _StoredMac;
|
||||
private byte[] _keyBytes;
|
||||
private Int16 PasswordVerificationStored;
|
||||
private Int16 PasswordVerificationGenerated;
|
||||
private int Rfc2898KeygenIterations = 1000;
|
||||
private string _Password;
|
||||
private bool _cryptoGenerated ;
|
||||
|
||||
private WinZipAesCrypto(string password, int KeyStrengthInBits)
|
||||
{
|
||||
_Password = password;
|
||||
_KeyStrengthInBits = KeyStrengthInBits;
|
||||
}
|
||||
|
||||
public static WinZipAesCrypto Generate(string password, int KeyStrengthInBits)
|
||||
{
|
||||
WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);
|
||||
|
||||
int saltSizeInBytes = c._KeyStrengthInBytes / 2;
|
||||
c._Salt = new byte[saltSizeInBytes];
|
||||
Random rnd = new Random();
|
||||
rnd.NextBytes(c._Salt);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static WinZipAesCrypto ReadFromStream(string password, int KeyStrengthInBits, Stream s)
|
||||
{
|
||||
// from http://www.winzip.com/aes_info.htm
|
||||
//
|
||||
// Size(bytes) Content
|
||||
// -----------------------------------
|
||||
// Variable Salt value
|
||||
// 2 Password verification value
|
||||
// Variable Encrypted file data
|
||||
// 10 Authentication code
|
||||
//
|
||||
// ZipEntry.CompressedSize represents the size of all of those elements.
|
||||
|
||||
// salt size varies with key length:
|
||||
// 128 bit key => 8 bytes salt
|
||||
// 192 bits => 12 bytes salt
|
||||
// 256 bits => 16 bytes salt
|
||||
|
||||
WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);
|
||||
|
||||
int saltSizeInBytes = c._KeyStrengthInBytes / 2;
|
||||
c._Salt = new byte[saltSizeInBytes];
|
||||
c._providedPv = new byte[2];
|
||||
|
||||
s.Read(c._Salt, 0, c._Salt.Length);
|
||||
s.Read(c._providedPv, 0, c._providedPv.Length);
|
||||
|
||||
c.PasswordVerificationStored = (Int16)(c._providedPv[0] + c._providedPv[1] * 256);
|
||||
if (password != null)
|
||||
{
|
||||
c.PasswordVerificationGenerated = (Int16)(c.GeneratedPV[0] + c.GeneratedPV[1] * 256);
|
||||
if (c.PasswordVerificationGenerated != c.PasswordVerificationStored)
|
||||
throw new BadPasswordException("bad password");
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public byte[] GeneratedPV
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_cryptoGenerated) _GenerateCryptoBytes();
|
||||
return _generatedPv;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] Salt
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Salt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int _KeyStrengthInBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _KeyStrengthInBits / 8;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public int SizeOfEncryptionMetadata
|
||||
{
|
||||
get
|
||||
{
|
||||
// 10 bytes after, (n-10) before the compressed data
|
||||
return _KeyStrengthInBytes / 2 + 10 + 2;
|
||||
}
|
||||
}
|
||||
|
||||
public string Password
|
||||
{
|
||||
set
|
||||
{
|
||||
_Password = value;
|
||||
if (_Password != null)
|
||||
{
|
||||
PasswordVerificationGenerated = (Int16)(GeneratedPV[0] + GeneratedPV[1] * 256);
|
||||
if (PasswordVerificationGenerated != PasswordVerificationStored)
|
||||
throw new Ionic.Zip.BadPasswordException();
|
||||
}
|
||||
}
|
||||
private get
|
||||
{
|
||||
return _Password;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void _GenerateCryptoBytes()
|
||||
{
|
||||
//Console.WriteLine(" provided password: '{0}'", _Password);
|
||||
|
||||
System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 =
|
||||
new System.Security.Cryptography.Rfc2898DeriveBytes(_Password, Salt, Rfc2898KeygenIterations);
|
||||
|
||||
_keyBytes = rfc2898.GetBytes(_KeyStrengthInBytes); // 16 or 24 or 32 ???
|
||||
_MacInitializationVector = rfc2898.GetBytes(_KeyStrengthInBytes);
|
||||
_generatedPv = rfc2898.GetBytes(2);
|
||||
|
||||
_cryptoGenerated = true;
|
||||
}
|
||||
|
||||
|
||||
public byte[] KeyBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_cryptoGenerated) _GenerateCryptoBytes();
|
||||
return _keyBytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] MacIv
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_cryptoGenerated) _GenerateCryptoBytes();
|
||||
return _MacInitializationVector;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] CalculatedMac;
|
||||
|
||||
|
||||
public void ReadAndVerifyMac(System.IO.Stream s)
|
||||
{
|
||||
bool invalid = false;
|
||||
|
||||
// read integrityCheckVector.
|
||||
// caller must ensure that the file pointer is in the right spot!
|
||||
_StoredMac = new byte[10]; // aka "authentication code"
|
||||
s.Read(_StoredMac, 0, _StoredMac.Length);
|
||||
|
||||
if (_StoredMac.Length != CalculatedMac.Length)
|
||||
invalid = true;
|
||||
|
||||
if (!invalid)
|
||||
{
|
||||
for (int i = 0; i < _StoredMac.Length; i++)
|
||||
{
|
||||
if (_StoredMac[i] != CalculatedMac[i])
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalid)
|
||||
throw new Ionic.Zip.BadStateException("The MAC does not match.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#region DONT_COMPILE_BUT_KEEP_FOR_POTENTIAL_FUTURE_USE
|
||||
#if NO
|
||||
internal class Util
|
||||
{
|
||||
private static void _Format(System.Text.StringBuilder sb1,
|
||||
byte[] b,
|
||||
int offset,
|
||||
int length)
|
||||
{
|
||||
|
||||
System.Text.StringBuilder sb2 = new System.Text.StringBuilder();
|
||||
sb1.Append("0000 ");
|
||||
int i;
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
int x = offset+i;
|
||||
if (i != 0 && i % 16 == 0)
|
||||
{
|
||||
sb1.Append(" ")
|
||||
.Append(sb2)
|
||||
.Append("\n")
|
||||
.Append(String.Format("{0:X4} ", i));
|
||||
sb2.Remove(0,sb2.Length);
|
||||
}
|
||||
sb1.Append(System.String.Format("{0:X2} ", b[x]));
|
||||
if (b[x] >=32 && b[x] <= 126)
|
||||
sb2.Append((char)b[x]);
|
||||
else
|
||||
sb2.Append(".");
|
||||
}
|
||||
if (sb2.Length > 0)
|
||||
{
|
||||
sb1.Append(new String(' ', ((16 - i%16) * 3) + 4))
|
||||
.Append(sb2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal static string FormatByteArray(byte[] b, int limit)
|
||||
{
|
||||
System.Text.StringBuilder sb1 = new System.Text.StringBuilder();
|
||||
|
||||
if ((limit * 2 > b.Length) || limit == 0)
|
||||
{
|
||||
_Format(sb1, b, 0, b.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// first N bytes of the buffer
|
||||
_Format(sb1, b, 0, limit);
|
||||
|
||||
if (b.Length > limit * 2)
|
||||
sb1.Append(String.Format("\n ...({0} other bytes here)....\n", b.Length - limit * 2));
|
||||
|
||||
// last N bytes of the buffer
|
||||
_Format(sb1, b, b.Length - limit, limit);
|
||||
}
|
||||
|
||||
return sb1.ToString();
|
||||
}
|
||||
|
||||
|
||||
internal static string FormatByteArray(byte[] b)
|
||||
{
|
||||
return FormatByteArray(b, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A stream that encrypts as it writes, or decrypts as it reads. The
|
||||
/// Crypto is AES in CTR (counter) mode, which is compatible with the AES
|
||||
/// encryption employed by WinZip 12.0.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The AES/CTR encryption protocol used by WinZip works like this:
|
||||
///
|
||||
/// - start with a counter, initialized to zero.
|
||||
///
|
||||
/// - to encrypt, take the data by 16-byte blocks. For each block:
|
||||
/// - apply the transform to the counter
|
||||
/// - increement the counter
|
||||
/// - XOR the result of the transform with the plaintext to
|
||||
/// get the ciphertext.
|
||||
/// - compute the mac on the encrypted bytes
|
||||
/// - when finished with all blocks, store the computed MAC.
|
||||
///
|
||||
/// - to decrypt, take the data by 16-byte blocks. For each block:
|
||||
/// - compute the mac on the encrypted bytes,
|
||||
/// - apply the transform to the counter
|
||||
/// - increement the counter
|
||||
/// - XOR the result of the transform with the ciphertext to
|
||||
/// get the plaintext.
|
||||
/// - when finished with all blocks, compare the computed MAC against
|
||||
/// the stored MAC
|
||||
///
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
//
|
||||
internal class WinZipAesCipherStream : Stream
|
||||
{
|
||||
private WinZipAesCrypto _params;
|
||||
private System.IO.Stream _s;
|
||||
private CryptoMode _mode;
|
||||
private int _nonce;
|
||||
private bool _finalBlock;
|
||||
|
||||
internal HMACSHA1 _mac;
|
||||
|
||||
// Use RijndaelManaged from .NET 2.0.
|
||||
// AesManaged came in .NET 3.5, but we want to limit
|
||||
// dependency to .NET 2.0. AES is just a restricted form
|
||||
// of Rijndael (fixed block size of 128, some crypto modes not supported).
|
||||
|
||||
internal RijndaelManaged _aesCipher;
|
||||
internal ICryptoTransform _xform;
|
||||
|
||||
private const int BLOCK_SIZE_IN_BYTES = 16;
|
||||
|
||||
private byte[] counter = new byte[BLOCK_SIZE_IN_BYTES];
|
||||
private byte[] counterOut = new byte[BLOCK_SIZE_IN_BYTES];
|
||||
|
||||
// I've had a problem when wrapping a WinZipAesCipherStream inside
|
||||
// a DeflateStream. Calling Read() on the DeflateStream results in
|
||||
// a Read() on the WinZipAesCipherStream, but the buffer is larger
|
||||
// than the total size of the encrypted data, and larger than the
|
||||
// initial Read() on the DeflateStream! When the encrypted
|
||||
// bytestream is embedded within a larger stream (As in a zip
|
||||
// archive), the Read() doesn't fail with EOF. This causes bad
|
||||
// data to be returned, and it messes up the MAC.
|
||||
|
||||
// This field is used to provide a hard-stop to the size of
|
||||
// data that can be read from the stream. In Read(), if the buffer or
|
||||
// read request goes beyond the stop, we truncate it.
|
||||
|
||||
private long _length;
|
||||
private long _totalBytesXferred;
|
||||
private byte[] _PendingWriteBlock;
|
||||
private int _pendingCount;
|
||||
private byte[] _iobuf;
|
||||
|
||||
/// <summary>
|
||||
/// The constructor.
|
||||
/// </summary>
|
||||
/// <param name="s">The underlying stream</param>
|
||||
/// <param name="mode">To either encrypt or decrypt.</param>
|
||||
/// <param name="cryptoParams">The pre-initialized WinZipAesCrypto object.</param>
|
||||
/// <param name="length">The maximum number of bytes to read from the stream.</param>
|
||||
internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, long length, CryptoMode mode)
|
||||
: this(s, cryptoParams, mode)
|
||||
{
|
||||
// don't read beyond this limit!
|
||||
_length = length;
|
||||
//Console.WriteLine("max length of AES stream: {0}", _length);
|
||||
}
|
||||
|
||||
|
||||
#if WANT_TRACE
|
||||
Stream untransformed;
|
||||
String traceFileUntransformed;
|
||||
Stream transformed;
|
||||
String traceFileTransformed;
|
||||
#endif
|
||||
|
||||
|
||||
internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, CryptoMode mode)
|
||||
: base()
|
||||
{
|
||||
TraceOutput("-------------------------------------------------------");
|
||||
TraceOutput("Create {0:X8}", this.GetHashCode());
|
||||
|
||||
_params = cryptoParams;
|
||||
_s = s;
|
||||
_mode = mode;
|
||||
_nonce = 1;
|
||||
|
||||
if (_params == null)
|
||||
throw new BadPasswordException("Supply a password to use AES encryption.");
|
||||
|
||||
int keySizeInBits = _params.KeyBytes.Length * 8;
|
||||
if (keySizeInBits != 256 && keySizeInBits != 128 && keySizeInBits != 192)
|
||||
throw new ArgumentOutOfRangeException("keysize",
|
||||
"size of key must be 128, 192, or 256");
|
||||
|
||||
_mac = new HMACSHA1(_params.MacIv);
|
||||
|
||||
_aesCipher = new System.Security.Cryptography.RijndaelManaged();
|
||||
_aesCipher.BlockSize = 128;
|
||||
_aesCipher.KeySize = keySizeInBits; // 128, 192, 256
|
||||
_aesCipher.Mode = CipherMode.ECB;
|
||||
_aesCipher.Padding = PaddingMode.None;
|
||||
|
||||
byte[] iv = new byte[BLOCK_SIZE_IN_BYTES]; // all zeroes
|
||||
|
||||
// Create an ENCRYPTOR, regardless whether doing decryption or encryption.
|
||||
// It is reflexive.
|
||||
_xform = _aesCipher.CreateEncryptor(_params.KeyBytes, iv);
|
||||
|
||||
if (_mode == CryptoMode.Encrypt)
|
||||
{
|
||||
_iobuf = new byte[2048];
|
||||
_PendingWriteBlock = new byte[BLOCK_SIZE_IN_BYTES];
|
||||
}
|
||||
|
||||
|
||||
#if WANT_TRACE
|
||||
traceFileUntransformed = "unpack\\WinZipAesCipherStream.trace.untransformed.out";
|
||||
traceFileTransformed = "unpack\\WinZipAesCipherStream.trace.transformed.out";
|
||||
|
||||
untransformed = System.IO.File.Create(traceFileUntransformed);
|
||||
transformed = System.IO.File.Create(traceFileTransformed);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void XorInPlace(byte[] buffer, int offset, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
buffer[offset + i] = (byte)(counterOut[i] ^ buffer[offset + i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteTransformOneBlock(byte[] buffer, int offset)
|
||||
{
|
||||
System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
|
||||
_xform.TransformBlock(counter,
|
||||
0,
|
||||
BLOCK_SIZE_IN_BYTES,
|
||||
counterOut,
|
||||
0);
|
||||
XorInPlace(buffer, offset, BLOCK_SIZE_IN_BYTES);
|
||||
_mac.TransformBlock(buffer, offset, BLOCK_SIZE_IN_BYTES, null, 0);
|
||||
}
|
||||
|
||||
|
||||
private void WriteTransformBlocks(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int posn = offset;
|
||||
int last = count + offset;
|
||||
|
||||
while (posn < buffer.Length && posn < last)
|
||||
{
|
||||
WriteTransformOneBlock (buffer, posn);
|
||||
posn += BLOCK_SIZE_IN_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void WriteTransformFinalBlock()
|
||||
{
|
||||
if (_pendingCount == 0)
|
||||
throw new InvalidOperationException("No bytes available.");
|
||||
|
||||
if (_finalBlock)
|
||||
throw new InvalidOperationException("The final block has already been transformed.");
|
||||
|
||||
System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
|
||||
counterOut = _xform.TransformFinalBlock(counter,
|
||||
0,
|
||||
BLOCK_SIZE_IN_BYTES);
|
||||
XorInPlace(_PendingWriteBlock, 0, _pendingCount);
|
||||
_mac.TransformFinalBlock(_PendingWriteBlock, 0, _pendingCount);
|
||||
_finalBlock = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private int ReadTransformOneBlock(byte[] buffer, int offset, int last)
|
||||
{
|
||||
if (_finalBlock)
|
||||
throw new NotSupportedException();
|
||||
|
||||
int bytesRemaining = last - offset;
|
||||
int bytesToRead = (bytesRemaining > BLOCK_SIZE_IN_BYTES)
|
||||
? BLOCK_SIZE_IN_BYTES
|
||||
: bytesRemaining;
|
||||
|
||||
// update the counter
|
||||
System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
|
||||
|
||||
// Determine if this is the final block
|
||||
if ((bytesToRead == bytesRemaining) &&
|
||||
(_length > 0) &&
|
||||
(_totalBytesXferred + last == _length))
|
||||
{
|
||||
_mac.TransformFinalBlock(buffer, offset, bytesToRead);
|
||||
counterOut = _xform.TransformFinalBlock(counter,
|
||||
0,
|
||||
BLOCK_SIZE_IN_BYTES);
|
||||
_finalBlock = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mac.TransformBlock(buffer, offset, bytesToRead, null, 0);
|
||||
_xform.TransformBlock(counter,
|
||||
0, // offset
|
||||
BLOCK_SIZE_IN_BYTES,
|
||||
counterOut,
|
||||
0); // offset
|
||||
}
|
||||
|
||||
XorInPlace(buffer, offset, bytesToRead);
|
||||
return bytesToRead;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void ReadTransformBlocks(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int posn = offset;
|
||||
int last = count + offset;
|
||||
|
||||
while (posn < buffer.Length && posn < last )
|
||||
{
|
||||
int n = ReadTransformOneBlock (buffer, posn, last);
|
||||
posn += n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_mode == CryptoMode.Encrypt)
|
||||
throw new NotSupportedException();
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset",
|
||||
"Must not be less than zero.");
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count",
|
||||
"Must not be less than zero.");
|
||||
|
||||
if (buffer.Length < offset + count)
|
||||
throw new ArgumentException("The buffer is too small");
|
||||
|
||||
// When I wrap a WinZipAesStream in a DeflateStream, the
|
||||
// DeflateStream asks its captive to read 4k blocks, even if the
|
||||
// encrypted bytestream is smaller than that. This is a way to
|
||||
// limit the number of bytes read.
|
||||
|
||||
int bytesToRead = count;
|
||||
|
||||
if (_totalBytesXferred >= _length)
|
||||
{
|
||||
return 0; // EOF
|
||||
}
|
||||
|
||||
long bytesRemaining = _length - _totalBytesXferred;
|
||||
if (bytesRemaining < count) bytesToRead = (int)bytesRemaining;
|
||||
|
||||
int n = _s.Read(buffer, offset, bytesToRead);
|
||||
|
||||
|
||||
#if WANT_TRACE
|
||||
untransformed.Write(buffer, offset, bytesToRead);
|
||||
#endif
|
||||
|
||||
ReadTransformBlocks(buffer, offset, bytesToRead);
|
||||
|
||||
#if WANT_TRACE
|
||||
transformed.Write(buffer, offset, bytesToRead);
|
||||
#endif
|
||||
_totalBytesXferred += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the final HMAC-SHA1-80 for the data that was encrypted.
|
||||
/// </summary>
|
||||
public byte[] FinalAuthentication
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_finalBlock)
|
||||
{
|
||||
// special-case zero-byte files
|
||||
if ( _totalBytesXferred != 0)
|
||||
throw new BadStateException("The final hash has not been computed.");
|
||||
|
||||
// Must call ComputeHash on an empty byte array when no data
|
||||
// has run through the MAC.
|
||||
|
||||
byte[] b = { };
|
||||
_mac.ComputeHash(b);
|
||||
// fall through
|
||||
}
|
||||
byte[] macBytes10 = new byte[10];
|
||||
System.Array.Copy(_mac.Hash, 0, macBytes10, 0, 10);
|
||||
return macBytes10;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_finalBlock)
|
||||
throw new InvalidOperationException("The final block has already been transformed.");
|
||||
|
||||
if (_mode == CryptoMode.Decrypt)
|
||||
throw new NotSupportedException();
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset",
|
||||
"Must not be less than zero.");
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count",
|
||||
"Must not be less than zero.");
|
||||
if (buffer.Length < offset + count)
|
||||
throw new ArgumentException("The offset and count are too large");
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
TraceOutput("Write off({0}) count({1})", offset, count);
|
||||
|
||||
#if WANT_TRACE
|
||||
untransformed.Write(buffer, offset, count);
|
||||
#endif
|
||||
|
||||
// For proper AES encryption, an AES encryptor application calls
|
||||
// TransformBlock repeatedly for all 16-byte blocks except the
|
||||
// last. For the last block, it then calls TransformFinalBlock().
|
||||
//
|
||||
// This class is a stream that encrypts via Write(). But, it's not
|
||||
// possible to recognize which are the "last" bytes from within the call
|
||||
// to Write(). The caller can call Write() several times in succession,
|
||||
// with varying buffers. This class only "knows" that the last bytes
|
||||
// have been written when the app calls Close().
|
||||
//
|
||||
// Therefore, this class buffers writes: After completion every Write(),
|
||||
// a 16-byte "pending" block (_PendingWriteBlock) must hold between 1
|
||||
// and 16 bytes, which will be used in TransformFinalBlock if the app
|
||||
// calls Close() immediately thereafter. Also, every write must
|
||||
// transform any pending bytes, before transforming the data passed in
|
||||
// to the current call.
|
||||
//
|
||||
// In operation, after the first call to Write() and before the call to
|
||||
// Close(), one full or partial block of bytes is always available,
|
||||
// pending. At time of Close(), this class calls
|
||||
// WriteTransformFinalBlock() to flush the pending bytes.
|
||||
//
|
||||
// This approach works whether the caller writes in odd-sized batches,
|
||||
// for example 5000 bytes, or in batches that are neat multiples of the
|
||||
// blocksize (16).
|
||||
//
|
||||
// Logicaly, what we do is this:
|
||||
//
|
||||
// 1. if there are fewer than 16 bytes (pending + current), then
|
||||
// just copy them into th pending buffer and return.
|
||||
//
|
||||
// 2. there are more than 16 bytes to write. So, take the leading slice
|
||||
// of bytes from the current buffer, enough to fill the pending
|
||||
// buffer. Transform the pending block, and write it out.
|
||||
//
|
||||
// 3. Take the trailing slice of bytes (a full block or a partial block),
|
||||
// and copy it to the pending block for next time.
|
||||
//
|
||||
// 4. transform and write all the other blocks, the middle slice.
|
||||
//
|
||||
|
||||
// There are 16 or fewer bytes, so just buffer the bytes.
|
||||
if (count + _pendingCount <= BLOCK_SIZE_IN_BYTES)
|
||||
{
|
||||
Buffer.BlockCopy(buffer,
|
||||
offset,
|
||||
_PendingWriteBlock,
|
||||
_pendingCount,
|
||||
count);
|
||||
_pendingCount += count;
|
||||
|
||||
// At this point, _PendingWriteBlock contains up to
|
||||
// BLOCK_SIZE_IN_BYTES bytes, and _pendingCount ranges from 0 to
|
||||
// BLOCK_SIZE_IN_BYTES. We don't want to xform+write them yet,
|
||||
// because this may have been the last block. The last block gets
|
||||
// written at Close().
|
||||
return;
|
||||
}
|
||||
|
||||
// We know there are at least 17 bytes, counting those in the current
|
||||
// buffer, along with the (possibly empty) pending block.
|
||||
|
||||
int bytesRemaining = count;
|
||||
int curOffset = offset;
|
||||
|
||||
// workitem 12815
|
||||
//
|
||||
// xform chunkwise ... Cannot transform in place using the original
|
||||
// buffer because that is user-maintained.
|
||||
|
||||
if (_pendingCount != 0)
|
||||
{
|
||||
// We have more than one block of data to write, therefore it is safe
|
||||
// to xform+write.
|
||||
int fillCount = BLOCK_SIZE_IN_BYTES - _pendingCount;
|
||||
|
||||
// fillCount is possibly zero here. That happens when the pending
|
||||
// buffer held 16 bytes (one complete block) before this call to
|
||||
// Write.
|
||||
if (fillCount > 0)
|
||||
{
|
||||
Buffer.BlockCopy(buffer,
|
||||
offset,
|
||||
_PendingWriteBlock,
|
||||
_pendingCount,
|
||||
fillCount);
|
||||
|
||||
// adjust counts:
|
||||
bytesRemaining -= fillCount;
|
||||
curOffset += fillCount;
|
||||
}
|
||||
|
||||
// xform and write:
|
||||
WriteTransformOneBlock(_PendingWriteBlock, 0);
|
||||
_s.Write(_PendingWriteBlock, 0, BLOCK_SIZE_IN_BYTES);
|
||||
_totalBytesXferred += BLOCK_SIZE_IN_BYTES;
|
||||
_pendingCount = 0;
|
||||
}
|
||||
|
||||
// At this point _PendingWriteBlock is empty, and bytesRemaining is
|
||||
// always greater than 0.
|
||||
|
||||
// Now, xform N blocks, where N = floor((bytesRemaining-1)/16). If
|
||||
// writing 32 bytes, then xform 1 block, and stage the remaining 16. If
|
||||
// writing 10037 bytes, xform 627 blocks of 16 bytes, then stage the
|
||||
// remaining 5 bytes.
|
||||
|
||||
int blocksToXform = (bytesRemaining-1)/BLOCK_SIZE_IN_BYTES;
|
||||
_pendingCount = bytesRemaining - (blocksToXform * BLOCK_SIZE_IN_BYTES);
|
||||
|
||||
// _pendingCount is ALWAYS between 1 and 16.
|
||||
// Put the last _pendingCount bytes into the pending block.
|
||||
Buffer.BlockCopy(buffer,
|
||||
curOffset + bytesRemaining - _pendingCount,
|
||||
_PendingWriteBlock,
|
||||
0,
|
||||
_pendingCount);
|
||||
bytesRemaining -= _pendingCount;
|
||||
_totalBytesXferred += bytesRemaining; // will be true after the loop
|
||||
|
||||
// now, transform all the full blocks preceding that.
|
||||
// bytesRemaining is always a multiple of 16 .
|
||||
if (blocksToXform > 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
int c = _iobuf.Length;
|
||||
if (c > bytesRemaining) c = bytesRemaining;
|
||||
Buffer.BlockCopy(buffer,
|
||||
curOffset,
|
||||
_iobuf,
|
||||
0,
|
||||
c);
|
||||
|
||||
WriteTransformBlocks(_iobuf, 0, c);
|
||||
_s.Write(_iobuf, 0, c);
|
||||
bytesRemaining -= c;
|
||||
curOffset += c;
|
||||
} while(bytesRemaining > 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Close the stream.
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
TraceOutput("Close {0:X8}", this.GetHashCode());
|
||||
|
||||
// In the degenerate case, no bytes have been written to the
|
||||
// stream at all. Need to check here, and NOT emit the
|
||||
// final block if Write has not been called.
|
||||
if (_pendingCount > 0)
|
||||
{
|
||||
WriteTransformFinalBlock();
|
||||
_s.Write(_PendingWriteBlock, 0, _pendingCount);
|
||||
_totalBytesXferred += _pendingCount;
|
||||
_pendingCount = 0;
|
||||
}
|
||||
_s.Close();
|
||||
|
||||
#if WANT_TRACE
|
||||
untransformed.Close();
|
||||
transformed.Close();
|
||||
Console.WriteLine("\nuntransformed bytestream is in {0}", traceFileUntransformed);
|
||||
Console.WriteLine("\ntransformed bytestream is in {0}", traceFileTransformed);
|
||||
#endif
|
||||
TraceOutput("-------------------------------------------------------");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the stream can be read.
|
||||
/// </summary>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mode != CryptoMode.Decrypt) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Always returns false.
|
||||
/// </summary>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the CryptoMode is Encrypt.
|
||||
/// </summary>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return (_mode == CryptoMode.Encrypt); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the content in the stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
_s.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getting this property throws a NotImplementedException.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getting or Setting this property throws a NotImplementedException.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method throws a NotImplementedException.
|
||||
/// </summary>
|
||||
public override long Seek(long offset, System.IO.SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method throws a NotImplementedException.
|
||||
/// </summary>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
[System.Diagnostics.ConditionalAttribute("Trace")]
|
||||
private void TraceOutput(string format, params object[] varParams)
|
||||
{
|
||||
lock(_outputLock)
|
||||
{
|
||||
int tid = System.Threading.Thread.CurrentThread.GetHashCode();
|
||||
Console.ForegroundColor = (ConsoleColor) (tid % 8 + 8);
|
||||
Console.Write("{0:000} WZACS ", tid);
|
||||
Console.WriteLine(format, varParams);
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
private object _outputLock = new Object();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue