[PATCH] Mutual authentication, keytabs, and SMB session keys

Andrew Bartlett abartlet at samba.org
Sun Feb 23 11:02:44 GMT 2003


On Sun, 2003-02-23 at 13:06, Luke Howard wrote:
> 
> The attach patch contains the following enhancements to HEAD:
> 
> 1. Support for using Kerberos keytabs (configure with --enable-keytab)
>    instead of the secrets database, for the local machine password.

Given the non-invasive nature of this patch, I'll add it without the
#ifdef.  Would it be possible for you to add 'write' support to this? 
(Changing the password and storing it into that keytab).

> 2. Mutual Kerberos GSS-API SPNEGO authentication.

Nice!  

Any chance we can implement this in our client code?  This would
certainly help in testing.

> 3. Support for extracting the SMB session key from ticket session keys
>    (useful for named pipe services that might need access to the session
>    key)

Have you looked at SMB signing with this key?  Or how SMB signing is
done on a kerberos connection?

> Caveats: I haven't tested for MIT compat (although I have tried to avoid
> any obvious breakage), and because ENCTYPE_ARCFOUR_HMAC_MD5 is an enum
> in Heimdal, it should really be tested for in configure... 

Yes - can you knock up such a test?

> I have tested this patch with Windows 2000 and Windows XP clients. One
> thing I didn't test is NTLMSSP over SPNEGO: hopefully I haven't broken
> anything.

That part looks fine to me.

> cheers,
> 
> -- Luke
> 
> ----
> 

More comments below:

> Index: libads/krb5_setpw.c
> ===================================================================
> RCS file: /cvsroot/samba/source/libads/krb5_setpw.c,v
> retrieving revision 1.13
> diff -u -r1.13 krb5_setpw.c
> --- libads/krb5_setpw.c	19 Feb 2003 20:37:34 -0000	1.13
> +++ libads/krb5_setpw.c	23 Feb 2003 01:51:12 -0000
> @@ -326,10 +326,12 @@
>  			case KRB5_KPASSWD_ACCESSDENIED:
>  				return KRB5KDC_ERR_BADOPTION;
>  				break;
> +#ifdef KV5M_ALT_METHOD
>  			case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
>  				return KRB5KDC_ERR_BADOPTION;
>  				/* return KV5M_ALT_METHOD; MIT-only define */
>  				break;
> +#endif

I'm not quite sure what you are doing here...

>  			case KRB5_KPASSWD_ETYPE_NOSUPP:
>  				return KRB5KDC_ERR_ETYPE_NOSUPP;
>  				break;
> Index: libsmb/clikrb5.c
> ===================================================================
> RCS file: /cvsroot/samba/source/libsmb/clikrb5.c,v
> retrieving revision 1.32
> diff -u -r1.32 clikrb5.c
> --- libsmb/clikrb5.c	19 Feb 2003 15:46:15 -0000	1.32
> +++ libsmb/clikrb5.c	23 Feb 2003 01:51:12 -0000
> @@ -52,6 +52,10 @@
>  
>  #if defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS)
>  /* HEIMDAL */
> +#define KRB5_KEY_TYPE(k)	((k)->keytype)
> +#define KRB5_KEY_LENGTH(k)	((k)->keyvalue.length)
> +#define KRB5_KEY_DATA(k)	((k)->keyvalue.data)
> +
>   void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr)
>  {
>  	pkaddr->addr_type = KRB5_ADDRESS_INET;
> @@ -60,6 +64,10 @@
>  }
>  #elif defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS)
>  /* MIT */
> +#define	KRB5_KEY_TYPE(k)	((k)->enctype)
> +#define KRB5_KEY_LENGTH(k)	((k)->length)
> +#define KRB5_KEY_DATA(k)	((k)->contents)
> +
>   void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr)
>  {
>  	pkaddr->addrtype = ADDRTYPE_INET;
> @@ -304,8 +312,8 @@
>  	DATA_BLOB ret;
>  	krb5_enctype enc_types[] = {
>  #ifdef ENCTYPE_ARCFOUR_HMAC
> -		ENCTYPE_ARCFOUR_HMAC, 
> -#endif
> +		ENCTYPE_ARCFOUR_HMAC,
> +#endif 
>  				    ENCTYPE_DES_CBC_MD5, 
>  				    ENCTYPE_DES_CBC_CRC, 
>  				    ENCTYPE_NULL};
> @@ -354,11 +362,39 @@
>  	return data_blob(NULL, 0);
>  }
>  
> + BOOL krb5_get_smb_session_key(krb5_context context, krb5_auth_context auth_context, uint8 session_key[16])
> + {
> +	krb5_keyblock *skey;
> +	BOOL ret = FALSE;
> +
> +	memset(session_key, 0, 16);
> +
> +#ifdef ENCTYPE_ARCFOUR_HMAC
> +	/* NB: for this to work with Heimdal, -DENCTYPE_ARCFOUR_HMAC=ENCTYPE_ARCFOUR_HMAC_MD5 */
> +	if (krb5_auth_con_getremotesubkey(context, auth_context, &skey) == 0 && skey != NULL) {
> +		if (KRB5_KEY_TYPE(skey) == ENCTYPE_ARCFOUR_HMAC &&
> +		    KRB5_KEY_LENGTH(skey) == 16) {
> +			memcpy(session_key, KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
> +			ret = TRUE;
> +		}
> +		krb5_free_keyblock(context, skey);
> +	}
> +#endif /* ENCTYPE_ARCFOUR_HMAC */

Firstly - can we have a configure test for the Heimdal name.  Secondly -
why is this restricted to the type 23 key?   If we do a login without a
type 23 key, why can't we use some other session key?  Or will the
client indicate what session key to use?

Secondly, I'm not quite sure why this isn't in kerberos_verify.c?  Or if
we can also use this client-side, can you add that?  It would greatly
assist in testing...

> +
> +	return ret;
> + }
>  #else /* HAVE_KRB5 */
>   /* this saves a few linking headaches */
>   DATA_BLOB krb5_get_ticket(const char *principal, time_t time_offset)
>   {
>  	 DEBUG(0,("NO KERBEROS SUPPORT\n"));
>  	 return data_blob(NULL, 0);
> + }
> +
> + BOOL krb5_get_smb_session_key(krb5_context context, krb5_auth_context ac, uint8 session_key[16])
> + {
> +	DEBUG(0,("NO KERBEROS SUPPORT\n"));
> +	memset(session_key, 0, 16);
> +	return FALSE;

This needs to be 'False', as FALSE isn't a portable define.

>   }
>  #endif
> Index: libsmb/clispnego.c
> ===================================================================
> RCS file: /cvsroot/samba/source/libsmb/clispnego.c,v
> retrieving revision 1.28
> diff -u -r1.28 clispnego.c
> --- libsmb/clispnego.c	15 Feb 2003 12:20:22 -0000	1.28
> +++ libsmb/clispnego.c	23 Feb 2003 01:51:12 -0000
> @@ -3,6 +3,7 @@
>     simple kerberos5/SPNEGO routines
>     Copyright (C) Andrew Tridgell 2001
>     Copyright (C) Jim McDonough   2002
> +   Copyright (C) Luke Howard     2003
>     
>     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
> @@ -259,16 +260,19 @@
>  /*
>    generate a krb5 GSS-API wrapper packet given a ticket
>  */
> -DATA_BLOB spnego_gen_krb5_wrap(DATA_BLOB ticket)
> +DATA_BLOB spnego_gen_krb5_wrap(DATA_BLOB ticket, uint16 tok_id)
>  {
>  	ASN1_DATA data;
>  	DATA_BLOB ret;
> +	char tok_id_data[2];
>  
>  	memset(&data, 0, sizeof(data));
>  
>  	asn1_push_tag(&data, ASN1_APPLICATION(0));
>  	asn1_write_OID(&data, OID_KERBEROS5);
> -	asn1_write_BOOLEAN(&data, 0);
> +
> +	SSVAL(tok_id_data, 0, tok_id);

Doesn't the kerberos deal with the byte order?  Or shouldn't we create a
asn1_write function to do this?

> +	asn1_write(&data, tok_id_data, sizeof(tok_id_data));
>  	asn1_write(&data, ticket.data, ticket.length);
>  	asn1_pop_tag(&data);
>  
> @@ -286,24 +290,26 @@
>  /*
>    parse a krb5 GSS-API wrapper packet giving a ticket
>  */
> -BOOL spnego_parse_krb5_wrap(DATA_BLOB blob, DATA_BLOB *ticket)
> +BOOL spnego_parse_krb5_wrap(DATA_BLOB blob, DATA_BLOB *ticket, uint16 *tok_id)
>  {
>  	BOOL ret;
>  	ASN1_DATA data;
>  	int data_remaining;
> +	char tok_id_data[2];
>  
>  	asn1_load(&data, blob);
>  	asn1_start_tag(&data, ASN1_APPLICATION(0));
>  	asn1_check_OID(&data, OID_KERBEROS5);
> -	asn1_check_BOOLEAN(&data, 0);
>  
>  	data_remaining = asn1_tag_remaining(&data);
>  
> -	if (data_remaining < 1) {
> +	if (data_remaining < 3) {
>  		data.has_error = True;
>  	} else {
> -		
> -		*ticket = data_blob(data.data, data_remaining);
> +		asn1_read(&data, tok_id_data, 2);
> +		*tok_id = SVAL(tok_id_data, 0);

Again, shouldn't there be an asn1 function for this?

> +		data_remaining -= 2;
> +		*ticket = data_blob(NULL, data_remaining);
>  		asn1_read(&data, ticket->data, ticket->length);
>  	}
>  
> @@ -330,7 +336,7 @@
>  	tkt = krb5_get_ticket(principal, time_offset);
>  
>  	/* wrap that up in a nice GSS-API wrapping */
> -	tkt_wrapped = spnego_gen_krb5_wrap(tkt);
> +	tkt_wrapped = spnego_gen_krb5_wrap(tkt, 0x0001);

Can we have a name for this magic number?  A define in asn_1.h or
similar?

>  
>  	/* and wrap that in a shiny SPNEGO wrapper */
>  	targ = gen_negTokenTarg(krb_mechs, tkt_wrapped);
> @@ -438,9 +444,10 @@
>  }
>  
>  /*
> -  generate a minimal SPNEGO NTLMSSP response packet.  Doesn't contain much.
> +  generate a minimal SPNEGO response packet.  Doesn't contain much.
>  */
> -DATA_BLOB spnego_gen_auth_response(DATA_BLOB *ntlmssp_reply, NTSTATUS nt_status)
> +DATA_BLOB spnego_gen_auth_response(DATA_BLOB *reply, NTSTATUS nt_status,
> +				   const char *mechOID)
>  {
>  	ASN1_DATA data;
>  	DATA_BLOB ret;
> @@ -462,13 +469,13 @@
>  	asn1_write_enumerated(&data, negResult);
>  	asn1_pop_tag(&data);
>  
> -	if (negResult == SPNEGO_NEG_RESULT_INCOMPLETE) {
> +	if (reply->data != NULL) {
>  		asn1_push_tag(&data,ASN1_CONTEXT(1));
> -		asn1_write_OID(&data, OID_NTLMSSP);
> +		asn1_write_OID(&data, mechOID);
>  		asn1_pop_tag(&data);
>  		
>  		asn1_push_tag(&data,ASN1_CONTEXT(2));
> -		asn1_write_OctetString(&data, ntlmssp_reply->data, ntlmssp_reply->length);
> +		asn1_write_OctetString(&data, reply->data, reply->length);
>  		asn1_pop_tag(&data);
>  	}
>  

This looks good.  I like generic code :-).

> Index: passdb/secrets.c
> ===================================================================
> RCS file: /cvsroot/samba/source/passdb/secrets.c,v
> retrieving revision 1.54
> diff -u -r1.54 secrets.c
> --- passdb/secrets.c	1 Feb 2003 04:39:15 -0000	1.54
> +++ passdb/secrets.c	23 Feb 2003 01:51:13 -0000
> @@ -221,6 +221,66 @@
>  	return True;
>  }
>  
> +#ifdef USE_KEYTAB
> +/************************************************************************
> + Read local secret from the keytab
> +************************************************************************/
> +
> +static BOOL secrets_fetch_keytab_password(uint8 ret_pwd[16], time_t *pass_last_set_time)
> +{
> +	char spn[MAXHOSTNAMELEN + 2], *p;
> +	krb5_context context;
> +	krb5_error_code ret;
> +	krb5_principal princ;
> +	krb5_keyblock *key;
> +
> +	ret = krb5_init_context(&context);
> +	if (ret) {
> +		DEBUG(1, ("secrets_fetch_keytab_password: failed to initialize Kerberos context\n"));
> +		return FALSE;
> +	}
> +
> +	spn[sizeof(spn) - 1] = '\0';
> +	if (gethostname(spn, sizeof(spn) - 2) < 0) {
> +		DEBUG(1, ("secrets_fetch_keytab_password: could not determine local hostname\n"));
> +		krb5_free_context(context);
> +		return FALSE;
> +	}
> +
> +	for (p = spn; *p && *p != '.'; p++)
> +		*p = toupper(*p);
> +	*p++ = '$';
> +	*p = '\0';
> +
> +	ret = krb5_parse_name(context, spn, &princ);
> +	if (ret) {
> +		DEBUG(1, ("secrets_fetch_keytab_password: failed to parse name %s\n", spn));
> +		krb5_free_context(context);
> +		return FALSE;
> +	}
> +
> +	ret = krb5_kt_read_service_key(context, NULL, princ, 0, ENCTYPE_ARCFOUR_HMAC, &key);
> +	if (ret) {
> +		DEBUG(1, ("secrets_fetch_keytab_password: failed to read secret for %s\n", spn));
> +		krb5_free_context(context);
> +		return FALSE;
> +	}
> +	if (key->keyvalue.length != 16) {
> +		DEBUG(1, ("secrets_fetch_keytab_password: key is incorrect length\n"));
> +		krb5_free_context(context);
> +		return FALSE;
> +	}
> +
> +	memcpy(ret_pwd, key->keyvalue.data, key->keyvalue.length);
> +	time(pass_last_set_time); /* XXX */
> +
> +	krb5_free_keyblock(context, key);
> +	krb5_free_context(context);
> +
> +	return TRUE;
> +}
> +#endif /* USE_KEYTAB */
> +
>  /************************************************************************
>   Routine to get the trust account password for a domain.
>   The user of this function must have locked the trust password file using
> @@ -243,6 +303,12 @@
>  		pass_last_set_time = 0;
>  		return True;
>  	}
> +
> +#ifdef USE_KEYTAB
> +	if (is_myworkgroup(domain)) {
> +		return secrets_fetch_keytab_password(ret_pwd, pass_last_set_time);
> +	}
> +#endif /* USE_KEYTAB */
>  
>  	if (!(pass = secrets_fetch(trust_keystr(domain), &size))) {
>  		DEBUG(5, ("secrets_fetch failed!\n"));
> Index: smbd/sesssetup.c
> ===================================================================
> RCS file: /cvsroot/samba/source/smbd/sesssetup.c,v
> retrieving revision 1.87
> diff -u -r1.87 sesssetup.c
> --- smbd/sesssetup.c	10 Feb 2003 09:16:05 -0000	1.87
> +++ smbd/sesssetup.c	23 Feb 2003 01:51:14 -0000
> @@ -4,6 +4,7 @@
>     Copyright (C) Andrew Tridgell 1998-2001
>     Copyright (C) Andrew Bartlett      2001
>     Copyright (C) Jim McDonough        2002
> +   Copyright (C) Luke Howard          2003
>  
>     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
> @@ -146,10 +147,13 @@
>  	int sess_vuid;
>  	NTSTATUS ret;
>  	DATA_BLOB auth_data;
> +	DATA_BLOB ap_rep, ap_rep_wrapped, response;
>  	auth_serversupplied_info *server_info = NULL;
>  	ADS_STRUCT *ads;
> +	uint8 session_key[16];
> +	uint16 tok_id;
>  
> -	if (!spnego_parse_krb5_wrap(*secblob, &ticket)) {
> +	if (!spnego_parse_krb5_wrap(*secblob, &ticket, &tok_id)) {
>  		return ERROR_NT(NT_STATUS_LOGON_FAILURE);
>  	}
>  
> @@ -161,7 +165,7 @@
>  
>  	ads->auth.realm = strdup(lp_realm());
>  
> -	ret = ads_verify_ticket(ads, &ticket, &client, &auth_data);
> +	ret = ads_verify_ticket(ads, &ticket, &client, &auth_data, &ap_rep, session_key);
>  	if (!NT_STATUS_IS_OK(ret)) {
>  		DEBUG(1,("Failed to verify incoming ticket!\n"));	
>  		ads_destroy(&ads);
> @@ -176,6 +180,7 @@
>  	if (!p) {
>  		DEBUG(3,("Doesn't look like a valid principal\n"));
>  		ads_destroy(&ads);
> +		data_blob_free(&ap_rep);
>  		return ERROR_NT(NT_STATUS_LOGON_FAILURE);
>  	}
>  
> @@ -183,6 +188,7 @@
>  	if (strcasecmp(p+1, ads->auth.realm) != 0) {
>  		DEBUG(3,("Ticket for foreign realm %s@%s\n", client, p+1));
>  		if (!lp_allow_trusted_domains()) {
> +			data_blob_free(&ap_rep);
>  			return ERROR_NT(NT_STATUS_LOGON_FAILURE);
>  		}
>  		/* this gives a fully qualified user name (ie. with full realm).
> @@ -213,31 +219,51 @@
>  
>  	if (!pw) {
>  		DEBUG(1,("Username %s is invalid on this system\n",user));
> +		data_blob_free(&ap_rep);
>  		return ERROR_NT(NT_STATUS_NO_SUCH_USER);
>  	}
>  
>  	if (!NT_STATUS_IS_OK(ret = make_server_info_pw(&server_info,pw))) {
>  		DEBUG(1,("make_server_info_from_pw failed!\n"));
> +		data_blob_free(&ap_rep);
>  		return ERROR_NT(ret);
>  	}
> -	
> +
> +	/* Copy out the session key from the AP_REP. */
> +	memcpy(server_info->session_key, session_key, sizeof(session_key));
> +
>  	/* register_vuid keeps the server info */
>  	sess_vuid = register_vuid(server_info, user);
>  
>  	free(user);
>  
>  	if (sess_vuid == -1) {
> -		return ERROR_NT(NT_STATUS_LOGON_FAILURE);
> +		ret = NT_STATUS_LOGON_FAILURE;
> +	} else {
> +		set_message(outbuf,4,0,True);
> +		SSVAL(outbuf, smb_vwv3, 0);
> +			
> +		if (server_info->guest) {
> +			SSVAL(outbuf,smb_vwv2,1);
> +		}
> +		
> +		SSVAL(outbuf, smb_uid, sess_vuid);
>  	}
>  
> -	set_message(outbuf,4,0,True);
> -	SSVAL(outbuf, smb_vwv3, 0);
> -	add_signature(outbuf);
> - 
> -	SSVAL(outbuf,smb_uid,sess_vuid);
> -	SSVAL(inbuf,smb_uid,sess_vuid);
> -	
> -	return chain_reply(inbuf,outbuf,length,bufsize);
> +        /* wrap that up in a nice GSS-API wrapping */
> +	if (NT_STATUS_IS_OK(ret)) {
> +		ap_rep_wrapped = spnego_gen_krb5_wrap(ap_rep, 0x0002);
> +	} else {
> +		ap_rep_wrapped = data_blob(NULL, 0);
> +	}
> +	response = spnego_gen_auth_response(&ap_rep_wrapped, ret, OID_KERBEROS5_OLD);
> +	reply_sesssetup_blob(conn, outbuf, response, ret);
> +
> +	data_blob_free(&ap_rep);
> +	data_blob_free(&ap_rep_wrapped);
> +	data_blob_free(&response);
> +
> +	return -1; /* already replied */
>  }
>  #endif
>  
> @@ -284,7 +310,7 @@
>  		}
>  	}
>  
> -        response = spnego_gen_auth_response(ntlmssp_blob, nt_status);
> +        response = spnego_gen_auth_response(ntlmssp_blob, nt_status, OID_NTLMSSP);
>  	ret = reply_sesssetup_blob(conn, outbuf, response, nt_status);
>  	data_blob_free(&response);
>  
> ----
> 

> --
> Luke Howard | PADL Software Pty Ltd | www.padl.com
-- 
Andrew Bartlett                                 abartlet at pcug.org.au
Manager, Authentication Subsystems, Samba Team  abartlet at samba.org
Student Network Administrator, Hawker College   abartlet at hawkerc.net
http://samba.org     http://build.samba.org     http://hawkerc.net
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part
Url : http://lists.samba.org/archive/samba-technical/attachments/20030223/d3877530/attachment.bin


More information about the samba-technical mailing list