710433ef071619a21464d6a07c31f1eafc12cff0
[mono.git] / mcs / class / System / Mono.AppleTls / AppleCertificateHelper.cs
1 #if SECURITY_DEP && MONO_FEATURE_APPLETLS
2 //
3 // AppleCertificateHelper.cs
4 //
5 // Author:
6 //       Martin Baulig <martin.baulig@xamarin.com>
7 //
8 // Copyright (c) 2015 Xamarin, Inc.
9 //
10
11 #if MONO_SECURITY_ALIAS
12 extern alias MonoSecurity;
13 #endif
14
15 using System;
16 using System.Collections;
17 using System.Reflection;
18 using System.Runtime.InteropServices;
19 using System.Security.Cryptography.X509Certificates;
20
21 #if MONO_SECURITY_ALIAS
22 using MonoSecurity::Mono.Security.Interface;
23 #else
24 using Mono.Security.Interface;
25 #endif
26
27 namespace Mono.AppleTls
28 {
29         static class AppleCertificateHelper
30         {
31                 public static SecIdentity GetIdentity (X509Certificate certificate)
32                 {
33                         /*
34                          * If we got an 'X509Certificate2', then we require it to have a private key
35                          * and import it.
36                          */
37                         var certificate2 = certificate as X509Certificate2;
38                         if (certificate2 != null)
39 #if MONOTOUCH
40                                 return SecIdentity.Import (certificate2);
41 #else
42                                 return SecImportExport.ItemImport (certificate2);
43 #endif
44
45                         /*
46                          * Reading Certificates from the Mac Keychain
47                          * ==========================================
48                          *
49                          * Reading the private key from the keychain is a new feature introduced with
50                          * AppleTls on XamMac and iOS. On Desktop Mono, this new feature has several
51                          * known issues and it also did not received any testing yet. We go back to the old
52                          * way of doing things, which is to explicitly provide an X509Certificate2 with a
53                          * private key.
54                          * 
55                          * Keychain Dialog Popups
56                          * ======================
57                          * 
58                          * When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
59                          * for the certificate and private key.
60                          * 
61                          * On Xamarin.iOS, this is easy because each app has its own keychain.
62                          * 
63                          * On Xamarin.Mac, the .app package needs to be trusted via code-sign
64                          * to get permission to access the user's keychain. [FIXME: I still have to
65                          * research how to actually do that.] Without this, you will get a popup
66                          * message each time, asking you whether you want to allow the app to access
67                          * the keychain, but you can make these go away by selecting "Trust always".
68                          * 
69                          * On Desktop Mono, this is problematic because selecting "Trust always"
70                          * give the 'mono' binary (and thus everything you'll ever run with Mono)
71                          * permission to retrieve the private key from the keychain.
72                          * 
73                          * This code would also trigger constant keychain popup messages,
74                          * which could only be suppressed by granting full trust. It also makes it
75                          * impossible to run Mono in headless mode.
76                          * 
77                          * SecIdentityCreate
78                          * =================
79                          * 
80                          * To avoid these problems, we are currently using an undocumented API
81                          * called SecIdentityRef() to avoid using the Mac keychain whenever a
82                          * X509Certificate2 with a private key is used.
83                          * 
84                          * On iOS and XamMac, you can still provide the X509Certificate without
85                          * a private key - in this case, a keychain search will be performed (and you
86                          * may get a popup message on XamMac).
87                          */
88
89 #if MOBILE
90                         using (var secCert = new SecCertificate (certificate)) {
91                                 return SecKeyChain.FindIdentity (secCert, true);
92                         }
93 #else
94                         return null;
95 #endif
96                 }
97
98                 public static SecIdentity GetIdentity (X509Certificate certificate, out SecCertificate[] intermediateCerts)
99                 {
100                         var identity = GetIdentity (certificate);
101
102                         var impl2 = certificate.Impl as X509Certificate2Impl;
103                         if (impl2 == null || impl2.IntermediateCertificates == null) {
104                                 intermediateCerts = new SecCertificate [0];
105                                 return identity;
106                         }
107
108                         try {
109                                 intermediateCerts = new SecCertificate [impl2.IntermediateCertificates.Count];
110                                 for (int i = 0; i < intermediateCerts.Length; i++)
111                                         intermediateCerts [i] = new SecCertificate (impl2.IntermediateCertificates [i]);
112
113                                 return identity;
114                         } catch {
115                                 identity.Dispose ();
116                                 throw;
117                         }
118                 }
119
120                 public static bool InvokeSystemCertificateValidator (
121                         ICertificateValidator2 validator, string targetHost, bool serverMode,
122                         X509CertificateCollection certificates,
123                         ref MonoSslPolicyErrors errors, ref int status11)
124                 {
125                         if (certificates == null) {
126                                 errors |= MonoSslPolicyErrors.RemoteCertificateNotAvailable;
127                                 return false;
128                         }
129
130                         if (!string.IsNullOrEmpty (targetHost)) {
131                                 var pos = targetHost.IndexOf (':');
132                                 if (pos > 0)
133                                         targetHost = targetHost.Substring (0, pos);
134                         }
135
136                         using (var policy = SecPolicy.CreateSslPolicy (!serverMode, targetHost))
137                         using (var trust = new SecTrust (certificates, policy)) {
138                                 if (validator.Settings.TrustAnchors != null) {
139                                         var status = trust.SetAnchorCertificates (validator.Settings.TrustAnchors);
140                                         if (status != SecStatusCode.Success)
141                                                 throw new InvalidOperationException (status.ToString ());
142                                         trust.SetAnchorCertificatesOnly (false);
143                                 }
144
145                                 if (validator.Settings.CertificateValidationTime != null) {
146                                         var status = trust.SetVerifyDate (validator.Settings.CertificateValidationTime.Value);
147                                         if (status != SecStatusCode.Success)
148                                                 throw new InvalidOperationException (status.ToString ());
149                                 }
150
151                                 var result = trust.Evaluate ();
152                                 if (result == SecTrustResult.Unspecified)
153                                         return true;
154
155                                 errors |= MonoSslPolicyErrors.RemoteCertificateChainErrors;
156                                 return false;
157                         }
158                 }
159         }
160 }
161 #endif