// // SslSecurityTokenAuthenticator.cs // // Author: // Atsushi Enomoto // // Copyright (C) 2007 Novell, Inc. http://www.novell.com // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Net.Security; using System.IdentityModel.Policy; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Xml; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Security; using System.ServiceModel.Security.Tokens; using System.Xml; using ReqType = System.ServiceModel.Security.Tokens.ServiceModelSecurityTokenRequirement; namespace System.ServiceModel.Security.Tokens { // FIXME: implement all class SslSecurityTokenAuthenticator : CommunicationSecurityTokenAuthenticator { ServiceCredentialsSecurityTokenManager manager; SslAuthenticatorCommunicationObject comm; bool mutual; public SslSecurityTokenAuthenticator ( ServiceCredentialsSecurityTokenManager manager, SecurityTokenRequirement r) { this.manager = manager; mutual = (r.TokenType == ServiceModelSecurityTokenTypes.MutualSslnego); comm = new SslAuthenticatorCommunicationObject (this); } public bool IsMutual { get { return mutual; } } public ServiceCredentialsSecurityTokenManager Manager { get { return manager; } } public override AuthenticatorCommunicationObject Communication { get { return comm; } } protected override bool CanValidateTokenCore (SecurityToken token) { throw new NotImplementedException (); } protected override ReadOnlyCollection ValidateTokenCore (SecurityToken token) { throw new NotImplementedException (); } } class SslAuthenticatorCommunicationObject : AuthenticatorCommunicationObject { SslSecurityTokenAuthenticator owner; public SslAuthenticatorCommunicationObject (SslSecurityTokenAuthenticator owner) { this.owner = owner; } WSTrustSecurityTokenServiceProxy proxy; protected internal override TimeSpan DefaultCloseTimeout { get { throw new NotImplementedException (); } } protected internal override TimeSpan DefaultOpenTimeout { get { throw new NotImplementedException (); } } public override Message ProcessNegotiation (Message request, TimeSpan timeout) { if (request.Headers.Action == Constants.WstIssueAction) return ProcessClientHello (request, timeout); else return ProcessClientKeyExchange (request, timeout); } class TlsServerSessionInfo { public TlsServerSessionInfo (string context, TlsServerSession tls) { ContextId = context; Tls = tls; } public string ContextId; public TlsServerSession Tls; public MemoryStream Messages = new MemoryStream (); } Dictionary sessions = new Dictionary (); void AppendNegotiationMessageXml (XmlReader reader, TlsServerSessionInfo tlsInfo) { XmlDsigExcC14NTransform t = new XmlDsigExcC14NTransform (); XmlDocument doc = new XmlDocument (); doc.PreserveWhitespace = true; reader.MoveToContent (); doc.AppendChild (doc.ReadNode (reader)); t.LoadInput (doc); MemoryStream stream = (MemoryStream) t.GetOutput (); byte [] bytes = stream.ToArray (); tlsInfo.Messages.Write (bytes, 0, bytes.Length); } // FIXME: use timeout Message ProcessClientHello (Message request, TimeSpan timeout) { // FIXME: use correct buffer size MessageBuffer buffer = request.CreateBufferedCopy (0x10000); WSTrustRequestSecurityTokenReader reader = new WSTrustRequestSecurityTokenReader (buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer); reader.Read (); if (sessions.ContainsKey (reader.Value.Context)) throw new SecurityNegotiationException (String.Format ("The context '{0}' already exists in this SSL negotiation manager", reader.Value.Context)); // FIXME: it seems .NET retrieves X509 Certificate through CreateSecurityTokenProvider(somex509requirement).GetToken().SecurityKeys[0] // (should result in X509AsymmetricSecurityKey) and continues tlsstart. // That's not very required feature so I ignore it. TlsServerSession tls = new TlsServerSession (owner.Manager.ServiceCredentials.ServiceCertificate.Certificate, owner.IsMutual); TlsServerSessionInfo tlsInfo = new TlsServerSessionInfo ( reader.Value.Context, tls); AppendNegotiationMessageXml (buffer.CreateMessage ().GetReaderAtBodyContents (), tlsInfo); tls.ProcessClientHello (reader.Value.BinaryExchange.Value); WstRequestSecurityTokenResponse rstr = new WstRequestSecurityTokenResponse (SecurityTokenSerializer); rstr.Context = reader.Value.Context; rstr.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls); rstr.BinaryExchange.Value = tls.ProcessServerHello (); Message reply = Message.CreateMessage (request.Version, Constants.WstIssueReplyAction, rstr); reply.Headers.RelatesTo = request.Headers.MessageId; // FIXME: use correct buffer size buffer = reply.CreateBufferedCopy (0x10000); AppendNegotiationMessageXml (buffer.CreateMessage ().GetReaderAtBodyContents (), tlsInfo); sessions [reader.Value.Context] = tlsInfo; return buffer.CreateMessage (); } // FIXME: use timeout Message ProcessClientKeyExchange (Message request, TimeSpan timeout) { // FIXME: use correct buffer size MessageBuffer buffer = request.CreateBufferedCopy (0x10000); WSTrustRequestSecurityTokenResponseReader reader = new WSTrustRequestSecurityTokenResponseReader (Constants.WstTlsnegoProofTokenType, buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer, null); reader.Read (); TlsServerSessionInfo tlsInfo; if (!sessions.TryGetValue (reader.Value.Context, out tlsInfo)) throw new SecurityNegotiationException (String.Format ("The context '{0}' does not exist in this SSL negotiation manager", reader.Value.Context)); TlsServerSession tls = tlsInfo.Tls; AppendNegotiationMessageXml (buffer.CreateMessage ().GetReaderAtBodyContents (), tlsInfo); //Console.WriteLine (System.Text.Encoding.UTF8.GetString (tlsInfo.Messages.ToArray ())); tls.ProcessClientKeyExchange (reader.Value.BinaryExchange.Value); byte [] serverFinished = tls.ProcessServerFinished (); // The shared key is computed as recommended in WS-Trust: // P_SHA1(encrypted_key,SHA1(exc14n(RST..RSTRs))+"CK-HASH") byte [] hash = SHA1.Create ().ComputeHash (tlsInfo.Messages.ToArray ()); byte [] key = tls.CreateHash (tls.MasterSecret, hash, "CK-HASH"); byte [] keyTlsApplied = tls.ProcessApplicationData (key); foreach (byte b in hash) Console.Write ("{0:X02} ", b); Console.WriteLine (); foreach (byte b in key) Console.Write ("{0:X02} ", b); Console.WriteLine (); WstRequestSecurityTokenResponseCollection col = new WstRequestSecurityTokenResponseCollection (); WstRequestSecurityTokenResponse rstr = new WstRequestSecurityTokenResponse (SecurityTokenSerializer); rstr.Context = reader.Value.Context; rstr.TokenType = Constants.WsscContextToken; DateTime from = DateTime.Now; // FIXME: not sure if arbitrary key is used here. SecurityContextSecurityToken sct = SecurityContextSecurityToken.CreateCookieSecurityContextToken ( // Create a new context. // (do not use sslnego context here.) new UniqueId (), "uuid-" + Guid.NewGuid (), key, from, // FIXME: use LocalServiceSecuritySettings.NegotiationTimeout from.AddHours (8), null, owner.Manager.ServiceCredentials.SecureConversationAuthentication.SecurityStateEncoder); rstr.RequestedSecurityToken = sct; // without this ProcessApplicationData(), .NET seems // to fail recovering the key. rstr.RequestedProofToken = keyTlsApplied; rstr.RequestedAttachedReference = new LocalIdKeyIdentifierClause (sct.Id); rstr.RequestedUnattachedReference = new SecurityContextKeyIdentifierClause (sct.ContextId, null); WstLifetime lt = new WstLifetime (); lt.Created = from; lt.Expires = from.Add (SecurityBindingElement.LocalServiceSettings.IssuedCookieLifetime); rstr.Lifetime = lt; rstr.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls); rstr.BinaryExchange.Value = serverFinished; col.Responses.Add (rstr); // Authenticator is mandatory for MS sslnego. rstr = new WstRequestSecurityTokenResponse (SecurityTokenSerializer); rstr.Context = reader.Value.Context; rstr.Authenticator = tls.CreateHash (key, hash, "AUTH-HASH"); col.Responses.Add (rstr); sessions.Remove (reader.Value.Context); // FIXME: get correct tokenRequestor address (probably identity authorized?) if (owner.IssuedSecurityTokenHandler != null) owner.IssuedSecurityTokenHandler (sct, request.Headers.ReplyTo); return Message.CreateMessage (request.Version, Constants.WstIssueReplyAction, col); } protected override void OnAbort () { throw new NotImplementedException (); } protected override void OnOpen (TimeSpan timeout) { if (State == CommunicationState.Opened) throw new InvalidOperationException ("Already opened."); EnsureProperties (); proxy = new WSTrustSecurityTokenServiceProxy ( IssuerBinding, IssuerAddress); } protected override IAsyncResult OnBeginOpen (TimeSpan timeout, AsyncCallback callback, object state) { throw new NotImplementedException (); } protected override void OnEndOpen (IAsyncResult result) { throw new NotImplementedException (); } protected override void OnClose (TimeSpan timeout) { if (proxy != null) proxy.Close (); } protected override IAsyncResult OnBeginClose (TimeSpan timeout, AsyncCallback callback, object state) { throw new NotImplementedException (); } protected override void OnEndClose (IAsyncResult result) { throw new NotImplementedException (); } } }