TlsServerCertificate.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Transport Security Layer (TLS)
  2. // Copyright (c) 2003-2004 Carlos Guzman Alvarez
  3. // Copyright (C) 2004, 2006-2007 Novell, Inc (http://www.novell.com)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining
  6. // a copy of this software and associated documentation files (the
  7. // "Software"), to deal in the Software without restriction, including
  8. // without limitation the rights to use, copy, modify, merge, publish,
  9. // distribute, sublicense, and/or sell copies of the Software, and to
  10. // permit persons to whom the Software is furnished to do so, subject to
  11. // the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be
  14. // included in all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. //
  24. using System;
  25. using System.Net;
  26. using System.Collections;
  27. using System.Globalization;
  28. using System.Text.RegularExpressions;
  29. using System.Security.Cryptography;
  30. using X509Cert = System.Security.Cryptography.X509Certificates;
  31. using Mono.Security.X509;
  32. using Mono.Security.X509.Extensions;
  33. namespace Mono.Security.Protocol.Tls.Handshake.Client
  34. {
  35. internal class TlsServerCertificate : HandshakeMessage
  36. {
  37. #region Fields
  38. private X509CertificateCollection certificates;
  39. #endregion
  40. #region Constructors
  41. public TlsServerCertificate(Context context, byte[] buffer)
  42. : base(context, HandshakeType.Certificate, buffer)
  43. {
  44. }
  45. #endregion
  46. #region Methods
  47. public override void Update()
  48. {
  49. base.Update();
  50. this.Context.ServerSettings.Certificates = this.certificates;
  51. this.Context.ServerSettings.UpdateCertificateRSA();
  52. }
  53. #endregion
  54. #region Protected Methods
  55. protected override void ProcessAsSsl3()
  56. {
  57. this.ProcessAsTls1();
  58. }
  59. protected override void ProcessAsTls1()
  60. {
  61. this.certificates = new X509CertificateCollection();
  62. int readed = 0;
  63. int length = this.ReadInt24();
  64. while (readed < length)
  65. {
  66. // Read certificate length
  67. int certLength = ReadInt24();
  68. // Increment readed
  69. readed += 3;
  70. if (certLength > 0)
  71. {
  72. // Read certificate data
  73. byte[] buffer = this.ReadBytes(certLength);
  74. // Create a new X509 Certificate
  75. X509Certificate certificate = new X509Certificate(buffer);
  76. certificates.Add(certificate);
  77. readed += certLength;
  78. DebugHelper.WriteLine(
  79. String.Format("Server Certificate {0}", certificates.Count),
  80. buffer);
  81. }
  82. }
  83. this.validateCertificates(certificates);
  84. }
  85. #endregion
  86. #region Private Methods
  87. // Note: this method only works for RSA certificates
  88. // DH certificates requires some changes - does anyone use one ?
  89. private bool checkCertificateUsage (X509Certificate cert)
  90. {
  91. ClientContext context = (ClientContext)this.Context;
  92. // certificate extensions are required for this
  93. // we "must" accept older certificates without proofs
  94. if (cert.Version < 3)
  95. return true;
  96. KeyUsages ku = KeyUsages.none;
  97. switch (context.Negotiating.Cipher.ExchangeAlgorithmType)
  98. {
  99. case ExchangeAlgorithmType.RsaSign:
  100. ku = KeyUsages.digitalSignature;
  101. break;
  102. case ExchangeAlgorithmType.RsaKeyX:
  103. ku = KeyUsages.keyEncipherment;
  104. break;
  105. case ExchangeAlgorithmType.DiffieHellman:
  106. ku = KeyUsages.keyAgreement;
  107. break;
  108. case ExchangeAlgorithmType.Fortezza:
  109. return false; // unsupported certificate type
  110. }
  111. KeyUsageExtension kux = null;
  112. ExtendedKeyUsageExtension eku = null;
  113. X509Extension xtn = cert.Extensions ["2.5.29.15"];
  114. if (xtn != null)
  115. kux = new KeyUsageExtension (xtn);
  116. xtn = cert.Extensions ["2.5.29.37"];
  117. if (xtn != null)
  118. eku = new ExtendedKeyUsageExtension (xtn);
  119. if ((kux != null) && (eku != null))
  120. {
  121. // RFC3280 states that when both KeyUsageExtension and
  122. // ExtendedKeyUsageExtension are present then BOTH should
  123. // be valid
  124. if (!kux.Support (ku))
  125. return false;
  126. return (eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1") ||
  127. eku.KeyPurpose.Contains ("2.16.840.1.113730.4.1"));
  128. }
  129. else if (kux != null)
  130. {
  131. return kux.Support (ku);
  132. }
  133. else if (eku != null)
  134. {
  135. // Server Authentication (1.3.6.1.5.5.7.3.1) or
  136. // Netscape Server Gated Crypto (2.16.840.1.113730.4)
  137. return (eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1") ||
  138. eku.KeyPurpose.Contains ("2.16.840.1.113730.4.1"));
  139. }
  140. // last chance - try with older (deprecated) Netscape extensions
  141. xtn = cert.Extensions ["2.16.840.1.113730.1.1"];
  142. if (xtn != null)
  143. {
  144. NetscapeCertTypeExtension ct = new NetscapeCertTypeExtension (xtn);
  145. return ct.Support (NetscapeCertTypeExtension.CertTypes.SslServer);
  146. }
  147. // certificate isn't valid for SSL server usage
  148. return false;
  149. }
  150. private void validateCertificates(X509CertificateCollection certificates)
  151. {
  152. ClientContext context = (ClientContext)this.Context;
  153. AlertDescription description = AlertDescription.BadCertificate;
  154. // the leaf is the web server certificate
  155. X509Certificate leaf = certificates [0];
  156. X509Cert.X509Certificate cert = new X509Cert.X509Certificate (leaf.RawData);
  157. ArrayList errors = new ArrayList();
  158. // SSL specific check - not all certificates can be
  159. // used to server-side SSL some rules applies after
  160. // all ;-)
  161. if (!checkCertificateUsage (leaf))
  162. {
  163. // WinError.h CERT_E_PURPOSE 0x800B0106
  164. errors.Add ((int)-2146762490);
  165. }
  166. // SSL specific check - does the certificate match
  167. // the host ?
  168. if (!checkServerIdentity (leaf))
  169. {
  170. // WinError.h CERT_E_CN_NO_MATCH 0x800B010F
  171. errors.Add ((int)-2146762481);
  172. }
  173. // Note: building and verifying a chain can take much time
  174. // so we do it last (letting simple things fails first)
  175. // Note: In TLS the certificates MUST be in order (and
  176. // optionally include the root certificate) so we're not
  177. // building the chain using LoadCertificate (it's faster)
  178. // Note: IIS doesn't seem to send the whole certificate chain
  179. // but only the server certificate :-( it's assuming that you
  180. // already have this chain installed on your computer. duh!
  181. // http://groups.google.ca/groups?q=IIS+server+certificate+chain&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=85058s%24avd%241%40nnrp1.deja.com&rnum=3
  182. // we must remove the leaf certificate from the chain
  183. X509CertificateCollection chain = new X509CertificateCollection (certificates);
  184. chain.Remove (leaf);
  185. X509Chain verify = new X509Chain (chain);
  186. bool result = false;
  187. try
  188. {
  189. result = verify.Build (leaf);
  190. }
  191. catch (Exception)
  192. {
  193. result = false;
  194. }
  195. if (!result)
  196. {
  197. switch (verify.Status)
  198. {
  199. case X509ChainStatusFlags.InvalidBasicConstraints:
  200. // WinError.h TRUST_E_BASIC_CONSTRAINTS 0x80096019
  201. errors.Add ((int)-2146869223);
  202. break;
  203. case X509ChainStatusFlags.NotSignatureValid:
  204. // WinError.h TRUST_E_BAD_DIGEST 0x80096010
  205. errors.Add ((int)-2146869232);
  206. break;
  207. case X509ChainStatusFlags.NotTimeNested:
  208. // WinError.h CERT_E_VALIDITYPERIODNESTING 0x800B0102
  209. errors.Add ((int)-2146762494);
  210. break;
  211. case X509ChainStatusFlags.NotTimeValid:
  212. // WinError.h CERT_E_EXPIRED 0x800B0101
  213. description = AlertDescription.CertificateExpired;
  214. errors.Add ((int)-2146762495);
  215. break;
  216. case X509ChainStatusFlags.PartialChain:
  217. // WinError.h CERT_E_CHAINING 0x800B010A
  218. description = AlertDescription.UnknownCA;
  219. errors.Add ((int)-2146762486);
  220. break;
  221. case X509ChainStatusFlags.UntrustedRoot:
  222. // WinError.h CERT_E_UNTRUSTEDROOT 0x800B0109
  223. description = AlertDescription.UnknownCA;
  224. errors.Add ((int)-2146762487);
  225. break;
  226. default:
  227. // unknown error
  228. description = AlertDescription.CertificateUnknown;
  229. errors.Add ((int)verify.Status);
  230. break;
  231. }
  232. }
  233. int[] certificateErrors = (int[])errors.ToArray(typeof(int));
  234. if (!context.SslStream.RaiseServerCertificateValidation(
  235. cert,
  236. certificateErrors))
  237. {
  238. throw new TlsException(
  239. description,
  240. "Invalid certificate received from server.");
  241. }
  242. }
  243. // RFC2818 - HTTP Over TLS, Section 3.1
  244. // http://www.ietf.org/rfc/rfc2818.txt
  245. //
  246. // 1. if present MUST use subjectAltName dNSName as identity
  247. // 1.1. if multiples entries a match of any one is acceptable
  248. // 1.2. wildcard * is acceptable
  249. // 2. URI may be an IP address -> subjectAltName.iPAddress
  250. // 2.1. exact match is required
  251. // 3. Use of the most specific Common Name (CN=) in the Subject
  252. // 3.1 Existing practice but DEPRECATED
  253. private bool checkServerIdentity (X509Certificate cert)
  254. {
  255. ClientContext context = (ClientContext)this.Context;
  256. string targetHost = context.ClientSettings.TargetHost;
  257. X509Extension ext = cert.Extensions ["2.5.29.17"];
  258. // 1. subjectAltName
  259. if (ext != null)
  260. {
  261. SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
  262. // 1.1 - multiple dNSName
  263. foreach (string dns in subjectAltName.DNSNames)
  264. {
  265. // 1.2 TODO - wildcard support
  266. if (Match (targetHost, dns))
  267. return true;
  268. }
  269. // 2. ipAddress
  270. foreach (string ip in subjectAltName.IPAddresses)
  271. {
  272. // 2.1. Exact match required
  273. if (ip == targetHost)
  274. return true;
  275. }
  276. }
  277. // 3. Common Name (CN=)
  278. return checkDomainName (cert.SubjectName);
  279. }
  280. private bool checkDomainName(string subjectName)
  281. {
  282. ClientContext context = (ClientContext)this.Context;
  283. string domainName = String.Empty;
  284. Regex search = new Regex(@"CN\s*=\s*([^,]*)");
  285. MatchCollection elements = search.Matches(subjectName);
  286. if (elements.Count == 1)
  287. {
  288. if (elements[0].Success)
  289. {
  290. domainName = elements[0].Groups[1].Value.ToString();
  291. }
  292. }
  293. return Match (context.ClientSettings.TargetHost, domainName);
  294. }
  295. // ensure the pattern is valid wrt to RFC2595 and RFC2818
  296. // http://www.ietf.org/rfc/rfc2595.txt
  297. // http://www.ietf.org/rfc/rfc2818.txt
  298. static bool Match (string hostname, string pattern)
  299. {
  300. // check if this is a pattern
  301. int index = pattern.IndexOf ('*');
  302. if (index == -1) {
  303. // not a pattern, do a direct case-insensitive comparison
  304. return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
  305. }
  306. // check pattern validity
  307. // A "*" wildcard character MAY be used as the left-most name component in the certificate.
  308. // unless this is the last char (valid)
  309. if (index != pattern.Length - 1) {
  310. // then the next char must be a dot .'.
  311. if (pattern [index + 1] != '.')
  312. return false;
  313. }
  314. // only one (A) wildcard is supported
  315. int i2 = pattern.IndexOf ('*', index + 1);
  316. if (i2 != -1)
  317. return false;
  318. // match the end of the pattern
  319. string end = pattern.Substring (index + 1);
  320. int length = hostname.Length - end.Length;
  321. // no point to check a pattern that is longer than the hostname
  322. if (length <= 0)
  323. return false;
  324. if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
  325. return false;
  326. // special case, we start with the wildcard
  327. if (index == 0) {
  328. // ensure we hostname non-matched part (start) doesn't contain a dot
  329. int i3 = hostname.IndexOf ('.');
  330. return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
  331. }
  332. // match the start of the pattern
  333. string start = pattern.Substring (0, index);
  334. return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);
  335. }
  336. #endregion
  337. }
  338. }