| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228 |
- // Transport Security Layer (TLS)
- // Copyright (c) 2003-2004 Carlos Guzman Alvarez
- // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- using System;
- using System.Collections;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using System.Security.Cryptography;
- using System.Security.Cryptography.X509Certificates;
- using System.Threading;
- namespace Mono.Security.Protocol.Tls
- {
- public abstract class SslStreamBase: Stream, IDisposable
- {
- private delegate void AsyncHandshakeDelegate(InternalAsyncResult asyncResult, bool fromWrite);
-
- #region Fields
- static ManualResetEvent record_processing = new ManualResetEvent (true);
- private const int WaitTimeOut = 5 * 60 * 1000;
- internal Stream innerStream;
- internal MemoryStream inputBuffer;
- internal Context context;
- internal RecordProtocol protocol;
- internal bool ownsStream;
- private volatile bool disposed;
- private bool checkCertRevocationStatus;
- private object negotiate;
- private object read;
- private object write;
- private ManualResetEvent negotiationComplete;
- #endregion
- #region Constructors
- protected SslStreamBase(
- Stream stream,
- bool ownsStream)
- {
- if (stream == null)
- {
- throw new ArgumentNullException("stream is null.");
- }
- if (!stream.CanRead || !stream.CanWrite)
- {
- throw new ArgumentNullException("stream is not both readable and writable.");
- }
- this.inputBuffer = new MemoryStream();
- this.innerStream = stream;
- this.ownsStream = ownsStream;
- this.negotiate = new object();
- this.read = new object();
- this.write = new object();
- this.negotiationComplete = new ManualResetEvent(false);
- }
- #endregion
- #region Handshakes
- private void AsyncHandshakeCallback(IAsyncResult asyncResult)
- {
- InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
- try
- {
- try
- {
- this.OnNegotiateHandshakeCallback(asyncResult);
- }
- catch (TlsException ex)
- {
- this.protocol.SendAlert(ex.Alert);
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- catch (Exception ex)
- {
- this.protocol.SendAlert(AlertDescription.InternalError);
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- if (internalResult.ProceedAfterHandshake)
- {
- //kick off the read or write process (whichever called us) after the handshake is complete
- if (internalResult.FromWrite)
- {
- InternalBeginWrite(internalResult);
- }
- else
- {
- InternalBeginRead(internalResult);
- }
- negotiationComplete.Set();
- }
- else
- {
- negotiationComplete.Set();
- internalResult.SetComplete();
- }
- }
- catch (Exception ex)
- {
- negotiationComplete.Set();
- internalResult.SetComplete(ex);
- }
- }
- internal bool MightNeedHandshake
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return false;
- }
- else
- {
- lock (this.negotiate)
- {
- return (this.context.HandshakeState != HandshakeState.Finished);
- }
- }
- }
- }
- internal void NegotiateHandshake()
- {
- if (this.MightNeedHandshake)
- {
- InternalAsyncResult ar = new InternalAsyncResult(null, null, null, 0, 0, false, false);
- //if something already started negotiation, wait for it.
- //otherwise end it ourselves.
- if (!BeginNegotiateHandshake(ar))
- {
- this.negotiationComplete.WaitOne();
- }
- else
- {
- this.EndNegotiateHandshake(ar);
- }
- }
- }
- #endregion
- #region Abstracts/Virtuals
- internal abstract IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state);
- internal abstract void OnNegotiateHandshakeCallback(IAsyncResult asyncResult);
- internal abstract X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates,
- X509Certificate serverCertificate,
- string targetHost,
- X509CertificateCollection serverRequestedCertificates);
- internal abstract bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors);
- internal abstract ValidationResult OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection);
- internal abstract bool HaveRemoteValidation2Callback { get; }
- internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
- #endregion
- #region Event Methods
- internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
- X509Certificate remoteCertificate,
- string targetHost,
- X509CertificateCollection requestedCertificates)
- {
- return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
- }
- internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
- {
- return OnRemoteCertificateValidation(certificate, errors);
- }
- internal ValidationResult RaiseRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
- {
- return OnRemoteCertificateValidation2 (collection);
- }
- internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
- X509Certificate certificate,
- string targetHost)
- {
- return OnLocalPrivateKeySelection(certificate, targetHost);
- }
- #endregion
- #region Security Properties
- public bool CheckCertRevocationStatus
- {
- get { return this.checkCertRevocationStatus; }
- set { this.checkCertRevocationStatus = value; }
- }
- public CipherAlgorithmType CipherAlgorithm
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return this.context.Current.Cipher.CipherAlgorithmType;
- }
- return CipherAlgorithmType.None;
- }
- }
- public int CipherStrength
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return this.context.Current.Cipher.EffectiveKeyBits;
- }
- return 0;
- }
- }
- public HashAlgorithmType HashAlgorithm
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return this.context.Current.Cipher.HashAlgorithmType;
- }
- return HashAlgorithmType.None;
- }
- }
- public int HashStrength
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return this.context.Current.Cipher.HashSize * 8;
- }
- return 0;
- }
- }
- public int KeyExchangeStrength
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return this.context.ServerSettings.Certificates[0].RSA.KeySize;
- }
- return 0;
- }
- }
- public ExchangeAlgorithmType KeyExchangeAlgorithm
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return this.context.Current.Cipher.ExchangeAlgorithmType;
- }
- return ExchangeAlgorithmType.None;
- }
- }
- public SecurityProtocolType SecurityProtocol
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- return this.context.SecurityProtocol;
- }
- return 0;
- }
- }
- public X509Certificate ServerCertificate
- {
- get
- {
- if (this.context.HandshakeState == HandshakeState.Finished)
- {
- if (this.context.ServerSettings.Certificates != null &&
- this.context.ServerSettings.Certificates.Count > 0)
- {
- return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
- }
- }
- return null;
- }
- }
- // this is used by Mono's certmgr tool to download certificates
- internal Mono.Security.X509.X509CertificateCollection ServerCertificates
- {
- get { return context.ServerSettings.Certificates; }
- }
- #endregion
- #region Internal Async Result/State Class
- private class InternalAsyncResult : IAsyncResult
- {
- private object locker = new object ();
- private AsyncCallback _userCallback;
- private object _userState;
- private Exception _asyncException;
- private ManualResetEvent handle;
- private bool completed;
- private int _bytesRead;
- private bool _fromWrite;
- private bool _proceedAfterHandshake;
- private byte[] _buffer;
- private int _offset;
- private int _count;
- public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
- {
- _userCallback = userCallback;
- _userState = userState;
- _buffer = buffer;
- _offset = offset;
- _count = count;
- _fromWrite = fromWrite;
- _proceedAfterHandshake = proceedAfterHandshake;
- }
- public bool ProceedAfterHandshake
- {
- get { return _proceedAfterHandshake; }
- }
- public bool FromWrite
- {
- get { return _fromWrite; }
- }
- public byte[] Buffer
- {
- get { return _buffer; }
- }
- public int Offset
- {
- get { return _offset; }
- }
- public int Count
- {
- get { return _count; }
- }
- public int BytesRead
- {
- get { return _bytesRead; }
- }
- public object AsyncState
- {
- get { return _userState; }
- }
- public Exception AsyncException
- {
- get { return _asyncException; }
- }
- public bool CompletedWithError
- {
- get {
- if (IsCompleted == false)
- return false;
- return null != _asyncException;
- }
- }
- public WaitHandle AsyncWaitHandle
- {
- get {
- lock (locker) {
- if (handle == null)
- handle = new ManualResetEvent (completed);
- }
- return handle;
- }
- }
- public bool CompletedSynchronously
- {
- get { return false; }
- }
- public bool IsCompleted
- {
- get {
- lock (locker)
- return completed;
- }
- }
- private void SetComplete(Exception ex, int bytesRead)
- {
- lock (locker) {
- if (completed)
- return;
- completed = true;
- _asyncException = ex;
- _bytesRead = bytesRead;
- if (handle != null)
- handle.Set ();
- }
- if (_userCallback != null)
- _userCallback.BeginInvoke (this, null, null);
- }
- public void SetComplete(Exception ex)
- {
- SetComplete(ex, 0);
- }
- public void SetComplete(int bytesRead)
- {
- SetComplete(null, bytesRead);
- }
- public void SetComplete()
- {
- SetComplete(null, 0);
- }
- }
- #endregion
- #region Stream Overrides and Async Stream Operations
- private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
- {
- try
- {
- lock (this.negotiate)
- {
- if (this.context.HandshakeState == HandshakeState.None)
- {
- this.OnBeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
- return true;
- }
- else
- {
- return false;
- }
- }
- }
- catch (TlsException ex)
- {
- this.negotiationComplete.Set();
- this.protocol.SendAlert(ex.Alert);
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- catch (Exception ex)
- {
- this.negotiationComplete.Set();
- this.protocol.SendAlert(AlertDescription.InternalError);
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- }
- private void EndNegotiateHandshake(InternalAsyncResult asyncResult)
- {
- if (asyncResult.IsCompleted == false)
- asyncResult.AsyncWaitHandle.WaitOne();
- if (asyncResult.CompletedWithError)
- {
- throw asyncResult.AsyncException;
- }
- }
- public override IAsyncResult BeginRead(
- byte[] buffer,
- int offset,
- int count,
- AsyncCallback callback,
- object state)
- {
- this.checkDisposed();
- if (buffer == null)
- {
- throw new ArgumentNullException("buffer is a null reference.");
- }
- if (offset < 0)
- {
- throw new ArgumentOutOfRangeException("offset is less than 0.");
- }
- if (offset > buffer.Length)
- {
- throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException("count is less than 0.");
- }
- if (count > (buffer.Length - offset))
- {
- throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
- }
- InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, false, true);
- if (this.MightNeedHandshake)
- {
- if (! BeginNegotiateHandshake(asyncResult))
- {
- //we made it down here so the handshake was not started.
- //another thread must have started it in the mean time.
- //wait for it to complete and then perform our original operation
- this.negotiationComplete.WaitOne();
- InternalBeginRead(asyncResult);
- }
- }
- else
- {
- InternalBeginRead(asyncResult);
- }
- return asyncResult;
- }
- // bigger than max record length for SSL/TLS
- private byte[] recbuf = new byte[16384];
- private void InternalBeginRead(InternalAsyncResult asyncResult)
- {
- try
- {
- int preReadSize = 0;
- lock (this.read)
- {
- // If actual buffer is fully read, reset it
- bool shouldReset = this.inputBuffer.Position == this.inputBuffer.Length && this.inputBuffer.Length > 0;
- // If the buffer isn't fully read, but does have data, we need to immediately
- // read the info from the buffer and let the user know that they have more data.
- bool shouldReadImmediately = (this.inputBuffer.Length > 0) && (asyncResult.Count > 0);
- if (shouldReset)
- {
- this.resetBuffer();
- }
- else if (shouldReadImmediately)
- {
- preReadSize = this.inputBuffer.Read(asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
- }
- }
- // This is explicitly done outside the synclock to avoid
- // any potential deadlocks in the delegate call.
- if (0 < preReadSize)
- {
- asyncResult.SetComplete(preReadSize);
- }
- else if (!this.context.ReceivedConnectionEnd)
- {
- // this will read data from the network until we have (at least) one
- // record to send back to the caller
- this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
- new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
- }
- else
- {
- // We're done with the connection so we need to let the caller know with 0 bytes read
- asyncResult.SetComplete(0);
- }
- }
- catch (TlsException ex)
- {
- this.protocol.SendAlert(ex.Alert);
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- catch (Exception ex)
- {
- throw new IOException("IO exception during read.", ex);
- }
- }
- private MemoryStream recordStream = new MemoryStream();
- // read encrypted data until we have enough to decrypt (at least) one
- // record and return are the records (may be more than one) we have
- private void InternalReadCallback(IAsyncResult result)
- {
- if (this.disposed)
- return;
- object[] state = (object[])result.AsyncState;
- byte[] recbuf = (byte[])state[0];
- InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
- try
- {
- int n = innerStream.EndRead(result);
- if (n > 0)
- {
- // Add the just received data to the waiting data
- recordStream.Write(recbuf, 0, n);
- }
- else
- {
- // 0 length data means this read operation is done (lost connection in the case of a network stream).
- internalResult.SetComplete(0);
- return;
- }
- bool dataToReturn = false;
- long pos = recordStream.Position;
- recordStream.Position = 0;
- byte[] record = null;
- // don't try to decode record unless we have at least 5 bytes
- // i.e. type (1), protocol (2) and length (2)
- if (recordStream.Length >= 5)
- {
- record = this.protocol.ReceiveRecord(recordStream);
- }
- // a record of 0 length is valid (and there may be more record after it)
- while (record != null)
- {
- // we probably received more stuff after the record, and we must keep it!
- long remainder = recordStream.Length - recordStream.Position;
- byte[] outofrecord = null;
- if (remainder > 0)
- {
- outofrecord = new byte[remainder];
- recordStream.Read(outofrecord, 0, outofrecord.Length);
- }
- lock (this.read)
- {
- long position = this.inputBuffer.Position;
- if (record.Length > 0)
- {
- // Write new data to the inputBuffer
- this.inputBuffer.Seek(0, SeekOrigin.End);
- this.inputBuffer.Write(record, 0, record.Length);
- // Restore buffer position
- this.inputBuffer.Seek(position, SeekOrigin.Begin);
- dataToReturn = true;
- }
- }
- recordStream.SetLength(0);
- record = null;
- if (remainder > 0)
- {
- recordStream.Write(outofrecord, 0, outofrecord.Length);
- // type (1), protocol (2) and length (2)
- if (recordStream.Length >= 5)
- {
- // try to see if another record is available
- recordStream.Position = 0;
- record = this.protocol.ReceiveRecord(recordStream);
- if (record == null)
- pos = recordStream.Length;
- }
- else
- pos = remainder;
- }
- else
- pos = 0;
- }
- if (!dataToReturn && (n > 0))
- {
- if (context.ReceivedConnectionEnd) {
- internalResult.SetComplete (0);
- } else {
- // there is no record to return to caller and (possibly) more data waiting
- // so continue reading from network (and appending to stream)
- recordStream.Position = recordStream.Length;
- this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
- new AsyncCallback(InternalReadCallback), state);
- }
- }
- else
- {
- // we have record(s) to return -or- no more available to read from network
- // reset position for further reading
- recordStream.Position = pos;
- int bytesRead = 0;
- lock (this.read)
- {
- bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
- }
- internalResult.SetComplete(bytesRead);
- }
- }
- catch (Exception ex)
- {
- internalResult.SetComplete(ex);
- }
- }
- private void InternalBeginWrite(InternalAsyncResult asyncResult)
- {
- try
- {
- // Send the buffer as a TLS record
- lock (this.write)
- {
- byte[] record = this.protocol.EncodeRecord(
- ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
- this.innerStream.BeginWrite(
- record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
- }
- }
- catch (TlsException ex)
- {
- this.protocol.SendAlert(ex.Alert);
- this.Close();
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- catch (Exception ex)
- {
- throw new IOException("IO exception during Write.", ex);
- }
- }
- private void InternalWriteCallback(IAsyncResult ar)
- {
- if (this.disposed)
- return;
-
- InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
- try
- {
- this.innerStream.EndWrite(ar);
- internalResult.SetComplete();
- }
- catch (Exception ex)
- {
- internalResult.SetComplete(ex);
- }
- }
- public override IAsyncResult BeginWrite(
- byte[] buffer,
- int offset,
- int count,
- AsyncCallback callback,
- object state)
- {
- this.checkDisposed();
- if (buffer == null)
- {
- throw new ArgumentNullException("buffer is a null reference.");
- }
- if (offset < 0)
- {
- throw new ArgumentOutOfRangeException("offset is less than 0.");
- }
- if (offset > buffer.Length)
- {
- throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException("count is less than 0.");
- }
- if (count > (buffer.Length - offset))
- {
- throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
- }
- InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
- if (this.MightNeedHandshake)
- {
- if (! BeginNegotiateHandshake(asyncResult))
- {
- //we made it down here so the handshake was not started.
- //another thread must have started it in the mean time.
- //wait for it to complete and then perform our original operation
- this.negotiationComplete.WaitOne();
- InternalBeginWrite(asyncResult);
- }
- }
- else
- {
- InternalBeginWrite(asyncResult);
- }
- return asyncResult;
- }
- public override int EndRead(IAsyncResult asyncResult)
- {
- this.checkDisposed();
- InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
- if (internalResult == null)
- {
- throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
- }
- // Always wait until the read is complete
- if (!asyncResult.IsCompleted)
- {
- if (!asyncResult.AsyncWaitHandle.WaitOne (WaitTimeOut, false))
- throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndRead");
- }
- if (internalResult.CompletedWithError)
- {
- throw internalResult.AsyncException;
- }
- return internalResult.BytesRead;
- }
- public override void EndWrite(IAsyncResult asyncResult)
- {
- this.checkDisposed();
- InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
- if (internalResult == null)
- {
- throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
- }
- if (!asyncResult.IsCompleted)
- {
- if (!internalResult.AsyncWaitHandle.WaitOne (WaitTimeOut, false))
- throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndWrite");
- }
- if (internalResult.CompletedWithError)
- {
- throw internalResult.AsyncException;
- }
- }
- public override void Close()
- {
- base.Close ();
- }
- public override void Flush()
- {
- this.checkDisposed();
- this.innerStream.Flush();
- }
- public int Read(byte[] buffer)
- {
- return this.Read(buffer, 0, buffer.Length);
- }
- public override int Read(byte[] buffer, int offset, int count)
- {
- this.checkDisposed ();
-
- if (buffer == null)
- {
- throw new ArgumentNullException ("buffer");
- }
- if (offset < 0)
- {
- throw new ArgumentOutOfRangeException("offset is less than 0.");
- }
- if (offset > buffer.Length)
- {
- throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException("count is less than 0.");
- }
- if (count > (buffer.Length - offset))
- {
- throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
- }
- if (this.context.HandshakeState != HandshakeState.Finished)
- {
- this.NegotiateHandshake (); // Handshake negotiation
- }
- lock (this.read) {
- try {
- record_processing.Reset ();
- // do we already have some decrypted data ?
- if (this.inputBuffer.Position > 0) {
- // or maybe we used all the buffer before ?
- if (this.inputBuffer.Position == this.inputBuffer.Length) {
- this.inputBuffer.SetLength (0);
- } else {
- int n = this.inputBuffer.Read (buffer, offset, count);
- if (n > 0) {
- record_processing.Set ();
- return n;
- }
- }
- }
- bool needMoreData = false;
- while (true) {
- // we first try to process the read with the data we already have
- if ((recordStream.Position == 0) || needMoreData) {
- needMoreData = false;
- // if we loop, then it either means we need more data
- byte[] recbuf = new byte[16384];
- int n = 0;
- if (count == 1) {
- int value = innerStream.ReadByte ();
- if (value >= 0) {
- recbuf[0] = (byte) value;
- n = 1;
- }
- } else {
- n = innerStream.Read (recbuf, 0, recbuf.Length);
- }
- if (n > 0) {
- // Add the new received data to the waiting data
- if ((recordStream.Length > 0) && (recordStream.Position != recordStream.Length))
- recordStream.Seek (0, SeekOrigin.End);
- recordStream.Write (recbuf, 0, n);
- } else {
- // or that the read operation is done (lost connection in the case of a network stream).
- record_processing.Set ();
- return 0;
- }
- }
- bool dataToReturn = false;
- recordStream.Position = 0;
- byte[] record = null;
- // don't try to decode record unless we have at least 5 bytes
- // i.e. type (1), protocol (2) and length (2)
- if (recordStream.Length >= 5) {
- record = this.protocol.ReceiveRecord (recordStream);
- needMoreData = (record == null);
- }
- // a record of 0 length is valid (and there may be more record after it)
- while (record != null) {
- // we probably received more stuff after the record, and we must keep it!
- long remainder = recordStream.Length - recordStream.Position;
- byte[] outofrecord = null;
- if (remainder > 0) {
- outofrecord = new byte[remainder];
- recordStream.Read (outofrecord, 0, outofrecord.Length);
- }
- long position = this.inputBuffer.Position;
- if (record.Length > 0) {
- // Write new data to the inputBuffer
- this.inputBuffer.Seek (0, SeekOrigin.End);
- this.inputBuffer.Write (record, 0, record.Length);
- // Restore buffer position
- this.inputBuffer.Seek (position, SeekOrigin.Begin);
- dataToReturn = true;
- }
- recordStream.SetLength (0);
- record = null;
- if (remainder > 0) {
- recordStream.Write (outofrecord, 0, outofrecord.Length);
- }
- if (dataToReturn) {
- // we have record(s) to return -or- no more available to read from network
- // reset position for further reading
- int i = inputBuffer.Read (buffer, offset, count);
- record_processing.Set ();
- return i;
- }
- }
- }
- }
- catch (TlsException ex)
- {
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- catch (Exception ex)
- {
- throw new IOException("IO exception during read.", ex);
- }
- }
- }
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
- public void Write(byte[] buffer)
- {
- this.Write(buffer, 0, buffer.Length);
- }
- public override void Write(byte[] buffer, int offset, int count)
- {
- this.checkDisposed ();
-
- if (buffer == null)
- {
- throw new ArgumentNullException ("buffer");
- }
- if (offset < 0)
- {
- throw new ArgumentOutOfRangeException("offset is less than 0.");
- }
- if (offset > buffer.Length)
- {
- throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException("count is less than 0.");
- }
- if (count > (buffer.Length - offset))
- {
- throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
- }
- if (this.context.HandshakeState != HandshakeState.Finished)
- {
- this.NegotiateHandshake ();
- }
- lock (this.write)
- {
- try
- {
- // Send the buffer as a TLS record
- byte[] record = this.protocol.EncodeRecord (ContentType.ApplicationData, buffer, offset, count);
- this.innerStream.Write (record, 0, record.Length);
- }
- catch (TlsException ex)
- {
- this.protocol.SendAlert(ex.Alert);
- this.Close();
- throw new IOException("The authentication or decryption has failed.", ex);
- }
- catch (Exception ex)
- {
- throw new IOException("IO exception during Write.", ex);
- }
- }
- }
- public override bool CanRead
- {
- get { return this.innerStream.CanRead; }
- }
- public override bool CanSeek
- {
- get { return false; }
- }
- public override bool CanWrite
- {
- get { return this.innerStream.CanWrite; }
- }
- public override long Length
- {
- get { throw new NotSupportedException(); }
- }
- public override long Position
- {
- get
- {
- throw new NotSupportedException();
- }
- set
- {
- throw new NotSupportedException();
- }
- }
- #endregion
- #region IDisposable Members and Finalizer
- ~SslStreamBase()
- {
- this.Dispose(false);
- }
- protected override void Dispose (bool disposing)
- {
- if (!this.disposed)
- {
- if (disposing)
- {
- if (this.innerStream != null)
- {
- if (this.context.HandshakeState == HandshakeState.Finished &&
- !this.context.SentConnectionEnd)
- {
- // Write close notify
- try {
- this.protocol.SendAlert(AlertDescription.CloseNotify);
- } catch {
- }
- }
- if (this.ownsStream)
- {
- // Close inner stream
- this.innerStream.Close();
- }
- }
- this.ownsStream = false;
- this.innerStream = null;
- }
- this.disposed = true;
- base.Dispose (disposing);
- }
- }
- #endregion
- #region Misc Methods
- private void resetBuffer()
- {
- this.inputBuffer.SetLength(0);
- this.inputBuffer.Position = 0;
- }
- internal void checkDisposed()
- {
- if (this.disposed)
- {
- throw new ObjectDisposedException("The Stream is closed.");
- }
- }
- #endregion
- }
- }
|