3 #if MONO_SECURITY_ALIAS
4 extern alias MonoSecurity;
7 extern alias PrebuiltSystem;
10 #if MONO_SECURITY_ALIAS
11 using MonoSecurity::Mono.Security.Interface;
12 using MSX = MonoSecurity::Mono.Security.X509;
13 using MonoSecurity::Mono.Security.X509.Extensions;
15 using Mono.Security.Interface;
16 using MSX = Mono.Security.X509;
17 using Mono.Security.X509.Extensions;
20 using XX509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
22 using XX509CertificateCollection = System.Security.Cryptography.X509Certificates.X509CertificateCollection;
27 using System.Threading;
28 using System.Collections;
29 using System.Collections.Generic;
30 using System.Collections.Specialized;
31 using System.Configuration;
32 using System.Net.Configuration;
33 using System.Text.RegularExpressions;
34 using System.Security.Cryptography.X509Certificates;
36 using System.Globalization;
37 using System.Net.Security;
38 using System.Diagnostics;
40 namespace Mono.Net.Security
42 internal class SystemCertificateValidator
44 static bool is_macosx;
45 static bool is_mobile;
47 static X509RevocationMode revocation_mode;
50 static SystemCertificateValidator ()
59 is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
64 revocation_mode = X509RevocationMode.NoCheck;
66 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
67 if (String.IsNullOrEmpty (str))
69 revocation_mode = (X509RevocationMode)Enum.Parse (typeof(X509RevocationMode), str, true);
75 public virtual X509Chain ComputeX509Chain (XX509CertificateCollection certs, ref SslPolicyErrors errors, ref int status11)
83 var chain = new X509Chain ();
84 chain.ChainPolicy = new X509ChainPolicy ();
86 chain.ChainPolicy.RevocationMode = revocation_mode;
88 for (int i = 1; i < certs.Count; i++) {
89 chain.ChainPolicy.ExtraStore.Add (certs [i]);
92 var leaf = (X509Certificate2)certs [0];
95 if (!chain.Build (leaf))
96 errors |= GetErrorsFromChain (chain);
97 } catch (Exception e) {
98 Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
99 Console.Error.WriteLine ("Please, report this problem to the Mono team");
100 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
103 status11 = GetStatusFromChain (chain);
109 public virtual void CheckUsage (XX509CertificateCollection certs, string host, ref SslPolicyErrors errors, ref int status11)
112 var leaf = (X509Certificate2)certs[0];
113 // for OSX and iOS we're using the native API to check for the SSL server policy and host names
115 if (!CheckCertificateUsage (leaf)) {
116 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
117 status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
120 if (host != null && !CheckServerIdentity (leaf, host)) {
121 errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
122 status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
128 public virtual bool EvaluateSystem (XX509CertificateCollection certs, XX509CertificateCollection anchors, string host, X509Chain chain, ref SslPolicyErrors errors, ref int status11)
130 var leaf = certs [0];
134 result = AndroidPlatform.TrustEvaluateSsl (certs);
136 // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
137 // Android (there are no mozroots or preinstalled root certificates),
138 // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
139 // Android just verified the chain; clear RemoteCertificateChainErrors.
140 errors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
144 // Attempt to use OSX certificates
145 // Ideally we should return the SecTrustResult
146 OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
148 trustResult = OSX509Certificates.TrustEvaluateSsl (certs, anchors, host);
149 // We could use the other values of trustResult to pass this extra information
150 // to the .NET 2 callback for values like SecTrustResult.Confirm
151 result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
152 trustResult == OSX509Certificates.SecTrustResult.Unspecified);
158 // TrustEvaluateSsl was successful so there's no trust error
159 // IOW we discard our own chain (since we trust OSX one instead)
162 // callback and DefaultCertificatePolicy needs this since 'result' is not specified
163 status11 = (int)trustResult;
164 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
173 static int GetStatusFromChain (X509Chain chain)
176 foreach (var status in chain.ChainStatus) {
177 X509ChainStatusFlags flags = status.Status;
178 if (flags == X509ChainStatusFlags.NoError)
182 if ((flags & X509ChainStatusFlags.NotTimeValid) != 0)
184 // CERT_E_VALIDITYPERIODNESTING
185 else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0)
188 else if ((flags & X509ChainStatusFlags.Revoked) != 0)
190 // TRUST_E_CERT_SIGNATURE
191 else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0)
193 // CERT_E_WRONG_USAGE
194 else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0)
196 // CERT_E_UNTRUSTEDROOT
197 else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0)
199 // CRYPT_E_NO_REVOCATION_CHECK
200 else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0)
203 else if ((flags & X509ChainStatusFlags.Cyclic) != 0)
205 // TRUST_E_FAIL - generic
206 else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0)
208 // CERT_E_UNTRUSTEDROOT
209 else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0)
211 // TRUST_E_BASIC_CONSTRAINTS
212 else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0)
214 // CERT_E_INVALID_NAME
215 else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0)
217 // CERT_E_INVALID_NAME
218 else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0)
220 // CERT_E_INVALID_NAME
221 else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0)
223 // CERT_E_INVALID_NAME
224 else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0)
226 // CERT_E_INVALID_NAME
227 else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0)
230 else if ((flags & X509ChainStatusFlags.PartialChain) != 0)
233 else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0)
235 // TRUST_E_CERT_SIGNATURE
236 else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0)
238 // CERT_E_WRONG_USAGE
239 else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0)
241 // CRYPT_E_NO_REVOCATION_CHECK
242 else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0)
244 // CERT_E_ISSUERCHAINING
245 else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0)
248 result = 0x800B010B; // TRUST_E_FAIL - generic
250 break; // Exit the loop on the first error
255 static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
257 SslPolicyErrors errors = SslPolicyErrors.None;
258 foreach (var status in chain.ChainStatus) {
259 if (status.Status == X509ChainStatusFlags.NoError)
261 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
269 static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature |
270 X509KeyUsageFlags.KeyAgreement |
271 X509KeyUsageFlags.KeyEncipherment;
272 // Adapted to System 2.0+ from TlsServerCertificate.cs
273 //------------------------------
274 // Note: this method only works for RSA certificates
275 // DH certificates requires some changes - does anyone use one ?
276 static bool CheckCertificateUsage (X509Certificate2 cert)
279 // certificate extensions are required for this
280 // we "must" accept older certificates without proofs
281 if (cert.Version < 3)
284 X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
285 X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
286 if (kux != null && eku != null) {
287 // RFC3280 states that when both KeyUsageExtension and
288 // ExtendedKeyUsageExtension are present then BOTH should
290 if ((kux.KeyUsages & s_flags) == 0)
292 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
293 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
294 } else if (kux != null) {
295 return ((kux.KeyUsages & s_flags) != 0);
296 } else if (eku != null) {
297 // Server Authentication (1.3.6.1.5.5.7.3.1) or
298 // Netscape Server Gated Crypto (2.16.840.1.113730.4)
299 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
300 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
303 // last chance - try with older (deprecated) Netscape extensions
304 X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
306 string text = ext.NetscapeCertType (false);
307 return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
310 } catch (Exception e) {
311 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
312 Console.Error.WriteLine ("Please, report this problem to the Mono team");
317 // RFC2818 - HTTP Over TLS, Section 3.1
318 // http://www.ietf.org/rfc/rfc2818.txt
320 // 1. if present MUST use subjectAltName dNSName as identity
321 // 1.1. if multiples entries a match of any one is acceptable
322 // 1.2. wildcard * is acceptable
323 // 2. URI may be an IP address -> subjectAltName.iPAddress
324 // 2.1. exact match is required
325 // 3. Use of the most specific Common Name (CN=) in the Subject
326 // 3.1 Existing practice but DEPRECATED
327 static bool CheckServerIdentity (X509Certificate2 cert, string targetHost)
330 var mcert = new MSX.X509Certificate (cert.RawData);
331 MSX.X509Extension ext = mcert.Extensions ["2.5.29.17"];
334 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
335 // 1.1 - multiple dNSName
336 foreach (string dns in subjectAltName.DNSNames) {
337 // 1.2 TODO - wildcard support
338 if (Match (targetHost, dns))
342 foreach (string ip in subjectAltName.IPAddresses) {
343 // 2.1. Exact match required
344 if (ip == targetHost)
348 // 3. Common Name (CN=)
349 return CheckDomainName (mcert.SubjectName, targetHost);
350 } catch (Exception e) {
351 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
352 Console.Error.WriteLine ("Please, report this problem to the Mono team");
357 static bool CheckDomainName (string subjectName, string targetHost)
359 string domainName = String.Empty;
360 Regex search = new Regex (@"CN\s*=\s*([^,]*)");
361 MatchCollection elements = search.Matches (subjectName);
362 if (elements.Count == 1) {
363 if (elements [0].Success)
364 domainName = elements [0].Groups [1].Value.ToString ();
367 return Match (targetHost, domainName);
370 // ensure the pattern is valid wrt to RFC2595 and RFC2818
371 // http://www.ietf.org/rfc/rfc2595.txt
372 // http://www.ietf.org/rfc/rfc2818.txt
373 static bool Match (string hostname, string pattern)
375 // check if this is a pattern
376 int index = pattern.IndexOf ('*');
378 // not a pattern, do a direct case-insensitive comparison
379 return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
382 // check pattern validity
383 // A "*" wildcard character MAY be used as the left-most name component in the certificate.
385 // unless this is the last char (valid)
386 if (index != pattern.Length - 1) {
387 // then the next char must be a dot .'.
388 if (pattern [index + 1] != '.')
392 // only one (A) wildcard is supported
393 int i2 = pattern.IndexOf ('*', index + 1);
397 // match the end of the pattern
398 string end = pattern.Substring (index + 1);
399 int length = hostname.Length - end.Length;
400 // no point to check a pattern that is longer than the hostname
404 if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
407 // special case, we start with the wildcard
409 // ensure we hostname non-matched part (start) doesn't contain a dot
410 int i3 = hostname.IndexOf ('.');
411 return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
414 // match the start of the pattern
415 string start = pattern.Substring (0, index);
416 return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);