diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs index 0079762a8907..b3add795f2ef 100644 --- a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs @@ -90,13 +90,20 @@ internal static extern Status AcceptSecContext( ref SafeGssContextHandle acceptContextHandle, byte[] inputBytes, int inputLength, - ref GssBuffer token); + ref GssBuffer token, + out uint retFlags); [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_DeleteSecContext")] internal static extern Status DeleteSecContext( out Status minorStatus, ref IntPtr contextHandle); + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_GetUser")] + internal static extern Status GetUser( + out Status minorStatus, + SafeGssContextHandle acceptContextHandle, + ref GssBuffer token); + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_Wrap")] private static extern Status Wrap( out Status minorStatus, diff --git a/src/Common/src/System/Net/NTAuthentication.Common.cs b/src/Common/src/System/Net/NTAuthentication.Common.cs index 64eb87d98ba2..f47cb85b7d8f 100644 --- a/src/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/Common/src/System/Net/NTAuthentication.Common.cs @@ -292,7 +292,8 @@ internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Secu } // The return value will tell us correctly if the handshake is over or not - if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK) + if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK + || (_isServer && statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded)) { // Success. _isCompleted = true; diff --git a/src/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 7ba998e3b065..1b17c6f15cc6 100644 --- a/src/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -3,14 +3,15 @@ // See the LICENSE file in the project root for more information. using System.IO; -using System.Security; -using System.Security.Principal; -using System.Threading; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Security; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; +using System.Security.Principal; +using System.Text; +using System.Threading; using Microsoft.Win32.SafeHandles; namespace System.Net.Security @@ -124,8 +125,10 @@ private static bool GssInitSecurityContext( // EstablishSecurityContext is called multiple times in a session. // In each call, we need to pass the context handle from the previous call. // For the first call, the context handle will be null. + bool newContext = false; if (context == null) { + newContext = true; context = new SafeGssContextHandle(); } @@ -152,6 +155,11 @@ private static bool GssInitSecurityContext( if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) && (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) { + if (newContext) + { + context.Dispose(); + context = null; + } throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); } @@ -165,6 +173,78 @@ private static bool GssInitSecurityContext( return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; } + private static bool GssAcceptSecurityContext( + ref SafeGssContextHandle context, + byte[] buffer, + out byte[] outputBuffer, + out uint outFlags) + { + bool newContext = false; + if (context == null) + { + newContext = true; + context = new SafeGssContextHandle(); + } + + Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); + Interop.NetSecurityNative.Status status; + + try + { + Interop.NetSecurityNative.Status minorStatus; + status = Interop.NetSecurityNative.AcceptSecContext(out minorStatus, + ref context, + buffer, + buffer?.Length ?? 0, + ref token, + out outFlags); + + if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) && + (status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) + { + if (newContext) + { + context.Dispose(); + context = null; + } + throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); + } + + outputBuffer = token.ToByteArray(); + } + finally + { + token.Dispose(); + } + + return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; + } + + private static string GssGetUser( + ref SafeGssContextHandle context) + { + Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); + + try + { + Interop.NetSecurityNative.Status status + = Interop.NetSecurityNative.GetUser(out var minorStatus, + context, + ref token); + + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); + } + + return Encoding.UTF8.GetString(token.ToByteArray()); + } + finally + { + token.Dispose(); + } + } + private static SecurityStatusPal EstablishSecurityContext( SafeFreeNegoCredentials credential, ref SafeDeleteContext context, @@ -293,7 +373,61 @@ internal static SecurityStatusPal AcceptSecurityContext( ref byte[] resultBlob, ref ContextFlagsPal contextFlags) { - throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); + if (securityContext == null) + { + securityContext = new SafeDeleteNegoContext((SafeFreeNegoCredentials)credentialsHandle); + } + + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext; + try + { + SafeGssContextHandle contextHandle = negoContext.GssContext; + bool done = GssAcceptSecurityContext( + ref contextHandle, + incomingBlob, + out resultBlob, + out uint outputFlags); + + Debug.Assert(resultBlob != null, "Unexpected null buffer returned by GssApi"); + Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); + + // Save the inner context handle for further calls to NetSecurity + Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); + if (null == negoContext.GssContext) + { + negoContext.SetGssContext(contextHandle); + } + + contextFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop( + (Interop.NetSecurityNative.GssFlags)outputFlags, isServer: true); + + SecurityStatusPalErrorCode errorCode = done ? + (negoContext.IsNtlmUsed && resultBlob.Length > 0 ? SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.CompleteNeeded) : + SecurityStatusPalErrorCode.ContinueNeeded; + + return new SecurityStatusPal(errorCode); + } + catch (Exception ex) + { + if (NetEventSource.IsEnabled) NetEventSource.Error(null, ex); + return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, ex); + } + } + + private static string GetUser( + ref SafeDeleteContext securityContext) + { + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext; + try + { + SafeGssContextHandle contextHandle = negoContext.GssContext; + return GssGetUser(ref contextHandle); + } + catch (Exception ex) + { + if (NetEventSource.IsEnabled) NetEventSource.Error(null, ex); + throw; + } } internal static Win32Exception CreateExceptionFromError(SecurityStatusPal statusCode) @@ -314,11 +448,6 @@ internal static SafeFreeCredentials AcquireDefaultCredential(string package, boo internal static SafeFreeCredentials AcquireCredentialsHandle(string package, bool isServer, NetworkCredential credential) { - if (isServer) - { - throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); - } - bool isEmptyCredential = string.IsNullOrWhiteSpace(credential.UserName) || string.IsNullOrWhiteSpace(credential.Password); bool ntlmOnly = string.Equals(package, NegotiationInfoClass.NTLM, StringComparison.OrdinalIgnoreCase); diff --git a/src/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs b/src/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs index 58cc7d68bb83..935ca3786413 100644 --- a/src/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs +++ b/src/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs @@ -32,10 +32,15 @@ public SafeGssContextHandle GssContext get { return _context; } } - public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetName) + public SafeDeleteNegoContext(SafeFreeNegoCredentials credential) : base(credential) { Debug.Assert((null != credential), "Null credential in SafeDeleteNegoContext"); + } + + public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetName) + : this(credential) + { try { // Convert any "SERVICE/HOST" style of targetName to use "SERVICE@HOST" style. diff --git a/src/Common/tests/System/Net/Capability.Security.cs b/src/Common/tests/System/Net/Capability.Security.cs index 5280f554731b..009a9cd9c377 100644 --- a/src/Common/tests/System/Net/Capability.Security.cs +++ b/src/Common/tests/System/Net/Capability.Security.cs @@ -27,6 +27,12 @@ public static bool IsDomainAvailable() return !string.IsNullOrWhiteSpace(Configuration.Security.ActiveDirectoryName); } + public static bool IsNegotiateClientAvailable() + { + return !(Configuration.Security.NegotiateClient == null) + && !(Configuration.Security.NegotiateClientUser == null); + } + public static bool IsNegotiateServerAvailable() { return !(Configuration.Security.NegotiateServer == null); diff --git a/src/Common/tests/System/Net/Configuration.Security.cs b/src/Common/tests/System/Net/Configuration.Security.cs index 91344ce760a7..b1e213bd8a6c 100644 --- a/src/Common/tests/System/Net/Configuration.Security.cs +++ b/src/Common/tests/System/Net/Configuration.Security.cs @@ -22,6 +22,10 @@ public static partial class Security public static Uri NegotiateServer => GetUriValue("COREFX_NET_SECURITY_NEGOSERVERURI"); + public static Uri NegotiateClient => GetUriValue("COREFX_NET_SECURITY_NEGOCLIENTURI"); + + public static string NegotiateClientUser => GetValue("COREFX_NET_SECURITY_NEGOCLIENTUSER"); + public static string TlsRenegotiationServer => GetValue("COREFX_NET_SECURITY_TLSREGOTIATIONSERVER", "corefx-net-tls.azurewebsites.net"); // This should be set if hostnames for all certificates within corefx-testdata have been set to point to 127.0.0.1. diff --git a/src/Common/tests/System/Threading/Tasks/TaskTimeoutExtensions.cs b/src/Common/tests/System/Threading/Tasks/TaskTimeoutExtensions.cs index c4c15187a8cd..734b57e9544c 100644 --- a/src/Common/tests/System/Threading/Tasks/TaskTimeoutExtensions.cs +++ b/src/Common/tests/System/Threading/Tasks/TaskTimeoutExtensions.cs @@ -25,33 +25,39 @@ public static async Task WithCancellation(this Task task, CancellationToken canc } } - public static async Task TimeoutAfter(this Task task, int millisecondsTimeout) + public static Task TimeoutAfter(this Task task, int millisecondsTimeout) + => task.TimeoutAfter(TimeSpan.FromMilliseconds(millisecondsTimeout)); + + public static async Task TimeoutAfter(this Task task, TimeSpan timeout) { var cts = new CancellationTokenSource(); - if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout, cts.Token)).ConfigureAwait(false)) + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false)) { cts.Cancel(); await task.ConfigureAwait(false); } else { - throw new TimeoutException($"Task timed out after {millisecondsTimeout}ms"); + throw new TimeoutException($"Task timed out after {timeout}"); } } - public static async Task TimeoutAfter(this Task task, int millisecondsTimeout) + public static Task TimeoutAfter(this Task task, int millisecondsTimeout) + => task.TimeoutAfter(TimeSpan.FromMilliseconds(millisecondsTimeout)); + + public static async Task TimeoutAfter(this Task task, TimeSpan timeout) { var cts = new CancellationTokenSource(); - if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout, cts.Token)).ConfigureAwait(false)) + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false)) { cts.Cancel(); return await task.ConfigureAwait(false); } else { - throw new TimeoutException($"Task timed out after {millisecondsTimeout}ms"); + throw new TimeoutException($"Task timed out after {timeout}"); } } diff --git a/src/Native/Unix/System.Net.Security.Native/pal_gssapi.c b/src/Native/Unix/System.Net.Security.Native/pal_gssapi.c index 8c614d3fab71..926cd4e64f23 100644 --- a/src/Native/Unix/System.Net.Security.Native/pal_gssapi.c +++ b/src/Native/Unix/System.Net.Security.Native/pal_gssapi.c @@ -247,7 +247,8 @@ uint32_t NetSecurityNative_AcceptSecContext(uint32_t* minorStatus, GssCtxId** contextHandle, uint8_t* inputBytes, uint32_t inputLength, - PAL_GssBuffer* outBuffer) + PAL_GssBuffer* outBuffer, + uint32_t* retFlags) { assert(minorStatus != NULL); assert(contextHandle != NULL); @@ -266,7 +267,7 @@ uint32_t NetSecurityNative_AcceptSecContext(uint32_t* minorStatus, NULL, NULL, &gssBuffer, - 0, + retFlags, NULL, NULL); @@ -274,6 +275,44 @@ uint32_t NetSecurityNative_AcceptSecContext(uint32_t* minorStatus, return majorStatus; } +uint32_t NetSecurityNative_GetUser(uint32_t* minorStatus, + GssCtxId* contextHandle, + PAL_GssBuffer* outBuffer) +{ + assert(minorStatus != NULL); + assert(contextHandle != NULL); + assert(outBuffer != NULL); + + gss_name_t srcName = GSS_C_NO_NAME; + + uint32_t majorStatus = gss_inquire_context(minorStatus, + contextHandle, + &srcName, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + + if (majorStatus == GSS_S_COMPLETE) + { + GssBuffer gssBuffer = {.length = 0, .value = NULL}; + majorStatus = gss_display_name(minorStatus, srcName, &gssBuffer, NULL); + if (majorStatus == GSS_S_COMPLETE) + { + NetSecurityNative_MoveBuffer(&gssBuffer, outBuffer); + } + } + + if (srcName != NULL) + { + majorStatus = gss_release_name(minorStatus, &srcName); + } + + return majorStatus; +} + uint32_t NetSecurityNative_ReleaseCred(uint32_t* minorStatus, GssCredId** credHandle) { assert(minorStatus != NULL); diff --git a/src/Native/Unix/System.Net.Security.Native/pal_gssapi.h b/src/Native/Unix/System.Net.Security.Native/pal_gssapi.h index 7ebbf3dc8956..e3cf72fb4cc3 100644 --- a/src/Native/Unix/System.Net.Security.Native/pal_gssapi.h +++ b/src/Native/Unix/System.Net.Security.Native/pal_gssapi.h @@ -124,7 +124,8 @@ DLLEXPORT uint32_t NetSecurityNative_AcceptSecContext(uint32_t* minorStatus, GssCtxId** contextHandle, uint8_t* inputBytes, uint32_t inputLength, - PAL_GssBuffer* outBuffer); + PAL_GssBuffer* outBuffer, + uint32_t* retFlags); /* @@ -167,3 +168,10 @@ DLLEXPORT uint32_t NetSecurityNative_InitiateCredWithPassword(uint32_t* minorSta Shims the gss_indicate_mechs method to detect if NTLM mech is installed. */ DLLEXPORT uint32_t NetSecurityNative_IsNtlmInstalled(void); + +/* +Shims gss_inquire_context and gss_display_name to get the remote user principal name. +*/ +DLLEXPORT uint32_t NetSecurityNative_GetUser(uint32_t* minorStatus, + GssCtxId* contextHandle, + PAL_GssBuffer* outBuffer); diff --git a/src/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 1232bfc0571b..ab7a245e4e16 100644 --- a/src/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -3,14 +3,15 @@ // See the LICENSE file in the project root for more information. using System.IO; -using System.Security; -using System.Security.Principal; -using System.Threading; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Security; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading; using Microsoft.Win32.SafeHandles; namespace System.Net.Security @@ -24,11 +25,19 @@ internal static partial class NegotiateStreamPal { internal static IIdentity GetIdentity(NTAuthentication context) { - Debug.Assert(!context.IsServer, "GetIdentity: Server is not supported"); - string name = context.Spn; string protocol = context.ProtocolName; + if (context.IsServer) + { + var safeContext = context.GetContext(out var status); + if (status.ErrorCode != SecurityStatusPalErrorCode.OK) + { + throw new Win32Exception((int)status.ErrorCode); + } + name = GetUser(ref safeContext); + } + return new GenericIdentity(name, protocol); } diff --git a/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamKerberosTest.cs b/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamKerberosTest.cs index be31a9948b9c..23f31e354270 100644 --- a/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamKerberosTest.cs +++ b/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamKerberosTest.cs @@ -3,12 +3,16 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.Linq; using System.Net.Sockets; using System.Net.Test.Common; using System.Security; using System.Security.Authentication; +using System.Security.Principal; using System.Text; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -21,6 +25,9 @@ public class NegotiateStreamKerberosTest { public static bool IsServerAndDomainAvailable => Capability.IsDomainAvailable() && Capability.IsNegotiateServerAvailable(); + + public static bool IsClientAvailable => + Capability.IsNegotiateClientAvailable(); public static IEnumerable GoodCredentialsData { @@ -92,22 +99,22 @@ public static IEnumerable BadCredentialsData [OuterLoop] [ConditionalTheory(nameof(IsServerAndDomainAvailable))] [MemberData(nameof(GoodCredentialsData))] - public async Task NegotiateStream_AuthenticationRemote_Success(object credentialObject) + public async Task NegotiateStream_ClientAuthenticationRemote_Success(object credentialObject) { var credential = (NetworkCredential)credentialObject; - await VerifyAuthentication(credential); + await VerifyClientAuthentication(credential); } [OuterLoop] [ConditionalTheory(nameof(IsServerAndDomainAvailable))] [MemberData(nameof(BadCredentialsData))] - public async Task NegotiateStream_AuthenticationRemote_Fails(object credentialObject) + public async Task NegotiateStream_ClientAuthenticationRemote_Fails(object credentialObject) { var credential = (NetworkCredential)credentialObject; - await Assert.ThrowsAsync(() => VerifyAuthentication(credential)); + await Assert.ThrowsAsync(() => VerifyClientAuthentication(credential)); } - private async Task VerifyAuthentication(NetworkCredential credential) + private async Task VerifyClientAuthentication(NetworkCredential credential) { string serverName = Configuration.Security.NegotiateServer.Host; int port = Configuration.Security.NegotiateServer.Port; @@ -115,7 +122,7 @@ private async Task VerifyAuthentication(NetworkCredential credential) bool isLocalhost = await IsLocalHost(serverName); string expectedAuthenticationType = "Kerberos"; - bool mutualAuthenitcated = true; + bool mutuallyAuthenticated = true; if (credential == CredentialCache.DefaultNetworkCredentials && isLocalhost) { @@ -126,7 +133,7 @@ private async Task VerifyAuthentication(NetworkCredential credential) { // Anonymous authentication. expectedAuthenticationType = "NTLM"; - mutualAuthenitcated = false; + mutuallyAuthenticated = false; } using (var client = new TcpClient()) @@ -147,7 +154,7 @@ await auth.AuthenticateAsClientAsync( Assert.Equal(true, auth.IsAuthenticated); Assert.Equal(true, auth.IsEncrypted); - Assert.Equal(mutualAuthenitcated, auth.IsMutuallyAuthenticated); + Assert.Equal(mutuallyAuthenticated, auth.IsMutuallyAuthenticated); Assert.Equal(true, auth.IsSigned); // Send a message to the server. Encode the test data into a byte array. @@ -157,6 +164,50 @@ await auth.AuthenticateAsClientAsync( } } + [OuterLoop] + [ConditionalFact(nameof(IsClientAvailable))] + public async Task NegotiateStream_ServerAuthenticationRemote_Success() + { + string expectedUser = Configuration.Security.NegotiateClientUser; + + string expectedAuthenticationType = "Kerberos"; + bool mutuallyAuthenticated = true; + + using (var controlClient = new TcpClient()) + { + string clientName = Configuration.Security.NegotiateClient.Host; + int clientPort = Configuration.Security.NegotiateClient.Port; + await controlClient.ConnectAsync(clientName, clientPort) + .TimeoutAfter(TimeSpan.FromSeconds(15)); + var serverStream = controlClient.GetStream(); + + using (var serverAuth = new NegotiateStream(serverStream, leaveInnerStreamOpen: false)) + { + await serverAuth.AuthenticateAsServerAsync( + CredentialCache.DefaultNetworkCredentials, + ProtectionLevel.EncryptAndSign, + TokenImpersonationLevel.Identification) + .TimeoutAfter(TimeSpan.FromSeconds(15)); + + Assert.True(serverAuth.IsAuthenticated, "IsAuthenticated"); + Assert.True(serverAuth.IsEncrypted, "IsEncrypted"); + Assert.True(serverAuth.IsSigned, "IsSigned"); + Assert.Equal(mutuallyAuthenticated, serverAuth.IsMutuallyAuthenticated); + + Assert.Equal(expectedAuthenticationType, serverAuth.RemoteIdentity.AuthenticationType); + Assert.Equal(expectedUser, serverAuth.RemoteIdentity.Name); + + // Receive a message from the client. + var message = "Hello from the client."; + using (var reader = new StreamReader(serverAuth)) + { + var response = await reader.ReadToEndAsync().TimeoutAfter(TimeSpan.FromSeconds(15)); + Assert.Equal(message, response); + } + } + } + } + private async static Task IsLocalHost(string hostname) { IPAddress[] hostAddresses = await Dns.GetHostAddressesAsync(hostname); diff --git a/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs b/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs index 20d21fa375a9..05ad57572dae 100644 --- a/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs +++ b/src/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs @@ -13,9 +13,11 @@ namespace System.Net.Security.Tests { - [PlatformSpecific(TestPlatforms.Windows)] // NegotiateStream only supports client-side functionality on Unix + [PlatformSpecific(TestPlatforms.Windows)] // NegotiateStream client needs explicit credentials or SPNs on unix. public abstract class NegotiateStreamStreamToStreamTest { + public static bool IsNtlmInstalled => Capability.IsNtlmInstalled(); + private const int PartialBytesToRead = 5; private static readonly byte[] s_sampleMsg = Encoding.UTF8.GetBytes("Sample Test Message"); @@ -26,7 +28,7 @@ public abstract class NegotiateStreamStreamToStreamTest protected abstract Task AuthenticateAsClientAsync(NegotiateStream client, NetworkCredential credential, string targetName); protected abstract Task AuthenticateAsServerAsync(NegotiateStream server); - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] public async Task NegotiateStream_StreamToStream_Authentication_Success() { VirtualNetwork network = new VirtualNetwork(); @@ -76,7 +78,7 @@ public async Task NegotiateStream_StreamToStream_Authentication_Success() } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] public async Task NegotiateStream_StreamToStream_Authentication_TargetName_Success() { string targetName = "testTargetName"; @@ -132,7 +134,7 @@ public async Task NegotiateStream_StreamToStream_Authentication_TargetName_Succe } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, ".NET Core difference in behavior: https://github.com/dotnet/corefx/issues/5241")] public async Task NegotiateStream_StreamToStream_Authentication_EmptyCredentials_Fails() { @@ -195,7 +197,7 @@ public async Task NegotiateStream_StreamToStream_Authentication_EmptyCredentials } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] public async Task NegotiateStream_StreamToStream_Successive_ClientWrite_Sync_Success() { byte[] recvBuf = new byte[s_sampleMsg.Length]; @@ -234,7 +236,7 @@ public async Task NegotiateStream_StreamToStream_Successive_ClientWrite_Sync_Suc } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] public async Task NegotiateStream_StreamToStream_Successive_ClientWrite_Async_Success() { byte[] recvBuf = new byte[s_sampleMsg.Length]; @@ -273,7 +275,7 @@ public async Task NegotiateStream_StreamToStream_Successive_ClientWrite_Async_Su } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] public async Task NegotiateStream_ReadWriteLongMsgSync_Success() { byte[] recvBuf = new byte[s_longMsg.Length]; @@ -300,7 +302,7 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] public async Task NegotiateStream_ReadWriteLongMsgAsync_Success() { byte[] recvBuf = new byte[s_longMsg.Length]; @@ -327,7 +329,7 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] public void NegotiateStream_StreamToStream_Flush_Propagated() { VirtualNetwork network = new VirtualNetwork(); @@ -341,7 +343,7 @@ public void NegotiateStream_StreamToStream_Flush_Propagated() } } - [Fact] + [ConditionalFact(nameof(IsNtlmInstalled))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Relies on FlushAsync override not available in desktop")] public void NegotiateStream_StreamToStream_FlushAsync_Propagated() { diff --git a/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index 5e2b1dc4478b..28c2f8178700 100644 --- a/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -107,9 +107,21 @@ + + Common\System\Net\Capability.Security.Windows.cs + + + Common\System\Net\Capability.Security.Unix.cs + + + Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs + + + Common\Interop\Unix\Interop.Libraries.cs + @@ -130,9 +142,6 @@ - - Common\Interop\Unix\Interop.Libraries.cs - Common\Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs