-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Expose SessionKey on NegotiateAuthentication #111099
Comments
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones |
Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones |
The request generally sounds reasonable. In the managed NTLM implementation we have the key calculated as |
@filipnavara do you have any thoughts as to whether it should a |
I generally prefer Spans. We already have some prior art in There are two things we need to consider - performance and security. I am only concerned with the buffer reuse for repetitive calls. This one seems more like a one-off and saving a few allocations on this code path is not going to make a big difference. On the other hand, the key is a sensitive material. With Spans you can place it in non-relocatable memory (such as (I wish we had |
Could another option be something similar to the string.Create with the SpanAction overload, in this case providing a ReadOnlySpanAction. That way the action is provided with a I’m unsure if that’s overkill but it does seem like it solves the performance, secret, and not knowing the key size problem. |
I am not sure I like it from API design standpoint (delegate overhead, etc.). Perhaps @bartonjs can point us to some guidance. From the security point of view it's tempting though. We can avoid copying the key around and we can enforce that the memory gets cleared after use. |
Logically it seems like the "I'll let you see a ReadOnlySpan of my data" Encode overload we added to AsnWriter: #75759 Here it'd probably be something like public TReturn DeriveKeyFromSessionKey<TReturn>(Func<ReadOnlySpan<byte>, TReturn> keyDerivationFunction);
public TReturn DeriveKeyFromSessionKey<TState, TReturn>(Func<ReadOnlySpan<byte>, TState, TReturn> keyDerivationFunction, TState state); might need some sort of |
Just in case I'm wrong is the main difference between ReadOnlySpanAction and what you are proposing is that yours allows a return value from the delegate?
AFAIK all the implemented negotiate protocols and C APIs support this functionality so I don't think it would add too much benefit. |
It looked like the SMB thing would be something like using SP800108HmacCounterKdf smb3kdf = a.DeriveKeyFromSessionKey(key => new(key, HashAlgorithmName.SHA256));
Span<byte> signingKey = stackallock byte[16];
smb3kdf.DeriveKey(
"SMBSigningKey\0"u8,
preauthHash, // Hash of preceding SMB messages
signingKey); Or to keep it more enclosed: Span<byte> signingKey = stackallock byte[16];
a.DeriveKeyFromSessionKey(
static (key, state) =>
{
using SP800108HmacCounterKdf smb3kdf = a.DeriveKeyFromSessionKey(key, HashAlgorithmName.SHA256);
smb3Kdf.DeriveKey("SMBSigningKey\0"u8, state.Item1, state.Item2);
return 0;
},
(preauthHash, signingKey)); For the later, Action would be nicer, so maybe you want to overload it for both Func and Action. |
Yea in the SMB case I think having the Just for clarity the proposal would be to add the following? public void DeriveKeyFromSessionKey(Action<ReadOnlySpan<byte>> keyDerivationFunction);
public void DeriveKeyFromSessionKey<TState>(Action<ReadOnlySpan<byte>, TState> keyDerivationFunction, TState state);
public TReturn DeriveKeyFromSessionKey<TReturn>(Func<ReadOnlySpan<byte>, TReturn> keyDerivationFunction);
public TReturn DeriveKeyFromSessionKey<TState, TReturn>(Func<ReadOnlySpan<byte>, TState, TReturn> keyDerivationFunction, TState state); Does it make sense to have the first overload, Action without a |
I can certainly imagine people using it with captures... public void DeriveKeyFromSessionKey(Action<ReadOnlySpan<byte>> keyDerivationFunction) =>
DeriveKeyFromSessionKey(static (key, state) => state(key), keyDerivationFunction);
public void DeriveKeyFromSessionKey<TState>(Action<ReadOnlySpan<byte>, TState> keyDerivationFunction, TState state) =>
DeriveKeyFromSessionKey(
static (key, state)
{
state.Kdf(key, state.State);
return 0;
},
(Kdf: keyDerivationFunction, State: state));
public TReturn DeriveKeyFromSessionKey<TReturn>(Func<ReadOnlySpan<byte>, TReturn> keyDerivationFunction) =>
DeriveKeyFromSessionKey(static (key, state) => state(key), keyDerivationFunction);
public TReturn DeriveKeyFromSessionKey<TState, TReturn>(Func<ReadOnlySpan<byte>, TState, TReturn> keyDerivationFunction, TState state)
{
// OK, this one needs to do something :)
} |
Cool, I'll update the original post with that proposal. I'm also happy to try and look at implementing this unless @filipnavara or anything else wants to hold off or do it themselves. |
I'm fine with the proposal assuming we can get it working on at least Windows & Linux without much hassle. Once we agree on the shape I can push it through the review process. |
Background and motivation
The NegotiateAuthentication class is a nice manager wrapper around SSPI/GSSAPI and deals with all the API differences and platform quirks to be able to authenticate using Negotiate/Kerberos/NTLM auth and also sign/seal messages after authentication. Some protocols do not use the builtin signing/sealing mechanisms that is done through SSPI/GSSAPI but instead uses the GSSAPI "session key". An example of this is SMB where after authenticating it derives the encryption/signing key from that key https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/7fd079ca-17e6-4f02-8449-46b606ea289c
If I want to implement my own SMB, or other protocol, implementation that uses this key in .NET I cannot use
NegotiateAuthentication
because it doesn't expose any public way of retrieving the session key. I'll have to setup my own PInvokes and call GSSAPI/SSPI myself.To retrieve this key on SSPI you would call QueryContextAttributesW with
ulAttribute
beingSECPKG_ATTR_SESSION_KEY
(9
) and apBuffer
being a pointer to SecPkgContext_SessionKey. Once copied or no longer needed theSecPkgContext_SessionKey.SessionKey
is passed to FreeContextBuffer to free the memory.To retrieve this key on GSSAPI you would call gss_inquire_sec_context_by_oid with the OID
GSS_C_INQ_SSPI_SESSION_KEY
(1.2.840.113554.1.2.2.5.5
). Once copied or no longer needed thedata_set
is passed to gss_release_buffer_set to free the memory. Whilegss_inquire_sec_context_by_oid
is an extension method it has been part of the three major GSSAPI implementations since:API Proposal
API Usage
Alternative Designs
A potential alternative is to have the caller pass in a
Span<byte>
and instead of returning thebyte[]
the code copies the session key to the provided span if large enough.A downside to this approach is that I'm unsure if the key size can be queried from GSSAPI/SSPI and that the sizes may change depending on the environment/protocol used. For example NTLM will always stay at 16 bytes but Kerberos is based on the etype used, i.e 32 bytes for an AES256 based etype but 16 bytes for an AES128 based etype.
Risks
The main risk is really just expanding the support area of SSPI/GSSAPI. The documentation should stress that this key is not normally meant to be used directly and is exposed for compatibility with implementations that require this key like SMB.
The text was updated successfully, but these errors were encountered: