Support NTLM verification, note that the KDC does no NTLM packet

parsing, its all done by the client side, the KDC just calculate and
verify the digest and return the result to the service.


git-svn-id: svn://svn.h5l.se/heimdal/trunk/heimdal@19378 ec53bebd-3082-4978-b11e-865c3cabbd6b
This commit is contained in:
Love Hörnquist Åstrand
2006-12-15 21:33:26 +00:00
parent 30cf3d7d07
commit be40e72015

View File

@@ -37,6 +37,31 @@
RCSID("$Id$");
static krb5_error_code
get_digest_key(krb5_context context,
krb5_kdc_configuration *config,
hdb_entry_ex *server,
krb5_crypto *crypto)
{
krb5_error_code ret;
krb5_enctype enctype;
Key *key;
ret = _kdc_get_preferred_key(context,
config,
server,
"digest-service",
&enctype,
&key);
if (ret)
return ret;
return krb5_crypto_init(context, &key->key, 0, crypto);
}
/*
*
*/
krb5_error_code
_kdc_do_digest(krb5_context context,
krb5_kdc_configuration *config,
@@ -57,7 +82,8 @@ _kdc_do_digest(krb5_context context,
krb5_storage *sp = NULL;
Checksum res;
hdb_entry_ex *server = NULL, *user = NULL;
char *password = NULL;
hdb_entry_ex *client = NULL;
char *client_name = NULL, *password = NULL;
krb5_data serverNonce;
if(!config->enable_digest) {
@@ -125,6 +151,7 @@ _kdc_do_digest(krb5_context context,
krb5_free_principal(context, principal);
goto out;
}
krb5_clear_error_string(context);
ret = _kdc_db_fetch(context, config, principal,
HDB_F_GET_SERVER, NULL, &server);
@@ -137,12 +164,17 @@ _kdc_do_digest(krb5_context context,
/* check the client is allowed to do digest auth */
{
krb5_principal principal = NULL;
hdb_entry_ex *client;
ret = krb5_ticket_get_client(context, ticket, &principal);
if (ret)
goto out;
ret = krb5_unparse_name(context, principal, &client_name);
if (ret) {
krb5_free_principal(context, principal);
goto out;
}
ret = _kdc_db_fetch(context, config, principal,
HDB_F_GET_CLIENT, NULL, &client);
krb5_free_principal(context, principal);
@@ -150,13 +182,15 @@ _kdc_do_digest(krb5_context context,
goto out;
if (client->entry.flags.allow_digest == 0) {
kdc_log(context, config, 0,
"Client %s tried to use digest "
"but is not allowed to",
client_name);
krb5_set_error_string(context,
"Client is not permitted to use digest");
ret = KRB5KDC_ERR_POLICY;
_kdc_free_ent (context, client);
goto out;
}
_kdc_free_ent (context, client);
}
/* unpack request */
@@ -192,6 +226,8 @@ _kdc_do_digest(krb5_context context,
goto out;
}
kdc_log(context, config, 0, "Valid digest request from %s", client_name);
/*
* Process the inner request
*/
@@ -289,22 +325,9 @@ _kdc_do_digest(krb5_context context,
goto out;
}
{
Key *key;
krb5_enctype enctype;
ret = _kdc_get_preferred_key(context,
config,
server,
"digest-service",
&enctype,
&key);
if (ret)
goto out;
ret = krb5_crypto_init(context, &key->key, 0, &crypto);
if (ret)
goto out;
}
ret = get_digest_key(context, config, server, &crypto);
if (ret)
goto out;
ret = krb5_create_checksum(context,
crypto,
@@ -421,22 +444,9 @@ _kdc_do_digest(krb5_context context,
serverNonce.length = ssize;
}
{
Key *key;
krb5_enctype enctype;
ret = _kdc_get_preferred_key(context,
config,
server,
"digest-service",
&enctype,
&key);
if (ret)
goto out;
ret = krb5_crypto_init(context, &key->key, 0, &crypto);
if (ret)
goto out;
}
ret = get_digest_key(context, config, server, &crypto);
if (ret)
goto out;
ret = krb5_verify_checksum(context, crypto,
KRB5_KU_DIGEST_OPAQUE,
@@ -629,6 +639,199 @@ _kdc_do_digest(krb5_context context,
break;
}
case choice_DigestReqInner_ntlmInit: {
char *targetname = NULL;
r.element = choice_DigestRepInner_ntlmInitReply;
r.u.ntlmInitReply.flags = NTLM_NEG_UNICODE;
if ((ireq.u.ntlmInit.flags & NTLM_NEG_UNICODE) == 0) {
kdc_log(context, config, 0, "NTLM client have no unicode");
goto out;
}
if (ireq.u.ntlmInit.flags & NTLM_NEG_NTLM)
r.u.ntlmInitReply.flags |= NTLM_NEG_NTLM;
else {
kdc_log(context, config, 0, "NTLM client doesn't support NTLM");
goto out;
}
r.u.ntlmInitReply.flags |=
(ireq.u.ntlmInit.flags & (NTLM_NEG_SIGN|NTLM_NEG_SEAL)) |
NTLM_NEG_TARGET_DOMAIN |
NTLM_ENC_128;
targetname = strdup(krb5_principal_get_realm(context,
client->entry.principal));
{
char *p = strchr(targetname, '.');
if (p)
*p = '\0';
}
strupr(targetname);
r.u.ntlmInitReply.targetname = targetname;
r.u.ntlmInitReply.challange.data = malloc(8);
if (r.u.ntlmInitReply.challange.data == NULL) {
krb5_set_error_string(context, "out of memory");
ret = ENOMEM;
goto out;
}
r.u.ntlmInitReply.challange.length = 8;
if (RAND_bytes(r.u.ntlmInitReply.challange.data,
r.u.ntlmInitReply.challange.length) != 1)
{
krb5_set_error_string(context, "out of random error");
ret = ENOMEM;
goto out;
}
r.u.ntlmInitReply.targetinfo = NULL; /* XXX fix targetinfo */
/*
* Save data encryted in opaque for the second part of the
* ntlm authentication
*/
sp = krb5_storage_emem();
if (sp == NULL) {
ret = ENOMEM;
krb5_set_error_string(context, "out of memory");
goto out;
}
ret = krb5_storage_write(sp, r.u.ntlmInitReply.challange.data, 8);
if (ret != 8) {
ret = ENOMEM;
krb5_set_error_string(context, "storage write challange");
goto out;
}
ret = krb5_store_uint32(sp, r.u.ntlmInitReply.flags);
if (ret) {
krb5_clear_error_string(context);
goto out;
}
ret = krb5_storage_to_data(sp, &buf);
if (ret) {
krb5_clear_error_string(context);
goto out;
}
ret = get_digest_key(context, config, server, &crypto);
if (ret)
goto out;
ret = krb5_encrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
buf.data, buf.length, &r.u.ntlmInitReply.opaque);
krb5_data_free(&buf);
krb5_crypto_destroy(context, crypto);
crypto = NULL;
if (ret)
goto out;
break;
}
case choice_DigestReqInner_ntlmRequest: {
krb5_principal clientprincipal;
unsigned char challange[8];
uint32_t flags;
r.element = choice_DigestRepInner_ntlmResponse;
r.u.ntlmResponse.success = 0;
r.u.ntlmResponse.flags = 0;
r.u.ntlmResponse.sessionkey = NULL;
r.u.ntlmResponse.tickets = NULL;
/* get username */
ret = krb5_parse_name(context,
ireq.u.ntlmRequest.username,
&clientprincipal);
if (ret)
goto out;
ret = _kdc_db_fetch(context, config, clientprincipal,
HDB_F_GET_CLIENT, NULL, &user);
krb5_free_principal(context, clientprincipal);
if (ret) {
krb5_set_error_string(context, "NTLM user %s not in database",
ireq.u.ntlmRequest.username);
goto out;
}
ret = get_digest_key(context, config, server, &crypto);
if (ret)
goto out;
ret = krb5_decrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
ireq.u.ntlmRequest.opaque.data,
ireq.u.ntlmRequest.opaque.length, &buf);
krb5_crypto_destroy(context, crypto);
crypto = NULL;
if (ret)
goto out;
sp = krb5_storage_from_data(&buf);
if (sp == NULL) {
ret = ENOMEM;
krb5_set_error_string(context, "out of memory");
goto out;
}
ret = krb5_storage_read(sp, challange, sizeof(challange));
if (ret != sizeof(challange)) {
krb5_set_error_string(context, "NTLM storage read challange");
ret = ENOMEM;
goto out;
}
ret = krb5_ret_uint32(sp, &flags);
if (ret) {
krb5_set_error_string(context, "NTLM storage read flags");
goto out;
}
krb5_data_free(&buf);
if (flags & NTLM_NEG_NTLM) {
struct ntlm_buf answer;
Key *key;
ret = hdb_enctype2key(context, &user->entry,
ETYPE_ARCFOUR_HMAC_MD5, &key);
if (ret) {
krb5_set_error_string(context, "NTLM missing arcfour key");
goto out;
}
ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
key->key.keyvalue.length,
challange, &answer);
if (ret) {
krb5_set_error_string(context, "NTLM missing arcfour key");
goto out;
}
if (ireq.u.ntlmRequest.ntlm.length != answer.length ||
memcmp(ireq.u.ntlmRequest.ntlm.data, answer.data, answer.length) != 0)
{
free(answer.data);
ret = EINVAL;
krb5_set_error_string(context, "NTLM hash mismatch");
goto out;
}
free(answer.data);
} else {
ret = EINVAL;
krb5_set_error_string(context, "NTLM not negotiated");
goto out;
}
r.u.ntlmResponse.success = 1;
kdc_log(context, config, 0, "NTLM successful for %s",
ireq.u.ntlmRequest.username);
break;
}
default:
r.element = choice_DigestRepInner_error;
r.u.error.reason = strdup("unknown operation");
@@ -698,10 +901,14 @@ out:
_kdc_free_ent (context, user);
if (server)
_kdc_free_ent (context, server);
if (client)
_kdc_free_ent (context, client);
if (password) {
memset(password, 0, strlen(password));
free (password);
}
if (client_name)
free (client_name);
krb5_data_free(&buf);
krb5_data_free(&serverNonce);
free_DigestREP(&rep);