1 #if SECURITY_DEP && MONO_FEATURE_APPLETLS
3 // AppleCertificateHelper.cs
6 // Martin Baulig <martin.baulig@xamarin.com>
8 // Copyright (c) 2015 Xamarin, Inc.
11 #if MONO_SECURITY_ALIAS
12 extern alias MonoSecurity;
16 using System.Collections;
17 using System.Reflection;
18 using System.Runtime.InteropServices;
19 using System.Security.Cryptography.X509Certificates;
21 #if MONO_SECURITY_ALIAS
22 using MonoSecurity::Mono.Security.Interface;
24 using Mono.Security.Interface;
27 namespace Mono.AppleTls
29 static class AppleCertificateHelper
31 public static SecIdentity GetIdentity (X509Certificate certificate)
34 * If we got an 'X509Certificate2', then we require it to have a private key
37 var certificate2 = certificate as X509Certificate2;
38 if (certificate2 != null)
40 return SecIdentity.Import (certificate2);
42 return SecImportExport.ItemImport (certificate2);
46 * Reading Certificates from the Mac Keychain
47 * ==========================================
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
55 * Keychain Dialog Popups
56 * ======================
58 * When using Xamarin.Mac or Xamarin.iOS, we try to search the keychain
59 * for the certificate and private key.
61 * On Xamarin.iOS, this is easy because each app has its own keychain.
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".
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.
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.
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.
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).
90 using (var secCert = new SecCertificate (certificate)) {
91 return SecKeyChain.FindIdentity (secCert, true);
98 public static SecIdentity GetIdentity (X509Certificate certificate, out SecCertificate[] intermediateCerts)
100 var identity = GetIdentity (certificate);
102 var impl2 = certificate.Impl as X509Certificate2Impl;
103 if (impl2 == null || impl2.IntermediateCertificates == null) {
104 intermediateCerts = new SecCertificate [0];
109 intermediateCerts = new SecCertificate [impl2.IntermediateCertificates.Count];
110 for (int i = 0; i < intermediateCerts.Length; i++)
111 intermediateCerts [i] = new SecCertificate (impl2.IntermediateCertificates [i]);
120 public static bool InvokeSystemCertificateValidator (
121 ICertificateValidator2 validator, string targetHost, bool serverMode,
122 X509CertificateCollection certificates,
123 ref MonoSslPolicyErrors errors, ref int status11)
125 if (certificates == null) {
126 errors |= MonoSslPolicyErrors.RemoteCertificateNotAvailable;
130 if (!string.IsNullOrEmpty (targetHost)) {
131 var pos = targetHost.IndexOf (':');
133 targetHost = targetHost.Substring (0, pos);
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);
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 ());
151 var result = trust.Evaluate ();
152 if (result == SecTrustResult.Unspecified)
155 errors |= MonoSslPolicyErrors.RemoteCertificateChainErrors;