5 // Martin Baulig <martin.baulig@xamarin.com>
7 // Copyright (c) 2016 Xamarin Inc. (http://www.xamarin.com)
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:
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
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
27 #if MONO_SECURITY_ALIAS
28 extern alias MonoSecurity;
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;
39 #if MONO_SECURITY_ALIAS
40 using MonoSecurity::Mono.Security.Interface;
42 using Mono.Security.Interface;
45 using MNS = Mono.Net.Security;
49 class MonoBtlsContext : MNS.MobileTlsContext, IMonoBtlsBioMono
51 X509Certificate remoteCertificate;
52 X509Certificate clientCertificate;
53 X509CertificateImplBtls nativeServerCertificate;
54 X509CertificateImplBtls nativeClientCertificate;
60 MonoTlsConnectionInfo connectionInfo;
61 bool certificateValidated;
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)
74 nativeServerCertificate = GetPrivateCertificate (serverCertificate);
77 static X509CertificateImplBtls GetPrivateCertificate (X509Certificate certificate)
79 var impl = certificate.Impl as X509CertificateImplBtls;
81 return (X509CertificateImplBtls)impl.Clone ();
83 var password = Guid.NewGuid ().ToString ();
84 var buffer = certificate.Export (X509ContentType.Pfx, password);
86 impl = new X509CertificateImplBtls ();
87 impl.Import (buffer, password, X509KeyStorageFlags.DefaultKeySet);
91 new public MonoBtlsProvider Provider {
92 get { return (MonoBtlsProvider)base.Provider; }
95 int VerifyCallback (MonoBtlsX509StoreCtx storeCtx)
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;
106 int SelectCallback ()
108 Debug ("SELECT CALLBACK!");
110 GetPeerCertificate ();
111 if (remoteCertificate == null)
112 throw new TlsException (AlertDescription.InternalError, "Cannot request client certificate before receiving one from the server.");
114 var clientCert = SelectClientCertificate (remoteCertificate, null);
115 Debug ("SELECT CALLBACK #1: {0}", clientCert);
116 if (clientCert == null)
119 nativeClientCertificate = GetPrivateCertificate (clientCert);
120 Debug ("SELECT CALLBACK #2: {0}", nativeClientCertificate);
121 clientCertificate = new X509Certificate (nativeClientCertificate);
122 SetPrivateCertificate (nativeClientCertificate);
126 public override void StartHandshake ()
128 InitializeConnection ();
130 ssl = new MonoBtlsSsl (ctx);
132 bio = new MonoBtlsBioMono (this);
136 SetPrivateCertificate (nativeServerCertificate);
138 ssl.SetServerName (TargetHost);
142 void SetPrivateCertificate (X509CertificateImplBtls privateCert)
144 Debug ("SetPrivateCertificate: {0}", privateCert);
145 ssl.SetCertificate (privateCert.X509);
146 ssl.SetPrivateKey (privateCert.NativePrivateKey);
147 var intermediate = privateCert.IntermediateCertificates;
148 if (intermediate == null)
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);
157 Exception GetException (MonoBtlsSslError status)
159 var error = MonoBtlsError.GetError ();
161 return new MonoBtlsException (status);
163 var text = MonoBtlsError.GetErrorString (error);
164 return new MonoBtlsException ("{0} {1}", status, text);
167 public override bool ProcessHandshake ()
171 Debug ("ProcessHandshake");
172 MonoBtlsError.ClearError ();
173 var status = DoProcessHandshake ();
174 Debug ("ProcessHandshake #1: {0}", status);
177 case MonoBtlsSslError.None:
183 case MonoBtlsSslError.WantRead:
184 case MonoBtlsSslError.WantWrite:
187 throw GetException (status);
196 MonoBtlsSslError DoProcessHandshake ()
199 return ssl.Handshake ();
201 return ssl.Accept ();
203 return ssl.Connect ();
206 public override void FinishHandshake ()
208 InitializeSession ();
210 isAuthenticated = true;
213 void SetupCertificateStore ()
216 ctx.CertificateStore.SetDefaultPaths ();
217 ctx.CertificateStore.AddAndroidLookup ();
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);
227 if (Settings != null && Settings.TrustAnchors != null) {
228 var trust = IsServer ? MonoBtlsX509TrustKind.TRUST_CLIENT : MonoBtlsX509TrustKind.TRUST_SERVER;
229 ctx.CertificateStore.AddCollection (Settings.TrustAnchors, trust);
233 void InitializeConnection ()
235 ctx = new MonoBtlsSslCtx ();
238 errbio = MonoBtlsBio.CreateMonoStream (Console.OpenStandardError ());
239 ctx.SetDebugBio (errbio);
242 SetupCertificateStore ();
244 if (!IsServer || AskForClientCertificate)
245 ctx.SetVerifyCallback (VerifyCallback, false);
247 ctx.SetSelectCallback (SelectCallback);
249 var host = TargetHost;
250 if (!string.IsNullOrEmpty (host)) {
251 var pos = TargetHost.IndexOf (':');
253 host = host.Substring (0, pos);
256 ctx.SetVerifyParam (MonoBtlsProvider.GetVerifyParam (host, IsServer));
258 TlsProtocolCode minProtocol, maxProtocol;
259 GetProtocolVersions (out minProtocol, out maxProtocol);
261 ctx.SetMinVersion ((int)minProtocol);
262 ctx.SetMaxVersion ((int)maxProtocol);
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);
272 void GetPeerCertificate ()
274 if (remoteCertificate != null)
276 using (var remoteCert = ssl.GetPeerCertificate ()) {
277 if (remoteCert != null)
278 remoteCertificate = MonoBtlsProvider.CreateCertificate (remoteCert);
282 void InitializeSession ()
284 GetPeerCertificate ();
286 if (IsServer && AskForClientCertificate && !certificateValidated) {
287 if (!ValidateCertificate (null, null))
288 throw new TlsException (AlertDescription.CertificateUnknown);
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);
295 connectionInfo = new MonoTlsConnectionInfo {
296 CipherSuiteCode = cipher,
297 ProtocolVersion = GetProtocol (protocol)
301 static TlsProtocols GetProtocol (TlsProtocolCode 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;
311 throw new NotSupportedException ();
315 public override void Flush ()
317 throw new NotImplementedException ();
320 public override int Read (byte[] buffer, int offset, int size, out bool wantMore)
322 Debug ("Read: {0} {1} {2}", buffer.Length, offset, size);
324 var data = Marshal.AllocHGlobal (size);
325 if (data == IntPtr.Zero)
326 throw new OutOfMemoryException ();
329 MonoBtlsError.ClearError ();
330 var status = ssl.Read (data, ref size);
331 Debug ("Read done: {0} {1}", status, size);
333 if (status == MonoBtlsSslError.WantRead) {
336 } else if (status != MonoBtlsSslError.None) {
337 throw GetException (status);
341 Marshal.Copy (data, buffer, offset, size);
346 Marshal.FreeHGlobal (data);
350 public override int Write (byte[] buffer, int offset, int size, out bool wantMore)
352 Debug ("Write: {0} {1} {2}", buffer.Length, offset, size);
354 var data = Marshal.AllocHGlobal (size);
355 if (data == IntPtr.Zero)
356 throw new OutOfMemoryException ();
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);
364 if (status == MonoBtlsSslError.WantWrite) {
367 } else if (status != MonoBtlsSslError.None) {
368 throw GetException (status);
374 Marshal.FreeHGlobal (data);
378 public override void Close ()
384 void Dispose<T> (ref T disposable)
385 where T : class, IDisposable
388 if (disposable != null)
389 disposable.Dispose ();
397 protected override void Dispose (bool disposing)
401 Dispose (ref remoteCertificate);
402 Dispose (ref nativeServerCertificate);
403 Dispose (ref nativeClientCertificate);
404 Dispose (ref clientCertificate);
408 Dispose (ref errbio);
411 base.Dispose (disposing);
415 int IMonoBtlsBioMono.Read (byte[] buffer, int offset, int size, out bool wantMore)
417 Debug ("InternalRead: {0} {1}", offset, size);
418 var ret = Parent.InternalRead (buffer, offset, size, out wantMore);
419 Debug ("InternalReadDone: {0} {1}", ret, wantMore);
423 bool IMonoBtlsBioMono.Write (byte[] buffer, int offset, int size)
425 Debug ("InternalWrite: {0} {1}", offset, size);
426 var ret = Parent.InternalWrite (buffer, offset, size);
427 Debug ("InternalWrite done: {0}", ret);
431 void IMonoBtlsBioMono.Flush ()
436 void IMonoBtlsBioMono.Close ()
441 public override bool HasContext {
442 get { return ssl != null && ssl.IsValid; }
444 public override bool IsAuthenticated {
445 get { return isAuthenticated; }
447 public override MonoTlsConnectionInfo ConnectionInfo {
448 get { return connectionInfo; }
450 internal override bool IsRemoteCertificateAvailable {
451 get { return remoteCertificate != null; }
453 internal override X509Certificate LocalClientCertificate {
454 get { return clientCertificate; }
456 public override X509Certificate RemoteCertificate {
457 get { return remoteCertificate; }
459 public override TlsProtocols NegotiatedProtocol {
460 get { return connectionInfo.ProtocolVersion; }