2008-11-01 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Security.Tokens / SslSecurityTokenProvider.cs
1 //
2 // SslSecurityTokenProvider.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2006-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.Net.Security;
32 using System.IdentityModel.Selectors;
33 using System.IdentityModel.Tokens;
34 using System.Security.Cryptography;
35 using System.Security.Cryptography.X509Certificates;
36 using System.Security.Cryptography.Xml;
37 using System.ServiceModel;
38 using System.ServiceModel.Channels;
39 using System.ServiceModel.Description;
40 using System.ServiceModel.Security;
41 using System.ServiceModel.Security.Tokens;
42 using System.Xml;
43
44 using ReqType = System.ServiceModel.Security.Tokens.ServiceModelSecurityTokenRequirement;
45
46 namespace System.ServiceModel.Security.Tokens
47 {
48         class SslSecurityTokenProvider : CommunicationSecurityTokenProvider
49         {
50                 SslCommunicationObject comm;
51                 ClientCredentialsSecurityTokenManager manager;
52
53                 public SslSecurityTokenProvider (ClientCredentialsSecurityTokenManager manager, bool mutual)
54                 {
55                         this.manager = manager;
56                         comm = new SslCommunicationObject (this, mutual);
57                 }
58
59                 public override ProviderCommunicationObject Communication {
60                         get { return comm; }
61                 }
62
63                 public ClientCredentialsSecurityTokenManager Manager {
64                         get { return manager; }
65                 }
66
67                 public override SecurityToken GetOnlineToken (TimeSpan timeout)
68                 {
69                         return comm.GetToken (timeout);
70                 }
71         }
72
73         class SslCommunicationObject : ProviderCommunicationObject
74         {
75                 SslSecurityTokenProvider owner;
76                 WSTrustSecurityTokenServiceProxy proxy;
77                 X509Certificate2 client_certificate;
78                 
79
80                 public SslCommunicationObject (SslSecurityTokenProvider owner, bool mutual)
81                 {
82                         if (mutual) {
83                                 client_certificate = owner.Manager.ClientCredentials.ClientCertificate.Certificate;
84                                 if (client_certificate == null)
85                                         throw new InvalidOperationException ("ClientCertificate is required for mutual SSL negotiation.");
86                         }
87                         this.owner = owner;
88                 }
89
90                 class TlsnegoClientSessionContext
91                 {
92                         XmlDocument doc = new XmlDocument ();
93                         XmlDsigExcC14NTransform t = new XmlDsigExcC14NTransform ();
94                         MemoryStream stream = new MemoryStream ();
95
96                         public void StoreMessage (XmlReader reader)
97                         {
98                                 doc.RemoveAll ();
99                                 doc.AppendChild (doc.ReadNode (reader));
100                                 t.LoadInput (doc);
101                                 MemoryStream s = (MemoryStream) t.GetOutput ();
102                                 byte [] bytes = s.ToArray ();
103                                 stream.Write (bytes, 0, bytes.Length);
104                         }
105
106                         public byte [] GetC14NResults ()
107                         {
108                                 return stream.ToArray ();
109                         }
110                 }
111
112                 public SecurityToken GetToken (TimeSpan timeout)
113                 {
114                         TlsnegoClientSessionContext tlsctx =
115                                 new TlsnegoClientSessionContext ();
116                         TlsClientSession tls = new TlsClientSession (IssuerAddress.Uri.ToString (), client_certificate);
117                         WstRequestSecurityToken rst =
118                                 new WstRequestSecurityToken ();
119                         string contextId = rst.Context;
120
121                         // send ClientHello
122                         rst.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls);
123                         rst.BinaryExchange.Value = tls.ProcessClientHello ();
124
125                         Message request = Message.CreateMessage (IssuerBinding.MessageVersion, Constants.WstIssueAction, rst);
126                         request.Headers.MessageId = new UniqueId ();
127                         request.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
128                         request.Headers.To = TargetAddress.Uri;
129                         MessageBuffer buffer = request.CreateBufferedCopy (0x10000);
130                         tlsctx.StoreMessage (buffer.CreateMessage ().GetReaderAtBodyContents ());
131                         Message response = proxy.Issue (buffer.CreateMessage ());
132
133                         // FIXME: use correct limitation
134                         buffer = response.CreateBufferedCopy (0x10000);
135                         tlsctx.StoreMessage (buffer.CreateMessage ().GetReaderAtBodyContents ());
136
137                         // receive ServerHello
138                         WSTrustRequestSecurityTokenResponseReader reader =
139                                 new WSTrustRequestSecurityTokenResponseReader (Constants.WstTlsnegoProofTokenType, buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer, null);
140                         reader.Read ();
141                         if (reader.Value.RequestedSecurityToken != null)
142                                 return reader.Value.RequestedSecurityToken;
143
144                         tls.ProcessServerHello (reader.Value.BinaryExchange.Value);
145
146                         // send ClientKeyExchange
147                         WstRequestSecurityTokenResponse rstr =
148                                 new WstRequestSecurityTokenResponse (SecurityTokenSerializer);
149                         rstr.Context = reader.Value.Context;
150                         rstr.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls);
151                         rstr.BinaryExchange.Value = tls.ProcessClientKeyExchange ();
152
153                         request = Message.CreateMessage (IssuerBinding.MessageVersion, Constants.WstIssueReplyAction, rstr);
154                         request.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
155                         request.Headers.To = TargetAddress.Uri;
156
157                         buffer = request.CreateBufferedCopy (0x10000);
158                         tlsctx.StoreMessage (buffer.CreateMessage ().GetReaderAtBodyContents ());
159 //Console.WriteLine (System.Text.Encoding.UTF8.GetString (tlsctx.GetC14NResults ()));
160
161                         // FIXME: regeneration of this instance is somehow required, but should not be.
162                         proxy = new WSTrustSecurityTokenServiceProxy (
163                                 IssuerBinding, IssuerAddress);
164                         response = proxy.IssueReply (buffer.CreateMessage ());
165                         // FIXME: use correct limitation
166                         buffer = response.CreateBufferedCopy (0x10000);
167
168                         WstRequestSecurityTokenResponseCollection coll =
169                                 new WstRequestSecurityTokenResponseCollection ();
170                         coll.Read (Constants.WstTlsnegoProofTokenType, buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer, null);
171                         if (coll.Responses.Count != 2)
172                                 throw new SecurityNegotiationException (String.Format ("Expected response is RequestSecurityTokenResponseCollection which contains two RequestSecurityTokenResponse items, but it actually contains {0} items", coll.Responses.Count));
173
174                         WstRequestSecurityTokenResponse r = coll.Responses [0];
175                         tls.ProcessServerFinished (r.BinaryExchange.Value);
176                         SecurityContextSecurityToken sctSrc =
177                                 r.RequestedSecurityToken;
178
179 #if false // FIXME: should this final RSTR included in RSTRC considered too?
180                         XmlDocument doc = new XmlDocument ();
181                         doc.PreserveWhitespace = true;
182                         using (XmlDictionaryWriter dw = XmlDictionaryWriter.CreateDictionaryWriter (doc.CreateNavigator ().AppendChild ())) {
183                                 if (r == null) throw new Exception ("r");
184                                 if (dw == null) throw new Exception ("dw");
185                                 r.WriteBodyContents (dw);
186                         }
187                         tlsctx.StoreMessage (XmlDictionaryReader.CreateDictionaryReader (new XmlNodeReader (doc)));
188 #endif
189
190                         // the RequestedProofToken is represented as 32 bytes
191                         // of TLS ApplicationData.
192                         // - According to WSE2 doc, it is *the* key, but not
193                         //   sure it also applies to WCF.
194                         // - WSS4J also seems to store the encryped shared key.
195                         // - (Important) It seems that without tls decryption,
196                         //   .NET fails to recover the key.
197                         byte [] proof = tls.ProcessApplicationData (
198                                 (byte []) r.RequestedProofToken);
199                         byte [] key = proof;
200
201                         // Authenticate token.
202
203                         byte [] actual = coll.Responses [1].Authenticator;
204                         if (actual == null)
205                                 throw new SecurityNegotiationException ("Token authenticator is expected in the RequestSecurityTokenResponse but not found.");
206
207                         if (coll.Responses [0].Context != contextId)
208                                 throw new SecurityNegotiationException ("The context Id does not match with that of the corresponding token authenticator.");
209
210                         // H = sha1(exc14n(RST..RSTRs))
211                         byte [] hash = SHA1.Create ().ComputeHash (tlsctx.GetC14NResults ());
212                         byte [] referent = tls.CreateHash (key, hash, "AUTH-HASH");
213 Console.WriteLine (System.Text.Encoding.ASCII.GetString (tlsctx.GetC14NResults ()));
214 Console.Write ("Hash: ");
215 foreach (byte b in hash) Console.Write ("{0:X02} ", b); Console.WriteLine ();
216 Console.Write ("Referent: ");
217 foreach (byte b in referent) Console.Write ("{0:X02} ", b); Console.WriteLine ();
218 Console.Write ("Actual: ");
219 foreach (byte b in actual) Console.Write ("{0:X02} ", b); Console.WriteLine ();
220 Console.Write ("Proof: ");
221 foreach (byte b in proof) Console.Write ("{0:X02} ", b); Console.WriteLine ();
222                         bool mismatch = referent.Length != actual.Length;
223                         if (!mismatch)
224                                 for (int i = 0; i < referent.Length; i++)
225                                         if (referent [i] != actual [i])
226                                                 mismatch = true;
227                         // FIXME: enable verification
228 //                      if (mismatch)
229 //                              throw new SecurityNegotiationException ("The CombinedHash does not match the expected value.");
230
231                         return sctSrc;
232                 }
233
234                 protected internal override TimeSpan DefaultCloseTimeout {
235                         get { throw new NotImplementedException (); }
236                 }
237
238                 protected internal override TimeSpan DefaultOpenTimeout {
239                         get { throw new NotImplementedException (); }
240                 }
241
242                 protected override void OnAbort ()
243                 {
244                         throw new NotImplementedException ();
245                 }
246
247                 protected override void OnOpen (TimeSpan timeout)
248                 {
249                         if (State == CommunicationState.Opened)
250                                 throw new InvalidOperationException ("Already opened.");
251
252                         EnsureProperties ();
253
254                         proxy = new WSTrustSecurityTokenServiceProxy (
255                                 IssuerBinding, IssuerAddress);
256                 }
257
258                 protected override IAsyncResult OnBeginOpen (TimeSpan timeout, AsyncCallback callback, object state)
259                 {
260                         throw new NotImplementedException ();
261                 }
262
263                 protected override void OnEndOpen (IAsyncResult result)
264                 {
265                         throw new NotImplementedException ();
266                 }
267
268                 protected override void OnClose (TimeSpan timeout)
269                 {
270                         if (proxy != null)
271                                 proxy.Close ();
272                 }
273
274                 protected override IAsyncResult OnBeginClose (TimeSpan timeout, AsyncCallback callback, object state)
275                 {
276                         throw new NotImplementedException ();
277                 }
278
279                 protected override void OnEndClose (IAsyncResult result)
280                 {
281                         throw new NotImplementedException ();
282                 }
283         }
284 }