diff options
-rw-r--r-- | src/gss_creds.c | 28 | ||||
-rw-r--r-- | src/gss_names.c | 2 | ||||
-rw-r--r-- | src/gss_ntlmssp.h | 36 | ||||
-rw-r--r-- | src/gss_sec_ctx.c | 449 | ||||
-rw-r--r-- | src/gss_spi.c | 25 | ||||
-rw-r--r-- | src/ntlm.c | 10 | ||||
-rw-r--r-- | src/ntlm.h | 32 | ||||
-rw-r--r-- | src/ntlm_crypto.c | 84 |
8 files changed, 625 insertions, 41 deletions
diff --git a/src/gss_creds.c b/src/gss_creds.c index 37343b6..5ccc344 100644 --- a/src/gss_creds.c +++ b/src/gss_creds.c @@ -25,7 +25,6 @@ #include "gss_ntlmssp.h" - static int get_user_file_creds(struct gssntlm_name *name, struct gssntlm_cred *cred) { @@ -196,7 +195,7 @@ static void gssntlm_copy_key(struct ntlm_key *dest, struct ntlm_key *src) int gssntlm_copy_creds(struct gssntlm_cred *in, struct gssntlm_cred *out) { - char *dom = NULL, *usr = NULL; + char *dom = NULL, *usr = NULL, *srv = NULL; int ret = 0; out->type = GSSNTLM_CRED_NONE; @@ -208,25 +207,18 @@ int gssntlm_copy_creds(struct gssntlm_cred *in, struct gssntlm_cred *out) out->cred.anon.dummy = 1; break; case GSSNTLM_CRED_USER: - dom = strdup(in->cred.user.user.data.user.domain); - if (!dom) { - ret = ENOMEM; - goto done; - } - usr = strdup(in->cred.user.user.data.user.name); - if (!usr) { - ret = ENOMEM; - goto done; - } - out->cred.user.user.data.user.domain = dom; - out->cred.user.user.data.user.name = usr; + ret = gssntlm_copy_name(&in->cred.user.user, + &out->cred.user.user); + if (ret) goto done; gssntlm_copy_key(&out->cred.user.nt_hash, &in->cred.user.nt_hash); gssntlm_copy_key(&out->cred.user.lm_hash, &in->cred.user.lm_hash); break; case GSSNTLM_CRED_SERVER: - out->cred.server.dummy = 1; + ret = gssntlm_copy_name(&in->cred.server.name, + &out->cred.server.name); + if (ret) goto done; break; } out->type = in->type; @@ -235,6 +227,7 @@ done: if (ret) { safefree(dom); safefree(usr); + safefree(srv); } return ret; } @@ -250,15 +243,14 @@ void gssntlm_int_release_cred(struct gssntlm_cred *cred) cred->cred.anon.dummy = 0; break; case GSSNTLM_CRED_USER: - safefree(cred->cred.user.user.data.user.domain); - safefree(cred->cred.user.user.data.user.name); + gssntlm_int_release_name(&cred->cred.user.user); safezero(cred->cred.user.nt_hash.data, 16); cred->cred.user.nt_hash.length = 0; safezero(cred->cred.user.lm_hash.data, 16); cred->cred.user.lm_hash.length = 0; break; case GSSNTLM_CRED_SERVER: - cred->cred.server.dummy = 0; + gssntlm_int_release_name(&cred->cred.server.name); break; } } diff --git a/src/gss_names.c b/src/gss_names.c index 9ab29c6..fe95aeb 100644 --- a/src/gss_names.c +++ b/src/gss_names.c @@ -93,7 +93,7 @@ static uint32_t uid_to_name(uint32_t *retmin, uid_t uid, char **name) } uint32_t gssntlm_import_name_by_mech(uint32_t *minor_status, - gss_OID mech_type, + gss_const_OID mech_type, gss_buffer_t input_name_buffer, gss_OID input_name_type, gss_name_t *output_name) diff --git a/src/gss_ntlmssp.h b/src/gss_ntlmssp.h index 8ea8851..670f182 100644 --- a/src/gss_ntlmssp.h +++ b/src/gss_ntlmssp.h @@ -42,6 +42,24 @@ NTLMSSP_REQUEST_TARGET | \ NTLMSSP_NEGOTIATE_UNICODE) +#define NTLMSSP_DEFAULT_ALLOWED_SERVER_FLAGS ( \ + NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \ + NTLMSSP_NEGOTIATE_56 | \ + NTLMSSP_NEGOTIATE_KEY_EXCH | \ + NTLMSSP_NEGOTIATE_128 | \ + NTLMSSP_NEGOTIATE_VERSION | \ + NTLMSSP_TARGET_TYPE_SERVER | \ + NTLMSSP_TARGET_TYPE_DOMAIN | \ + NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \ + NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED | \ + NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED | \ + NTLMSSP_NEGOTIATE_NTLM | \ + NTLMSSP_NEGOTIATE_SEAL | \ + NTLMSSP_NEGOTIATE_SIGN | \ + NTLMSSP_REQUEST_TARGET | \ + NTLMSSP_NEGOTIATE_OEM | \ + NTLMSSP_NEGOTIATE_UNICODE) + struct gssntlm_name { enum ntlm_name_type { GSSNTLM_NAME_ANON, @@ -78,7 +96,7 @@ struct gssntlm_cred { struct ntlm_key lm_hash; } user; struct { - int dummy; + struct gssntlm_name name; } server; } cred; }; @@ -114,6 +132,8 @@ struct gssntlm_ctx { struct ntlm_buffer chal_msg; struct ntlm_buffer auth_msg; + uint8_t server_chal[8]; + /* requested gss fags */ uint32_t gss_flags; @@ -176,7 +196,7 @@ uint32_t gssntlm_import_name(uint32_t *minor_status, gss_name_t *output_name); uint32_t gssntlm_import_name_by_mech(uint32_t *minor_status, - gss_OID mech_type, + gss_const_OID mech_type, gss_buffer_t input_name_buffer, gss_OID input_name_type, gss_name_t *output_name); @@ -211,4 +231,16 @@ uint32_t gssntlm_context_time(uint32_t *minor_status, gss_ctx_id_t context_handle, uint32_t *time_rec); +uint32_t gssntlm_accept_sec_context(uint32_t *minor_status, + gss_ctx_id_t *context_handle, + gss_cred_id_t acceptor_cred_handle, + gss_buffer_t input_token_buffer, + gss_channel_bindings_t input_chan_bindings, + gss_name_t *src_name, + gss_OID *mech_type, + gss_buffer_t output_token, + uint32_t *ret_flags, + uint32_t *time_rec, + gss_cred_id_t *delegated_cred_handle); + #endif /* _GSS_NTLMSSP_H_ */ diff --git a/src/gss_sec_ctx.c b/src/gss_sec_ctx.c index 5ee4964..fb574f4 100644 --- a/src/gss_sec_ctx.c +++ b/src/gss_sec_ctx.c @@ -71,6 +71,10 @@ uint32_t gssntlm_init_sec_context(uint32_t *minor_status, if (ret_flags) *ret_flags = 0; if (time_rec) *time_rec = 0; + if (output_token == GSS_C_NO_BUFFER) { + return GSS_S_CALL_INACCESSIBLE_WRITE; + } + if (target_name) { server = (struct gssntlm_name *)target_name; if (server->type != GSSNTLM_NAME_SERVER) { @@ -475,8 +479,7 @@ uint32_t gssntlm_init_sec_context(uint32_t *minor_status, } if (in_flags & (NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_SEAL)) { - retmin = ntlm_signseal_keys(in_flags, - (ctx->role == GSSNTLM_CLIENT), + retmin = ntlm_signseal_keys(in_flags, true, &ctx->exported_session_key, &ctx->send.sign_key, &ctx->recv.sign_key, @@ -508,7 +511,7 @@ uint32_t gssntlm_init_sec_context(uint32_t *minor_status, goto done; } - ctx->stage = NTLMSSP_STAGE_AUTHENTICATE; + ctx->stage = NTLMSSP_STAGE_DONE; output_token->value = malloc(ctx->auth_msg.length); if (!output_token->value) { @@ -599,3 +602,443 @@ uint32_t gssntlm_context_time(uint32_t *minor_status, *time_rec = ctx->expiration_time - now; return GSS_S_COMPLETE; } + +uint32_t gssntlm_accept_sec_context(uint32_t *minor_status, + gss_ctx_id_t *context_handle, + gss_cred_id_t acceptor_cred_handle, + gss_buffer_t input_token, + gss_channel_bindings_t input_chan_bindings, + gss_name_t *src_name, + gss_OID *mech_type, + gss_buffer_t output_token, + uint32_t *ret_flags, + uint32_t *time_rec, + gss_cred_id_t *delegated_cred_handle) +{ + struct gssntlm_ctx *ctx; + struct gssntlm_cred *cred; + int lm_compat_lvl = -1; + char *workstation = NULL; + char *domain = NULL; + struct ntlm_buffer challenge = { 0 }; + struct gssntlm_name *server_name = NULL; + char *computer_name = NULL; + gss_buffer_desc tmpbuf; + uint64_t timestamp; + struct ntlm_buffer target_info = { 0 }; + struct ntlm_buffer nt_chal_resp = { 0 }; + struct ntlm_buffer lm_chal_resp = { 0 }; + struct ntlm_buffer enc_sess_key = { 0 }; + struct ntlm_key encrypted_random_session_key = { .length = 16 }; + struct ntlm_key key_exchange_key = { .length = 16 }; + uint8_t mic_data[16]; + struct ntlm_buffer mic = { mic_data, 16 }; + char *dom_name = NULL; + char *usr_name = NULL; + char *wks_name = NULL; + struct gssntlm_name *gss_usrname = NULL; + struct gssntlm_cred *usr_cred = NULL; + uint32_t retmin = 0; + uint32_t retmaj = 0; + uint32_t tmpmin; + uint32_t in_flags; + uint32_t msg_type; + uint8_t sec_req; + char *p; + + if (context_handle == NULL) return GSS_S_CALL_INACCESSIBLE_READ; + if (input_token == GSS_C_NO_BUFFER) { + return GSS_S_CALL_INACCESSIBLE_READ; + } + if (output_token == GSS_C_NO_BUFFER) { + return GSS_S_CALL_INACCESSIBLE_WRITE; + } + + /* reset return values */ + *minor_status = 0; + if (src_name) *src_name = GSS_C_NO_NAME; + if (mech_type) *mech_type = GSS_C_NO_OID; + if (ret_flags) *ret_flags = 0; + if (time_rec) *time_rec = 0; + if (delegated_cred_handle) *delegated_cred_handle = GSS_C_NO_CREDENTIAL; + + if (*context_handle == GSS_C_NO_CONTEXT) { + + /* first call */ + ctx = calloc(1, sizeof(struct gssntlm_ctx)); + if (!ctx) { + retmin = ENOMEM; + retmaj = GSS_S_FAILURE; + goto done; + } + + /* FIXME: add call to determine if we are any other type of + * server, including setting up callbacks to perform validation + * against a remote DC */ + ctx->role = GSSNTLM_SERVER; + + if (acceptor_cred_handle) { + cred = (struct gssntlm_cred *)acceptor_cred_handle; + if (cred->type != GSSNTLM_CRED_SERVER) { + retmaj = GSS_S_DEFECTIVE_CREDENTIAL; + goto done; + } + if (cred->cred.server.name.type != GSSNTLM_NAME_SERVER) { + retmaj = GSS_S_DEFECTIVE_CREDENTIAL; + goto done; + } + /* FIXME: duplicate */ + retmaj = gssntlm_duplicate_name(&retmin, + (const gss_name_t)&cred->cred.server.name, + (gss_name_t *)&server_name); + if (retmaj) goto done; + } + + lm_compat_lvl = gssntlm_get_lm_compatibility_level(); + sec_req = gssntlm_required_security(lm_compat_lvl, ctx->role); + if (sec_req == 0xff) { + retmaj = GSS_S_FAILURE; + goto done; + } + + ctx->neg_flags = NTLMSSP_DEFAULT_ALLOWED_SERVER_FLAGS; + /* Fixme: How do we allow anonymous negotition ? */ + + if ((sec_req & SEC_LM_OK) || (sec_req & SEC_DC_LM_OK)) { + ctx->neg_flags |= NTLMSSP_REQUEST_NON_NT_SESSION_KEY; + ctx->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + } + if (sec_req & SEC_EXT_SEC_OK) { + ctx->neg_flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY; + } + + retmin = ntlm_init_ctx(&ctx->ntlm); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + + ctx->nego_msg.data = malloc(input_token->length); + if (!ctx->nego_msg.data) { + retmin = ENOMEM; + retmaj = GSS_S_FAILURE; + goto done; + } + memcpy(ctx->nego_msg.data, input_token->value, input_token->length); + ctx->nego_msg.length = input_token->length; + + retmin = ntlm_decode_msg_type(ctx->ntlm, &ctx->nego_msg, &msg_type); + if (retmin || (msg_type != NEGOTIATE_MESSAGE)) { + retmaj = GSS_S_DEFECTIVE_TOKEN; + goto done; + } + + retmin = ntlm_decode_neg_msg(ctx->ntlm, &ctx->nego_msg, &in_flags, + &domain, &workstation); + if (retmin) { + retmaj = GSS_S_DEFECTIVE_TOKEN; + goto done; + } + + /* TODO: Support MS-NLMP ServerBlock ? */ + + /* leave only the crossing between requested and allowed flags */ + ctx->neg_flags &= in_flags; + + /* TODO: Check some minimum required flags ? */ + /* TODO: Check MS-NLMP ServerRequire128bitEncryption */ + + if (ctx->neg_flags & NTLMSSP_NEGOTIATE_UNICODE) { + /* Choose unicode in preferemce if both are set */ + ctx->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM; + } else if (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_OEM)) { + /* no agreement */ + retmaj = GSS_S_FAILURE; + goto done; + } + + if (ctx->neg_flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) { + ctx->neg_flags &= ~NTLMSSP_REQUEST_NON_NT_SESSION_KEY; + ctx->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + /* TODO: support Domain type */ + if (true) { + ctx->neg_flags |= NTLMSSP_TARGET_TYPE_SERVER; + ctx->neg_flags &= ~NTLMSSP_TARGET_TYPE_DOMAIN; + } + + if (ctx->neg_flags & NTLMSSP_REQUEST_TARGET) { + ctx->neg_flags |= NTLMSSP_NEGOTIATE_TARGET_INFO; + } + + if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + ctx->gss_flags |= GSS_C_INTEG_FLAG; + } + if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + ctx->gss_flags |= GSS_C_CONF_FLAG; + } + + /* Random server challenge */ + challenge.data = ctx->server_chal; + challenge.length = 8; + retmin = RAND_BUFFER(&challenge); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + + /* acquire our own name */ + if (!server_name) { + tmpbuf.value = ""; + tmpbuf.length = 0; + retmaj = gssntlm_import_name_by_mech(&retmin, + &gssntlm_oid, + &tmpbuf, + GSS_C_NT_HOSTBASED_SERVICE, + (gss_name_t *)&server_name); + if (retmaj) goto done; + } + + computer_name = strdup(server_name->data.server.name); + if (!computer_name) { + retmin = ENOMEM; + retmaj = GSS_S_FAILURE; + goto done; + } + if ((p = strchr(server_name->data.server.name, '.')) != NULL) { + /* we want only the non qualified computer name for now */ + *p = '\0'; + } + + timestamp = ntlm_timestamp_now(); + + retmin = ntlm_encode_target_info(ctx->ntlm, computer_name, NULL, + NULL, NULL, NULL, + NULL, ×tamp, + NULL, computer_name, NULL, + &target_info); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + + retmin = ntlm_encode_chal_msg(ctx->ntlm, ctx->neg_flags, + computer_name, &challenge, + &target_info, &ctx->chal_msg); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + + ctx->stage = NTLMSSP_STAGE_CHALLENGE; + + output_token->value = malloc(ctx->chal_msg.length); + if (!output_token->value) { + retmin = ENOMEM; + retmaj = GSS_S_FAILURE; + goto done; + } + memcpy(output_token->value, ctx->chal_msg.data, ctx->chal_msg.length); + output_token->length = ctx->chal_msg.length; + + retmaj = GSS_S_CONTINUE_NEEDED; + + } else { + ctx = (struct gssntlm_ctx *)(*context_handle); + + if (ctx->role != GSSNTLM_SERVER) { + retmaj = GSS_S_NO_CONTEXT; + goto done; + } + + ctx->auth_msg.data = malloc(input_token->length); + if (!ctx->auth_msg.data) { + retmin = ENOMEM; + retmaj = GSS_S_FAILURE; + goto done; + } + memcpy(ctx->auth_msg.data, input_token->value, input_token->length); + ctx->auth_msg.length = input_token->length; + + retmin = ntlm_decode_msg_type(ctx->ntlm, &ctx->auth_msg, &msg_type); + if (retmin) { + retmaj = GSS_S_DEFECTIVE_TOKEN; + goto done; + } + + if (msg_type != AUTHENTICATE_MESSAGE || + ctx->stage != NTLMSSP_STAGE_CHALLENGE) { + retmaj = GSS_S_NO_CONTEXT; + goto done; + } + + retmin = ntlm_decode_auth_msg(ctx->ntlm, &ctx->auth_msg, + ctx->neg_flags, + &lm_chal_resp, &nt_chal_resp, + &dom_name, &usr_name, &wks_name, + &enc_sess_key, &mic); + if (retmin) { + retmaj = GSS_S_DEFECTIVE_TOKEN; + goto done; + } + + lm_compat_lvl = gssntlm_get_lm_compatibility_level(); + sec_req = gssntlm_required_security(lm_compat_lvl, ctx->role); + if (sec_req == 0xff) { + retmaj = GSS_S_FAILURE; + goto done; + } + + if (((usr_name == NULL) || (usr_name[0] == '\0')) && + (nt_chal_resp.length == 0) && + (((lm_chal_resp.length == 1) && (lm_chal_resp.data[0] == '\0')) || + (lm_chal_resp.length == 0))) { + /* Anonymous auth */ + /* FIXME: not supported for now */ + retmin = EINVAL; + retmaj = GSS_S_FAILURE; + + } else if (sec_req & SEC_V2_ONLY) { + + /* ### NTLMv2 ### */ + struct ntlm_key ntlmv2_key = { .length = 16 }; + struct ntlm_buffer nt_proof = { 0 }; + char useratdom[1024]; + size_t ulen, dlen, uadlen; + gss_buffer_desc usrname; + + ulen = strlen(usr_name); + dlen = strlen(dom_name); + if (ulen + dlen + 2 > 1024) { + retmin = EINVAL; + retmaj = GSS_S_FAILURE; + goto done; + } + strncpy(useratdom, usr_name, ulen); + uadlen = ulen; + if (dlen) { + useratdom[uadlen] = '@'; + uadlen++; + strncpy(&useratdom[uadlen], dom_name, dlen); + uadlen += dlen; + } + useratdom[uadlen] = '\0'; + + usrname.value = useratdom; + usrname.length = uadlen; + retmaj = gssntlm_import_name(&retmin, &usrname, + GSS_C_NT_USER_NAME, + (gss_name_t *)&gss_usrname); + if (retmaj) goto done; + + retmaj = gssntlm_acquire_cred(&retmin, + (gss_name_t)gss_usrname, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + (gss_cred_id_t *)&usr_cred, + NULL, NULL); + if (retmaj) goto done; + + /* NTLMv2 Key */ + retmin = NTOWFv2(ctx->ntlm, &usr_cred->cred.user.nt_hash, + usr_cred->cred.user.user.data.user.name, + usr_cred->cred.user.user.data.user.domain, + &ntlmv2_key); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + + /* NTLMv2 Response */ + retmin = ntlmv2_verify_nt_response(&nt_chal_resp, &ntlmv2_key, + ctx->server_chal); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + + /* FIXME: retries using NULL as domain name in case of failure */ + + /* LMv2 Response */ + retmin = ntlmv2_verify_lm_response(&lm_chal_resp, &ntlmv2_key, + ctx->server_chal); + if (retmin) { + retmaj = GSS_S_FAILURE; + 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) { + retmaj = GSS_S_FAILURE; + goto done; + } + + /* FIXME: Verify MIC if client sent it */ + + } else { + /* ### NTLMv1 ### */ + retmaj = GSS_S_FAILURE; + goto done; + } + + if (ctx->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + memcpy(encrypted_random_session_key.data, enc_sess_key.data, 16); + ctx->exported_session_key.length = 16; + + retmin = ntlm_encrypted_session_key(&key_exchange_key, + &encrypted_random_session_key, + &ctx->exported_session_key); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + } else { + ctx->exported_session_key = key_exchange_key; + } + + if (ctx->neg_flags & (NTLMSSP_NEGOTIATE_SIGN | + NTLMSSP_NEGOTIATE_SEAL)) { + retmin = ntlm_signseal_keys(ctx->neg_flags, false, + &ctx->exported_session_key, + &ctx->send.sign_key, + &ctx->recv.sign_key, + &ctx->send.seal_key, + &ctx->recv.seal_key, + &ctx->send.seal_handle, + &ctx->recv.seal_handle); + if (retmin) { + retmaj = GSS_S_FAILURE; + goto done; + } + } + + ctx->stage = NTLMSSP_STAGE_DONE; + ctx->expiration_time = time(NULL) + MAX_CHALRESP_LIFETIME; + ctx->established = true; + retmaj = GSS_S_COMPLETE; + } + +done: + + if ((retmaj != GSS_S_COMPLETE) && + (retmaj != GSS_S_CONTINUE_NEEDED)) { + gssntlm_delete_sec_context(&tmpmin, (gss_ctx_id_t *)&ctx, NULL); + *minor_status = retmin; + } + *context_handle = (gss_ctx_id_t)ctx; + gssntlm_release_name(&tmpmin, (gss_name_t *)&server_name); + safefree(computer_name); + safefree(workstation); + safefree(domain); + ntlm_free_buffer_data(&target_info); + return retmaj; +} diff --git a/src/gss_spi.c b/src/gss_spi.c index 44db903..557e23c 100644 --- a/src/gss_spi.c +++ b/src/gss_spi.c @@ -149,3 +149,28 @@ OM_uint32 gss_context_time(OM_uint32 *minor_status, return gssntlm_context_time(minor_status, context_handle, time_rec); } +OM_uint32 gss_accept_sec_context(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_cred_id_t acceptor_cred_handle, + gss_buffer_t input_token_buffer, + gss_channel_bindings_t input_chan_bindings, + gss_name_t *src_name, + gss_OID *mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec, + gss_cred_id_t *delegated_cred_handle) +{ + return gssntlm_accept_sec_context(minor_status, + context_handle, + acceptor_cred_handle, + input_token_buffer, + input_chan_bindings, + src_name, + mech_type, + output_token, + ret_flags, + time_rec, + delegated_cred_handle); +} + @@ -1302,22 +1302,28 @@ int ntlm_decode_auth_msg(struct ntlm_ctx *ctx, payload_offs += sizeof(struct wire_version); } - /* this must be second as it pushes the payload further down */ + /* Unconditionally copy 16 bytes for the MIC, if it was really + * added by the client it will be flagged in the AV_PAIR contained + * in the NT Response, that will be fully decoded later by the caller + * and the MIC checked otherwise these 16 bytes will just be ignored */ if (mic) { if (mic->length < 16) return ERR_DECODE; /* mic is at payload_offs right now */ if (buffer->length - payload_offs < 16) return ERR_DECODE; memcpy(mic->data, &buffer->data[payload_offs], 16); - payload_offs += 16; + /* NOTE: we do not push down the payload because we do not know that + * the MIC is actually present yet for real */ } if (msg->lm_chalresp.len != 0 && lm_chalresp) { ret = ntlm_decode_field(&msg->lm_chalresp, buffer, payload_offs, lm_chalresp); + if (ret) goto done; } if (msg->nt_chalresp.len != 0 && nt_chalresp) { ret = ntlm_decode_field(&msg->nt_chalresp, buffer, payload_offs, nt_chalresp); + if (ret) goto done; } if (msg->domain_name.len != 0 && domain_name) { if (flags & NTLMSSP_NEGOTIATE_UNICODE) { @@ -268,7 +268,7 @@ int ntlm_exported_session_key(struct ntlm_key *key_exchange_key, struct ntlm_key *exported_session_key); /** - * @brief Comutes the NTLM encrypted session key + * @brief Encrypts or Decrypts the NTLM session key using RC4K * * @param key_exchange_key[16] The Key Exchange Key * @param exported_session_key[16] Resulting exported session key @@ -276,9 +276,8 @@ int ntlm_exported_session_key(struct ntlm_key *key_exchange_key, * * @return 0 on success or error. */ -int ntlm_encrypted_session_key(struct ntlm_key *key_exchange_key, - struct ntlm_key *exported_session_key, - struct ntlm_key *encrypted_random_session_key); +int ntlm_encrypted_session_key(struct ntlm_key *key, + struct ntlm_key *in, struct ntlm_key *out); /** * @brief Computes all the sign and seal keys from the session key @@ -304,6 +303,31 @@ int ntlm_signseal_keys(uint32_t flags, bool client, struct ntlm_rc4_handle **seal_send_handle, struct ntlm_rc4_handle **seal_recv_handle); +/** + * @brief Verifies a 16 bit NT Response + * + * @param nt_response The NT Response buffer including client challenge + * @param ntlmv2_key The NTLMv2 key + * @param server_chal[8] The server challenge used to compute the response + * + * @return 0 on success, or an error + */ +int ntlmv2_verify_nt_response(struct ntlm_buffer *nt_response, + struct ntlm_key *ntlmv2_key, + uint8_t server_chal[8]); + +/** + * @brief Verifies a 16 bit LM Response + * + * @param nt_response The LM Response buffer including client challenge + * @param ntlmv2_key The NTLMv2 key + * @param server_chal[8] The server challenge used to compute the response + * + * @return 0 on success, or an error + */ +int ntlmv2_verify_lm_response(struct ntlm_buffer *nt_response, + struct ntlm_key *ntlmv2_key, + uint8_t server_chal[8]); /* ############## ENCODING / DECODING ############## */ diff --git a/src/ntlm_crypto.c b/src/ntlm_crypto.c index d999d0f..1993148 100644 --- a/src/ntlm_crypto.c +++ b/src/ntlm_crypto.c @@ -409,18 +409,14 @@ int ntlm_exported_session_key(struct ntlm_key *key_exchange_key, return RAND_BUFFER(&nonce); } -int ntlm_encrypted_session_key(struct ntlm_key *key_exchange_key, - struct ntlm_key *exported_session_key, - struct ntlm_key *encrypted_random_session_key) +int ntlm_encrypted_session_key(struct ntlm_key *key, + struct ntlm_key *in, struct ntlm_key *out) { - struct ntlm_buffer key = { key_exchange_key->data, - key_exchange_key->length }; - struct ntlm_buffer nonce = { exported_session_key->data, - exported_session_key->length }; - struct ntlm_buffer cipher = { encrypted_random_session_key->data, - encrypted_random_session_key->length }; - - return RC4K(&key, NTLM_CIPHER_ENCRYPT, &nonce, &cipher); + struct ntlm_buffer _key = { key->data, key->length }; + struct ntlm_buffer data = { in->data, in->length }; + struct ntlm_buffer result = { out->data, out->length }; + + return RC4K(&_key, NTLM_CIPHER_ENCRYPT, &data, &result); } static int ntlm_key_derivation_function(struct ntlm_key *key, @@ -557,3 +553,69 @@ int ntlm_signseal_keys(uint32_t flags, bool client, return 0; } + +int ntlmv2_verify_nt_response(struct ntlm_buffer *nt_response, + struct ntlm_key *ntlmv2_key, + uint8_t server_chal[8]) +{ + union wire_ntlm_response *nt_resp = NULL; + struct ntlm_buffer key = { ntlmv2_key->data, ntlmv2_key->length }; + uint8_t proof[16]; + struct ntlm_buffer nt_proof = { proof, 16 }; + struct ntlm_buffer payload; + int ret; + + if (nt_response->length < 24) return EINVAL; + + nt_resp = (union wire_ntlm_response *)nt_response->data; + + payload.length = 8; + payload.data = server_chal; + + payload.length = nt_response->length - 8; + payload.data = malloc(payload.length); + if (!payload.data) return ENOMEM; + memcpy(payload.data, server_chal, 8); + memcpy(&payload.data[8], nt_resp->v2.cli_chal, payload.length - 8); + + ret = HMAC_MD5(&key, &payload, &nt_proof); + + if (ret) goto done; + + ret = EINVAL; + if (memcmp(nt_resp->v2.resp, proof, 16) == 0) { + ret = 0; + } + +done: + safefree(payload.data); + return ret; +} + +int ntlmv2_verify_lm_response(struct ntlm_buffer *lm_response, + struct ntlm_key *ntlmv2_key, + uint8_t server_chal[8]) +{ + struct ntlm_buffer key = { ntlmv2_key->data, ntlmv2_key->length }; + union wire_lm_response *lm_resp = NULL; + uint8_t payload_buf[16]; + struct ntlm_buffer payload = { payload_buf, 16 }; + uint8_t proof[16]; + struct ntlm_buffer lm_proof = { proof, 16 }; + int ret; + + if (lm_response->length != 24) return EINVAL; + + /* now caluclate the LM Proof */ + lm_resp = (union wire_lm_response *)lm_response->data; + + memcpy(payload.data, server_chal, 8); + memcpy(&payload.data[8], lm_resp->v2.cli_chal, 8); + ret = HMAC_MD5(&key, &payload, &lm_proof); + + if (ret) return ret; + + if (memcmp(lm_resp->v2.resp, proof, 16) == 0) return 0; + + return EINVAL; +} |