1 //----------------------------------------------------------------------------- 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //----------------------------------------------------------------------------- 4 5 namespace System.ServiceModel.Security 6 { 7 using System.Collections.Generic; 8 using System.Collections.ObjectModel; 9 using System.IdentityModel.Policy; 10 using System.IdentityModel.Selectors; 11 using System.IdentityModel.Tokens; 12 using System.IO; 13 using System.Runtime; 14 using System.Security.Authentication.ExtendedProtection; 15 using System.Security.Cryptography; 16 using System.ServiceModel; 17 using System.ServiceModel.Channels; 18 using System.ServiceModel.Diagnostics; 19 using System.ServiceModel.Security.Tokens; 20 using System.Xml; 21 22 using CanonicalizationDriver = System.IdentityModel.CanonicalizationDriver; 23 using Psha1DerivedKeyGenerator = System.IdentityModel.Psha1DerivedKeyGenerator; 24 using SafeFreeCredentials = System.IdentityModel.SafeFreeCredentials; 25 26 abstract class SspiNegotiationTokenProvider : NegotiationTokenProvider<SspiNegotiationTokenProviderState> 27 { 28 bool negotiateTokenOnOpen; 29 SecurityBindingElement securityBindingElement; 30 SspiNegotiationTokenProvider()31 protected SspiNegotiationTokenProvider() 32 : this(null) 33 { 34 } 35 SspiNegotiationTokenProvider(SecurityBindingElement securityBindingElement)36 protected SspiNegotiationTokenProvider(SecurityBindingElement securityBindingElement) 37 : base() 38 { 39 this.securityBindingElement = securityBindingElement; 40 } 41 42 public bool NegotiateTokenOnOpen 43 { 44 get 45 { 46 return this.negotiateTokenOnOpen; 47 } 48 set 49 { 50 this.CommunicationObject.ThrowIfDisposedOrImmutable(); 51 this.negotiateTokenOnOpen = value; 52 } 53 } 54 55 // SspiNegotiationTokenProvider abstract methods ValidateSspiNegotiation(ISspiNegotiation sspiNegotiation)56 protected abstract ReadOnlyCollection<IAuthorizationPolicy> ValidateSspiNegotiation(ISspiNegotiation sspiNegotiation); 57 public abstract XmlDictionaryString NegotiationValueType { get; } 58 OnOpen(TimeSpan timeout)59 public override void OnOpen(TimeSpan timeout) 60 { 61 TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); 62 this.EnsureEndpointAddressDoesNotRequireEncryption(this.TargetAddress); 63 base.OnOpen(timeoutHelper.RemainingTime()); 64 if (this.negotiateTokenOnOpen) 65 { 66 this.DoNegotiation(timeoutHelper.RemainingTime()); 67 } 68 } 69 GetNegotiationChannelFactory(IChannelFactory<IRequestChannel> transportChannelFactory, ChannelBuilder channelBuilder)70 protected override IChannelFactory<IRequestChannel> GetNegotiationChannelFactory(IChannelFactory<IRequestChannel> transportChannelFactory, ChannelBuilder channelBuilder) 71 { 72 return transportChannelFactory; 73 } 74 75 // helper methods ValidateIncomingBinaryNegotiation(BinaryNegotiation incomingNego)76 void ValidateIncomingBinaryNegotiation(BinaryNegotiation incomingNego) 77 { 78 incomingNego.Validate(NegotiationValueType); 79 } 80 AddToDigest(HashAlgorithm negotiationDigest, Stream stream)81 static void AddToDigest(HashAlgorithm negotiationDigest, Stream stream) 82 { 83 stream.Flush(); 84 stream.Seek(0, SeekOrigin.Begin); 85 CanonicalizationDriver canonicalizer = new CanonicalizationDriver(); 86 canonicalizer.SetInput(stream); 87 byte[] canonicalizedData = canonicalizer.GetBytes(); 88 lock (negotiationDigest) 89 { 90 negotiationDigest.TransformBlock(canonicalizedData, 0, canonicalizedData.Length, canonicalizedData, 0); 91 } 92 } 93 AddToDigest(SspiNegotiationTokenProviderState sspiState, RequestSecurityToken rst)94 static void AddToDigest(SspiNegotiationTokenProviderState sspiState, RequestSecurityToken rst) 95 { 96 MemoryStream stream = new MemoryStream(); 97 XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream); 98 rst.WriteTo(writer); 99 writer.Flush(); 100 AddToDigest(sspiState.NegotiationDigest, stream); 101 } 102 AddToDigest(SspiNegotiationTokenProviderState sspiState, RequestSecurityTokenResponse rstr, bool wasReceived, bool isFinalRstr)103 void AddToDigest(SspiNegotiationTokenProviderState sspiState, RequestSecurityTokenResponse rstr, bool wasReceived, bool isFinalRstr) 104 { 105 MemoryStream stream = new MemoryStream(); 106 XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream); 107 if (!wasReceived) 108 { 109 rstr.WriteTo(writer); 110 } 111 else 112 { 113 if (!isFinalRstr) 114 { 115 rstr.RequestSecurityTokenResponseXml.WriteTo(writer); 116 } 117 else 118 { 119 XmlElement rstrClone = (XmlElement) rstr.RequestSecurityTokenResponseXml.CloneNode(true); 120 List<XmlNode> nodesToRemove = new List<XmlNode>(2); 121 for (int i = 0; i < rstrClone.ChildNodes.Count; ++i) 122 { 123 XmlNode child = (rstrClone.ChildNodes[i]); 124 if (this.StandardsManager.TrustDriver.IsRequestedSecurityTokenElement(child.LocalName, child.NamespaceURI)) 125 { 126 nodesToRemove.Add(child); 127 } 128 else if (this.StandardsManager.TrustDriver.IsRequestedProofTokenElement(child.LocalName, child.NamespaceURI)) 129 { 130 nodesToRemove.Add(child); 131 } 132 } 133 for (int i = 0; i < nodesToRemove.Count; ++i) 134 { 135 rstrClone.RemoveChild(nodesToRemove[i]); 136 } 137 rstrClone.WriteTo(writer); 138 } 139 } 140 writer.Flush(); 141 AddToDigest(sspiState.NegotiationDigest, stream); 142 } 143 IsCorrectAuthenticator(SspiNegotiationTokenProviderState sspiState, byte[] proofKey, byte[] serverAuthenticator)144 static bool IsCorrectAuthenticator(SspiNegotiationTokenProviderState sspiState, byte[] proofKey, byte[] serverAuthenticator) 145 { 146 byte[] negotiationHash; 147 lock (sspiState.NegotiationDigest) 148 { 149 sspiState.NegotiationDigest.TransformFinalBlock(CryptoHelper.EmptyBuffer, 0, 0); 150 negotiationHash = sspiState.NegotiationDigest.Hash; 151 } 152 Psha1DerivedKeyGenerator generator = new Psha1DerivedKeyGenerator(proofKey); 153 byte[] clientAuthenticator = generator.GenerateDerivedKey(SecurityUtils.CombinedHashLabel, negotiationHash, SecurityNegotiationConstants.NegotiationAuthenticatorSize, 0); 154 if (clientAuthenticator.Length != serverAuthenticator.Length) 155 { 156 return false; 157 } 158 for (int i = 0; i < clientAuthenticator.Length; ++i) 159 { 160 if (clientAuthenticator[i] != serverAuthenticator[i]) 161 { 162 return false; 163 } 164 } 165 return true; 166 } 167 PrepareRstr( SspiNegotiationTokenProviderState sspiState, byte[] outgoingBlob )168 BodyWriter PrepareRstr( SspiNegotiationTokenProviderState sspiState, byte[] outgoingBlob ) 169 { 170 RequestSecurityTokenResponse rstr = new RequestSecurityTokenResponse(this.StandardsManager); 171 rstr.Context = sspiState.Context; 172 rstr.SetBinaryNegotiation(new BinaryNegotiation(NegotiationValueType, outgoingBlob)); 173 rstr.MakeReadOnly(); 174 AddToDigest(sspiState, rstr, false, false); 175 return rstr; 176 } 177 GetFirstOutgoingMessageBody( SspiNegotiationTokenProviderState sspiState, out MessageProperties messageProperties )178 protected override BodyWriter GetFirstOutgoingMessageBody( SspiNegotiationTokenProviderState sspiState, out MessageProperties messageProperties ) 179 { 180 messageProperties = null; 181 182 // both message logging and Visual Studio trigger message serialization and hence can cause 183 // premature invocation of OnGetBinaryNegotiation(); flag this RST as streamed to block 184 // serialization of its body and hence premature calls to InitializeSecurityContext() 185 186 RequestSecurityToken rst = new RequestSecurityToken(this.StandardsManager, false); 187 rst.Context = sspiState.Context; 188 rst.TokenType = this.StandardsManager.SecureConversationDriver.TokenTypeUri; 189 rst.KeySize = this.SecurityAlgorithmSuite.DefaultSymmetricKeyLength; 190 191 // delay GetOutgoingBlob()'s first call to InitializeSecurityContext() until a channel binding 192 // is available 193 194 rst.OnGetBinaryNegotiation = (new GetOutgoingBlobProxy(sspiState, this, rst)).GetOutgoingBlob; 195 196 return rst; 197 } 198 CreateClientChannel( EndpointAddress target, Uri via )199 protected override IRequestChannel CreateClientChannel( EndpointAddress target, Uri via ) 200 { 201 IRequestChannel rstChannel = base.CreateClientChannel(target, via); 202 203 if (!SecurityUtils.IsChannelBindingDisabled && (this.securityBindingElement is TransportSecurityBindingElement)) 204 { 205 // enable channel binding on this side channel 206 IChannelBindingProvider cbp = rstChannel.GetProperty<IChannelBindingProvider>(); 207 if (cbp != null) 208 { 209 cbp.EnableChannelBindingSupport(); 210 } 211 } 212 213 return rstChannel; 214 } 215 216 /// <summary> 217 /// Proxy helps in implementating the delay of obtaining the binary data till later in the stack until the 218 /// ChannelBinding is obtained from the message. 219 /// </summary> 220 class GetOutgoingBlobProxy 221 { 222 RequestSecurityToken _rst; 223 SspiNegotiationTokenProvider _sspiProvider; 224 SspiNegotiationTokenProviderState _sspiState; 225 GetOutgoingBlobProxy( SspiNegotiationTokenProviderState sspiState, SspiNegotiationTokenProvider sspiProvider, RequestSecurityToken rst )226 public GetOutgoingBlobProxy( SspiNegotiationTokenProviderState sspiState, SspiNegotiationTokenProvider sspiProvider, RequestSecurityToken rst ) 227 { 228 _sspiState = sspiState; 229 _sspiProvider = sspiProvider; 230 _rst = rst; 231 } 232 GetOutgoingBlob( ChannelBinding channelBinding )233 public void GetOutgoingBlob( ChannelBinding channelBinding ) 234 { 235 byte[] outgoingBlob = _sspiState.SspiNegotiation.GetOutgoingBlob(null, channelBinding, null); 236 237 if (outgoingBlob == null && _sspiState.SspiNegotiation.IsCompleted == false) 238 { 239 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.NoBinaryNegoToSend))); 240 } 241 242 _rst.SetBinaryNegotiation(new BinaryNegotiation(_sspiProvider.NegotiationValueType, outgoingBlob)); 243 SspiNegotiationTokenProvider.AddToDigest(_sspiState, _rst); 244 _rst.MakeReadOnly(); 245 } 246 } 247 GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)248 protected override BodyWriter GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState) 249 { 250 try 251 { 252 ThrowIfFault(incomingMessage, this.TargetAddress); 253 } 254 catch (FaultException fault) 255 { 256 if (fault.Code.IsSenderFault) 257 { 258 if (fault.Code.SubCode.Name == TrustApr2004Strings.FailedAuthenticationFaultCode || fault.Code.SubCode.Name == TrustFeb2005Strings.FailedAuthenticationFaultCode) 259 throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.AuthenticationOfClientFailed), fault), incomingMessage); 260 261 throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.FailedSspiNegotiation), fault), incomingMessage); 262 } 263 else 264 { 265 throw; 266 } 267 } 268 RequestSecurityTokenResponse negotiationRstr = null; 269 RequestSecurityTokenResponse authenticatorRstr = null; 270 XmlDictionaryReader bodyReader = incomingMessage.GetReaderAtBodyContents(); 271 using (bodyReader) 272 { 273 if (this.StandardsManager.TrustDriver.IsAtRequestSecurityTokenResponseCollection(bodyReader)) 274 { 275 RequestSecurityTokenResponseCollection rstrCollection = this.StandardsManager.TrustDriver.CreateRequestSecurityTokenResponseCollection(bodyReader); 276 using (IEnumerator<RequestSecurityTokenResponse> enumerator = rstrCollection.RstrCollection.GetEnumerator()) 277 { 278 enumerator.MoveNext(); 279 negotiationRstr = enumerator.Current; 280 if (enumerator.MoveNext()) 281 { 282 authenticatorRstr = enumerator.Current; 283 } 284 } 285 if (authenticatorRstr == null) 286 { 287 throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.AuthenticatorNotPresentInRSTRCollection)), incomingMessage); 288 } 289 else if (authenticatorRstr.Context != negotiationRstr.Context) 290 { 291 throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.RSTRAuthenticatorHasBadContext)), incomingMessage); 292 } 293 AddToDigest(sspiState, negotiationRstr, true, true); 294 } 295 else if (this.StandardsManager.TrustDriver.IsAtRequestSecurityTokenResponse(bodyReader)) 296 { 297 negotiationRstr = RequestSecurityTokenResponse.CreateFrom(this.StandardsManager, bodyReader); 298 AddToDigest(sspiState, negotiationRstr, true, false); 299 } 300 else 301 { 302 this.StandardsManager.TrustDriver.OnRSTRorRSTRCMissingException(); 303 } 304 incomingMessage.ReadFromBodyContentsToEnd(bodyReader); 305 } 306 if (negotiationRstr.Context != sspiState.Context) 307 { 308 throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.BadSecurityNegotiationContext)), incomingMessage); 309 } 310 BinaryNegotiation incomingBinaryNego = negotiationRstr.GetBinaryNegotiation(); 311 byte[] incomingBlob; 312 if (incomingBinaryNego != null) 313 { 314 ValidateIncomingBinaryNegotiation(incomingBinaryNego); 315 incomingBlob = incomingBinaryNego.GetNegotiationData(); 316 } 317 else 318 { 319 incomingBlob = null; 320 } 321 BodyWriter nextMessageBody; 322 if (incomingBlob == null && sspiState.SspiNegotiation.IsCompleted == false) 323 { 324 throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.NoBinaryNegoToReceive)), incomingMessage); 325 } 326 else if (incomingBlob == null && sspiState.SspiNegotiation.IsCompleted == true) 327 { 328 // the incoming RSTR must have the negotiated token 329 OnNegotiationComplete(sspiState, negotiationRstr, authenticatorRstr); 330 nextMessageBody = null; 331 } 332 else 333 { 334 // we got an incoming blob. Process it and see if there is an outgoing blob 335 byte[] outgoingBlob = sspiState.SspiNegotiation.GetOutgoingBlob(incomingBlob, 336 SecurityUtils.GetChannelBindingFromMessage(incomingMessage), 337 null); 338 339 if (outgoingBlob == null && sspiState.SspiNegotiation.IsCompleted == false) 340 { 341 throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.NoBinaryNegoToSend)), incomingMessage); 342 } 343 else if (outgoingBlob == null && sspiState.SspiNegotiation.IsCompleted == true) 344 { 345 // the incoming RSTR had the last blob. It must have the token too 346 this.OnNegotiationComplete(sspiState, negotiationRstr, authenticatorRstr); 347 nextMessageBody = null; 348 } 349 else 350 { 351 nextMessageBody = PrepareRstr(sspiState, outgoingBlob); 352 } 353 } 354 return nextMessageBody; 355 } 356 OnNegotiationComplete(SspiNegotiationTokenProviderState sspiState, RequestSecurityTokenResponse negotiationRstr, RequestSecurityTokenResponse authenticatorRstr)357 void OnNegotiationComplete(SspiNegotiationTokenProviderState sspiState, RequestSecurityTokenResponse negotiationRstr, RequestSecurityTokenResponse authenticatorRstr) 358 { 359 ISspiNegotiation sspiNegotiation = sspiState.SspiNegotiation; 360 ReadOnlyCollection<IAuthorizationPolicy> authorizationPolicies = ValidateSspiNegotiation(sspiNegotiation); 361 // the negotiation has completed successfully - the service token needs to be extracted from the 362 // negotiationRstr 363 SecurityTokenResolver tokenResolver = new SspiSecurityTokenResolver(sspiNegotiation); 364 GenericXmlSecurityToken serviceToken = negotiationRstr.GetIssuedToken(tokenResolver, EmptyReadOnlyCollection<SecurityTokenAuthenticator>.Instance, 365 SecurityKeyEntropyMode.ServerEntropy, null, this.SecurityContextTokenUri, authorizationPolicies, 0, false); 366 if (serviceToken == null) 367 { 368 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.NoServiceTokenReceived))); 369 } 370 WrappedKeySecurityToken wrappedToken = (serviceToken.ProofToken as WrappedKeySecurityToken); 371 if (wrappedToken == null || wrappedToken.WrappingAlgorithm != sspiNegotiation.KeyEncryptionAlgorithm) 372 { 373 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.ProofTokenWasNotWrappedCorrectly))); 374 } 375 byte[] proofKey = wrappedToken.GetWrappedKey(); 376 if (authenticatorRstr == null) 377 { 378 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.RSTRAuthenticatorNotPresent))); 379 } 380 byte[] serverAuthenticator = authenticatorRstr.GetAuthenticator(); 381 if (serverAuthenticator == null) 382 { 383 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.RSTRAuthenticatorNotPresent))); 384 } 385 if (!IsCorrectAuthenticator(sspiState, proofKey, serverAuthenticator)) 386 { 387 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.RSTRAuthenticatorIncorrect))); 388 } 389 sspiState.SetServiceToken(serviceToken); 390 } 391 392 class SspiSecurityTokenResolver : SecurityTokenResolver, ISspiNegotiationInfo 393 { 394 ISspiNegotiation sspiNegotiation; 395 SspiSecurityTokenResolver(ISspiNegotiation sspiNegotiation)396 public SspiSecurityTokenResolver(ISspiNegotiation sspiNegotiation) 397 { 398 this.sspiNegotiation = sspiNegotiation; 399 } 400 401 public ISspiNegotiation SspiNegotiation 402 { 403 get { return this.sspiNegotiation; } 404 } 405 TryResolveTokenCore(SecurityKeyIdentifier keyIdentifier, out SecurityToken token)406 protected override bool TryResolveTokenCore(SecurityKeyIdentifier keyIdentifier, out SecurityToken token) 407 { 408 token = null; 409 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException()); 410 } 411 TryResolveTokenCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityToken token)412 protected override bool TryResolveTokenCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityToken token) 413 { 414 token = null; 415 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException()); 416 } 417 TryResolveSecurityKeyCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityKey key)418 protected override bool TryResolveSecurityKeyCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityKey key) 419 { 420 key = null; 421 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException()); 422 } 423 } 424 } 425 426 class SspiIssuanceChannelParameter 427 { 428 bool getTokenOnOpen; 429 SafeFreeCredentials credentialsHandle; 430 SspiIssuanceChannelParameter(bool getTokenOnOpen, SafeFreeCredentials credentialsHandle)431 public SspiIssuanceChannelParameter(bool getTokenOnOpen, SafeFreeCredentials credentialsHandle) 432 { 433 this.getTokenOnOpen = getTokenOnOpen; 434 this.credentialsHandle = credentialsHandle; 435 } 436 437 public bool GetTokenOnOpen 438 { 439 get { return this.getTokenOnOpen; } 440 } 441 442 public SafeFreeCredentials CredentialsHandle 443 { 444 get { return this.credentialsHandle; } 445 } 446 } 447 448 } 449