(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls.Handshake.Client / TlsServerCertificate.cs
blob822c766722851b0e70f06dedf28cf75739660590
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Sebastien Pouliot, Copyright (c) 2004 Novell (http://www.novell.com)
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the
8 // "Software"), to deal in the Software without restriction, including
9 // without limitation the rights to use, copy, modify, merge, publish,
10 // distribute, sublicense, and/or sell copies of the Software, and to
11 // permit persons to whom the Software is furnished to do so, subject to
12 // the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be
15 // included in all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 using System;
27 using System.Net;
28 using System.Collections;
29 using System.Globalization;
30 using System.Text.RegularExpressions;
31 using System.Security.Cryptography;
32 using X509Cert = System.Security.Cryptography.X509Certificates;
34 using Mono.Security.X509;
35 using Mono.Security.X509.Extensions;
37 namespace Mono.Security.Protocol.Tls.Handshake.Client
39 internal class TlsServerCertificate : HandshakeMessage
41 #region Fields
43 private X509CertificateCollection certificates;
45 #endregion
47 #region Constructors
49 public TlsServerCertificate(Context context, byte[] buffer)
50 : base(context, HandshakeType.Certificate, buffer)
54 #endregion
56 #region Methods
58 public override void Update()
60 base.Update();
61 this.Context.ServerSettings.Certificates = this.certificates;
62 this.Context.ServerSettings.UpdateCertificateRSA();
65 #endregion
67 #region Protected Methods
69 protected override void ProcessAsSsl3()
71 this.ProcessAsTls1();
74 protected override void ProcessAsTls1()
76 this.certificates = new X509CertificateCollection();
78 int readed = 0;
79 int length = this.ReadInt24();
81 while (readed < length)
83 // Read certificate length
84 int certLength = ReadInt24();
86 // Increment readed
87 readed += 3;
89 if (certLength > 0)
91 // Read certificate data
92 byte[] buffer = this.ReadBytes(certLength);
94 // Create a new X509 Certificate
95 X509Certificate certificate = new X509Certificate(buffer);
96 certificates.Add(certificate);
98 readed += certLength;
100 DebugHelper.WriteLine(
101 String.Format("Server Certificate {0}", certificates.Count),
102 buffer);
106 this.validateCertificates(certificates);
109 #endregion
111 #region Private Methods
113 // Note: this method only works for RSA certificates
114 // DH certificates requires some changes - does anyone use one ?
115 private bool checkCertificateUsage (X509Certificate cert)
117 ClientContext context = (ClientContext)this.Context;
119 // certificate extensions are required for this
120 // we "must" accept older certificates without proofs
121 if (cert.Version < 3)
122 return true;
124 KeyUsages ku = KeyUsages.none;
125 switch (context.Cipher.ExchangeAlgorithmType)
127 case ExchangeAlgorithmType.RsaSign:
128 ku = KeyUsages.digitalSignature;
129 break;
130 case ExchangeAlgorithmType.RsaKeyX:
131 ku = KeyUsages.keyEncipherment;
132 break;
133 case ExchangeAlgorithmType.DiffieHellman:
134 ku = KeyUsages.keyAgreement;
135 break;
136 case ExchangeAlgorithmType.Fortezza:
137 return false; // unsupported certificate type
140 KeyUsageExtension kux = null;
141 ExtendedKeyUsageExtension eku = null;
143 X509Extension xtn = cert.Extensions ["2.5.29.15"];
144 if (xtn != null)
145 kux = new KeyUsageExtension (xtn);
147 xtn = cert.Extensions ["2.5.29.37"];
148 if (xtn != null)
149 eku = new ExtendedKeyUsageExtension (xtn);
151 if ((kux != null) && (eku != null))
153 // RFC3280 states that when both KeyUsageExtension and
154 // ExtendedKeyUsageExtension are present then BOTH should
155 // be valid
156 return (kux.Support (ku) &&
157 eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1"));
159 else if (kux != null)
161 return kux.Support (ku);
163 else if (eku != null)
165 // Server Authentication (1.3.6.1.5.5.7.3.1)
166 return eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1");
169 // last chance - try with older (deprecated) Netscape extensions
170 xtn = cert.Extensions ["2.16.840.1.113730.1.1"];
171 if (xtn != null)
173 NetscapeCertTypeExtension ct = new NetscapeCertTypeExtension (xtn);
174 return ct.Support (NetscapeCertTypeExtension.CertTypes.SslServer);
177 // certificate isn't valid for SSL server usage
178 return false;
181 private void validateCertificates(X509CertificateCollection certificates)
183 ClientContext context = (ClientContext)this.Context;
184 AlertDescription description = AlertDescription.BadCertificate;
186 // the leaf is the web server certificate
187 X509Certificate leaf = certificates [0];
188 X509Cert.X509Certificate cert = new X509Cert.X509Certificate (leaf.RawData);
190 ArrayList errors = new ArrayList();
192 // SSL specific check - not all certificates can be
193 // used to server-side SSL some rules applies after
194 // all ;-)
195 if (!checkCertificateUsage (leaf))
197 // WinError.h CERT_E_PURPOSE 0x800B0106
198 errors.Add ((int)-2146762490);
201 // SSL specific check - does the certificate match
202 // the host ?
203 if (!checkServerIdentity (leaf))
205 // WinError.h CERT_E_CN_NO_MATCH 0x800B010F
206 errors.Add ((int)-2146762481);
209 // Note: building and verifying a chain can take much time
210 // so we do it last (letting simple things fails first)
212 // Note: In TLS the certificates MUST be in order (and
213 // optionally include the root certificate) so we're not
214 // building the chain using LoadCertificate (it's faster)
216 // Note: IIS doesn't seem to send the whole certificate chain
217 // but only the server certificate :-( it's assuming that you
218 // already have this chain installed on your computer. duh!
219 // 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
221 // we must remove the leaf certificate from the chain
222 X509CertificateCollection chain = new X509CertificateCollection (certificates);
223 chain.Remove (leaf);
224 X509Chain verify = new X509Chain (chain);
226 bool result = false;
230 result = verify.Build (leaf);
232 catch (Exception)
234 result = false;
237 if (!result)
239 switch (verify.Status)
241 case X509ChainStatusFlags.InvalidBasicConstraints:
242 // WinError.h TRUST_E_BASIC_CONSTRAINTS 0x80096019
243 errors.Add ((int)-2146869223);
244 break;
246 case X509ChainStatusFlags.NotSignatureValid:
247 // WinError.h TRUST_E_BAD_DIGEST 0x80096010
248 errors.Add ((int)-2146869232);
249 break;
251 case X509ChainStatusFlags.NotTimeNested:
252 // WinError.h CERT_E_VALIDITYPERIODNESTING 0x800B0102
253 errors.Add ((int)-2146762494);
254 break;
256 case X509ChainStatusFlags.NotTimeValid:
257 // WinError.h CERT_E_EXPIRED 0x800B0101
258 description = AlertDescription.CertificateExpired;
259 errors.Add ((int)-2146762495);
260 break;
262 case X509ChainStatusFlags.PartialChain:
263 // WinError.h CERT_E_CHAINING 0x800B010A
264 description = AlertDescription.UnknownCA;
265 errors.Add ((int)-2146762486);
266 break;
268 case X509ChainStatusFlags.UntrustedRoot:
269 // WinError.h CERT_E_UNTRUSTEDROOT 0x800B0109
270 description = AlertDescription.UnknownCA;
271 errors.Add ((int)-2146762487);
272 break;
274 default:
275 // unknown error
276 description = AlertDescription.CertificateUnknown;
277 errors.Add ((int)verify.Status);
278 break;
282 int[] certificateErrors = (int[])errors.ToArray(typeof(int));
284 if (!context.SslStream.RaiseServerCertificateValidation(
285 cert,
286 certificateErrors))
288 throw new TlsException(
289 description,
290 "Invalid certificate received form server.");
294 // RFC2818 - HTTP Over TLS, Section 3.1
295 // http://www.ietf.org/rfc/rfc2818.txt
297 // 1. if present MUST use subjectAltName dNSName as identity
298 // 1.1. if multiples entries a match of any one is acceptable
299 // 1.2. wildcard * is acceptable
300 // 2. URI may be an IP address -> subjectAltName.iPAddress
301 // 2.1. exact match is required
302 // 3. Use of the most specific Common Name (CN=) in the Subject
303 // 3.1 Existing practice but DEPRECATED
304 private bool checkServerIdentity (X509Certificate cert)
306 ClientContext context = (ClientContext)this.Context;
308 string targetHost = context.ClientSettings.TargetHost;
310 X509Extension ext = cert.Extensions ["2.5.29.17"];
311 // 1. subjectAltName
312 if (ext != null)
314 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
315 // 1.1 - multiple dNSName
316 foreach (string dns in subjectAltName.DNSNames)
318 // 1.2 TODO - wildcard support
319 if (dns == targetHost)
320 return true;
322 // 2. ipAddress
323 foreach (string ip in subjectAltName.IPAddresses)
325 // 2.1. Exact match required
326 if (ip == targetHost)
327 return true;
330 // 3. Common Name (CN=)
331 return checkDomainName (cert.SubjectName);
334 private bool checkDomainName(string subjectName)
336 ClientContext context = (ClientContext)this.Context;
338 string domainName = String.Empty;
339 Regex search = new Regex(@"CN\s*=\s*([^,]*)");
341 MatchCollection elements = search.Matches(subjectName);
343 if (elements.Count == 1)
345 if (elements[0].Success)
347 domainName = elements[0].Groups[1].Value.ToString();
351 // TODO: add wildcard * support
352 return (String.Compare (context.ClientSettings.TargetHost, domainName, true, CultureInfo.InvariantCulture) == 0);
355 * the only document found describing this is:
356 * http://www.geocities.com/SiliconValley/Byte/4170/articulos/tls/autentic.htm#Autenticaci%F3n%20del%20Server
357 * however I don't see how this could deal with wildcards ?
358 * other issues
359 * a. there could also be many address returned
360 * b. Address property is obsoleted in .NET 1.1
362 if (domainName == String.Empty)
364 return false;
366 else
368 string targetHost = context.ClientSettings.TargetHost;
370 // Check that the IP is correct
373 IPAddress ipHost = Dns.Resolve(targetHost).AddressList[0];
374 IPAddress ipDomain = Dns.Resolve(domainName).AddressList[0];
376 // Note: Address is obsolete in 1.1
377 return (ipHost.Address == ipDomain.Address);
379 catch (Exception)
381 return false;
386 #endregion