Skip to content

gss_wrap/gss_unwrap should work on connection without NEGOTIATE_SEAL #77

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

Closed
filipnavara opened this issue Jun 27, 2022 · 10 comments · Fixed by #78
Closed

gss_wrap/gss_unwrap should work on connection without NEGOTIATE_SEAL #77

filipnavara opened this issue Jun 27, 2022 · 10 comments · Fixed by #78

Comments

@filipnavara
Copy link

filipnavara commented Jun 27, 2022

Let's start with a bit of a background.

RFC 2222 originally specified the GSSAPI SASL mechanism for authentication. It didn't specifically tie the mechanism to Kerberos unlike the later RFC 4752 revision. As a last step of the authentication exchange the server sends a token with supported protection schemes and maximum token size to the client. This is encoded with GSS_Wrap API with conf_flag == 0. The client decodes it with GSS_Unwrap, chooses the preferred protection for rest of communication, and then encodes the reply with GSS_Wrap and sends it back to server.

Microsoft Exchange implements this mechanism for authentication to SMTP, IMAP and POP3 services. It works with Negotiate, Kerberos and NTLM protocols. The NTLM one is particularly tricky because the semantics for GSS_Warp/GSS_Unwrap are not fully covered by the specifications. So I went ahead, set up an Exchange server with Windows AD and tested what is really sent on the wire.


In case NEGOTIATE_SEAL is negotiated in the initial authentication the gss-ntlmssp library is already interoperable. The conf_flag is ignored (not a surprise since NTLM doesn't have a way to transmit the flag on the wire like Kerberos does), sealing and signing is applied.

In case NEGOTIATE_SEAL is NOT negotiated and NEGOTIATE_SIGN+NEGOTIATE_KEY_EXHANGE were negotiated the tokens should still be sealed with the computed key and signed. gss-ntlmssp currently doesn't implement that.

I didn't check the behavior for other flag combinations yet. I am not particularly interested in them because anything without MIC calculation downgrades the security beyond a point where latest Windows versions may reject it.


Notably, I would expect the GSS_Wrap behavior to align with the behavior of the EncryptMessage (NTLM) API on Windows with the data buffer not having the SECBUFFER_READONLY flag and the conf_flag flag matching the SECQOP_WRAP_NO_ENCRYPT flag. That means always sealing the data (at least for NTLM2 w/ key exchange) and ignoring the conf_flag flag.

Confusingly, the NegotiateStream specification is also defined in terms of GSS_Wrap/GSS_Unwrap methods. Unlike the SMTP authentication it behaves differently on NTLM where it sends the messages as <signature token><unsealed message> if NEGOTIATE_SEAL was not specified. This difference is caused by a special NTLM code path in the NegotiateStream code that passes the SECBUFFER_READONLY flag to the EncryptMessage/DecryptMessage methods. The equivalent GSSAPI transformation is to call gss_get_mic to produce the <signature token> and use gss_verify_mic to verify it. This cannot be expressed with GSS_Wrap/GSS_Unwrap directly.

filipnavara added a commit to filipnavara/runtime that referenced this issue Jun 29, 2022
wfurt pushed a commit to dotnet/runtime that referenced this issue Jun 30, 2022
…tion (#71373)

* Implement NTLM quirk in NegotiateStreamPal.Encrypt/Decrypt

NegotiateStream on non-encrypted connections with NTLM sends the
messages in a special `<signature token><plain text message>` format.
That's not something that gss_wrap/gss_unwrap would produce. It can be
produced through gss_get_mic/gss_verify_mic calls though so let's do
that.

* Remove MakeSignature/VerifySignature from SspiCli interop

The method names were misleading since they wrapped the EncryptMessage
and DecryptMessage native APIs and not the MakeSignature/VerifySignature
APIs that also exist.

* Remove unused sequenceNumber parameters in NegotiateStreamPal.Encrypt/Decrypt

The SSPI / GSSAPI providers keep track of the sequence numbers
themselves.

* Replace NTAuthentication.MakeSignature/VerifySignature with Wrap/Unwrap

This maps directly to the semantics of gss_wrap/gss_unwrap methods
that are used in many specifications. It replaces the misleading name
which in SSPI API is an equivalent of gss_get_mic/gss_verify_mic.
It also fixes the declaration to actually decode the buffers both
on Windows and Unix. In NTLM the content of the message is sealed
and needs to be decoded.

Note that previously on Unix the VerifySignature API didn't decode the
content. On Windows it did decode the content inside a buffer that was
passed as ReadOnlySpan<byte> but it didn't communicate back the offset
of the decoded data.

The SMTP GSSAPI authentication code was thus reading incorrect data.
In case the underlying authentication was Kerberos the data were
not encrypted and they were located at the beginning of the buffer
so it was not an issue. In case the underlying authentication was
NTLM it was looking at the NTLM signature token which luckily
happens to always start with the bytes 01 00 00 00. That exactly
matched the expected value by accident.

* Fix processing of last SMTP GSSAPI token

The last token in the GSSAPI SASL authentication mechanism is a bit
mask that specifies the supported security protections offered by the
server and the maximum token size. The client is supposed to choose
one of the protections and reply back. Relax the check to actually
support servers that offer anything but "no protection". As long
as the server also offers no protection we can choose it.

* Update unit test to use the new Wrap/Unwrap APIs

* Reset NTLM keys after successful Negotiate authentication

Updated the managed NTLM implementation and the fake servers to
implement the specification quirk:

MS-SPNG section 3.2.5.1 NTLM RC4 Key State for MechListMIC and First
Signed Message specifies that the RC4 sealing keys are reset back to
the initial state for the first message.

Since the managed implementation doesn't expose encryption yet it
didn't affect any observable behavior. Likewise the fake servers
didn't need this code path yet.

* Add GSSAPI authentication test to the loopback SMTP server

* Workaround for gssapi/gss-ntlmssp#77

* Expose the confidentiality flag from the native gss_wrap/unwrap APIs

* Allow default credentials for NTLM server-side on Linux/macOS
@simo5
Copy link
Collaborator

simo5 commented Aug 1, 2022

@filipnavara I finally came around to take a second good look at this.

So it seem the main issue here is my return of ENOSUP when NTLMSSP_NEGOTIATE_SEAL is not negotiated.

In this case I should check if NEGOTIATE_SIGN and NEGOTIATE_KEY_EXCHANGE are instead set and continue as normal calling ntlmv2-sign ... but in order to do that I would need a seal_handle, so I need to always create a seal handle regardless of negotiated flags.

Is this what you are suggesting?

@simo5
Copy link
Collaborator

simo5 commented Aug 1, 2022

And of curse if NEGOTIATE_SIGN is set we are already guaranteed to have a seal_handle ... so this is just about removing the flag check from ntlm_seal and ntlm_unseal ...

@filipnavara
Copy link
Author

Is this what you are suggesting?

Yep

And of curse if NEGOTIATE_SIGN is set we are already guaranteed to have a seal_handle ... so this is just about removing the flag check from ntlm_seal and ntlm_unseal ...

I think so.

@simo5
Copy link
Collaborator

simo5 commented Aug 1, 2022

@filipnavara should this be conditional to anything?
I have a specific test to check thet gss_wrap does fail if NTLMSSP_SEAL is not negotiated, which obviously now fails.
I guess I should always ignore that/
Or should I check the gss and ntlm flags disjointly and fail if GSS_GONF_FLAG us not set ignoring the NTLM level negotiate flags?

@simo5
Copy link
Collaborator

simo5 commented Aug 1, 2022

Oh wait, I should juts apply the signature, but not seal the content, perhaps ?

@simo5
Copy link
Collaborator

simo5 commented Aug 1, 2022

NVM my last comment. It sounds like what we are doing here is that if the app ask to gss_wrap something we wrap it regardless of what we negotiated with the peer.
So basically we take these flags just as advisory and if the peer can't handle the message ... well too bad.

@filipnavara
Copy link
Author

filipnavara commented Aug 1, 2022

should this be conditional to anything?

Good question. Unfortunately I don't have a good answer to that at the moment. If you have seal_handle then it should work and do sealing with the computed key.

Oh wait, I should juts apply the signature, but not seal the content, perhaps ?

If you have seal_handle then it should do sealing regardles of NTLMSSP_NEGOTIATE_SEAL. The experiments I did on Windows showed that it didn't just copy plain text data.

I am not sure what should be the behavior if you don't have seal_handle. The option is either to fail with ENOTSUP (like implemented today) or to copy the plain text verbatim. I can do some experiments on Windows to see what EncryptMessage API does in that case but I am generally fine with keeping the current behavior in that case (ENOTSUP). It would always be possible to emulate that with gss_get_mic/gss_verify_mic if necessary while that's not possible for the first case where seal_handle exists.

@simo5
Copy link
Collaborator

simo5 commented Aug 1, 2022

seal_handle is always generated regardless, so I am just going to return EINVAL if it is missing.
As for behavior, I'll let applications just gss_wrap all they want.
If they really just want a signature GSSAPI requires they use gss_get_mic() already, so the application retains all the options.

Flags can also be checked by applications so they have all the tools needed to decide whether to use gss_warp() or just gss_get_mic() if the protocol requires a choice be made.

simo5 added a commit to simo5/gssntlmssp that referenced this issue Aug 1, 2022
So according to Issue gssapi#77 we have an interop issue if we prevent the use
of gss_wrap when sealing has not been negotiated. On the technical side,
whether we negotiate sealing or not we always create a seal handle with
RC4.

Change behavior to allow applications to still wrap/unwrap data if they
want, even though the negotiation marked sealing as not selected.
The worst thing that can happen is that the peer application does no
like sealed content and bails.

Applications that need to avoid seeling should already just use
gss_get_mic() anyway and they can check the returned GSS flags to see if
sealing was negotiated (returned as GSS_CONF_FLAG), so applications
still have all they need to make their choice and be compatible with
whatever peer they need to speak to.

Thanks to Filip Navara for finding this.

Signed-off-by: Simo Sorce <simo@redhat.com>
simo5 added a commit to simo5/gssntlmssp that referenced this issue Aug 1, 2022
So according to Issue gssapi#77 we have an interop issue if we prevent the use
of gss_wrap when sealing has not been negotiated. On the technical side,
whether we negotiate sealing or not we always create a seal handle with
RC4.

Change behavior to allow applications to still wrap/unwrap data if they
want, even though the negotiation marked sealing as not selected.
The worst thing that can happen is that the peer application does no
like sealed content and bails.

Applications that need to avoid seeling should already just use
gss_get_mic() anyway and they can check the returned GSS flags to see if
sealing was negotiated (returned as GSS_CONF_FLAG), so applications
still have all they need to make their choice and be compatible with
whatever peer they need to speak to.

Thanks to Filip Navara for finding this.

Signed-off-by: Simo Sorce <simo@redhat.com>
@simo5
Copy link
Collaborator

simo5 commented Aug 1, 2022

Btw I wonder if any of the dotnet test suite can be easily run in a container w/o rebuilding all of it, I think it might be a good idea to run at least some dotnet tests when I change gssntlmssp to insure things don't break.
Alternatively if you can schedule a weekly(?) run in dotnet CI when gssntlmssp changes, and pester me if ntlm related tests fails that could also be an option :-)

@filipnavara
Copy link
Author

The dotnet test suites actually have very small coverage right now so it's likely not worth the effort, at least not yet. Some of the tests require complex Docker setup. I started working on expanding them and making it all easier to run but it's pretty slow effort so far. That said, I am aware that there is interest in the area of sharing the testing infrastructure and I regularly discuss that both with the .NET Networking team and other people working on various libraries in the ecosystem...

simo5 added a commit that referenced this issue Aug 2, 2022
So according to Issue #77 we have an interop issue if we prevent the use
of gss_wrap when sealing has not been negotiated. On the technical side,
whether we negotiate sealing or not we always create a seal handle with
RC4.

Change behavior to allow applications to still wrap/unwrap data if they
want, even though the negotiation marked sealing as not selected.
The worst thing that can happen is that the peer application does no
like sealed content and bails.

Applications that need to avoid seeling should already just use
gss_get_mic() anyway and they can check the returned GSS flags to see if
sealing was negotiated (returned as GSS_CONF_FLAG), so applications
still have all they need to make their choice and be compatible with
whatever peer they need to speak to.

Thanks to Filip Navara for finding this.

Signed-off-by: Simo Sorce <simo@redhat.com>
@simo5 simo5 closed this as completed in #78 Aug 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants