using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Security.Cryptography; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using System.Threading; namespace EncryptedNetwork { internal interface IEncryptedContainer { EncryptedNetworkStream Parent { get; } EncryptedNetworkStreamBlock WriteBlock(); EncryptedNetworkStreamBlock ReadBlock(); EncryptedNetworkStreamBlock ReadBlock(int msTimeout); Task ReadBlockAsync(CancellationToken cancel); Task ReadBlockAsync(); } /// /// An encrypted block container for an . /// public abstract class EncryptedNetworkStreamBlock : BackedStream, IEncryptedContainer { internal EncryptedNetworkStreamBlock(Stream back) : base(back) { } /// /// The EncryptedNetworkStream that owns this block. /// public abstract EncryptedNetworkStream Parent { get; } /// /// Start a Read block synchronously with a milisecond timeout. /// /// The timeout /// The new Read block. public abstract EncryptedNetworkStreamBlock ReadBlock(int msTimeout); /// /// Start a Read block synchronously. /// /// The new Read block. public abstract EncryptedNetworkStreamBlock ReadBlock(); /// /// Start a Write block. /// /// public abstract EncryptedNetworkStreamBlock WriteBlock(); /// /// Start a Read block asynchronously. /// /// Token for cancellation. /// Awaitable Task for new Read block. public async virtual Task ReadBlockAsync(CancellationToken cancel) { return await Task.Run(ReadBlock); } /// /// Start a Read block asynchronously. /// /// Awaitable Task for new Read block. public virtual Task ReadBlockAsync() => ReadBlockAsync(CancellationToken.None); } /// /// Proveides RSA & AES cryptography wrapper over a . /// public class EncryptedNetworkStream : BackedStream, IEncryptedContainer { private class EncryptedReadBlock : EncryptedNetworkStreamBlock { private AesCryptoServiceProvider aes = null; EncryptedNetworkStream ens; public EncryptedReadBlock(BackedStream ens, IEncryptedContainer container) : base(ens) { KeepBackingStreamAlive = true; this.ens = container.Parent; } public override bool KeepBackingStreamAlive { get => true; set { if (!value) throw new NotSupportedException("Cannot set ReadBlock to dispose of its parent."); } } public override EncryptedNetworkStream Parent => ens; public async Task Initialise(CancellationToken cancel) { ens.ThrowIfNotExchanged(); aes = new AesCryptoServiceProvider(); try { var len = (await backing.BlockingReadValueUnmanagedAsync(cancel)).NetOrd(); if (len <= 0) throw new ArgumentException("Invalid length read. ("+len+")"); byte[] by = new byte[len]; await backing.BlockingReadAsync(by, 0, len, cancel); var decrypted = ens.you.Decrypt(by, false); var key = decrypted.ToStructureUnmanaged(); key.ToCSP(aes); } catch(Exception ex) { aes.Dispose(); aes = null; throw ex; } } public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).Sync(); public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { static int roundUp(int numToRound, int multiple) { if (multiple == 0) return numToRound; int remainder = Math.Abs(numToRound) % multiple; if (remainder == 0) return numToRound; if (numToRound < 0) return -(Math.Abs(numToRound) - remainder); return numToRound + multiple - remainder; } if (aes == null) return await backing.ReadAsync(buffer, offset, count, cancellationToken); else { byte[] byr = new byte[count % 16 == 0 ? count + 16 : roundUp(count, 16)]; await backing.BlockingReadAsync(byr, 0, byr.Length, cancellationToken); using (var dec = aes.CreateDecryptor()) { Array.Copy(dec.TransformFinalBlock(byr, 0, byr.Length), 0, buffer, offset, count); return count; } } } public override void Write(byte[] buffer, int offset, int count) => backing.Write(buffer, offset, count); public override EncryptedNetworkStreamBlock WriteBlock() { var w = new EncryptedWriteBlock(this, this); w.Initialise(); return w; } public override EncryptedNetworkStreamBlock ReadBlock() { var r = new EncryptedReadBlock(this, this); r.Initialise(CancellationToken.None).Sync(); return r; } public override EncryptedNetworkStreamBlock ReadBlock(int msTimeout) { var r = new EncryptedReadBlock(this, this); r.Initialise(CancellationToken.None).Sync(msTimeout); return r; } public async override Task ReadBlockAsync(CancellationToken cancel) { var r = new EncryptedReadBlock(this, this); await r.Initialise(cancel); return r; } ~EncryptedReadBlock() { Dispose(false); aes = null; } } private class EncryptedWriteBlock : EncryptedNetworkStreamBlock { private AesCryptoServiceProvider aes = null; EncryptedNetworkStream ens; public override EncryptedNetworkStream Parent => ens; public override bool KeepBackingStreamAlive { get => true; set { if (!value) throw new NotSupportedException("Cannot set WriteBlock to dispose of its parent."); } } public EncryptedWriteBlock(BackedStream ens, IEncryptedContainer container) : base(ens) { KeepBackingStreamAlive = true; this.ens = container.Parent; } public void Initialise() { ens.ThrowIfNotExchanged(); aes = new AesCryptoServiceProvider(); try { var key = AESKey.NewKey(); key.ToCSP(aes); var encrypted = ens.them.Encrypt(key.ToByteArrayUnmanaged(), false); var size = encrypted.Length.NetOrd(); backing.WriteValueUnmanaged(size); backing.Write(encrypted, 0, encrypted.Length); } catch(Exception ex) { aes.Dispose(); aes = null; throw ex; } } protected override void Dispose(bool disposing) { base.Dispose(disposing); aes.Dispose(); } public override int Read(byte[] buffer, int offset, int count) => backing.Read(buffer, offset, count); public override void Write(byte[] buffer, int offset, int count) { if (aes == null) backing.Write(buffer, offset, count); else { using(var enc = aes.CreateEncryptor()) { var ebuf = enc.TransformFinalBlock(buffer, offset, count); backing.Write(ebuf, 0, ebuf.Length); } } } public override EncryptedNetworkStreamBlock WriteBlock() { var w = new EncryptedWriteBlock(this, this); w.Initialise(); return w; } public override EncryptedNetworkStreamBlock ReadBlock() { var r = new EncryptedReadBlock(this, this); r.Initialise(CancellationToken.None).Sync(); return r; } public override EncryptedNetworkStreamBlock ReadBlock(int msTimeout) { var r = new EncryptedReadBlock(this, this); r.Initialise(CancellationToken.None).Sync(msTimeout); return r; } public async override Task ReadBlockAsync(CancellationToken cancel) { var r = new EncryptedReadBlock(this, this); await r.Initialise(cancel); return r; } ~EncryptedWriteBlock() { Dispose(false); aes = null; } } private RSACryptoServiceProvider you; private RSACryptoServiceProvider them; /// /// Your local RSA CSP (with both public and private keys.) /// public RSACryptoServiceProvider PrivateCSP => you; /// /// Your local RSA public key. /// public RSAPublicKey LocalPublicKey => RSAPublicKey.FromCSP(you); /// /// Remote endpoint's RSA public key. /// /// Thrown if RSA public keys have not been exchanged yet. public RSAPublicKey RemotePublicKey => them == null ? throw ThrowNotYetExchangedException() : RSAPublicKey.FromCSP(them); private static ArgumentException ThrowNotYetExchangedException() => throw new ArgumentException("Keys not yet exchanged."); private void ThrowIfNotExchanged() { if (!Exchanged) ThrowNotYetExchangedException(); } /// /// Have RSA public keys been exchanged yet? /// public bool Exchanged => them != null; EncryptedNetworkStream IEncryptedContainer.Parent => this; /// /// Initialise a new from a /// /// The Stream to set backing for. /// Your local RSA CSP to use for private a public keys. public EncryptedNetworkStream(NetworkStream stream, RSACryptoServiceProvider key) :base(stream) { you = key; } /// /// Initialise a new from a /// /// The Stream to set backing for. public EncryptedNetworkStream(NetworkStream stream) : this(stream, new RSACryptoServiceProvider()) { KeepPrivateCSPAlive = false; } /// /// Initialise a new from a /// /// The Socket to set backing for. (NOTE: Closes the socket on dispose) /// Your local RSA CSP to use for private a public keys. public EncryptedNetworkStream(Socket sock, RSACryptoServiceProvider key) : this(new NetworkStream(sock, true), key) { } /// /// Initialise a new from a /// /// The Socket to set backing for. (NOTE: Closes the socket on dispose) public EncryptedNetworkStream(Socket sock) : this(sock, new RSACryptoServiceProvider()) { KeepPrivateCSPAlive = false; } /// /// Exchange the RSA public keys asynchronously. /// /// Awaitable Task that completes when the operation is successful. public Task ExchangeAsync() => ExchangeAsync(CancellationToken.None); /// /// Exchange the RSA public keys asynchronously. /// /// Cancellation token. /// Awaitable Task that completes when the operation is successful. public async Task ExchangeAsync(CancellationToken cancel) { backing.WriteValueUnmanaged(LocalPublicKey); try { var pub = await backing.BlockingReadValueUnmanagedAsync(cancel); them??= new RSACryptoServiceProvider(); pub.ToCSP(them); } catch (Exception ex) { them?.Dispose(); them = null; throw ex; } } /// /// Exchange RSA public keys synchronously with a milisecond timeout. /// /// The timout. public void Exchange(int msTimeout) => ExchangeAsync().Sync(msTimeout); /// /// Exchange RSA public keys synchronously. /// public void Exchange() => ExchangeAsync().Sync(); /// /// Keep alive after disposing? /// public bool KeepPrivateCSPAlive { get; set; } = true; protected override void Dispose(bool disposing) { base.Dispose(disposing); if(disposing) { if (!KeepPrivateCSPAlive) you.Dispose(); them?.Dispose(); } you = null; them = null; } ~EncryptedNetworkStream() => Dispose(false); /// /// Read unencrypted data from the backing stream. /// /// Buffer to read into. /// Offset of buffer. /// Number of bytes to read into buffer. /// The number of bytes successfully read. public override int Read(byte[] buffer, int offset, int count) => backing.Read(buffer, offset, count); /// /// Write unencrypted data to the backing stream. /// /// Buffer to write from. /// Offset of buffer. /// Number of bytes to write from buffer. public override void Write(byte[] buffer, int offset, int count) => backing.Write(buffer, offset, count); /// /// Create a new encrypted write block for this stream. /// /// The new WriteBlock. public EncryptedNetworkStreamBlock WriteBlock() { var w = new EncryptedWriteBlock(this, this); w.Initialise(); return w; } /// /// Create a new encrypted read block for this stream synchronously. /// /// The new ReadBlock. public EncryptedNetworkStreamBlock ReadBlock() { var r = new EncryptedReadBlock(this, this); r.Initialise(CancellationToken.None).Sync(); return r; } /// /// Create a new encrypted read block for this stream synchronously with a milisecond timeout. /// /// The timeout. /// The new ReadBlock. public EncryptedNetworkStreamBlock ReadBlock(int msTimeout) { var r = new EncryptedReadBlock(this, this); r.Initialise(CancellationToken.None).Sync(msTimeout); return r; } /// /// Create a new encrypted read block for this stream asynchronously. /// /// Cancellation token. /// Awaitable Task that completes and returns the new ReadBlock. public async Task ReadBlockAsync(CancellationToken cancel) { var r = new EncryptedReadBlock(this, this); await r.Initialise(cancel); return r; } /// /// Create a new encrypted read block for this stream asynchronously. /// /// Awaitable Task that completes and returns the new ReadBlock. public Task ReadBlockAsync() => ReadBlockAsync(CancellationToken.None); } }