Merge pull request #2200 from xmcclure/image-audit-oops
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls.Handshake.Server / TlsClientCertificate.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 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.Collections;
27 using SSCX = System.Security.Cryptography.X509Certificates;
28 using Mono.Security.X509;
29 using Mono.Security.X509.Extensions;
30
31 namespace Mono.Security.Protocol.Tls.Handshake.Server
32 {
33         internal class TlsClientCertificate : HandshakeMessage
34         {
35                 #region Fields
36
37                 private X509CertificateCollection clientCertificates;
38
39                 #endregion
40
41                 #region Constructors
42
43                 public TlsClientCertificate(Context context, byte[] buffer)
44                         : base(context, HandshakeType.Certificate, buffer)
45                 {
46                 }
47
48                 #endregion
49
50                 #region Methods
51
52                 public override void Update()
53                 {
54                         foreach (X509Certificate certificate in clientCertificates) {
55                                 this.Context.ClientSettings.Certificates.Add (new SSCX.X509Certificate (certificate.RawData));
56                         }
57                 }
58
59                 public bool HasCertificate {
60                         get { return clientCertificates.Count > 0; }
61                 }
62
63                 #endregion
64
65                 #region Protected Methods
66
67                 protected override void ProcessAsSsl3()
68                 {
69                         this.ProcessAsTls1();
70                 }
71
72                 protected override void ProcessAsTls1()
73                 {
74                         int bytesRead = 0;
75                         int length = this.ReadInt24 ();
76                         this.clientCertificates = new X509CertificateCollection ();
77                         while (length > bytesRead) {
78                                 int certLength = this.ReadInt24 ();
79                                 bytesRead += certLength + 3;
80                                 byte[] cert = this.ReadBytes (certLength);
81                                 this.clientCertificates.Add (new X509Certificate (cert));
82                         }
83
84                         if (this.clientCertificates.Count > 0) 
85                         {
86                                 this.validateCertificates (this.clientCertificates);
87                         } 
88                         else if ((this.Context as ServerContext).ClientCertificateRequired) 
89                         {
90                                 throw new TlsException (AlertDescription.NoCertificate);
91                         }
92                 }
93
94                 #endregion
95
96                 #region Private Methods
97
98                 private bool checkCertificateUsage (X509Certificate cert)
99                 {
100                         ServerContext context = (ServerContext)this.Context;
101
102                         // certificate extensions are required for this
103                         // we "must" accept older certificates without proofs
104                         if (cert.Version < 3)
105                                 return true;
106
107                         KeyUsages ku = KeyUsages.none;
108                         switch (context.Negotiating.Cipher.ExchangeAlgorithmType)
109                         {
110                                 case ExchangeAlgorithmType.RsaSign:
111                                 case ExchangeAlgorithmType.RsaKeyX:
112                                         ku = KeyUsages.digitalSignature;
113                                         break;
114                                 case ExchangeAlgorithmType.DiffieHellman:
115                                         ku = KeyUsages.keyAgreement;
116                                         break;
117                                 case ExchangeAlgorithmType.Fortezza:
118                                         return false; // unsupported certificate type
119                         }
120
121                         KeyUsageExtension kux = null;
122                         ExtendedKeyUsageExtension eku = null;
123
124                         X509Extension xtn = cert.Extensions["2.5.29.15"];
125                         if (xtn != null)
126                                 kux = new KeyUsageExtension (xtn);
127
128                         xtn = cert.Extensions["2.5.29.37"];
129                         if (xtn != null)
130                                 eku = new ExtendedKeyUsageExtension (xtn);
131
132                         if ((kux != null) && (eku != null))
133                         {
134                                 // RFC3280 states that when both KeyUsageExtension and 
135                                 // ExtendedKeyUsageExtension are present then BOTH should
136                                 // be valid
137                                 return (kux.Support (ku) &&
138                                         eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.2"));
139                         }
140                         else if (kux != null)
141                         {
142                                 return kux.Support (ku);
143                         }
144                         else if (eku != null)
145                         {
146                                 // Client Authentication (1.3.6.1.5.5.7.3.2)
147                                 return eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.2");
148                         }
149
150                         // last chance - try with older (deprecated) Netscape extensions
151                         xtn = cert.Extensions["2.16.840.1.113730.1.1"];
152                         if (xtn != null)
153                         {
154                                 NetscapeCertTypeExtension ct = new NetscapeCertTypeExtension (xtn);
155                                 return ct.Support (NetscapeCertTypeExtension.CertTypes.SslClient);
156                         }
157
158                         // certificate isn't valid for SSL server usage
159                         return false;
160                 }
161
162                 private void validateCertificates (X509CertificateCollection certificates)
163                 {
164                         ServerContext context = (ServerContext)this.Context;
165                         AlertDescription description = AlertDescription.BadCertificate;
166                         SSCX.X509Certificate client = null;
167                         int[] certificateErrors = null;
168
169                         // note: certificate may be null is no certificate is sent
170                         // (e.g. optional mutual authentication)
171                         if (certificates.Count > 0) {
172                                 X509Certificate leaf = certificates[0];
173                         
174                                 ArrayList errors = new ArrayList ();
175
176                                 // SSL specific check - not all certificates can be 
177                                 // used to server-side SSL some rules applies after 
178                                 // all ;-)
179                                 if (!checkCertificateUsage (leaf))
180                                 {
181                                         // WinError.h CERT_E_PURPOSE 0x800B0106
182                                         errors.Add ((int)-2146762490);
183                                 }
184
185                                 X509Chain verify;
186                                 // was a chain supplied ? if so use it, if not
187                                 if (certificates.Count > 1) {
188                                         // if so use it (and don't build our own)
189                                         X509CertificateCollection chain = new X509CertificateCollection (certificates);
190                                         chain.Remove (leaf);
191                                         verify = new X509Chain (chain);
192                                 } else {
193                                         // if not, then let's build our own (based on what's available in the stores)
194                                         verify = new X509Chain ();
195                                 }
196
197                                 bool result = false;
198
199                                 try
200                                 {
201                                         result = verify.Build (leaf);
202                                 }
203                                 catch (Exception)
204                                 {
205                                         result = false;
206                                 }
207
208                                 if (!result)
209                                 {
210                                         switch (verify.Status)
211                                         {
212                                                 case X509ChainStatusFlags.InvalidBasicConstraints:
213                                                         // WinError.h TRUST_E_BASIC_CONSTRAINTS 0x80096019
214                                                         errors.Add ((int)-2146869223);
215                                                         break;
216
217                                                 case X509ChainStatusFlags.NotSignatureValid:
218                                                         // WinError.h TRUST_E_BAD_DIGEST 0x80096010
219                                                         errors.Add ((int)-2146869232);
220                                                         break;
221
222                                                 case X509ChainStatusFlags.NotTimeNested:
223                                                         // WinError.h CERT_E_VALIDITYPERIODNESTING 0x800B0102
224                                                         errors.Add ((int)-2146762494);
225                                                         break;
226
227                                                 case X509ChainStatusFlags.NotTimeValid:
228                                                         // WinError.h CERT_E_EXPIRED 0x800B0101
229                                                         description = AlertDescription.CertificateExpired;
230                                                         errors.Add ((int)-2146762495);
231                                                         break;
232
233                                                 case X509ChainStatusFlags.PartialChain:
234                                                         // WinError.h CERT_E_CHAINING 0x800B010A
235                                                         description = AlertDescription.UnknownCA;
236                                                         errors.Add ((int)-2146762486);
237                                                         break;
238
239                                                 case X509ChainStatusFlags.UntrustedRoot:
240                                                         // WinError.h CERT_E_UNTRUSTEDROOT 0x800B0109
241                                                         description = AlertDescription.UnknownCA;
242                                                         errors.Add ((int)-2146762487);
243                                                         break;
244
245                                                 default:
246                                                         // unknown error
247                                                         description = AlertDescription.CertificateUnknown;
248                                                         errors.Add ((int)verify.Status);
249                                                         break;
250                                         }
251                                 }
252                                 client = new SSCX.X509Certificate (leaf.RawData);
253                                 certificateErrors = (int[])errors.ToArray (typeof (int));
254                         }
255                         else
256                         {
257                                 certificateErrors = new int[0];
258                         }
259
260                         SSCX.X509CertificateCollection certCollection = new SSCX.X509CertificateCollection ();
261                         foreach (X509Certificate certificate in certificates) {
262                                 certCollection.Add (new SSCX.X509Certificate (certificate.RawData));
263                         }
264                         if (!context.SslStream.RaiseClientCertificateValidation(client, certificateErrors))
265                         {
266                                 throw new TlsException (
267                                         description,
268                                         "Invalid certificate received from client.");
269                         }
270
271                         this.Context.ClientSettings.ClientCertificate = client;
272                 }
273
274                 #endregion
275         }
276 }