1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-04 05:18:06 +03:00
samba-mirror/source4/auth/gensec/gensec_krb5.c

804 lines
26 KiB
C
Raw Normal View History

/*
Unix SMB/CIFS implementation.
Kerberos backend for GENSEC
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
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 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"
#include "system/kerberos.h"
#include "auth/kerberos/kerberos.h"
#include "librpc/gen_ndr/krb5pac.h"
#include "auth/auth.h"
#include "lib/ldb/include/ldb.h"
#include "auth/auth_sam.h"
#include "system/network.h"
#include "lib/socket/socket.h"
#include "librpc/rpc/dcerpc.h"
#include "auth/credentials/credentials.h"
#include "auth/credentials/credentials_krb5.h"
#include "auth/gensec/gensec.h"
#include "auth/gensec/gensec_proto.h"
#include "param/param.h"
#include "auth/session_proto.h"
enum GENSEC_KRB5_STATE {
GENSEC_KRB5_SERVER_START,
GENSEC_KRB5_CLIENT_START,
GENSEC_KRB5_CLIENT_MUTUAL_AUTH,
GENSEC_KRB5_DONE
};
struct gensec_krb5_state {
DATA_BLOB session_key;
DATA_BLOB pac;
enum GENSEC_KRB5_STATE state_position;
struct smb_krb5_context *smb_krb5_context;
krb5_auth_context auth_context;
krb5_data enc_ticket;
krb5_keyblock *keyblock;
krb5_ticket *ticket;
bool gssapi;
};
static int gensec_krb5_destroy(struct gensec_krb5_state *gensec_krb5_state)
{
if (!gensec_krb5_state->smb_krb5_context) {
/* We can't clean anything else up unless we started up this far */
return 0;
}
if (gensec_krb5_state->enc_ticket.length) {
kerberos_free_data_contents(gensec_krb5_state->smb_krb5_context->krb5_context,
&gensec_krb5_state->enc_ticket);
}
if (gensec_krb5_state->ticket) {
krb5_free_ticket(gensec_krb5_state->smb_krb5_context->krb5_context,
gensec_krb5_state->ticket);
}
/* ccache freed in a child destructor */
krb5_free_keyblock(gensec_krb5_state->smb_krb5_context->krb5_context,
gensec_krb5_state->keyblock);
if (gensec_krb5_state->auth_context) {
krb5_auth_con_free(gensec_krb5_state->smb_krb5_context->krb5_context,
gensec_krb5_state->auth_context);
}
return 0;
}
static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security)
{
krb5_error_code ret;
struct gensec_krb5_state *gensec_krb5_state;
struct cli_credentials *creds;
const struct socket_address *my_addr, *peer_addr;
krb5_address my_krb5_addr, peer_krb5_addr;
creds = gensec_get_credentials(gensec_security);
if (!creds) {
return NT_STATUS_INVALID_PARAMETER;
}
gensec_krb5_state = talloc(gensec_security, struct gensec_krb5_state);
if (!gensec_krb5_state) {
return NT_STATUS_NO_MEMORY;
}
gensec_security->private_data = gensec_krb5_state;
gensec_krb5_state->smb_krb5_context = NULL;
gensec_krb5_state->auth_context = NULL;
gensec_krb5_state->ticket = NULL;
ZERO_STRUCT(gensec_krb5_state->enc_ticket);
gensec_krb5_state->keyblock = NULL;
gensec_krb5_state->session_key = data_blob(NULL, 0);
gensec_krb5_state->pac = data_blob(NULL, 0);
gensec_krb5_state->gssapi = false;
talloc_set_destructor(gensec_krb5_state, gensec_krb5_destroy);
if (cli_credentials_get_krb5_context(creds, gensec_security->lp_ctx, &gensec_krb5_state->smb_krb5_context)) {
talloc_free(gensec_krb5_state);
return NT_STATUS_INTERNAL_ERROR;
}
ret = krb5_auth_con_init(gensec_krb5_state->smb_krb5_context->krb5_context, &gensec_krb5_state->auth_context);
if (ret) {
DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
ret, gensec_krb5_state)));
talloc_free(gensec_krb5_state);
return NT_STATUS_INTERNAL_ERROR;
}
ret = krb5_auth_con_setflags(gensec_krb5_state->smb_krb5_context->krb5_context,
gensec_krb5_state->auth_context,
KRB5_AUTH_CONTEXT_DO_SEQUENCE);
if (ret) {
DEBUG(1,("gensec_krb5_start: krb5_auth_con_setflags failed (%s)\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
ret, gensec_krb5_state)));
talloc_free(gensec_krb5_state);
return NT_STATUS_INTERNAL_ERROR;
}
my_addr = gensec_get_my_addr(gensec_security);
if (my_addr && my_addr->sockaddr) {
ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
my_addr->sockaddr, &my_krb5_addr);
if (ret) {
DEBUG(1,("gensec_krb5_start: krb5_sockaddr2address (local) failed (%s)\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
ret, gensec_krb5_state)));
talloc_free(gensec_krb5_state);
return NT_STATUS_INTERNAL_ERROR;
}
}
peer_addr = gensec_get_peer_addr(gensec_security);
if (peer_addr && peer_addr->sockaddr) {
ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
peer_addr->sockaddr, &peer_krb5_addr);
if (ret) {
DEBUG(1,("gensec_krb5_start: krb5_sockaddr2address (local) failed (%s)\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
ret, gensec_krb5_state)));
talloc_free(gensec_krb5_state);
return NT_STATUS_INTERNAL_ERROR;
}
}
ret = krb5_auth_con_setaddrs(gensec_krb5_state->smb_krb5_context->krb5_context,
gensec_krb5_state->auth_context,
my_addr ? &my_krb5_addr : NULL,
peer_addr ? &peer_krb5_addr : NULL);
if (ret) {
DEBUG(1,("gensec_krb5_start: krb5_auth_con_setaddrs failed (%s)\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
ret, gensec_krb5_state)));
talloc_free(gensec_krb5_state);
return NT_STATUS_INTERNAL_ERROR;
}
return NT_STATUS_OK;
}
static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security)
{
NTSTATUS nt_status;
struct gensec_krb5_state *gensec_krb5_state;
nt_status = gensec_krb5_start(gensec_security);
if (!NT_STATUS_IS_OK(nt_status)) {
return nt_status;
}
gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
gensec_krb5_state->state_position = GENSEC_KRB5_SERVER_START;
return NT_STATUS_OK;
}
static NTSTATUS gensec_fake_gssapi_krb5_server_start(struct gensec_security *gensec_security)
{
NTSTATUS nt_status = gensec_krb5_server_start(gensec_security);
if (NT_STATUS_IS_OK(nt_status)) {
struct gensec_krb5_state *gensec_krb5_state;
gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
gensec_krb5_state->gssapi = true;
}
return nt_status;
}
static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security)
{
struct gensec_krb5_state *gensec_krb5_state;
krb5_error_code ret;
NTSTATUS nt_status;
struct ccache_container *ccache_container;
const char *hostname;
krb5_flags ap_req_options = AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED;
const char *principal;
krb5_data in_data;
hostname = gensec_get_target_hostname(gensec_security);
r6028: A MAJOR update to intergrate the new credentails system fully with GENSEC, and to pull SCHANNEL into GENSEC, by making it less 'special'. GENSEC now no longer has it's own handling of 'set username' etc, instead it uses cli_credentials calls. In order to link the credentails code right though Samba, a lot of interfaces have changed to remove 'username, domain, password' arguments, and these have been replaced with a single 'struct cli_credentials'. In the session setup code, a new parameter 'workgroup' contains the client/server current workgroup, which seems unrelated to the authentication exchange (it was being filled in from the auth info). This allows in particular kerberos to only call back for passwords when it actually needs to perform the kinit. The kerberos code has been modified not to use the SPNEGO provided 'principal name' (in the mechListMIC), but to instead use the name the host was connected to as. This better matches Microsoft behaviour, is more secure and allows better use of standard kerberos functions. To achieve this, I made changes to our socket code so that the hostname (before name resolution) is now recorded on the socket. In schannel, most of the code from librpc/rpc/dcerpc_schannel.c is now in libcli/auth/schannel.c, and it looks much more like a standard GENSEC module. The actual sign/seal code moved to libcli/auth/schannel_sign.c in a previous commit. The schannel credentails structure is now merged with the rest of the credentails, as many of the values (username, workstation, domain) where already present there. This makes handling this in a generic manner much easier, as there is no longer a custom entry-point. The auth_domain module continues to be developed, but is now just as functional as auth_winbind. The changes here are consequential to the schannel changes. The only removed function at this point is the RPC-LOGIN test (simulating the load of a WinXP login), which needs much more work to clean it up (it contains copies of too much code from all over the torture suite, and I havn't been able to penetrate its 'structure'). Andrew Bartlett (This used to be commit 2301a4b38a21aa60917973451687063d83d18d66)
2005-03-24 07:14:06 +03:00
if (!hostname) {
DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n"));
return NT_STATUS_INVALID_PARAMETER;
}
if (is_ipaddress(hostname)) {
DEBUG(2, ("Cannot do krb5 to an IP address"));
return NT_STATUS_INVALID_PARAMETER;
r6028: A MAJOR update to intergrate the new credentails system fully with GENSEC, and to pull SCHANNEL into GENSEC, by making it less 'special'. GENSEC now no longer has it's own handling of 'set username' etc, instead it uses cli_credentials calls. In order to link the credentails code right though Samba, a lot of interfaces have changed to remove 'username, domain, password' arguments, and these have been replaced with a single 'struct cli_credentials'. In the session setup code, a new parameter 'workgroup' contains the client/server current workgroup, which seems unrelated to the authentication exchange (it was being filled in from the auth info). This allows in particular kerberos to only call back for passwords when it actually needs to perform the kinit. The kerberos code has been modified not to use the SPNEGO provided 'principal name' (in the mechListMIC), but to instead use the name the host was connected to as. This better matches Microsoft behaviour, is more secure and allows better use of standard kerberos functions. To achieve this, I made changes to our socket code so that the hostname (before name resolution) is now recorded on the socket. In schannel, most of the code from librpc/rpc/dcerpc_schannel.c is now in libcli/auth/schannel.c, and it looks much more like a standard GENSEC module. The actual sign/seal code moved to libcli/auth/schannel_sign.c in a previous commit. The schannel credentails structure is now merged with the rest of the credentails, as many of the values (username, workstation, domain) where already present there. This makes handling this in a generic manner much easier, as there is no longer a custom entry-point. The auth_domain module continues to be developed, but is now just as functional as auth_winbind. The changes here are consequential to the schannel changes. The only removed function at this point is the RPC-LOGIN test (simulating the load of a WinXP login), which needs much more work to clean it up (it contains copies of too much code from all over the torture suite, and I havn't been able to penetrate its 'structure'). Andrew Bartlett (This used to be commit 2301a4b38a21aa60917973451687063d83d18d66)
2005-03-24 07:14:06 +03:00
}
if (strcmp(hostname, "localhost") == 0) {
DEBUG(2, ("krb5 to 'localhost' does not make sense"));
return NT_STATUS_INVALID_PARAMETER;
}
r6028: A MAJOR update to intergrate the new credentails system fully with GENSEC, and to pull SCHANNEL into GENSEC, by making it less 'special'. GENSEC now no longer has it's own handling of 'set username' etc, instead it uses cli_credentials calls. In order to link the credentails code right though Samba, a lot of interfaces have changed to remove 'username, domain, password' arguments, and these have been replaced with a single 'struct cli_credentials'. In the session setup code, a new parameter 'workgroup' contains the client/server current workgroup, which seems unrelated to the authentication exchange (it was being filled in from the auth info). This allows in particular kerberos to only call back for passwords when it actually needs to perform the kinit. The kerberos code has been modified not to use the SPNEGO provided 'principal name' (in the mechListMIC), but to instead use the name the host was connected to as. This better matches Microsoft behaviour, is more secure and allows better use of standard kerberos functions. To achieve this, I made changes to our socket code so that the hostname (before name resolution) is now recorded on the socket. In schannel, most of the code from librpc/rpc/dcerpc_schannel.c is now in libcli/auth/schannel.c, and it looks much more like a standard GENSEC module. The actual sign/seal code moved to libcli/auth/schannel_sign.c in a previous commit. The schannel credentails structure is now merged with the rest of the credentails, as many of the values (username, workstation, domain) where already present there. This makes handling this in a generic manner much easier, as there is no longer a custom entry-point. The auth_domain module continues to be developed, but is now just as functional as auth_winbind. The changes here are consequential to the schannel changes. The only removed function at this point is the RPC-LOGIN test (simulating the load of a WinXP login), which needs much more work to clean it up (it contains copies of too much code from all over the torture suite, and I havn't been able to penetrate its 'structure'). Andrew Bartlett (This used to be commit 2301a4b38a21aa60917973451687063d83d18d66)
2005-03-24 07:14:06 +03:00
nt_status = gensec_krb5_start(gensec_security);
if (!NT_STATUS_IS_OK(nt_status)) {
return nt_status;
}
gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START;
principal = gensec_get_target_principal(gensec_security);
ret = cli_credentials_get_ccache(gensec_get_credentials(gensec_security), gensec_security->lp_ctx, &ccache_container);
switch (ret) {
case 0:
break;
case KRB5KDC_ERR_PREAUTH_FAILED:
return NT_STATUS_LOGON_FAILURE;
case KRB5_KDC_UNREACH:
DEBUG(3, ("Cannot reach a KDC we require to contact %s\n", principal));
return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
default:
DEBUG(1, ("gensec_krb5_start: Aquiring initiator credentials failed: %s\n", error_message(ret)));
return NT_STATUS_UNSUCCESSFUL;
}
in_data.length = 0;
if (principal && lp_client_use_spnego_principal(gensec_security->lp_ctx)) {
krb5_principal target_principal;
ret = krb5_parse_name(gensec_krb5_state->smb_krb5_context->krb5_context, principal,
&target_principal);
if (ret == 0) {
ret = krb5_mk_req_exact(gensec_krb5_state->smb_krb5_context->krb5_context,
&gensec_krb5_state->auth_context,
ap_req_options,
target_principal,
&in_data, ccache_container->ccache,
&gensec_krb5_state->enc_ticket);
krb5_free_principal(gensec_krb5_state->smb_krb5_context->krb5_context,
target_principal);
}
} else {
ret = krb5_mk_req(gensec_krb5_state->smb_krb5_context->krb5_context,
&gensec_krb5_state->auth_context,
ap_req_options,
gensec_get_target_service(gensec_security),
hostname,
&in_data, ccache_container->ccache,
&gensec_krb5_state->enc_ticket);
}
switch (ret) {
case 0:
return NT_STATUS_OK;
case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
DEBUG(3, ("Server [%s] is not registered with our KDC: %s\n",
hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
case KRB5_KDC_UNREACH:
DEBUG(3, ("Cannot reach a KDC we require to contact host [%s]: %s\n",
hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
case KRB5KDC_ERR_PREAUTH_FAILED:
case KRB5KRB_AP_ERR_TKT_EXPIRED:
case KRB5_CC_END:
/* Too much clock skew - we will need to kinit to re-skew the clock */
case KRB5KRB_AP_ERR_SKEW:
case KRB5_KDCREP_SKEW:
{
DEBUG(3, ("kerberos (mk_req) failed: %s\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
/*fall through*/
}
/* just don't print a message for these really ordinary messages */
case KRB5_FCC_NOFILE:
case KRB5_CC_NOTFOUND:
case ENOENT:
return NT_STATUS_UNSUCCESSFUL;
break;
default:
DEBUG(0, ("kerberos: %s\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
return NT_STATUS_UNSUCCESSFUL;
}
}
static NTSTATUS gensec_fake_gssapi_krb5_client_start(struct gensec_security *gensec_security)
{
NTSTATUS nt_status = gensec_krb5_client_start(gensec_security);
if (NT_STATUS_IS_OK(nt_status)) {
struct gensec_krb5_state *gensec_krb5_state;
gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
gensec_krb5_state->gssapi = true;
}
return nt_status;
}
/**
* Check if the packet is one for this mechansim
*
* @param gensec_security GENSEC state
* @param in The request, as a DATA_BLOB
* @return Error, INVALID_PARAMETER if it's not a packet for us
* or NT_STATUS_OK if the packet is ok.
*/
static NTSTATUS gensec_fake_gssapi_krb5_magic(struct gensec_security *gensec_security,
const DATA_BLOB *in)
{
if (gensec_gssapi_check_oid(in, GENSEC_OID_KERBEROS5)) {
return NT_STATUS_OK;
} else {
return NT_STATUS_INVALID_PARAMETER;
}
}
/**
* Next state function for the Krb5 GENSEC mechanism
*
* @param gensec_krb5_state KRB5 State
* @param out_mem_ctx The TALLOC_CTX for *out to be allocated on
* @param in The request, as a DATA_BLOB
* @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx
* @return Error, MORE_PROCESSING_REQUIRED if a reply is sent,
* or NT_STATUS_OK if the user is authenticated.
*/
static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
TALLOC_CTX *out_mem_ctx,
const DATA_BLOB in, DATA_BLOB *out)
{
struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
krb5_error_code ret = 0;
NTSTATUS nt_status;
switch (gensec_krb5_state->state_position) {
case GENSEC_KRB5_CLIENT_START:
{
DATA_BLOB unwrapped_out;
if (gensec_krb5_state->gssapi) {
unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length);
/* wrap that up in a nice GSS-API wrapping */
*out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ);
} else {
*out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length);
}
gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH;
nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
return nt_status;
}
case GENSEC_KRB5_CLIENT_MUTUAL_AUTH:
{
DATA_BLOB unwrapped_in;
krb5_data inbuf;
krb5_ap_rep_enc_part *repl = NULL;
uint8_t tok_id[2];
if (gensec_krb5_state->gssapi) {
if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
DEBUG(1,("gensec_gssapi_parse_krb5_wrap(mutual authentication) failed to parse\n"));
dump_data_pw("Mutual authentication message:\n", in.data, in.length);
return NT_STATUS_INVALID_PARAMETER;
}
} else {
unwrapped_in = in;
}
/* TODO: check the tok_id */
inbuf.data = unwrapped_in.data;
inbuf.length = unwrapped_in.length;
ret = krb5_rd_rep(gensec_krb5_state->smb_krb5_context->krb5_context,
gensec_krb5_state->auth_context,
&inbuf, &repl);
if (ret) {
DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx)));
dump_data_pw("Mutual authentication message:\n", (uint8_t *)inbuf.data, inbuf.length);
nt_status = NT_STATUS_ACCESS_DENIED;
} else {
*out = data_blob(NULL, 0);
nt_status = NT_STATUS_OK;
gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
}
if (repl) {
krb5_free_ap_rep_enc_part(gensec_krb5_state->smb_krb5_context->krb5_context, repl);
}
return nt_status;
}
case GENSEC_KRB5_SERVER_START:
{
DATA_BLOB unwrapped_in;
DATA_BLOB unwrapped_out = data_blob(NULL, 0);
krb5_data inbuf, outbuf;
uint8_t tok_id[2];
struct keytab_container *keytab;
krb5_principal server_in_keytab;
if (!in.data) {
return NT_STATUS_INVALID_PARAMETER;
}
/* Grab the keytab, however generated */
ret = cli_credentials_get_keytab(gensec_get_credentials(gensec_security), gensec_security->lp_ctx, &keytab);
if (ret) {
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
/* This ensures we lookup the correct entry in that keytab */
ret = principal_from_credentials(out_mem_ctx, gensec_get_credentials(gensec_security),
gensec_krb5_state->smb_krb5_context,
&server_in_keytab);
if (ret) {
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
/* Parse the GSSAPI wrapping, if it's there... (win2k3 allows it to be omited) */
if (gensec_krb5_state->gssapi
&& gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
inbuf.data = unwrapped_in.data;
inbuf.length = unwrapped_in.length;
} else {
inbuf.data = in.data;
inbuf.length = in.length;
}
ret = smb_rd_req_return_stuff(gensec_krb5_state->smb_krb5_context->krb5_context,
&gensec_krb5_state->auth_context,
&inbuf, keytab->keytab, server_in_keytab,
&outbuf,
&gensec_krb5_state->ticket,
&gensec_krb5_state->keyblock);
if (ret) {
return NT_STATUS_LOGON_FAILURE;
}
unwrapped_out.data = (uint8_t *)outbuf.data;
unwrapped_out.length = outbuf.length;
gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
/* wrap that up in a nice GSS-API wrapping */
if (gensec_krb5_state->gssapi) {
*out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP);
} else {
*out = data_blob_talloc(out_mem_ctx, outbuf.data, outbuf.length);
}
krb5_data_free(&outbuf);
return NT_STATUS_OK;
}
case GENSEC_KRB5_DONE:
default:
/* Asking too many times... */
return NT_STATUS_INVALID_PARAMETER;
}
}
static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
DATA_BLOB *session_key)
{
struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
krb5_auth_context auth_context = gensec_krb5_state->auth_context;
krb5_keyblock *skey;
krb5_error_code err = -1;
if (gensec_krb5_state->session_key.data) {
*session_key = gensec_krb5_state->session_key;
return NT_STATUS_OK;
}
switch (gensec_security->gensec_role) {
case GENSEC_CLIENT:
err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey);
break;
case GENSEC_SERVER:
err = krb5_auth_con_getremotesubkey(context, auth_context, &skey);
break;
}
if (err == 0 && skey != NULL) {
DEBUG(10, ("Got KRB5 session key of length %d\n",
(int)KRB5_KEY_LENGTH(skey)));
gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state,
KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
*session_key = gensec_krb5_state->session_key;
dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length);
krb5_free_keyblock(context, skey);
return NT_STATUS_OK;
} else {
DEBUG(10, ("KRB5 error getting session key %d\n", err));
return NT_STATUS_NO_USER_SESSION_KEY;
}
}
static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security,
struct auth_session_info **_session_info)
{
NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
struct auth_serversupplied_info *server_info = NULL;
struct auth_session_info *session_info = NULL;
struct PAC_LOGON_INFO *logon_info;
krb5_principal client_principal;
char *principal_string;
DATA_BLOB pac;
krb5_data pac_data;
krb5_error_code ret;
TALLOC_CTX *mem_ctx = talloc_new(gensec_security);
if (!mem_ctx) {
return NT_STATUS_NO_MEMORY;
}
ret = krb5_ticket_get_client(context, gensec_krb5_state->ticket, &client_principal);
if (ret) {
DEBUG(5, ("krb5_ticket_get_client failed to get cleint principal: %s\n",
smb_get_krb5_error_message(context,
ret, mem_ctx)));
talloc_free(mem_ctx);
return NT_STATUS_NO_MEMORY;
}
ret = krb5_unparse_name(gensec_krb5_state->smb_krb5_context->krb5_context,
client_principal, &principal_string);
if (ret) {
DEBUG(1, ("Unable to parse client principal: %s\n",
smb_get_krb5_error_message(context,
ret, mem_ctx)));
talloc_free(mem_ctx);
return NT_STATUS_NO_MEMORY;
}
ret = krb5_ticket_get_authorization_data_type(context, gensec_krb5_state->ticket,
KRB5_AUTHDATA_WIN2K_PAC,
&pac_data);
if (ret && lp_parm_bool(gensec_security->lp_ctx, NULL, "gensec", "require_pac", false)) {
DEBUG(1, ("Unable to find PAC in ticket from %s, failing to allow access: %s \n",
principal_string,
smb_get_krb5_error_message(context,
ret, mem_ctx)));
krb5_free_principal(context, client_principal);
free(principal_string);
return NT_STATUS_ACCESS_DENIED;
} else if (ret) {
/* NO pac */
DEBUG(5, ("krb5_ticket_get_authorization_data_type failed to find PAC: %s\n",
smb_get_krb5_error_message(context,
ret, mem_ctx)));
nt_status = sam_get_server_info_principal(mem_ctx, gensec_security->lp_ctx, principal_string,
&server_info);
krb5_free_principal(context, client_principal);
free(principal_string);
if (!NT_STATUS_IS_OK(nt_status)) {
talloc_free(mem_ctx);
return nt_status;
}
} else {
/* Found pac */
union netr_Validation validation;
free(principal_string);
pac = data_blob_talloc(mem_ctx, pac_data.data, pac_data.length);
if (!pac.data) {
krb5_free_principal(context, client_principal);
talloc_free(mem_ctx);
return NT_STATUS_NO_MEMORY;
}
/* decode and verify the pac */
nt_status = kerberos_pac_logon_info(gensec_krb5_state,
lp_iconv_convenience(gensec_security->lp_ctx),
&logon_info, pac,
gensec_krb5_state->smb_krb5_context->krb5_context,
NULL, gensec_krb5_state->keyblock,
client_principal,
gensec_krb5_state->ticket->ticket.authtime, NULL);
krb5_free_principal(context, client_principal);
if (!NT_STATUS_IS_OK(nt_status)) {
talloc_free(mem_ctx);
return nt_status;
}
validation.sam3 = &logon_info->info3;
nt_status = make_server_info_netlogon_validation(mem_ctx,
NULL,
3, &validation,
&server_info);
if (!NT_STATUS_IS_OK(nt_status)) {
talloc_free(mem_ctx);
return nt_status;
}
}
/* references the server_info into the session_info */
nt_status = auth_generate_session_info(mem_ctx, gensec_security->lp_ctx, server_info, &session_info);
if (!NT_STATUS_IS_OK(nt_status)) {
talloc_free(mem_ctx);
return nt_status;
}
nt_status = gensec_krb5_session_key(gensec_security, &session_info->session_key);
if (!NT_STATUS_IS_OK(nt_status)) {
talloc_free(mem_ctx);
return nt_status;
}
*_session_info = session_info;
talloc_steal(gensec_krb5_state, session_info);
talloc_free(mem_ctx);
return NT_STATUS_OK;
}
static NTSTATUS gensec_krb5_wrap(struct gensec_security *gensec_security,
TALLOC_CTX *mem_ctx,
const DATA_BLOB *in,
DATA_BLOB *out)
{
struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
krb5_auth_context auth_context = gensec_krb5_state->auth_context;
krb5_error_code ret;
krb5_data input, output;
input.length = in->length;
input.data = in->data;
if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
ret = krb5_mk_priv(context, auth_context, &input, &output, NULL);
if (ret) {
DEBUG(1, ("krb5_mk_priv failed: %s\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
ret, mem_ctx)));
return NT_STATUS_ACCESS_DENIED;
}
*out = data_blob_talloc(mem_ctx, output.data, output.length);
krb5_data_free(&output);
} else {
return NT_STATUS_ACCESS_DENIED;
}
return NT_STATUS_OK;
}
static NTSTATUS gensec_krb5_unwrap(struct gensec_security *gensec_security,
TALLOC_CTX *mem_ctx,
const DATA_BLOB *in,
DATA_BLOB *out)
{
struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
krb5_auth_context auth_context = gensec_krb5_state->auth_context;
krb5_error_code ret;
krb5_data input, output;
krb5_replay_data replay;
input.length = in->length;
input.data = in->data;
if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
ret = krb5_rd_priv(context, auth_context, &input, &output, &replay);
if (ret) {
DEBUG(1, ("krb5_rd_priv failed: %s\n",
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
ret, mem_ctx)));
return NT_STATUS_ACCESS_DENIED;
}
*out = data_blob_talloc(mem_ctx, output.data, output.length);
krb5_data_free(&output);
} else {
return NT_STATUS_ACCESS_DENIED;
}
return NT_STATUS_OK;
}
static bool gensec_krb5_have_feature(struct gensec_security *gensec_security,
uint32_t feature)
{
struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
if (feature & GENSEC_FEATURE_SESSION_KEY) {
return true;
}
if (!gensec_krb5_state->gssapi &&
(feature & GENSEC_FEATURE_SEAL)) {
return true;
}
return false;
}
static const char *gensec_krb5_oids[] = {
GENSEC_OID_KERBEROS5,
GENSEC_OID_KERBEROS5_OLD,
NULL
};
static const struct gensec_security_ops gensec_fake_gssapi_krb5_security_ops = {
.name = "fake_gssapi_krb5",
.auth_type = DCERPC_AUTH_TYPE_KRB5,
.oid = gensec_krb5_oids,
.client_start = gensec_fake_gssapi_krb5_client_start,
.server_start = gensec_fake_gssapi_krb5_server_start,
.update = gensec_krb5_update,
.magic = gensec_fake_gssapi_krb5_magic,
.session_key = gensec_krb5_session_key,
.session_info = gensec_krb5_session_info,
.have_feature = gensec_krb5_have_feature,
.enabled = false,
.kerberos = true,
.priority = GENSEC_KRB5
};
static const struct gensec_security_ops gensec_krb5_security_ops = {
.name = "krb5",
.client_start = gensec_krb5_client_start,
.server_start = gensec_krb5_server_start,
.update = gensec_krb5_update,
.session_key = gensec_krb5_session_key,
.session_info = gensec_krb5_session_info,
.have_feature = gensec_krb5_have_feature,
.wrap = gensec_krb5_wrap,
.unwrap = gensec_krb5_unwrap,
.enabled = true,
.kerberos = true,
.priority = GENSEC_KRB5
};
_PUBLIC_ NTSTATUS gensec_krb5_init(void)
{
NTSTATUS ret;
auth_init();
ret = gensec_register(&gensec_krb5_security_ops);
if (!NT_STATUS_IS_OK(ret)) {
DEBUG(0,("Failed to register '%s' gensec backend!\n",
gensec_krb5_security_ops.name));
return ret;
}
ret = gensec_register(&gensec_fake_gssapi_krb5_security_ops);
if (!NT_STATUS_IS_OK(ret)) {
DEBUG(0,("Failed to register '%s' gensec backend!\n",
gensec_krb5_security_ops.name));
return ret;
}
return ret;
}