//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Channels { using System.Collections.Generic; using System.Diagnostics; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; using System.Net; using System.Net.Security; using System.Runtime; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; using System.ServiceModel; using System.ServiceModel.Security; using System.ServiceModel.Security.Tokens; static class AuthenticationLevelHelper { internal static string ToString(AuthenticationLevel authenticationLevel) { if (authenticationLevel == AuthenticationLevel.MutualAuthRequested) { return "mutualAuthRequested"; } else if (authenticationLevel == AuthenticationLevel.MutualAuthRequired) { return "mutualAuthRequired"; } else if (authenticationLevel == AuthenticationLevel.None) { return "none"; } Fx.Assert("unknown authentication level"); return authenticationLevel.ToString(); } } static class HttpTransportSecurityHelpers { static Dictionary targetNameCounter = new Dictionary(); public static bool AddIdentityMapping(Uri via, EndpointAddress target) { string key = via.AbsoluteUri; string value; EndpointIdentity identity = target.Identity; if (identity != null && !(identity is X509CertificateEndpointIdentity)) { value = SecurityUtils.GetSpnFromIdentity(identity, target); } else { value = SecurityUtils.GetSpnFromTarget(target); } lock (targetNameCounter) { int refCount = 0; if (targetNameCounter.TryGetValue(key, out refCount)) { if (!AuthenticationManager.CustomTargetNameDictionary.ContainsKey(key) || AuthenticationManager.CustomTargetNameDictionary[key] != value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.HttpTargetNameDictionaryConflict, key, value))); } targetNameCounter[key] = refCount + 1; } else { if (AuthenticationManager.CustomTargetNameDictionary.ContainsKey(key) && AuthenticationManager.CustomTargetNameDictionary[key] != value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.HttpTargetNameDictionaryConflict, key, value))); } AuthenticationManager.CustomTargetNameDictionary[key] = value; targetNameCounter.Add(key, 1); } } return true; } public static void RemoveIdentityMapping(Uri via, EndpointAddress target, bool validateState) { string key = via.AbsoluteUri; string value; EndpointIdentity identity = target.Identity; if (identity != null && !(identity is X509CertificateEndpointIdentity)) { value = SecurityUtils.GetSpnFromIdentity(identity, target); } else { value = SecurityUtils.GetSpnFromTarget(target); } lock (targetNameCounter) { int refCount = targetNameCounter[key]; if (refCount == 1) { targetNameCounter.Remove(key); } else { targetNameCounter[key] = refCount - 1; } if (validateState) { if (!AuthenticationManager.CustomTargetNameDictionary.ContainsKey(key) || AuthenticationManager.CustomTargetNameDictionary[key] != value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.HttpTargetNameDictionaryConflict, key, value))); } } } } static Dictionary serverCertMap = new Dictionary(); static RemoteCertificateValidationCallback chainedServerCertValidationCallback = null; static bool serverCertValidationCallbackInstalled = false; public static void AddServerCertMapping(HttpWebRequest request, EndpointAddress to) { Fx.Assert(request.RequestUri.Scheme == Uri.UriSchemeHttps, "Wrong URI scheme for AddServerCertMapping()."); X509CertificateEndpointIdentity remoteCertificateIdentity = to.Identity as X509CertificateEndpointIdentity; if (remoteCertificateIdentity != null) { // The following condition should have been validated when the channel was created. Fx.Assert(remoteCertificateIdentity.Certificates.Count <= 1, "HTTPS server certificate identity contains multiple certificates"); AddServerCertMapping(request, remoteCertificateIdentity.Certificates[0].Thumbprint); } } static void AddServerCertMapping(HttpWebRequest request, string thumbprint) { lock (serverCertMap) { if (!serverCertValidationCallbackInstalled) { chainedServerCertValidationCallback = ServicePointManager.ServerCertificateValidationCallback; ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback( OnValidateServerCertificate); serverCertValidationCallbackInstalled = true; } serverCertMap.Add(request, thumbprint); } } static bool OnValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { HttpWebRequest request = sender as HttpWebRequest; if (request != null) { string thumbprint; lock (serverCertMap) { serverCertMap.TryGetValue(request, out thumbprint); } if (thumbprint != null) { try { ValidateServerCertificate(certificate, thumbprint); } catch (SecurityNegotiationException e) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); return false; } } } if (chainedServerCertValidationCallback == null) { return (sslPolicyErrors == SslPolicyErrors.None); } else { return chainedServerCertValidationCallback(sender, certificate, chain, sslPolicyErrors); } } public static void RemoveServerCertMapping(HttpWebRequest request) { lock (serverCertMap) { serverCertMap.Remove(request); } } static void ValidateServerCertificate(X509Certificate certificate, string thumbprint) { string certHashString = certificate.GetCertHashString(); if (!thumbprint.Equals(certHashString)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityNegotiationException(SR.GetString(SR.HttpsServerCertThumbprintMismatch, certificate.Subject, certHashString, thumbprint))); } } } static class TransportSecurityHelpers { public static IAsyncResult BeginGetSspiCredential(SecurityTokenProviderContainer tokenProvider, TimeSpan timeout, AsyncCallback callback, object state) { return new GetSspiCredentialAsyncResult(tokenProvider.TokenProvider as SspiSecurityTokenProvider, timeout, callback, state); } public static IAsyncResult BeginGetSspiCredential(SecurityTokenProvider tokenProvider, TimeSpan timeout, AsyncCallback callback, object state) { return new GetSspiCredentialAsyncResult((SspiSecurityTokenProvider)tokenProvider, timeout, callback, state); } public static NetworkCredential EndGetSspiCredential(IAsyncResult result, out TokenImpersonationLevel impersonationLevel, out AuthenticationLevel authenticationLevel) { return GetSspiCredentialAsyncResult.End(result, out impersonationLevel, out authenticationLevel); } public static NetworkCredential EndGetSspiCredential(IAsyncResult result, out TokenImpersonationLevel impersonationLevel, out bool allowNtlm) { return GetSspiCredentialAsyncResult.End(result, out impersonationLevel, out allowNtlm); } // used for HTTP (from HttpChannelUtilities.GetCredential) public static NetworkCredential GetSspiCredential(SecurityTokenProviderContainer tokenProvider, TimeSpan timeout, out TokenImpersonationLevel impersonationLevel, out AuthenticationLevel authenticationLevel) { bool dummyExtractWindowsGroupClaims; bool allowNtlm; NetworkCredential result = GetSspiCredential(tokenProvider.TokenProvider as SspiSecurityTokenProvider, timeout, out dummyExtractWindowsGroupClaims, out impersonationLevel, out allowNtlm); authenticationLevel = allowNtlm ? AuthenticationLevel.MutualAuthRequested : AuthenticationLevel.MutualAuthRequired; return result; } // used by client WindowsStream security (from InitiateUpgrade) public static NetworkCredential GetSspiCredential(SspiSecurityTokenProvider tokenProvider, TimeSpan timeout, out TokenImpersonationLevel impersonationLevel, out bool allowNtlm) { bool dummyExtractWindowsGroupClaims; return GetSspiCredential(tokenProvider, timeout, out dummyExtractWindowsGroupClaims, out impersonationLevel, out allowNtlm); } // used by server WindowsStream security (from Open) public static NetworkCredential GetSspiCredential(SecurityTokenManager credentialProvider, SecurityTokenRequirement sspiTokenRequirement, TimeSpan timeout, out bool extractGroupsForWindowsAccounts) { extractGroupsForWindowsAccounts = TransportDefaults.ExtractGroupsForWindowsAccounts; NetworkCredential result = null; if (credentialProvider != null) { SecurityTokenProvider tokenProvider = credentialProvider.CreateSecurityTokenProvider(sspiTokenRequirement); if (tokenProvider != null) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); SecurityUtils.OpenTokenProviderIfRequired(tokenProvider, timeoutHelper.RemainingTime()); bool success = false; try { TokenImpersonationLevel dummyImpersonationLevel; bool dummyAllowNtlm; result = GetSspiCredential((SspiSecurityTokenProvider)tokenProvider, timeoutHelper.RemainingTime(), out extractGroupsForWindowsAccounts, out dummyImpersonationLevel, out dummyAllowNtlm); success = true; } finally { if (!success) { SecurityUtils.AbortTokenProviderIfRequired(tokenProvider); } } SecurityUtils.CloseTokenProviderIfRequired(tokenProvider, timeoutHelper.RemainingTime()); } } return result; } // core Cred lookup code static NetworkCredential GetSspiCredential(SspiSecurityTokenProvider tokenProvider, TimeSpan timeout, out bool extractGroupsForWindowsAccounts, out TokenImpersonationLevel impersonationLevel, out bool allowNtlm) { NetworkCredential credential = null; extractGroupsForWindowsAccounts = TransportDefaults.ExtractGroupsForWindowsAccounts; impersonationLevel = TokenImpersonationLevel.Identification; allowNtlm = ConnectionOrientedTransportDefaults.AllowNtlm; if (tokenProvider != null) { SspiSecurityToken token = TransportSecurityHelpers.GetToken(tokenProvider, timeout); if (token != null) { extractGroupsForWindowsAccounts = token.ExtractGroupsForWindowsAccounts; impersonationLevel = token.ImpersonationLevel; allowNtlm = token.AllowNtlm; if (token.NetworkCredential != null) { credential = token.NetworkCredential; SecurityUtils.FixNetworkCredential(ref credential); } } } // Initialize to the default value if no token provided. A partial trust app should not have access to the // default network credentials but should be able to provide credentials. The DefaultNetworkCredentials // getter will throw under partial trust. if (credential == null) { credential = CredentialCache.DefaultNetworkCredentials; } return credential; } public static SecurityTokenRequirement CreateSspiTokenRequirement(string transportScheme, Uri listenUri) { RecipientServiceModelSecurityTokenRequirement tokenRequirement = new RecipientServiceModelSecurityTokenRequirement(); tokenRequirement.TransportScheme = transportScheme; tokenRequirement.RequireCryptographicToken = false; tokenRequirement.ListenUri = listenUri; tokenRequirement.TokenType = ServiceModelSecurityTokenTypes.SspiCredential; return tokenRequirement; } static SecurityTokenRequirement CreateSspiTokenRequirement(EndpointAddress target, Uri via, string transportScheme) { InitiatorServiceModelSecurityTokenRequirement sspiTokenRequirement = new InitiatorServiceModelSecurityTokenRequirement(); sspiTokenRequirement.TokenType = ServiceModelSecurityTokenTypes.SspiCredential; sspiTokenRequirement.RequireCryptographicToken = false; sspiTokenRequirement.TransportScheme = transportScheme; sspiTokenRequirement.TargetAddress = target; sspiTokenRequirement.Via = via; return sspiTokenRequirement; } public static SspiSecurityTokenProvider GetSspiTokenProvider( SecurityTokenManager tokenManager, EndpointAddress target, Uri via, string transportScheme, AuthenticationSchemes authenticationScheme, ChannelParameterCollection channelParameters) { if (tokenManager != null) { SecurityTokenRequirement sspiRequirement = CreateSspiTokenRequirement(target, via, transportScheme); sspiRequirement.Properties[ServiceModelSecurityTokenRequirement.HttpAuthenticationSchemeProperty] = authenticationScheme; if (channelParameters != null) { sspiRequirement.Properties[ServiceModelSecurityTokenRequirement.ChannelParametersCollectionProperty] = channelParameters; } SspiSecurityTokenProvider tokenProvider = tokenManager.CreateSecurityTokenProvider(sspiRequirement) as SspiSecurityTokenProvider; return tokenProvider; } else { return null; } } public static SspiSecurityTokenProvider GetSspiTokenProvider( SecurityTokenManager tokenManager, EndpointAddress target, Uri via, string transportScheme, out IdentityVerifier identityVerifier) { identityVerifier = null; if (tokenManager != null) { SspiSecurityTokenProvider tokenProvider = tokenManager.CreateSecurityTokenProvider(CreateSspiTokenRequirement(target, via, transportScheme)) as SspiSecurityTokenProvider; if (tokenProvider != null) { identityVerifier = IdentityVerifier.CreateDefault(); } return tokenProvider; } return null; } public static SecurityTokenProvider GetDigestTokenProvider( SecurityTokenManager tokenManager, EndpointAddress target, Uri via, string transportScheme, AuthenticationSchemes authenticationScheme, ChannelParameterCollection channelParameters) { if (tokenManager != null) { InitiatorServiceModelSecurityTokenRequirement digestTokenRequirement = new InitiatorServiceModelSecurityTokenRequirement(); digestTokenRequirement.TokenType = ServiceModelSecurityTokenTypes.SspiCredential; digestTokenRequirement.TargetAddress = target; digestTokenRequirement.Via = via; digestTokenRequirement.RequireCryptographicToken = false; digestTokenRequirement.TransportScheme = transportScheme; digestTokenRequirement.Properties[ServiceModelSecurityTokenRequirement.HttpAuthenticationSchemeProperty] = authenticationScheme; if (channelParameters != null) { digestTokenRequirement.Properties[ServiceModelSecurityTokenRequirement.ChannelParametersCollectionProperty] = channelParameters; } return tokenManager.CreateSecurityTokenProvider(digestTokenRequirement) as SspiSecurityTokenProvider; } return null; } public static SecurityTokenAuthenticator GetCertificateTokenAuthenticator(SecurityTokenManager tokenManager, string transportScheme, Uri listenUri) { RecipientServiceModelSecurityTokenRequirement clientAuthRequirement = new RecipientServiceModelSecurityTokenRequirement(); clientAuthRequirement.TokenType = SecurityTokenTypes.X509Certificate; clientAuthRequirement.RequireCryptographicToken = true; clientAuthRequirement.KeyUsage = SecurityKeyUsage.Signature; clientAuthRequirement.TransportScheme = transportScheme; clientAuthRequirement.ListenUri = listenUri; SecurityTokenResolver dummy; return tokenManager.CreateSecurityTokenAuthenticator(clientAuthRequirement, out dummy); } public static SecurityTokenProvider GetCertificateTokenProvider( SecurityTokenManager tokenManager, EndpointAddress target, Uri via, string transportScheme, ChannelParameterCollection channelParameters) { if (tokenManager != null) { InitiatorServiceModelSecurityTokenRequirement certificateTokenRequirement = new InitiatorServiceModelSecurityTokenRequirement(); certificateTokenRequirement.TokenType = SecurityTokenTypes.X509Certificate; certificateTokenRequirement.TargetAddress = target; certificateTokenRequirement.Via = via; certificateTokenRequirement.RequireCryptographicToken = false; certificateTokenRequirement.TransportScheme = transportScheme; if (channelParameters != null) { certificateTokenRequirement.Properties[ServiceModelSecurityTokenRequirement.ChannelParametersCollectionProperty] = channelParameters; } return tokenManager.CreateSecurityTokenProvider(certificateTokenRequirement); } return null; } static T GetToken(SecurityTokenProvider tokenProvider, TimeSpan timeout) where T : SecurityToken { SecurityToken result = tokenProvider.GetToken(timeout); if ((result != null) && !(result is T)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString( SR.InvalidTokenProvided, tokenProvider.GetType(), typeof(T)))); } return result as T; } public static IAsyncResult BeginGetUserNameCredential(SecurityTokenProviderContainer tokenProvider, TimeSpan timeout, AsyncCallback callback, object state) { return new GetUserNameCredentialAsyncResult(tokenProvider, timeout, callback, state); } public static NetworkCredential EndGetUserNameCredential(IAsyncResult result) { return GetUserNameCredentialAsyncResult.End(result); } public static NetworkCredential GetUserNameCredential(SecurityTokenProviderContainer tokenProvider, TimeSpan timeout) { NetworkCredential result = null; if (tokenProvider != null && tokenProvider.TokenProvider != null) { UserNameSecurityToken token = GetToken(tokenProvider.TokenProvider, timeout); if (token != null) { SecurityUtils.PrepareNetworkCredential(); result = new NetworkCredential(token.UserName, token.Password); } } if (result == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString( SR.NoUserNameTokenProvided))); } return result; } static InitiatorServiceModelSecurityTokenRequirement CreateUserNameTokenRequirement( EndpointAddress target, Uri via, string transportScheme) { InitiatorServiceModelSecurityTokenRequirement usernameRequirement = new InitiatorServiceModelSecurityTokenRequirement(); usernameRequirement.RequireCryptographicToken = false; usernameRequirement.TokenType = SecurityTokenTypes.UserName; usernameRequirement.TargetAddress = target; usernameRequirement.Via = via; usernameRequirement.TransportScheme = transportScheme; return usernameRequirement; } public static SecurityTokenProvider GetUserNameTokenProvider( SecurityTokenManager tokenManager, EndpointAddress target, Uri via, string transportScheme, AuthenticationSchemes authenticationScheme, ChannelParameterCollection channelParameters) { SecurityTokenProvider result = null; if (tokenManager != null) { SecurityTokenRequirement usernameRequirement = CreateUserNameTokenRequirement(target, via, transportScheme); usernameRequirement.Properties[ServiceModelSecurityTokenRequirement.HttpAuthenticationSchemeProperty] = authenticationScheme; if (channelParameters != null) { usernameRequirement.Properties[ServiceModelSecurityTokenRequirement.ChannelParametersCollectionProperty] = channelParameters; } result = tokenManager.CreateSecurityTokenProvider(usernameRequirement); } return result; } public static Uri GetListenUri(Uri baseAddress, string relativeAddress) { Uri fullUri = baseAddress; // Ensure that baseAddress Path does end with a slash if we have a relative address if (!String.IsNullOrEmpty(relativeAddress)) { if (!baseAddress.AbsolutePath.EndsWith("/", StringComparison.Ordinal)) { UriBuilder uriBuilder = new UriBuilder(baseAddress); TcpChannelListener.FixIpv6Hostname(uriBuilder, baseAddress); uriBuilder.Path = uriBuilder.Path + "/"; baseAddress = uriBuilder.Uri; } fullUri = new Uri(baseAddress, relativeAddress); } return fullUri; } class GetUserNameCredentialAsyncResult : AsyncResult { NetworkCredential credential; static AsyncCallback onGetToken = Fx.ThunkCallback(new AsyncCallback(OnGetToken)); SecurityTokenProvider tokenProvider; public GetUserNameCredentialAsyncResult(SecurityTokenProviderContainer tokenProvider, TimeSpan timeout, AsyncCallback callback, object state) : base(callback, state) { if (tokenProvider == null || tokenProvider.TokenProvider == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString( SR.NoUserNameTokenProvided))); } this.tokenProvider = tokenProvider.TokenProvider; IAsyncResult result = this.tokenProvider.BeginGetToken(timeout, onGetToken, this); if (result.CompletedSynchronously) { CompleteGetToken(result); base.Complete(true); } } void CompleteGetToken(IAsyncResult result) { UserNameSecurityToken token = (UserNameSecurityToken)this.tokenProvider.EndGetToken(result); if (token != null) { SecurityUtils.PrepareNetworkCredential(); this.credential = new NetworkCredential(token.UserName, token.Password); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString( SR.NoUserNameTokenProvided))); } } static void OnGetToken(IAsyncResult result) { if (result.CompletedSynchronously) return; GetUserNameCredentialAsyncResult thisPtr = (GetUserNameCredentialAsyncResult)result.AsyncState; Exception completionException = null; try { thisPtr.CompleteGetToken(result); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } thisPtr.Complete(false, completionException); } public static NetworkCredential End(IAsyncResult result) { GetUserNameCredentialAsyncResult thisPtr = AsyncResult.End(result); return thisPtr.credential; } } class GetSspiCredentialAsyncResult : AsyncResult { bool allowNtlm; NetworkCredential credential; TokenImpersonationLevel impersonationLevel; static AsyncCallback onGetToken; SspiSecurityTokenProvider credentialProvider; public GetSspiCredentialAsyncResult(SspiSecurityTokenProvider credentialProvider, TimeSpan timeout, AsyncCallback callback, object state) : base(callback, state) { this.allowNtlm = ConnectionOrientedTransportDefaults.AllowNtlm; this.impersonationLevel = TokenImpersonationLevel.Identification; if (credentialProvider == null) { this.EnsureCredentialInitialized(); base.Complete(true); return; } this.credentialProvider = credentialProvider; if (onGetToken == null) { onGetToken = Fx.ThunkCallback(new AsyncCallback(OnGetToken)); } IAsyncResult result = credentialProvider.BeginGetToken(timeout, onGetToken, this); if (result.CompletedSynchronously) { CompleteGetToken(result); base.Complete(true); } } void CompleteGetToken(IAsyncResult result) { SspiSecurityToken token = (SspiSecurityToken)this.credentialProvider.EndGetToken(result); if (token != null) { this.impersonationLevel = token.ImpersonationLevel; this.allowNtlm = token.AllowNtlm; if (token.NetworkCredential != null) { this.credential = token.NetworkCredential; SecurityUtils.FixNetworkCredential(ref this.credential); } } this.EnsureCredentialInitialized(); } void EnsureCredentialInitialized() { // Initialize to the default value if no token provided. A partial trust app should not have access to // the default network credentials but should be able to provide credentials. The // DefaultNetworkCredentials getter will throw under partial trust. if (this.credential == null) { this.credential = CredentialCache.DefaultNetworkCredentials; } } static void OnGetToken(IAsyncResult result) { if (result.CompletedSynchronously) return; GetSspiCredentialAsyncResult thisPtr = (GetSspiCredentialAsyncResult)result.AsyncState; Exception completionException = null; try { thisPtr.CompleteGetToken(result); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } thisPtr.Complete(false, completionException); } public static NetworkCredential End(IAsyncResult result, out TokenImpersonationLevel impersonationLevel, out AuthenticationLevel authenticationLevel) { GetSspiCredentialAsyncResult thisPtr = AsyncResult.End(result); impersonationLevel = thisPtr.impersonationLevel; authenticationLevel = thisPtr.allowNtlm ? AuthenticationLevel.MutualAuthRequested : AuthenticationLevel.MutualAuthRequired; return thisPtr.credential; } public static NetworkCredential End(IAsyncResult result, out TokenImpersonationLevel impersonationLevel, out bool allowNtlm) { GetSspiCredentialAsyncResult thisPtr = AsyncResult.End(result); impersonationLevel = thisPtr.impersonationLevel; allowNtlm = thisPtr.allowNtlm; return thisPtr.credential; } } } }