/* Copyright (C) 2014 GSS-NTLMSSP contributors, see COPYING for License */ #include #include #include "gss_ntlmssp.h" uint32_t gssntlm_cli_auth(uint32_t *minor_status, struct gssntlm_ctx *ctx, struct gssntlm_cred *cred, struct ntlm_buffer *target_info, uint32_t in_flags, gss_channel_bindings_t input_chan_bindings) { struct ntlm_buffer nt_chal_resp = { 0 }; struct ntlm_buffer lm_chal_resp = { 0 }; struct ntlm_buffer client_target_info = { 0 }; struct ntlm_key key_exchange_key = { .length = 16 }; struct ntlm_key encrypted_random_session_key = { .length = 16 }; struct ntlm_buffer enc_sess_key = { 0 }; struct ntlm_buffer auth_mic = { NULL, 16 }; uint8_t micbuf[16]; struct ntlm_buffer mic = { micbuf, 16 }; bool add_mic = false; bool key_exch; uint32_t retmaj; uint32_t retmin; switch (cred->type) { case GSSNTLM_CRED_USER: if (ctx->gss_flags & GSS_C_ANON_FLAG) { /* Anonymous auth, empty responses */ memset(&nt_chal_resp, 0, sizeof(nt_chal_resp)); lm_chal_resp.data = malloc(1); if (!lm_chal_resp.data) { set_GSSERR(ENOMEM); goto done; } lm_chal_resp.data[0] = 0; lm_chal_resp.length = 1; } else if (ctx->sec_req & SEC_V2_ONLY) { /* ### NTLMv2 ### */ uint8_t client_chal[8]; struct ntlm_buffer cli_chal = { client_chal, 8 }; struct ntlm_key ntlmv2_key = { .length = 16 }; struct ntlm_buffer nt_proof = { 0 }; struct ntlm_buffer cb = { 0 }; uint64_t srv_time = 0; if (target_info->length == 0 && input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) { set_GSSERRS(ERR_NOBINDINGS, GSS_S_BAD_BINDINGS); goto done; } if (target_info->length > 0) { bool *add_mic_ptr = NULL; bool protect; if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) { if (input_chan_bindings->initiator_addrtype != 0 || input_chan_bindings->initiator_address.length != 0 || input_chan_bindings->acceptor_addrtype != 0 || input_chan_bindings->acceptor_address.length != 0 || input_chan_bindings->application_data.length == 0) { set_GSSERRS(ERR_BADARG, GSS_S_BAD_BINDINGS); goto done; } cb.length = input_chan_bindings->application_data.length; cb.data = input_chan_bindings->application_data.value; } protect = in_flags & (NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_SEAL); if (protect) { if (ctx->int_flags & NTLMSSP_CTX_FLAG_SPNEGO_CAN_MIC) { add_mic_ptr = &add_mic; } } retmin = ntlm_process_target_info( ctx->ntlm, protect, target_info, ctx->target_name.data.server.name, &cb, &client_target_info, &srv_time, add_mic_ptr); if (retmin) { set_GSSERR(retmin); goto done; } if (srv_time != 0) { long int tdiff; tdiff = ntlm_timestamp_now() - srv_time; if ((tdiff / 10000000) > MAX_CHALRESP_LIFETIME) { set_GSSERRS(ERR_TIMESKEW, GSS_S_CONTEXT_EXPIRED); goto done; } } } /* Random client challenge */ retmin = RAND_BUFFER(&cli_chal); if (retmin) { set_GSSERR(retmin); goto done; } /* NTLMv2 Key */ retmin = NTOWFv2(ctx->ntlm, &cred->cred.user.nt_hash, cred->cred.user.user.data.user.name, cred->cred.user.user.data.user.domain, &ntlmv2_key); if (retmin) { set_GSSERR(retmin); goto done; } /* NTLMv2 Response */ retmin = ntlmv2_compute_nt_response(&ntlmv2_key, ctx->server_chal, client_chal, srv_time, &client_target_info, &nt_chal_resp); if (retmin) { set_GSSERR(retmin); goto done; } if (target_info->length == 0) { /* LMv2 Response * (only sent if challenge response has no target_info) */ retmin = ntlmv2_compute_lm_response(&ntlmv2_key, ctx->server_chal, client_chal, &lm_chal_resp); if (retmin) { set_GSSERR(retmin); goto done; } } /* The NT proof is the first 16 bytes */ nt_proof.data = nt_chal_resp.data; nt_proof.length = 16; /* The Session Base Key */ /* In NTLMv2 the Key Exchange Key is the Session Base Key */ retmin = ntlmv2_session_base_key(&ntlmv2_key, &nt_proof, &key_exchange_key); if (retmin) { set_GSSERR(retmin); goto done; } } else { /* ### NTLMv1 ### */ uint8_t client_chal[8]; struct ntlm_buffer cli_chal = { client_chal, 8 }; struct ntlm_key session_base_key = { .length = 16 }; bool NoLMResponseNTLMv1 = true; /* FIXME: get from conf/env */ bool ext_sec; nt_chal_resp.length = 24; nt_chal_resp.data = calloc(1, nt_chal_resp.length); lm_chal_resp.length = 24; lm_chal_resp.data = calloc(1, lm_chal_resp.length); if (!nt_chal_resp.data || !lm_chal_resp.data) { set_GSSERR(ENOMEM); goto done; } /* Random client challenge */ retmin = RAND_BUFFER(&cli_chal); if (retmin) { set_GSSERR(retmin); goto done; } ext_sec = (in_flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY); retmin = ntlm_compute_nt_response(&cred->cred.user.nt_hash, ext_sec, ctx->server_chal, client_chal, &nt_chal_resp); if (retmin) { set_GSSERR(retmin); goto done; } if (!ext_sec && NoLMResponseNTLMv1) { memcpy(lm_chal_resp.data, nt_chal_resp.data, 24); } else { retmin = ntlm_compute_lm_response(&cred->cred.user.lm_hash, ext_sec, ctx->server_chal, client_chal, &lm_chal_resp); if (retmin) { set_GSSERR(retmin); goto done; } } retmin = ntlm_session_base_key(&cred->cred.user.nt_hash, &session_base_key); if (retmin) { set_GSSERR(retmin); goto done; } retmin = KXKEY(ctx->ntlm, ext_sec, (in_flags & NTLMSSP_NEGOTIATE_LM_KEY), (in_flags & NTLMSSP_REQUEST_NON_NT_SESSION_KEY), ctx->server_chal, &cred->cred.user.lm_hash, &session_base_key, &lm_chal_resp, &key_exchange_key); if (retmin) { set_GSSERR(retmin); goto done; } } key_exch = (in_flags & NTLMSSP_NEGOTIATE_KEY_EXCH); retmin = ntlm_exported_session_key(&key_exchange_key, key_exch, &ctx->exported_session_key); if (retmin) { set_GSSERR(retmin); goto done; } if (key_exch) { retmin = ntlm_encrypted_session_key(&key_exchange_key, &ctx->exported_session_key, &encrypted_random_session_key); if (retmin) { set_GSSERR(retmin); goto done; } } /* in_flags all verified, assign as current flags */ ctx->neg_flags |= in_flags; enc_sess_key.data = encrypted_random_session_key.data; enc_sess_key.length = encrypted_random_session_key.length; retmin = ntlm_encode_auth_msg(ctx->ntlm, ctx->neg_flags, &lm_chal_resp, &nt_chal_resp, cred->cred.user.user.data.user.domain, cred->cred.user.user.data.user.name, ctx->workstation, &enc_sess_key, add_mic ? &auth_mic : NULL, &ctx->auth_msg); if (retmin) { set_GSSERR(retmin); goto done; } /* Now we need to calculate the MIC, because the MIC is part of the * message it protects, ntlm_encode_auth_msg() always add a zeroeth * buffer, however it returns in data_mic the pointer to the actual * area in the auth_msg that points at the mic, so we can backfill */ if (add_mic) { retmin = ntlm_mic(&ctx->exported_session_key, &ctx->nego_msg, &ctx->chal_msg, &ctx->auth_msg, &mic); if (retmin) { set_GSSERR(retmin); goto done; } /* now that we have the mic, copy it into the auth message */ memcpy(auth_mic.data, mic.data, 16); /* Make sure SPNEGO gets to know it has to add mechlistMIC too */ ctx->int_flags |= NTLMSSP_CTX_FLAG_AUTH_WITH_MIC; } set_GSSERRS(0, GSS_S_COMPLETE); break; case GSSNTLM_CRED_EXTERNAL: retmin = external_cli_auth(ctx, cred, in_flags, input_chan_bindings); if (retmin) { set_GSSERR(retmin); goto done; } set_GSSERRS(0, GSS_S_COMPLETE); break; default: set_GSSERR(ERR_NOUSRCRED); } done: ntlm_free_buffer_data(&client_target_info); ntlm_free_buffer_data(&nt_chal_resp); ntlm_free_buffer_data(&lm_chal_resp); return GSSERR(); } bool is_ntlm_v1(struct ntlm_buffer *nt_chal_resp) { return (nt_chal_resp->length == 24); } uint32_t gssntlm_srv_auth(uint32_t *minor_status, struct gssntlm_ctx *ctx, struct gssntlm_cred *cred, struct ntlm_buffer *nt_chal_resp, struct ntlm_buffer *lm_chal_resp, struct ntlm_key *key_exchange_key) { struct ntlm_key session_base_key = { .length = 16 }; struct ntlm_key ntlmv2_key = { .length = 16 }; struct ntlm_buffer nt_proof = { 0 }; uint32_t retmaj, retmin; const char *domstr; bool ntlm_v1; bool ext_sec; int retries; if (key_exchange_key->length != 16) { return GSSERRS(ERR_KEYLEN, GSS_S_FAILURE); } ntlm_v1 = is_ntlm_v1(nt_chal_resp); if (ntlm_v1 && !gssntlm_sec_lm_ok(ctx) && !gssntlm_sec_ntlm_ok(ctx)) { return GSSERRS(ERR_NONTLMV1, GSS_S_FAILURE); } ext_sec = (ctx->neg_flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY); switch (cred->type) { case GSSNTLM_CRED_USER: if (ntlm_v1) { uint8_t client_chal[8] = { 0 }; if (ext_sec) { memcpy(client_chal, lm_chal_resp->data, 8); } retmin = ntlm_verify_nt_response(nt_chal_resp, &cred->cred.user.nt_hash, ext_sec, ctx->server_chal, client_chal); if (retmin && gssntlm_sec_lm_ok(ctx)) { retmin = ntlm_verify_lm_response(lm_chal_resp, &cred->cred.user.lm_hash, ext_sec, ctx->server_chal, client_chal); } } else for (retries = 2; retries > 0; retries--) { if (retries == 2) { domstr = cred->cred.user.user.data.user.domain; } else { domstr = NULL; } /* NTLMv2 Key */ retmin = NTOWFv2(ctx->ntlm, &cred->cred.user.nt_hash, cred->cred.user.user.data.user.name, domstr, &ntlmv2_key); if (retmin) { set_GSSERR(retmin); goto done; } /* NTLMv2 Response */ retmin = ntlmv2_verify_nt_response(nt_chal_resp, &ntlmv2_key, ctx->server_chal); if (retmin && gssntlm_sec_lm_ok(ctx)) { /* LMv2 Response */ retmin = ntlmv2_verify_lm_response(lm_chal_resp, &ntlmv2_key, ctx->server_chal); } if (retmin == 0) break; } if (retmin) { set_GSSERR(retmin); goto done; } if (ntlm_v1) { retmin = ntlm_session_base_key(&cred->cred.user.nt_hash, &session_base_key); if (retmin) { set_GSSERR(retmin); goto done; } break; } /* The NT proof is the first 16 bytes */ nt_proof.data = nt_chal_resp->data; nt_proof.length = 16; /* The Session Base Key */ /* In NTLMv2 the Key Exchange Key is the Session Base Key */ retmin = ntlmv2_session_base_key(&ntlmv2_key, &nt_proof, &session_base_key); if (retmin) { set_GSSERR(retmin); goto done; } break; case GSSNTLM_CRED_EXTERNAL: retmin = external_srv_auth(ctx, cred, nt_chal_resp, lm_chal_resp, &session_base_key); if (retmin) { set_GSSERR(retmin); goto done; } break; default: set_GSSERR(ERR_NOUSRCRED); goto done; } if (ntlm_v1) { retmin = KXKEY(ctx->ntlm, ext_sec, (ctx->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY), (ctx->neg_flags & NTLMSSP_REQUEST_NON_NT_SESSION_KEY), ctx->server_chal, &cred->cred.user.lm_hash, &session_base_key, lm_chal_resp, key_exchange_key); if (retmin) { set_GSSERR(retmin); goto done; } } else { memcpy(key_exchange_key->data, session_base_key.data, session_base_key.length); } set_GSSERRS(0, GSS_S_COMPLETE); done: return GSSERR(); }