New test.
[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 // Copyright (C) 2004, 2006 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
25 using System;
26 using System.Net;
27 using System.Collections;
28 using System.Globalization;
29 using System.Text.RegularExpressions;
30 using System.Security.Cryptography;
31 using X509Cert = System.Security.Cryptography.X509Certificates;
32
33 using Mono.Security.X509;
34 using Mono.Security.X509.Extensions;
35
36 namespace Mono.Security.Protocol.Tls.Handshake.Client
37 {
38         internal class TlsServerCertificate : HandshakeMessage
39         {
40                 #region Fields
41
42                 private X509CertificateCollection certificates;
43                 
44                 #endregion
45
46                 #region Constructors
47
48                 public TlsServerCertificate(Context context, byte[] buffer) 
49                         : base(context, HandshakeType.Certificate, buffer)
50                 {
51                 }
52
53                 #endregion
54
55                 #region Methods
56
57                 public override void Update()
58                 {
59                         base.Update();
60                         this.Context.ServerSettings.Certificates = this.certificates;
61                         this.Context.ServerSettings.UpdateCertificateRSA();
62                 }
63
64                 #endregion
65
66                 #region Protected Methods
67
68                 protected override void ProcessAsSsl3()
69                 {
70                         this.ProcessAsTls1();
71                 }
72
73                 protected override void ProcessAsTls1()
74                 {
75                         this.certificates = new X509CertificateCollection();
76                         
77                         int readed      = 0;
78                         int length      = this.ReadInt24();
79
80                         while (readed < length)
81                         {
82                                 // Read certificate length
83                                 int certLength = ReadInt24();
84
85                                 // Increment readed
86                                 readed += 3;
87
88                                 if (certLength > 0)
89                                 {
90                                         // Read certificate data
91                                         byte[] buffer = this.ReadBytes(certLength);
92
93                                         // Create a new X509 Certificate
94                                         X509Certificate certificate = new X509Certificate(buffer);
95                                         certificates.Add(certificate);
96
97                                         readed += certLength;
98
99                                         DebugHelper.WriteLine(
100                                                 String.Format("Server Certificate {0}", certificates.Count),
101                                                 buffer);
102                                 }
103                         }
104
105                         this.validateCertificates(certificates);
106                 }
107
108                 #endregion
109
110                 #region Private Methods
111
112                 // Note: this method only works for RSA certificates
113                 // DH certificates requires some changes - does anyone use one ?
114                 private bool checkCertificateUsage (X509Certificate cert) 
115                 {
116                         ClientContext context = (ClientContext)this.Context;
117
118                         // certificate extensions are required for this
119                         // we "must" accept older certificates without proofs
120                         if (cert.Version < 3)
121                                 return true;
122
123                         KeyUsages ku = KeyUsages.none;
124                         switch (context.Negotiating.Cipher.ExchangeAlgorithmType) 
125                         {
126                                 case ExchangeAlgorithmType.RsaSign:
127                                         ku = KeyUsages.digitalSignature;
128                                         break;
129                                 case ExchangeAlgorithmType.RsaKeyX:
130                                         ku = KeyUsages.keyEncipherment;
131                                         break;
132                                 case ExchangeAlgorithmType.DiffieHellman:
133                                         ku = KeyUsages.keyAgreement;
134                                         break;
135                                 case ExchangeAlgorithmType.Fortezza:
136                                         return false; // unsupported certificate type
137                         }
138
139                         KeyUsageExtension kux = null;
140                         ExtendedKeyUsageExtension eku = null;
141
142                         X509Extension xtn = cert.Extensions ["2.5.29.15"];
143                         if (xtn != null)
144                                 kux = new KeyUsageExtension (xtn);
145
146                         xtn = cert.Extensions ["2.5.29.37"];
147                         if (xtn != null)
148                                 eku = new ExtendedKeyUsageExtension (xtn);
149
150                         if ((kux != null) && (eku != null)) 
151                         {
152                                 // RFC3280 states that when both KeyUsageExtension and 
153                                 // ExtendedKeyUsageExtension are present then BOTH should
154                                 // be valid
155                                 if (!kux.Support (ku))
156                                         return false;
157                                 return (eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1") ||
158                                         eku.KeyPurpose.Contains ("2.16.840.1.113730.4.1"));
159                         }
160                         else if (kux != null) 
161                         {
162                                 return kux.Support (ku);
163                         }
164                         else if (eku != null) 
165                         {
166                                 // Server Authentication (1.3.6.1.5.5.7.3.1) or
167                                 // Netscape Server Gated Crypto (2.16.840.1.113730.4)
168                                 return (eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.1") ||
169                                         eku.KeyPurpose.Contains ("2.16.840.1.113730.4.1"));
170                         }
171
172                         // last chance - try with older (deprecated) Netscape extensions
173                         xtn = cert.Extensions ["2.16.840.1.113730.1.1"];
174                         if (xtn != null) 
175                         {
176                                 NetscapeCertTypeExtension ct = new NetscapeCertTypeExtension (xtn);
177                                 return ct.Support (NetscapeCertTypeExtension.CertTypes.SslServer);
178                         }
179
180                         // certificate isn't valid for SSL server usage
181                         return false;
182                 }
183
184                 private void validateCertificates(X509CertificateCollection certificates)
185                 {
186                         ClientContext           context                 = (ClientContext)this.Context;
187                         AlertDescription        description             = AlertDescription.BadCertificate;
188
189                         // the leaf is the web server certificate
190                         X509Certificate leaf = certificates [0];
191                         X509Cert.X509Certificate cert = new X509Cert.X509Certificate (leaf.RawData);
192
193                         ArrayList errors = new ArrayList();
194
195                         // SSL specific check - not all certificates can be 
196                         // used to server-side SSL some rules applies after 
197                         // all ;-)
198                         if (!checkCertificateUsage (leaf)) 
199                         {
200                                 // WinError.h CERT_E_PURPOSE 0x800B0106
201                                 errors.Add ((int)-2146762490);
202                         }
203
204                         // SSL specific check - does the certificate match 
205                         // the host ?
206                         if (!checkServerIdentity (leaf))
207                         {
208                                 // WinError.h CERT_E_CN_NO_MATCH 0x800B010F
209                                 errors.Add ((int)-2146762481);
210                         }
211
212                         // Note: building and verifying a chain can take much time
213                         // so we do it last (letting simple things fails first)
214
215                         // Note: In TLS the certificates MUST be in order (and
216                         // optionally include the root certificate) so we're not
217                         // building the chain using LoadCertificate (it's faster)
218
219                         // Note: IIS doesn't seem to send the whole certificate chain
220                         // but only the server certificate :-( it's assuming that you
221                         // already have this chain installed on your computer. duh!
222                         // 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
223
224                         // we must remove the leaf certificate from the chain
225                         X509CertificateCollection chain = new X509CertificateCollection (certificates);
226                         chain.Remove (leaf);
227                         X509Chain verify = new X509Chain (chain);
228
229                         bool result = false;
230
231                         try
232                         {
233                                 result = verify.Build (leaf);
234                         }
235                         catch (Exception)
236                         {
237                                 result = false;
238                         }
239
240                         if (!result) 
241                         {
242                                 switch (verify.Status) 
243                                 {
244                                         case X509ChainStatusFlags.InvalidBasicConstraints:
245                                                 // WinError.h TRUST_E_BASIC_CONSTRAINTS 0x80096019
246                                                 errors.Add ((int)-2146869223);
247                                                 break;
248                                         
249                                         case X509ChainStatusFlags.NotSignatureValid:
250                                                 // WinError.h TRUST_E_BAD_DIGEST 0x80096010
251                                                 errors.Add ((int)-2146869232);
252                                                 break;
253                                         
254                                         case X509ChainStatusFlags.NotTimeNested:
255                                                 // WinError.h CERT_E_VALIDITYPERIODNESTING 0x800B0102
256                                                 errors.Add ((int)-2146762494);
257                                                 break;
258                                         
259                                         case X509ChainStatusFlags.NotTimeValid:
260                                                 // WinError.h CERT_E_EXPIRED 0x800B0101
261                                                 description = AlertDescription.CertificateExpired;
262                                                 errors.Add ((int)-2146762495);
263                                                 break;
264                                         
265                                         case X509ChainStatusFlags.PartialChain:
266                                                 // WinError.h CERT_E_CHAINING 0x800B010A
267                                                 description = AlertDescription.UnknownCA;
268                                                 errors.Add ((int)-2146762486);
269                                                 break;
270                                         
271                                         case X509ChainStatusFlags.UntrustedRoot:
272                                                 // WinError.h CERT_E_UNTRUSTEDROOT 0x800B0109
273                                                 description = AlertDescription.UnknownCA;
274                                                 errors.Add ((int)-2146762487);
275                                                 break;
276                                         
277                                         default:
278                                                 // unknown error
279                                                 description = AlertDescription.CertificateUnknown;
280                                                 errors.Add ((int)verify.Status);
281                                                 break;
282                                 }
283                         }
284
285                         int[] certificateErrors = (int[])errors.ToArray(typeof(int));
286
287                         if (!context.SslStream.RaiseServerCertificateValidation(
288                                 cert, 
289                                 certificateErrors))
290                         {
291                                 throw new TlsException(
292                                         description,
293                                         "Invalid certificate received form server.");
294                         }
295                 }
296
297                 // RFC2818 - HTTP Over TLS, Section 3.1
298                 // http://www.ietf.org/rfc/rfc2818.txt
299                 // 
300                 // 1.   if present MUST use subjectAltName dNSName as identity
301                 // 1.1.         if multiples entries a match of any one is acceptable
302                 // 1.2.         wildcard * is acceptable
303                 // 2.   URI may be an IP address -> subjectAltName.iPAddress
304                 // 2.1.         exact match is required
305                 // 3.   Use of the most specific Common Name (CN=) in the Subject
306                 // 3.1          Existing practice but DEPRECATED
307                 private bool checkServerIdentity (X509Certificate cert) 
308                 {
309                         ClientContext context = (ClientContext)this.Context;
310
311                         string targetHost = context.ClientSettings.TargetHost;
312
313                         X509Extension ext = cert.Extensions ["2.5.29.17"];
314                         // 1. subjectAltName
315                         if (ext != null) 
316                         {
317                                 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
318                                 // 1.1 - multiple dNSName
319                                 foreach (string dns in subjectAltName.DNSNames) 
320                                 {
321                                         // 1.2 TODO - wildcard support
322                                         if (dns == targetHost)
323                                                 return true;
324                                 }
325                                 // 2. ipAddress
326                                 foreach (string ip in subjectAltName.IPAddresses) 
327                                 {
328                                         // 2.1. Exact match required
329                                         if (ip == targetHost)
330                                                 return true;
331                                 }
332                         }
333                         // 3. Common Name (CN=)
334                         return checkDomainName (cert.SubjectName);
335                 }
336
337                 private bool checkDomainName(string subjectName)
338                 {
339                         ClientContext context = (ClientContext)this.Context;
340
341                         string  domainName = String.Empty;
342                         Regex search = new Regex(@"CN\s*=\s*([^,]*)");
343
344                         MatchCollection elements = search.Matches(subjectName);
345
346                         if (elements.Count == 1)
347                         {
348                                 if (elements[0].Success)
349                                 {
350                                         domainName = elements[0].Groups[1].Value.ToString();
351                                 }
352                         }
353
354                         // TODO: add wildcard * support
355                         return (String.Compare (context.ClientSettings.TargetHost, domainName, true, CultureInfo.InvariantCulture) == 0);
356
357                         /*
358                          * the only document found describing this is:
359                          * http://www.geocities.com/SiliconValley/Byte/4170/articulos/tls/autentic.htm#Autenticaci%F3n%20del%20Server
360                          * however I don't see how this could deal with wildcards ?
361                          * other issues
362                          * a. there could also be many address returned
363                          * b. Address property is obsoleted in .NET 1.1
364                          * 
365                                                 if (domainName == String.Empty)
366                                                 {
367                                                         return false;
368                                                 }
369                                                 else
370                                                 {
371                                                         string targetHost = context.ClientSettings.TargetHost;
372
373                                                         // Check that the IP is correct
374                                                         try
375                                                         {
376                                                                 IPAddress       ipHost          = Dns.Resolve(targetHost).AddressList[0];
377                                                                 IPAddress       ipDomain        = Dns.Resolve(domainName).AddressList[0];
378
379                                                                 // Note: Address is obsolete in 1.1
380                                                                 return (ipHost.Address == ipDomain.Address);
381                                                         }
382                                                         catch (Exception)
383                                                         {
384                                                                 return false;
385                                                         }
386                                                 }*/
387                 }
388
389                 #endregion
390         }
391 }