3 #if MONO_SECURITY_ALIAS
4 extern alias MonoSecurity;
7 #if MONO_SECURITY_ALIAS
8 using MonoSecurity::Mono.Security.Interface;
9 using MSX = MonoSecurity::Mono.Security.X509;
10 using MonoSecurity::Mono.Security.X509.Extensions;
12 using Mono.Security.Interface;
13 using MSX = Mono.Security.X509;
14 using Mono.Security.X509.Extensions;
19 using System.Threading;
20 using System.Collections;
21 using System.Collections.Generic;
22 using System.Collections.Specialized;
23 using System.Configuration;
24 using System.Net.Configuration;
25 using System.Text.RegularExpressions;
26 using System.Security.Cryptography.X509Certificates;
28 using System.Globalization;
29 using System.Net.Security;
30 using System.Diagnostics;
32 namespace Mono.Net.Security
34 internal static class SystemCertificateValidator
36 static bool is_macosx;
38 static X509RevocationMode revocation_mode;
41 static SystemCertificateValidator ()
45 #elif MONODROID || ORBIS
48 is_macosx = Environment.OSVersion.Platform != PlatformID.Win32NT && System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
52 revocation_mode = X509RevocationMode.NoCheck;
54 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
55 if (String.IsNullOrEmpty (str))
57 revocation_mode = (X509RevocationMode)Enum.Parse (typeof(X509RevocationMode), str, true);
63 public static X509Chain CreateX509Chain (X509CertificateCollection certs)
65 var chain = new X509Chain ();
66 chain.ChainPolicy = new X509ChainPolicy ((X509CertificateCollection)(object)certs);
69 chain.ChainPolicy.RevocationMode = revocation_mode;
75 static bool BuildX509Chain (X509CertificateCollection certs, X509Chain chain, ref SslPolicyErrors errors, ref int status11)
83 var leaf = (X509Certificate2)certs [0];
87 ok = chain.Build (leaf);
89 errors |= GetErrorsFromChain (chain);
90 } catch (Exception e) {
91 Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
92 Console.Error.WriteLine ("Please, report this problem to the Mono team");
93 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
98 status11 = GetStatusFromChain (chain);
100 status11 = -2146762485; // TRUST_E_FAIL - generic
107 static bool CheckUsage (X509CertificateCollection certs, string host, ref SslPolicyErrors errors, ref int status11)
110 var leaf = certs[0] as X509Certificate2;
112 leaf = new 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
121 if (!string.IsNullOrEmpty (host) && !CheckServerIdentity (leaf, host)) {
122 errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
123 status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
131 static bool EvaluateSystem (X509CertificateCollection certs, X509CertificateCollection anchors, string host, X509Chain chain, ref SslPolicyErrors errors, ref int status11)
133 var leaf = certs [0];
138 result = AndroidPlatform.TrustEvaluateSsl (certs);
140 // FIXME: check whether this is still correct.
142 // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
143 // Android (there are no mozroots or preinstalled root certificates),
144 // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
145 // Android just verified the chain; clear RemoteCertificateChainErrors.
146 errors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
148 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
149 status11 = unchecked((int)0x800B010B);
153 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
154 status11 = unchecked((int)0x800B010B);
160 // Attempt to use OSX certificates
161 // Ideally we should return the SecTrustResult
162 OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
164 trustResult = OSX509Certificates.TrustEvaluateSsl (certs, anchors, host);
165 // We could use the other values of trustResult to pass this extra information
166 // to the .NET 2 callback for values like SecTrustResult.Confirm
167 result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
168 trustResult == OSX509Certificates.SecTrustResult.Unspecified);
171 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
176 // TrustEvaluateSsl was successful so there's no trust error
177 // IOW we discard our own chain (since we trust OSX one instead)
180 // callback and DefaultCertificatePolicy needs this since 'result' is not specified
181 status11 = (int)trustResult;
182 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
185 throw new PlatformNotSupportedException ();
188 result = BuildX509Chain (certs, chain, ref errors, ref status11);
195 public static bool Evaluate (
196 MonoTlsSettings settings, string host, X509CertificateCollection certs,
197 X509Chain chain, ref SslPolicyErrors errors, ref int status11)
199 if (!CheckUsage (certs, host, ref errors, ref status11))
202 if (settings != null && settings.SkipSystemValidators)
205 var anchors = settings != null ? settings.TrustAnchors : null;
206 return EvaluateSystem (certs, anchors, host, chain, ref errors, ref status11);
209 internal static bool NeedsChain (MonoTlsSettings settings)
216 if (!CertificateValidationHelper.SupportsX509Chain)
218 if (settings != null)
219 return !settings.SkipSystemValidators || settings.CallbackNeedsCertificateChain;
226 static int GetStatusFromChain (X509Chain chain)
229 foreach (var status in chain.ChainStatus) {
230 X509ChainStatusFlags flags = status.Status;
231 if (flags == X509ChainStatusFlags.NoError)
235 if ((flags & X509ChainStatusFlags.NotTimeValid) != 0)
237 // CERT_E_VALIDITYPERIODNESTING
238 else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0)
241 else if ((flags & X509ChainStatusFlags.Revoked) != 0)
243 // TRUST_E_CERT_SIGNATURE
244 else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0)
246 // CERT_E_WRONG_USAGE
247 else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0)
249 // CERT_E_UNTRUSTEDROOT
250 else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0)
252 // CRYPT_E_NO_REVOCATION_CHECK
253 else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0)
256 else if ((flags & X509ChainStatusFlags.Cyclic) != 0)
258 // TRUST_E_FAIL - generic
259 else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0)
261 // CERT_E_UNTRUSTEDROOT
262 else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0)
264 // TRUST_E_BASIC_CONSTRAINTS
265 else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0)
267 // CERT_E_INVALID_NAME
268 else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0)
270 // CERT_E_INVALID_NAME
271 else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0)
273 // CERT_E_INVALID_NAME
274 else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0)
276 // CERT_E_INVALID_NAME
277 else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0)
279 // CERT_E_INVALID_NAME
280 else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0)
283 else if ((flags & X509ChainStatusFlags.PartialChain) != 0)
286 else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0)
288 // TRUST_E_CERT_SIGNATURE
289 else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0)
291 // CERT_E_WRONG_USAGE
292 else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0)
294 // CRYPT_E_NO_REVOCATION_CHECK
295 else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0)
297 // CERT_E_ISSUERCHAINING
298 else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0)
301 result = 0x800B010B; // TRUST_E_FAIL - generic
303 break; // Exit the loop on the first error
308 static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
310 SslPolicyErrors errors = SslPolicyErrors.None;
311 foreach (var status in chain.ChainStatus) {
312 if (status.Status == X509ChainStatusFlags.NoError)
314 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
322 static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature |
323 X509KeyUsageFlags.KeyAgreement |
324 X509KeyUsageFlags.KeyEncipherment;
325 // Adapted to System 2.0+ from TlsServerCertificate.cs
326 //------------------------------
327 // Note: this method only works for RSA certificates
328 // DH certificates requires some changes - does anyone use one ?
329 static bool CheckCertificateUsage (X509Certificate2 cert)
332 // certificate extensions are required for this
333 // we "must" accept older certificates without proofs
334 if (cert.Version < 3)
337 X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
338 X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
339 if (kux != null && eku != null) {
340 // RFC3280 states that when both KeyUsageExtension and
341 // ExtendedKeyUsageExtension are present then BOTH should
343 if ((kux.KeyUsages & s_flags) == 0)
345 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
346 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
347 } else if (kux != null) {
348 return ((kux.KeyUsages & s_flags) != 0);
349 } else if (eku != null) {
350 // Server Authentication (1.3.6.1.5.5.7.3.1) or
351 // Netscape Server Gated Crypto (2.16.840.1.113730.4)
352 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
353 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
356 // last chance - try with older (deprecated) Netscape extensions
357 X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
359 string text = ext.NetscapeCertType (false);
360 return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
363 } catch (Exception e) {
364 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
365 Console.Error.WriteLine ("Please, report this problem to the Mono team");
370 // RFC2818 - HTTP Over TLS, Section 3.1
371 // http://www.ietf.org/rfc/rfc2818.txt
373 // 1. if present MUST use subjectAltName dNSName as identity
374 // 1.1. if multiples entries a match of any one is acceptable
375 // 1.2. wildcard * is acceptable
376 // 2. URI may be an IP address -> subjectAltName.iPAddress
377 // 2.1. exact match is required
378 // 3. Use of the most specific Common Name (CN=) in the Subject
379 // 3.1 Existing practice but DEPRECATED
380 static bool CheckServerIdentity (X509Certificate2 cert, string targetHost)
383 var mcert = new MSX.X509Certificate (cert.RawData);
384 MSX.X509Extension ext = mcert.Extensions ["2.5.29.17"];
387 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
388 // 1.1 - multiple dNSName
389 foreach (string dns in subjectAltName.DNSNames) {
390 // 1.2 TODO - wildcard support
391 if (Match (targetHost, dns))
395 foreach (string ip in subjectAltName.IPAddresses) {
396 // 2.1. Exact match required
397 if (ip == targetHost)
401 // 3. Common Name (CN=)
402 return CheckDomainName (mcert.SubjectName, targetHost);
403 } catch (Exception e) {
404 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
405 Console.Error.WriteLine ("Please, report this problem to the Mono team");
410 static bool CheckDomainName (string subjectName, string targetHost)
412 string domainName = String.Empty;
413 Regex search = new Regex (@"CN\s*=\s*([^,]*)");
414 MatchCollection elements = search.Matches (subjectName);
415 if (elements.Count == 1) {
416 if (elements [0].Success)
417 domainName = elements [0].Groups [1].Value.ToString ();
420 return Match (targetHost, domainName);
423 // ensure the pattern is valid wrt to RFC2595 and RFC2818
424 // http://www.ietf.org/rfc/rfc2595.txt
425 // http://www.ietf.org/rfc/rfc2818.txt
426 static bool Match (string hostname, string pattern)
428 // check if this is a pattern
429 int index = pattern.IndexOf ('*');
431 // not a pattern, do a direct case-insensitive comparison
432 return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
435 // check pattern validity
436 // A "*" wildcard character MAY be used as the left-most name component in the certificate.
438 // unless this is the last char (valid)
439 if (index != pattern.Length - 1) {
440 // then the next char must be a dot .'.
441 if (pattern [index + 1] != '.')
445 // only one (A) wildcard is supported
446 int i2 = pattern.IndexOf ('*', index + 1);
450 // match the end of the pattern
451 string end = pattern.Substring (index + 1);
452 int length = hostname.Length - end.Length;
453 // no point to check a pattern that is longer than the hostname
457 if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
460 // special case, we start with the wildcard
462 // ensure we hostname non-matched part (start) doesn't contain a dot
463 int i3 = hostname.IndexOf ('.');
464 return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
467 // match the start of the pattern
468 string start = pattern.Substring (0, index);
469 return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);