From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- auth/kerberos/kerberos_pac.c | 554 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 auth/kerberos/kerberos_pac.c (limited to 'auth/kerberos/kerberos_pac.c') diff --git a/auth/kerberos/kerberos_pac.c b/auth/kerberos/kerberos_pac.c new file mode 100644 index 0000000..ae4557b --- /dev/null +++ b/auth/kerberos/kerberos_pac.c @@ -0,0 +1,554 @@ +/* + Unix SMB/CIFS implementation. + kerberos authorization data (PAC) utility library + Copyright (C) Jim McDonough 2003 + Copyright (C) Andrew Bartlett 2004-2005 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-2005 + Copyright (C) Guenther Deschner 2005,2007,2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#ifdef HAVE_KRB5 + +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "librpc/gen_ndr/auth.h" +#include "auth/common_auth.h" +#include "auth/kerberos/pac_utils.h" + +krb5_error_code check_pac_checksum(DATA_BLOB pac_data, + struct PAC_SIGNATURE_DATA *sig, + krb5_context context, + const krb5_keyblock *keyblock) +{ + krb5_error_code ret; + krb5_checksum cksum; + krb5_keyusage usage = 0; + krb5_boolean checksum_valid = false; + krb5_data input; + + switch (sig->type) { + case CKSUMTYPE_HMAC_MD5: + /* ignores the key type */ + break; + case CKSUMTYPE_HMAC_SHA1_96_AES_256: + if (KRB5_KEY_TYPE(keyblock) != ENCTYPE_AES256_CTS_HMAC_SHA1_96) { + return EINVAL; + } + /* ok */ + break; + case CKSUMTYPE_HMAC_SHA1_96_AES_128: + if (KRB5_KEY_TYPE(keyblock) != ENCTYPE_AES128_CTS_HMAC_SHA1_96) { + return EINVAL; + } + /* ok */ + break; + default: + DEBUG(2,("check_pac_checksum: Checksum Type %"PRIu32" is not supported\n", + sig->type)); + return EINVAL; + } + +#ifdef HAVE_CHECKSUM_IN_KRB5_CHECKSUM /* Heimdal */ + cksum.cksumtype = (krb5_cksumtype)sig->type; + cksum.checksum.length = sig->signature.length; + cksum.checksum.data = sig->signature.data; +#else /* MIT */ + cksum.checksum_type = (krb5_cksumtype)sig->type; + cksum.length = sig->signature.length; + cksum.contents = sig->signature.data; +#endif + +#ifdef HAVE_KRB5_KU_OTHER_CKSUM /* Heimdal */ + usage = KRB5_KU_OTHER_CKSUM; +#elif defined(HAVE_KRB5_KEYUSAGE_APP_DATA_CKSUM) /* MIT */ + usage = KRB5_KEYUSAGE_APP_DATA_CKSUM; +#else +#error UNKNOWN_KRB5_KEYUSAGE +#endif + + input.data = (char *)pac_data.data; + input.length = pac_data.length; + + ret = krb5_c_verify_checksum(context, + keyblock, + usage, + &input, + &cksum, + &checksum_valid); + if (!checksum_valid) { + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } + if (ret){ + DEBUG(2,("check_pac_checksum: PAC Verification failed: %s (%d)\n", + error_message(ret), ret)); + return ret; + } + + return ret; +} + +/** +* @brief Decode a blob containing a NDR encoded PAC structure +* +* @param mem_ctx - The memory context +* @param pac_data_blob - The data blob containing the NDR encoded data +* @param context - The Kerberos Context +* @param service_keyblock - The Service Key used to verify the checksum +* @param client_principal - The client principal +* @param tgs_authtime - The ticket timestamp +* @param pac_data_out - [out] The decoded PAC +* +* @return - A NTSTATUS error code +*/ +NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx, + DATA_BLOB pac_data_blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + struct PAC_DATA **pac_data_out) +{ + NTSTATUS status; + enum ndr_err_code ndr_err; + krb5_error_code ret; + DATA_BLOB modified_pac_blob; + + NTTIME tgs_authtime_nttime; + int i; + + struct PAC_SIGNATURE_DATA *srv_sig_ptr = NULL; + struct PAC_SIGNATURE_DATA *kdc_sig_ptr = NULL; + struct PAC_SIGNATURE_DATA *srv_sig_wipe = NULL; + struct PAC_SIGNATURE_DATA *kdc_sig_wipe = NULL; + struct PAC_LOGON_NAME *logon_name = NULL; + struct PAC_LOGON_INFO *logon_info = NULL; + struct PAC_DATA *pac_data = NULL; + struct PAC_DATA_RAW *pac_data_raw = NULL; + + DATA_BLOB *srv_sig_blob = NULL; + DATA_BLOB *kdc_sig_blob = NULL; + + bool bool_ret; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + if (pac_data_out) { + *pac_data_out = NULL; + } + + pac_data = talloc(tmp_ctx, struct PAC_DATA); + pac_data_raw = talloc(tmp_ctx, struct PAC_DATA_RAW); + kdc_sig_wipe = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA); + srv_sig_wipe = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA); + if (!pac_data_raw || !pac_data || !kdc_sig_wipe || !srv_sig_wipe) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_pull_struct_blob(&pac_data_blob, pac_data, pac_data, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + if (pac_data->num_buffers < 4) { + /* we need logon_info, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ndr_err = ndr_pull_struct_blob( + &pac_data_blob, pac_data_raw, pac_data_raw, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA_RAW); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + if (pac_data_raw->num_buffers < 4) { + /* we need logon_info, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (pac_data->num_buffers != pac_data_raw->num_buffers) { + /* we need logon_info, service_key and kdc_key */ + DEBUG(0, ("misparse! PAC_DATA has %d buffers while " + "PAC_DATA_RAW has %d\n", pac_data->num_buffers, + pac_data_raw->num_buffers)); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; i < pac_data->num_buffers; i++) { + struct PAC_BUFFER *data_buf = &pac_data->buffers[i]; + struct PAC_BUFFER_RAW *raw_buf = &pac_data_raw->buffers[i]; + + if (data_buf->type != raw_buf->type) { + DEBUG(0, ("misparse! PAC_DATA buffer %d has type " + "%d while PAC_DATA_RAW has %d\n", i, + data_buf->type, raw_buf->type)); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + switch (data_buf->type) { + case PAC_TYPE_LOGON_INFO: + if (!data_buf->info) { + break; + } + logon_info = data_buf->info->logon_info.info; + break; + case PAC_TYPE_SRV_CHECKSUM: + if (!data_buf->info) { + break; + } + srv_sig_ptr = &data_buf->info->srv_cksum; + srv_sig_blob = &raw_buf->info->remaining; + break; + case PAC_TYPE_KDC_CHECKSUM: + if (!data_buf->info) { + break; + } + kdc_sig_ptr = &data_buf->info->kdc_cksum; + kdc_sig_blob = &raw_buf->info->remaining; + break; + case PAC_TYPE_LOGON_NAME: + logon_name = &data_buf->info->logon_name; + break; + default: + break; + } + } + + if (!logon_info) { + DEBUG(0,("PAC no logon_info\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!logon_name) { + DEBUG(0,("PAC no logon_name\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!srv_sig_ptr || !srv_sig_blob) { + DEBUG(0,("PAC no srv_key\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!kdc_sig_ptr || !kdc_sig_blob) { + DEBUG(0,("PAC no kdc_key\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Find and zero out the signatures, + * as required by the signing algorithm */ + + /* We find the data blobs above, + * now we parse them to get at the exact portion we should zero */ + ndr_err = ndr_pull_struct_blob( + kdc_sig_blob, kdc_sig_wipe, kdc_sig_wipe, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the KDC signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + ndr_err = ndr_pull_struct_blob( + srv_sig_blob, srv_sig_wipe, srv_sig_wipe, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the SRV signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + /* Now zero the decoded structure */ + memset(kdc_sig_wipe->signature.data, + '\0', kdc_sig_wipe->signature.length); + memset(srv_sig_wipe->signature.data, + '\0', srv_sig_wipe->signature.length); + + /* and re-encode, back into the same place it came from */ + ndr_err = ndr_push_struct_blob( + kdc_sig_blob, pac_data_raw, kdc_sig_wipe, + (ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the KDC signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + ndr_err = ndr_push_struct_blob( + srv_sig_blob, pac_data_raw, srv_sig_wipe, + (ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the SRV signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + /* push out the whole structure, but now with zero'ed signatures */ + ndr_err = ndr_push_struct_blob( + &modified_pac_blob, pac_data_raw, pac_data_raw, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA_RAW); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the RAW PAC: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + if (service_keyblock) { + /* verify by service_key */ + ret = check_pac_checksum(modified_pac_blob, srv_sig_ptr, + context, + service_keyblock); + if (ret) { + DEBUG(5, ("PAC Decode: Failed to verify the service " + "signature: %s\n", error_message(ret))); + return NT_STATUS_ACCESS_DENIED; + } + + if (krbtgt_keyblock) { + /* verify the service key checksum by krbtgt_key */ + ret = check_pac_checksum(srv_sig_ptr->signature, kdc_sig_ptr, + context, krbtgt_keyblock); + if (ret) { + DEBUG(1, ("PAC Decode: Failed to verify the KDC signature: %s\n", + smb_get_krb5_error_message(context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } + } + + if (tgs_authtime) { + /* Convert to NT time, so as not to lose accuracy in comparison */ + unix_to_nt_time(&tgs_authtime_nttime, tgs_authtime); + + if (tgs_authtime_nttime != logon_name->logon_time) { + DEBUG(2, ("PAC Decode: " + "Logon time mismatch between ticket and PAC!\n")); + DEBUG(2, ("PAC Decode: PAC: %s\n", + nt_time_string(tmp_ctx, logon_name->logon_time))); + DEBUG(2, ("PAC Decode: Ticket: %s\n", + nt_time_string(tmp_ctx, tgs_authtime_nttime))); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (client_principal) { + char *client_principal_string; + ret = krb5_unparse_name_flags(context, client_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM|KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &client_principal_string); + if (ret) { + DEBUG(2, ("Could not unparse name from ticket to match with name from PAC: [%s]:%s\n", + logon_name->account_name, error_message(ret))); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + bool_ret = strcmp(client_principal_string, logon_name->account_name) == 0; + + if (!bool_ret) { + DEBUG(2, ("Name in PAC [%s] does not match principal name " + "in ticket [%s]\n", + logon_name->account_name, + client_principal_string)); + SAFE_FREE(client_principal_string); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + SAFE_FREE(client_principal_string); + + } + + DEBUG(3,("Found account name from PAC: %s [%s]\n", + logon_info->info3.base.account_name.string, + logon_info->info3.base.full_name.string)); + + DEBUG(10,("Successfully validated Kerberos PAC\n")); + + if (DEBUGLEVEL >= 10) { + const char *s; + s = NDR_PRINT_STRUCT_STRING(tmp_ctx, PAC_DATA, pac_data); + if (s) { + DEBUGADD(10,("%s\n", s)); + } + } + + if (pac_data_out) { + *pac_data_out = talloc_steal(mem_ctx, pac_data); + } + + return NT_STATUS_OK; +} + +NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx, + DATA_BLOB blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + struct PAC_LOGON_INFO **logon_info) +{ + NTSTATUS nt_status; + struct PAC_DATA *pac_data; + int i; + nt_status = kerberos_decode_pac(mem_ctx, + blob, + context, + krbtgt_keyblock, + service_keyblock, + client_principal, + tgs_authtime, + &pac_data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + *logon_info = NULL; + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != PAC_TYPE_LOGON_INFO) { + continue; + } + *logon_info = pac_data->buffers[i].info->logon_info.info; + } + if (!*logon_info) { + return NT_STATUS_INVALID_PARAMETER; + } + return NT_STATUS_OK; +} + +static NTSTATUS auth4_context_fetch_PAC_DATA_CTR( + struct auth4_context *auth_ctx, + TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *princ_name, + const struct tsocket_address *remote_address, + uint32_t session_info_flags, + struct auth_session_info **session_info) +{ + struct PAC_DATA_CTR *pac_data_ctr = NULL; + NTSTATUS status; + + if (pac_blob == NULL) { + return NT_STATUS_NO_IMPERSONATION_TOKEN; + } + + pac_data_ctr = talloc_zero(mem_ctx, struct PAC_DATA_CTR); + if (pac_data_ctr == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + status = kerberos_decode_pac(pac_data_ctr, + *pac_blob, + NULL, + NULL, + NULL, + NULL, + 0, + &pac_data_ctr->pac_data); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + pac_data_ctr->pac_blob = data_blob_talloc(pac_data_ctr, + pac_blob->data, + pac_blob->length); + if (pac_data_ctr->pac_blob.length != pac_blob->length) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + *session_info = talloc_zero(mem_ctx, struct auth_session_info); + if (*session_info == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + TALLOC_FREE(auth_ctx->private_data); + auth_ctx->private_data = talloc_move(auth_ctx, &pac_data_ctr); + + return NT_STATUS_OK; + +fail: + TALLOC_FREE(pac_data_ctr); + + return status; +} + +struct auth4_context *auth4_context_for_PAC_DATA_CTR(TALLOC_CTX *mem_ctx) +{ + struct auth4_context *auth_ctx = NULL; + + auth_ctx = talloc_zero(mem_ctx, struct auth4_context); + if (auth_ctx == NULL) { + return NULL; + } + auth_ctx->generate_session_info_pac = auth4_context_fetch_PAC_DATA_CTR; + + return auth_ctx; +} + +struct PAC_DATA_CTR *auth4_context_get_PAC_DATA_CTR(struct auth4_context *auth_ctx, + TALLOC_CTX *mem_ctx) +{ + struct PAC_DATA_CTR *p = NULL; + SMB_ASSERT(auth_ctx->generate_session_info_pac == auth4_context_fetch_PAC_DATA_CTR); + p = talloc_get_type_abort(auth_ctx->private_data, struct PAC_DATA_CTR); + auth_ctx->private_data = NULL; + return talloc_move(mem_ctx, &p); +} + +#endif -- cgit v1.2.3