// (c) Copyright slimCODE Software Inc. - www.slimcode.com
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace SlimCode.Utils
{
internal class AuthenticationStream : Stream
{
public AuthenticationStream( Stream innerStream, byte[] authenticationKey, AuthenticationType authenticationType, bool writing )
{
if( innerStream == null )
throw new ArgumentNullException( "innerStream" );
if( writing && !innerStream.CanWrite )
throw new ArgumentException( "Cannot write to inner stream.", "innerStream" );
if( !writing && !innerStream.CanRead )
throw new ArgumentException( "Cannot read from inner stream.", "innerStream" );
if( authenticationKey == null )
throw new ArgumentNullException( "authenticationKey" );
if( authenticationKey.Length < 16 )
throw new ArgumentException( "The authentication key cannot be less than 16 bytes." );
switch( authenticationType )
{
case AuthenticationType.MD5:
m_hmac = new HMACMD5( authenticationKey );
break;
case AuthenticationType.SHA1:
m_hmac = new HMACSHA1( authenticationKey, true );
break;
case AuthenticationType.SHA256:
m_hmac = new HMACSHA256( authenticationKey );
break;
case AuthenticationType.SHA384:
m_hmac = new HMACSHA384( authenticationKey );
break;
case AuthenticationType.SHA512:
m_hmac = new HMACSHA512( authenticationKey );
break;
default:
throw new ArgumentException( "Unknown authentication type.", "authenticationType" );
}
m_innerStream = innerStream;
m_writing = writing;
}
public override bool CanRead
{
get { return !m_writing; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return m_writing; }
}
public override void Flush()
{
m_innerStream.Flush();
}
public override long Length
{
get { return m_innerStream.Length; }
}
public override long Position
{
get { return m_innerStream.Position; }
set { throw new NotSupportedException( "This stream does not support seeking." ); }
}
public override int Read( byte[] buffer, int offset, int count )
{
if( m_writing )
throw new IOException( "Cannot read from this stream. It is open for writing." );
if( buffer == null )
throw new ArgumentNullException( "buffer" );
if( ( offset < 0 ) || ( offset >= buffer.Length ) )
throw new ArgumentOutOfRangeException( "offset" );
if( count < 0 )
throw new ArgumentOutOfRangeException( "count" );
if( offset + count > buffer.Length )
throw new ArgumentOutOfRangeException( "count" );
if( count == 0 )
return 0;
int read = 0;
int hashSize = m_hmac.HashSize / 8;
if( m_bufferAvailable > hashSize )
{
read = ( count < ( m_bufferAvailable - hashSize ) ) ? ( count ) : ( m_bufferAvailable - hashSize );
Array.Copy( m_buffer, 0, buffer, offset, read );
m_hmac.TransformBlock( buffer, offset, read, buffer, offset );
offset += read;
count -= read;
m_bufferAvailable -= read;
Array.Copy( m_buffer, read, m_buffer, 0, m_bufferAvailable );
}
if( count > 0 )
{
int innerRead = m_innerStream.Read( m_buffer, m_bufferAvailable, m_buffer.Length - m_bufferAvailable );
if( innerRead > 0 )
{
m_bufferAvailable += innerRead;
read += this.Read( buffer, offset, count );
}
}
return read;
}
public override long Seek( long offset, SeekOrigin origin )
{
throw new NotSupportedException( "This stream does not support seeking." );
}
public override void SetLength( long value )
{
throw new NotSupportedException( "This stream does not support seeking." );
}
public override void Write( byte[] buffer, int offset, int count )
{
if( !m_writing )
throw new IOException( "Cannot write to this stream. It is open for reading." );
if( buffer == null )
throw new ArgumentNullException( "buffer" );
if( ( offset < 0 ) || ( offset >= buffer.Length ) )
throw new ArgumentOutOfRangeException( "offset" );
if( count < 0 )
throw new ArgumentOutOfRangeException( "count" );
if( offset + count > buffer.Length )
throw new ArgumentOutOfRangeException( "count" );
if( count > 0 )
{
m_hmac.TransformBlock( buffer, offset, count, buffer, offset );
m_innerStream.Write( buffer, offset, count );
}
}
protected override void Dispose( bool disposing )
{
try
{
if( disposing )
{
m_hmac.TransformFinalBlock( new byte[ 0 ], 0, 0 );
byte[] hash = m_hmac.Hash;
System.Diagnostics.Trace.Assert( hash.Length == ( m_hmac.HashSize / 8 ) );
if( m_writing )
{
System.Diagnostics.Trace.Assert( hash.Length == ( m_hmac.HashSize / 8 ) );
m_innerStream.Write( hash, 0, hash.Length );
}
else
{
if( hash.Length != m_bufferAvailable )
throw new IOException( "Invalid authentication hash length." );
for( int i = 0; i < hash.Length; i++ )
{
if( hash[ i ] != m_buffer[ i ] )
throw new IOException( "Invalid authentication hash signature." );
}
}
m_innerStream.Close();
( ( IDisposable )m_hmac ).Dispose();
}
m_innerStream = null;
m_hmac = null;
}
finally
{
base.Dispose( disposing );
}
}
private byte[] m_buffer = new byte[ 32768 ];
private int m_bufferAvailable; // = 0
private Stream m_innerStream; // = null
private bool m_writing; // = false
private HMAC m_hmac; // = null
}
}