[eglib] make g_mkdir_with_parents work for paths not ending in /
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Security.Tokens / TlsClientSession.cs
1 //
2 // TlsClientSession.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2007 Novell, Inc.  http://www.novell.com
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.IdentityModel.Selectors;
32 using System.Security.Cryptography;
33 using System.Security.Cryptography.X509Certificates;
34 using System.Text;
35 using Mono.Security.Protocol.Tls;
36 using Mono.Security.Protocol.Tls.Handshake;
37 using Mono.Security.Protocol.Tls.Handshake.Client;
38
39 namespace System.ServiceModel.Security.Tokens
40 {
41         internal abstract class TlsSession
42         {
43                 protected abstract Context Context { get; }
44
45                 protected abstract RecordProtocol Protocol { get; }
46
47                 public byte [] MasterSecret {
48                         get { return Context.MasterSecret; }
49                 }
50
51                 public byte [] CreateHash (byte [] key, byte [] seedSrc, string label)
52                 {
53                         byte [] labelBytes = Encoding.UTF8.GetBytes (label);
54                         byte [] seed = new byte [seedSrc.Length + labelBytes.Length];
55                         Array.Copy (seedSrc, seed, seedSrc.Length);
56                         Array.Copy (labelBytes, 0, seed, seedSrc.Length, labelBytes.Length);
57                         return Context.Current.Cipher.Expand ("SHA1", key, seed, 256 / 8);
58                 }
59
60                 public byte [] CreateHashAlt (byte [] key, byte [] seed, string label)
61                 {
62                         return Context.Current.Cipher.PRF (key, label, seed, 256 / 8);
63                 }
64
65                 protected void WriteHandshake (MemoryStream ms)
66                 {
67                         Context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers (SecurityProtocolType.Tls);
68                         ms.WriteByte (0x16); // Handshake
69                         ms.WriteByte (3); // version-major
70                         ms.WriteByte (1); // version-minor
71                 }
72
73                 protected void WriteChangeCipherSpec (MemoryStream ms)
74                 {
75                         ms.WriteByte (0x14); // Handshake
76                         ms.WriteByte (3); // version-major
77                         ms.WriteByte (1); // version-minor
78                         ms.WriteByte (0); // size-upper
79                         ms.WriteByte (1); // size-lower
80                         ms.WriteByte (1); // ChangeCipherSpec content (1 byte)
81                 }
82
83                 protected void ReadHandshake (MemoryStream ms)
84                 {
85                         if (ms.ReadByte () != 0x16)
86                                 throw new Exception ("INTERNAL ERROR: handshake is expected");
87                         Context.ChangeProtocol ((short) (ms.ReadByte () * 0x100 + ms.ReadByte ()));
88                 }
89
90                 protected void ReadChangeCipherSpec (MemoryStream ms)
91                 {
92                         if (ms.ReadByte () != 0x14)
93                                 throw new Exception ("INTERNAL ERROR: ChangeCipherSpec is expected");
94                         Context.ChangeProtocol ((short) (ms.ReadByte () * 0x100 + ms.ReadByte ()));
95                         if (ms.ReadByte () * 0x100 + ms.ReadByte () != 1)
96                                 throw new Exception ("INTERNAL ERROR: unexpected ChangeCipherSpec length");
97                         ms.ReadByte (); // ChangeCipherSpec content (1 byte) ... anything is OK?
98                 }
99
100                 protected byte [] ReadNextOperation (MemoryStream ms, HandshakeType expected)
101                 {
102                         if (ms.ReadByte () != (int) expected)
103                                 throw new Exception ("INTERNAL ERROR: unexpected server response");
104                         int size = ms.ReadByte () * 0x10000 + ms.ReadByte () * 0x100 + ms.ReadByte ();
105                         // FIXME: use correct valid input range
106                         if (size > 0x100000)
107                                 throw new Exception ("rejected massive input size.");
108                         byte [] bytes = new byte [size];
109                         ms.Read (bytes, 0, size);
110                         return bytes;
111                 }
112
113                 protected void WriteOperations (MemoryStream ms, params HandshakeMessage [] msgs)
114                 {
115                         List<byte []> rawbufs = new List<byte []> ();
116                         int total = 0;
117                         for (int i = 0; i < msgs.Length; i++) {
118                                 HandshakeMessage msg = msgs [i];
119                                 msg.Process ();
120                                 rawbufs.Add (msg.EncodeMessage ());
121                                 total += rawbufs [i].Length;
122                                 msg.Update ();
123                         }
124                         // FIXME: split packets when the size exceeded 0x10000 (or so)
125                         ms.WriteByte ((byte) (total / 0x100));
126                         ms.WriteByte ((byte) (total % 0x100));
127                         foreach (byte [] bytes in rawbufs)
128                                 ms.Write (bytes, 0, bytes.Length);
129                 }
130
131                 protected void VerifyEndOfTransmit (MemoryStream ms)
132                 {
133                         if (ms.Position == ms.Length)
134                                 return;
135
136                         /*
137                         byte [] bytes = new byte [ms.Length - ms.Position];
138                         ms.Read (bytes, 0, bytes.Length);
139                         foreach (byte b in bytes)
140                                 Console.Write ("{0:X02} ", b);
141                         Console.WriteLine (" - total {0} bytes remained.", bytes.Length);
142                         */
143
144                         throw new Exception ("INTERNAL ERROR: unexpected server response");
145                 }
146         }
147
148         internal class TlsClientSession : TlsSession
149         {
150                 SslClientStream ssl;
151                 MemoryStream stream;
152                 bool mutual;
153
154                 public TlsClientSession (string host, X509Certificate2 clientCert, X509ServiceCertificateAuthentication auth)
155                 {
156                         stream = new MemoryStream ();
157                         if (clientCert == null)
158                                 ssl = new SslClientStream (stream, host, true, SecurityProtocolType.Tls);
159                         else {
160                                 ssl = new SslClientStream (stream, host, true, SecurityProtocolType.Tls, new X509CertificateCollection (new X509Certificate [] {clientCert}));
161                                 mutual = true;
162                                 ssl.ClientCertSelection += delegate (
163                                         X509CertificateCollection clientCertificates,
164                                 X509Certificate serverCertificate,
165                                 string targetHost,
166                                 X509CertificateCollection serverRequestedCertificates) {
167                                         return clientCertificates [0];
168                                 };
169                         }
170                         X509CertificateValidator v = null;
171                         switch (auth.CertificateValidationMode) {
172                         case X509CertificateValidationMode.None:
173                                 v = X509CertificateValidator.None;
174                                 break;
175                         case X509CertificateValidationMode.PeerTrust:
176                                 v = X509CertificateValidator.PeerTrust;
177                                 break;
178                         case X509CertificateValidationMode.ChainTrust:
179                                 v = X509CertificateValidator.ChainTrust;
180                                 break;
181                         case X509CertificateValidationMode.PeerOrChainTrust:
182                                 v = X509CertificateValidator.PeerOrChainTrust;
183                                 break;
184                         case X509CertificateValidationMode.Custom:
185                                 v = auth.CustomCertificateValidator;
186                                 break;
187                         }
188                         ssl.ServerCertValidationDelegate = delegate (X509Certificate certificate, int [] certificateErrors) {
189                                 v.Validate (new X509Certificate2 (certificate)); // will throw SecurityTokenvalidationException if invalid.
190                                 return true;
191                                 };
192                 }
193
194                 protected override Context Context {
195                         get { return ssl.context; }
196                 }
197
198                 protected override RecordProtocol Protocol {
199                         get { return ssl.protocol; }
200                 }
201
202                 public byte [] ProcessClientHello ()
203                 {
204                         Context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers (Context.SecurityProtocol);
205                         Context.HandshakeState = HandshakeState.Started;
206                         Protocol.SendRecord (HandshakeType.ClientHello);
207                         stream.Flush ();
208                         return stream.ToArray ();
209                 }
210
211                 // ServerHello, ServerCertificate and ServerHelloDone
212                 public void ProcessServerHello (byte [] raw)
213                 {
214                         stream.SetLength (0);
215                         stream.Write (raw, 0, raw.Length);
216                         stream.Seek (0, SeekOrigin.Begin);
217
218 //foreach (var b in raw) Console.Write ("{0:X02} ", b); Console.WriteLine ();
219
220 #if false // FIXME: should be enabled, taking some right shape.
221                         ReadNextOperation (stream, HandshakeType.ServerHello);
222                         ReadNextOperation (stream, HandshakeType.Certificate);
223                         if (mutual)
224                                 ReadNextOperation (stream, HandshakeType.CertificateRequest);
225                         ReadNextOperation (stream, HandshakeType.ServerHelloDone);
226 #else
227                         Protocol.ReceiveRecord (stream); // ServerHello
228                         Protocol.ReceiveRecord (stream); // ServerCertificate
229                         if (mutual)
230                                 Protocol.ReceiveRecord (stream); // CertificateRequest
231                         Protocol.ReceiveRecord (stream); // ServerHelloDone
232 #endif
233                         if (stream.Position != stream.Length)
234                                 throw new SecurityNegotiationException (String.Format ("Unexpected SSL negotiation binary: {0} bytes of excess in {1} bytes of the octets", stream.Length - stream.Position, stream.Length));
235                 }
236
237                 public byte [] ProcessClientKeyExchange ()
238                 {
239                         stream.SetLength (0);
240                         if (mutual)
241                                 Protocol.SendRecord (HandshakeType.Certificate);
242                         Protocol.SendRecord (HandshakeType.ClientKeyExchange);
243                         Context.Negotiating.Cipher.ComputeKeys ();
244                         Context.Negotiating.Cipher.InitializeCipher ();
245                         Protocol.SendChangeCipherSpec ();
246                         Context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers (SecurityProtocolType.Tls);
247                         Protocol.SendRecord (HandshakeType.Finished);
248                         stream.Flush ();
249                         return stream.ToArray ();
250                 }
251
252                 public void ProcessServerFinished (byte [] raw)
253                 {
254                         stream.SetLength (0);
255                         stream.Write (raw, 0, raw.Length);
256                         stream.Seek (0, SeekOrigin.Begin);
257
258                         Protocol.ReceiveRecord (stream); // ChangeCipherSpec
259                         Protocol.ReceiveRecord (stream); // ServerFinished
260                 }
261
262                 public byte [] ProcessApplicationData (byte [] raw)
263                 {
264                         stream.SetLength (0);
265                         stream.Write (raw, 0, raw.Length);
266                         stream.Seek (0, SeekOrigin.Begin);
267                         return Protocol.ReceiveRecord (stream); // ApplicationData
268                 }
269         }
270 }