2 // System.Net.Security.SslStream.cs
5 // Tim Coleman (tim@timcoleman.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
7 // Marek Safar (marek.safar@gmail.com)
9 // Copyright (C) Tim Coleman, 2004
10 // (c) 2004,2007 Novell, Inc. (http://www.novell.com)
11 // Copyright 2011 Xamarin Inc.
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 extern alias PrebuiltSystem;
39 using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
45 using System.Security.Authentication;
46 using System.Security.Cryptography.X509Certificates;
47 using System.Security.Principal;
48 using System.Security.Cryptography;
49 using Mono.Security.Protocol.Tls;
51 using CipherAlgorithmType = System.Security.Authentication.CipherAlgorithmType;
52 using HashAlgorithmType = System.Security.Authentication.HashAlgorithmType;
53 using ExchangeAlgorithmType = System.Security.Authentication.ExchangeAlgorithmType;
55 using MonoCipherAlgorithmType = Mono.Security.Protocol.Tls.CipherAlgorithmType;
56 using MonoHashAlgorithmType = Mono.Security.Protocol.Tls.HashAlgorithmType;
57 using MonoExchangeAlgorithmType = Mono.Security.Protocol.Tls.ExchangeAlgorithmType;
58 using MonoSecurityProtocolType = Mono.Security.Protocol.Tls.SecurityProtocolType;
61 using System.Threading.Tasks;
64 namespace System.Net.Security
66 [MonoTODO ("Non-X509Certificate2 certificate is not supported")]
67 public class SslStream : AuthenticatedStream
71 SslStreamBase ssl_stream;
72 RemoteCertificateValidationCallback validation_callback;
73 LocalCertificateSelectionCallback selection_callback;
79 public SslStream (Stream innerStream)
80 : this (innerStream, false)
84 public SslStream (Stream innerStream, bool leaveInnerStreamOpen)
85 : base (innerStream, leaveInnerStreamOpen)
89 [MonoTODO ("userCertificateValidationCallback is not passed X509Chain and SslPolicyErrors correctly")]
90 public SslStream (Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
91 : this (innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, null)
95 [MonoTODO ("userCertificateValidationCallback is not passed X509Chain and SslPolicyErrors correctly")]
96 public SslStream (Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback)
97 : base (innerStream, leaveInnerStreamOpen)
100 validation_callback = userCertificateValidationCallback;
101 selection_callback = userCertificateSelectionCallback;
103 #endregion // Constructors
107 public override bool CanRead {
108 get { return InnerStream.CanRead; }
111 public override bool CanSeek {
112 get { return InnerStream.CanSeek; }
115 public override bool CanTimeout {
116 get { return InnerStream.CanTimeout; }
119 public override bool CanWrite {
120 get { return InnerStream.CanWrite; }
123 public override long Length {
124 get { return InnerStream.Length; }
127 public override long Position {
128 get { return InnerStream.Position; }
130 throw new NotSupportedException ("This stream does not support seek operations");
134 // AuthenticatedStream overrides
136 public override bool IsAuthenticated {
137 get { return ssl_stream != null; }
140 public override bool IsEncrypted {
141 get { return IsAuthenticated; }
144 public override bool IsMutuallyAuthenticated {
145 get { return IsAuthenticated && (IsServer ? RemoteCertificate != null : LocalCertificate != null); }
148 public override bool IsServer {
150 get { return false; }
152 get { return ssl_stream is SslServerStream; }
156 public override bool IsSigned {
157 get { return IsAuthenticated; }
160 public override int ReadTimeout {
161 get { return InnerStream.ReadTimeout; }
162 set { InnerStream.ReadTimeout = value; }
165 public override int WriteTimeout {
166 get { return InnerStream.WriteTimeout; }
167 set { InnerStream.WriteTimeout = value; }
172 public virtual bool CheckCertRevocationStatus {
174 if (!IsAuthenticated)
177 return ssl_stream.CheckCertRevocationStatus;
181 public virtual CipherAlgorithmType CipherAlgorithm {
183 CheckConnectionAuthenticated ();
185 switch (ssl_stream.CipherAlgorithm) {
186 case MonoCipherAlgorithmType.Des:
187 return CipherAlgorithmType.Des;
188 case MonoCipherAlgorithmType.None:
189 return CipherAlgorithmType.None;
190 case MonoCipherAlgorithmType.Rc2:
191 return CipherAlgorithmType.Rc2;
192 case MonoCipherAlgorithmType.Rc4:
193 return CipherAlgorithmType.Rc4;
194 case MonoCipherAlgorithmType.SkipJack:
196 case MonoCipherAlgorithmType.TripleDes:
197 return CipherAlgorithmType.TripleDes;
198 case MonoCipherAlgorithmType.Rijndael:
199 switch (ssl_stream.CipherStrength) {
201 return CipherAlgorithmType.Aes128;
203 return CipherAlgorithmType.Aes192;
205 return CipherAlgorithmType.Aes256;
210 throw new InvalidOperationException ("Not supported cipher algorithm is in use. It is likely a bug in SslStream.");
214 public virtual int CipherStrength {
216 CheckConnectionAuthenticated ();
218 return ssl_stream.CipherStrength;
222 public virtual HashAlgorithmType HashAlgorithm {
224 CheckConnectionAuthenticated ();
226 switch (ssl_stream.HashAlgorithm) {
227 case MonoHashAlgorithmType.Md5:
228 return HashAlgorithmType.Md5;
229 case MonoHashAlgorithmType.None:
230 return HashAlgorithmType.None;
231 case MonoHashAlgorithmType.Sha1:
232 return HashAlgorithmType.Sha1;
235 throw new InvalidOperationException ("Not supported hash algorithm is in use. It is likely a bug in SslStream.");
239 public virtual int HashStrength {
241 CheckConnectionAuthenticated ();
243 return ssl_stream.HashStrength;
247 public virtual ExchangeAlgorithmType KeyExchangeAlgorithm {
249 CheckConnectionAuthenticated ();
251 switch (ssl_stream.KeyExchangeAlgorithm) {
252 case MonoExchangeAlgorithmType.DiffieHellman:
253 return ExchangeAlgorithmType.DiffieHellman;
254 case MonoExchangeAlgorithmType.Fortezza:
256 case MonoExchangeAlgorithmType.None:
257 return ExchangeAlgorithmType.None;
258 case MonoExchangeAlgorithmType.RsaKeyX:
259 return ExchangeAlgorithmType.RsaKeyX;
260 case MonoExchangeAlgorithmType.RsaSign:
261 return ExchangeAlgorithmType.RsaSign;
264 throw new InvalidOperationException ("Not supported exchange algorithm is in use. It is likely a bug in SslStream.");
268 public virtual int KeyExchangeStrength {
270 CheckConnectionAuthenticated ();
272 return ssl_stream.KeyExchangeStrength;
276 public virtual X509Certificate LocalCertificate {
278 CheckConnectionAuthenticated ();
280 return IsServer ? ssl_stream.ServerCertificate : ((SslClientStream) ssl_stream).SelectedClientCertificate;
284 public virtual X509Certificate RemoteCertificate {
286 CheckConnectionAuthenticated ();
288 return ssl_stream.ServerCertificate;
290 return !IsServer ? ssl_stream.ServerCertificate : ((SslServerStream) ssl_stream).ClientCertificate;
295 public virtual SslProtocols SslProtocol {
297 CheckConnectionAuthenticated ();
299 switch (ssl_stream.SecurityProtocol) {
300 case MonoSecurityProtocolType.Default:
301 return SslProtocols.Default;
302 case MonoSecurityProtocolType.Ssl2:
303 return SslProtocols.Ssl2;
304 case MonoSecurityProtocolType.Ssl3:
305 return SslProtocols.Ssl3;
306 case MonoSecurityProtocolType.Tls:
307 return SslProtocols.Tls;
310 throw new InvalidOperationException ("Not supported SSL/TLS protocol is in use. It is likely a bug in SslStream.");
314 #endregion // Properties
318 AsymmetricAlgorithm GetPrivateKey (X509Certificate cert, string targetHost)
320 // FIXME: what can I do for non-X509Certificate2 ?
321 X509Certificate2 cert2 = cert as X509Certificate2;
322 return cert2 != null ? cert2.PrivateKey : null;
325 X509Certificate OnCertificateSelection (X509CertificateCollection clientCerts, X509Certificate serverCert, string targetHost, X509CertificateCollection serverRequestedCerts)
327 string [] acceptableIssuers = new string [serverRequestedCerts != null ? serverRequestedCerts.Count : 0];
328 for (int i = 0; i < acceptableIssuers.Length; i++)
329 acceptableIssuers [i] = serverRequestedCerts [i].GetIssuerName ();
330 return selection_callback (this, targetHost, clientCerts, serverCert, acceptableIssuers);
333 public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, AsyncCallback asyncCallback, object asyncState)
335 return BeginAuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false, asyncCallback, asyncState);
338 public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
341 throw new InvalidOperationException ("This SslStream is already authenticated");
343 SslClientStream s = new SslClientStream (InnerStream, targetHost, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols), clientCertificates);
344 s.CheckCertRevocationStatus = checkCertificateRevocation;
346 // Due to the Mono.Security internal, it cannot reuse
347 // the delegated argument, as Mono.Security creates
348 // another instance of X509Certificate which lacks
349 // private key but is filled the private key via this
351 s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string host) {
352 string hash = cert.GetCertHashString ();
353 // ... so, we cannot use the delegate argument.
354 foreach (X509Certificate cc in clientCertificates) {
355 if (cc.GetCertHashString () != hash)
357 X509Certificate2 cert2 = cc as X509Certificate2;
358 cert2 = cert2 ?? new X509Certificate2 (cc);
359 return cert2.PrivateKey;
364 if (validation_callback != null)
365 s.ServerCertValidationDelegate = delegate (X509Certificate cert, int [] certErrors) {
366 X509Chain chain = new X509Chain ();
367 X509Certificate2 x2 = (cert as X509Certificate2);
369 x2 = new X509Certificate2 (cert);
371 if (!ServicePointManager.CheckCertificateRevocationList)
372 chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
374 // SSL specific checks (done by Mono.Security.dll SSL/TLS implementation)
375 SslPolicyErrors errors = SslPolicyErrors.None;
376 foreach (int i in certErrors) {
378 case -2146762490: // CERT_E_PURPOSE
379 errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
381 case -2146762481: // CERT_E_CN_NO_MATCH
382 errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
385 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
392 // non-SSL specific X509 checks (i.e. RFC3280 related checks)
393 foreach (X509ChainStatus status in chain.ChainStatus) {
394 if (status.Status == X509ChainStatusFlags.NoError)
396 if ((status.Status & X509ChainStatusFlags.PartialChain) != 0)
397 errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
399 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
402 return validation_callback (this, cert, chain, errors);
404 if (selection_callback != null)
405 s.ClientCertSelectionDelegate = OnCertificateSelection;
409 return BeginWrite (new byte [0], 0, 0, asyncCallback, asyncState);
412 public override IAsyncResult BeginRead (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
414 CheckConnectionAuthenticated ();
416 return ssl_stream.BeginRead (buffer, offset, count, asyncCallback, asyncState);
419 public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, AsyncCallback asyncCallback, object asyncState)
421 return BeginAuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false, asyncCallback, asyncState);
424 public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
427 throw new InvalidOperationException ("This SslStream is already authenticated");
429 SslServerStream s = new SslServerStream (InnerStream, serverCertificate, clientCertificateRequired, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols));
430 s.CheckCertRevocationStatus = checkCertificateRevocation;
431 // Due to the Mono.Security internal, it cannot reuse
432 // the delegated argument, as Mono.Security creates
433 // another instance of X509Certificate which lacks
434 // private key but is filled the private key via this
436 s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string targetHost) {
437 // ... so, we cannot use the delegate argument.
438 X509Certificate2 cert2 = serverCertificate as X509Certificate2 ?? new X509Certificate2 (serverCertificate);
439 return cert2 != null ? cert2.PrivateKey : null;
442 if (validation_callback != null)
443 s.ClientCertValidationDelegate = delegate (X509Certificate cert, int [] certErrors) {
444 X509Chain chain = null;
445 if (cert is X509Certificate2) {
446 chain = new X509Chain ();
447 chain.Build ((X509Certificate2) cert);
449 // FIXME: SslPolicyErrors is incomplete
450 SslPolicyErrors errors = certErrors.Length > 0 ? SslPolicyErrors.RemoteCertificateChainErrors : SslPolicyErrors.None;
451 return validation_callback (this, cert, chain, errors);
456 return BeginWrite (new byte[0], 0, 0, asyncCallback, asyncState);
459 MonoSecurityProtocolType GetMonoSslProtocol (SslProtocols ms)
462 case SslProtocols.Ssl2:
463 return MonoSecurityProtocolType.Ssl2;
464 case SslProtocols.Ssl3:
465 return MonoSecurityProtocolType.Ssl3;
466 case SslProtocols.Tls:
467 return MonoSecurityProtocolType.Tls;
469 return MonoSecurityProtocolType.Default;
473 public override IAsyncResult BeginWrite (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
475 CheckConnectionAuthenticated ();
477 return ssl_stream.BeginWrite (buffer, offset, count, asyncCallback, asyncState);
480 public virtual void AuthenticateAsClient (string targetHost)
482 AuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false);
485 public virtual void AuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
487 EndAuthenticateAsClient (BeginAuthenticateAsClient (
488 targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, null, null));
491 public virtual void AuthenticateAsServer (X509Certificate serverCertificate)
493 AuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false);
496 public virtual void AuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
498 EndAuthenticateAsServer (BeginAuthenticateAsServer (
499 serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, null, null));
502 protected override void Dispose (bool disposing)
505 if (ssl_stream != null)
506 ssl_stream.Dispose ();
509 base.Dispose (disposing);
512 public virtual void EndAuthenticateAsClient (IAsyncResult asyncResult)
514 CheckConnectionAuthenticated ();
517 ssl_stream.EndRead (asyncResult);
519 ssl_stream.EndWrite (asyncResult);
522 public virtual void EndAuthenticateAsServer (IAsyncResult asyncResult)
524 CheckConnectionAuthenticated ();
527 ssl_stream.EndRead (asyncResult);
529 ssl_stream.EndWrite (asyncResult);
532 public override int EndRead (IAsyncResult asyncResult)
534 CheckConnectionAuthenticated ();
536 return ssl_stream.EndRead (asyncResult);
539 public override void EndWrite (IAsyncResult asyncResult)
541 CheckConnectionAuthenticated ();
543 ssl_stream.EndWrite (asyncResult);
546 public override void Flush ()
548 CheckConnectionAuthenticated ();
550 InnerStream.Flush ();
553 public override int Read (byte[] buffer, int offset, int count)
555 return EndRead (BeginRead (buffer, offset, count, null, null));
558 public override long Seek (long offset, SeekOrigin origin)
560 throw new NotSupportedException ("This stream does not support seek operations");
563 public override void SetLength (long value)
565 InnerStream.SetLength (value);
568 public override void Write (byte[] buffer, int offset, int count)
570 EndWrite (BeginWrite (buffer, offset, count, null, null));
573 public void Write (byte[] buffer)
575 Write (buffer, 0, buffer.Length);
578 void CheckConnectionAuthenticated ()
580 if (!IsAuthenticated)
581 throw new InvalidOperationException ("This operation is invalid until it is successfully authenticated");
585 public virtual Task AuthenticateAsClientAsync (string targetHost)
587 return Task.Factory.FromAsync (BeginAuthenticateAsClient, EndAuthenticateAsClient, targetHost, null);
590 public virtual Task AuthenticateAsClientAsync (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
592 var t = Tuple.Create (targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, this);
594 return Task.Factory.FromAsync ((callback, state) => {
595 var d = (Tuple<string, X509CertificateCollection, SslProtocols, bool, SslStream>) state;
596 return d.Item5.BeginAuthenticateAsClient (d.Item1, d.Item2, d.Item3, d.Item4, callback, null);
597 }, EndAuthenticateAsClient, t);
600 public virtual Task AuthenticateAsServerAsync (X509Certificate serverCertificate)
602 return Task.Factory.FromAsync (BeginAuthenticateAsServer, EndAuthenticateAsServer, serverCertificate, null);
605 public virtual Task AuthenticateAsServerAsync (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
607 var t = Tuple.Create (serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, this);
609 return Task.Factory.FromAsync ((callback, state) => {
610 var d = (Tuple<X509Certificate, bool, SslProtocols, bool, SslStream>) state;
611 return d.Item5.BeginAuthenticateAsServer (d.Item1, d.Item2, d.Item3, d.Item4, callback, null);
612 }, EndAuthenticateAsServer, t);
616 #endregion // Methods