/*******************************************************************************
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
* this file except in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file.
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
* *****************************************************************************
* __ _ _ ___
* ( )( \/\/ )/ __)
* /__\ \ / \__ \
* (_)(_) \/\/ (___/
*
* AWS SDK for .NET
* API Version: 2006-03-01
*
*/
using System;
using System.IO;
using Amazon.Runtime;
using System.Collections;
using System.Diagnostics;
#if AWS_ASYNC_API
using System.Threading;
using System.Threading.Tasks;
#endif
namespace Amazon.Runtime.Internal.Util
{
///
/// A wrapper stream that encrypts the base stream as it
/// is being read.
///
public abstract class EncryptStream : WrapperStream
{
#region Properties
protected IEncryptionWrapper Algorithm { get; set; }
private const int internalEncryptionBlockSize = 16;
private byte[] internalBuffer;
private bool performedLastBlockTransform;
#endregion
#region Constructors
///
/// Initializes an EncryptStream with an encryption algorithm and a base stream.
///
/// Stream to perform encryption on..
protected EncryptStream(Stream baseStream)
: base(baseStream)
{
performedLastBlockTransform = false;
internalBuffer = new byte[internalEncryptionBlockSize];
ValidateBaseStream();
}
#endregion
#region Stream overrides
///
/// Reads a sequence of bytes from the current stream and advances the position
/// within the stream by the number of bytes read.
///
///
/// An array of bytes. When this method returns, the buffer contains the specified
/// byte array with the values between offset and (offset + count - 1) replaced
/// by the bytes read from the current source.
///
///
/// The zero-based byte offset in buffer at which to begin storing the data read
/// from the current stream.
///
///
/// The maximum number of bytes to be read from the current stream.
///
///
/// The total number of bytes read into the buffer. This can be less than the
/// number of bytes requested if that many bytes are not currently available,
/// or zero (0) if the end of the stream has been reached.
///
public override int Read(byte[] buffer, int offset, int count)
{
if (performedLastBlockTransform)
return 0;
long previousPosition = this.Position;
int maxBytesRead = count - (count % internalEncryptionBlockSize);
int readBytes = base.Read(buffer, offset, maxBytesRead);
return Append(buffer, offset, previousPosition, readBytes);
}
#if AWS_ASYNC_API
///
/// Asynchronously reads a sequence of bytes from the current stream, advances
/// the position within the stream by the number of bytes read, and monitors
/// cancellation requests.
///
///
/// An array of bytes. When this method returns, the buffer contains the specified
/// byte array with the values between offset and (offset + count - 1) replaced
/// by the bytes read from the current source.
///
///
/// The zero-based byte offset in buffer at which to begin storing the data read
/// from the current stream.
///
///
/// The maximum number of bytes to be read from the current stream.
///
///
/// The token to monitor for cancellation requests. The default value is
/// System.Threading.CancellationToken.None.
///
///
/// A task that represents the asynchronous read operation. The value of the TResult
/// parameter contains the total number of bytes read into the buffer. This can be
/// less than the number of bytes requested if that many bytes are not currently
/// available, or zero (0) if the end of the stream has been reached.
///
public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (performedLastBlockTransform)
return 0;
long previousPosition = this.Position;
int maxBytesRead = count - (count % internalEncryptionBlockSize);
int readBytes = await base.ReadAsync(buffer, offset, maxBytesRead, cancellationToken).ConfigureAwait(false);
return Append(buffer, offset, previousPosition, readBytes);
}
#endif
private int Append(byte[] buffer, int offset, long previousPosition, int readBytes)
{
if (readBytes == 0)
{
byte[] finalBytes = Algorithm.AppendLastBlock(buffer, offset, 0);
finalBytes.CopyTo(buffer, offset);
performedLastBlockTransform = true;
return finalBytes.Length;
}
long currentPosition = previousPosition;
while (this.Position - currentPosition >= internalEncryptionBlockSize)
{
currentPosition += Algorithm.AppendBlock(buffer, offset, internalEncryptionBlockSize, internalBuffer, 0);
Buffer.BlockCopy(internalBuffer, 0, buffer, offset, internalEncryptionBlockSize);
offset = offset + internalEncryptionBlockSize;
}
if ((this.Length - this.Position) < internalEncryptionBlockSize)
{
byte[] finalBytes = Algorithm.AppendLastBlock(buffer, offset, (int)(this.Position - currentPosition));
finalBytes.CopyTo(buffer, offset);
currentPosition += finalBytes.Length;
performedLastBlockTransform = true;
}
return (int)(currentPosition - previousPosition);
}
#if BCL
public override void Close()
{
base.Close();
}
#endif
///
/// Gets a value indicating whether the current stream supports seeking.
///
public override bool CanSeek
{
get
{
return true;
}
}
///
/// Returns encrypted content length.
///
public override long Length
{
get
{
if (base.Length % internalEncryptionBlockSize == 0)
{
return (base.Length + internalEncryptionBlockSize);
}
else
{
return (base.Length + internalEncryptionBlockSize - (base.Length % internalEncryptionBlockSize));
}
}
}
///
/// Gets or sets the position within the current stream.
///
public override long Position
{
get
{
return BaseStream.Position;
}
set
{
Seek(offset: value, origin: SeekOrigin.Begin);
}
}
///
/// Sets the position within the current stream.
///
/// A byte offset relative to the origin parameter.
///
/// A value of type System.IO.SeekOrigin indicating the reference point used
/// to obtain the new position.
/// The new position within the current stream.
public override long Seek(long offset, SeekOrigin origin)
{
long position = BaseStream.Seek(offset, origin);
this.performedLastBlockTransform = false;
this.Algorithm.Reset();
return position;
}
#endregion
#region Private methods
///
/// Validates the underlying stream.
///
private void ValidateBaseStream()
{
if (!BaseStream.CanRead && !BaseStream.CanWrite)
throw new InvalidDataException("EncryptStream does not support base streams that are not capable of reading or writing");
}
#endregion
}
///
/// A wrapper stream that encrypts the base stream as it
/// is being read.
///
public class EncryptStream : EncryptStream
where T : class, IEncryptionWrapper, new()
{
#region Constructors
///
/// Initializes an EncryptStream with an encryption algorithm and a base stream.
///
/// Stream to perform encryption on..
/// Symmetric key to perform encryption
/// Initialization vector to perform encryption
public EncryptStream(Stream baseStream, byte[] key, byte[] IV)
: base(baseStream)
{
Algorithm = new T();
Algorithm.SetEncryptionData(key, IV);
Algorithm.CreateEncryptor();
}
#endregion
}
///
/// A wrapper stream that encrypts the base stream using AES algorithm as it
/// is being read.
///
public class AESEncryptionPutObjectStream : EncryptStream
{
#region Constructors
///
/// Initializes an AESEncryptionStream with a base stream.
///
/// Stream to perform encryption on..
/// Symmetric key to perform encryption
/// Initialization vector to perform encryption
public AESEncryptionPutObjectStream(Stream baseStream, byte[] key, byte[] IV)
: base(baseStream, key, IV) { }
#endregion
}
}