Open Charlieface opened 1 year ago
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.
Tagging subscribers to this area: @dotnet/ncl, @vcsjones See info in area-owners.md if you want to be subscribed.
Author: | Charlieface |
---|---|
Assignees: | - |
Labels: | `api-suggestion`, `area-System.Net.Security`, `untriaged` |
Milestone: | - |
ability to pin Kerberso seems reasonable to me. Any thoughts on this @filipnavara ?
One the cancellation, I'm not sure if that is really doable. While waiting in GSSAPI call, I'm not sure we have any good way how to interrupt it. So even if we add it it may do nothing as cancellation is best effort.
What would you expect on the wire? Plain Kerberos or Kerberos wrapped in Negotiate? IIRC the first one would violate the specification for the wire format, in addition to being incompatible with older clients.
We currently don't have API to specify that you want Negotiate with specific algorithms. It may be a useful API request on its own.
If you want to use Kerberos directly and thus change the wire format then I would argue that using NegotiateStream in new software may not be the best option.
True you can't at the moment do an async Kerberos request but the actual negotiation between the parties is just a standard WriteAsync and ReadAsync, fully cancellable. Is it wrong to have a CancellationToken
which only works partly? I recall Dns.GetHostEntry
had a similar issue for a long time until it was fixed.
Looking at the code, it seems it's using InitializeSecurityContextAsyncW
instead of SspiInitializeSecurityContextAsyncW
, the latter appears to be asynchronous, although there does not appear to be any cancellation function
What would you expect on the wire? Plain Kerberos or Kerberos wrapped in Negotiate? IIRC the first one would violate the specification for the wire format, in addition to being incompatible with older clients.
We currently don't have API to specify that you want Negotiate with specific algorithms. It may be a useful API request on its own.
If you want to use Kerberos directly and thus change the wire format then I would argue that using NegotiateStream in new software may not be the best option.
Does it actually change the wire format? Does it not just end up with Kerberos being negotiated as the only protocol?
My testing (using some rather nasty reflection to change the packageName
) seems to work when one side specifies Kerberos and the other Negotiate. I can maybe put up a dotnetfiddle to show it.
In my opinion, supporting Negotiate w/ Kerberos (ie. explicitly disabling NTLM) would make sense but we currently lack the underlying API for that. That said, there is already system policy for disabling NTLM, and given how Windows Authentication is tied to deployment it seems that for most scenarios it may be good enough to disable NTLM through global policies.
Does it actually change the wire format? Does it not just end up with Kerberos being negotiated as the only protocol?
Yes, it does. The old NTAuthentication
and the new public NegotiateAuthentication
can do Negotiate, raw Kerberos, or raw NTLM. It currently doesn't allow Negotiate with specific algorithm.
My testing (using some rather nasty reflection to change the packageName) seems to work when one side specifies Kerberos and the other Negotiate.
It may work due to some compatibility hacks. For example, raw NTLM is often accepted in Negotiate contexts. It's still not ideal and probably violates the on-the-wire specification.
One the cancellation, I'm not sure if that is really doable. While waiting in GSSAPI call, I'm not sure we have any good way how to interrupt it.
Correct. The GSSAPI ticket acquisition is not really cancellable and can block for few seconds (depending on system settings).
Looking at the code, it seems it's using
InitializeSecurityContextAsyncW
instead ofSspiInitializeSecurityContextAsyncW
, the latter appears to be asynchronous, although there does not appear to be any cancellation function
SSPI (Windows) has async APIs, GSSAPI (Linux, macOS) does not. I never tried to work with them though.
The GSSAPI spec is actually quite vague as I recall. All of the packages are GSSAPI-compatible. Whereas SPNEGO is a specific package, which internally negotiates which one to use. So is there a blocking reason why not to make available a raw GSSAPI stream (i.e. where you can specify the package)? Putting it another way: NegotiateStream
should have originally been designed to inherit from a generic GssApiStream
anyway, as it's an outgrowth of that.
As far as the async APIs are concerned: currently a caller will expect ticket acquisition to happen asynchronously, as part of the whole AuthenticateAs
but it does not. So essentially it's a bug. That we cannnot fix it in Linux shouldn't prevent fixing it in Windows to use the async APIs.
I'm supportive of exposing a way to allow callers to choose which package they want to use for authentication, but I would caution you on using this as a mechanism for excluding packages like NTLM by doing something like explicitly forcing Kerberos. The correct way to do that is to inform the negotiate package to behave as normal but exclude specific packages. This is done through the use of parameters on the credential when setting up the context, specifically with structures like SEC_WINNT_AUTH_IDENTITY_EX2
: https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_ex2
If you want to continue using negotiate (as one should in 95% of all cases) but want to exclude NTLM, you can pass !NTLM
in the PackageList**
fields.
I would also recommend having a different class name. If you're specifying a package other than negotiate
, by definition you aren't negotiating.
Sorry for the double post, but also the usage of something like SEC_WINNT_AUTH_IDENTITY_EX2
is not dependent on the usage of supplied credentials. You can pass this struct with null username/password/domain fields and it'll treat that as instructions to use the current SSO creds.
I admit it's much more appealing to have an exclusion rather than inclusion list, but the amount of engineering is going to be immense. We'd need to change the following classes and interfaces to feed that data down to the PInvoke calls: NTAuthentication
, NegotiateStreamPal
, SSPIAuthType
, ISSPIInterface
and SafeFreeCredentials
. It may be worth it or it may not, Note that some of those classes have separate Windows, Unix and Managed implementations, complicating it even further.
If this is going to be too much effort then perhaps it would make sense to derive another class KerberosStream
, with the only change being the package name. Maybe even split off a base abstract class below NegotiateStream
and have both derive from that (I understand that shouldn't be a problem as far as breaking changes are concerned).
As far as the CancellationToken
is concerned, are there any other concerns beyond what is mentioned? I hadn't originally considered using the async SSPI functions, I was only considering the handshake between the parties, which is much more likely to be over a poor/unstable connection than a link to a DC, which is normally a short round-trip to a beefy server. But adding support for async SSPI sounds like a good idea also.
I'm thinking something like this:
// rename current NegotiateStream class
public abstract class GssStream : AuthenticatedStream
{
public abstract string DefaultPackage { get; }
// etc
}
public class NegotiateStream : GssStream
{
public override string DefaultPackage => NegotiationInfoClass.Negotiate;
}
public class KerberosStream : GssStream
{
public override string DefaultPackage => NegotiationInfoClass.Kerberos;
}
An alternative might be to allow setting the value in the base class, and override it in NegotiateStream
// rename current NegotiateStream class
public class GssStream : AuthenticatedStream
{
public string DefaultPackage { get; set; } = NegotiationInfoClass.Negotiate;
// etc
}
public class NegotiateStream : GssStream
{
public override string DefaultPackage
{
get => return NegotiationInfoClass.Negotiate;
set => throw new InvalidOperationException("Changing package not allowed");
}
}
Triage: not critical for 8.0, putting to future for now.
Triage: not critical for 8.0, putting to future for now.
The cancellationToken
part seems to be pretty trivial, does it not make sense to prioritize just that part?
The networking team is a bit short-staffed atm. We might get to this in 8.0, but we are not committing to it. This may change depending on the demand for the feature.
Adding cancellation that only work on Windows seems weird to me. I feel that would send wrong message to developers who consume this x-plat. I feel we should investigate more before making decision and proposal for public API.
Adding cancellation that only work on Windows seems weird to me. I feel that would send wrong message to developers who consume this x-plat. I feel we should investigate more before making decision and proposal for public API.
Like I said upthread: cancellation would work as far as cancelling the handshake is concerned, even on Linux. It only doesn't work for acquiring the token, which is less of a concern (because of fast Kerberos responses), and would need quite a bit of extra work even for Windows. Cancelling the handshake is a very easy win here because the token can be passed straight in, all the underlying code is there. Whether that is unexpected I don't know: we've certainly had many async APIs in the past that have blocking sections, and we still do (File.ReadAllBytesAsync
for example, which blocks on opening the file).
Any case, a simple hack for cancelling token acquisition until a better option is available: just hand off the acquisition to the threadpool, and do a cancellable wait on it. Ugly but works.
Background and motivation
NegotiateStream currently attempts Authentication using only the Negotiate protocol i.e. it will only (at least on Windows) select between Kerberos and NTLM, with no way to force only one, or to use any other package.
The underlying SSPI provider does allow it though, and the internal
System.Net.NTAuthentication
class is set up to do so, so it would be trivial to add such functionality by simply passing through another parameter fromAuthenticateAsClient
andAuthenticateAsServer
.Forcing Kerberos is something that may be essential for some applications, as NTLM has many known issues, such as Pass-the-Hash, replay attacks, and low hash cracking time. Many applications may want to disallow NTLM altogether.
We may want to also add a static function that can retrieve a list of possible security packages, as well as making the
DefaultPackage
apublic readonly
property rather than aprivate const
.Most of the work for this is done already.
Another parameter that should be added is
CancellationToken
. There is currently no way to pass one through when authenticating, which means an authentication could block indefinitely. But the async work onNegotiateStream
related to issue #36572 has now enabled this functionality on the internalA/SyncReadWriterAdapter
classes. So again, this could be trivially solved by passing through an extra parameter.API Proposal
For brevity I have not added the old APM Begin... functions.
For listing available packages, most of the work has already been done in
Interop.SSPIWrapper
. It's primarily a matter of exposing it, as well as theSecurityPackageInfoClass
, in a public API. Do we want to just expose the names, or do we want to expose the other data also?API Usage
Usage should be pretty obvious.
Alternative Designs
The number of overloads has now increased significantly, because both server and client have sync, async Task and async APM functions, and we have added two parameters.
I have narrowed this down by using an optional parameter for
cancellationToken
, as this covers all cases in the smallest extra code. We could alternately do:packageName
instead. Needs thinking about which of them is more likely to be used.packageName
, and overload forpackageName
andcancellationToken
, alternately v.v.Risks
Do we want to expose such functionality at all? Is a
NegotiateStream
by definition only able to use Negotiate?It seems all of the code should be identical otherwise, so there is little risk in adding this. Perhaps we only want to allow Kerberos, NTLM and Negotiate as possible options.
Needs investigating: are there any packages we want to specifically disallow, because the responses they give or the way their state machine runs are not compatible with current code?