1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Sebastien Pouliot, Copyright (c) 2004 Novell (http://www.novell.com)
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:
14 // The above copyright notice and this permission notice shall be
15 // included in all copies or substantial portions of the Software.
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.
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
43 private X509CertificateCollection certificates
;
49 public TlsServerCertificate(Context context
, byte[] buffer
)
50 : base(context
, HandshakeType
.Certificate
, buffer
)
58 public override void Update()
61 this.Context
.ServerSettings
.Certificates
= this.certificates
;
62 this.Context
.ServerSettings
.UpdateCertificateRSA();
67 #region Protected Methods
69 protected override void ProcessAsSsl3()
74 protected override void ProcessAsTls1()
76 this.certificates
= new X509CertificateCollection();
79 int length
= this.ReadInt24();
81 while (readed
< length
)
83 // Read certificate length
84 int certLength
= ReadInt24();
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
);
100 DebugHelper
.WriteLine(
101 String
.Format("Server Certificate {0}", certificates
.Count
),
106 this.validateCertificates(certificates
);
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)
124 KeyUsages ku
= KeyUsages
.none
;
125 switch (context
.Cipher
.ExchangeAlgorithmType
)
127 case ExchangeAlgorithmType
.RsaSign
:
128 ku
= KeyUsages
.digitalSignature
;
130 case ExchangeAlgorithmType
.RsaKeyX
:
131 ku
= KeyUsages
.keyEncipherment
;
133 case ExchangeAlgorithmType
.DiffieHellman
:
134 ku
= KeyUsages
.keyAgreement
;
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"];
145 kux
= new KeyUsageExtension (xtn
);
147 xtn
= cert
.Extensions
["2.5.29.37"];
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
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"];
173 NetscapeCertTypeExtension ct
= new NetscapeCertTypeExtension (xtn
);
174 return ct
.Support (NetscapeCertTypeExtension
.CertTypes
.SslServer
);
177 // certificate isn't valid for SSL server usage
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
195 if (!checkCertificateUsage (leaf
))
197 // WinError.h CERT_E_PURPOSE 0x800B0106
198 errors
.Add ((int)-2146762490);
201 // SSL specific check - does the certificate match
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
);
224 X509Chain verify
= new X509Chain (chain
);
230 result
= verify
.Build (leaf
);
239 switch (verify
.Status
)
241 case X509ChainStatusFlags
.InvalidBasicConstraints
:
242 // WinError.h TRUST_E_BASIC_CONSTRAINTS 0x80096019
243 errors
.Add ((int)-2146869223);
246 case X509ChainStatusFlags
.NotSignatureValid
:
247 // WinError.h TRUST_E_BAD_DIGEST 0x80096010
248 errors
.Add ((int)-2146869232);
251 case X509ChainStatusFlags
.NotTimeNested
:
252 // WinError.h CERT_E_VALIDITYPERIODNESTING 0x800B0102
253 errors
.Add ((int)-2146762494);
256 case X509ChainStatusFlags
.NotTimeValid
:
257 // WinError.h CERT_E_EXPIRED 0x800B0101
258 description
= AlertDescription
.CertificateExpired
;
259 errors
.Add ((int)-2146762495);
262 case X509ChainStatusFlags
.PartialChain
:
263 // WinError.h CERT_E_CHAINING 0x800B010A
264 description
= AlertDescription
.UnknownCA
;
265 errors
.Add ((int)-2146762486);
268 case X509ChainStatusFlags
.UntrustedRoot
:
269 // WinError.h CERT_E_UNTRUSTEDROOT 0x800B0109
270 description
= AlertDescription
.UnknownCA
;
271 errors
.Add ((int)-2146762487);
276 description
= AlertDescription
.CertificateUnknown
;
277 errors
.Add ((int)verify
.Status
);
282 int[] certificateErrors
= (int[])errors
.ToArray(typeof(int));
284 if (!context
.SslStream
.RaiseServerCertificateValidation(
288 throw new TlsException(
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"];
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
)
323 foreach (string ip
in subjectAltName
.IPAddresses
)
325 // 2.1. Exact match required
326 if (ip
== targetHost
)
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 ?
359 * a. there could also be many address returned
360 * b. Address property is obsoleted in .NET 1.1
362 if (domainName == String.Empty)
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);