diff options
Diffstat (limited to 'source3/libads/authdata.c')
-rw-r--r-- | source3/libads/authdata.c | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/source3/libads/authdata.c b/source3/libads/authdata.c new file mode 100644 index 0000000000..0bde3e6984 --- /dev/null +++ b/source3/libads/authdata.c @@ -0,0 +1,563 @@ +/* + Unix SMB/CIFS implementation. + kerberos authorization data (PAC) utility library + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#ifdef HAVE_KRB5 + +/**************************************************************** +****************************************************************/ + +static krb5_error_code check_pac_checksum(TALLOC_CTX *mem_ctx, + DATA_BLOB pac_data, + struct PAC_SIGNATURE_DATA *sig, + krb5_context context, + krb5_keyblock *keyblock) +{ + krb5_error_code ret; + krb5_checksum cksum; + krb5_keyusage usage = 0; + + smb_krb5_checksum_from_pac_sig(&cksum, sig); + +#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 + + ret = smb_krb5_verify_checksum(context, + keyblock, + usage, + &cksum, + pac_data.data, + pac_data.length); + + if (ret) { + DEBUG(2,("check_pac_checksum: PAC Verification failed: %s (%d)\n", + error_message(ret), ret)); + return ret; + } + + return ret; +} + +/**************************************************************** +****************************************************************/ + + NTSTATUS decode_pac_data(TALLOC_CTX *mem_ctx, + DATA_BLOB *pac_data_blob, + krb5_context context, + 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; + krb5_principal client_principal_pac = NULL; + 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; + + *pac_data_out = NULL; + + pac_data = TALLOC_ZERO_P(mem_ctx, struct PAC_DATA); + pac_data_raw = TALLOC_ZERO_P(mem_ctx, struct PAC_DATA_RAW); + kdc_sig_wipe = TALLOC_ZERO_P(mem_ctx, struct PAC_SIGNATURE_DATA); + srv_sig_wipe = TALLOC_ZERO_P(mem_ctx, struct PAC_SIGNATURE_DATA); + if (!pac_data_raw || !pac_data || !kdc_sig_wipe || !srv_sig_wipe) { + 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))); + return status; + } + + if (pac_data->num_buffers < 4) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + 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))); + return status; + } + + if (pac_data_raw->num_buffers < 4) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (pac_data->num_buffers != pac_data_raw->num_buffers) { + /* we need logon_ingo, 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)); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != pac_data_raw->buffers[i].type) { + DEBUG(0,("misparse! PAC_DATA buffer %d has type %d while PAC_DATA_RAW has %d\n", + i, pac_data->buffers[i].type, pac_data->buffers[i].type)); + return NT_STATUS_INVALID_PARAMETER; + } + switch (pac_data->buffers[i].type) { + case PAC_TYPE_LOGON_INFO: + if (!pac_data->buffers[i].info) { + break; + } + logon_info = pac_data->buffers[i].info->logon_info.info; + break; + case PAC_TYPE_SRV_CHECKSUM: + if (!pac_data->buffers[i].info) { + break; + } + srv_sig_ptr = &pac_data->buffers[i].info->srv_cksum; + srv_sig_blob = &pac_data_raw->buffers[i].info->remaining; + break; + case PAC_TYPE_KDC_CHECKSUM: + if (!pac_data->buffers[i].info) { + break; + } + kdc_sig_ptr = &pac_data->buffers[i].info->kdc_cksum; + kdc_sig_blob = &pac_data_raw->buffers[i].info->remaining; + break; + case PAC_TYPE_LOGON_NAME: + logon_name = &pac_data->buffers[i].info->logon_name; + break; + default: + break; + } + } + + if (!logon_info) { + DEBUG(0,("PAC no logon_info\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!logon_name) { + DEBUG(0,("PAC no logon_name\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!srv_sig_ptr || !srv_sig_blob) { + DEBUG(0,("PAC no srv_key\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!kdc_sig_ptr || !kdc_sig_blob) { + DEBUG(0,("PAC no kdc_key\n")); + 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))); + 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))); + 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 reencode, 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))); + 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))); + 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))); + return status; + } + + /* verify by service_key */ + ret = check_pac_checksum(mem_ctx, + modified_pac_blob, srv_sig_ptr, + context, + service_keyblock); + if (ret) { + DEBUG(1, ("PAC Decode: Failed to verify the service signature: %s\n", + error_message(ret))); + return NT_STATUS_ACCESS_DENIED; + } + + /* Convert to NT time, so as not to loose 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(mem_ctx, logon_name->logon_time))); + DEBUG(2, ("PAC Decode: Ticket: %s\n", nt_time_string(mem_ctx, tgs_authtime_nttime))); + return NT_STATUS_ACCESS_DENIED; + } + + ret = smb_krb5_parse_name_norealm(context, logon_name->account_name, + &client_principal_pac); + if (ret) { + DEBUG(2, ("Could not parse name from incoming PAC: [%s]: %s\n", + logon_name->account_name, + error_message(ret))); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!smb_krb5_principal_compare_any_realm(context, client_principal, client_principal_pac)) { + DEBUG(2, ("Name in PAC [%s] does not match principal name in ticket\n", + logon_name->account_name)); + krb5_free_principal(context, client_principal_pac); + return NT_STATUS_ACCESS_DENIED; + } + + 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(mem_ctx, PAC_DATA, pac_data); + if (s) { + DEBUGADD(10,("%s\n", s)); + } + } + + *pac_data_out = pac_data; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +struct PAC_LOGON_INFO *get_logon_info_from_pac(struct PAC_DATA *pac_data) +{ + int i; + + for (i=0; i < pac_data->num_buffers; i++) { + + if (pac_data->buffers[i].type != PAC_TYPE_LOGON_INFO) { + continue; + } + + return pac_data->buffers[i].info->logon_info.info; + } + + return NULL; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS kerberos_return_pac(TALLOC_CTX *mem_ctx, + const char *name, + const char *pass, + time_t time_offset, + time_t *expire_time, + time_t *renew_till_time, + const char *cache_name, + bool request_pac, + bool add_netbios_addr, + time_t renewable_time, + struct PAC_DATA **pac_ret) +{ + krb5_error_code ret; + NTSTATUS status = NT_STATUS_INVALID_PARAMETER; + DATA_BLOB tkt, ap_rep, sesskey1, sesskey2; + struct PAC_DATA *pac_data = NULL; + char *client_princ_out = NULL; + const char *auth_princ = NULL; + const char *local_service = NULL; + const char *cc = "MEMORY:kerberos_return_pac"; + + ZERO_STRUCT(tkt); + ZERO_STRUCT(ap_rep); + ZERO_STRUCT(sesskey1); + ZERO_STRUCT(sesskey2); + + if (!name || !pass) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (cache_name) { + cc = cache_name; + } + + if (!strchr_m(name, '@')) { + auth_princ = talloc_asprintf(mem_ctx, "%s@%s", name, + lp_realm()); + } else { + auth_princ = name; + } + NT_STATUS_HAVE_NO_MEMORY(auth_princ); + + local_service = talloc_asprintf(mem_ctx, "%s$@%s", + global_myname(), lp_realm()); + NT_STATUS_HAVE_NO_MEMORY(local_service); + + ret = kerberos_kinit_password_ext(auth_princ, + pass, + time_offset, + expire_time, + renew_till_time, + cc, + request_pac, + add_netbios_addr, + renewable_time, + &status); + if (ret) { + DEBUG(1,("kinit failed for '%s' with: %s (%d)\n", + auth_princ, error_message(ret), ret)); + /* status already set */ + goto out; + } + + DEBUG(10,("got TGT for %s in %s\n", auth_princ, cc)); + if (expire_time) { + DEBUGADD(10,("\tvalid until: %s (%d)\n", + http_timestring(*expire_time), + (int)*expire_time)); + } + if (renew_till_time) { + DEBUGADD(10,("\trenewable till: %s (%d)\n", + http_timestring(*renew_till_time), + (int)*renew_till_time)); + } + + /* we cannot continue with krb5 when UF_DONT_REQUIRE_PREAUTH is set, + * in that case fallback to NTLM - gd */ + + if (expire_time && renew_till_time && + (*expire_time == 0) && (*renew_till_time == 0)) { + return NT_STATUS_INVALID_LOGON_TYPE; + } + + + ret = cli_krb5_get_ticket(local_service, + time_offset, + &tkt, + &sesskey1, + 0, + cc, + NULL); + if (ret) { + DEBUG(1,("failed to get ticket for %s: %s\n", + local_service, error_message(ret))); + status = krb5_to_nt_status(ret); + goto out; + } + + status = ads_verify_ticket(mem_ctx, + lp_realm(), + time_offset, + &tkt, + &client_princ_out, + &pac_data, + &ap_rep, + &sesskey2, + False); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("ads_verify_ticket failed: %s\n", + nt_errstr(status))); + goto out; + } + + if (!pac_data) { + DEBUG(1,("no PAC\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + *pac_ret = pac_data; + +out: + if (cc != cache_name) { + ads_kdestroy(cc); + } + + data_blob_free(&tkt); + data_blob_free(&ap_rep); + data_blob_free(&sesskey1); + data_blob_free(&sesskey2); + + SAFE_FREE(client_princ_out); + + return status; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS kerberos_return_pac_logon_info(TALLOC_CTX *mem_ctx, + const char *name, + const char *pass, + time_t time_offset, + time_t *expire_time, + time_t *renew_till_time, + const char *cache_name, + bool request_pac, + bool add_netbios_addr, + time_t renewable_time, + struct PAC_LOGON_INFO **logon_info) +{ + NTSTATUS status; + struct PAC_DATA *pac_data = NULL; + struct PAC_LOGON_INFO *info = NULL; + + status = kerberos_return_pac(mem_ctx, + name, + pass, + time_offset, + expire_time, + renew_till_time, + cache_name, + request_pac, + add_netbios_addr, + renewable_time, + &pac_data); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!pac_data) { + DEBUG(3,("no pac\n")); + return NT_STATUS_INVALID_USER_BUFFER; + } + + info = get_logon_info_from_pac(pac_data); + if (!info) { + DEBUG(1,("no logon_info\n")); + return NT_STATUS_INVALID_USER_BUFFER; + } + + *logon_info = info; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS kerberos_return_info3_from_pac(TALLOC_CTX *mem_ctx, + const char *name, + const char *pass, + time_t time_offset, + time_t *expire_time, + time_t *renew_till_time, + const char *cache_name, + bool request_pac, + bool add_netbios_addr, + time_t renewable_time, + struct netr_SamInfo3 **info3) +{ + NTSTATUS status; + struct PAC_LOGON_INFO *logon_info = NULL; + + status = kerberos_return_pac_logon_info(mem_ctx, + name, + pass, + time_offset, + expire_time, + renew_till_time, + cache_name, + request_pac, + add_netbios_addr, + renewable_time, + &logon_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *info3 = &logon_info->info3; + + return NT_STATUS_OK; +} +#endif |