Merge pull request #1718 from madewokherd/sgenthreadcleanup
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Security.Tokens / SslSecurityTokenAuthenticator.cs
1 //
2 // SslSecurityTokenAuthenticator.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.Collections.ObjectModel;
31 using System.IO;
32 using System.Net.Security;
33 using System.IdentityModel.Policy;
34 using System.IdentityModel.Selectors;
35 using System.IdentityModel.Tokens;
36 using System.Security.Cryptography;
37 using System.Security.Cryptography.X509Certificates;
38 using System.Security.Cryptography.Xml;
39 using System.ServiceModel.Channels;
40 using System.ServiceModel.Description;
41 using System.ServiceModel.Security;
42 using System.ServiceModel.Security.Tokens;
43 using System.Xml;
44
45 using ReqType = System.ServiceModel.Security.Tokens.ServiceModelSecurityTokenRequirement;
46
47 namespace System.ServiceModel.Security.Tokens
48 {
49         // FIXME: implement all
50         class SslSecurityTokenAuthenticator : CommunicationSecurityTokenAuthenticator
51         {
52                 ServiceCredentialsSecurityTokenManager manager;
53                 SslAuthenticatorCommunicationObject comm;
54                 bool mutual;
55
56                 public SslSecurityTokenAuthenticator (
57                         ServiceCredentialsSecurityTokenManager manager, 
58                         SecurityTokenRequirement r)
59                 {
60                         this.manager = manager;
61                         mutual = (r.TokenType == ServiceModelSecurityTokenTypes.MutualSslnego);
62                         comm = new SslAuthenticatorCommunicationObject (this);
63                 }
64
65                 public bool IsMutual {
66                         get { return mutual; }
67                 }
68
69                 public ServiceCredentialsSecurityTokenManager Manager {
70                         get { return manager; }
71                 }
72
73                 public override AuthenticatorCommunicationObject Communication {
74                         get { return comm; }
75                 }
76
77                 protected override bool CanValidateTokenCore (SecurityToken token)
78                 {
79                         throw new NotImplementedException ();
80                 }
81
82                 protected override ReadOnlyCollection<IAuthorizationPolicy>
83                         ValidateTokenCore (SecurityToken token)
84                 {
85                         throw new NotImplementedException ();
86                 }
87         }
88
89         class SslAuthenticatorCommunicationObject : AuthenticatorCommunicationObject
90         {
91                 SslSecurityTokenAuthenticator owner;
92
93                 public SslAuthenticatorCommunicationObject (SslSecurityTokenAuthenticator owner)
94                 {
95                         this.owner = owner;
96                 }
97
98                 WSTrustSecurityTokenServiceProxy proxy;
99
100                 protected internal override TimeSpan DefaultCloseTimeout {
101                         get { throw new NotImplementedException (); }
102                 }
103
104                 protected internal override TimeSpan DefaultOpenTimeout {
105                         get { throw new NotImplementedException (); }
106                 }
107
108                 public override Message ProcessNegotiation (Message request, TimeSpan timeout)
109                 {
110                         if (request.Headers.Action == Constants.WstIssueAction)
111                                 return ProcessClientHello (request, timeout);
112                         else
113                                 return ProcessClientKeyExchange (request, timeout);
114                 }
115
116                 class TlsServerSessionInfo
117                 {
118                         public TlsServerSessionInfo (string context, TlsServerSession tls)
119                         {
120                                 ContextId = context;
121                                 Tls = tls;
122                         }
123
124                         public string ContextId;
125                         public TlsServerSession Tls;
126                         public MemoryStream Messages = new MemoryStream ();
127                 }
128
129                 Dictionary<string,TlsServerSessionInfo> sessions =
130                         new Dictionary<string,TlsServerSessionInfo> ();
131
132                 void AppendNegotiationMessageXml (XmlReader reader, TlsServerSessionInfo tlsInfo)
133                 {
134                         XmlDsigExcC14NTransform t = new XmlDsigExcC14NTransform ();
135                         XmlDocument doc = new XmlDocument ();
136                         doc.PreserveWhitespace = true;
137                         reader.MoveToContent ();
138                         doc.AppendChild (doc.ReadNode (reader));
139                         t.LoadInput (doc);
140                         MemoryStream stream = (MemoryStream) t.GetOutput ();
141                         byte [] bytes = stream.ToArray ();
142                         tlsInfo.Messages.Write (bytes, 0, bytes.Length);
143                 }
144
145                 // FIXME: use timeout
146                 Message ProcessClientHello (Message request, TimeSpan timeout)
147                 {
148                         // FIXME: use correct buffer size
149                         MessageBuffer buffer = request.CreateBufferedCopy (0x10000);
150                         WSTrustRequestSecurityTokenReader reader =
151                                 new WSTrustRequestSecurityTokenReader (buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer);
152                         reader.Read ();
153
154                         if (sessions.ContainsKey (reader.Value.Context))
155                                 throw new SecurityNegotiationException (String.Format ("The context '{0}' already exists in this SSL negotiation manager", reader.Value.Context));
156
157                         // FIXME: it seems .NET retrieves X509 Certificate through CreateSecurityTokenProvider(somex509requirement).GetToken().SecurityKeys[0]
158                         // (should result in X509AsymmetricSecurityKey) and continues tlsstart.
159                         // That's not very required feature so I ignore it.
160                         TlsServerSession tls = new TlsServerSession (owner.Manager.ServiceCredentials.ServiceCertificate.Certificate, owner.IsMutual);
161                         TlsServerSessionInfo tlsInfo = new TlsServerSessionInfo (
162                                 reader.Value.Context, tls);
163
164                         AppendNegotiationMessageXml (buffer.CreateMessage ().GetReaderAtBodyContents (), tlsInfo);
165
166                         tls.ProcessClientHello (reader.Value.BinaryExchange.Value);
167                         WstRequestSecurityTokenResponse rstr =
168                                 new WstRequestSecurityTokenResponse (SecurityTokenSerializer);
169                         rstr.Context = reader.Value.Context;
170                         rstr.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls);
171                         rstr.BinaryExchange.Value = tls.ProcessServerHello ();
172
173                         Message reply = Message.CreateMessage (request.Version, Constants.WstIssueReplyAction, rstr);
174                         reply.Headers.RelatesTo = request.Headers.MessageId;
175
176                         // FIXME: use correct buffer size
177                         buffer = reply.CreateBufferedCopy (0x10000);
178                         AppendNegotiationMessageXml (buffer.CreateMessage ().GetReaderAtBodyContents (), tlsInfo);
179
180                         sessions [reader.Value.Context] = tlsInfo;
181
182                         return buffer.CreateMessage ();
183                 }
184
185                 // FIXME: use timeout
186                 Message ProcessClientKeyExchange (Message request, TimeSpan timeout)
187                 {
188                         // FIXME: use correct buffer size
189                         MessageBuffer buffer = request.CreateBufferedCopy (0x10000);
190
191                         WSTrustRequestSecurityTokenResponseReader reader =
192                                 new WSTrustRequestSecurityTokenResponseReader (Constants.WstTlsnegoProofTokenType, buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer, null);
193                         reader.Read ();
194
195                         TlsServerSessionInfo tlsInfo;
196                         if (!sessions.TryGetValue (reader.Value.Context, out tlsInfo))
197                                 throw new SecurityNegotiationException (String.Format ("The context '{0}' does not exist in this SSL negotiation manager", reader.Value.Context));
198                         TlsServerSession tls = tlsInfo.Tls;
199
200                         AppendNegotiationMessageXml (buffer.CreateMessage ().GetReaderAtBodyContents (), tlsInfo);
201 //Console.WriteLine (System.Text.Encoding.UTF8.GetString (tlsInfo.Messages.ToArray ()));
202
203                         tls.ProcessClientKeyExchange (reader.Value.BinaryExchange.Value);
204
205                         byte [] serverFinished = tls.ProcessServerFinished ();
206
207                         // The shared key is computed as recommended in WS-Trust:
208                         // P_SHA1(encrypted_key,SHA1(exc14n(RST..RSTRs))+"CK-HASH")
209                         byte [] hash = SHA1.Create ().ComputeHash (tlsInfo.Messages.ToArray ());
210                         byte [] key = tls.CreateHash (tls.MasterSecret, hash, "CK-HASH");
211                         byte [] keyTlsApplied = tls.ProcessApplicationData (key);
212 foreach (byte b in hash) Console.Write ("{0:X02} ", b); Console.WriteLine ();
213 foreach (byte b in key) Console.Write ("{0:X02} ", b); Console.WriteLine ();
214
215                         WstRequestSecurityTokenResponseCollection col =
216                                 new WstRequestSecurityTokenResponseCollection ();
217                         WstRequestSecurityTokenResponse rstr =
218                                 new WstRequestSecurityTokenResponse (SecurityTokenSerializer);
219                         rstr.Context = reader.Value.Context;
220                         rstr.TokenType = Constants.WsscContextToken;
221                         DateTime from = DateTime.Now;
222                         // FIXME: not sure if arbitrary key is used here.
223                         SecurityContextSecurityToken sct = SecurityContextSecurityToken.CreateCookieSecurityContextToken (
224                                 // Create a new context.
225                                 // (do not use sslnego context here.)
226                                 new UniqueId (),
227                                 "uuid-" + Guid.NewGuid (),
228                                 key,
229                                 from,
230                                 // FIXME: use LocalServiceSecuritySettings.NegotiationTimeout
231                                 from.AddHours (8),
232                                 null,
233                                 owner.Manager.ServiceCredentials.SecureConversationAuthentication.SecurityStateEncoder);
234                         rstr.RequestedSecurityToken = sct;
235                         // without this ProcessApplicationData(), .NET seems
236                         // to fail recovering the key.
237                         rstr.RequestedProofToken = keyTlsApplied;
238                         rstr.RequestedAttachedReference = new LocalIdKeyIdentifierClause (sct.Id);
239                         rstr.RequestedUnattachedReference = new SecurityContextKeyIdentifierClause (sct.ContextId, null);
240                         WstLifetime lt = new WstLifetime ();
241                         lt.Created = from;
242                         lt.Expires = from.Add (SecurityBindingElement.LocalServiceSettings.IssuedCookieLifetime);
243                         rstr.Lifetime = lt;
244                         rstr.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls);
245                         rstr.BinaryExchange.Value = serverFinished;
246
247                         col.Responses.Add (rstr);
248
249                         // Authenticator is mandatory for MS sslnego.
250                         rstr = new WstRequestSecurityTokenResponse (SecurityTokenSerializer);
251                         rstr.Context = reader.Value.Context;
252                         rstr.Authenticator = tls.CreateHash (key, hash, "AUTH-HASH");
253                         col.Responses.Add (rstr);
254
255                         sessions.Remove (reader.Value.Context);
256
257                         // FIXME: get correct tokenRequestor address (probably identity authorized?)
258                         if (owner.IssuedSecurityTokenHandler != null)
259                                 owner.IssuedSecurityTokenHandler (sct, request.Headers.ReplyTo);
260
261                         return Message.CreateMessage (request.Version, Constants.WstIssueReplyAction, col);
262                 }
263
264                 protected override void OnAbort ()
265                 {
266                         throw new NotImplementedException ();
267                 }
268
269                 protected override void OnOpen (TimeSpan timeout)
270                 {
271                         if (State == CommunicationState.Opened)
272                                 throw new InvalidOperationException ("Already opened.");
273
274                         EnsureProperties ();
275
276                         proxy = new WSTrustSecurityTokenServiceProxy (
277                                 IssuerBinding, IssuerAddress);
278                 }
279
280                 protected override IAsyncResult OnBeginOpen (TimeSpan timeout, AsyncCallback callback, object state)
281                 {
282                         throw new NotImplementedException ();
283                 }
284
285                 protected override void OnEndOpen (IAsyncResult result)
286                 {
287                         throw new NotImplementedException ();
288                 }
289
290                 protected override void OnClose (TimeSpan timeout)
291                 {
292                         if (proxy != null)
293                                 proxy.Close ();
294                 }
295
296                 protected override IAsyncResult OnBeginClose (TimeSpan timeout, AsyncCallback callback, object state)
297                 {
298                         throw new NotImplementedException ();
299                 }
300
301                 protected override void OnEndClose (IAsyncResult result)
302                 {
303                         throw new NotImplementedException ();
304                 }
305         }
306 }