copying the latest Sys.Web.Services from trunk.
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls.Handshake.Client / TlsServerCertificate.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Sebastien Pouliot, Copyright (c) 2004 Novell (http://www.novell.com)
4
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.
24 //
25
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;
33
34 using Mono.Security.X509;
35 using Mono.Security.X509.Extensions;
36
37 namespace Mono.Security.Protocol.Tls.Handshake.Client
38 {
39         internal class TlsServerCertificate : HandshakeMessage
40         {
41                 #region Fields
42
43                 private X509CertificateCollection certificates;
44                 
45                 #endregion
46
47                 #region Constructors
48
49                 public TlsServerCertificate(Context context, byte[] buffer) 
50                         : base(context, HandshakeType.Certificate, buffer)
51                 {
52                 }
53
54                 #endregion
55
56                 #region Methods
57
58                 public override void Update()
59                 {
60                         base.Update();
61                         this.Context.ServerSettings.Certificates = this.certificates;
62                         this.Context.ServerSettings.UpdateCertificateRSA();
63                 }
64
65                 #endregion
66
67                 #region Protected Methods
68
69                 protected override void ProcessAsSsl3()
70                 {
71                         this.ProcessAsTls1();
72                 }
73
74                 protected override void ProcessAsTls1()
75                 {
76                         this.certificates = new X509CertificateCollection();
77                         
78                         int readed      = 0;
79                         int length      = this.ReadInt24();
80
81                         while (readed < length)
82                         {
83                                 // Read certificate length
84                                 int certLength = ReadInt24();
85
86                                 // Increment readed
87                                 readed += 3;
88
89                                 if (certLength > 0)
90                                 {
91                                         // Read certificate data
92                                         byte[] buffer = this.ReadBytes(certLength);
93
94                                         // Create a new X509 Certificate
95                                         X509Certificate certificate = new X509Certificate(buffer);
96                                         certificates.Add(certificate);
97
98                                         readed += certLength;
99
100                                         DebugHelper.WriteLine(
101                                                 String.Format("Server Certificate {0}", certificates.Count),
102                                                 buffer);
103                                 }
104                         }
105
106                         this.validateCertificates(certificates);
107                 }
108
109                 #endregion
110
111                 #region Private Methods
112
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) 
116                 {
117                         ClientContext context = (ClientContext)this.Context;
118
119                         // certificate extensions are required for this
120                         // we "must" accept older certificates without proofs
121                         if (cert.Version < 3)
122                                 return true;
123
124                         KeyUsages ku = KeyUsages.none;
125                         switch (context.Cipher.ExchangeAlgorithmType) 
126                         {
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
138                         }
139
140                         KeyUsageExtension kux = null;
141                         ExtendedKeyUsageExtension eku = null;
142
143                         X509Extension xtn = cert.Extensions ["2.5.29.15"];
144                         if (xtn != null)
145                                 kux = new KeyUsageExtension (xtn);
146
147                         xtn = cert.Extensions ["2.5.29.37"];
148                         if (xtn != null)
149                                 eku = new ExtendedKeyUsageExtension (xtn);
150
151                         if ((kux != null) && (eku != null)) 
152                         {
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"));
158                         }
159                         else if (kux != null) 
160                         {
161                                 return kux.Support (ku);
162                         }
163                         else if (eku != null) 
164                         {
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");
167                         }
168
169                         // last chance - try with older (deprecated) Netscape extensions
170                         xtn = cert.Extensions ["2.16.840.1.113730.1.1"];
171                         if (xtn != null) 
172                         {
173                                 NetscapeCertTypeExtension ct = new NetscapeCertTypeExtension (xtn);
174                                 return ct.Support (NetscapeCertTypeExtension.CertTypes.SslServer);
175                         }
176
177                         // certificate isn't valid for SSL server usage
178                         return false;
179                 }
180
181                 private void validateCertificates(X509CertificateCollection certificates)
182                 {
183                         ClientContext           context                 = (ClientContext)this.Context;
184                         AlertDescription        description             = AlertDescription.BadCertificate;
185
186                         // the leaf is the web server certificate
187                         X509Certificate leaf = certificates [0];
188                         X509Cert.X509Certificate cert = new X509Cert.X509Certificate (leaf.RawData);
189
190                         ArrayList errors = new ArrayList();
191
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)) 
196                         {
197                                 // WinError.h CERT_E_PURPOSE 0x800B0106
198                                 errors.Add ((int)-2146762490);
199                         }
200
201                         // SSL specific check - does the certificate match 
202                         // the host ?
203                         if (!checkServerIdentity (leaf))
204                         {
205                                 // WinError.h CERT_E_CN_NO_MATCH 0x800B010F
206                                 errors.Add ((int)-2146762481);
207                         }
208
209                         // Note: building and verifying a chain can take much time
210                         // so we do it last (letting simple things fails first)
211
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)
215
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
220
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);
225
226                         bool result = false;
227
228                         try
229                         {
230                                 result = verify.Build (leaf);
231                         }
232                         catch (Exception)
233                         {
234                                 result = false;
235                         }
236
237                         if (!result) 
238                         {
239                                 switch (verify.Status) 
240                                 {
241                                         case X509ChainStatusFlags.InvalidBasicConstraints:
242                                                 // WinError.h TRUST_E_BASIC_CONSTRAINTS 0x80096019
243                                                 errors.Add ((int)-2146869223);
244                                                 break;
245                                         
246                                         case X509ChainStatusFlags.NotSignatureValid:
247                                                 // WinError.h TRUST_E_BAD_DIGEST 0x80096010
248                                                 errors.Add ((int)-2146869232);
249                                                 break;
250                                         
251                                         case X509ChainStatusFlags.NotTimeNested:
252                                                 // WinError.h CERT_E_VALIDITYPERIODNESTING 0x800B0102
253                                                 errors.Add ((int)-2146762494);
254                                                 break;
255                                         
256                                         case X509ChainStatusFlags.NotTimeValid:
257                                                 // WinError.h CERT_E_EXPIRED 0x800B0101
258                                                 description = AlertDescription.CertificateExpired;
259                                                 errors.Add ((int)-2146762495);
260                                                 break;
261                                         
262                                         case X509ChainStatusFlags.PartialChain:
263                                                 // WinError.h CERT_E_CHAINING 0x800B010A
264                                                 description = AlertDescription.UnknownCA;
265                                                 errors.Add ((int)-2146762486);
266                                                 break;
267                                         
268                                         case X509ChainStatusFlags.UntrustedRoot:
269                                                 // WinError.h CERT_E_UNTRUSTEDROOT 0x800B0109
270                                                 description = AlertDescription.UnknownCA;
271                                                 errors.Add ((int)-2146762487);
272                                                 break;
273                                         
274                                         default:
275                                                 // unknown error
276                                                 description = AlertDescription.CertificateUnknown;
277                                                 errors.Add ((int)verify.Status);
278                                                 break;
279                                 }
280                         }
281
282                         int[] certificateErrors = (int[])errors.ToArray(typeof(int));
283
284                         if (!context.SslStream.RaiseServerCertificateValidation(
285                                 cert, 
286                                 certificateErrors))
287                         {
288                                 throw new TlsException(
289                                         description,
290                                         "Invalid certificate received form server.");
291                         }
292                 }
293
294                 // RFC2818 - HTTP Over TLS, Section 3.1
295                 // http://www.ietf.org/rfc/rfc2818.txt
296                 // 
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) 
305                 {
306                         ClientContext context = (ClientContext)this.Context;
307
308                         string targetHost = context.ClientSettings.TargetHost;
309
310                         X509Extension ext = cert.Extensions ["2.5.29.17"];
311                         // 1. subjectAltName
312                         if (ext != null) 
313                         {
314                                 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
315                                 // 1.1 - multiple dNSName
316                                 foreach (string dns in subjectAltName.DNSNames) 
317                                 {
318                                         // 1.2 TODO - wildcard support
319                                         if (dns == targetHost)
320                                                 return true;
321                                 }
322                                 // 2. ipAddress
323                                 foreach (string ip in subjectAltName.IPAddresses) 
324                                 {
325                                         // 2.1. Exact match required
326                                         if (ip == targetHost)
327                                                 return true;
328                                 }
329                         }
330                         // 3. Common Name (CN=)
331                         return checkDomainName (cert.SubjectName);
332                 }
333
334                 private bool checkDomainName(string subjectName)
335                 {
336                         ClientContext context = (ClientContext)this.Context;
337
338                         string  domainName = String.Empty;
339                         Regex search = new Regex(@"CN\s*=\s*([^,]*)");
340
341                         MatchCollection elements = search.Matches(subjectName);
342
343                         if (elements.Count == 1)
344                         {
345                                 if (elements[0].Success)
346                                 {
347                                         domainName = elements[0].Groups[1].Value.ToString();
348                                 }
349                         }
350
351                         // TODO: add wildcard * support
352                         return (String.Compare (context.ClientSettings.TargetHost, domainName, true, CultureInfo.InvariantCulture) == 0);
353
354                         /*
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
361                          * 
362                                                 if (domainName == String.Empty)
363                                                 {
364                                                         return false;
365                                                 }
366                                                 else
367                                                 {
368                                                         string targetHost = context.ClientSettings.TargetHost;
369
370                                                         // Check that the IP is correct
371                                                         try
372                                                         {
373                                                                 IPAddress       ipHost          = Dns.Resolve(targetHost).AddressList[0];
374                                                                 IPAddress       ipDomain        = Dns.Resolve(domainName).AddressList[0];
375
376                                                                 // Note: Address is obsolete in 1.1
377                                                                 return (ipHost.Address == ipDomain.Address);
378                                                         }
379                                                         catch (Exception)
380                                                         {
381                                                                 return false;
382                                                         }
383                                                 }*/
384                 }
385
386                 #endregion
387         }
388 }