internal static IIdentity GetIdentity(NTAuthentication context) { Debug.Assert(!context.IsServer, "GetIdentity: Server is not supported"); string name = context.Spn; string protocol = context.ProtocolName; return new GenericIdentity(name, protocol); }
internal static IIdentity GetIdentity(NTAuthentication context) { IIdentity result = null; string name = context.IsServer ? context.AssociatedName : context.Spn; string protocol = context.ProtocolName; if (context.IsServer) { SecurityContextTokenHandle token = null; try { SecurityStatusPal status; SafeDeleteContext securityContext = context.GetContext(out status); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { throw new Win32Exception((int)SecurityStatusAdapterPal.GetInteropFromSecurityStatusPal(status)); } // This will return a client token when conducted authentication on server side. // This token can be used for impersonation. We use it to create a WindowsIdentity and hand it out to the server app. Interop.SecurityStatus winStatus = (Interop.SecurityStatus)SSPIWrapper.QuerySecurityContextToken( GlobalSSPI.SSPIAuth, securityContext, out token); if (winStatus != Interop.SecurityStatus.OK) { throw new Win32Exception((int)winStatus); } string authtype = context.ProtocolName; // TODO #5241: // The following call was also specifying WindowsAccountType.Normal, true. // WindowsIdentity.IsAuthenticated is no longer supported in CoreFX. result = new WindowsIdentity(token.DangerousGetHandle(), authtype); return result; } catch (SecurityException) { // Ignore and construct generic Identity if failed due to security problem. } finally { if (token != null) { token.Dispose(); } } } // On the client we don't have access to the remote side identity. result = new GenericIdentity(name, protocol); return result; }
public Authorization Authenticate(string challenge, NetworkCredential credential, object sessionCookie, string spn, ChannelBinding channelBindingToken) { if (NetEventSource.IsEnabled) NetEventSource.Enter(this, "Authenticate"); try { lock (_sessions) { NTAuthentication clientContext; if (!_sessions.TryGetValue(sessionCookie, out clientContext)) { if (credential == null) { return null; } _sessions[sessionCookie] = clientContext = new NTAuthentication(false, "Ntlm", credential, spn, ContextFlagsPal.Connection, channelBindingToken); } string resp = clientContext.GetOutgoingBlob(challenge); if (!clientContext.IsCompleted) { return new Authorization(resp, false); } else { _sessions.Remove(sessionCookie); return new Authorization(resp, true); } } } // From reflected type NTAuthentication in System.Net.Security. catch (NullReferenceException) { return null; } finally { if (NetEventSource.IsEnabled) NetEventSource.Exit(this, "Authenticate"); } }
internal void ValidateCreateContext( string package, bool isServer, NetworkCredential credential, string servicePrincipalName, ChannelBinding channelBinding, ProtectionLevel protectionLevel, TokenImpersonationLevel impersonationLevel) { if (_exception != null && !_canRetryAuthentication) { throw _exception; } if (_context != null && _context.IsValidContext) { throw new InvalidOperationException(SR.net_auth_reauth); } if (credential == null) { throw new ArgumentNullException(nameof(credential)); } if (servicePrincipalName == null) { throw new ArgumentNullException(nameof(servicePrincipalName)); } if (impersonationLevel != TokenImpersonationLevel.Identification && impersonationLevel != TokenImpersonationLevel.Impersonation && impersonationLevel != TokenImpersonationLevel.Delegation) { throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(), SR.net_auth_supported_impl_levels); } if (_context != null && IsServer != isServer) { throw new InvalidOperationException(SR.net_auth_client_server); } _exception = null; _remoteOk = false; _framer = new StreamFramer(_innerStream); _framer.WriteHeader.MessageId = FrameHeader.HandshakeId; _expectedProtectionLevel = protectionLevel; _expectedImpersonationLevel = isServer ? impersonationLevel : TokenImpersonationLevel.None; _writeSequenceNumber = 0; _readSequenceNumber = 0; Interop.SspiCli.ContextFlags flags = Interop.SspiCli.ContextFlags.Connection; // A workaround for the client when talking to Win9x on the server side. if (protectionLevel == ProtectionLevel.None && !isServer) { package = NegotiationInfoClass.NTLM; } else if (protectionLevel == ProtectionLevel.EncryptAndSign) { flags |= Interop.SspiCli.ContextFlags.Confidentiality; } else if (protectionLevel == ProtectionLevel.Sign) { // Assuming user expects NT4 SP4 and above. flags |= Interop.SspiCli.ContextFlags.ReplayDetect | Interop.SspiCli.ContextFlags.SequenceDetect | Interop.SspiCli.ContextFlags.InitIntegrity; } if (isServer) { if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) { flags |= Interop.SspiCli.ContextFlags.AllowMissingBindings; } if (_extendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never && _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy) { flags |= Interop.SspiCli.ContextFlags.ProxyBindings; } } else { // Server side should not request any of these flags. if (protectionLevel != ProtectionLevel.None) { flags |= Interop.SspiCli.ContextFlags.MutualAuth; } if (impersonationLevel == TokenImpersonationLevel.Identification) { flags |= Interop.SspiCli.ContextFlags.InitIdentify; } if (impersonationLevel == TokenImpersonationLevel.Delegation) { flags |= Interop.SspiCli.ContextFlags.Delegate; } } _canRetryAuthentication = false; try { _context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding); } catch (Win32Exception e) { throw new AuthenticationException(SR.net_auth_SSPI, e); } }
public Authorization Authenticate(string challenge, NetworkCredential credential, object sessionCookie, string spn, ChannelBinding channelBindingToken) { if (NetEventSource.IsEnabled) NetEventSource.Enter(this, "Authenticate"); try { lock (_sessions) { NTAuthentication clientContext; if (!_sessions.TryGetValue(sessionCookie, out clientContext)) { if (credential == null) { return null; } _sessions[sessionCookie] = clientContext = new NTAuthentication(false, "Negotiate", credential, spn, ContextFlags.Connection | ContextFlags.InitIntegrity, channelBindingToken); } byte[] byteResp; string resp = null; if (!clientContext.IsCompleted) { // If auth is not yet completed keep producing // challenge responses with GetOutgoingBlob byte[] decodedChallenge = null; if (challenge != null) { decodedChallenge = Convert.FromBase64String(challenge); } byteResp = clientContext.GetOutgoingBlob(decodedChallenge, false); if (clientContext.IsCompleted && byteResp == null) { resp = "\r\n"; } if (byteResp != null) { resp = Convert.ToBase64String(byteResp); } } else { // If auth completed and still have a challenge then // server may be doing "correct" form of GSSAPI SASL. // Validate incoming and produce outgoing SASL security // layer negotiate message. resp = GetSecurityLayerOutgoingBlob(challenge, clientContext); } return new Authorization(resp, clientContext.IsCompleted); } } // From reflected type NTAuthentication in System.Net.Security. catch (NullReferenceException) { return null; } finally { if (NetEventSource.IsEnabled) NetEventSource.Exit(this, "Authenticate"); } }
// Function for SASL security layer negotiation after // authorization completes. // // Returns null for failure, Base64 encoded string on // success. private string GetSecurityLayerOutgoingBlob(string challenge, NTAuthentication clientContext) { // must have a security layer challenge if (challenge == null) return null; // "unwrap" challenge byte[] input = Convert.FromBase64String(challenge); int len; try { len = clientContext.VerifySignature(input, 0, input.Length); } catch (Win32Exception) { // any decrypt failure is an auth failure return null; } // Per RFC 2222 Section 7.2.2: // the client should then expect the server to issue a // token in a subsequent challenge. The client passes // this token to GSS_Unwrap and interprets the first // octet of cleartext as a bit-mask specifying the // security layers supported by the server and the // second through fourth octets as the maximum size // output_message to send to the server. // Section 7.2.3 // The security layer and their corresponding bit-masks // are as follows: // 1 No security layer // 2 Integrity protection // Sender calls GSS_Wrap with conf_flag set to FALSE // 4 Privacy protection // Sender calls GSS_Wrap with conf_flag set to TRUE // // Exchange 2007 and our client only support // "No security layer". Therefore verify first byte is value 1 // and the 2nd-4th bytes are value zero since token size is not // applicable when there is no security layer. if (len < 4 || // expect 4 bytes input[0] != 1 || // first value 1 input[1] != 0 || // rest value 0 input[2] != 0 || input[3] != 0) { return null; } // Continuing with RFC 2222 section 7.2.2: // The client then constructs data, with the first octet // containing the bit-mask specifying the selected security // layer, the second through fourth octets containing in // network byte order the maximum size output_message the client // is able to receive, and the remaining octets containing the // authorization identity. // // So now this contructs the "wrapped" response. The response is // payload is identical to the received server payload and the // "authorization identity" is not supplied as it is unnecessary. // let MakeSignature figure out length of output byte[] output = null; try { len = clientContext.MakeSignature(input, 0, 4, ref output); } catch (Win32Exception) { // any encrypt failure is an auth failure return null; } // return Base64 encoded string of signed payload return Convert.ToBase64String(output, 0, len); }
internal void ValidateCreateContext( string package, bool isServer, NetworkCredential credential, string servicePrincipalName, ChannelBinding channelBinding, ProtectionLevel protectionLevel, TokenImpersonationLevel impersonationLevel) { if (_exception != null && !_canRetryAuthentication) { ExceptionDispatchInfo.Throw(_exception); } if (_context != null && _context.IsValidContext) { throw new InvalidOperationException(SR.net_auth_reauth); } if (credential == null) { throw new ArgumentNullException(nameof(credential)); } if (servicePrincipalName == null) { throw new ArgumentNullException(nameof(servicePrincipalName)); } NegotiateStreamPal.ValidateImpersonationLevel(impersonationLevel); if (_context != null && IsServer != isServer) { throw new InvalidOperationException(SR.net_auth_client_server); } _exception = null; _remoteOk = false; _framer = new StreamFramer(_innerStream); _framer.WriteHeader.MessageId = FrameHeader.HandshakeId; _expectedProtectionLevel = protectionLevel; _expectedImpersonationLevel = isServer ? impersonationLevel : TokenImpersonationLevel.None; _writeSequenceNumber = 0; _readSequenceNumber = 0; ContextFlagsPal flags = ContextFlagsPal.Connection; // A workaround for the client when talking to Win9x on the server side. if (protectionLevel == ProtectionLevel.None && !isServer) { package = NegotiationInfoClass.NTLM; } else if (protectionLevel == ProtectionLevel.EncryptAndSign) { flags |= ContextFlagsPal.Confidentiality; } else if (protectionLevel == ProtectionLevel.Sign) { // Assuming user expects NT4 SP4 and above. flags |= (ContextFlagsPal.ReplayDetect | ContextFlagsPal.SequenceDetect | ContextFlagsPal.InitIntegrity); } if (isServer) { if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) { flags |= ContextFlagsPal.AllowMissingBindings; } if (_extendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never && _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy) { flags |= ContextFlagsPal.ProxyBindings; } } else { // Server side should not request any of these flags. if (protectionLevel != ProtectionLevel.None) { flags |= ContextFlagsPal.MutualAuth; } if (impersonationLevel == TokenImpersonationLevel.Identification) { flags |= ContextFlagsPal.InitIdentify; } if (impersonationLevel == TokenImpersonationLevel.Delegation) { flags |= ContextFlagsPal.Delegate; } } _canRetryAuthentication = false; try { _context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding); } catch (Win32Exception e) { throw new AuthenticationException(SR.net_auth_SSPI, e); } }
public Authorization Authenticate(string challenge, NetworkCredential credential, object sessionCookie, string spn, ChannelBinding channelBindingToken) { if (Logging.On) { Logging.Enter(Logging.Web, this, "Authenticate", null); } try { lock (this.sessions) { NTAuthentication clientContext = this.sessions[sessionCookie] as NTAuthentication; if (clientContext == null) { if (credential == null) { return(null); } this.sessions[sessionCookie] = clientContext = new NTAuthentication(false, "Negotiate", credential, spn, ContextFlags.Connection | ContextFlags.InitIntegrity, channelBindingToken); } byte[] byteResp; string resp = null; if (!clientContext.IsCompleted) { // If auth is not yet completed keep producing // challenge responses with GetOutgoingBlob SecurityStatus statusCode; byte[] decodedChallenge = null; if (challenge != null) { decodedChallenge = Convert.FromBase64String(challenge); } byteResp = clientContext.GetOutgoingBlob( decodedChallenge, false, out statusCode); // Note sure why this is here...keeping it. if (clientContext.IsCompleted && byteResp == null) { resp = "\r\n"; } if (byteResp != null) { resp = Convert.ToBase64String(byteResp); } } else { // If auth completed and still have a challenge then // server may be doing "correct" form of GSSAPI SASL. // Validate incoming and produce outgoing SASL security // layer negotiate message. resp = GetSecurityLayerOutgoingBlob( challenge, clientContext); } return(new Authorization(resp, clientContext.IsCompleted)); } } finally { if (Logging.On) { Logging.Exit(Logging.Web, this, "Authenticate", null); } } }
// Function for SASL security layer negotiation after // authorization completes. // // Returns null for failure, Base64 encoded string on // success. private string GetSecurityLayerOutgoingBlob( string challenge, NTAuthentication clientContext) { // must have a security layer challenge if (challenge == null) { return(null); } // "unwrap" challenge byte[] input = Convert.FromBase64String(challenge); int len; try { len = clientContext.VerifySignature(input, 0, input.Length); } catch (Win32Exception) { // any decrypt failure is an auth failure return(null); } // Per RFC 2222 Section 7.2.2: // the client should then expect the server to issue a // token in a subsequent challenge. The client passes // this token to GSS_Unwrap and interprets the first // octet of cleartext as a bit-mask specifying the // security layers supported by the server and the // second through fourth octets as the maximum size // output_message to send to the server. // Section 7.2.3 // The security layer and their corresponding bit-masks // are as follows: // 1 No security layer // 2 Integrity protection // Sender calls GSS_Wrap with conf_flag set to FALSE // 4 Privacy protection // Sender calls GSS_Wrap with conf_flag set to TRUE // // Exchange 2007 and our client only support // "No security layer". Therefore verify first byte is value 1 // and the 2nd-4th bytes are value zero since token size is not // applicable when there is no security layer. if (len < 4 || // expect 4 bytes input[0] != 1 || // first value 1 input[1] != 0 || // rest value 0 input[2] != 0 || input[3] != 0) { return(null); } // Continuing with RFC 2222 section 7.2.2: // The client then constructs data, with the first octet // containing the bit-mask specifying the selected security // layer, the second through fourth octets containing in // network byte order the maximum size output_message the client // is able to receive, and the remaining octets containing the // authorization identity. // // So now this contructs the "wrapped" response. The response is // payload is identical to the received server payload and the // "authorization identity" is not supplied as it is unnecessary. // let MakeSignature figure out length of output byte[] output = null; try { len = clientContext.MakeSignature(input, 0, 4, ref output); } catch (Win32Exception) { // any decrypt failure is an auth failure return(null); } // return Base64 encoded string of signed payload return(Convert.ToBase64String(output, 0, len)); }
// internal void ValidateCreateContext( string package, bool isServer, NetworkCredential credential, string servicePrincipalName, ChannelBinding channelBinding, ProtectionLevel protectionLevel, TokenImpersonationLevel impersonationLevel ) { if (_Exception != null && !_CanRetryAuthentication) { throw _Exception; } if (_Context != null && _Context.IsValidContext) { throw new InvalidOperationException(SR.GetString(SR.net_auth_reauth)); } if (credential == null) { throw new ArgumentNullException("credential"); } if (servicePrincipalName == null) { throw new ArgumentNullException("servicePrincipalName"); } if (impersonationLevel != TokenImpersonationLevel.Identification && impersonationLevel != TokenImpersonationLevel.Impersonation && impersonationLevel != TokenImpersonationLevel.Delegation) { throw new ArgumentOutOfRangeException("impersonationLevel", impersonationLevel.ToString(), SR.GetString(SR.net_auth_supported_impl_levels)); } if (_Context != null && IsServer != isServer) { throw new InvalidOperationException(SR.GetString(SR.net_auth_client_server)); } _Exception = null; _RemoteOk = false; _Framer = new StreamFramer(_InnerStream); _Framer.WriteHeader.MessageId = FrameHeader.HandshakeId; _ExpectedProtectionLevel = protectionLevel; _ExpectedImpersonationLevel = isServer? impersonationLevel: TokenImpersonationLevel.None; _WriteSequenceNumber = 0; _ReadSequenceNumber = 0; ContextFlags flags = ContextFlags.Connection; // A workaround for the client when talking to Win9x on the server side if (protectionLevel == ProtectionLevel.None && !isServer) { package = NegotiationInfoClass.NTLM; } else if (protectionLevel == ProtectionLevel.EncryptAndSign) { flags |= ContextFlags.Confidentiality; } else if (protectionLevel == ProtectionLevel.Sign) { // Assuming user expects NT4 SP4 and above flags |= ContextFlags.ReplayDetect | ContextFlags.SequenceDetect | ContextFlags.InitIntegrity; } if (isServer) { if (_ExtendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) { flags |= ContextFlags.AllowMissingBindings; } if (_ExtendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never && _ExtendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy) { flags |= ContextFlags.ProxyBindings; } } else { // According to lzhu server side should not request any of these flags if (protectionLevel != ProtectionLevel.None) { flags |= ContextFlags.MutualAuth; } if (impersonationLevel == TokenImpersonationLevel.Identification) { flags |= ContextFlags.InitIdentify; } if (impersonationLevel == TokenImpersonationLevel.Delegation) { flags |= ContextFlags.Delegate; } } _CanRetryAuthentication = false; // // Security: We used to rely on NetworkCredential class to demand permission // Switched over to explicit ControlPrincipalPermission demand (except for DefaultCredential case) // The mitigated attack is brute-force pasword guessing through SSPI. if (!(credential is SystemNetworkCredential)) { ExceptionHelper.ControlPrincipalPermission.Demand(); } try { // _Context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding); } catch (Win32Exception e) { throw new AuthenticationException(SR.GetString(SR.net_auth_SSPI), e); } }
private static async Task <HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, bool async, ICredentials credentials, bool isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { HttpResponseMessage response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (!isProxyAuth && connection.Kind == HttpConnectionKind.Proxy && !ProxySupportsConnectionAuth(response)) { // Proxy didn't indicate that it supports connection-based auth, so we can't proceed. if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(connection, $"Proxy doesn't support connection-based auth, uri={authUri}"); } return(response); } if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge)) { if (challenge.AuthenticationType == AuthenticationType.Negotiate || challenge.AuthenticationType == AuthenticationType.Ntlm) { bool isNewConnection = false; bool needDrain = true; try { if (response.Headers.ConnectionClose.GetValueOrDefault()) { // Server is closing the connection and asking us to authenticate on a new connection. // First, detach the current connection from the pool. This means it will no longer count against the connection limit. // Instead, it will be replaced by the new connection below. connection.DetachFromPool(); connection = await connectionPool.CreateHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); connection !.Acquire(); isNewConnection = true; needDrain = false; } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Uri: {authUri.AbsoluteUri}"); } // Calculate SPN (Service Principal Name) using the host name of the request. // Use the request's 'Host' header if available. Otherwise, use the request uri. // Ignore the 'Host' header if this is proxy authentication since we need to use // the host name of the proxy itself for SPN calculation. string hostName; if (!isProxyAuth && request.HasHeaders && request.Headers.Host != null) { // Use the host name without any normalization. hostName = request.Headers.Host; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {hostName}"); } } else { // Need to use FQDN normalized host so that CNAME's are traversed. // Use DNS to do the forward lookup to an A (host) record. // But skip DNS lookup on IP literals. Otherwise, we would end up // doing an unintended reverse DNS lookup. UriHostNameType hnt = authUri.HostNameType; if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4) { hostName = authUri.IdnHost; } else { IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost, cancellationToken).ConfigureAwait(false); hostName = result.HostName; } if (!isProxyAuth && !authUri.IsDefaultPort && UsePortInSpn) { hostName = string.Create(null, stackalloc char[128], $"{hostName}:{authUri.Port}"); } } string spn = "HTTP/" + hostName; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, SPN: {spn}"); } ChannelBinding? channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(isServer: false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity, channelBinding); string? challengeData = challenge.ChallengeData; try { while (true) { string?challengeResponse = authContext.GetOutgoingBlob(challengeData); if (challengeResponse == null) { // Response indicated denial even after login, so stop processing and return current response. break; } if (needDrain) { await connection.DrainResponseAsync(response !, cancellationToken).ConfigureAwait(false); } SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) { break; } needDrain = true; } } finally { authContext.CloseContext(); } } finally { if (isNewConnection) { connection !.Release(); } } } } return(response !); }