[corlib] RemotingConfiguration now try to use the bundled machine.config first.
[mono.git] / mcs / class / System / Mono.Btls / MonoBtlsContext.cs
1 //
2 // MonoBtlsContext.cs
3 //
4 // Author:
5 //       Martin Baulig <martin.baulig@xamarin.com>
6 //
7 // Copyright (c) 2016 Xamarin Inc. (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a copy
10 // of this software and associated documentation files (the "Software"), to deal
11 // in the Software without restriction, including without limitation the rights
12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 // copies of the Software, and to permit persons to whom the Software is
14 // furnished to do so, subject to the following conditions:
15 //
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
18 //
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 // THE SOFTWARE.
26 #if SECURITY_DEP
27 #if MONO_SECURITY_ALIAS
28 extern alias MonoSecurity;
29 #endif
30
31 using System;
32 using System.IO;
33 using System.Threading;
34 using System.Threading.Tasks;
35 using System.Security.Cryptography.X509Certificates;
36 using System.Security.Authentication;
37 using System.Runtime.InteropServices;
38
39 #if MONO_SECURITY_ALIAS
40 using MonoSecurity::Mono.Security.Interface;
41 #else
42 using Mono.Security.Interface;
43 #endif
44
45 using MNS = Mono.Net.Security;
46
47 namespace Mono.Btls
48 {
49         class MonoBtlsContext : MNS.MobileTlsContext, IMonoBtlsBioMono
50         {
51                 X509Certificate remoteCertificate;
52                 X509Certificate clientCertificate;
53                 X509CertificateImplBtls nativeServerCertificate;
54                 X509CertificateImplBtls nativeClientCertificate;
55                 MonoBtlsSslCtx ctx;
56                 MonoBtlsSsl ssl;
57                 MonoBtlsBio bio;
58                 MonoBtlsBio errbio;
59
60                 MonoTlsConnectionInfo connectionInfo;
61                 bool certificateValidated;
62                 bool isAuthenticated;
63                 bool connected;
64
65                 public MonoBtlsContext (
66                         MNS.MobileAuthenticatedStream parent,
67                         bool serverMode, string targetHost,
68                         SslProtocols enabledProtocols, X509Certificate serverCertificate,
69                         X509CertificateCollection clientCertificates, bool askForClientCert)
70                         : base (parent, serverMode, targetHost, enabledProtocols,
71                                 serverCertificate, clientCertificates, askForClientCert)
72                 {
73                         if (serverMode)
74                                 nativeServerCertificate = GetPrivateCertificate (serverCertificate);
75                 }
76
77                 static X509CertificateImplBtls GetPrivateCertificate (X509Certificate certificate)
78                 {
79                         var impl = certificate.Impl as X509CertificateImplBtls;
80                         if (impl != null)
81                                 return (X509CertificateImplBtls)impl.Clone ();
82
83                         var password = Guid.NewGuid ().ToString ();
84                         var buffer = certificate.Export (X509ContentType.Pfx, password);
85
86                         impl = new X509CertificateImplBtls ();
87                         impl.Import (buffer, password, X509KeyStorageFlags.DefaultKeySet);
88                         return impl;
89                 }
90
91                 new public MonoBtlsProvider Provider {
92                         get { return (MonoBtlsProvider)base.Provider; }
93                 }
94
95                 int VerifyCallback (MonoBtlsX509StoreCtx storeCtx)
96                 {
97                         using (var chainImpl = new X509ChainImplBtls (storeCtx))
98                         using (var managedChain = new X509Chain (chainImpl)) {
99                                 var leaf = managedChain.ChainElements[0].Certificate;
100                                 var result = ValidateCertificate (leaf, managedChain);
101                                 certificateValidated = true;
102                                 return result ? 1 : 0;
103                         }
104                 }
105
106                 int SelectCallback ()
107                 {
108                         Debug ("SELECT CALLBACK!");
109
110                         GetPeerCertificate ();
111                         if (remoteCertificate == null)
112                                 throw new TlsException (AlertDescription.InternalError, "Cannot request client certificate before receiving one from the server.");
113
114                         var clientCert = SelectClientCertificate (remoteCertificate, null);
115                         Debug ("SELECT CALLBACK #1: {0}", clientCert);
116                         if (clientCert == null)
117                                 return 1;
118
119                         nativeClientCertificate = GetPrivateCertificate (clientCert);
120                         Debug ("SELECT CALLBACK #2: {0}", nativeClientCertificate);
121                         clientCertificate = new X509Certificate (nativeClientCertificate);
122                         SetPrivateCertificate (nativeClientCertificate);
123                         return 1;
124                 }
125
126                 public override void StartHandshake ()
127                 {
128                         InitializeConnection ();
129
130                         ssl = new MonoBtlsSsl (ctx);
131
132                         bio = new MonoBtlsBioMono (this);
133                         ssl.SetBio (bio);
134
135                         if (IsServer) {
136                                 SetPrivateCertificate (nativeServerCertificate);
137                         } else {
138                                 ssl.SetServerName (TargetHost);
139                         }
140                 }
141
142                 void SetPrivateCertificate (X509CertificateImplBtls privateCert)
143                 {
144                         Debug ("SetPrivateCertificate: {0}", privateCert);
145                         ssl.SetCertificate (privateCert.X509);
146                         ssl.SetPrivateKey (privateCert.NativePrivateKey);
147                         var intermediate = privateCert.IntermediateCertificates;
148                         if (intermediate == null)
149                                 return;
150                         for (int i = 0; i < intermediate.Count; i++) {
151                                 var impl = (X509CertificateImplBtls)intermediate [i];
152                                 Debug ("SetPrivateCertificate - add intermediate: {0}", impl);
153                                 ssl.AddIntermediateCertificate (impl.X509);
154                         }
155                 }
156
157                 Exception GetException (MonoBtlsSslError status)
158                 {
159                         var error = MonoBtlsError.GetError ();
160                         if (error == null)
161                                 return new MonoBtlsException (status);
162
163                         var text = MonoBtlsError.GetErrorString (error);
164                         return new MonoBtlsException ("{0} {1}", status, text);
165                 }
166
167                 public override bool ProcessHandshake ()
168                 {
169                         var done = false;
170                         while (!done) {
171                                 Debug ("ProcessHandshake");
172                                 MonoBtlsError.ClearError ();
173                                 var status = DoProcessHandshake ();
174                                 Debug ("ProcessHandshake #1: {0}", status);
175
176                                 switch (status) {
177                                 case MonoBtlsSslError.None:
178                                         if (connected)
179                                                 done = true;
180                                         else
181                                                 connected = true;
182                                         break;
183                                 case MonoBtlsSslError.WantRead:
184                                 case MonoBtlsSslError.WantWrite:
185                                         return false;
186                                 default:
187                                         throw GetException (status);
188                                 }
189                         }
190
191                         ssl.PrintErrors ();
192
193                         return true;
194                 }
195
196                 MonoBtlsSslError DoProcessHandshake ()
197                 {
198                         if (connected)
199                                 return ssl.Handshake ();
200                         else if (IsServer)
201                                 return ssl.Accept ();
202                         else
203                                 return ssl.Connect ();
204                 }
205
206                 public override void FinishHandshake ()
207                 {
208                         InitializeSession ();
209
210                         isAuthenticated = true;
211                 }
212
213                 void SetupCertificateStore ()
214                 {
215 #if MONODROID
216                         ctx.CertificateStore.SetDefaultPaths ();
217                         ctx.CertificateStore.AddAndroidLookup ();
218 #else
219                         var userPath = MonoBtlsX509StoreManager.GetStorePath (MonoBtlsX509StoreType.UserTrustedRoots);
220                         if (Directory.Exists (userPath))
221                                 ctx.CertificateStore.AddDirectoryLookup (userPath, MonoBtlsX509FileType.PEM);
222                         var machinePath = MonoBtlsX509StoreManager.GetStorePath (MonoBtlsX509StoreType.MachineTrustedRoots);
223                         if (Directory.Exists (machinePath))
224                                 ctx.CertificateStore.AddDirectoryLookup (machinePath, MonoBtlsX509FileType.PEM);
225 #endif
226
227                         if (Settings != null && Settings.TrustAnchors != null) {
228                                 var trust = IsServer ? MonoBtlsX509TrustKind.TRUST_CLIENT : MonoBtlsX509TrustKind.TRUST_SERVER;
229                                 ctx.CertificateStore.AddCollection (Settings.TrustAnchors, trust);
230                         }
231                 }
232
233                 void InitializeConnection ()
234                 {
235                         ctx = new MonoBtlsSslCtx ();
236
237 #if MARTIN_DEBUG
238                         errbio = MonoBtlsBio.CreateMonoStream (Console.OpenStandardError ());
239                         ctx.SetDebugBio (errbio);
240 #endif
241
242                         SetupCertificateStore ();
243
244                         if (!IsServer || AskForClientCertificate)
245                                 ctx.SetVerifyCallback (VerifyCallback, false);
246                         if (!IsServer)
247                                 ctx.SetSelectCallback (SelectCallback);
248
249                         var host = TargetHost;
250                         if (!string.IsNullOrEmpty (host)) {
251                                 var pos = TargetHost.IndexOf (':');
252                                 if (pos > 0)
253                                         host = host.Substring (0, pos);
254                         }
255
256                         ctx.SetVerifyParam (MonoBtlsProvider.GetVerifyParam (host, IsServer));
257
258                         TlsProtocolCode minProtocol, maxProtocol;
259                         GetProtocolVersions (out minProtocol, out maxProtocol);
260
261                         ctx.SetMinVersion ((int)minProtocol);
262                         ctx.SetMaxVersion ((int)maxProtocol);
263
264                         if (Settings != null && Settings.EnabledCiphers != null) {
265                                 var ciphers = new short [Settings.EnabledCiphers.Length];
266                                 for (int i = 0; i < ciphers.Length; i++)
267                                         ciphers [i] = (short)Settings.EnabledCiphers [i];
268                                 ctx.SetCiphers (ciphers, true);
269                         }
270                 }
271
272                 void GetPeerCertificate ()
273                 {
274                         if (remoteCertificate != null)
275                                 return;
276                         using (var remoteCert = ssl.GetPeerCertificate ()) {
277                                 if (remoteCert != null)
278                                         remoteCertificate = MonoBtlsProvider.CreateCertificate (remoteCert);
279                         }
280                 }
281
282                 void InitializeSession ()
283                 {
284                         GetPeerCertificate ();
285
286                         if (IsServer && AskForClientCertificate && !certificateValidated) {
287                                 if (!ValidateCertificate (null, null))
288                                         throw new TlsException (AlertDescription.CertificateUnknown);
289                         }
290
291                         var cipher = (CipherSuiteCode)ssl.GetCipher ();
292                         var protocol = (TlsProtocolCode)ssl.GetVersion ();
293                         Debug ("GET CONNECTION INFO: {0:x}:{0} {1:x}:{1} {2}", cipher, protocol, (TlsProtocolCode)protocol);
294
295                         connectionInfo = new MonoTlsConnectionInfo {
296                                 CipherSuiteCode = cipher,
297                                 ProtocolVersion = GetProtocol (protocol)
298                         };
299                 }
300
301                 static TlsProtocols GetProtocol (TlsProtocolCode protocol)
302                 {
303                         switch (protocol) {
304                         case TlsProtocolCode.Tls10:
305                                 return TlsProtocols.Tls10;
306                         case TlsProtocolCode.Tls11:
307                                 return TlsProtocols.Tls11;
308                         case TlsProtocolCode.Tls12:
309                                 return TlsProtocols.Tls12;
310                         default:
311                                 throw new NotSupportedException ();
312                         }
313                 }
314
315                 public override void Flush ()
316                 {
317                         throw new NotImplementedException ();
318                 }
319
320                 public override int Read (byte[] buffer, int offset, int size, out bool wantMore)
321                 {
322                         Debug ("Read: {0} {1} {2}", buffer.Length, offset, size);
323
324                         var data = Marshal.AllocHGlobal (size);
325                         if (data == IntPtr.Zero)
326                                 throw new OutOfMemoryException ();
327
328                         try {
329                                 MonoBtlsError.ClearError ();
330                                 var status = ssl.Read (data, ref size);
331                                 Debug ("Read done: {0} {1}", status, size);
332
333                                 if (status == MonoBtlsSslError.WantRead) {
334                                         wantMore = true;
335                                         return 0;
336                                 } else if (status != MonoBtlsSslError.None) {
337                                         throw GetException (status);
338                                 }
339
340                                 if (size > 0)
341                                         Marshal.Copy (data, buffer, offset, size);
342
343                                 wantMore = false;
344                                 return size;
345                         } finally {
346                                 Marshal.FreeHGlobal (data);
347                         }
348                 }
349
350                 public override int Write (byte[] buffer, int offset, int size, out bool wantMore)
351                 {
352                         Debug ("Write: {0} {1} {2}", buffer.Length, offset, size);
353
354                         var data = Marshal.AllocHGlobal (size);
355                         if (data == IntPtr.Zero)
356                                 throw new OutOfMemoryException ();
357
358                         try {
359                                 MonoBtlsError.ClearError ();
360                                 Marshal.Copy (buffer, offset, data, size);
361                                 var status = ssl.Write (data, ref size);
362                                 Debug ("Write done: {0} {1}", status, size);
363
364                                 if (status == MonoBtlsSslError.WantWrite) {
365                                         wantMore = true;
366                                         return 0;
367                                 } else if (status != MonoBtlsSslError.None) {
368                                         throw GetException (status);
369                                 }
370
371                                 wantMore = false;
372                                 return size;
373                         } finally {
374                                 Marshal.FreeHGlobal (data);
375                         }
376                 }
377
378                 public override void Close ()
379                 {
380                         Debug ("Close!");
381                         ssl.Dispose ();
382                 }
383
384                 void Dispose<T> (ref T disposable)
385                         where T : class, IDisposable
386                 {
387                         try {
388                                 if (disposable != null)
389                                         disposable.Dispose ();
390                         } catch {
391                                 ;
392                         } finally {
393                                 disposable = null;
394                         }
395                 }
396
397                 protected override void Dispose (bool disposing)
398                 {
399                         try {
400                                 if (disposing) {
401                                         Dispose (ref remoteCertificate);
402                                         Dispose (ref nativeServerCertificate);
403                                         Dispose (ref nativeClientCertificate);
404                                         Dispose (ref clientCertificate);
405                                         Dispose (ref ctx);
406                                         Dispose (ref ssl);
407                                         Dispose (ref bio);
408                                         Dispose (ref errbio);
409                                 }
410                         } finally {
411                                 base.Dispose (disposing);
412                         }
413                 }
414
415                 int IMonoBtlsBioMono.Read (byte[] buffer, int offset, int size, out bool wantMore)
416                 {
417                         Debug ("InternalRead: {0} {1}", offset, size);
418                         var ret = Parent.InternalRead (buffer, offset, size, out wantMore);
419                         Debug ("InternalReadDone: {0} {1}", ret, wantMore);
420                         return ret;
421                 }
422
423                 bool IMonoBtlsBioMono.Write (byte[] buffer, int offset, int size)
424                 {
425                         Debug ("InternalWrite: {0} {1}", offset, size);
426                         var ret = Parent.InternalWrite (buffer, offset, size);
427                         Debug ("InternalWrite done: {0}", ret);
428                         return ret;
429                 }
430
431                 void IMonoBtlsBioMono.Flush ()
432                 {
433                         ;
434                 }
435
436                 void IMonoBtlsBioMono.Close ()
437                 {
438                         ;
439                 }
440
441                 public override bool HasContext {
442                         get { return ssl != null && ssl.IsValid; }
443                 }
444                 public override bool IsAuthenticated {
445                         get { return isAuthenticated; }
446                 }
447                 public override MonoTlsConnectionInfo ConnectionInfo {
448                         get { return connectionInfo; }
449                 }
450                 internal override bool IsRemoteCertificateAvailable {
451                         get { return remoteCertificate != null; }
452                 }
453                 internal override X509Certificate LocalClientCertificate {
454                         get { return clientCertificate; }
455                 }
456                 public override X509Certificate RemoteCertificate {
457                         get { return remoteCertificate; }
458                 }
459                 public override TlsProtocols NegotiatedProtocol {
460                         get { return connectionInfo.ProtocolVersion; }
461                 }
462         }
463 }
464 #endif