Hi Greg,
   Thanks for the prompt reply. I think you are right. I mistook mechListMIC set to GSS_C_NO_BUFFER as an invalid token. These 2 are different. I added some more logs and here is more detailed information:

In, init_ctx_cont the buf->length is 9. From init_ctx_cont, we call get_negTokenResp, which returns empty mechListMIC token with status: GSS_S_COMPLETE.
Progressing in init_ctx_cont, we hit the following if-else block


        ....
        } else if (!sc->mech_complete ||
                   (sc->mic_reqd &&
                    (sc->ctx_flags & GSS_C_INTEG_FLAG))) {
                /* Not obviously done; we may decide we're done later in
                 * init_ctx_call_init or handle_mic. */
                *tokflag = CONT_TOKEN_SEND;
                ret = GSS_S_COMPLETE;
        } else {
                /* mech finished on last pass and no MIC required, so done. */
                *tokflag = NO_TOKEN_SEND;
                ret = GSS_S_COMPLETE;
        }

        ...

After returning from here, we return back to spnego_gss_init_sec_context function (from where we called init_ctx_cont). In this function, the handle_mic call leads to the problem. Code snippet for the call:

        ...
        negState = ACCEPT_INCOMPLETE;
        if (spnego_ctx->mech_complete &&
            (spnego_ctx->ctx_flags & GSS_C_INTEG_FLAG)) {
                        ret = handle_mic(minor_status,
                                         mechListMIC_in,
                                         (mechtok_out.length != 0),
                                         spnego_ctx, &mechListMIC_out,
                                         &negState, &send_token);
                        if (HARD_ERROR(ret))
                                goto cleanup;
        ...

To be more specific, in handle_mic function, I hit the following section:

        ...
        } else if (sc->mic_reqd && !send_mechtok) {
                /*
                 * If the peer sends the final mechanism token, it
                 * must send the MIC with that token if the
                 * negotiation requires MICs.
                 */
                *negState = REJECT;
                *tokflag = ERROR_TOKEN_SEND;
                return GSS_S_DEFECTIVE_TOKEN;
        }
        ...

So, I think you were right.

About, the suggestions that were made, I think what you are saying is that (1) is not possible (or very hard). If you can add more details on (2), I will be happy to make the change and test it.
Basically, I do not understand if the problem is
(a) The assumption that: if we send one, we should receive one? OR
(b) Sending MIC in the first place.

Regards,
Aman


On Mon, Jul 26, 2021 at 11:53 AM Greg Hudson via RT <rt@kerborg-prod-app-1.mit.edu> wrote:
Thanks for the comprehensive interop issue report.  I have one question:

> In function, src/lib/gssapi/spnego/spnego_mech.c: get_negTokenResp we return
error code that it is a defective token.

This function should only return a defective token error if the packet has a
bad length, which does not appear to be the case.  Is it possible that the
error came from handle_mic() at the first else-if clause?

Some background:

* RFC 4178 (SPNEGO) section 5 only requires a mechlistMIC exchange if the
negotiated mech is not the most-preferred mech of one of the two parties.
Otherwise the MIC exchange is optional (unless the negotiated mech has no MIC
support, in which case it's impossible).

* RFC 4178 section 5 (c) (I) says, for the acceptor processing the final
initiator token: "If a mechlistMIC token was included and is correctly
verified, GSS_Accept_sec_context() indicates GSS_S_COMPLETE.  The output
negotiation message contains a mechlistMIC token and an accept_complete
state."

In this exchange both parties prefer NTLMSSP, so one would expect the MIC
exchange to be optional.   However, Microsoft imposes an additional constraint
for NTLMSSP.  This is both observed and documented; [MS-SPNG] Appendix A says:

    If NTLM authentication is most preferred by the client and the server, and
the
    client includes a MIC in AUTHENTICATE_MESSAGE ([MS-NLMP] section
    2.2.1.3), then the mechListMIC field becomes mandatory in order for the
    authentication to succeed. Windows clients in this case send an NTLM token
    instead of an SPNEGO token.

Accordingly, we have a helper mech_requires_mechlistMIC(), so that NTLMSSP can
report that it's behaving in a way that causes a SPNEGO MIC to be required.
In our code, the MIC requirement is treated as symmetrical; if we send one, we
believe we must receive one, and (following RFC 4178 5.c.I) if we receive one,
we send one.

From the packet traces it would appear that the Azure server does not believe
a MIC is required (i.e. it is not behaving according to [MS-SPNG] Appendix A)
and isn't sending a MIC in response to the initiator's MIC (i.e. it it
violates RFC 4178 section 5(c)(I)).  This apparent misbehavior could be
accomodated in two ways:

1. Following the hint in [MS-SPNG] Appendix A, the application could send an
NTLM token instead of a SPNEGO token.  Generally if a client prefers NTLM, it
can only do NTLM, so negotiation isn't required.  However, it may be difficult
for an application using MIT krb5 to implement this due to the abstraction
boundaries; the application doesn't know that the client is only capable of
NTLM and the SPNEGO layer can't generate a non-SPNEGO token.

2. In our SPNEGO layer, a true result from mech_requires_mechlistMIC() could
cause a MIC token to be generated, but not required from the other party.