// Transport Security Layer (TLS)
// Copyright (c) 2003-2004 Carlos Guzman Alvarez
-// Sebastien Pouliot, Copyright (c) 2004 Novell (http://www.novell.com)
-
+// Copyright (C) 2004, 2006-2010 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
using Mono.Security.X509;
using Mono.Security.X509.Extensions;
+using Mono.Security.Interface;
+
namespace Mono.Security.Protocol.Tls.Handshake.Client
{
internal class TlsServerCertificate : HandshakeMessage
return true;
KeyUsages ku = KeyUsages.none;
- switch (context.Cipher.ExchangeAlgorithmType)
+ switch (context.Negotiating.Cipher.ExchangeAlgorithmType)
{
case ExchangeAlgorithmType.RsaSign:
ku = KeyUsages.digitalSignature;
// RFC3280 states that when both KeyUsageExtension and
// ExtendedKeyUsageExtension are present then BOTH should
// be valid
- return (kux.Support (ku) &&
- eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1"));
+ if (!kux.Support (ku))
+ return false;
+ return (eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1") ||
+ eku.KeyPurpose.Contains ("2.16.840.1.113730.4.1"));
}
else if (kux != null)
{
}
else if (eku != null)
{
- // Server Authentication (1.3.6.1.5.5.7.3.1)
- return eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1");
+ // Server Authentication (1.3.6.1.5.5.7.3.1) or
+ // Netscape Server Gated Crypto (2.16.840.1.113730.4)
+ return (eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1") ||
+ eku.KeyPurpose.Contains ("2.16.840.1.113730.4.1"));
}
// last chance - try with older (deprecated) Netscape extensions
return ct.Support (NetscapeCertTypeExtension.CertTypes.SslServer);
}
- // certificate isn't valid for SSL server usage
- return false;
+ // if the CN=host (checked later) then we assume this is meant for SSL/TLS
+ // e.g. the new smtp.gmail.com certificate
+ return true;
}
private void validateCertificates(X509CertificateCollection certificates)
ClientContext context = (ClientContext)this.Context;
AlertDescription description = AlertDescription.BadCertificate;
+#if INSIDE_SYSTEM
+ // This helps the linker to remove a lot of validation code that will never be used since
+ // System.dll will, for OSX and iOS, uses the operating system X.509 certificate validations
+ RemoteValidation (context, description);
+#else
+ if (context.SslStream.HaveRemoteValidation2Callback)
+ RemoteValidation (context, description);
+ else
+ LocalValidation (context, description);
+#endif
+ }
+
+ void RemoteValidation (ClientContext context, AlertDescription description)
+ {
+ ValidationResult res = context.SslStream.RaiseServerCertificateValidation2 (certificates);
+ if (res.Trusted)
+ return;
+
+ long error = res.ErrorCode;
+ switch (error) {
+ case 0x800B0101:
+ description = AlertDescription.CertificateExpired;
+ break;
+ case 0x800B010A:
+ description = AlertDescription.UnknownCA;
+ break;
+ case 0x800B0109:
+ description = AlertDescription.UnknownCA;
+ break;
+ default:
+ description = AlertDescription.CertificateUnknown;
+ break;
+ }
+ string err = String.Format ("Invalid certificate received from server. Error code: 0x{0:x}", error);
+ throw new TlsException (description, err);
+ }
+
+ void LocalValidation (ClientContext context, AlertDescription description)
+ {
// the leaf is the web server certificate
X509Certificate leaf = certificates [0];
X509Cert.X509Certificate cert = new X509Cert.X509Certificate (leaf.RawData);
{
throw new TlsException(
description,
- "Invalid certificate received form server.");
+ "Invalid certificate received from server.");
}
}
foreach (string dns in subjectAltName.DNSNames)
{
// 1.2 TODO - wildcard support
- if (dns == targetHost)
+ if (Match (targetHost, dns))
return true;
}
// 2. ipAddress
}
}
- // TODO: add wildcard * support
- return (String.Compare (context.ClientSettings.TargetHost, domainName, true, CultureInfo.InvariantCulture) == 0);
-
- /*
- * the only document found describing this is:
- * http://www.geocities.com/SiliconValley/Byte/4170/articulos/tls/autentic.htm#Autenticaci%F3n%20del%20Server
- * however I don't see how this could deal with wildcards ?
- * other issues
- * a. there could also be many address returned
- * b. Address property is obsoleted in .NET 1.1
- *
- if (domainName == String.Empty)
- {
- return false;
- }
- else
- {
- string targetHost = context.ClientSettings.TargetHost;
-
- // Check that the IP is correct
- try
- {
- IPAddress ipHost = Dns.Resolve(targetHost).AddressList[0];
- IPAddress ipDomain = Dns.Resolve(domainName).AddressList[0];
-
- // Note: Address is obsolete in 1.1
- return (ipHost.Address == ipDomain.Address);
- }
- catch (Exception)
- {
- return false;
- }
- }*/
+ return Match (context.ClientSettings.TargetHost, domainName);
+ }
+
+ // ensure the pattern is valid wrt to RFC2595 and RFC2818
+ // http://www.ietf.org/rfc/rfc2595.txt
+ // http://www.ietf.org/rfc/rfc2818.txt
+ static bool Match (string hostname, string pattern)
+ {
+ // check if this is a pattern
+ int index = pattern.IndexOf ('*');
+ if (index == -1) {
+ // not a pattern, do a direct case-insensitive comparison
+ return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
+ }
+
+ // check pattern validity
+ // A "*" wildcard character MAY be used as the left-most name component in the certificate.
+
+ // unless this is the last char (valid)
+ if (index != pattern.Length - 1) {
+ // then the next char must be a dot .'.
+ if (pattern [index + 1] != '.')
+ return false;
+ }
+
+ // only one (A) wildcard is supported
+ int i2 = pattern.IndexOf ('*', index + 1);
+ if (i2 != -1)
+ return false;
+
+ // match the end of the pattern
+ string end = pattern.Substring (index + 1);
+ int length = hostname.Length - end.Length;
+ // no point to check a pattern that is longer than the hostname
+ if (length <= 0)
+ return false;
+
+ if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
+ return false;
+
+ // special case, we start with the wildcard
+ if (index == 0) {
+ // ensure we hostname non-matched part (start) doesn't contain a dot
+ int i3 = hostname.IndexOf ('.');
+ return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
+ }
+
+ // match the start of the pattern
+ string start = pattern.Substring (0, index);
+ return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);
}
#endregion