1
0
mirror of https://github.com/samba-team/samba.git synced 2025-11-02 20:23:50 +03:00
Files
samba-mirror/source/auth/kerberos/kerberos_pac.c
Andrew Bartlett 36973b1eef r11543: A major upgrade to our KDC and PAC handling.
We now put the PAC in the AS-REP, so that the client has it in the
TGT.  We then validate it (and re-sign it) on a TGS-REQ, ie when the
client wants a ticket.

This should also allow us to interop with windows KDCs.

If we get an invalid PAC at the TGS stage, we just drop it.

I'm slowly trying to move the application logic out of hdb-ldb.c, and
back in with the rest of Samba's auth system, for consistancy.  This
continues that trend.

Andrew Bartlett
2007-10-10 13:45:52 -05:00

618 lines
17 KiB
C

/*
Unix SMB/CIFS implementation.
Create and parse the krb5 PAC
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
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 2 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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "includes.h"
#include "system/kerberos.h"
#include "system/time.h"
#include "system/network.h"
#include "auth/auth.h"
#include "auth/kerberos/kerberos.h"
#include "librpc/gen_ndr/ndr_krb5pac.h"
#include "auth/auth.h"
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_crypto crypto;
Checksum cksum;
cksum.cksumtype = (CKSUMTYPE)sig->type;
cksum.checksum.length = sig->signature.length;
cksum.checksum.data = sig->signature.data;
ret = krb5_crypto_init(context,
keyblock,
0,
&crypto);
if (ret) {
DEBUG(0,("krb5_crypto_init() failed: %s\n",
smb_get_krb5_error_message(context, ret, mem_ctx)));
return ret;
}
ret = krb5_verify_checksum(context,
crypto,
KRB5_KU_OTHER_CKSUM,
pac_data.data,
pac_data.length,
&cksum);
krb5_crypto_destroy(context, crypto);
return ret;
}
NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
struct PAC_DATA **pac_data_out,
DATA_BLOB blob,
krb5_context context,
krb5_keyblock *krbtgt_keyblock,
krb5_keyblock *service_keyblock,
krb5_const_principal client_principal,
time_t tgs_authtime,
krb5_error_code *k5ret)
{
krb5_error_code ret;
NTSTATUS status;
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_INFO *logon_info = NULL;
struct PAC_LOGON_NAME *logon_name = NULL;
struct PAC_DATA *pac_data;
struct PAC_DATA_RAW *pac_data_raw;
DATA_BLOB *srv_sig_blob = NULL;
DATA_BLOB *kdc_sig_blob = NULL;
DATA_BLOB modified_pac_blob;
NTTIME tgs_authtime_nttime;
krb5_principal client_principal_pac;
int i;
krb5_clear_error_string(context);
if (k5ret) {
*k5ret = KRB5_PARSE_MALFORMED;
}
pac_data = talloc(mem_ctx, struct PAC_DATA);
pac_data_raw = talloc(mem_ctx, struct PAC_DATA_RAW);
kdc_sig_wipe = talloc(mem_ctx, struct PAC_SIGNATURE_DATA);
srv_sig_wipe = talloc(mem_ctx, struct PAC_SIGNATURE_DATA);
if (!pac_data_raw || !pac_data || !kdc_sig_wipe || !srv_sig_wipe) {
if (k5ret) {
*k5ret = ENOMEM;
}
return NT_STATUS_NO_MEMORY;
}
status = ndr_pull_struct_blob(&blob, pac_data, pac_data,
(ndr_pull_flags_fn_t)ndr_pull_PAC_DATA);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("can't parse the PAC\n"));
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;
}
status = ndr_pull_struct_blob(&blob, pac_data_raw, pac_data_raw,
(ndr_pull_flags_fn_t)ndr_pull_PAC_DATA_RAW);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("can't parse the PAC\n"));
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 */
status = ndr_pull_struct_blob(kdc_sig_blob, kdc_sig_wipe, kdc_sig_wipe,
(ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("can't parse the KDC signature\n"));
return status;
}
status = ndr_pull_struct_blob(srv_sig_blob, srv_sig_wipe, srv_sig_wipe,
(ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("can't parse the SRV signature\n"));
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 */
status = ndr_push_struct_blob(kdc_sig_blob, pac_data_raw, kdc_sig_wipe,
(ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("can't repack the KDC signature\n"));
return status;
}
status = ndr_push_struct_blob(srv_sig_blob, pac_data_raw, srv_sig_wipe,
(ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("can't repack the SRV signature\n"));
return status;
}
/* push out the whole structure, but now with zero'ed signatures */
status = ndr_push_struct_blob(&modified_pac_blob, pac_data_raw, pac_data_raw,
(ndr_push_flags_fn_t)ndr_push_PAC_DATA_RAW);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("can't repack the RAW PAC\n"));
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",
smb_get_krb5_error_message(context, ret, mem_ctx)));
if (k5ret) {
*k5ret = ret;
}
return NT_STATUS_ACCESS_DENIED;
}
if (krbtgt_keyblock) {
ret = check_pac_checksum(mem_ctx,
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, mem_ctx)));
if (k5ret) {
*k5ret = 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 = 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,
smb_get_krb5_error_message(context, ret, mem_ctx)));
if (k5ret) {
*k5ret = ret;
}
return NT_STATUS_INVALID_PARAMETER;
}
if (!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));
return NT_STATUS_ACCESS_DENIED;
}
#if 0
if (strcasecmp(logon_info->info3.base.account_name.string,
"Administrator")== 0) {
file_save("tmp_pac_data-admin.dat",blob.data,blob.length);
}
#endif
DEBUG(0,("account_name: %s [%s]\n",
logon_info->info3.base.account_name.string,
logon_info->info3.base.full_name.string));
*pac_data_out = pac_data;
return status;
}
NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx,
struct PAC_LOGON_INFO **logon_info,
DATA_BLOB blob,
krb5_context context,
krb5_keyblock *krbtgt_keyblock,
krb5_keyblock *service_keyblock,
krb5_const_principal client_principal,
time_t tgs_authtime,
krb5_error_code *k5ret)
{
NTSTATUS nt_status;
struct PAC_DATA *pac_data;
int i;
nt_status = kerberos_decode_pac(mem_ctx, &pac_data,
blob,
context,
krbtgt_keyblock,
service_keyblock,
client_principal,
tgs_authtime,
k5ret);
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 krb5_error_code make_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_crypto crypto;
Checksum cksum;
ret = krb5_crypto_init(context,
keyblock,
0,
&crypto);
if (ret) {
DEBUG(0,("krb5_crypto_init() failed: %s\n",
smb_get_krb5_error_message(context, ret, mem_ctx)));
return ret;
}
ret = krb5_create_checksum(context,
crypto,
KRB5_KU_OTHER_CKSUM,
0,
pac_data->data,
pac_data->length,
&cksum);
if (ret) {
DEBUG(2, ("PAC Verification failed: %s\n",
smb_get_krb5_error_message(context, ret, mem_ctx)));
}
krb5_crypto_destroy(context, crypto);
if (ret) {
return ret;
}
sig->type = cksum.cksumtype;
sig->signature = data_blob_talloc(mem_ctx, cksum.checksum.data, cksum.checksum.length);
free_Checksum(&cksum);
return 0;
}
krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx,
struct PAC_DATA *pac_data,
krb5_context context,
krb5_keyblock *krbtgt_keyblock,
krb5_keyblock *service_keyblock,
DATA_BLOB *pac)
{
NTSTATUS nt_status;
krb5_error_code ret;
DATA_BLOB zero_blob = data_blob(NULL, 0);
DATA_BLOB tmp_blob = data_blob(NULL, 0);
struct PAC_SIGNATURE_DATA *kdc_checksum = NULL;
struct PAC_SIGNATURE_DATA *srv_checksum = NULL;
int i;
/* First, just get the keytypes filled in (and lengths right, eventually) */
for (i=0; i < pac_data->num_buffers; i++) {
if (pac_data->buffers[i].type != PAC_TYPE_KDC_CHECKSUM) {
continue;
}
kdc_checksum = &pac_data->buffers[i].info->kdc_cksum,
ret = make_pac_checksum(mem_ctx, &zero_blob,
kdc_checksum,
context, krbtgt_keyblock);
if (ret) {
DEBUG(2, ("making krbtgt PAC checksum failed: %s\n",
smb_get_krb5_error_message(context, ret, mem_ctx)));
talloc_free(pac_data);
return ret;
}
}
for (i=0; i < pac_data->num_buffers; i++) {
if (pac_data->buffers[i].type != PAC_TYPE_SRV_CHECKSUM) {
continue;
}
srv_checksum = &pac_data->buffers[i].info->srv_cksum;
ret = make_pac_checksum(mem_ctx, &zero_blob,
srv_checksum,
context, service_keyblock);
if (ret) {
DEBUG(2, ("making service PAC checksum failed: %s\n",
smb_get_krb5_error_message(context, ret, mem_ctx)));
talloc_free(pac_data);
return ret;
}
}
if (!kdc_checksum) {
DEBUG(2, ("Invalid PAC constructed for signing, no KDC checksum present!"));
return EINVAL;
}
if (!srv_checksum) {
DEBUG(2, ("Invalid PAC constructed for signing, no SRV checksum present!"));
return EINVAL;
}
/* But wipe out the actual signatures */
memset(kdc_checksum->signature.data, '\0', kdc_checksum->signature.length);
memset(srv_checksum->signature.data, '\0', srv_checksum->signature.length);
nt_status = ndr_push_struct_blob(&tmp_blob, mem_ctx, pac_data,
(ndr_push_flags_fn_t)ndr_push_PAC_DATA);
if (!NT_STATUS_IS_OK(nt_status)) {
DEBUG(1, ("PAC (presig) push failed: %s\n", nt_errstr(nt_status)));
talloc_free(pac_data);
return EINVAL;
}
/* Then sign the result of the previous push, where the sig was zero'ed out */
ret = make_pac_checksum(mem_ctx, &tmp_blob, srv_checksum,
context, service_keyblock);
/* Then sign Server checksum */
ret = make_pac_checksum(mem_ctx, &srv_checksum->signature, kdc_checksum, context, krbtgt_keyblock);
if (ret) {
DEBUG(2, ("making krbtgt PAC checksum failed: %s\n",
smb_get_krb5_error_message(context, ret, mem_ctx)));
talloc_free(pac_data);
return ret;
}
/* And push it out again, this time to the world. This relies on determanistic pointer values */
nt_status = ndr_push_struct_blob(&tmp_blob, mem_ctx, pac_data,
(ndr_push_flags_fn_t)ndr_push_PAC_DATA);
if (!NT_STATUS_IS_OK(nt_status)) {
DEBUG(1, ("PAC (final) push failed: %s\n", nt_errstr(nt_status)));
talloc_free(pac_data);
return EINVAL;
}
*pac = tmp_blob;
return ret;
}
krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx,
struct auth_serversupplied_info *server_info,
krb5_context context,
krb5_keyblock *krbtgt_keyblock,
krb5_keyblock *service_keyblock,
krb5_principal client_principal,
time_t tgs_authtime,
DATA_BLOB *pac)
{
NTSTATUS nt_status;
krb5_error_code ret;
struct PAC_DATA *pac_data = talloc(mem_ctx, struct PAC_DATA);
struct netr_SamInfo3 *sam3;
union PAC_INFO *u_LOGON_INFO;
struct PAC_LOGON_INFO *LOGON_INFO;
union PAC_INFO *u_LOGON_NAME;
struct PAC_LOGON_NAME *LOGON_NAME;
union PAC_INFO *u_KDC_CHECKSUM;
union PAC_INFO *u_SRV_CHECKSUM;
char *name;
enum {
PAC_BUF_LOGON_INFO = 0,
PAC_BUF_LOGON_NAME = 1,
PAC_BUF_SRV_CHECKSUM = 2,
PAC_BUF_KDC_CHECKSUM = 3,
PAC_BUF_NUM_BUFFERS = 4
};
if (!pac_data) {
return ENOMEM;
}
pac_data->num_buffers = PAC_BUF_NUM_BUFFERS;
pac_data->version = 0;
pac_data->buffers = talloc_array(pac_data,
struct PAC_BUFFER,
pac_data->num_buffers);
if (!pac_data->buffers) {
talloc_free(pac_data);
return ENOMEM;
}
/* LOGON_INFO */
u_LOGON_INFO = talloc_zero(pac_data->buffers, union PAC_INFO);
if (!u_LOGON_INFO) {
talloc_free(pac_data);
return ENOMEM;
}
pac_data->buffers[PAC_BUF_LOGON_INFO].type = PAC_TYPE_LOGON_INFO;
pac_data->buffers[PAC_BUF_LOGON_INFO].info = u_LOGON_INFO;
/* LOGON_NAME */
u_LOGON_NAME = talloc_zero(pac_data->buffers, union PAC_INFO);
if (!u_LOGON_NAME) {
talloc_free(pac_data);
return ENOMEM;
}
pac_data->buffers[PAC_BUF_LOGON_NAME].type = PAC_TYPE_LOGON_NAME;
pac_data->buffers[PAC_BUF_LOGON_NAME].info = u_LOGON_NAME;
LOGON_NAME = &u_LOGON_NAME->logon_name;
/* SRV_CHECKSUM */
u_SRV_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO);
if (!u_SRV_CHECKSUM) {
talloc_free(pac_data);
return ENOMEM;
}
pac_data->buffers[PAC_BUF_SRV_CHECKSUM].type = PAC_TYPE_SRV_CHECKSUM;
pac_data->buffers[PAC_BUF_SRV_CHECKSUM].info = u_SRV_CHECKSUM;
/* KDC_CHECKSUM */
u_KDC_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO);
if (!u_KDC_CHECKSUM) {
talloc_free(pac_data);
return ENOMEM;
}
pac_data->buffers[PAC_BUF_KDC_CHECKSUM].type = PAC_TYPE_KDC_CHECKSUM;
pac_data->buffers[PAC_BUF_KDC_CHECKSUM].info = u_KDC_CHECKSUM;
/* now the real work begins... */
LOGON_INFO = talloc_zero(u_LOGON_INFO, struct PAC_LOGON_INFO);
if (!LOGON_INFO) {
talloc_free(pac_data);
return ENOMEM;
}
nt_status = auth_convert_server_info_saminfo3(LOGON_INFO, server_info, &sam3);
if (!NT_STATUS_IS_OK(nt_status)) {
DEBUG(1, ("Getting Samba info failed: %s\n", nt_errstr(nt_status)));
talloc_free(pac_data);
return EINVAL;
}
u_LOGON_INFO->logon_info.info = LOGON_INFO;
LOGON_INFO->info3 = *sam3;
ret = krb5_unparse_name_norealm(context, client_principal, &name);
if (ret) {
return ret;
}
LOGON_NAME->account_name = talloc_strdup(LOGON_NAME, name);
free(name);
/*
this logon_time field is absolutely critical. This is what
caused all our PAC troubles :-)
*/
unix_to_nt_time(&LOGON_NAME->logon_time, tgs_authtime);
ret = kerberos_encode_pac(mem_ctx,
pac_data,
context,
krbtgt_keyblock,
service_keyblock,
pac);
talloc_free(pac_data);
return ret;
}