[btls]: Cleanup certificate store initialization (#4683)
[mono.git] / mcs / class / System / Mono.Btls / MonoBtlsProvider.cs
1 //
2 // MonoBtlsProvider.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 && MONO_FEATURE_BTLS
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
38 #if MONO_SECURITY_ALIAS
39 using MonoSecurity::Mono.Security.Interface;
40 using MX = MonoSecurity::Mono.Security.X509;
41 #else
42 using Mono.Security.Interface;
43 using MX = Mono.Security.X509;
44 #endif
45
46 using MNS = Mono.Net.Security;
47
48 namespace Mono.Btls
49 {
50         class MonoBtlsProvider : MonoTlsProvider
51         {
52                 static readonly Guid id = new Guid ("432d18c9-9348-4b90-bfbf-9f2a10e1f15b");
53
54                 public override Guid ID {
55                         get { return id; }
56                 }
57                 public override string Name {
58                         get { return "btls"; }
59                 }
60
61                 internal MonoBtlsProvider ()
62                 {
63                         if (!MNS.MonoTlsProviderFactory.IsBtlsSupported ())
64                                 throw new NotSupportedException ("BTLS is not supported in this runtime.");
65                 }
66
67                 public override bool SupportsSslStream {
68                         get { return true; }
69                 }
70
71                 public override bool SupportsMonoExtensions {
72                         get { return true; }
73                 }
74
75                 public override bool SupportsConnectionInfo {
76                         get { return true; }
77                 }
78
79                 public override SslProtocols SupportedProtocols {
80                         get { return SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; }
81                 }
82
83                 public override IMonoSslStream CreateSslStream (
84                         Stream innerStream, bool leaveInnerStreamOpen,
85                         MonoTlsSettings settings = null)
86                 {
87                         return new MonoBtlsStream (
88                                 innerStream, leaveInnerStreamOpen, settings, this);
89                 }
90
91                 internal override bool HasNativeCertificates {
92                         get { return true; }
93                 }
94
95                 internal override X509Certificate2Impl GetNativeCertificate (
96                         byte[] data, string password, X509KeyStorageFlags flags)
97                 {
98                         var impl = new X509CertificateImplBtls (false);
99                         impl.Import (data, password, flags);
100                         return impl;
101                 }
102
103                 internal override X509Certificate2Impl GetNativeCertificate (
104                         X509Certificate certificate)
105                 {
106                         var impl = certificate.Impl as X509CertificateImplBtls;
107                         if (impl != null)
108                                 return (X509Certificate2Impl)impl.Clone ();
109
110                         var data = certificate.GetRawCertData ();
111                         return new X509CertificateImplBtls (data, MonoBtlsX509Format.DER, false);
112                 }
113
114                 internal static MonoBtlsX509VerifyParam GetVerifyParam (MonoTlsSettings settings, string targetHost, bool serverMode)
115                 {
116                         MonoBtlsX509VerifyParam param;
117                         if (serverMode)
118                                 param = MonoBtlsX509VerifyParam.GetSslClient ();
119                         else
120                                 param = MonoBtlsX509VerifyParam.GetSslServer ();
121
122                         if (targetHost == null && settings?.CertificateValidationTime == null)
123                                 return param;
124
125                         try {
126                                 var copy = param.Copy ();
127                                 if (targetHost != null)
128                                         copy.SetHost (targetHost);
129                                 if (settings?.CertificateValidationTime != null)
130                                         copy.SetTime (settings.CertificateValidationTime.Value);
131                                 return copy;
132                         } finally {
133                                 param.Dispose ();
134                         }
135                 }
136
137                 internal override bool ValidateCertificate (
138                         ICertificateValidator2 validator, string targetHost, bool serverMode,
139                         X509CertificateCollection certificates, bool wantsChain, ref X509Chain chain,
140                         ref MonoSslPolicyErrors errors, ref int status11)
141                 {
142                         if (chain != null) {
143                                 var chainImpl = (X509ChainImplBtls)chain.Impl;
144                                 var success = chainImpl.StoreCtx.VerifyResult == 1;
145                                 CheckValidationResult (
146                                         validator, targetHost, serverMode, certificates,
147                                         wantsChain, chain, chainImpl.StoreCtx,
148                                         success, ref errors, ref status11);
149                                 return success;
150                         }
151
152                         using (var store = new MonoBtlsX509Store ())
153                         using (var nativeChain = MonoBtlsProvider.GetNativeChain (certificates))
154                         using (var param = GetVerifyParam (validator.Settings, targetHost, serverMode))
155                         using (var storeCtx = new MonoBtlsX509StoreCtx ()) {
156                                 SetupCertificateStore (store, validator.Settings, serverMode);
157
158                                 storeCtx.Initialize (store, nativeChain);
159
160                                 storeCtx.SetVerifyParam (param);
161
162                                 var ret = storeCtx.Verify ();
163
164                                 var success = ret == 1;
165
166                                 if (wantsChain && chain == null) {
167                                         chain = GetManagedChain (nativeChain);
168                                 }
169
170                                 CheckValidationResult (
171                                         validator, targetHost, serverMode, certificates,
172                                         wantsChain, null, storeCtx,
173                                         success, ref errors, ref status11);
174                                 return success;
175                         }
176                 }
177
178                 internal static bool ValidateCertificate (MonoBtlsX509Chain chain, MonoBtlsX509VerifyParam param)
179                 {
180                         using (var store = new MonoBtlsX509Store ())
181                         using (var storeCtx = new MonoBtlsX509StoreCtx ()) {
182                                 /*
183                                  * We're called from X509Certificate2.Verify() via X509CertificateImplBtls.Verify().
184                                  *
185                                  * Use the default settings and assume client-mode.
186                                  */
187                                 SetupCertificateStore (store, MonoTlsSettings.DefaultSettings, false);
188
189                                 storeCtx.Initialize (store, chain);
190
191                                 if (param != null)
192                                         storeCtx.SetVerifyParam (param);
193
194                                 var ret = storeCtx.Verify ();
195
196                                 return ret == 1;
197                         }
198                 }
199
200                 void CheckValidationResult (
201                         ICertificateValidator validator, string targetHost, bool serverMode,
202                         X509CertificateCollection certificates, bool wantsChain,
203                         X509Chain chain, MonoBtlsX509StoreCtx storeCtx,
204                         bool success, ref MonoSslPolicyErrors errors, ref int status11)
205                 {
206                         if (!success) {
207                                 errors = MonoSslPolicyErrors.RemoteCertificateChainErrors;
208                                 status11 = unchecked((int)0x800B010B);
209                         }
210                 }
211
212                 internal static void SetupCertificateStore (MonoBtlsX509Store store, MonoTlsSettings settings, bool server)
213                 {
214                         /*
215                          * In server-mode, we only add certificates which are explicitly trusted via
216                          * MonoTlsSettings.TrustAnchors.
217                          * 
218                          * MonoTlsSettings.CertificateSearchPaths is ignored on Android.
219                          * 
220                          */
221
222 #if MONODROID
223                         AddTrustedRoots (store, settings, server);
224                         if (!server)
225                                 SetupDefaultCertificateStore (store);
226                         return;
227 #else
228                         if (server || settings?.CertificateSearchPaths == null) {
229                                 AddTrustedRoots (store, settings, server);
230                                 if (!server)
231                                         SetupDefaultCertificateStore (store);
232                                 return;
233                         }
234
235                         foreach (var path in settings.CertificateSearchPaths) {
236                                 switch (path) {
237                                 case "@default":
238                                         AddTrustedRoots (store, settings, server);
239                                         AddUserStore (store);
240                                         AddMachineStore (store);
241                                         break;
242                                 case "@trusted":
243                                         AddTrustedRoots (store, settings, server);
244                                         break;
245                                 case "@user":
246                                         AddUserStore (store);
247                                         break;
248                                 case "@machine":
249                                         AddMachineStore (store);
250                                         break;
251                                 default:
252                                         if (path.StartsWith ("@pem:")) {
253                                                 var realPath = path.Substring (5);
254                                                 if (Directory.Exists (realPath))
255                                                         store.AddDirectoryLookup (realPath, MonoBtlsX509FileType.PEM);
256                                                 break;
257                                         } else if (path.StartsWith ("@der:")) {
258                                                 var realPath = path.Substring (5);
259                                                 if (Directory.Exists (realPath))
260                                                         store.AddDirectoryLookup (realPath, MonoBtlsX509FileType.ASN1);
261                                                 break;
262                                         }
263                                         throw new NotSupportedException (string.Format ("Invalid item `{0}' in MonoTlsSettings.CertificateSearchPaths.", path));
264                                 }
265                         }
266 #endif
267                 }
268
269                 static void SetupDefaultCertificateStore (MonoBtlsX509Store store)
270                 {
271 #if MONODROID
272                         store.SetDefaultPaths ();
273                         store.AddAndroidLookup ();
274 #else
275                         AddUserStore (store);
276                         AddMachineStore (store);
277 #endif
278                 }
279
280 #if !MONODROID
281                 static void AddUserStore (MonoBtlsX509Store store)
282                 {
283                         var userPath = MonoBtlsX509StoreManager.GetStorePath (MonoBtlsX509StoreType.UserTrustedRoots);
284                         if (Directory.Exists (userPath))
285                                 store.AddDirectoryLookup (userPath, MonoBtlsX509FileType.PEM);
286                 }
287
288                 static void AddMachineStore (MonoBtlsX509Store store)
289                 {
290                         var machinePath = MonoBtlsX509StoreManager.GetStorePath (MonoBtlsX509StoreType.MachineTrustedRoots);
291                         if (Directory.Exists (machinePath))
292                                 store.AddDirectoryLookup (machinePath, MonoBtlsX509FileType.PEM);
293                 }
294 #endif
295
296                 static void AddTrustedRoots (MonoBtlsX509Store store, MonoTlsSettings settings, bool server)
297                 {
298                         if (settings?.TrustAnchors == null)
299                                 return;
300                         var trust = server ? MonoBtlsX509TrustKind.TRUST_CLIENT : MonoBtlsX509TrustKind.TRUST_SERVER;
301                         store.AddCollection (settings.TrustAnchors, trust);
302                 }
303
304                 public static string GetSystemStoreLocation ()
305                 {
306 #if MONODROID
307                         return "/system/etc/security/cacerts";
308 #else
309                         return MonoBtlsX509StoreManager.GetStorePath (MonoBtlsX509StoreType.MachineTrustedRoots);
310 #endif
311                 }
312
313                 public static X509Certificate CreateCertificate (byte[] data, MonoBtlsX509Format format, bool disallowFallback = false)
314                 {
315                         using (var impl = new X509CertificateImplBtls (data, format, disallowFallback)) {
316                                 return new X509Certificate (impl);
317                         }
318                 }
319
320                 public static X509Certificate2 CreateCertificate2 (byte[] data, MonoBtlsX509Format format, bool disallowFallback = false)
321                 {
322                         using (var impl = new X509CertificateImplBtls (data, format, disallowFallback)) {
323                                 return new X509Certificate2 (impl);
324                         }
325                 }
326
327                 public static X509Certificate2 CreateCertificate2 (byte[] data, string password, bool disallowFallback = false)
328                 {
329                         using (var impl = new X509CertificateImplBtls (disallowFallback)) {
330                                 impl.Import (data, password, X509KeyStorageFlags.DefaultKeySet);
331                                 return new X509Certificate2 (impl);
332                         }
333                 }
334
335                 public static X509Certificate CreateCertificate (MonoBtlsX509 x509)
336                 {
337                         using (var impl = new X509CertificateImplBtls (x509, true))
338                                 return new X509Certificate (impl);
339                 }
340
341                 public static X509Chain CreateChain ()
342                 {
343                         using (var impl = new X509ChainImplBtls ())
344                                 return new X509Chain (impl);
345                 }
346
347                 public static X509Chain GetManagedChain (MonoBtlsX509Chain chain)
348                 {
349                         var impl = new X509ChainImplBtls (chain);
350                         return new X509Chain (impl);
351                 }
352
353                 public static MonoBtlsX509 GetBtlsCertificate (X509Certificate certificate)
354                 {
355                         var impl = certificate.Impl as X509CertificateImplBtls;
356                         if (impl != null)
357                                 return impl.X509.Copy ();
358
359                         return MonoBtlsX509.LoadFromData (certificate.GetRawCertData (), MonoBtlsX509Format.DER);
360                 }
361
362                 public static MonoBtlsX509Chain GetNativeChain (X509CertificateCollection certificates)
363                 {
364                         var chain = new MonoBtlsX509Chain ();
365                         try {
366                                 foreach (var cert in certificates) {
367                                         using (var x509 = GetBtlsCertificate (cert))
368                                                 chain.AddCertificate (x509);
369                                 }
370                                 return chain;
371                         } catch {
372                                 chain.Dispose ();
373                                 throw;
374                         }
375                 }
376         }
377 }
378 #endif