/*******************************************************************************
* 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
*
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.IO;
namespace Amazon.Extensions.S3.Encryption.Util
{
///
/// A wrapper stream that encrypts the base stream using AES GCM algorithm and caches the contents as it
/// is being read.
///
public class AesGcmEncryptCachingStream : AesGcmEncryptStream
{
private long _position;
///
/// Gets or sets the position within the current stream.
///
public override long Position
{
get => _position;
set
{
if (value < _readBufferStartPosition || value > _position)
{
throw new NotSupportedException($"New position must be >= {_readBufferStartPosition} and <= {_position}");
}
_position = value;
}
}
///
public override bool CanSeek => true;
///
/// Buffer to cache the read bytes
///
private readonly List _readBuffer;
///
/// Offset since _readBuffer has the read cache
///
private long _readBufferStartPosition;
///
/// Constructor for initializing encryption stream
///
/// Original data stream
/// Key to be used for encryption
/// Nonce to be used for encryption
/// Tag size for the tag appended in the end of the stream
/// Additional associated data
public AesGcmEncryptCachingStream(Stream baseStream, byte[] key, byte[] nonce, int tagSize, byte[] associatedText = null)
: base(baseStream, key, nonce, tagSize, associatedText)
{
_readBuffer = new List();
}
///
/// Reads and cache a sequence of encrypted bytes in _readBuffer from the current stream and advances the position
/// within the stream by the number of bytes read.
/// If current position lies in between lower bound and upper bound of _readBuffer, reads from the _readBuffer,
/// else read from the original stream
///
///
/// 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.
///
///
/// Underlying crypto exception wrapped in Amazon exception
///
public override int Read(byte[] buffer, int offset, int count)
{
var previousPosition = _position;
CopyFromReadBuffer(buffer, ref offset, ref count);
var readBytes = base.Read(buffer, offset, count);
AddReadBytesToReadBuffer(buffer, offset, readBytes);
return (int)(_position - previousPosition);
}
#if AWS_ASYNC_API
///
/// Asynchronously reads and cache a sequence of encrypted bytes in _readBuffer from the current stream, advances
/// the position within the stream by the number of bytes read, and monitors
/// cancellation requests.
/// If current position lies in between lower bound and upper bound of _readBuffer, reads from the _readBuffer,
/// else read from the original stream
///
///
/// 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.
///
///
/// Underlying crypto exception wrapped in Amazon exception
///
public override async System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken)
{
var previousPosition = _position;
CopyFromReadBuffer(buffer, ref offset, ref count);
var readBytes = await base.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
AddReadBytesToReadBuffer(buffer, offset, readBytes);
return (int)(_position - previousPosition);
}
#endif
///
/// Copies bytes to buffer if the current position lies between lower and upper bound of the _readBuffer and
/// advances the current position.
///
///
/// 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 copied from the _readBuffer.
///
///
/// 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 copied from _readBuffer.
///
private void CopyFromReadBuffer(byte[] buffer, ref int offset, ref int count)
{
var readBufferOffset = (int)(_position - _readBufferStartPosition);
var bytesToCopy = Math.Min(_readBuffer.Count - readBufferOffset, count);
if (bytesToCopy == 0)
{
return;
}
_readBuffer.CopyTo(readBufferOffset, buffer, offset, bytesToCopy);
offset += bytesToCopy;
count -= bytesToCopy;
_position += bytesToCopy;
}
///
/// Add read bytes to _readBuffer and advances the current position
///
///
/// An array of bytes containing read bytes
///
/// The zero-based byte offset in buffer at which read byte are stored
///
///
/// Total number of read bytes
///
private void AddReadBytesToReadBuffer(byte[] buffer, int offset, int readBytes)
{
if (readBytes == 0)
{
return;
}
_readBuffer.AddRange(buffer.Skip(offset).Take(readBytes));
_position += readBytes;
}
///
/// Clear read bytes buffer before current position
///
public void ClearReadBufferToPosition()
{
var bytesToRemove = (int)Math.Min(_position - _readBufferStartPosition, _readBuffer.Count);
_readBufferStartPosition = _position;
_readBuffer.RemoveRange(0, bytesToRemove);
}
}
}