2 // SslSecurityTokenProvider.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2006-2007 Novell, Inc. http://www.novell.com
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
29 using System
.Collections
.Generic
;
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
;
44 using ReqType
= System
.ServiceModel
.Security
.Tokens
.ServiceModelSecurityTokenRequirement
;
46 namespace System
.ServiceModel
.Security
.Tokens
48 class SslSecurityTokenProvider
: CommunicationSecurityTokenProvider
50 SslCommunicationObject comm
;
51 ClientCredentialsSecurityTokenManager manager
;
53 public SslSecurityTokenProvider (ClientCredentialsSecurityTokenManager manager
, bool mutual
)
55 this.manager
= manager
;
56 comm
= new SslCommunicationObject (this, mutual
);
59 public override ProviderCommunicationObject Communication
{
63 public ClientCredentialsSecurityTokenManager Manager
{
64 get { return manager; }
67 public override SecurityToken
GetOnlineToken (TimeSpan timeout
)
69 return comm
.GetToken (timeout
);
73 class SslCommunicationObject
: ProviderCommunicationObject
75 SslSecurityTokenProvider owner
;
76 WSTrustSecurityTokenServiceProxy proxy
;
77 X509Certificate2 client_certificate
;
80 public SslCommunicationObject (SslSecurityTokenProvider owner
, bool 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.");
90 class TlsnegoClientSessionContext
92 XmlDocument doc
= new XmlDocument ();
93 XmlDsigExcC14NTransform t
= new XmlDsigExcC14NTransform ();
94 MemoryStream stream
= new MemoryStream ();
96 public void StoreMessage (XmlReader reader
)
99 doc
.AppendChild (doc
.ReadNode (reader
));
101 MemoryStream s
= (MemoryStream
) t
.GetOutput ();
102 byte [] bytes
= s
.ToArray ();
103 stream
.Write (bytes
, 0, bytes
.Length
);
106 public byte [] GetC14NResults ()
108 return stream
.ToArray ();
112 public SecurityToken
GetToken (TimeSpan timeout
)
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
;
122 rst
.BinaryExchange
= new WstBinaryExchange (Constants
.WstBinaryExchangeValueTls
);
123 rst
.BinaryExchange
.Value
= tls
.ProcessClientHello ();
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 ());
133 // FIXME: use correct limitation
134 buffer
= response
.CreateBufferedCopy (0x10000);
135 tlsctx
.StoreMessage (buffer
.CreateMessage ().GetReaderAtBodyContents ());
137 // receive ServerHello
138 WSTrustRequestSecurityTokenResponseReader reader
=
139 new WSTrustRequestSecurityTokenResponseReader (Constants
.WstTlsnegoProofTokenType
, buffer
.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer
, null);
141 if (reader
.Value
.RequestedSecurityToken
!= null)
142 return reader
.Value
.RequestedSecurityToken
;
144 tls
.ProcessServerHello (reader
.Value
.BinaryExchange
.Value
);
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 ();
153 request
= Message
.CreateMessage (IssuerBinding
.MessageVersion
, Constants
.WstIssueReplyAction
, rstr
);
154 request
.Headers
.ReplyTo
= new EndpointAddress (Constants
.WsaAnonymousUri
);
155 request
.Headers
.To
= TargetAddress
.Uri
;
157 buffer
= request
.CreateBufferedCopy (0x10000);
158 tlsctx
.StoreMessage (buffer
.CreateMessage ().GetReaderAtBodyContents ());
159 //Console.WriteLine (System.Text.Encoding.UTF8.GetString (tlsctx.GetC14NResults ()));
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);
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
));
174 WstRequestSecurityTokenResponse r
= coll
.Responses
[0];
175 tls
.ProcessServerFinished (r
.BinaryExchange
.Value
);
176 SecurityContextSecurityToken sctSrc
=
177 r
.RequestedSecurityToken
;
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
);
187 tlsctx
.StoreMessage (XmlDictionaryReader
.CreateDictionaryReader (new XmlNodeReader (doc
)));
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
);
201 // Authenticate token.
203 byte [] actual
= coll
.Responses
[1].Authenticator
;
205 throw new SecurityNegotiationException ("Token authenticator is expected in the RequestSecurityTokenResponse but not found.");
207 if (coll
.Responses
[0].Context
!= contextId
)
208 throw new SecurityNegotiationException ("The context Id does not match with that of the corresponding token authenticator.");
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
;
224 for (int i
= 0; i
< referent
.Length
; i
++)
225 if (referent
[i
] != actual
[i
])
227 // FIXME: enable verification
229 // throw new SecurityNegotiationException ("The CombinedHash does not match the expected value.");
234 protected internal override TimeSpan DefaultCloseTimeout
{
235 get { throw new NotImplementedException (); }
238 protected internal override TimeSpan DefaultOpenTimeout
{
239 get { throw new NotImplementedException (); }
242 protected override void OnAbort ()
244 throw new NotImplementedException ();
247 protected override void OnOpen (TimeSpan timeout
)
249 if (State
== CommunicationState
.Opened
)
250 throw new InvalidOperationException ("Already opened.");
254 proxy
= new WSTrustSecurityTokenServiceProxy (
255 IssuerBinding
, IssuerAddress
);
258 protected override IAsyncResult
OnBeginOpen (TimeSpan timeout
, AsyncCallback callback
, object state
)
260 throw new NotImplementedException ();
263 protected override void OnEndOpen (IAsyncResult result
)
265 throw new NotImplementedException ();
268 protected override void OnClose (TimeSpan timeout
)
274 protected override IAsyncResult
OnBeginClose (TimeSpan timeout
, AsyncCallback callback
, object state
)
276 throw new NotImplementedException ();
279 protected override void OnEndClose (IAsyncResult result
)
281 throw new NotImplementedException ();