diff options
Diffstat (limited to 'source4/heimdal/kdc')
-rw-r--r-- | source4/heimdal/kdc/524.c | 395 | ||||
-rw-r--r-- | source4/heimdal/kdc/default_config.c | 61 | ||||
-rw-r--r-- | source4/heimdal/kdc/headers.h | 101 | ||||
-rw-r--r-- | source4/heimdal/kdc/kaserver.c | 908 | ||||
-rw-r--r-- | source4/heimdal/kdc/kdc-protos.h | 68 | ||||
-rw-r--r-- | source4/heimdal/kdc/kdc.h | 81 | ||||
-rw-r--r-- | source4/heimdal/kdc/kdc_locl.h | 154 | ||||
-rw-r--r-- | source4/heimdal/kdc/kerberos4.c | 783 | ||||
-rw-r--r-- | source4/heimdal/kdc/kerberos5.c | 2422 | ||||
-rw-r--r-- | source4/heimdal/kdc/log.c | 89 | ||||
-rw-r--r-- | source4/heimdal/kdc/misc.c | 84 | ||||
-rwxr-xr-x | source4/heimdal/kdc/pkinit.c | 1607 | ||||
-rw-r--r-- | source4/heimdal/kdc/process.c | 117 | ||||
-rw-r--r-- | source4/heimdal/kdc/rx.h | 79 |
14 files changed, 6949 insertions, 0 deletions
diff --git a/source4/heimdal/kdc/524.c b/source4/heimdal/kdc/524.c new file mode 100644 index 0000000000..497539b2e0 --- /dev/null +++ b/source4/heimdal/kdc/524.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" + +RCSID("$Id: 524.c,v 1.34 2005/06/30 01:47:35 lha Exp $"); + +#include <krb5-v4compat.h> + +/* + * fetch the server from `t', returning the name in malloced memory in + * `spn' and the entry itself in `server' + */ + +static krb5_error_code +fetch_server (krb5_context context, + krb5_kdc_configuration *config, + const Ticket *t, + char **spn, + hdb_entry **server, + const char *from) +{ + krb5_error_code ret; + krb5_principal sprinc; + + ret = _krb5_principalname2krb5_principal(&sprinc, t->sname, t->realm); + if (ret) { + kdc_log(context, config, 0, "_krb5_principalname2krb5_principal: %s", + krb5_get_err_text(context, ret)); + return ret; + } + ret = krb5_unparse_name(context, sprinc, spn); + if (ret) { + krb5_free_principal(context, sprinc); + kdc_log(context, config, 0, "krb5_unparse_name: %s", + krb5_get_err_text(context, ret)); + return ret; + } + ret = _kdc_db_fetch(context, config, sprinc, HDB_ENT_TYPE_SERVER, server); + krb5_free_principal(context, sprinc); + if (ret) { + kdc_log(context, config, 0, + "Request to convert ticket from %s for unknown principal %s: %s", + from, *spn, krb5_get_err_text(context, ret)); + if (ret == HDB_ERR_NOENTRY) + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + return ret; + } + return 0; +} + +static krb5_error_code +log_524 (krb5_context context, + krb5_kdc_configuration *config, + const EncTicketPart *et, + const char *from, + const char *spn) +{ + krb5_principal client; + char *cpn; + krb5_error_code ret; + + ret = _krb5_principalname2krb5_principal(&client, et->cname, et->crealm); + if (ret) { + kdc_log(context, config, 0, "_krb5_principalname2krb5_principal: %s", + krb5_get_err_text (context, ret)); + return ret; + } + ret = krb5_unparse_name(context, client, &cpn); + if (ret) { + krb5_free_principal(context, client); + kdc_log(context, config, 0, "krb5_unparse_name: %s", + krb5_get_err_text (context, ret)); + return ret; + } + kdc_log(context, config, 1, "524-REQ %s from %s for %s", cpn, from, spn); + free(cpn); + krb5_free_principal(context, client); + return 0; +} + +static krb5_error_code +verify_flags (krb5_context context, + krb5_kdc_configuration *config, + const EncTicketPart *et, + const char *spn) +{ + if(et->endtime < kdc_time){ + kdc_log(context, config, 0, "Ticket expired (%s)", spn); + return KRB5KRB_AP_ERR_TKT_EXPIRED; + } + if(et->flags.invalid){ + kdc_log(context, config, 0, "Ticket not valid (%s)", spn); + return KRB5KRB_AP_ERR_TKT_NYV; + } + return 0; +} + +/* + * set the `et->caddr' to the most appropriate address to use, where + * `addr' is the address the request was received from. + */ + +static krb5_error_code +set_address (krb5_context context, + krb5_kdc_configuration *config, + EncTicketPart *et, + struct sockaddr *addr, + const char *from) +{ + krb5_error_code ret; + krb5_address *v4_addr; + + v4_addr = malloc (sizeof(*v4_addr)); + if (v4_addr == NULL) + return ENOMEM; + + ret = krb5_sockaddr2address(context, addr, v4_addr); + if(ret) { + free (v4_addr); + kdc_log(context, config, 0, "Failed to convert address (%s)", from); + return ret; + } + + if (et->caddr && !krb5_address_search (context, v4_addr, et->caddr)) { + kdc_log(context, config, 0, "Incorrect network address (%s)", from); + krb5_free_address(context, v4_addr); + free (v4_addr); + return KRB5KRB_AP_ERR_BADADDR; + } + if(v4_addr->addr_type == KRB5_ADDRESS_INET) { + /* we need to collapse the addresses in the ticket to a + single address; best guess is to use the address the + connection came from */ + + if (et->caddr != NULL) { + free_HostAddresses(et->caddr); + } else { + et->caddr = malloc (sizeof (*et->caddr)); + if (et->caddr == NULL) { + krb5_free_address(context, v4_addr); + free(v4_addr); + return ENOMEM; + } + } + et->caddr->val = v4_addr; + et->caddr->len = 1; + } else { + krb5_free_address(context, v4_addr); + free(v4_addr); + } + return 0; +} + + +static krb5_error_code +encrypt_v4_ticket(krb5_context context, + krb5_kdc_configuration *config, + void *buf, + size_t len, + krb5_keyblock *skey, + EncryptedData *reply) +{ + krb5_crypto crypto; + krb5_error_code ret; + ret = krb5_crypto_init(context, skey, ETYPE_DES_PCBC_NONE, &crypto); + if (ret) { + free(buf); + kdc_log(context, config, 0, "krb5_crypto_init failed: %s", + krb5_get_err_text(context, ret)); + return ret; + } + + ret = krb5_encrypt_EncryptedData(context, + crypto, + KRB5_KU_TICKET, + buf, + len, + 0, + reply); + krb5_crypto_destroy(context, crypto); + if(ret) { + kdc_log(context, config, 0, "Failed to encrypt data: %s", + krb5_get_err_text(context, ret)); + return ret; + } + return 0; +} + +static krb5_error_code +encode_524_response(krb5_context context, + krb5_kdc_configuration *config, + const char *spn, const EncTicketPart et, + const Ticket *t, hdb_entry *server, + EncryptedData *ticket, int *kvno) +{ + krb5_error_code ret; + int use_2b; + size_t len; + + use_2b = krb5_config_get_bool(context, NULL, "kdc", "use_2b", spn, NULL); + if(use_2b) { + ASN1_MALLOC_ENCODE(EncryptedData, + ticket->cipher.data, ticket->cipher.length, + &t->enc_part, &len, ret); + + if (ret) { + kdc_log(context, config, 0, + "Failed to encode v4 (2b) ticket (%s)", spn); + return ret; + } + + ticket->etype = 0; + ticket->kvno = NULL; + *kvno = 213; /* 2b's use this magic kvno */ + } else { + unsigned char buf[MAX_KTXT_LEN + 4 * 4]; + Key *skey; + + if (!config->enable_v4_cross_realm && strcmp (et.crealm, t->realm) != 0) { + kdc_log(context, config, 0, "524 cross-realm %s -> %s disabled", et.crealm, + t->realm); + return KRB5KDC_ERR_POLICY; + } + + ret = _kdc_encode_v4_ticket(context, config, + buf + sizeof(buf) - 1, sizeof(buf), + &et, &t->sname, &len); + if(ret){ + kdc_log(context, config, 0, + "Failed to encode v4 ticket (%s)", spn); + return ret; + } + ret = _kdc_get_des_key(context, server, TRUE, FALSE, &skey); + if(ret){ + kdc_log(context, config, 0, + "no suitable DES key for server (%s)", spn); + return ret; + } + ret = encrypt_v4_ticket(context, config, buf + sizeof(buf) - len, len, + &skey->key, ticket); + if(ret){ + kdc_log(context, config, 0, + "Failed to encrypt v4 ticket (%s)", spn); + return ret; + } + *kvno = server->kvno; + } + + return 0; +} + +/* + * process a 5->4 request, based on `t', and received `from, addr', + * returning the reply in `reply' + */ + +krb5_error_code +_kdc_do_524(krb5_context context, + krb5_kdc_configuration *config, + const Ticket *t, krb5_data *reply, + const char *from, struct sockaddr *addr) +{ + krb5_error_code ret = 0; + krb5_crypto crypto; + hdb_entry *server = NULL; + Key *skey; + krb5_data et_data; + EncTicketPart et; + EncryptedData ticket; + krb5_storage *sp; + char *spn = NULL; + unsigned char buf[MAX_KTXT_LEN + 4 * 4]; + size_t len; + int kvno = 0; + + if(!config->enable_524) { + ret = KRB5KDC_ERR_POLICY; + kdc_log(context, config, 0, + "Rejected ticket conversion request from %s", from); + goto out; + } + + ret = fetch_server (context, config, t, &spn, &server, from); + if (ret) { + goto out; + } + + ret = hdb_enctype2key(context, server, t->enc_part.etype, &skey); + if(ret){ + kdc_log(context, config, 0, + "No suitable key found for server (%s) from %s", spn, from); + goto out; + } + ret = krb5_crypto_init(context, &skey->key, 0, &crypto); + if (ret) { + kdc_log(context, config, 0, "krb5_crypto_init failed: %s", + krb5_get_err_text(context, ret)); + goto out; + } + ret = krb5_decrypt_EncryptedData (context, + crypto, + KRB5_KU_TICKET, + &t->enc_part, + &et_data); + krb5_crypto_destroy(context, crypto); + if(ret){ + kdc_log(context, config, 0, + "Failed to decrypt ticket from %s for %s", from, spn); + goto out; + } + ret = krb5_decode_EncTicketPart(context, et_data.data, et_data.length, + &et, &len); + krb5_data_free(&et_data); + if(ret){ + kdc_log(context, config, 0, + "Failed to decode ticket from %s for %s", from, spn); + goto out; + } + + ret = log_524 (context, config, &et, from, spn); + if (ret) { + free_EncTicketPart(&et); + goto out; + } + + ret = verify_flags (context, config, &et, spn); + if (ret) { + free_EncTicketPart(&et); + goto out; + } + + ret = set_address (context, config, &et, addr, from); + if (ret) { + free_EncTicketPart(&et); + goto out; + } + + ret = encode_524_response(context, config, spn, et, t, + server, &ticket, &kvno); + free_EncTicketPart(&et); + + out: + /* make reply */ + memset(buf, 0, sizeof(buf)); + sp = krb5_storage_from_mem(buf, sizeof(buf)); + krb5_store_int32(sp, ret); + if(ret == 0){ + krb5_store_int32(sp, kvno); + krb5_store_data(sp, ticket.cipher); + /* Aargh! This is coded as a KTEXT_ST. */ + krb5_storage_seek(sp, MAX_KTXT_LEN - ticket.cipher.length, SEEK_CUR); + krb5_store_int32(sp, 0); /* mbz */ + free_EncryptedData(&ticket); + } + ret = krb5_storage_to_data(sp, reply); + reply->length = krb5_storage_seek(sp, 0, SEEK_CUR); + krb5_storage_free(sp); + + if(spn) + free(spn); + if(server) + _kdc_free_ent (context, server); + return ret; +} diff --git a/source4/heimdal/kdc/default_config.c b/source4/heimdal/kdc/default_config.c new file mode 100644 index 0000000000..5152fe9ab1 --- /dev/null +++ b/source4/heimdal/kdc/default_config.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" + +/* + * Setup some of the defaults for the KDC configuration. + * + * Note: Caller must also fill in: + * - db + * - num_db + * - logf + * +*/ + +void +krb5_kdc_default_config(krb5_kdc_configuration *config) +{ + config->require_preauth = TRUE; + config->kdc_warn_pwexpire = -1; + config->encode_as_rep_as_tgs_rep = FALSE; /* bug compatibility */ + config->check_ticket_addresses = TRUE; + config->allow_null_ticket_addresses = TRUE; + config->allow_anonymous = FALSE; + config->trpolicy = TRPOLICY_ALWAYS_CHECK; + config->enable_v4 = FALSE; + config->enable_kaserver = FALSE; + config->enable_524 = FALSE; /* overriden by enable_v4 in configure()) */ + config->enable_v4_cross_realm = FALSE; + config->enable_pkinit = FALSE; + config->enable_pkinit_princ_in_cert = TRUE; + config->db = NULL; + config->num_db = 0; + config->logf = NULL; +} diff --git a/source4/heimdal/kdc/headers.h b/source4/heimdal/kdc/headers.h new file mode 100644 index 0000000000..86f162aa94 --- /dev/null +++ b/source4/heimdal/kdc/headers.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * $Id: headers.h,v 1.16 2005/04/24 13:49:00 lha Exp $ + */ + +#ifndef __HEADERS_H__ +#define __HEADERS_H__ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <stdarg.h> +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif +#ifdef HAVE_NETINET6_IN6_H +#include <netinet6/in6.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_UTIL_H +#include <util.h> +#endif +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#include <err.h> +#include <roken.h> +#include <getarg.h> +#include <base64.h> +#include <parse_units.h> +#include <krb5.h> +#include <krb5_locl.h> +#include <hdb.h> +#include <hdb_err.h> +#include <der.h> /* copy_octet_string */ + +#undef ALLOC +#define ALLOC(X) ((X) = malloc(sizeof(*(X)))) +#undef ALLOC_SEQ +#define ALLOC_SEQ(X, N) do { (X)->len = (N); \ +(X)->val = calloc((X)->len, sizeof(*(X)->val)); } while(0) + +#endif /* __HEADERS_H__ */ diff --git a/source4/heimdal/kdc/kaserver.c b/source4/heimdal/kdc/kaserver.c new file mode 100644 index 0000000000..4a9bd87cb6 --- /dev/null +++ b/source4/heimdal/kdc/kaserver.c @@ -0,0 +1,908 @@ +/* + * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" + +RCSID("$Id: kaserver.c,v 1.30 2005/06/30 01:49:39 lha Exp $"); + +#include <krb5-v4compat.h> +#include <rx.h> + +#define KA_AUTHENTICATION_SERVICE 731 +#define KA_TICKET_GRANTING_SERVICE 732 +#define KA_MAINTENANCE_SERVICE 733 + +#define AUTHENTICATE_OLD 1 +#define CHANGEPASSWORD 2 +#define GETTICKET_OLD 3 +#define SETPASSWORD 4 +#define SETFIELDS 5 +#define CREATEUSER 6 +#define DELETEUSER 7 +#define GETENTRY 8 +#define LISTENTRY 9 +#define GETSTATS 10 +#define DEBUG 11 +#define GETPASSWORD 12 +#define GETRANDOMKEY 13 +#define AUTHENTICATE 21 +#define AUTHENTICATE_V2 22 +#define GETTICKET 23 + +/* XXX - Where do we get these? */ + +#define RXGEN_OPCODE (-455) + +#define KADATABASEINCONSISTENT (180480L) +#define KAEXIST (180481L) +#define KAIO (180482L) +#define KACREATEFAIL (180483L) +#define KANOENT (180484L) +#define KAEMPTY (180485L) +#define KABADNAME (180486L) +#define KABADINDEX (180487L) +#define KANOAUTH (180488L) +#define KAANSWERTOOLONG (180489L) +#define KABADREQUEST (180490L) +#define KAOLDINTERFACE (180491L) +#define KABADARGUMENT (180492L) +#define KABADCMD (180493L) +#define KANOKEYS (180494L) +#define KAREADPW (180495L) +#define KABADKEY (180496L) +#define KAUBIKINIT (180497L) +#define KAUBIKCALL (180498L) +#define KABADPROTOCOL (180499L) +#define KANOCELLS (180500L) +#define KANOCELL (180501L) +#define KATOOMANYUBIKS (180502L) +#define KATOOMANYKEYS (180503L) +#define KABADTICKET (180504L) +#define KAUNKNOWNKEY (180505L) +#define KAKEYCACHEINVALID (180506L) +#define KABADSERVER (180507L) +#define KABADUSER (180508L) +#define KABADCPW (180509L) +#define KABADCREATE (180510L) +#define KANOTICKET (180511L) +#define KAASSOCUSER (180512L) +#define KANOTSPECIAL (180513L) +#define KACLOCKSKEW (180514L) +#define KANORECURSE (180515L) +#define KARXFAIL (180516L) +#define KANULLPASSWORD (180517L) +#define KAINTERNALERROR (180518L) +#define KAPWEXPIRED (180519L) +#define KAREUSED (180520L) +#define KATOOSOON (180521L) +#define KALOCKED (180522L) + +static void +decode_rx_header (krb5_storage *sp, + struct rx_header *h) +{ + krb5_ret_int32(sp, &h->epoch); + krb5_ret_int32(sp, &h->connid); + krb5_ret_int32(sp, &h->callid); + krb5_ret_int32(sp, &h->seqno); + krb5_ret_int32(sp, &h->serialno); + krb5_ret_int8(sp, &h->type); + krb5_ret_int8(sp, &h->flags); + krb5_ret_int8(sp, &h->status); + krb5_ret_int8(sp, &h->secindex); + krb5_ret_int16(sp, &h->reserved); + krb5_ret_int16(sp, &h->serviceid); +} + +static void +encode_rx_header (struct rx_header *h, + krb5_storage *sp) +{ + krb5_store_int32(sp, h->epoch); + krb5_store_int32(sp, h->connid); + krb5_store_int32(sp, h->callid); + krb5_store_int32(sp, h->seqno); + krb5_store_int32(sp, h->serialno); + krb5_store_int8(sp, h->type); + krb5_store_int8(sp, h->flags); + krb5_store_int8(sp, h->status); + krb5_store_int8(sp, h->secindex); + krb5_store_int16(sp, h->reserved); + krb5_store_int16(sp, h->serviceid); +} + +static void +init_reply_header (struct rx_header *hdr, + struct rx_header *reply_hdr, + u_char type, + u_char flags) +{ + reply_hdr->epoch = hdr->epoch; + reply_hdr->connid = hdr->connid; + reply_hdr->callid = hdr->callid; + reply_hdr->seqno = 1; + reply_hdr->serialno = 1; + reply_hdr->type = type; + reply_hdr->flags = flags; + reply_hdr->status = 0; + reply_hdr->secindex = 0; + reply_hdr->reserved = 0; + reply_hdr->serviceid = hdr->serviceid; +} + +static void +make_error_reply (struct rx_header *hdr, + u_int32_t ret, + krb5_data *reply) + +{ + krb5_storage *sp; + struct rx_header reply_hdr; + + init_reply_header (hdr, &reply_hdr, HT_ABORT, HF_LAST); + sp = krb5_storage_emem(); + encode_rx_header (&reply_hdr, sp); + krb5_store_int32(sp, ret); + krb5_storage_to_data (sp, reply); + krb5_storage_free (sp); +} + +static krb5_error_code +krb5_ret_xdr_data(krb5_storage *sp, + krb5_data *data) +{ + int ret; + int size; + ret = krb5_ret_int32(sp, &size); + if(ret) + return ret; + if(size < 0) + return ERANGE; + data->length = size; + if (size) { + u_char foo[4]; + size_t pad = (4 - size % 4) % 4; + + data->data = malloc(size); + if (data->data == NULL) + return ENOMEM; + ret = krb5_storage_read(sp, data->data, size); + if(ret != size) + return (ret < 0)? errno : KRB5_CC_END; + if (pad) { + ret = krb5_storage_read(sp, foo, pad); + if (ret != pad) + return (ret < 0)? errno : KRB5_CC_END; + } + } else + data->data = NULL; + return 0; +} + +static krb5_error_code +krb5_store_xdr_data(krb5_storage *sp, + krb5_data data) +{ + u_char zero[4] = {0, 0, 0, 0}; + int ret; + size_t pad; + + ret = krb5_store_int32(sp, data.length); + if(ret < 0) + return ret; + ret = krb5_storage_write(sp, data.data, data.length); + if(ret != data.length){ + if(ret < 0) + return errno; + return KRB5_CC_END; + } + pad = (4 - data.length % 4) % 4; + if (pad) { + ret = krb5_storage_write(sp, zero, pad); + if (ret != pad) { + if (ret < 0) + return errno; + return KRB5_CC_END; + } + } + return 0; +} + + +static krb5_error_code +create_reply_ticket (krb5_context context, + struct rx_header *hdr, + Key *skey, + char *name, char *instance, char *realm, + struct sockaddr_in *addr, + int life, + int kvno, + int32_t max_seq_len, + const char *sname, const char *sinstance, + u_int32_t challenge, + const char *label, + krb5_keyblock *key, + krb5_data *reply) +{ + krb5_data ticket; + krb5_keyblock session; + krb5_storage *sp; + krb5_data enc_data; + struct rx_header reply_hdr; + char zero[8]; + size_t pad; + unsigned fyrtiosjuelva; + + /* create the ticket */ + + krb5_generate_random_keyblock(context, ETYPE_DES_PCBC_NONE, &session); + + _krb5_krb_create_ticket(context, + 0, + name, + instance, + realm, + addr->sin_addr.s_addr, + &session, + life, + kdc_time, + sname, + sinstance, + &skey->key, + &ticket); + + /* create the encrypted part of the reply */ + sp = krb5_storage_emem (); + krb5_generate_random_block(&fyrtiosjuelva, sizeof(fyrtiosjuelva)); + fyrtiosjuelva &= 0xffffffff; + krb5_store_int32 (sp, fyrtiosjuelva); + krb5_store_int32 (sp, challenge); + krb5_storage_write (sp, session.keyvalue.data, 8); + krb5_free_keyblock_contents(context, &session); + krb5_store_int32 (sp, kdc_time); + krb5_store_int32 (sp, kdc_time + _krb5_krb_life_to_time (0, life)); + krb5_store_int32 (sp, kvno); + krb5_store_int32 (sp, ticket.length); + krb5_store_stringz (sp, name); + krb5_store_stringz (sp, instance); +#if 1 /* XXX - Why shouldn't the realm go here? */ + krb5_store_stringz (sp, ""); +#else + krb5_store_stringz (sp, realm); +#endif + krb5_store_stringz (sp, sname); + krb5_store_stringz (sp, sinstance); + krb5_storage_write (sp, ticket.data, ticket.length); + krb5_storage_write (sp, label, strlen(label)); + + /* pad to DES block */ + memset (zero, 0, sizeof(zero)); + pad = (8 - krb5_storage_seek (sp, 0, SEEK_CUR) % 8) % 8; + krb5_storage_write (sp, zero, pad); + + krb5_storage_to_data (sp, &enc_data); + krb5_storage_free (sp); + + if (enc_data.length > max_seq_len) { + krb5_data_free (&enc_data); + make_error_reply (hdr, KAANSWERTOOLONG, reply); + return 0; + } + + /* encrypt it */ + { + DES_key_schedule schedule; + DES_cblock deskey; + + memcpy (&deskey, key->keyvalue.data, sizeof(deskey)); + DES_set_key (&deskey, &schedule); + DES_pcbc_encrypt (enc_data.data, + enc_data.data, + enc_data.length, + &schedule, + &deskey, + DES_ENCRYPT); + memset (&schedule, 0, sizeof(schedule)); + memset (&deskey, 0, sizeof(deskey)); + } + + /* create the reply packet */ + init_reply_header (hdr, &reply_hdr, HT_DATA, HF_LAST); + sp = krb5_storage_emem (); + encode_rx_header (&reply_hdr, sp); + krb5_store_int32 (sp, max_seq_len); + krb5_store_xdr_data (sp, enc_data); + krb5_data_free (&enc_data); + krb5_storage_to_data (sp, reply); + krb5_storage_free (sp); + return 0; +} + +static krb5_error_code +unparse_auth_args (krb5_storage *sp, + char **name, + char **instance, + time_t *start_time, + time_t *end_time, + krb5_data *request, + int32_t *max_seq_len) +{ + krb5_data data; + int32_t tmp; + + krb5_ret_xdr_data (sp, &data); + *name = malloc(data.length + 1); + if (*name == NULL) + return ENOMEM; + memcpy (*name, data.data, data.length); + (*name)[data.length] = '\0'; + krb5_data_free (&data); + + krb5_ret_xdr_data (sp, &data); + *instance = malloc(data.length + 1); + if (*instance == NULL) { + free (*name); + return ENOMEM; + } + memcpy (*instance, data.data, data.length); + (*instance)[data.length] = '\0'; + krb5_data_free (&data); + + krb5_ret_int32 (sp, &tmp); + *start_time = tmp; + krb5_ret_int32 (sp, &tmp); + *end_time = tmp; + krb5_ret_xdr_data (sp, request); + krb5_ret_int32 (sp, max_seq_len); + /* ignore the rest */ + return 0; +} + +static void +do_authenticate (krb5_context context, + krb5_kdc_configuration *config, + struct rx_header *hdr, + krb5_storage *sp, + struct sockaddr_in *addr, + const char *from, + krb5_data *reply) +{ + krb5_error_code ret; + char *name = NULL; + char *instance = NULL; + time_t start_time; + time_t end_time; + krb5_data request; + int32_t max_seq_len; + hdb_entry *client_entry = NULL; + hdb_entry *server_entry = NULL; + Key *ckey = NULL; + Key *skey = NULL; + krb5_storage *reply_sp; + time_t max_life; + u_int8_t life; + int32_t chal; + char client_name[256]; + char server_name[256]; + + krb5_data_zero (&request); + + ret = unparse_auth_args (sp, &name, &instance, &start_time, &end_time, + &request, &max_seq_len); + if (ret != 0 || request.length < 8) { + make_error_reply (hdr, KABADREQUEST, reply); + goto out; + } + + snprintf (client_name, sizeof(client_name), "%s.%s@%s", + name, instance, config->v4_realm); + snprintf (server_name, sizeof(server_name), "%s.%s@%s", + "krbtgt", config->v4_realm, config->v4_realm); + + kdc_log(context, config, 0, "AS-REQ (kaserver) %s from %s for %s", + client_name, from, server_name); + + ret = _kdc_db_fetch4 (context, config, name, instance, + config->v4_realm, HDB_ENT_TYPE_CLIENT, + &client_entry); + if (ret) { + kdc_log(context, config, 0, "Client not found in database: %s: %s", + client_name, krb5_get_err_text(context, ret)); + make_error_reply (hdr, KANOENT, reply); + goto out; + } + + ret = _kdc_db_fetch4 (context, config, "krbtgt", + config->v4_realm, config->v4_realm, + HDB_ENT_TYPE_SERVER, &server_entry); + if (ret) { + kdc_log(context, config, 0, "Server not found in database: %s: %s", + server_name, krb5_get_err_text(context, ret)); + make_error_reply (hdr, KANOENT, reply); + goto out; + } + + ret = _kdc_check_flags (context, config, + client_entry, client_name, + server_entry, server_name, + TRUE); + if (ret) { + make_error_reply (hdr, KAPWEXPIRED, reply); + goto out; + } + + /* find a DES key */ + ret = _kdc_get_des_key(context, client_entry, FALSE, TRUE, &ckey); + if(ret){ + kdc_log(context, config, 0, "no suitable DES key for client"); + make_error_reply (hdr, KANOKEYS, reply); + goto out; + } + + /* find a DES key */ + ret = _kdc_get_des_key(context, server_entry, TRUE, TRUE, &skey); + if(ret){ + kdc_log(context, config, 0, "no suitable DES key for server"); + make_error_reply (hdr, KANOKEYS, reply); + goto out; + } + + { + DES_cblock key; + DES_key_schedule schedule; + + /* try to decode the `request' */ + memcpy (&key, ckey->key.keyvalue.data, sizeof(key)); + DES_set_key (&key, &schedule); + DES_pcbc_encrypt (request.data, + request.data, + request.length, + &schedule, + &key, + DES_DECRYPT); + memset (&schedule, 0, sizeof(schedule)); + memset (&key, 0, sizeof(key)); + } + + /* check for the magic label */ + if (memcmp ((char *)request.data + 4, "gTGS", 4) != 0) { + kdc_log(context, config, 0, "preauth failed for %s", client_name); + make_error_reply (hdr, KABADREQUEST, reply); + goto out; + } + + reply_sp = krb5_storage_from_mem (request.data, 4); + krb5_ret_int32 (reply_sp, &chal); + krb5_storage_free (reply_sp); + + if (abs(chal - kdc_time) > context->max_skew) { + make_error_reply (hdr, KACLOCKSKEW, reply); + goto out; + } + + /* life */ + max_life = end_time - kdc_time; + /* end_time - kdc_time can sometimes be non-positive due to slight + time skew between client and server. Let's make sure it is postive */ + if(max_life < 1) + max_life = 1; + if (client_entry->max_life) + max_life = min(max_life, *client_entry->max_life); + if (server_entry->max_life) + max_life = min(max_life, *server_entry->max_life); + + life = krb_time_to_life(kdc_time, kdc_time + max_life); + + create_reply_ticket (context, + hdr, skey, + name, instance, config->v4_realm, + addr, life, server_entry->kvno, + max_seq_len, + "krbtgt", config->v4_realm, + chal + 1, "tgsT", + &ckey->key, reply); + + out: + if (request.length) { + memset (request.data, 0, request.length); + krb5_data_free (&request); + } + if (name) + free (name); + if (instance) + free (instance); + if (client_entry) + _kdc_free_ent (context, client_entry); + if (server_entry) + _kdc_free_ent (context, server_entry); +} + +static krb5_error_code +unparse_getticket_args (krb5_storage *sp, + int *kvno, + char **auth_domain, + krb5_data *ticket, + char **name, + char **instance, + krb5_data *times, + int32_t *max_seq_len) +{ + krb5_data data; + int32_t tmp; + + krb5_ret_int32 (sp, &tmp); + *kvno = tmp; + + krb5_ret_xdr_data (sp, &data); + *auth_domain = malloc(data.length + 1); + if (*auth_domain == NULL) + return ENOMEM; + memcpy (*auth_domain, data.data, data.length); + (*auth_domain)[data.length] = '\0'; + krb5_data_free (&data); + + krb5_ret_xdr_data (sp, ticket); + + krb5_ret_xdr_data (sp, &data); + *name = malloc(data.length + 1); + if (*name == NULL) { + free (*auth_domain); + return ENOMEM; + } + memcpy (*name, data.data, data.length); + (*name)[data.length] = '\0'; + krb5_data_free (&data); + + krb5_ret_xdr_data (sp, &data); + *instance = malloc(data.length + 1); + if (*instance == NULL) { + free (*auth_domain); + free (*name); + return ENOMEM; + } + memcpy (*instance, data.data, data.length); + (*instance)[data.length] = '\0'; + krb5_data_free (&data); + + krb5_ret_xdr_data (sp, times); + + krb5_ret_int32 (sp, max_seq_len); + /* ignore the rest */ + return 0; +} + +static void +do_getticket (krb5_context context, + krb5_kdc_configuration *config, + struct rx_header *hdr, + krb5_storage *sp, + struct sockaddr_in *addr, + const char *from, + krb5_data *reply) +{ + krb5_error_code ret; + int kvno; + char *auth_domain = NULL; + krb5_data aticket; + char *name = NULL; + char *instance = NULL; + krb5_data times; + int32_t max_seq_len; + hdb_entry *server_entry = NULL; + hdb_entry *client_entry = NULL; + hdb_entry *krbtgt_entry = NULL; + Key *kkey = NULL; + Key *skey = NULL; + DES_cblock key; + DES_key_schedule schedule; + DES_cblock session; + time_t max_life; + int8_t life; + time_t start_time, end_time; + char server_name[256]; + char client_name[256]; + struct _krb5_krb_auth_data ad; + + krb5_data_zero (&aticket); + krb5_data_zero (×); + + memset(&ad, 0, sizeof(ad)); + + unparse_getticket_args (sp, &kvno, &auth_domain, &aticket, + &name, &instance, ×, &max_seq_len); + if (times.length < 8) { + make_error_reply (hdr, KABADREQUEST, reply); + goto out; + + } + + snprintf (server_name, sizeof(server_name), + "%s.%s@%s", name, instance, config->v4_realm); + + ret = _kdc_db_fetch4 (context, config, name, instance, + config->v4_realm, HDB_ENT_TYPE_SERVER, + &server_entry); + if (ret) { + kdc_log(context, config, 0, "Server not found in database: %s: %s", + server_name, krb5_get_err_text(context, ret)); + make_error_reply (hdr, KANOENT, reply); + goto out; + } + + ret = _kdc_db_fetch4 (context, config, "krbtgt", + config->v4_realm, config->v4_realm, + HDB_ENT_TYPE_CLIENT, &krbtgt_entry); + if (ret) { + kdc_log(context, config, 0, + "Server not found in database: %s.%s@%s: %s", + "krbtgt", config->v4_realm, config->v4_realm, + krb5_get_err_text(context, ret)); + make_error_reply (hdr, KANOENT, reply); + goto out; + } + + /* find a DES key */ + ret = _kdc_get_des_key(context, krbtgt_entry, TRUE, TRUE, &kkey); + if(ret){ + kdc_log(context, config, 0, "no suitable DES key for krbtgt"); + make_error_reply (hdr, KANOKEYS, reply); + goto out; + } + + /* find a DES key */ + ret = _kdc_get_des_key(context, server_entry, TRUE, TRUE, &skey); + if(ret){ + kdc_log(context, config, 0, "no suitable DES key for server"); + make_error_reply (hdr, KANOKEYS, reply); + goto out; + } + + /* decrypt the incoming ticket */ + memcpy (&key, kkey->key.keyvalue.data, sizeof(key)); + + /* unpack the ticket */ + { + char *sname = NULL; + char *sinstance = NULL; + + ret = _krb5_krb_decomp_ticket(context, &aticket, &kkey->key, + config->v4_realm, &sname, + &sinstance, &ad); + if (ret) { + kdc_log(context, config, 0, + "kaserver: decomp failed for %s.%s with %d", + sname, sinstance, ret); + make_error_reply (hdr, KABADTICKET, reply); + goto out; + } + + if (strcmp (sname, "krbtgt") != 0 + || strcmp (sinstance, config->v4_realm) != 0) { + kdc_log(context, config, 0, "no TGT: %s.%s for %s.%s@%s", + sname, sinstance, + ad.pname, ad.pinst, ad.prealm); + make_error_reply (hdr, KABADTICKET, reply); + free(sname); + free(sinstance); + goto out; + } + free(sname); + free(sinstance); + + if (kdc_time > _krb5_krb_life_to_time(ad.time_sec, ad.life)) { + kdc_log(context, config, 0, "TGT expired: %s.%s@%s", + ad.pname, ad.pinst, ad.prealm); + make_error_reply (hdr, KABADTICKET, reply); + goto out; + } + } + + snprintf (client_name, sizeof(client_name), + "%s.%s@%s", ad.pname, ad.pinst, ad.prealm); + + kdc_log(context, config, 0, "TGS-REQ (kaserver) %s from %s for %s", + client_name, from, server_name); + + ret = _kdc_db_fetch4 (context, config, + ad.pname, ad.pinst, ad.prealm, + HDB_ENT_TYPE_CLIENT, &client_entry); + if(ret && ret != HDB_ERR_NOENTRY) { + kdc_log(context, config, 0, + "Client not found in database: (krb4) %s: %s", + client_name, krb5_get_err_text(context, ret)); + make_error_reply (hdr, KANOENT, reply); + goto out; + } + if (client_entry == NULL && strcmp(ad.prealm, config->v4_realm) == 0) { + kdc_log(context, config, 0, + "Local client not found in database: (krb4) " + "%s", client_name); + make_error_reply (hdr, KANOENT, reply); + goto out; + } + + ret = _kdc_check_flags (context, config, + client_entry, client_name, + server_entry, server_name, + FALSE); + if (ret) { + make_error_reply (hdr, KAPWEXPIRED, reply); + goto out; + } + + /* decrypt the times */ + memcpy(&session, ad.session.keyvalue.data, sizeof(session)); + DES_set_key (&session, &schedule); + DES_ecb_encrypt (times.data, + times.data, + &schedule, + DES_DECRYPT); + memset (&schedule, 0, sizeof(schedule)); + memset (&session, 0, sizeof(session)); + + /* and extract them */ + { + krb5_storage *tsp; + int32_t tmp; + + tsp = krb5_storage_from_mem (times.data, times.length); + krb5_ret_int32 (tsp, &tmp); + start_time = tmp; + krb5_ret_int32 (tsp, &tmp); + end_time = tmp; + krb5_storage_free (tsp); + } + + /* life */ + max_life = end_time - kdc_time; + /* end_time - kdc_time can sometimes be non-positive due to slight + time skew between client and server. Let's make sure it is postive */ + if(max_life < 1) + max_life = 1; + if (krbtgt_entry->max_life) + max_life = min(max_life, *krbtgt_entry->max_life); + if (server_entry->max_life) + max_life = min(max_life, *server_entry->max_life); + /* if this is a cross realm request, the client_entry will likely + be NULL */ + if (client_entry && client_entry->max_life) + max_life = min(max_life, *client_entry->max_life); + + life = _krb5_krb_time_to_life(kdc_time, kdc_time + max_life); + + create_reply_ticket (context, + hdr, skey, + ad.pname, ad.pinst, ad.prealm, + addr, life, server_entry->kvno, + max_seq_len, + name, instance, + 0, "gtkt", + &ad.session, reply); + + out: + _krb5_krb_free_auth_data(context, &ad); + if (aticket.length) { + memset (aticket.data, 0, aticket.length); + krb5_data_free (&aticket); + } + if (times.length) { + memset (times.data, 0, times.length); + krb5_data_free (×); + } + if (auth_domain) + free (auth_domain); + if (name) + free (name); + if (instance) + free (instance); + if (krbtgt_entry) + _kdc_free_ent (context, krbtgt_entry); + if (server_entry) + _kdc_free_ent (context, server_entry); +} + +krb5_error_code +_kdc_do_kaserver(krb5_context context, + krb5_kdc_configuration *config, + unsigned char *buf, + size_t len, + krb5_data *reply, + const char *from, + struct sockaddr_in *addr) +{ + krb5_error_code ret = 0; + struct rx_header hdr; + u_int32_t op; + krb5_storage *sp; + + if (len < RX_HEADER_SIZE) + return -1; + sp = krb5_storage_from_mem (buf, len); + + decode_rx_header (sp, &hdr); + buf += RX_HEADER_SIZE; + len -= RX_HEADER_SIZE; + + switch (hdr.type) { + case HT_DATA : + break; + case HT_ACK : + case HT_BUSY : + case HT_ABORT : + case HT_ACKALL : + case HT_CHAL : + case HT_RESP : + case HT_DEBUG : + default: + /* drop */ + goto out; + } + + + if (hdr.serviceid != KA_AUTHENTICATION_SERVICE + && hdr.serviceid != KA_TICKET_GRANTING_SERVICE) { + ret = -1; + goto out; + } + + krb5_ret_int32(sp, &op); + switch (op) { + case AUTHENTICATE : + case AUTHENTICATE_V2 : + do_authenticate (context, config, &hdr, sp, addr, from, reply); + break; + case GETTICKET : + do_getticket (context, config, &hdr, sp, addr, from, reply); + break; + case AUTHENTICATE_OLD : + case CHANGEPASSWORD : + case GETTICKET_OLD : + case SETPASSWORD : + case SETFIELDS : + case CREATEUSER : + case DELETEUSER : + case GETENTRY : + case LISTENTRY : + case GETSTATS : + case DEBUG : + case GETPASSWORD : + case GETRANDOMKEY : + default : + make_error_reply (&hdr, RXGEN_OPCODE, reply); + break; + } + +out: + krb5_storage_free (sp); + return ret; +} diff --git a/source4/heimdal/kdc/kdc-protos.h b/source4/heimdal/kdc/kdc-protos.h new file mode 100644 index 0000000000..5967f933f3 --- /dev/null +++ b/source4/heimdal/kdc/kdc-protos.h @@ -0,0 +1,68 @@ +/* This is a generated file */ +#ifndef __kdc_protos_h__ +#define __kdc_protos_h__ + +#include <stdarg.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void +kdc_log ( + krb5_context /*context*/, + krb5_kdc_configuration */*config*/, + int /*level*/, + const char */*fmt*/, + ...); + +char* +kdc_log_msg ( + krb5_context /*context*/, + krb5_kdc_configuration */*config*/, + int /*level*/, + const char */*fmt*/, + ...); + +char* +kdc_log_msg_va ( + krb5_context /*context*/, + krb5_kdc_configuration */*config*/, + int /*level*/, + const char */*fmt*/, + va_list /*ap*/); + +void +kdc_openlog ( + krb5_context /*context*/, + krb5_kdc_configuration */*config*/); + +void +krb5_kdc_default_config (krb5_kdc_configuration */*config*/); + +int +krb5_kdc_process_generic_request ( + krb5_context /*context*/, + krb5_kdc_configuration */*config*/, + unsigned char */*buf*/, + size_t /*len*/, + krb5_data */*reply*/, + krb5_boolean */*prependlength*/, + const char */*from*/, + struct sockaddr */*addr*/); + +int +krb5_kdc_process_krb5_request ( + krb5_context /*context*/, + krb5_kdc_configuration */*config*/, + unsigned char */*buf*/, + size_t /*len*/, + krb5_data */*reply*/, + const char */*from*/, + struct sockaddr */*addr*/); + +#ifdef __cplusplus +} +#endif + +#endif /* __kdc_protos_h__ */ diff --git a/source4/heimdal/kdc/kdc.h b/source4/heimdal/kdc/kdc.h new file mode 100644 index 0000000000..f186983cef --- /dev/null +++ b/source4/heimdal/kdc/kdc.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 1997-2003 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * + * Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * $Id: kdc.h,v 1.4 2005/06/30 01:50:42 lha Exp $ + */ + +#ifndef __KDC_H__ +#define __KDC_H__ + +#include <krb5.h> + +enum krb5_kdc_trpolicy { + TRPOLICY_ALWAYS_CHECK, + TRPOLICY_ALLOW_PER_PRINCIPAL, + TRPOLICY_ALWAYS_HONOUR_REQUEST +}; + +typedef struct krb5_kdc_configuration { + krb5_boolean require_preauth; /* require preauth for all principals */ + time_t kdc_warn_pwexpire; /* time before expiration to print a warning */ + + struct HDB **db; + int num_db; + + krb5_boolean encode_as_rep_as_tgs_rep; /* bug compatibility */ + + krb5_boolean check_ticket_addresses; + krb5_boolean allow_null_ticket_addresses; + krb5_boolean allow_anonymous; + enum krb5_kdc_trpolicy trpolicy; + + char *v4_realm; + krb5_boolean enable_v4; + krb5_boolean enable_kaserver; + + krb5_boolean enable_524; + krb5_boolean enable_v4_cross_realm; + + krb5_boolean enable_pkinit; + krb5_boolean enable_pkinit_princ_in_cert; + + krb5_log_facility *logf; +} krb5_kdc_configuration; + +#include <kdc-protos.h> + +#endif diff --git a/source4/heimdal/kdc/kdc_locl.h b/source4/heimdal/kdc/kdc_locl.h new file mode 100644 index 0000000000..d347c6080c --- /dev/null +++ b/source4/heimdal/kdc/kdc_locl.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * $Id: kdc_locl.h,v 1.71 2005/07/01 15:36:16 lha Exp $ + */ + +#ifndef __KDC_LOCL_H__ +#define __KDC_LOCL_H__ + +#include "headers.h" +#include "kdc.h" + +extern sig_atomic_t exit_flag; +extern size_t max_request; +extern const char *port_str; +extern krb5_addresses explicit_addresses; + +extern int enable_http; + +#define DETACH_IS_DEFAULT FALSE + +extern int detach_from_console; + +#define _PATH_KDC_CONF HDB_DB_DIR "/kdc.conf" +#define DEFAULT_LOG_DEST "0-1/FILE:" HDB_DB_DIR "/kdc.log" + +extern struct timeval _kdc_now; +#define kdc_time (_kdc_now.tv_sec) + +krb5_error_code +_kdc_as_rep(krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ*, krb5_data*, const char*, struct sockaddr*); + +krb5_kdc_configuration * +configure(krb5_context context, int argc, char **argv); + +krb5_error_code +_kdc_db_fetch(krb5_context, krb5_kdc_configuration *, + krb5_principal, enum hdb_ent_type, hdb_entry **); + +void +_kdc_free_ent(krb5_context context, hdb_entry *); + +void +loop(krb5_context context, krb5_kdc_configuration *config); + +krb5_error_code +_kdc_tgs_rep (krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ*, krb5_data*, const char*, struct sockaddr *); + +krb5_error_code +_kdc_check_flags(krb5_context context, + krb5_kdc_configuration *config, + hdb_entry *client, const char *client_name, + hdb_entry *server, const char *server_name, + krb5_boolean is_as_req); + +krb5_error_code +_kdc_get_des_key(krb5_context context, hdb_entry*, + krb5_boolean, krb5_boolean, Key**); + +krb5_error_code +_kdc_encode_v4_ticket(krb5_context context, + krb5_kdc_configuration *config, + void *buf, size_t len, const EncTicketPart *et, + const PrincipalName *service, size_t *size); +krb5_error_code +_kdc_do_524(krb5_context context, + krb5_kdc_configuration *config, + const Ticket *t, krb5_data *reply, + const char *from, struct sockaddr *addr); + + +#ifdef PKINIT +typedef struct pk_client_params pk_client_params; +krb5_error_code _kdc_pk_initialize(krb5_context, + krb5_kdc_configuration *, + const char *, + const char *); +krb5_error_code _kdc_pk_rd_padata(krb5_context, krb5_kdc_configuration *, + KDC_REQ *, PA_DATA *, pk_client_params **); +krb5_error_code _kdc_pk_mk_pa_reply(krb5_context, + krb5_kdc_configuration *, + pk_client_params *, + const hdb_entry *, + const KDC_REQ *, + krb5_keyblock **, + METHOD_DATA *); +krb5_error_code _kdc_pk_check_client(krb5_context, + krb5_kdc_configuration *, + krb5_principal, + const hdb_entry *, + pk_client_params *, char **); +void _kdc_pk_free_client_param(krb5_context, pk_client_params *); +#endif + +/* + * Kerberos 4 + */ + +krb5_error_code +_kdc_db_fetch4 (krb5_context context, + krb5_kdc_configuration *config, + const char*, const char*, const char*, enum hdb_ent_type, hdb_entry**); + +krb5_error_code +_kdc_do_version4 (krb5_context context, + krb5_kdc_configuration *config, + unsigned char*, size_t, krb5_data*, const char*, + struct sockaddr_in*); +int +_kdc_maybe_version4(unsigned char*, int); + +krb5_error_code +_kdc_do_kaserver (krb5_context context, + krb5_kdc_configuration *config, + unsigned char*, size_t, krb5_data*, + const char*, struct sockaddr_in*); + + +#endif /* __KDC_LOCL_H__ */ diff --git a/source4/heimdal/kdc/kerberos4.c b/source4/heimdal/kdc/kerberos4.c new file mode 100644 index 0000000000..a81fbb7b59 --- /dev/null +++ b/source4/heimdal/kdc/kerberos4.c @@ -0,0 +1,783 @@ +/* + * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" + +#include <krb5-v4compat.h> + +RCSID("$Id: kerberos4.c,v 1.54 2005/06/30 01:51:43 lha Exp $"); + +#ifndef swap32 +static u_int32_t +swap32(u_int32_t x) +{ + return ((x << 24) & 0xff000000) | + ((x << 8) & 0xff0000) | + ((x >> 8) & 0xff00) | + ((x >> 24) & 0xff); +} +#endif /* swap32 */ + +int +_kdc_maybe_version4(unsigned char *buf, int len) +{ + return len > 0 && *buf == 4; +} + +static void +make_err_reply(krb5_context context, krb5_data *reply, + int code, const char *msg) +{ + _krb5_krb_cr_err_reply(context, "", "", "", + kdc_time, code, msg, reply); +} + +static krb5_boolean +valid_princ(krb5_context context, + void *funcctx, + krb5_principal princ) +{ + krb5_kdc_configuration *config = funcctx; + krb5_error_code ret; + char *s; + hdb_entry *ent; + + ret = krb5_unparse_name(context, princ, &s); + if (ret) + return FALSE; + ret = _kdc_db_fetch(context, config, princ, HDB_ENT_TYPE_ANY, &ent); + if (ret) { + kdc_log(context, config, 7, "Lookup %s failed: %s", s, + krb5_get_err_text (context, ret)); + free(s); + return FALSE; + } + kdc_log(context, config, 7, "Lookup %s succeeded", s); + free(s); + _kdc_free_ent(context, ent); + return TRUE; +} + +krb5_error_code +_kdc_db_fetch4(krb5_context context, + krb5_kdc_configuration *config, + const char *name, const char *instance, const char *realm, + enum hdb_ent_type ent_type, + hdb_entry **ent) +{ + krb5_principal p; + krb5_error_code ret; + + ret = krb5_425_conv_principal_ext2(context, name, instance, realm, + valid_princ, config, 0, &p); + if(ret) + return ret; + ret = _kdc_db_fetch(context, config, p, ent_type, ent); + krb5_free_principal(context, p); + return ret; +} + +#define RCHECK(X, L) if(X){make_err_reply(context, reply, KFAILURE, "Packet too short"); goto L;} + +/* + * Process the v4 request in `buf, len' (received from `addr' + * (with string `from'). + * Return an error code and a reply in `reply'. + */ + +krb5_error_code +_kdc_do_version4(krb5_context context, + krb5_kdc_configuration *config, + unsigned char *buf, + size_t len, + krb5_data *reply, + const char *from, + struct sockaddr_in *addr) +{ + krb5_storage *sp; + krb5_error_code ret; + hdb_entry *client = NULL, *server = NULL; + Key *ckey, *skey; + int8_t pvno; + int8_t msg_type; + int lsb; + char *name = NULL, *inst = NULL, *realm = NULL; + char *sname = NULL, *sinst = NULL; + int32_t req_time; + time_t max_life; + u_int8_t life; + char client_name[256]; + char server_name[256]; + + if(!config->enable_v4) { + kdc_log(context, config, 0, + "Rejected version 4 request from %s", from); + make_err_reply(context, reply, KDC_GEN_ERR, "function not enabled"); + return 0; + } + + sp = krb5_storage_from_mem(buf, len); + RCHECK(krb5_ret_int8(sp, &pvno), out); + if(pvno != 4){ + kdc_log(context, config, 0, + "Protocol version mismatch (krb4) (%d)", pvno); + make_err_reply(context, reply, KDC_PKT_VER, "protocol mismatch"); + goto out; + } + RCHECK(krb5_ret_int8(sp, &msg_type), out); + lsb = msg_type & 1; + msg_type &= ~1; + switch(msg_type){ + case AUTH_MSG_KDC_REQUEST: { + krb5_data ticket, cipher; + krb5_keyblock session; + + krb5_data_zero(&ticket); + krb5_data_zero(&cipher); + + RCHECK(krb5_ret_stringz(sp, &name), out1); + RCHECK(krb5_ret_stringz(sp, &inst), out1); + RCHECK(krb5_ret_stringz(sp, &realm), out1); + RCHECK(krb5_ret_int32(sp, &req_time), out1); + if(lsb) + req_time = swap32(req_time); + RCHECK(krb5_ret_int8(sp, &life), out1); + RCHECK(krb5_ret_stringz(sp, &sname), out1); + RCHECK(krb5_ret_stringz(sp, &sinst), out1); + snprintf (client_name, sizeof(client_name), + "%s.%s@%s", name, inst, realm); + snprintf (server_name, sizeof(server_name), + "%s.%s@%s", sname, sinst, config->v4_realm); + + kdc_log(context, config, 0, "AS-REQ (krb4) %s from %s for %s", + client_name, from, server_name); + + ret = _kdc_db_fetch4(context, config, name, inst, realm, HDB_ENT_TYPE_CLIENT, &client); + if(ret) { + kdc_log(context, config, 0, "Client not found in database: %s: %s", + client_name, krb5_get_err_text(context, ret)); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, + "principal unknown"); + goto out1; + } + ret = _kdc_db_fetch4(context, config, sname, sinst, + config->v4_realm, HDB_ENT_TYPE_SERVER, &server); + if(ret){ + kdc_log(context, config, 0, "Server not found in database: %s: %s", + server_name, krb5_get_err_text(context, ret)); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, + "principal unknown"); + goto out1; + } + + ret = _kdc_check_flags (context, config, + client, client_name, + server, server_name, + TRUE); + if (ret) { + /* good error code? */ + make_err_reply(context, reply, KERB_ERR_NAME_EXP, + "operation not allowed"); + goto out1; + } + + /* + * There's no way to do pre-authentication in v4 and thus no + * good error code to return if preauthentication is required. + */ + + if (config->require_preauth + || client->flags.require_preauth + || server->flags.require_preauth) { + kdc_log(context, config, 0, + "Pre-authentication required for v4-request: " + "%s for %s", + client_name, server_name); + make_err_reply(context, reply, KERB_ERR_NULL_KEY, + "preauth required"); + goto out1; + } + + ret = _kdc_get_des_key(context, client, FALSE, FALSE, &ckey); + if(ret){ + kdc_log(context, config, 0, "no suitable DES key for client"); + make_err_reply(context, reply, KDC_NULL_KEY, + "no suitable DES key for client"); + goto out1; + } + +#if 0 + /* this is not necessary with the new code in libkrb */ + /* find a properly salted key */ + while(ckey->salt == NULL || ckey->salt->salt.length != 0) + ret = hdb_next_keytype2key(context, client, KEYTYPE_DES, &ckey); + if(ret){ + kdc_log(context, config, 0, "No version-4 salted key in database -- %s.%s@%s", + name, inst, realm); + make_err_reply(context, reply, KDC_NULL_KEY, + "No version-4 salted key in database"); + goto out1; + } +#endif + + ret = _kdc_get_des_key(context, server, TRUE, FALSE, &skey); + if(ret){ + kdc_log(context, config, 0, "no suitable DES key for server"); + /* XXX */ + make_err_reply(context, reply, KDC_NULL_KEY, + "no suitable DES key for server"); + goto out1; + } + + max_life = _krb5_krb_life_to_time(0, life); + if(client->max_life) + max_life = min(max_life, *client->max_life); + if(server->max_life) + max_life = min(max_life, *server->max_life); + + life = krb_time_to_life(kdc_time, kdc_time + max_life); + + ret = krb5_generate_random_keyblock(context, + ETYPE_DES_PCBC_NONE, + &session); + if (ret) { + make_err_reply(context, reply, KFAILURE, + "Not enough random i KDC"); + goto out1; + } + + ret = _krb5_krb_create_ticket(context, + 0, + name, + inst, + config->v4_realm, + addr->sin_addr.s_addr, + &session, + life, + kdc_time, + sname, + sinst, + &skey->key, + &ticket); + if (ret) { + krb5_free_keyblock_contents(context, &session); + make_err_reply(context, reply, KFAILURE, + "failed to create v4 ticket"); + goto out1; + } + + ret = _krb5_krb_create_ciph(context, + &session, + sname, + sinst, + config->v4_realm, + life, + server->kvno % 255, + &ticket, + kdc_time, + &ckey->key, + &cipher); + krb5_free_keyblock_contents(context, &session); + krb5_data_free(&ticket); + if (ret) { + make_err_reply(context, reply, KFAILURE, + "Failed to create v4 cipher"); + goto out1; + } + + ret = _krb5_krb_create_auth_reply(context, + name, + inst, + realm, + req_time, + 0, + client->pw_end ? *client->pw_end : 0, + client->kvno % 256, + &cipher, + reply); + krb5_data_free(&cipher); + + out1: + break; + } + case AUTH_MSG_APPL_REQUEST: { + struct _krb5_krb_auth_data ad; + int8_t kvno; + int8_t ticket_len; + int8_t req_len; + krb5_data auth; + int32_t address; + size_t pos; + krb5_principal tgt_princ = NULL; + hdb_entry *tgt = NULL; + Key *tkey; + time_t max_end, actual_end, issue_time; + + memset(&ad, 0, sizeof(ad)); + krb5_data_zero(&auth); + + RCHECK(krb5_ret_int8(sp, &kvno), out2); + RCHECK(krb5_ret_stringz(sp, &realm), out2); + + ret = krb5_425_conv_principal(context, "krbtgt", realm, + config->v4_realm, + &tgt_princ); + if(ret){ + kdc_log(context, config, 0, + "Converting krbtgt principal (krb4): %s", + krb5_get_err_text(context, ret)); + make_err_reply(context, reply, KFAILURE, + "Failed to convert v4 principal (krbtgt)"); + goto out2; + } + + ret = _kdc_db_fetch(context, config, tgt_princ, HDB_ENT_TYPE_SERVER, &tgt); + if(ret){ + char *s; + s = kdc_log_msg(context, config, 0, "Ticket-granting ticket not " + "found in database (krb4): krbtgt.%s@%s: %s", + realm, config->v4_realm, + krb5_get_err_text(context, ret)); + make_err_reply(context, reply, KFAILURE, s); + free(s); + goto out2; + } + + if(tgt->kvno % 256 != kvno){ + kdc_log(context, config, 0, + "tgs-req (krb4) with old kvno %d (current %d) for " + "krbtgt.%s@%s", kvno, tgt->kvno % 256, + realm, config->v4_realm); + make_err_reply(context, reply, KDC_AUTH_EXP, + "old krbtgt kvno used"); + goto out2; + } + + ret = _kdc_get_des_key(context, tgt, TRUE, FALSE, &tkey); + if(ret){ + kdc_log(context, config, 0, + "no suitable DES key for krbtgt (krb4)"); + /* XXX */ + make_err_reply(context, reply, KDC_NULL_KEY, + "no suitable DES key for krbtgt"); + goto out2; + } + + RCHECK(krb5_ret_int8(sp, &ticket_len), out2); + RCHECK(krb5_ret_int8(sp, &req_len), out2); + + pos = krb5_storage_seek(sp, ticket_len + req_len, SEEK_CUR); + + auth.data = buf; + auth.length = pos; + + if (config->check_ticket_addresses) + address = addr->sin_addr.s_addr; + else + address = 0; + + ret = _krb5_krb_rd_req(context, &auth, "krbtgt", realm, + config->v4_realm, + address, &tkey->key, &ad); + if(ret){ + kdc_log(context, config, 0, "krb_rd_req: %d", ret); + make_err_reply(context, reply, ret, "failed to parse request"); + goto out2; + } + + RCHECK(krb5_ret_int32(sp, &req_time), out2); + if(lsb) + req_time = swap32(req_time); + RCHECK(krb5_ret_int8(sp, &life), out2); + RCHECK(krb5_ret_stringz(sp, &sname), out2); + RCHECK(krb5_ret_stringz(sp, &sinst), out2); + snprintf (server_name, sizeof(server_name), + "%s.%s@%s", + sname, sinst, config->v4_realm); + snprintf (client_name, sizeof(client_name), + "%s.%s@%s", + ad.pname, ad.pinst, ad.prealm); + + kdc_log(context, config, 0, "TGS-REQ (krb4) %s from %s for %s", + client_name, from, server_name); + + if(strcmp(ad.prealm, realm)){ + kdc_log(context, config, 0, + "Can't hop realms (krb4) %s -> %s", realm, ad.prealm); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, + "Can't hop realms"); + goto out2; + } + + if (!config->enable_v4_cross_realm && strcmp(realm, config->v4_realm) != 0) { + kdc_log(context, config, 0, + "krb4 Cross-realm %s -> %s disabled", + realm, config->v4_realm); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, + "Can't hop realms"); + goto out2; + } + + if(strcmp(sname, "changepw") == 0){ + kdc_log(context, config, 0, + "Bad request for changepw ticket (krb4)"); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, + "Can't authorize password change based on TGT"); + goto out2; + } + + ret = _kdc_db_fetch4(context, config, ad.pname, ad.pinst, ad.prealm, HDB_ENT_TYPE_CLIENT, &client); + if(ret && ret != HDB_ERR_NOENTRY) { + char *s; + s = kdc_log_msg(context, config, 0, + "Client not found in database: (krb4) %s: %s", + client_name, krb5_get_err_text(context, ret)); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, s); + free(s); + goto out2; + } + if (client == NULL && strcmp(ad.prealm, config->v4_realm) == 0) { + char *s; + s = kdc_log_msg(context, config, 0, + "Local client not found in database: (krb4) " + "%s", client_name); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, s); + free(s); + goto out2; + } + + ret = _kdc_db_fetch4(context, config, sname, sinst, config->v4_realm, + HDB_ENT_TYPE_SERVER, &server); + if(ret){ + char *s; + s = kdc_log_msg(context, config, 0, + "Server not found in database (krb4): %s: %s", + server_name, krb5_get_err_text(context, ret)); + make_err_reply(context, reply, KERB_ERR_PRINCIPAL_UNKNOWN, s); + free(s); + goto out2; + } + + ret = _kdc_check_flags (context, config, + client, client_name, + server, server_name, + FALSE); + if (ret) { + /* good error code? */ + make_err_reply(context, reply, KERB_ERR_NAME_EXP, + "operation not allowed"); + goto out2; + } + + ret = _kdc_get_des_key(context, server, TRUE, FALSE, &skey); + if(ret){ + kdc_log(context, config, 0, + "no suitable DES key for server (krb4)"); + /* XXX */ + make_err_reply(context, reply, KDC_NULL_KEY, + "no suitable DES key for server"); + goto out2; + } + + max_end = _krb5_krb_life_to_time(ad.time_sec, ad.life); + max_end = min(max_end, _krb5_krb_life_to_time(kdc_time, life)); + if(server->max_life) + max_end = min(max_end, kdc_time + *server->max_life); + if(client && client->max_life) + max_end = min(max_end, kdc_time + *client->max_life); + life = min(life, krb_time_to_life(kdc_time, max_end)); + + issue_time = kdc_time; + actual_end = _krb5_krb_life_to_time(issue_time, life); + while (actual_end > max_end && life > 1) { + /* move them into the next earlier lifetime bracket */ + life--; + actual_end = _krb5_krb_life_to_time(issue_time, life); + } + if (actual_end > max_end) { + /* if life <= 1 and it's still too long, backdate the ticket */ + issue_time -= actual_end - max_end; + } + + { + krb5_data ticket, cipher; + krb5_keyblock session; + + krb5_data_zero(&ticket); + krb5_data_zero(&cipher); + + ret = krb5_generate_random_keyblock(context, + ETYPE_DES_PCBC_NONE, + &session); + if (ret) { + make_err_reply(context, reply, KFAILURE, + "Not enough random i KDC"); + goto out2; + } + + ret = _krb5_krb_create_ticket(context, + 0, + ad.pname, + ad.pinst, + ad.prealm, + addr->sin_addr.s_addr, + &session, + life, + issue_time, + sname, + sinst, + &skey->key, + &ticket); + if (ret) { + krb5_free_keyblock_contents(context, &session); + make_err_reply(context, reply, KFAILURE, + "failed to create v4 ticket"); + goto out2; + } + + ret = _krb5_krb_create_ciph(context, + &session, + sname, + sinst, + config->v4_realm, + life, + server->kvno % 255, + &ticket, + issue_time, + &ad.session, + &cipher); + krb5_free_keyblock_contents(context, &session); + if (ret) { + make_err_reply(context, reply, KFAILURE, + "failed to create v4 cipher"); + goto out2; + } + + ret = _krb5_krb_create_auth_reply(context, + ad.pname, + ad.pinst, + ad.prealm, + req_time, + 0, + 0, + 0, + &cipher, + reply); + krb5_data_free(&cipher); + } + out2: + _krb5_krb_free_auth_data(context, &ad); + if(tgt_princ) + krb5_free_principal(context, tgt_princ); + if(tgt) + _kdc_free_ent(context, tgt); + break; + } + case AUTH_MSG_ERR_REPLY: + break; + default: + kdc_log(context, config, 0, "Unknown message type (krb4): %d from %s", + msg_type, from); + + make_err_reply(context, reply, KFAILURE, "Unknown message type"); + } + out: + if(name) + free(name); + if(inst) + free(inst); + if(realm) + free(realm); + if(sname) + free(sname); + if(sinst) + free(sinst); + if(client) + _kdc_free_ent(context, client); + if(server) + _kdc_free_ent(context, server); + krb5_storage_free(sp); + return 0; +} + +krb5_error_code +_kdc_encode_v4_ticket(krb5_context context, + krb5_kdc_configuration *config, + void *buf, size_t len, const EncTicketPart *et, + const PrincipalName *service, size_t *size) +{ + krb5_storage *sp; + krb5_error_code ret; + char name[40], inst[40], realm[40]; + char sname[40], sinst[40]; + + { + krb5_principal princ; + _krb5_principalname2krb5_principal(&princ, + *service, + et->crealm); + ret = krb5_524_conv_principal(context, + princ, + sname, + sinst, + realm); + krb5_free_principal(context, princ); + if(ret) + return ret; + + _krb5_principalname2krb5_principal(&princ, + et->cname, + et->crealm); + + ret = krb5_524_conv_principal(context, + princ, + name, + inst, + realm); + krb5_free_principal(context, princ); + } + if(ret) + return ret; + + sp = krb5_storage_emem(); + + krb5_store_int8(sp, 0); /* flags */ + krb5_store_stringz(sp, name); + krb5_store_stringz(sp, inst); + krb5_store_stringz(sp, realm); + { + unsigned char tmp[4] = { 0, 0, 0, 0 }; + int i; + if(et->caddr){ + for(i = 0; i < et->caddr->len; i++) + if(et->caddr->val[i].addr_type == AF_INET && + et->caddr->val[i].address.length == 4){ + memcpy(tmp, et->caddr->val[i].address.data, 4); + break; + } + } + krb5_storage_write(sp, tmp, sizeof(tmp)); + } + + if((et->key.keytype != ETYPE_DES_CBC_MD5 && + et->key.keytype != ETYPE_DES_CBC_MD4 && + et->key.keytype != ETYPE_DES_CBC_CRC) || + et->key.keyvalue.length != 8) + return -1; + krb5_storage_write(sp, et->key.keyvalue.data, 8); + + { + time_t start = et->starttime ? *et->starttime : et->authtime; + krb5_store_int8(sp, krb_time_to_life(start, et->endtime)); + krb5_store_int32(sp, start); + } + + krb5_store_stringz(sp, sname); + krb5_store_stringz(sp, sinst); + + { + krb5_data data; + krb5_storage_to_data(sp, &data); + krb5_storage_free(sp); + *size = (data.length + 7) & ~7; /* pad to 8 bytes */ + if(*size > len) + return -1; + memset((unsigned char*)buf - *size + 1, 0, *size); + memcpy((unsigned char*)buf - *size + 1, data.data, data.length); + krb5_data_free(&data); + } + return 0; +} + +krb5_error_code +_kdc_get_des_key(krb5_context context, + hdb_entry *principal, krb5_boolean is_server, + krb5_boolean prefer_afs_key, Key **ret_key) +{ + Key *v5_key = NULL, *v4_key = NULL, *afs_key = NULL, *server_key = NULL; + int i; + krb5_enctype etypes[] = { ETYPE_DES_CBC_MD5, + ETYPE_DES_CBC_MD4, + ETYPE_DES_CBC_CRC }; + + for(i = 0; + i < sizeof(etypes)/sizeof(etypes[0]) + && (v5_key == NULL || v4_key == NULL || + afs_key == NULL || server_key == NULL); + ++i) { + Key *key = NULL; + while(hdb_next_enctype2key(context, principal, etypes[i], &key) == 0) { + if(key->salt == NULL) { + if(v5_key == NULL) + v5_key = key; + } else if(key->salt->type == hdb_pw_salt && + key->salt->salt.length == 0) { + if(v4_key == NULL) + v4_key = key; + } else if(key->salt->type == hdb_afs3_salt) { + if(afs_key == NULL) + afs_key = key; + } else if(server_key == NULL) + server_key = key; + } + } + + if(prefer_afs_key) { + if(afs_key) + *ret_key = afs_key; + else if(v4_key) + *ret_key = v4_key; + else if(v5_key) + *ret_key = v5_key; + else if(is_server && server_key) + *ret_key = server_key; + else + return KERB_ERR_NULL_KEY; + } else { + if(v4_key) + *ret_key = v4_key; + else if(afs_key) + *ret_key = afs_key; + else if(v5_key) + *ret_key = v5_key; + else if(is_server && server_key) + *ret_key = server_key; + else + return KERB_ERR_NULL_KEY; + } + + if((*ret_key)->key.keyvalue.length == 0) + return KERB_ERR_NULL_KEY; + return 0; +} + diff --git a/source4/heimdal/kdc/kerberos5.c b/source4/heimdal/kdc/kerberos5.c new file mode 100644 index 0000000000..122c9ab780 --- /dev/null +++ b/source4/heimdal/kdc/kerberos5.c @@ -0,0 +1,2422 @@ +/* + * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" +#ifdef _SAMBA_BUILD_ +#include "kdc/pac-glue.h" +#endif + +RCSID("$Id: kerberos5.c,v 1.177 2005/06/15 11:34:53 lha Exp $"); + +#define MAX_TIME ((time_t)((1U << 31) - 1)) + +static void +fix_time(time_t **t) +{ + if(*t == NULL){ + ALLOC(*t); + **t = MAX_TIME; + } + if(**t == 0) **t = MAX_TIME; /* fix for old clients */ +} + +static int +realloc_method_data(METHOD_DATA *md) +{ + PA_DATA *pa; + pa = realloc(md->val, (md->len + 1) * sizeof(*md->val)); + if(pa == NULL) + return ENOMEM; + md->val = pa; + md->len++; + return 0; +} + +static void +set_salt_padata (METHOD_DATA *md, Salt *salt) +{ + if (salt) { + realloc_method_data(md); + md->val[md->len - 1].padata_type = salt->type; + copy_octet_string(&salt->salt, + &md->val[md->len - 1].padata_value); + } +} + +static PA_DATA* +find_padata(KDC_REQ *req, int *start, int type) +{ + while(*start < req->padata->len){ + (*start)++; + if(req->padata->val[*start - 1].padata_type == type) + return &req->padata->val[*start - 1]; + } + return NULL; +} + +/* + * return the first appropriate key of `princ' in `ret_key'. Look for + * all the etypes in (`etypes', `len'), stopping as soon as we find + * one, but preferring one that has default salt + */ + +static krb5_error_code +find_etype(krb5_context context, hdb_entry *princ, + krb5_enctype *etypes, unsigned len, + Key **ret_key, krb5_enctype *ret_etype) +{ + int i; + krb5_error_code ret = KRB5KDC_ERR_ETYPE_NOSUPP; + + for(i = 0; ret != 0 && i < len ; i++) { + Key *key = NULL; + + if (krb5_enctype_valid(context, etypes[i]) != 0) + continue; + + while (hdb_next_enctype2key(context, princ, etypes[i], &key) == 0) { + if (key->key.keyvalue.length == 0) { + ret = KRB5KDC_ERR_NULL_KEY; + continue; + } + *ret_key = key; + *ret_etype = etypes[i]; + ret = 0; + if (key->salt == NULL) + return ret; + } + } + return ret; +} + +static krb5_error_code +find_keys(krb5_context context, + krb5_kdc_configuration *config, + hdb_entry *client, + hdb_entry *server, + Key **ckey, + krb5_enctype *cetype, + Key **skey, + krb5_enctype *setype, + krb5_enctype *etypes, + unsigned num_etypes) +{ + char unparse_name[] = "krb5_unparse_name failed"; + krb5_error_code ret; + char *name; + + if(client){ + /* find client key */ + ret = find_etype(context, client, etypes, num_etypes, ckey, cetype); + if (ret) { + if (krb5_unparse_name(context, client->principal, &name) != 0) + name = unparse_name; + kdc_log(context, config, 0, + "Client (%s) has no support for etypes", name); + if (name != unparse_name) + free(name); + return ret; + } + } + + if(server){ + /* find server key */ + ret = find_etype(context, server, etypes, num_etypes, skey, setype); + if (ret) { + if (krb5_unparse_name(context, server->principal, &name) != 0) + name = unparse_name; + kdc_log(context, config, 0, + "Server (%s) has no support for etypes", name); + if (name != unparse_name) + free(name); + return ret; + } + } + return 0; +} + +static krb5_error_code +make_anonymous_principalname (PrincipalName *pn) +{ + pn->name_type = KRB5_NT_PRINCIPAL; + pn->name_string.len = 1; + pn->name_string.val = malloc(sizeof(*pn->name_string.val)); + if (pn->name_string.val == NULL) + return ENOMEM; + pn->name_string.val[0] = strdup("anonymous"); + if (pn->name_string.val[0] == NULL) { + free(pn->name_string.val); + pn->name_string.val = NULL; + return ENOMEM; + } + return 0; +} + +static void +log_timestamp(krb5_context context, + krb5_kdc_configuration *config, + const char *type, + KerberosTime authtime, KerberosTime *starttime, + KerberosTime endtime, KerberosTime *renew_till) +{ + char atime[100], stime[100], etime[100], rtime[100]; + + krb5_format_time(context, authtime, atime, sizeof(atime), TRUE); + if (starttime) + krb5_format_time(context, *starttime, stime, sizeof(stime), TRUE); + else + strlcpy(stime, "unset", sizeof(stime)); + krb5_format_time(context, endtime, etime, sizeof(etime), TRUE); + if (renew_till) + krb5_format_time(context, *renew_till, rtime, sizeof(rtime), TRUE); + else + strlcpy(rtime, "unset", sizeof(rtime)); + + kdc_log(context, config, 5, + "%s authtime: %s starttime: %s endtype: %s renew till: %s", + type, atime, stime, etime, rtime); +} + +static krb5_error_code +encode_reply(krb5_context context, + krb5_kdc_configuration *config, + KDC_REP *rep, EncTicketPart *et, EncKDCRepPart *ek, + krb5_enctype etype, + int skvno, EncryptionKey *skey, + int ckvno, EncryptionKey *ckey, + const char **e_text, + krb5_data *reply) +{ + unsigned char *buf; + size_t buf_size; + size_t len; + krb5_error_code ret; + krb5_crypto crypto; + + ASN1_MALLOC_ENCODE(EncTicketPart, buf, buf_size, et, &len, ret); + if(ret) { + kdc_log(context, config, 0, "Failed to encode ticket: %s", + krb5_get_err_text(context, ret)); + return ret; + } + if(buf_size != len) { + free(buf); + kdc_log(context, config, 0, "Internal error in ASN.1 encoder"); + *e_text = "KDC internal error"; + return KRB5KRB_ERR_GENERIC; + } + + ret = krb5_crypto_init(context, skey, etype, &crypto); + if (ret) { + free(buf); + kdc_log(context, config, 0, "krb5_crypto_init failed: %s", + krb5_get_err_text(context, ret)); + return ret; + } + + ret = krb5_encrypt_EncryptedData(context, + crypto, + KRB5_KU_TICKET, + buf, + len, + skvno, + &rep->ticket.enc_part); + free(buf); + krb5_crypto_destroy(context, crypto); + if(ret) { + kdc_log(context, config, 0, "Failed to encrypt data: %s", + krb5_get_err_text(context, ret)); + return ret; + } + + if(rep->msg_type == krb_as_rep && !config->encode_as_rep_as_tgs_rep) + ASN1_MALLOC_ENCODE(EncASRepPart, buf, buf_size, ek, &len, ret); + else + ASN1_MALLOC_ENCODE(EncTGSRepPart, buf, buf_size, ek, &len, ret); + if(ret) { + kdc_log(context, config, 0, "Failed to encode KDC-REP: %s", + krb5_get_err_text(context, ret)); + return ret; + } + if(buf_size != len) { + free(buf); + kdc_log(context, config, 0, "Internal error in ASN.1 encoder"); + *e_text = "KDC internal error"; + return KRB5KRB_ERR_GENERIC; + } + ret = krb5_crypto_init(context, ckey, 0, &crypto); + if (ret) { + free(buf); + kdc_log(context, config, 0, "krb5_crypto_init failed: %s", + krb5_get_err_text(context, ret)); + return ret; + } + if(rep->msg_type == krb_as_rep) { + krb5_encrypt_EncryptedData(context, + crypto, + KRB5_KU_AS_REP_ENC_PART, + buf, + len, + ckvno, + &rep->enc_part); + free(buf); + ASN1_MALLOC_ENCODE(AS_REP, buf, buf_size, rep, &len, ret); + } else { + krb5_encrypt_EncryptedData(context, + crypto, + KRB5_KU_TGS_REP_ENC_PART_SESSION, + buf, + len, + ckvno, + &rep->enc_part); + free(buf); + ASN1_MALLOC_ENCODE(TGS_REP, buf, buf_size, rep, &len, ret); + } + krb5_crypto_destroy(context, crypto); + if(ret) { + kdc_log(context, config, 0, "Failed to encode KDC-REP: %s", + krb5_get_err_text(context, ret)); + return ret; + } + if(buf_size != len) { + free(buf); + kdc_log(context, config, 0, "Internal error in ASN.1 encoder"); + *e_text = "KDC internal error"; + return KRB5KRB_ERR_GENERIC; + } + reply->data = buf; + reply->length = buf_size; + return 0; +} + +static krb5_error_code +make_etype_info_entry(krb5_context context, ETYPE_INFO_ENTRY *ent, Key *key) +{ + ent->etype = key->key.keytype; + if(key->salt){ + ALLOC(ent->salttype); +#if 0 + if(key->salt->type == hdb_pw_salt) + *ent->salttype = 0; /* or 1? or NULL? */ + else if(key->salt->type == hdb_afs3_salt) + *ent->salttype = 2; + else { + kdc_log(context, config, 0, "unknown salt-type: %d", + key->salt->type); + return KRB5KRB_ERR_GENERIC; + } + /* according to `the specs', we can't send a salt if + we have AFS3 salted key, but that requires that you + *know* what cell you are using (e.g by assuming + that the cell is the same as the realm in lower + case) */ +#else + *ent->salttype = key->salt->type; +#endif + krb5_copy_data(context, &key->salt->salt, + &ent->salt); + } else { + /* we return no salt type at all, as that should indicate + * the default salt type and make everybody happy. some + * systems (like w2k) dislike being told the salt type + * here. */ + + ent->salttype = NULL; + ent->salt = NULL; + } + return 0; +} + +static krb5_error_code +get_pa_etype_info(krb5_context context, + krb5_kdc_configuration *config, + METHOD_DATA *md, hdb_entry *client, + ENCTYPE *etypes, unsigned int etypes_len) +{ + krb5_error_code ret = 0; + int i, j; + unsigned int n = 0; + ETYPE_INFO pa; + unsigned char *buf; + size_t len; + + + pa.len = client->keys.len; + if(pa.len > UINT_MAX/sizeof(*pa.val)) + return ERANGE; + pa.val = malloc(pa.len * sizeof(*pa.val)); + if(pa.val == NULL) + return ENOMEM; + memset(pa.val, 0, pa.len * sizeof(*pa.val)); + + for(j = 0; j < etypes_len; j++) { + for (i = 0; i < n; i++) + if (pa.val[i].etype == etypes[j]) + goto skip1; + for(i = 0; i < client->keys.len; i++) { + if(client->keys.val[i].key.keytype == etypes[j]) { + if (krb5_enctype_valid(context, etypes[j]) != 0) + continue; + if((ret = make_etype_info_entry(context, + &pa.val[n++], + &client->keys.val[i])) != 0) { + free_ETYPE_INFO(&pa); + return ret; + } + } + } + skip1:; + } + for(i = 0; i < client->keys.len; i++) { + for(j = 0; j < etypes_len; j++) { + if(client->keys.val[i].key.keytype == etypes[j]) + goto skip2; + } + if (krb5_enctype_valid(context, client->keys.val[i].key.keytype) != 0) + continue; + if((ret = make_etype_info_entry(context, + &pa.val[n++], + &client->keys.val[i])) != 0) { + free_ETYPE_INFO(&pa); + return ret; + } + skip2:; + } + + if(n != pa.len) { + char *name; + ret = krb5_unparse_name(context, client->principal, &name); + if (ret) + name = "<unparse_name failed>"; + kdc_log(context, config, 0, "internal error in get_pa_etype_info(%s): %d != %d", + name, n, pa.len); + if (ret == 0) + free(name); + pa.len = n; + } + + ASN1_MALLOC_ENCODE(ETYPE_INFO, buf, len, &pa, &len, ret); + free_ETYPE_INFO(&pa); + if(ret) + return ret; + ret = realloc_method_data(md); + if(ret) { + free(buf); + return ret; + } + md->val[md->len - 1].padata_type = KRB5_PADATA_ETYPE_INFO; + md->val[md->len - 1].padata_value.length = len; + md->val[md->len - 1].padata_value.data = buf; + return 0; +} + +/* + * + */ + +extern int _krb5_AES_string_to_default_iterator; + +static krb5_error_code +make_etype_info2_entry(ETYPE_INFO2_ENTRY *ent, Key *key) +{ + ent->etype = key->key.keytype; + if(key->salt) { + ALLOC(ent->salt); + if (ent->salt == NULL) + return ENOMEM; + *ent->salt = malloc(key->salt->salt.length + 1); + if (*ent->salt == NULL) { + free(ent->salt); + ent->salt = NULL; + return ENOMEM; + } + memcpy(*ent->salt, key->salt->salt.data, key->salt->salt.length); + (*ent->salt)[key->salt->salt.length] = '\0'; + } else + ent->salt = NULL; + + ent->s2kparams = NULL; + + switch (key->key.keytype) { + case KEYTYPE_AES128: + case KEYTYPE_AES256: + ALLOC(ent->s2kparams); + if (ent->s2kparams == NULL) + return ENOMEM; + ent->s2kparams->length = 4; + ent->s2kparams->data = malloc(ent->s2kparams->length); + if (ent->s2kparams->data == NULL) { + free(ent->s2kparams); + ent->s2kparams = NULL; + return ENOMEM; + } + _krb5_put_int(ent->s2kparams->data, + _krb5_AES_string_to_default_iterator, + ent->s2kparams->length); + break; + default: + break; + } + return 0; +} + +/* + * Return 1 if the client have only older enctypes, this is for + * determining if the server should send ETYPE_INFO2 or not. + */ + +static int +only_older_enctype_p(const KDC_REQ *req) +{ + int i; + + for(i = 0; i < req->req_body.etype.len; i++) { + switch (req->req_body.etype.val[i]) { + case ETYPE_DES_CBC_CRC: + case ETYPE_DES_CBC_MD4: + case ETYPE_DES_CBC_MD5: + case ETYPE_DES3_CBC_SHA1: + case ETYPE_ARCFOUR_HMAC_MD5: + case ETYPE_ARCFOUR_HMAC_MD5_56: + break; + default: + return 0; + } + } + return 1; +} + +/* + * + */ + +static krb5_error_code +get_pa_etype_info2(krb5_context context, + krb5_kdc_configuration *config, + METHOD_DATA *md, hdb_entry *client, + ENCTYPE *etypes, unsigned int etypes_len) +{ + krb5_error_code ret = 0; + int i, j; + unsigned int n = 0; + ETYPE_INFO2 pa; + unsigned char *buf; + size_t len; + + pa.len = client->keys.len; + if(pa.len > UINT_MAX/sizeof(*pa.val)) + return ERANGE; + pa.val = malloc(pa.len * sizeof(*pa.val)); + if(pa.val == NULL) + return ENOMEM; + memset(pa.val, 0, pa.len * sizeof(*pa.val)); + + for(j = 0; j < etypes_len; j++) { + for (i = 0; i < n; i++) + if (pa.val[i].etype == etypes[j]) + goto skip1; + for(i = 0; i < client->keys.len; i++) { + if(client->keys.val[i].key.keytype == etypes[j]) { + if (krb5_enctype_valid(context, etypes[j]) != 0) + continue; + if((ret = make_etype_info2_entry(&pa.val[n++], + &client->keys.val[i])) != 0) { + free_ETYPE_INFO2(&pa); + return ret; + } + } + } + skip1:; + } + for(i = 0; i < client->keys.len; i++) { + for(j = 0; j < etypes_len; j++) { + if(client->keys.val[i].key.keytype == etypes[j]) + goto skip2; + } + if (krb5_enctype_valid(context, client->keys.val[i].key.keytype) != 0) + continue; + if((ret = make_etype_info2_entry(&pa.val[n++], + &client->keys.val[i])) != 0) { + free_ETYPE_INFO2(&pa); + return ret; + } + skip2:; + } + + if(n != pa.len) { + char *name; + ret = krb5_unparse_name(context, client->principal, &name); + if (ret) + name = "<unparse_name failed>"; + kdc_log(context, config, 0, "internal error in get_pa_etype_info2(%s): %d != %d", + name, n, pa.len); + if (ret == 0) + free(name); + pa.len = n; + } + + ASN1_MALLOC_ENCODE(ETYPE_INFO2, buf, len, &pa, &len, ret); + free_ETYPE_INFO2(&pa); + if(ret) + return ret; + ret = realloc_method_data(md); + if(ret) { + free(buf); + return ret; + } + md->val[md->len - 1].padata_type = KRB5_PADATA_ETYPE_INFO2; + md->val[md->len - 1].padata_value.length = len; + md->val[md->len - 1].padata_value.data = buf; + return 0; +} + +/* + * verify the flags on `client' and `server', returning 0 + * if they are OK and generating an error messages and returning + * and error code otherwise. + */ + +krb5_error_code +_kdc_check_flags(krb5_context context, + krb5_kdc_configuration *config, + hdb_entry *client, const char *client_name, + hdb_entry *server, const char *server_name, + krb5_boolean is_as_req) +{ + if(client != NULL) { + /* check client */ + if (client->flags.invalid) { + kdc_log(context, config, 0, + "Client (%s) has invalid bit set", client_name); + return KRB5KDC_ERR_POLICY; + } + + if(!client->flags.client){ + kdc_log(context, config, 0, + "Principal may not act as client -- %s", + client_name); + return KRB5KDC_ERR_POLICY; + } + + if (client->valid_start && *client->valid_start > kdc_time) { + kdc_log(context, config, 0, "Client not yet valid -- %s", client_name); + return KRB5KDC_ERR_CLIENT_NOTYET; + } + + if (client->valid_end && *client->valid_end < kdc_time) { + kdc_log(context, config, 0, "Client expired -- %s", client_name); + return KRB5KDC_ERR_NAME_EXP; + } + + if (client->pw_end && *client->pw_end < kdc_time + && !server->flags.change_pw) { + kdc_log(context, config, 0, "Client's key has expired -- %s", client_name); + return KRB5KDC_ERR_KEY_EXPIRED; + } + } + + /* check server */ + + if (server != NULL) { + if (server->flags.invalid) { + kdc_log(context, config, 0, "Server has invalid flag set -- %s", server_name); + return KRB5KDC_ERR_POLICY; + } + + if(!server->flags.server){ + kdc_log(context, config, 0, "Principal may not act as server -- %s", + server_name); + return KRB5KDC_ERR_POLICY; + } + + if(!is_as_req && server->flags.initial) { + kdc_log(context, config, 0, "AS-REQ is required for server -- %s", server_name); + return KRB5KDC_ERR_POLICY; + } + + if (server->valid_start && *server->valid_start > kdc_time) { + kdc_log(context, config, 0, "Server not yet valid -- %s", server_name); + return KRB5KDC_ERR_SERVICE_NOTYET; + } + + if (server->valid_end && *server->valid_end < kdc_time) { + kdc_log(context, config, 0, "Server expired -- %s", server_name); + return KRB5KDC_ERR_SERVICE_EXP; + } + + if (server->pw_end && *server->pw_end < kdc_time) { + kdc_log(context, config, 0, "Server's key has expired -- %s", server_name); + return KRB5KDC_ERR_KEY_EXPIRED; + } + } + return 0; +} + +/* + * Return TRUE if `from' is part of `addresses' taking into consideration + * the configuration variables that tells us how strict we should be about + * these checks + */ + +static krb5_boolean +check_addresses(krb5_context context, + krb5_kdc_configuration *config, + HostAddresses *addresses, const struct sockaddr *from) +{ + krb5_error_code ret; + krb5_address addr; + krb5_boolean result; + + if(config->check_ticket_addresses == 0) + return TRUE; + + if(addresses == NULL) + return config->allow_null_ticket_addresses; + + ret = krb5_sockaddr2address (context, from, &addr); + if(ret) + return FALSE; + + result = krb5_address_search(context, &addr, addresses); + krb5_free_address (context, &addr); + return result; +} + +krb5_error_code +_kdc_as_rep(krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ *req, + krb5_data *reply, + const char *from, + struct sockaddr *from_addr) +{ + KDC_REQ_BODY *b = &req->req_body; + AS_REP rep; + KDCOptions f = b->kdc_options; + hdb_entry *client = NULL, *server = NULL; + krb5_enctype cetype, setype; + EncTicketPart et; + EncKDCRepPart ek; + krb5_principal client_princ = NULL, server_princ = NULL; + char *client_name = NULL, *server_name = NULL; + krb5_error_code ret = 0; + const char *e_text = NULL; + krb5_crypto crypto; + Key *ckey, *skey; + EncryptionKey *reply_key; +#ifdef PKINIT + pk_client_params *pkp = NULL; +#endif + + memset(&rep, 0, sizeof(rep)); + + if(b->sname == NULL){ + ret = KRB5KRB_ERR_GENERIC; + e_text = "No server in request"; + } else{ + _krb5_principalname2krb5_principal (&server_princ, + *(b->sname), b->realm); + ret = krb5_unparse_name(context, server_princ, &server_name); + } + if (ret) { + kdc_log(context, config, 0, "AS-REQ malformed server name from %s", from); + goto out; + } + + if(b->cname == NULL){ + ret = KRB5KRB_ERR_GENERIC; + e_text = "No client in request"; + } else { + _krb5_principalname2krb5_principal (&client_princ, + *(b->cname), b->realm); + ret = krb5_unparse_name(context, client_princ, &client_name); + } + if (ret) { + kdc_log(context, config, 0, "AS-REQ malformed client name from %s", from); + goto out; + } + + kdc_log(context, config, 0, "AS-REQ %s from %s for %s", + client_name, from, server_name); + + ret = _kdc_db_fetch(context, config, client_princ, HDB_ENT_TYPE_CLIENT, &client); + if(ret){ + kdc_log(context, config, 0, "UNKNOWN -- %s: %s", client_name, + krb5_get_err_text(context, ret)); + ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + goto out; + } + + ret = _kdc_db_fetch(context, config, server_princ, HDB_ENT_TYPE_SERVER, &server); + if(ret){ + kdc_log(context, config, 0, "UNKNOWN -- %s: %s", server_name, + krb5_get_err_text(context, ret)); + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + goto out; + } + + ret = _kdc_check_flags(context, config, + client, client_name, + server, server_name, + TRUE); + if(ret) + goto out; + + memset(&et, 0, sizeof(et)); + memset(&ek, 0, sizeof(ek)); + + if(req->padata){ + int i = 0; + PA_DATA *pa; + int found_pa = 0; + +#ifdef PKINIT + kdc_log(context, config, 5, + "Looking for PKINIT pa-data -- %s", client_name); + + e_text = "No PKINIT PA found"; + + i = 0; + if ((pa = find_padata(req, &i, KRB5_PADATA_PK_AS_REQ))) + ; + if (pa == NULL) { + i = 0; + if((pa = find_padata(req, &i, KRB5_PADATA_PK_AS_REQ_19))) + ; + } + if (pa == NULL) { + i = 0; + if((pa = find_padata(req, &i, KRB5_PADATA_PK_AS_REQ_WIN))) + ; + } + if (pa) { + char *client_cert = NULL; + + ret = _kdc_pk_rd_padata(context, config, req, pa, &pkp); + if (ret) { + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + kdc_log(context, config, 5, + "Failed to decode PKINIT PA-DATA -- %s", + client_name); + goto ts_enc; + } + if (ret == 0 && pkp == NULL) + goto ts_enc; + + ret = _kdc_pk_check_client(context, + config, + client_princ, + client, + pkp, + &client_cert); + if (ret) { + e_text = "PKINIT certificate not allowed to " + "impersonate principal"; + _kdc_pk_free_client_param(context, pkp); + pkp = NULL; + goto ts_enc; + } + found_pa = 1; + et.flags.pre_authent = 1; + kdc_log(context, config, 2, + "PKINIT pre-authentication succeeded -- %s using %s", + client_name, client_cert); + free(client_cert); + if (pkp) + goto preauth_done; + } + ts_enc: +#endif + kdc_log(context, config, 5, "Looking for ENC-TS pa-data -- %s", + client_name); + + i = 0; + e_text = "No ENC-TS found"; + while((pa = find_padata(req, &i, KRB5_PADATA_ENC_TIMESTAMP))){ + krb5_data ts_data; + PA_ENC_TS_ENC p; + size_t len; + EncryptedData enc_data; + Key *pa_key; + + found_pa = 1; + + ret = decode_EncryptedData(pa->padata_value.data, + pa->padata_value.length, + &enc_data, + &len); + if (ret) { + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + kdc_log(context, config, 5, "Failed to decode PA-DATA -- %s", + client_name); + goto out; + } + + ret = hdb_enctype2key(context, client, enc_data.etype, &pa_key); + if(ret){ + char *estr; + e_text = "No key matches pa-data"; + ret = KRB5KDC_ERR_PREAUTH_FAILED; + if(krb5_enctype_to_string(context, enc_data.etype, &estr)) + estr = NULL; + if(estr == NULL) + kdc_log(context, config, 5, + "No client key matching pa-data (%d) -- %s", + enc_data.etype, client_name); + else + kdc_log(context, config, 5, + "No client key matching pa-data (%s) -- %s", + estr, client_name); + free(estr); + + free_EncryptedData(&enc_data); + continue; + } + + try_next_key: + ret = krb5_crypto_init(context, &pa_key->key, 0, &crypto); + if (ret) { + kdc_log(context, config, 0, "krb5_crypto_init failed: %s", + krb5_get_err_text(context, ret)); + free_EncryptedData(&enc_data); + continue; + } + + ret = krb5_decrypt_EncryptedData (context, + crypto, + KRB5_KU_PA_ENC_TIMESTAMP, + &enc_data, + &ts_data); + krb5_crypto_destroy(context, crypto); + if(ret){ + if(hdb_next_enctype2key(context, client, + enc_data.etype, &pa_key) == 0) + goto try_next_key; + free_EncryptedData(&enc_data); + e_text = "Failed to decrypt PA-DATA"; + kdc_log(context, config, + 5, "Failed to decrypt PA-DATA -- %s", + client_name); + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + continue; + } + free_EncryptedData(&enc_data); + ret = decode_PA_ENC_TS_ENC(ts_data.data, + ts_data.length, + &p, + &len); + krb5_data_free(&ts_data); + if(ret){ + e_text = "Failed to decode PA-ENC-TS-ENC"; + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + kdc_log(context, config, + 5, "Failed to decode PA-ENC-TS_ENC -- %s", + client_name); + continue; + } + free_PA_ENC_TS_ENC(&p); + if (abs(kdc_time - p.patimestamp) > context->max_skew) { + ret = KRB5KDC_ERR_PREAUTH_FAILED; + e_text = "Too large time skew"; + kdc_log(context, config, 0, + "Too large time skew -- %s", client_name); + goto out; + } + et.flags.pre_authent = 1; + kdc_log(context, config, 2, + "ENC-TS Pre-authentication succeeded -- %s", + client_name); + break; + } +#ifdef PKINIT + preauth_done: +#endif + if(found_pa == 0 && config->require_preauth) + goto use_pa; + /* We come here if we found a pa-enc-timestamp, but if there + was some problem with it, other than too large skew */ + if(found_pa && et.flags.pre_authent == 0){ + kdc_log(context, config, 0, "%s -- %s", e_text, client_name); + e_text = NULL; + goto out; + } + }else if (config->require_preauth + || client->flags.require_preauth + || server->flags.require_preauth) { + METHOD_DATA method_data; + PA_DATA *pa; + unsigned char *buf; + size_t len; + krb5_data foo_data; + + use_pa: + method_data.len = 0; + method_data.val = NULL; + + ret = realloc_method_data(&method_data); + pa = &method_data.val[method_data.len-1]; + pa->padata_type = KRB5_PADATA_ENC_TIMESTAMP; + pa->padata_value.length = 0; + pa->padata_value.data = NULL; + +#ifdef PKINIT + ret = realloc_method_data(&method_data); + pa = &method_data.val[method_data.len-1]; + pa->padata_type = KRB5_PADATA_PK_AS_REQ; + pa->padata_value.length = 0; + pa->padata_value.data = NULL; + + ret = realloc_method_data(&method_data); + pa = &method_data.val[method_data.len-1]; + pa->padata_type = KRB5_PADATA_PK_AS_REQ_19; + pa->padata_value.length = 0; + pa->padata_value.data = NULL; +#endif + + /* XXX check ret */ + if (only_older_enctype_p(req)) + ret = get_pa_etype_info(context, config, &method_data, client, + b->etype.val, b->etype.len); + /* XXX check ret */ + ret = get_pa_etype_info2(context, config, &method_data, client, + b->etype.val, b->etype.len); + + + ASN1_MALLOC_ENCODE(METHOD_DATA, buf, len, &method_data, &len, ret); + free_METHOD_DATA(&method_data); + foo_data.data = buf; + foo_data.length = len; + + ret = KRB5KDC_ERR_PREAUTH_REQUIRED; + krb5_mk_error(context, + ret, + "Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ", + &foo_data, + client_princ, + server_princ, + NULL, + NULL, + reply); + free(buf); + kdc_log(context, config, 0, + "No preauth found, returning PREAUTH-REQUIRED -- %s", + client_name); + ret = 0; + goto out2; + } + + ret = find_keys(context, config, + client, server, &ckey, &cetype, &skey, &setype, + b->etype.val, b->etype.len); + if(ret) { + kdc_log(context, config, 0, "Server/client has no support for etypes"); + goto out; + } + + { + struct rk_strpool *p = NULL; + char *str; + int i; + + for (i = 0; i < b->etype.len; i++) { + ret = krb5_enctype_to_string(context, b->etype.val[i], &str); + if (ret == 0) { + p = rk_strpoolprintf(p, "%s", str); + free(str); + } else + p = rk_strpoolprintf(p, "%d", b->etype.val[i]); + if (p && i + 1 < b->etype.len) + p = rk_strpoolprintf(p, ", "); + if (p == NULL) { + kdc_log(context, config, 0, "out of meory"); + goto out; + } + } + str = rk_strpoolcollect(p); + kdc_log(context, config, 0, "Client supported enctypes: %s", str); + free(str); + } + { + char *cet; + char *set; + + ret = krb5_enctype_to_string(context, cetype, &cet); + if(ret == 0) { + ret = krb5_enctype_to_string(context, setype, &set); + if (ret == 0) { + kdc_log(context, config, 5, "Using %s/%s", cet, set); + free(set); + } + free(cet); + } + if (ret != 0) + kdc_log(context, config, 5, "Using e-types %d/%d", cetype, setype); + } + + { + char str[128]; + unparse_flags(KDCOptions2int(f), asn1_KDCOptions_units(), + str, sizeof(str)); + if(*str) + kdc_log(context, config, 2, "Requested flags: %s", str); + } + + + if(f.renew || f.validate || f.proxy || f.forwarded || f.enc_tkt_in_skey + || (f.request_anonymous && !config->allow_anonymous)) { + ret = KRB5KDC_ERR_BADOPTION; + kdc_log(context, config, 0, "Bad KDC options -- %s", client_name); + goto out; + } + + rep.pvno = 5; + rep.msg_type = krb_as_rep; + copy_Realm(&client->principal->realm, &rep.crealm); + if (f.request_anonymous) + make_anonymous_principalname (&rep.cname); + else + _krb5_principal2principalname(&rep.cname, + client->principal); + rep.ticket.tkt_vno = 5; + copy_Realm(&server->principal->realm, &rep.ticket.realm); + _krb5_principal2principalname(&rep.ticket.sname, + server->principal); + + et.flags.initial = 1; + if(client->flags.forwardable && server->flags.forwardable) + et.flags.forwardable = f.forwardable; + else if (f.forwardable) { + ret = KRB5KDC_ERR_POLICY; + kdc_log(context, config, 0, + "Ticket may not be forwardable -- %s", client_name); + goto out; + } + if(client->flags.proxiable && server->flags.proxiable) + et.flags.proxiable = f.proxiable; + else if (f.proxiable) { + ret = KRB5KDC_ERR_POLICY; + kdc_log(context, config, 0, + "Ticket may not be proxiable -- %s", client_name); + goto out; + } + if(client->flags.postdate && server->flags.postdate) + et.flags.may_postdate = f.allow_postdate; + else if (f.allow_postdate){ + ret = KRB5KDC_ERR_POLICY; + kdc_log(context, config, 0, + "Ticket may not be postdatable -- %s", client_name); + goto out; + } + + /* check for valid set of addresses */ + if(!check_addresses(context, config, b->addresses, from_addr)) { + ret = KRB5KRB_AP_ERR_BADADDR; + kdc_log(context, config, 0, + "Bad address list requested -- %s", client_name); + goto out; + } + + krb5_generate_random_keyblock(context, setype, &et.key); + copy_PrincipalName(&rep.cname, &et.cname); + copy_Realm(&rep.crealm, &et.crealm); + + { + time_t start; + time_t t; + + start = et.authtime = kdc_time; + + if(f.postdated && req->req_body.from){ + ALLOC(et.starttime); + start = *et.starttime = *req->req_body.from; + et.flags.invalid = 1; + et.flags.postdated = 1; /* XXX ??? */ + } + fix_time(&b->till); + t = *b->till; + + /* be careful not overflowing */ + + if(client->max_life) + t = start + min(t - start, *client->max_life); + if(server->max_life) + t = start + min(t - start, *server->max_life); +#if 0 + t = min(t, start + realm->max_life); +#endif + et.endtime = t; + if(f.renewable_ok && et.endtime < *b->till){ + f.renewable = 1; + if(b->rtime == NULL){ + ALLOC(b->rtime); + *b->rtime = 0; + } + if(*b->rtime < *b->till) + *b->rtime = *b->till; + } + if(f.renewable && b->rtime){ + t = *b->rtime; + if(t == 0) + t = MAX_TIME; + if(client->max_renew) + t = start + min(t - start, *client->max_renew); + if(server->max_renew) + t = start + min(t - start, *server->max_renew); +#if 0 + t = min(t, start + realm->max_renew); +#endif + ALLOC(et.renew_till); + *et.renew_till = t; + et.flags.renewable = 1; + } + } + + if (f.request_anonymous) + et.flags.anonymous = 1; + + if(b->addresses){ + ALLOC(et.caddr); + copy_HostAddresses(b->addresses, et.caddr); + } + + et.transited.tr_type = DOMAIN_X500_COMPRESS; + krb5_data_zero(&et.transited.contents); + + copy_EncryptionKey(&et.key, &ek.key); + + /* The MIT ASN.1 library (obviously) doesn't tell lengths encoded + * as 0 and as 0x80 (meaning indefinite length) apart, and is thus + * incapable of correctly decoding SEQUENCE OF's of zero length. + * + * To fix this, always send at least one no-op last_req + * + * If there's a pw_end or valid_end we will use that, + * otherwise just a dummy lr. + */ + ek.last_req.val = malloc(2 * sizeof(*ek.last_req.val)); + ek.last_req.len = 0; + if (client->pw_end + && (config->kdc_warn_pwexpire == 0 + || kdc_time + config->kdc_warn_pwexpire <= *client->pw_end)) { + ek.last_req.val[ek.last_req.len].lr_type = LR_PW_EXPTIME; + ek.last_req.val[ek.last_req.len].lr_value = *client->pw_end; + ++ek.last_req.len; + } + if (client->valid_end) { + ek.last_req.val[ek.last_req.len].lr_type = LR_ACCT_EXPTIME; + ek.last_req.val[ek.last_req.len].lr_value = *client->valid_end; + ++ek.last_req.len; + } + if (ek.last_req.len == 0) { + ek.last_req.val[ek.last_req.len].lr_type = LR_NONE; + ek.last_req.val[ek.last_req.len].lr_value = 0; + ++ek.last_req.len; + } + ek.nonce = b->nonce; + if (client->valid_end || client->pw_end) { + ALLOC(ek.key_expiration); + if (client->valid_end) { + if (client->pw_end) + *ek.key_expiration = min(*client->valid_end, *client->pw_end); + else + *ek.key_expiration = *client->valid_end; + } else + *ek.key_expiration = *client->pw_end; + } else + ek.key_expiration = NULL; + ek.flags = et.flags; + ek.authtime = et.authtime; + if (et.starttime) { + ALLOC(ek.starttime); + *ek.starttime = *et.starttime; + } + ek.endtime = et.endtime; + if (et.renew_till) { + ALLOC(ek.renew_till); + *ek.renew_till = *et.renew_till; + } + copy_Realm(&rep.ticket.realm, &ek.srealm); + copy_PrincipalName(&rep.ticket.sname, &ek.sname); + if(et.caddr){ + ALLOC(ek.caddr); + copy_HostAddresses(et.caddr, ek.caddr); + } + + ALLOC(rep.padata); + rep.padata->len = 0; + rep.padata->val = NULL; + + reply_key = &ckey->key; +#if PKINIT + if (pkp) { + ret = _kdc_pk_mk_pa_reply(context, config, pkp, client, req, + &reply_key, rep.padata); + if (ret) + goto out; + } +#endif + + set_salt_padata (rep.padata, ckey->salt); + + if (rep.padata->len == 0) { + free(rep.padata); + rep.padata = NULL; + } + + log_timestamp(context, config, "AS-REQ", et.authtime, et.starttime, + et.endtime, et.renew_till); + + ret = encode_reply(context, config, + &rep, &et, &ek, setype, server->kvno, &skey->key, + client->kvno, reply_key, &e_text, reply); + free_EncTicketPart(&et); + free_EncKDCRepPart(&ek); + out: + free_AS_REP(&rep); + if(ret){ + krb5_mk_error(context, + ret, + e_text, + NULL, + client_princ, + server_princ, + NULL, + NULL, + reply); + ret = 0; + } + out2: +#ifdef PKINIT + if (pkp) + _kdc_pk_free_client_param(context, pkp); +#endif + if (client_princ) + krb5_free_principal(context, client_princ); + free(client_name); + if (server_princ) + krb5_free_principal(context, server_princ); + free(server_name); + if(client) + _kdc_free_ent(context, client); + if(server) + _kdc_free_ent(context, server); + return ret; +} + + +static krb5_error_code +check_tgs_flags(krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ_BODY *b, EncTicketPart *tgt, EncTicketPart *et) +{ + KDCOptions f = b->kdc_options; + + if(f.validate){ + if(!tgt->flags.invalid || tgt->starttime == NULL){ + kdc_log(context, config, 0, "Bad request to validate ticket"); + return KRB5KDC_ERR_BADOPTION; + } + if(*tgt->starttime > kdc_time){ + kdc_log(context, config, 0, "Early request to validate ticket"); + return KRB5KRB_AP_ERR_TKT_NYV; + } + /* XXX tkt = tgt */ + et->flags.invalid = 0; + }else if(tgt->flags.invalid){ + kdc_log(context, config, 0, "Ticket-granting ticket has INVALID flag set"); + return KRB5KRB_AP_ERR_TKT_INVALID; + } + + if(f.forwardable){ + if(!tgt->flags.forwardable){ + kdc_log(context, config, 0, "Bad request for forwardable ticket"); + return KRB5KDC_ERR_BADOPTION; + } + et->flags.forwardable = 1; + } + if(f.forwarded){ + if(!tgt->flags.forwardable){ + kdc_log(context, config, 0, "Request to forward non-forwardable ticket"); + return KRB5KDC_ERR_BADOPTION; + } + et->flags.forwarded = 1; + et->caddr = b->addresses; + } + if(tgt->flags.forwarded) + et->flags.forwarded = 1; + + if(f.proxiable){ + if(!tgt->flags.proxiable){ + kdc_log(context, config, 0, + "Bad request for proxiable ticket"); + return KRB5KDC_ERR_BADOPTION; + } + et->flags.proxiable = 1; + } + if(f.proxy){ + if(!tgt->flags.proxiable){ + kdc_log(context, config, 0, + "Request to proxy non-proxiable ticket"); + return KRB5KDC_ERR_BADOPTION; + } + et->flags.proxy = 1; + et->caddr = b->addresses; + } + if(tgt->flags.proxy) + et->flags.proxy = 1; + + if(f.allow_postdate){ + if(!tgt->flags.may_postdate){ + kdc_log(context, config, 0, + "Bad request for post-datable ticket"); + return KRB5KDC_ERR_BADOPTION; + } + et->flags.may_postdate = 1; + } + if(f.postdated){ + if(!tgt->flags.may_postdate){ + kdc_log(context, config, 0, + "Bad request for postdated ticket"); + return KRB5KDC_ERR_BADOPTION; + } + if(b->from) + *et->starttime = *b->from; + et->flags.postdated = 1; + et->flags.invalid = 1; + }else if(b->from && *b->from > kdc_time + context->max_skew){ + kdc_log(context, config, 0, "Ticket cannot be postdated"); + return KRB5KDC_ERR_CANNOT_POSTDATE; + } + + if(f.renewable){ + if(!tgt->flags.renewable){ + kdc_log(context, config, 0, + "Bad request for renewable ticket"); + return KRB5KDC_ERR_BADOPTION; + } + et->flags.renewable = 1; + ALLOC(et->renew_till); + fix_time(&b->rtime); + *et->renew_till = *b->rtime; + } + if(f.renew){ + time_t old_life; + if(!tgt->flags.renewable || tgt->renew_till == NULL){ + kdc_log(context, config, 0, + "Request to renew non-renewable ticket"); + return KRB5KDC_ERR_BADOPTION; + } + old_life = tgt->endtime; + if(tgt->starttime) + old_life -= *tgt->starttime; + else + old_life -= tgt->authtime; + et->endtime = *et->starttime + old_life; + if (et->renew_till != NULL) + et->endtime = min(*et->renew_till, et->endtime); + } + + /* checks for excess flags */ + if(f.request_anonymous && !config->allow_anonymous){ + kdc_log(context, config, 0, + "Request for anonymous ticket"); + return KRB5KDC_ERR_BADOPTION; + } + return 0; +} + +static krb5_error_code +fix_transited_encoding(krb5_context context, + krb5_kdc_configuration *config, + krb5_boolean check_policy, + TransitedEncoding *tr, + EncTicketPart *et, + const char *client_realm, + const char *server_realm, + const char *tgt_realm) +{ + krb5_error_code ret = 0; + char **realms, **tmp; + int num_realms; + int i; + + if(tr->tr_type != DOMAIN_X500_COMPRESS) { + kdc_log(context, config, 0, + "Unknown transited type: %u", tr->tr_type); + return KRB5KDC_ERR_TRTYPE_NOSUPP; + } + + ret = krb5_domain_x500_decode(context, + tr->contents, + &realms, + &num_realms, + client_realm, + server_realm); + if(ret){ + krb5_warn(context, ret, + "Decoding transited encoding"); + return ret; + } + if(strcmp(client_realm, tgt_realm) && strcmp(server_realm, tgt_realm)) { + /* not us, so add the previous realm to transited set */ + if (num_realms < 0 || num_realms + 1 > UINT_MAX/sizeof(*realms)) { + ret = ERANGE; + goto free_realms; + } + tmp = realloc(realms, (num_realms + 1) * sizeof(*realms)); + if(tmp == NULL){ + ret = ENOMEM; + goto free_realms; + } + realms = tmp; + realms[num_realms] = strdup(tgt_realm); + if(realms[num_realms] == NULL){ + ret = ENOMEM; + goto free_realms; + } + num_realms++; + } + if(num_realms == 0) { + if(strcmp(client_realm, server_realm)) + kdc_log(context, config, 0, + "cross-realm %s -> %s", client_realm, server_realm); + } else { + size_t l = 0; + char *rs; + for(i = 0; i < num_realms; i++) + l += strlen(realms[i]) + 2; + rs = malloc(l); + if(rs != NULL) { + *rs = '\0'; + for(i = 0; i < num_realms; i++) { + if(i > 0) + strlcat(rs, ", ", l); + strlcat(rs, realms[i], l); + } + kdc_log(context, config, 0, + "cross-realm %s -> %s via [%s]", + client_realm, server_realm, rs); + free(rs); + } + } + if(check_policy) { + ret = krb5_check_transited(context, client_realm, + server_realm, + realms, num_realms, NULL); + if(ret) { + krb5_warn(context, ret, "cross-realm %s -> %s", + client_realm, server_realm); + goto free_realms; + } + et->flags.transited_policy_checked = 1; + } + et->transited.tr_type = DOMAIN_X500_COMPRESS; + ret = krb5_domain_x500_encode(realms, num_realms, &et->transited.contents); + if(ret) + krb5_warn(context, ret, "Encoding transited encoding"); + free_realms: + for(i = 0; i < num_realms; i++) + free(realms[i]); + free(realms); + return ret; +} + + +static krb5_error_code +tgs_make_reply(krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ_BODY *b, + EncTicketPart *tgt, + EncTicketPart *adtkt, + AuthorizationData *auth_data, + hdb_entry *server, + hdb_entry *client, + krb5_principal client_principal, + hdb_entry *krbtgt, + EncryptionKey *tgtkey, + krb5_enctype cetype, + const char **e_text, + krb5_data *reply) +{ + KDC_REP rep; + EncKDCRepPart ek; + EncTicketPart et; + KDCOptions f = b->kdc_options; + krb5_error_code ret; + krb5_enctype etype; + Key *skey; + EncryptionKey *ekey; + + if(adtkt) { + int i; + krb5_keytype kt; + ekey = &adtkt->key; + for(i = 0; i < b->etype.len; i++){ + ret = krb5_enctype_to_keytype(context, b->etype.val[i], &kt); + if(ret) + continue; + if(adtkt->key.keytype == kt) + break; + } + if(i == b->etype.len) + return KRB5KDC_ERR_ETYPE_NOSUPP; + etype = b->etype.val[i]; + }else{ + ret = find_keys(context, config, + NULL, server, NULL, NULL, &skey, &etype, + b->etype.val, b->etype.len); + if(ret) { + kdc_log(context, config, 0, "Server has no support for etypes"); + return ret; + } + ekey = &skey->key; + } + + memset(&rep, 0, sizeof(rep)); + memset(&et, 0, sizeof(et)); + memset(&ek, 0, sizeof(ek)); + + rep.pvno = 5; + rep.msg_type = krb_tgs_rep; + + et.authtime = tgt->authtime; + fix_time(&b->till); + et.endtime = min(tgt->endtime, *b->till); + ALLOC(et.starttime); + *et.starttime = kdc_time; + + ret = check_tgs_flags(context, config, b, tgt, &et); + if(ret) + goto out; + + /* We should check the transited encoding if: + 1) the request doesn't ask not to be checked + 2) globally enforcing a check + 3) principal requires checking + 4) we allow non-check per-principal, but principal isn't marked as allowing this + 5) we don't globally allow this + */ + +#define GLOBAL_FORCE_TRANSITED_CHECK \ + (config->trpolicy == TRPOLICY_ALWAYS_CHECK) +#define GLOBAL_ALLOW_PER_PRINCIPAL \ + (config->trpolicy == TRPOLICY_ALLOW_PER_PRINCIPAL) +#define GLOBAL_ALLOW_DISABLE_TRANSITED_CHECK \ + (config->trpolicy == TRPOLICY_ALWAYS_HONOUR_REQUEST) + +/* these will consult the database in future release */ +#define PRINCIPAL_FORCE_TRANSITED_CHECK(P) 0 +#define PRINCIPAL_ALLOW_DISABLE_TRANSITED_CHECK(P) 0 + + ret = fix_transited_encoding(context, config, + !f.disable_transited_check || + GLOBAL_FORCE_TRANSITED_CHECK || + PRINCIPAL_FORCE_TRANSITED_CHECK(server) || + !((GLOBAL_ALLOW_PER_PRINCIPAL && + PRINCIPAL_ALLOW_DISABLE_TRANSITED_CHECK(server)) || + GLOBAL_ALLOW_DISABLE_TRANSITED_CHECK), + &tgt->transited, &et, + *krb5_princ_realm(context, client_principal), + *krb5_princ_realm(context, server->principal), + *krb5_princ_realm(context, krbtgt->principal)); + if(ret) + goto out; + + copy_Realm(krb5_princ_realm(context, server->principal), + &rep.ticket.realm); + _krb5_principal2principalname(&rep.ticket.sname, server->principal); + copy_Realm(&tgt->crealm, &rep.crealm); + if (f.request_anonymous) + make_anonymous_principalname (&tgt->cname); + else + copy_PrincipalName(&tgt->cname, &rep.cname); + rep.ticket.tkt_vno = 5; + + ek.caddr = et.caddr; + if(et.caddr == NULL) + et.caddr = tgt->caddr; + + { + time_t life; + life = et.endtime - *et.starttime; + if(client && client->max_life) + life = min(life, *client->max_life); + if(server->max_life) + life = min(life, *server->max_life); + et.endtime = *et.starttime + life; + } + if(f.renewable_ok && tgt->flags.renewable && + et.renew_till == NULL && et.endtime < *b->till){ + et.flags.renewable = 1; + ALLOC(et.renew_till); + *et.renew_till = *b->till; + } + if(et.renew_till){ + time_t renew; + renew = *et.renew_till - et.authtime; + if(client && client->max_renew) + renew = min(renew, *client->max_renew); + if(server->max_renew) + renew = min(renew, *server->max_renew); + *et.renew_till = et.authtime + renew; + } + + if(et.renew_till){ + *et.renew_till = min(*et.renew_till, *tgt->renew_till); + *et.starttime = min(*et.starttime, *et.renew_till); + et.endtime = min(et.endtime, *et.renew_till); + } + + *et.starttime = min(*et.starttime, et.endtime); + + if(*et.starttime == et.endtime){ + ret = KRB5KDC_ERR_NEVER_VALID; + goto out; + } + if(et.renew_till && et.endtime == *et.renew_till){ + free(et.renew_till); + et.renew_till = NULL; + et.flags.renewable = 0; + } + + et.flags.pre_authent = tgt->flags.pre_authent; + et.flags.hw_authent = tgt->flags.hw_authent; + et.flags.anonymous = tgt->flags.anonymous; + et.flags.ok_as_delegate = server->flags.ok_as_delegate; + +#ifdef _SAMBA_BUILD_ + + { + + unsigned char *buf; + size_t buf_size; + size_t len; + + krb5_data pac; + AD_IF_RELEVANT *if_relevant; + ALLOC(if_relevant); + if_relevant->len = 1; + if_relevant->val = malloc(sizeof(*if_relevant->val)); + if_relevant->val[0].ad_type = KRB5_AUTHDATA_WIN2K_PAC; + if_relevant->val[0].ad_data.data = NULL; + if_relevant->val[0].ad_data.length = 0; + + /* Get PAC from Samba */ + ret = samba_get_pac(context, config, + client->principal, + tgtkey, + ekey, + &pac); + if (ret) { + free_AuthorizationData(if_relevant); + goto out; + } + + /* pac.data will be freed with this */ + if_relevant->val[0].ad_data.data = pac.data; + if_relevant->val[0].ad_data.length = pac.length; + + ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, if_relevant, &len, ret); + + auth_data = NULL; + ALLOC(auth_data); + auth_data->len = 1; + auth_data->val = malloc(sizeof(*auth_data->val)); + auth_data->val[0].ad_type = KRB5_AUTHDATA_IF_RELEVANT; + auth_data->val[0].ad_data.length = len; + auth_data->val[0].ad_data.data = buf; + if (ret) { + goto out; + } + } + +#endif + /* XXX Check enc-authorization-data */ + et.authorization_data = auth_data; + + krb5_generate_random_keyblock(context, etype, &et.key); + et.crealm = tgt->crealm; + et.cname = tgt->cname; + + ek.key = et.key; + /* MIT must have at least one last_req */ + ek.last_req.len = 1; + ek.last_req.val = calloc(1, sizeof(*ek.last_req.val)); + ek.nonce = b->nonce; + ek.flags = et.flags; + ek.authtime = et.authtime; + ek.starttime = et.starttime; + ek.endtime = et.endtime; + ek.renew_till = et.renew_till; + ek.srealm = rep.ticket.realm; + ek.sname = rep.ticket.sname; + + log_timestamp(context, config, "TGS-REQ", et.authtime, et.starttime, + et.endtime, et.renew_till); + + /* It is somewhat unclear where the etype in the following + encryption should come from. What we have is a session + key in the passed tgt, and a list of preferred etypes + *for the new ticket*. Should we pick the best possible + etype, given the keytype in the tgt, or should we look + at the etype list here as well? What if the tgt + session key is DES3 and we want a ticket with a (say) + CAST session key. Should the DES3 etype be added to the + etype list, even if we don't want a session key with + DES3? */ + ret = encode_reply(context, config, + &rep, &et, &ek, etype, adtkt ? 0 : server->kvno, ekey, + 0, &tgt->key, e_text, reply); + out: + free_TGS_REP(&rep); + free_TransitedEncoding(&et.transited); + if(et.starttime) + free(et.starttime); + if(et.renew_till) + free(et.renew_till); + free_LastReq(&ek.last_req); + memset(et.key.keyvalue.data, 0, et.key.keyvalue.length); + free_EncryptionKey(&et.key); + return ret; +} + +static krb5_error_code +tgs_check_authenticator(krb5_context context, + krb5_kdc_configuration *config, + krb5_auth_context ac, + KDC_REQ_BODY *b, + const char **e_text, + krb5_keyblock *key) +{ + krb5_authenticator auth; + size_t len; + unsigned char *buf; + size_t buf_size; + krb5_error_code ret; + krb5_crypto crypto; + + krb5_auth_con_getauthenticator(context, ac, &auth); + if(auth->cksum == NULL){ + kdc_log(context, config, 0, "No authenticator in request"); + ret = KRB5KRB_AP_ERR_INAPP_CKSUM; + goto out; + } + /* + * according to RFC1510 it doesn't need to be keyed, + * but according to the latest draft it needs to. + */ + if ( +#if 0 +!krb5_checksum_is_keyed(context, auth->cksum->cksumtype) + || +#endif + !krb5_checksum_is_collision_proof(context, auth->cksum->cksumtype)) { + kdc_log(context, config, 0, "Bad checksum type in authenticator: %d", + auth->cksum->cksumtype); + ret = KRB5KRB_AP_ERR_INAPP_CKSUM; + goto out; + } + + /* XXX should not re-encode this */ + ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, b, &len, ret); + if(ret){ + kdc_log(context, config, 0, "Failed to encode KDC-REQ-BODY: %s", + krb5_get_err_text(context, ret)); + goto out; + } + if(buf_size != len) { + free(buf); + kdc_log(context, config, 0, "Internal error in ASN.1 encoder"); + *e_text = "KDC internal error"; + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + ret = krb5_crypto_init(context, key, 0, &crypto); + if (ret) { + free(buf); + kdc_log(context, config, 0, "krb5_crypto_init failed: %s", + krb5_get_err_text(context, ret)); + goto out; + } + ret = krb5_verify_checksum(context, + crypto, + KRB5_KU_TGS_REQ_AUTH_CKSUM, + buf, + len, + auth->cksum); + free(buf); + krb5_crypto_destroy(context, crypto); + if(ret){ + kdc_log(context, config, 0, "Failed to verify checksum: %s", + krb5_get_err_text(context, ret)); + } +out: + free_Authenticator(auth); + free(auth); + return ret; +} + +/* + * return the realm of a krbtgt-ticket or NULL + */ + +static Realm +get_krbtgt_realm(const PrincipalName *p) +{ + if(p->name_string.len == 2 + && strcmp(p->name_string.val[0], KRB5_TGS_NAME) == 0) + return p->name_string.val[1]; + else + return NULL; +} + +static const char * +find_rpath(krb5_context context, Realm crealm, Realm srealm) +{ + const char *new_realm = krb5_config_get_string(context, + NULL, + "capaths", + crealm, + srealm, + NULL); + return new_realm; +} + + +static krb5_boolean +need_referral(krb5_context context, krb5_principal server, krb5_realm **realms) +{ + if(server->name.name_type != KRB5_NT_SRV_INST || + server->name.name_string.len != 2) + return FALSE; + + return _krb5_get_host_realm_int(context, server->name.name_string.val[1], + FALSE, realms) == 0; +} + +static krb5_error_code +tgs_rep2(krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ_BODY *b, + PA_DATA *tgs_req, + krb5_data *reply, + const char *from, + const struct sockaddr *from_addr, + time_t **csec, + int **cusec) +{ + krb5_ap_req ap_req; + krb5_error_code ret; + krb5_principal princ; + krb5_auth_context ac = NULL; + krb5_ticket *ticket = NULL; + krb5_flags ap_req_options; + krb5_flags verify_ap_req_flags; + const char *e_text = NULL; + krb5_crypto crypto; + + hdb_entry *krbtgt = NULL; + EncTicketPart *tgt; + Key *tkey; + krb5_enctype cetype; + krb5_principal cp = NULL; + krb5_principal sp = NULL; + AuthorizationData *auth_data = NULL; + + *csec = NULL; + *cusec = NULL; + + memset(&ap_req, 0, sizeof(ap_req)); + ret = krb5_decode_ap_req(context, &tgs_req->padata_value, &ap_req); + if(ret){ + kdc_log(context, config, 0, "Failed to decode AP-REQ: %s", + krb5_get_err_text(context, ret)); + goto out2; + } + + if(!get_krbtgt_realm(&ap_req.ticket.sname)){ + /* XXX check for ticket.sname == req.sname */ + kdc_log(context, config, 0, "PA-DATA is not a ticket-granting ticket"); + ret = KRB5KDC_ERR_POLICY; /* ? */ + goto out2; + } + + _krb5_principalname2krb5_principal(&princ, + ap_req.ticket.sname, + ap_req.ticket.realm); + + ret = _kdc_db_fetch(context, config, princ, HDB_ENT_TYPE_SERVER, &krbtgt); + + if(ret) { + char *p; + ret = krb5_unparse_name(context, princ, &p); + if (ret != 0) + p = "<unparse_name failed>"; + krb5_free_principal(context, princ); + kdc_log(context, config, 0, + "Ticket-granting ticket not found in database: %s: %s", + p, krb5_get_err_text(context, ret)); + if (ret == 0) + free(p); + ret = KRB5KRB_AP_ERR_NOT_US; + goto out2; + } + + if(ap_req.ticket.enc_part.kvno && + *ap_req.ticket.enc_part.kvno != krbtgt->kvno){ + char *p; + + ret = krb5_unparse_name (context, princ, &p); + krb5_free_principal(context, princ); + if (ret != 0) + p = "<unparse_name failed>"; + kdc_log(context, config, 0, + "Ticket kvno = %d, DB kvno = %d (%s)", + *ap_req.ticket.enc_part.kvno, + krbtgt->kvno, + p); + if (ret == 0) + free (p); + ret = KRB5KRB_AP_ERR_BADKEYVER; + goto out2; + } + + ret = hdb_enctype2key(context, krbtgt, ap_req.ticket.enc_part.etype, &tkey); + if(ret){ + char *str; + krb5_enctype_to_string(context, ap_req.ticket.enc_part.etype, &str); + kdc_log(context, config, 0, + "No server key found for %s", str); + free(str); + ret = KRB5KRB_AP_ERR_BADKEYVER; + goto out2; + } + + if (b->kdc_options.validate) + verify_ap_req_flags = KRB5_VERIFY_AP_REQ_IGNORE_INVALID; + else + verify_ap_req_flags = 0; + + ret = krb5_verify_ap_req2(context, + &ac, + &ap_req, + princ, + &tkey->key, + verify_ap_req_flags, + &ap_req_options, + &ticket, + KRB5_KU_TGS_REQ_AUTH); + + krb5_free_principal(context, princ); + if(ret) { + kdc_log(context, config, 0, "Failed to verify AP-REQ: %s", + krb5_get_err_text(context, ret)); + goto out2; + } + + { + krb5_authenticator auth; + + ret = krb5_auth_con_getauthenticator(context, ac, &auth); + if (ret == 0) { + *csec = malloc(sizeof(**csec)); + if (*csec == NULL) { + krb5_free_authenticator(context, &auth); + kdc_log(context, config, 0, "malloc failed"); + goto out2; + } + **csec = auth->ctime; + *cusec = malloc(sizeof(**cusec)); + if (*cusec == NULL) { + krb5_free_authenticator(context, &auth); + kdc_log(context, config, 0, "malloc failed"); + goto out2; + } + **csec = auth->cusec; + krb5_free_authenticator(context, &auth); + } + } + + cetype = ap_req.authenticator.etype; + + tgt = &ticket->ticket; + + ret = tgs_check_authenticator(context, config, + ac, b, &e_text, &tgt->key); + + if (b->enc_authorization_data) { + krb5_keyblock *subkey; + krb5_data ad; + ret = krb5_auth_con_getremotesubkey(context, + ac, + &subkey); + if(ret){ + krb5_auth_con_free(context, ac); + kdc_log(context, config, 0, "Failed to get remote subkey: %s", + krb5_get_err_text(context, ret)); + goto out2; + } + if(subkey == NULL){ + ret = krb5_auth_con_getkey(context, ac, &subkey); + if(ret) { + krb5_auth_con_free(context, ac); + kdc_log(context, config, 0, "Failed to get session key: %s", + krb5_get_err_text(context, ret)); + goto out2; + } + } + if(subkey == NULL){ + krb5_auth_con_free(context, ac); + kdc_log(context, config, 0, + "Failed to get key for enc-authorization-data"); + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */ + goto out2; + } + ret = krb5_crypto_init(context, subkey, 0, &crypto); + if (ret) { + krb5_auth_con_free(context, ac); + kdc_log(context, config, 0, "krb5_crypto_init failed: %s", + krb5_get_err_text(context, ret)); + goto out2; + } + ret = krb5_decrypt_EncryptedData (context, + crypto, + KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY, + b->enc_authorization_data, + &ad); + krb5_crypto_destroy(context, crypto); + if(ret){ + krb5_auth_con_free(context, ac); + kdc_log(context, config, 0, "Failed to decrypt enc-authorization-data"); + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */ + goto out2; + } + krb5_free_keyblock(context, subkey); + ALLOC(auth_data); + ret = decode_AuthorizationData(ad.data, ad.length, auth_data, NULL); + if(ret){ + krb5_auth_con_free(context, ac); + free(auth_data); + auth_data = NULL; + kdc_log(context, config, 0, "Failed to decode authorization data"); + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */ + goto out2; + } + } + + krb5_auth_con_free(context, ac); + + if(ret){ + kdc_log(context, config, 0, "Failed to verify authenticator: %s", + krb5_get_err_text(context, ret)); + goto out2; + } + + { + PrincipalName *s; + Realm r; + char *spn = NULL, *cpn = NULL; + hdb_entry *server = NULL, *client = NULL; + int nloop = 0; + EncTicketPart adtkt; + char opt_str[128]; + + s = b->sname; + r = b->realm; + if(b->kdc_options.enc_tkt_in_skey){ + Ticket *t; + hdb_entry *uu; + krb5_principal p; + Key *uukey; + + if(b->additional_tickets == NULL || + b->additional_tickets->len == 0){ + ret = KRB5KDC_ERR_BADOPTION; /* ? */ + kdc_log(context, config, 0, + "No second ticket present in request"); + goto out; + } + t = &b->additional_tickets->val[0]; + if(!get_krbtgt_realm(&t->sname)){ + kdc_log(context, config, 0, + "Additional ticket is not a ticket-granting ticket"); + ret = KRB5KDC_ERR_POLICY; + goto out2; + } + _krb5_principalname2krb5_principal(&p, t->sname, t->realm); + ret = _kdc_db_fetch(context, config, p, HDB_ENT_TYPE_SERVER, &uu); + krb5_free_principal(context, p); + if(ret){ + if (ret == HDB_ERR_NOENTRY) + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + goto out; + } + ret = hdb_enctype2key(context, uu, t->enc_part.etype, &uukey); + if(ret){ + ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ + goto out; + } + ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0); + + if(ret) + goto out; + s = &adtkt.cname; + r = adtkt.crealm; + } + + _krb5_principalname2krb5_principal(&sp, *s, r); + ret = krb5_unparse_name(context, sp, &spn); + if (ret) + goto out; + _krb5_principalname2krb5_principal(&cp, tgt->cname, tgt->crealm); + ret = krb5_unparse_name(context, cp, &cpn); + if (ret) + goto out; + unparse_flags (KDCOptions2int(b->kdc_options), + asn1_KDCOptions_units(), + opt_str, sizeof(opt_str)); + if(*opt_str) + kdc_log(context, config, 0, + "TGS-REQ %s from %s for %s [%s]", + cpn, from, spn, opt_str); + else + kdc_log(context, config, 0, + "TGS-REQ %s from %s for %s", cpn, from, spn); + server_lookup: + ret = _kdc_db_fetch(context, config, sp, HDB_ENT_TYPE_SERVER, &server); + + if(ret){ + const char *new_rlm; + Realm req_rlm; + krb5_realm *realms; + + if ((req_rlm = get_krbtgt_realm(&sp->name)) != NULL) { + if(nloop++ < 2) { + new_rlm = find_rpath(context, tgt->crealm, req_rlm); + if(new_rlm) { + kdc_log(context, config, 5, "krbtgt for realm %s not found, trying %s", + req_rlm, new_rlm); + krb5_free_principal(context, sp); + free(spn); + krb5_make_principal(context, &sp, r, + KRB5_TGS_NAME, new_rlm, NULL); + ret = krb5_unparse_name(context, sp, &spn); + if (ret) + goto out; + goto server_lookup; + } + } + } else if(need_referral(context, sp, &realms)) { + if (strcmp(realms[0], sp->realm) != 0) { + kdc_log(context, config, 5, + "Returning a referral to realm %s for " + "server %s that was not found", + realms[0], spn); + krb5_free_principal(context, sp); + free(spn); + krb5_make_principal(context, &sp, r, KRB5_TGS_NAME, + realms[0], NULL); + ret = krb5_unparse_name(context, sp, &spn); + if (ret) + goto out; + krb5_free_host_realm(context, realms); + goto server_lookup; + } + krb5_free_host_realm(context, realms); + } + kdc_log(context, config, 0, + "Server not found in database: %s: %s", spn, + krb5_get_err_text(context, ret)); + if (ret == HDB_ERR_NOENTRY) + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + goto out; + } + + ret = _kdc_db_fetch(context, config, cp, HDB_ENT_TYPE_CLIENT, &client); + if(ret) + kdc_log(context, config, 1, "Client not found in database: %s: %s", + cpn, krb5_get_err_text(context, ret)); +#if 0 + /* XXX check client only if same realm as krbtgt-instance */ + if(ret){ + kdc_log(context, config, 0, + "Client not found in database: %s: %s", + cpn, krb5_get_err_text(context, ret)); + if (ret == HDB_ERR_NOENTRY) + ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + goto out; + } +#endif + + if(strcmp(krb5_principal_get_realm(context, sp), + krb5_principal_get_comp_string(context, krbtgt->principal, 1)) != 0) { + char *tpn; + ret = krb5_unparse_name(context, krbtgt->principal, &tpn); + kdc_log(context, config, 0, + "Request with wrong krbtgt: %s", + (ret == 0) ? tpn : "<unknown>"); + if(ret == 0) + free(tpn); + ret = KRB5KRB_AP_ERR_NOT_US; + goto out; + + } + + ret = _kdc_check_flags(context, config, + client, cpn, + server, spn, + FALSE); + if(ret) + goto out; + + if((b->kdc_options.validate || b->kdc_options.renew) && + !krb5_principal_compare(context, + krbtgt->principal, + server->principal)){ + kdc_log(context, config, 0, "Inconsistent request."); + ret = KRB5KDC_ERR_SERVER_NOMATCH; + goto out; + } + + /* check for valid set of addresses */ + if(!check_addresses(context, config, tgt->caddr, from_addr)) { + ret = KRB5KRB_AP_ERR_BADADDR; + kdc_log(context, config, 0, "Request from wrong address"); + goto out; + } + + ret = tgs_make_reply(context, config, + b, + tgt, + b->kdc_options.enc_tkt_in_skey ? &adtkt : NULL, + auth_data, + server, + client, + cp, + krbtgt, + &tkey->key, + cetype, + &e_text, + reply); + + out: + free(spn); + free(cpn); + + if(server) + _kdc_free_ent(context, server); + if(client) + _kdc_free_ent(context, client); + } + out2: + if(ret) { + krb5_mk_error(context, + ret, + e_text, + NULL, + cp, + sp, + NULL, + NULL, + reply); + free(*csec); + free(*cusec); + *csec = NULL; + *cusec = NULL; + } + krb5_free_principal(context, cp); + krb5_free_principal(context, sp); + if (ticket) + krb5_free_ticket(context, ticket); + free_AP_REQ(&ap_req); + if(auth_data){ + free_AuthorizationData(auth_data); + free(auth_data); + } + + if(krbtgt) + _kdc_free_ent(context, krbtgt); + + return ret; +} + + +krb5_error_code +_kdc_tgs_rep(krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ *req, + krb5_data *data, + const char *from, + struct sockaddr *from_addr) +{ + krb5_error_code ret; + int i = 0; + PA_DATA *tgs_req = NULL; + time_t *csec = NULL; + int *cusec = NULL; + + if(req->padata == NULL){ + ret = KRB5KDC_ERR_PREAUTH_REQUIRED; /* XXX ??? */ + kdc_log(context, config, 0, + "TGS-REQ from %s without PA-DATA", from); + goto out; + } + + tgs_req = find_padata(req, &i, KRB5_PADATA_TGS_REQ); + + if(tgs_req == NULL){ + ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP; + + kdc_log(context, config, 0, + "TGS-REQ from %s without PA-TGS-REQ", from); + goto out; + } + ret = tgs_rep2(context, config, + &req->req_body, tgs_req, data, from, from_addr, + &csec, &cusec); +out: + if(ret && data->data == NULL){ + krb5_mk_error(context, + ret, + NULL, + NULL, + NULL, + NULL, + csec, + cusec, + data); + } + free(csec); + free(cusec); + return 0; +} diff --git a/source4/heimdal/kdc/log.c b/source4/heimdal/kdc/log.c new file mode 100644 index 0000000000..c316b0c5f8 --- /dev/null +++ b/source4/heimdal/kdc/log.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 1997, 1998, 2002 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" +RCSID("$Id: log.c,v 1.16 2005/06/30 01:52:48 lha Exp $"); + +void +kdc_openlog(krb5_context context, + krb5_kdc_configuration *config) +{ + char **s = NULL, **p; + krb5_initlog(context, "kdc", &config->logf); + s = krb5_config_get_strings(context, NULL, "kdc", "logging", NULL); + if(s == NULL) + s = krb5_config_get_strings(context, NULL, "logging", "kdc", NULL); + if(s){ + for(p = s; *p; p++) + krb5_addlog_dest(context, config->logf, *p); + krb5_config_free_strings(s); + }else + krb5_addlog_dest(context, config->logf, DEFAULT_LOG_DEST); + krb5_set_warn_dest(context, config->logf); +} + +char* +kdc_log_msg_va(krb5_context context, + krb5_kdc_configuration *config, + int level, const char *fmt, va_list ap) +{ + char *msg; + krb5_vlog_msg(context, config->logf, &msg, level, fmt, ap); + return msg; +} + +char* +kdc_log_msg(krb5_context context, + krb5_kdc_configuration *config, + int level, const char *fmt, ...) +{ + va_list ap; + char *s; + va_start(ap, fmt); + s = kdc_log_msg_va(context, config, level, fmt, ap); + va_end(ap); + return s; +} + +void +kdc_log(krb5_context context, + krb5_kdc_configuration *config, + int level, const char *fmt, ...) +{ + va_list ap; + char *s; + va_start(ap, fmt); + s = kdc_log_msg_va(context, config, level, fmt, ap); + if(s) free(s); + va_end(ap); +} diff --git a/source4/heimdal/kdc/misc.c b/source4/heimdal/kdc/misc.c new file mode 100644 index 0000000000..5a251607b6 --- /dev/null +++ b/source4/heimdal/kdc/misc.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" + +RCSID("$Id: misc.c,v 1.25 2005/06/30 01:53:48 lha Exp $"); + +struct timeval _kdc_now; + +krb5_error_code +_kdc_db_fetch(krb5_context context, + krb5_kdc_configuration *config, + krb5_principal principal, enum hdb_ent_type ent_type, + hdb_entry **h) +{ + hdb_entry *ent; + krb5_error_code ret = HDB_ERR_NOENTRY; + int i; + + ent = malloc (sizeof (*ent)); + if (ent == NULL) + return ENOMEM; + ent->principal = principal; + + for(i = 0; i < config->num_db; i++) { + ret = config->db[i]->hdb_open(context, config->db[i], O_RDONLY, 0); + if (ret) { + kdc_log(context, config, 0, "Failed to open database: %s", + krb5_get_err_text(context, ret)); + continue; + } + ret = config->db[i]->hdb_fetch(context, + config->db[i], + HDB_F_DECRYPT, + principal, + ent_type, + ent); + config->db[i]->hdb_close(context, config->db[i]); + if(ret == 0) { + *h = ent; + return 0; + } + } + free(ent); + return ret; +} + +void +_kdc_free_ent(krb5_context context, hdb_entry *ent) +{ + hdb_free_entry (context, ent); + free (ent); +} + diff --git a/source4/heimdal/kdc/pkinit.c b/source4/heimdal/kdc/pkinit.c new file mode 100755 index 0000000000..d83e1d3b2e --- /dev/null +++ b/source4/heimdal/kdc/pkinit.c @@ -0,0 +1,1607 @@ +/* + * Copyright (c) 2003 - 2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" + +RCSID("$Id: pkinit.c,v 1.36 2005/07/01 15:37:24 lha Exp $"); + +#ifdef PKINIT + +#include <heim_asn1.h> +#include <rfc2459_asn1.h> +#include <cms_asn1.h> +#include <pkinit_asn1.h> + +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/bn.h> +#include <openssl/asn1.h> +#include <openssl/err.h> + +/* XXX copied from lib/krb5/pkinit.c */ +struct krb5_pk_identity { + EVP_PKEY *private_key; + STACK_OF(X509) *cert; + STACK_OF(X509) *trusted_certs; + STACK_OF(X509_CRL) *crls; + ENGINE *engine; +}; + +/* XXX copied from lib/krb5/pkinit.c */ +struct krb5_pk_cert { + X509 *cert; +}; + +enum pkinit_type { + PKINIT_COMPAT_WIN2K = 1, + PKINIT_COMPAT_19 = 2, + PKINIT_COMPAT_25 = 3 +}; + +struct pk_client_params { + enum pkinit_type type; + BIGNUM *dh_public_key; + struct krb5_pk_cert *certificate; + unsigned nonce; + DH *dh; + EncryptionKey reply_key; +}; + +struct pk_principal_mapping { + unsigned int len; + struct pk_allowed_princ { + krb5_principal principal; + char *subject; + } *val; +}; + +/* XXX copied from lib/krb5/pkinit.c */ +#define OPENSSL_ASN1_MALLOC_ENCODE(T, B, BL, S, R) \ +{ \ + unsigned char *p; \ + (BL) = i2d_##T((S), NULL); \ + if ((BL) <= 0) { \ + (R) = EINVAL; \ + } else { \ + (B) = malloc((BL)); \ + if ((B) == NULL) { \ + (R) = ENOMEM; \ + } else { \ + p = (B); \ + (R) = 0; \ + (BL) = i2d_##T((S), &p); \ + if ((BL) <= 0) { \ + free((B)); \ + (R) = ASN1_OVERRUN; \ + } \ + } \ + } \ +} + +static struct krb5_pk_identity *kdc_identity; +static struct pk_principal_mapping principal_mappings; + +/* + * + */ + +static krb5_error_code +pk_check_pkauthenticator_win2k(krb5_context context, + PKAuthenticator_Win2k *a, + KDC_REQ *req) +{ + krb5_timestamp now; + + krb5_timeofday (context, &now); + + /* XXX cusec */ + if (a->ctime == 0 || abs(a->ctime - now) > context->max_skew) { + krb5_clear_error_string(context); + return KRB5KRB_AP_ERR_SKEW; + } + return 0; +} + +static krb5_error_code +pk_check_pkauthenticator_19(krb5_context context, + PKAuthenticator_19 *a, + KDC_REQ *req) +{ + u_char *buf = NULL; + size_t buf_size; + krb5_error_code ret; + size_t len; + krb5_timestamp now; + + krb5_timeofday (context, &now); + + /* XXX cusec */ + if (a->ctime == 0 || abs(a->ctime - now) > context->max_skew) { + krb5_clear_error_string(context); + return KRB5KRB_AP_ERR_SKEW; + } + + if (a->paChecksum.cksumtype != CKSUMTYPE_RSA_MD5 && + a->paChecksum.cksumtype != CKSUMTYPE_SHA1) + { + krb5_clear_error_string(context); + ret = KRB5KRB_ERR_GENERIC; + } + + ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, &req->req_body, &len, ret); + if (ret) { + krb5_clear_error_string(context); + return ret; + } + if (buf_size != len) + krb5_abortx(context, "Internal error in ASN.1 encoder"); + + ret = krb5_verify_checksum(context, NULL, 0, buf, len, + &a->paChecksum); + if (ret) + krb5_clear_error_string(context); + + free(buf); + return ret; +} + +static krb5_error_code +pk_check_pkauthenticator(krb5_context context, + PKAuthenticator *a, + KDC_REQ *req) +{ + u_char *buf = NULL; + size_t buf_size; + krb5_error_code ret; + size_t len; + krb5_timestamp now; + Checksum checksum; + + krb5_timeofday (context, &now); + + /* XXX cusec */ + if (a->ctime == 0 || abs(a->ctime - now) > context->max_skew) { + krb5_clear_error_string(context); + return KRB5KRB_AP_ERR_SKEW; + } + + ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, &req->req_body, &len, ret); + if (ret) { + krb5_clear_error_string(context); + return ret; + } + if (buf_size != len) + krb5_abortx(context, "Internal error in ASN.1 encoder"); + + ret = krb5_create_checksum(context, + NULL, + 0, + CKSUMTYPE_SHA1, + buf, + len, + &checksum); + free(buf); + if (ret) { + krb5_clear_error_string(context); + return ret; + } + + if (a->paChecksum.length != checksum.checksum.length || + memcmp(a->paChecksum.data, checksum.checksum.data, + checksum.checksum.length) != 0) + { + krb5_clear_error_string(context); + ret = KRB5KRB_ERR_GENERIC; + } + free_Checksum(&checksum); + + return ret; +} + +static krb5_error_code +pk_encrypt_key(krb5_context context, + krb5_keyblock *key, + EVP_PKEY *public_key, + krb5_data *encrypted_key, + const heim_oid **oid) +{ + krb5_error_code ret; + + encrypted_key->length = EVP_PKEY_size(public_key); + + if (encrypted_key->length < key->keyvalue.length + 11) { /* XXX */ + krb5_set_error_string(context, "pkinit: encrypted key too long"); + return KRB5KRB_ERR_GENERIC; + } + + encrypted_key->data = malloc(encrypted_key->length); + if (encrypted_key->data == NULL) { + krb5_set_error_string(context, "malloc: out of memory"); + return ENOMEM; + } + + ret = EVP_PKEY_encrypt(encrypted_key->data, + key->keyvalue.data, + key->keyvalue.length, + public_key); + if (ret < 0) { + free(encrypted_key->data); + krb5_set_error_string(context, "Can't encrypt key: %s", + ERR_error_string(ERR_get_error(), NULL)); + return KRB5KRB_ERR_GENERIC; + } + if (encrypted_key->length != ret) + krb5_abortx(context, "size of EVP_PKEY_size is not the " + "size of the output"); + + *oid = oid_id_pkcs1_rsaEncryption(); + + return 0; +} + +void +_kdc_pk_free_client_param(krb5_context context, + pk_client_params *client_params) +{ + if (client_params->certificate) + _krb5_pk_cert_free(client_params->certificate); + if (client_params->dh) + DH_free(client_params->dh); + if (client_params->dh_public_key) + BN_free(client_params->dh_public_key); + krb5_free_keyblock_contents(context, &client_params->reply_key); + memset(client_params, 0, sizeof(*client_params)); + free(client_params); +} + +static krb5_error_code +check_dh_params(DH *dh) +{ + /* XXX check the DH parameters come from 1st or 2nd Oeakley Group */ + return 0; +} + +static krb5_error_code +generate_dh_keyblock(krb5_context context, pk_client_params *client_params, + krb5_enctype enctype, krb5_keyblock *reply_key) +{ + unsigned char *dh_gen_key = NULL; + krb5_keyblock key; + int dh_gen_keylen; + krb5_error_code ret; + + memset(&key, 0, sizeof(key)); + + dh_gen_key = malloc(DH_size(client_params->dh)); + if (dh_gen_key == NULL) { + krb5_set_error_string(context, "malloc: out of memory"); + ret = ENOMEM; + goto out; + } + + if (!DH_generate_key(client_params->dh)) { + krb5_set_error_string(context, "Can't generate Diffie-Hellman " + "keys (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + if (client_params->dh_public_key == NULL) { + krb5_set_error_string(context, "dh_public_key"); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + dh_gen_keylen = DH_compute_key(dh_gen_key, + client_params->dh_public_key, + client_params->dh); + if (dh_gen_keylen == -1) { + krb5_set_error_string(context, "Can't compute Diffie-Hellman key (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + ret = krb5_random_to_key(context, enctype, + dh_gen_key, dh_gen_keylen, &key); + + if (ret) { + krb5_set_error_string(context, + "pkinit - can't create key from DH key"); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + ret = krb5_copy_keyblock_contents(context, &key, reply_key); + + out: + if (dh_gen_key) + free(dh_gen_key); + if (key.keyvalue.data) + krb5_free_keyblock_contents(context, &key); + + return ret; +} + +static BIGNUM * +integer_to_BN(krb5_context context, const char *field, heim_integer *f) +{ + BIGNUM *bn; + + bn = BN_bin2bn((const unsigned char *)f->data, f->length, NULL); + if (bn == NULL) { + krb5_set_error_string(context, "PKINIT: parsing BN failed %s", field); + return NULL; + } + bn->neg = f->negative; + return bn; +} + +static krb5_error_code +get_dh_param(krb5_context context, SubjectPublicKeyInfo *dh_key_info, + pk_client_params *client_params) +{ + DomainParameters dhparam; + DH *dh = NULL; + krb5_error_code ret; + int dhret; + + memset(&dhparam, 0, sizeof(dhparam)); + + if (heim_oid_cmp(&dh_key_info->algorithm.algorithm, oid_id_dhpublicnumber())) { + krb5_set_error_string(context, + "PKINIT invalid oid in clientPublicValue"); + return KRB5_BADMSGTYPE; + } + + if (dh_key_info->algorithm.parameters == NULL) { + krb5_set_error_string(context, "PKINIT missing algorithm parameter " + "in clientPublicValue"); + return KRB5_BADMSGTYPE; + } + + ret = decode_DomainParameters(dh_key_info->algorithm.parameters->data, + dh_key_info->algorithm.parameters->length, + &dhparam, + NULL); + if (ret) { + krb5_set_error_string(context, "Can't decode algorithm " + "parameters in clientPublicValue"); + goto out; + } + + dh = DH_new(); + if (dh == NULL) { + krb5_set_error_string(context, "Cannot create DH structure (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = ENOMEM; + goto out; + } + ret = KRB5_BADMSGTYPE; + dh->p = integer_to_BN(context, "DH prime", &dhparam.p); + if (dh->p == NULL) + goto out; + dh->g = integer_to_BN(context, "DH base", &dhparam.g); + if (dh->g == NULL) + goto out; + dh->q = integer_to_BN(context, "DH p-1 factor", &dhparam.q); + if (dh->g == NULL) + goto out; + + { + heim_integer glue; + glue.data = dh_key_info->subjectPublicKey.data; + glue.length = dh_key_info->subjectPublicKey.length; + + client_params->dh_public_key = integer_to_BN(context, + "subjectPublicKey", + &glue); + if (client_params->dh_public_key == NULL) { + krb5_clear_error_string(context); + goto out; + } + } + + if (DH_check(dh, &dhret) != 1) { + krb5_set_error_string(context, "PKINIT DH data not ok: %s", + ERR_error_string(ERR_get_error(), NULL)); + ret = KRB5_KDC_ERR_KEY_SIZE; + goto out; + } + + client_params->dh = dh; + dh = NULL; + ret = 0; + + out: + if (dh) + DH_free(dh); + free_DomainParameters(&dhparam); + return ret; +} + +#if 0 +/* + * XXX We only need this function if there are several certs for the + * KDC to choose from, and right now, we can't handle that so punt for + * now. + * + * If client has sent a list of CA's trusted by him, make sure our + * CA is in the list. + * + */ + +static void +verify_trusted_ca(PA_PK_AS_REQ_19 *r) +{ + + if (r.trustedCertifiers != NULL) { + X509_NAME *kdc_issuer; + X509 *kdc_cert; + + kdc_cert = sk_X509_value(kdc_identity->cert, 0); + kdc_issuer = X509_get_issuer_name(kdc_cert); + + /* XXX will work for heirarchical CA's ? */ + /* XXX also serial_number should be compared */ + + ret = KRB5_KDC_ERR_KDC_NOT_TRUSTED; + for (i = 0; i < r.trustedCertifiers->len; i++) { + TrustedCA_19 *ca = &r.trustedCertifiers->val[i]; + + switch (ca->element) { + case choice_TrustedCA_19_caName: { + X509_NAME *name; + unsigned char *p; + + p = ca->u.caName.data; + name = d2i_X509_NAME(NULL, &p, ca->u.caName.length); + if (name == NULL) /* XXX should this be a failure instead ? */ + break; + if (X509_NAME_cmp(name, kdc_issuer) == 0) + ret = 0; + X509_NAME_free(name); + break; + } + case choice_TrustedCA_19_issuerAndSerial: + /* IssuerAndSerialNumber issuerAndSerial */ + break; + default: + break; + } + if (ret == 0) + break; + } + if (ret) + goto out; + } +} +#endif /* 0 */ + +krb5_error_code +_kdc_pk_rd_padata(krb5_context context, + krb5_kdc_configuration *config, + KDC_REQ *req, + PA_DATA *pa, + pk_client_params **ret_params) +{ + pk_client_params *client_params; + krb5_error_code ret; + heim_oid eContentType = { 0, NULL }; + krb5_data eContent = { 0, NULL }; + krb5_data signed_content = { 0, NULL }; + const char *type = "unknown type"; + const heim_oid *pa_contentType; + + *ret_params = NULL; + + if (!config->enable_pkinit) { + krb5_clear_error_string(context); + return 0; + } + + client_params = malloc(sizeof(*client_params)); + if (client_params == NULL) { + krb5_clear_error_string(context); + ret = ENOMEM; + goto out; + } + memset(client_params, 0, sizeof(*client_params)); + + if (pa->padata_type == KRB5_PADATA_PK_AS_REQ_WIN) { + PA_PK_AS_REQ_Win2k r; + ContentInfo info; + + type = "PK-INIT-Win2k"; + pa_contentType = oid_id_pkcs7_data(); + + ret = decode_PA_PK_AS_REQ_Win2k(pa->padata_value.data, + pa->padata_value.length, + &r, + NULL); + if (ret) { + krb5_set_error_string(context, "Can't decode " + "PK-AS-REQ-Win2k: %d", ret); + goto out; + } + + ret = decode_ContentInfo(r.signed_auth_pack.data, + r.signed_auth_pack.length, &info, NULL); + free_PA_PK_AS_REQ_Win2k(&r); + if (ret) { + krb5_set_error_string(context, "Can't decode PK-AS-REQ: %d", ret); + goto out; + } + + if (heim_oid_cmp(&info.contentType, oid_id_pkcs7_signedData())) { + krb5_set_error_string(context, "PK-AS-REQ-Win2k invalid content " + "type oid"); + free_ContentInfo(&info); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + if (info.content == NULL) { + krb5_set_error_string(context, + "PK-AS-REQ-Win2k no signed auth pack"); + free_ContentInfo(&info); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + signed_content.data = malloc(info.content->length); + if (signed_content.data == NULL) { + ret = ENOMEM; + free_ContentInfo(&info); + krb5_set_error_string(context, "PK-AS-REQ-Win2k out of memory"); + goto out; + } + signed_content.length = info.content->length; + memcpy(signed_content.data, info.content->data, signed_content.length); + + free_ContentInfo(&info); + + } else if (pa->padata_type == KRB5_PADATA_PK_AS_REQ_19) { + PA_PK_AS_REQ_19 r; + + type = "PK-INIT-19"; + pa_contentType = oid_id_pkauthdata(); + + ret = decode_PA_PK_AS_REQ_19(pa->padata_value.data, + pa->padata_value.length, + &r, + NULL); + if (ret) { + krb5_set_error_string(context, "Can't decode " + "PK-AS-REQ-19: %d", ret); + goto out; + } + + if (heim_oid_cmp(&r.signedAuthPack.contentType, + oid_id_pkcs7_signedData())) + { + krb5_set_error_string(context, "PK-AS-REQ-19 invalid content " + "type oid"); + free_PA_PK_AS_REQ_19(&r); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + if (r.signedAuthPack.content == NULL) { + krb5_set_error_string(context, "PK-AS-REQ-19 no signed auth pack"); + free_PA_PK_AS_REQ_19(&r); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + signed_content.data = malloc(r.signedAuthPack.content->length); + if (signed_content.data == NULL) { + ret = ENOMEM; + free_PA_PK_AS_REQ_19(&r); + krb5_set_error_string(context, "PK-AS-REQ-19 out of memory"); + goto out; + } + signed_content.length = r.signedAuthPack.content->length; + memcpy(signed_content.data, r.signedAuthPack.content->data, + signed_content.length); + + free_PA_PK_AS_REQ_19(&r); + } else if (pa->padata_type == KRB5_PADATA_PK_AS_REQ) { + PA_PK_AS_REQ r; + ContentInfo info; + + type = "PK-INIT-25"; + pa_contentType = oid_id_pkauthdata(); + + ret = decode_PA_PK_AS_REQ(pa->padata_value.data, + pa->padata_value.length, + &r, + NULL); + if (ret) { + krb5_set_error_string(context, "Can't decode PK-AS-REQ: %d", ret); + goto out; + } + + ret = decode_ContentInfo(r.signedAuthPack.data, + r.signedAuthPack.length, &info, NULL); + if (ret) { + krb5_set_error_string(context, "Can't decode PK-AS-REQ: %d", ret); + goto out; + } + + if (heim_oid_cmp(&info.contentType, oid_id_pkcs7_signedData())) { + krb5_set_error_string(context, "PK-AS-REQ invalid content " + "type oid"); + free_ContentInfo(&info); + free_PA_PK_AS_REQ(&r); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + if (info.content == NULL) { + krb5_set_error_string(context, "PK-AS-REQ no signed auth pack"); + free_PA_PK_AS_REQ(&r); + free_ContentInfo(&info); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + signed_content.data = malloc(info.content->length); + if (signed_content.data == NULL) { + ret = ENOMEM; + free_ContentInfo(&info); + free_PA_PK_AS_REQ(&r); + krb5_set_error_string(context, "PK-AS-REQ out of memory"); + goto out; + } + signed_content.length = info.content->length; + memcpy(signed_content.data, info.content->data, signed_content.length); + + free_ContentInfo(&info); + free_PA_PK_AS_REQ(&r); + + } else { + krb5_clear_error_string(context); + ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP; + goto out; + } + + ret = _krb5_pk_verify_sign(context, + signed_content.data, + signed_content.length, + kdc_identity, + &eContentType, + &eContent, + &client_params->certificate); + if (ret) + goto out; + + /* Signature is correct, now verify the signed message */ + if (heim_oid_cmp(&eContentType, pa_contentType)) { + krb5_set_error_string(context, "got wrong oid for pkauthdata"); + ret = KRB5_BADMSGTYPE; + goto out; + } + + if (pa->padata_type == KRB5_PADATA_PK_AS_REQ_WIN) { + AuthPack_Win2k ap; + + ret = decode_AuthPack_Win2k(eContent.data, + eContent.length, + &ap, + NULL); + if (ret) { + krb5_set_error_string(context, "can't decode AuthPack: %d", ret); + goto out; + } + + ret = pk_check_pkauthenticator_win2k(context, + &ap.pkAuthenticator, + req); + if (ret) { + free_AuthPack_Win2k(&ap); + goto out; + } + + client_params->type = PKINIT_COMPAT_WIN2K; + client_params->nonce = ap.pkAuthenticator.nonce; + + if (ap.clientPublicValue) { + krb5_set_error_string(context, "DH not supported for windows"); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + free_AuthPack_Win2k(&ap); + + } else if (pa->padata_type == KRB5_PADATA_PK_AS_REQ_19) { + AuthPack_19 ap; + + ret = decode_AuthPack_19(eContent.data, + eContent.length, + &ap, + NULL); + if (ret) { + krb5_set_error_string(context, "can't decode AuthPack: %d", ret); + free_AuthPack_19(&ap); + goto out; + } + + ret = pk_check_pkauthenticator_19(context, + &ap.pkAuthenticator, + req); + if (ret) { + free_AuthPack_19(&ap); + goto out; + } + + client_params->type = PKINIT_COMPAT_19; + client_params->nonce = ap.pkAuthenticator.nonce; + + if (ap.clientPublicValue) { + ret = get_dh_param(context, ap.clientPublicValue, client_params); + if (ret) { + free_AuthPack_19(&ap); + goto out; + } + } + free_AuthPack_19(&ap); + } else if (pa->padata_type == KRB5_PADATA_PK_AS_REQ) { + AuthPack ap; + + ret = decode_AuthPack(eContent.data, + eContent.length, + &ap, + NULL); + if (ret) { + krb5_set_error_string(context, "can't decode AuthPack: %d", ret); + free_AuthPack(&ap); + goto out; + } + + ret = pk_check_pkauthenticator(context, + &ap.pkAuthenticator, + req); + if (ret) { + free_AuthPack(&ap); + goto out; + } + + client_params->type = PKINIT_COMPAT_25; + client_params->nonce = ap.pkAuthenticator.nonce; + + if (ap.clientPublicValue) { + krb5_set_error_string(context, "PK-INIT, no support for DH"); + ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP; + free_AuthPack(&ap); + goto out; + } + free_AuthPack(&ap); + } else + krb5_abortx(context, "internal pkinit error"); + + /* + * Remaining fields (ie kdcCert and encryptionCert) in the request + * are ignored for now. + */ + + kdc_log(context, config, 0, "PK-INIT request of type %s", type); + + out: + + if (signed_content.data) + free(signed_content.data); + krb5_data_free(&eContent); + free_oid(&eContentType); + if (ret) + _kdc_pk_free_client_param(context, client_params); + else + *ret_params = client_params; + return ret; +} + +/* + * + */ + +static krb5_error_code +BN_to_integer(krb5_context context, BIGNUM *bn, heim_integer *integer) +{ + integer->length = BN_num_bytes(bn); + integer->data = malloc(integer->length); + if (integer->data == NULL) { + krb5_clear_error_string(context); + return ENOMEM; + } + BN_bn2bin(bn, integer->data); + integer->negative = bn->neg; + return 0; +} + +static krb5_error_code +pk_mk_pa_reply_enckey(krb5_context context, + pk_client_params *client_params, + const KDC_REQ *req, + krb5_keyblock *reply_key, + ContentInfo *content_info) +{ + KeyTransRecipientInfo *ri; + EnvelopedData ed; + krb5_error_code ret; + krb5_crypto crypto = NULL; + krb5_data buf, sd_data, enc_sd_data, iv, params; + krb5_keyblock tmp_key; + krb5_enctype enveloped_enctype; + X509_NAME *issuer_name; + heim_integer *serial; + size_t size; + AlgorithmIdentifier *enc_alg; + int i; + + krb5_data_zero(&enc_sd_data); + krb5_data_zero(&sd_data); + krb5_data_zero(&iv); + + memset(&tmp_key, 0, sizeof(tmp_key)); + memset(&ed, 0, sizeof(ed)); + + /* default to DES3 if client doesn't tell us */ + enveloped_enctype = ETYPE_DES3_CBC_NONE_CMS; + + for (i = 0; i < req->req_body.etype.len; i++) { + switch(req->req_body.etype.val[i]) { + case 15: /* des-ede3-cbc-Env-OID */ + enveloped_enctype = ETYPE_DES3_CBC_NONE_CMS; + break; + default: + break; + } + } + + ret = krb5_generate_random_keyblock(context, enveloped_enctype, &tmp_key); + if (ret) + goto out; + + ret = krb5_crypto_init(context, &tmp_key, 0, &crypto); + if (ret) + goto out; + + + ret = krb5_crypto_getblocksize(context, crypto, &iv.length); + if (ret) + goto out; + + ret = krb5_data_alloc(&iv, iv.length); + if (ret) { + krb5_set_error_string(context, "malloc out of memory"); + goto out; + } + + krb5_generate_random_block(iv.data, iv.length); + + enc_alg = &ed.encryptedContentInfo.contentEncryptionAlgorithm; + + ret = krb5_enctype_to_oid(context, enveloped_enctype, &enc_alg->algorithm); + if (ret) + goto out; + + ret = krb5_crypto_set_params(context, crypto, &iv, ¶ms); + if (ret) + goto out; + + ALLOC(enc_alg->parameters); + if (enc_alg->parameters == NULL) { + krb5_data_free(¶ms); + krb5_set_error_string(context, "malloc out of memory"); + return ENOMEM; + } + enc_alg->parameters->data = params.data; + enc_alg->parameters->length = params.length; + + if (client_params->type == PKINIT_COMPAT_WIN2K || client_params->type == PKINIT_COMPAT_19 || client_params->type == PKINIT_COMPAT_25) { + ReplyKeyPack kp; + memset(&kp, 0, sizeof(kp)); + + ret = copy_EncryptionKey(reply_key, &kp.replyKey); + if (ret) { + krb5_clear_error_string(context); + goto out; + } + kp.nonce = client_params->nonce; + + ASN1_MALLOC_ENCODE(ReplyKeyPack, buf.data, buf.length, &kp, &size,ret); + free_ReplyKeyPack(&kp); + } else { + krb5_abortx(context, "internal pkinit error"); + } + if (ret) { + krb5_set_error_string(context, "ASN.1 encoding of ReplyKeyPack " + "failed (%d)", ret); + goto out; + } + if (buf.length != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + + /* + * CRL's are not transfered -- should be ? + */ + + ret = _krb5_pk_create_sign(context, + oid_id_pkrkeydata(), + &buf, + kdc_identity, + &sd_data); + krb5_data_free(&buf); + if (ret) + goto out; + + ret = krb5_encrypt_ivec(context, crypto, 0, + sd_data.data, sd_data.length, + &enc_sd_data, + iv.data); + + ALLOC_SEQ(&ed.recipientInfos, 1); + if (ed.recipientInfos.val == NULL) { + krb5_clear_error_string(context); + ret = ENOMEM; + goto out; + } + + ri = &ed.recipientInfos.val[0]; + + ri->version = 0; + ri->rid.element = choice_CMSIdentifier_issuerAndSerialNumber; + + issuer_name = X509_get_issuer_name(client_params->certificate->cert); + OPENSSL_ASN1_MALLOC_ENCODE(X509_NAME, buf.data, buf.length, + issuer_name, ret); + if (ret) { + krb5_clear_error_string(context); + goto out; + } + ret = decode_Name(buf.data, buf.length, + &ri->rid.u.issuerAndSerialNumber.issuer, + NULL); + free(buf.data); + if (ret) { + krb5_set_error_string(context, "pkinit: failed to parse Name"); + goto out; + } + + serial = &ri->rid.u.issuerAndSerialNumber.serialNumber; + { + ASN1_INTEGER *isn; + BIGNUM *bn; + + isn = X509_get_serialNumber(client_params->certificate->cert); + bn = ASN1_INTEGER_to_BN(isn, NULL); + if (bn == NULL) { + ret = ENOMEM; + krb5_clear_error_string(context); + goto out; + } + ret = BN_to_integer(context, bn, serial); + BN_free(bn); + if (ret) { + krb5_clear_error_string(context); + goto out; + } + } + + { + const heim_oid *pk_enc_key_oid; + krb5_data enc_tmp_key; + + ret = pk_encrypt_key(context, &tmp_key, + X509_get_pubkey(client_params->certificate->cert), + &enc_tmp_key, + &pk_enc_key_oid); + if (ret) + goto out; + + ri->encryptedKey.length = enc_tmp_key.length; + ri->encryptedKey.data = enc_tmp_key.data; + + ret = copy_oid(pk_enc_key_oid, &ri->keyEncryptionAlgorithm.algorithm); + if (ret) + goto out; + } + + /* + * + */ + + ed.version = 0; + ed.originatorInfo = NULL; + + ret = copy_oid(oid_id_pkcs7_signedData(), &ed.encryptedContentInfo.contentType); + if (ret) { + krb5_clear_error_string(context); + goto out; + } + + ALLOC(ed.encryptedContentInfo.encryptedContent); + if (ed.encryptedContentInfo.encryptedContent == NULL) { + krb5_clear_error_string(context); + ret = ENOMEM; + goto out; + } + + ed.encryptedContentInfo.encryptedContent->data = enc_sd_data.data; + ed.encryptedContentInfo.encryptedContent->length = enc_sd_data.length; + krb5_data_zero(&enc_sd_data); + + ed.unprotectedAttrs = NULL; + + ASN1_MALLOC_ENCODE(EnvelopedData, buf.data, buf.length, &ed, &size, ret); + if (ret) { + krb5_set_error_string(context, + "ASN.1 encoding of EnvelopedData failed (%d)", + ret); + goto out; + } + + ret = _krb5_pk_mk_ContentInfo(context, + &buf, + oid_id_pkcs7_envelopedData(), + content_info); + krb5_data_free(&buf); + + out: + if (crypto) + krb5_crypto_destroy(context, crypto); + krb5_free_keyblock_contents(context, &tmp_key); + krb5_data_free(&enc_sd_data); + krb5_data_free(&iv); + free_EnvelopedData(&ed); + + return ret; +} + +/* + * + */ + +static krb5_error_code +pk_mk_pa_reply_dh(krb5_context context, + DH *kdc_dh, + pk_client_params *client_params, + krb5_keyblock *reply_key, + ContentInfo *content_info) +{ + ASN1_INTEGER *dh_pub_key = NULL; + KDCDHKeyInfo dh_info; + krb5_error_code ret; + SignedData sd; + krb5_data buf, sd_buf; + size_t size; + + memset(&dh_info, 0, sizeof(dh_info)); + memset(&sd, 0, sizeof(sd)); + krb5_data_zero(&buf); + krb5_data_zero(&sd_buf); + + dh_pub_key = BN_to_ASN1_INTEGER(kdc_dh->pub_key, NULL); + if (dh_pub_key == NULL) { + krb5_set_error_string(context, "BN_to_ASN1_INTEGER() failed (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = ENOMEM; + goto out; + } + + OPENSSL_ASN1_MALLOC_ENCODE(ASN1_INTEGER, buf.data, buf.length, dh_pub_key, + ret); + ASN1_INTEGER_free(dh_pub_key); + if (ret) { + krb5_set_error_string(context, "Encoding of ASN1_INTEGER failed (%s)", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + dh_info.subjectPublicKey.length = buf.length * 8; + dh_info.subjectPublicKey.data = buf.data; + + dh_info.nonce = client_params->nonce; + + ASN1_MALLOC_ENCODE(KDCDHKeyInfo, buf.data, buf.length, &dh_info, &size, + ret); + if (ret) { + krb5_set_error_string(context, "ASN.1 encoding of " + "KdcDHKeyInfo failed (%d)", ret); + goto out; + } + if (buf.length != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + + /* + * Create the SignedData structure and sign the KdcDHKeyInfo + * filled in above + */ + + ret = _krb5_pk_create_sign(context, + oid_id_pkdhkeydata(), + &buf, + kdc_identity, + &sd_buf); + krb5_data_free(&buf); + if (ret) + goto out; + + ret = _krb5_pk_mk_ContentInfo(context, &sd_buf, oid_id_pkcs7_signedData(), + content_info); + krb5_data_free(&sd_buf); + + out: + free_KDCDHKeyInfo(&dh_info); + + return ret; +} + +/* + * + */ + +krb5_error_code +_kdc_pk_mk_pa_reply(krb5_context context, + krb5_kdc_configuration *config, + pk_client_params *client_params, + const hdb_entry *client, + const KDC_REQ *req, + krb5_keyblock **reply_key, + METHOD_DATA *md) +{ + krb5_error_code ret; + void *buf; + size_t len, size; + krb5_enctype enctype; + int pa_type; + int i; + + if (!config->enable_pkinit) { + krb5_clear_error_string(context); + return 0; + } + + if (req->req_body.etype.len > 0) { + for (i = 0; i < req->req_body.etype.len; i++) + if (krb5_enctype_valid(context, req->req_body.etype.val[i]) == 0) + break; + if (req->req_body.etype.len <= i) { + ret = KRB5KRB_ERR_GENERIC; + krb5_set_error_string(context, + "No valid enctype available from client"); + goto out; + } + enctype = req->req_body.etype.val[i]; + } else + enctype = ETYPE_DES3_CBC_SHA1; + + if (client_params->type == PKINIT_COMPAT_25) { + PA_PK_AS_REP rep; + + pa_type = KRB5_PADATA_PK_AS_REP; + + memset(&rep, 0, sizeof(rep)); + + if (client_params->dh == NULL) { + rep.element = choice_PA_PK_AS_REP_encKeyPack; + ContentInfo info; + + krb5_generate_random_keyblock(context, enctype, + &client_params->reply_key); + ret = pk_mk_pa_reply_enckey(context, + client_params, + req, + &client_params->reply_key, + &info); + if (ret) { + free_PA_PK_AS_REP(&rep); + goto out; + } + ASN1_MALLOC_ENCODE(ContentInfo, rep.u.encKeyPack.data, + rep.u.encKeyPack.length, &info, &size, + ret); + free_ContentInfo(&info); + if (ret) { + krb5_set_error_string(context, "encoding of Key ContentInfo " + "failed %d", ret); + free_PA_PK_AS_REP(&rep); + goto out; + } + if (rep.u.encKeyPack.length != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + + } else { + krb5_set_error_string(context, "DH -25 not implemented"); + ret = KRB5KRB_ERR_GENERIC; + } + if (ret) { + free_PA_PK_AS_REP(&rep); + goto out; + } + + ASN1_MALLOC_ENCODE(PA_PK_AS_REP, buf, len, &rep, &size, ret); + free_PA_PK_AS_REP(&rep); + if (ret) { + krb5_set_error_string(context, "encode PA-PK-AS-REP failed %d", + ret); + goto out; + } + if (len != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + + } else if (client_params->type == PKINIT_COMPAT_19) { + PA_PK_AS_REP_19 rep; + + pa_type = KRB5_PADATA_PK_AS_REP_19; + + memset(&rep, 0, sizeof(rep)); + + if (client_params->dh == NULL) { + rep.element = choice_PA_PK_AS_REP_19_encKeyPack; + krb5_generate_random_keyblock(context, enctype, + &client_params->reply_key); + ret = pk_mk_pa_reply_enckey(context, + client_params, + req, + &client_params->reply_key, + &rep.u.encKeyPack); + } else { + rep.element = choice_PA_PK_AS_REP_19_dhSignedData; + + ret = check_dh_params(client_params->dh); + if (ret) + return ret; + + ret = generate_dh_keyblock(context, client_params, enctype, + &client_params->reply_key); + if (ret) + return ret; + + ret = pk_mk_pa_reply_dh(context, client_params->dh, + client_params, + &client_params->reply_key, + &rep.u.dhSignedData); + } + if (ret) { + free_PA_PK_AS_REP_19(&rep); + goto out; + } + + ASN1_MALLOC_ENCODE(PA_PK_AS_REP_19, buf, len, &rep, &size, ret); + free_PA_PK_AS_REP_19(&rep); + if (ret) { + krb5_set_error_string(context, + "encode PA-PK-AS-REP-19 failed %d", ret); + goto out; + } + if (len != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + } else if (client_params->type == PKINIT_COMPAT_WIN2K) { + PA_PK_AS_REP_Win2k rep; + + pa_type = KRB5_PADATA_PK_AS_REP_19; + + memset(&rep, 0, sizeof(rep)); + + if (client_params->dh) { + krb5_set_error_string(context, "DH -25 not implemented"); + ret = KRB5KRB_ERR_GENERIC; + } else { + rep.element = choice_PA_PK_AS_REP_encKeyPack; + ContentInfo info; + + krb5_generate_random_keyblock(context, enctype, + &client_params->reply_key); + ret = pk_mk_pa_reply_enckey(context, + client_params, + req, + &client_params->reply_key, + &info); + if (ret) { + free_PA_PK_AS_REP_Win2k(&rep); + goto out; + } + ASN1_MALLOC_ENCODE(ContentInfo, rep.u.encKeyPack.data, + rep.u.encKeyPack.length, &info, &size, + ret); + free_ContentInfo(&info); + if (ret) { + krb5_set_error_string(context, "encoding of Key ContentInfo " + "failed %d", ret); + free_PA_PK_AS_REP_Win2k(&rep); + goto out; + } + if (rep.u.encKeyPack.length != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + + } + if (ret) { + free_PA_PK_AS_REP_Win2k(&rep); + goto out; + } + + ASN1_MALLOC_ENCODE(PA_PK_AS_REP_Win2k, buf, len, &rep, &size, ret); + free_PA_PK_AS_REP_Win2k(&rep); + if (ret) { + krb5_set_error_string(context, + "encode PA-PK-AS-REP-Win2k failed %d", ret); + goto out; + } + if (len != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + + } else + krb5_abortx(context, "PK-INIT internal error"); + + + ret = krb5_padata_add(context, md, pa_type, buf, len); + if (ret) { + krb5_set_error_string(context, "failed adding " + "PA-PK-AS-REP-19 %d", ret); + free(buf); + } + out: + if (ret == 0) + *reply_key = &client_params->reply_key; + return ret; +} + +static int +pk_principal_from_X509(krb5_context context, + krb5_kdc_configuration *config, + struct krb5_pk_cert *client_cert, + krb5_principal *principal) +{ + krb5_error_code ret; + GENERAL_NAMES *gens; + GENERAL_NAME *gen; + ASN1_OBJECT *obj; + int i; + + *principal = NULL; + + obj = OBJ_txt2obj("1.3.6.1.5.2.2",1); + + gens = X509_get_ext_d2i(client_cert->cert, NID_subject_alt_name, + NULL, NULL); + if (gens == NULL) + return 1; + + for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) { + KRB5PrincipalName kn; + size_t len, size; + void *p; + + gen = sk_GENERAL_NAME_value(gens, i); + if (gen->type != GEN_OTHERNAME) + continue; + + if(OBJ_cmp(obj, gen->d.otherName->type_id) != 0) + continue; + + p = ASN1_STRING_data(gen->d.otherName->value->value.sequence); + len = ASN1_STRING_length(gen->d.otherName->value->value.sequence); + + ret = decode_KRB5PrincipalName(p, len, &kn, &size); + if (ret) { + kdc_log(context, config, 0, + "Decoding kerberos name in certificate failed: %s", + krb5_get_err_text(context, ret)); + continue; + } + + *principal = malloc(sizeof(**principal)); + if (*principal == NULL) { + free_KRB5PrincipalName(&kn); + return 1; + } + + (*principal)->name = kn.principalName; + (*principal)->realm = kn.realm; + return 0; + } + return 1; +} + + +/* XXX match with issuer too ? */ + +krb5_error_code +_kdc_pk_check_client(krb5_context context, + krb5_kdc_configuration *config, + krb5_principal client_princ, + const hdb_entry *client, + pk_client_params *client_params, + char **subject_name) +{ + struct krb5_pk_cert *client_cert = client_params->certificate; + krb5_principal cert_princ; + X509_NAME *name; + char *subject = NULL; + krb5_error_code ret; + krb5_boolean b; + int i; + + *subject_name = NULL; + + name = X509_get_subject_name(client_cert->cert); + if (name == NULL) { + krb5_set_error_string(context, "PKINIT can't get subject name"); + return ENOMEM; + } + subject = X509_NAME_oneline(name, NULL, 0); + if (subject == NULL) { + krb5_set_error_string(context, "PKINIT can't get subject name"); + return ENOMEM; + } + *subject_name = strdup(subject); + if (*subject_name == NULL) { + krb5_set_error_string(context, "out of memory"); + return ENOMEM; + } + OPENSSL_free(subject); + + if (config->enable_pkinit_princ_in_cert) { + ret = pk_principal_from_X509(context, config, + client_cert, &cert_princ); + if (ret == 0) { + b = krb5_principal_compare(context, client_princ, cert_princ); + krb5_free_principal(context, cert_princ); + if (b == TRUE) + return 0; + } + } + + for (i = 0; i < principal_mappings.len; i++) { + b = krb5_principal_compare(context, + client_princ, + principal_mappings.val[i].principal); + if (b == FALSE) + continue; + if (strcmp(principal_mappings.val[i].subject, *subject_name) != 0) + continue; + return 0; + } + free(*subject_name); + *subject_name = NULL; + krb5_set_error_string(context, "PKINIT no matching principals"); + return KRB5_KDC_ERROR_CLIENT_NAME_MISMATCH; +} + +static krb5_error_code +add_principal_mapping(krb5_context context, + const char *principal_name, + const char * subject) +{ + struct pk_allowed_princ *tmp; + krb5_principal principal; + krb5_error_code ret; + + tmp = realloc(principal_mappings.val, + (principal_mappings.len + 1) * sizeof(*tmp)); + if (tmp == NULL) + return ENOMEM; + principal_mappings.val = tmp; + + ret = krb5_parse_name(context, principal_name, &principal); + if (ret) + return ret; + + principal_mappings.val[principal_mappings.len].principal = principal; + + principal_mappings.val[principal_mappings.len].subject = strdup(subject); + if (principal_mappings.val[principal_mappings.len].subject == NULL) { + krb5_free_principal(context, principal); + return ENOMEM; + } + principal_mappings.len++; + + return 0; +} + + +krb5_error_code +_kdc_pk_initialize(krb5_context context, + krb5_kdc_configuration *config, + const char *user_id, + const char *x509_anchors) +{ + const char *mapping_file; + krb5_error_code ret; + char buf[1024]; + unsigned long lineno = 0; + FILE *f; + + principal_mappings.len = 0; + principal_mappings.val = NULL; + + ret = _krb5_pk_load_openssl_id(context, + &kdc_identity, + user_id, + x509_anchors, + NULL, + NULL, + NULL); + if (ret) { + krb5_warn(context, ret, "PKINIT: failed to load"); + config->enable_pkinit = 0; + return ret; + } + + mapping_file = krb5_config_get_string_default(context, + NULL, + HDB_DB_DIR "/pki-mapping", + "kdc", + "pki-mappings-file", + NULL); + f = fopen(mapping_file, "r"); + if (f == NULL) { + krb5_warnx(context, "PKINIT: failed to load mappings file %s", + mapping_file); + return 0; + } + + while (fgets(buf, sizeof(buf), f) != NULL) { + char *subject_name, *p; + + buf[strcspn(buf, "\n")] = '\0'; + lineno++; + + p = buf + strspn(buf, " \t"); + + if (*p == '#' || *p == '\0') + continue; + + subject_name = strchr(p, ':'); + if (subject_name == NULL) { + krb5_warnx(context, "pkinit mapping file line %lu " + "missing \":\" :%s", + lineno, buf); + continue; + } + *subject_name++ = '\0'; + + ret = add_principal_mapping(context, p, subject_name); + if (ret) { + krb5_warn(context, ret, "failed to add line %lu \":\" :%s\n", + lineno, buf); + continue; + } + } + + fclose(f); + + return 0; +} + +#endif /* PKINIT */ diff --git a/source4/heimdal/kdc/process.c b/source4/heimdal/kdc/process.c new file mode 100644 index 0000000000..22cf23c48d --- /dev/null +++ b/source4/heimdal/kdc/process.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kdc_locl.h" + +RCSID("$Id: process.c,v 1.2 2005/06/30 01:54:49 lha Exp $"); + +/* + * handle the request in `buf, len', from `addr' (or `from' as a string), + * sending a reply in `reply'. + */ + +int +krb5_kdc_process_generic_request(krb5_context context, + krb5_kdc_configuration *config, + unsigned char *buf, + size_t len, + krb5_data *reply, + krb5_boolean *prependlength, + const char *from, + struct sockaddr *addr) +{ + KDC_REQ req; + Ticket ticket; + krb5_error_code ret; + size_t i; + + gettimeofday(&_kdc_now, NULL); + if(decode_AS_REQ(buf, len, &req, &i) == 0){ + ret = _kdc_as_rep(context, config, &req, reply, from, addr); + free_AS_REQ(&req); + return ret; + }else if(decode_TGS_REQ(buf, len, &req, &i) == 0){ + ret = _kdc_tgs_rep(context, config, &req, reply, from, addr); + free_TGS_REQ(&req); + return ret; + }else if(decode_Ticket(buf, len, &ticket, &i) == 0){ + ret = _kdc_do_524(context, config, &ticket, reply, from, addr); + free_Ticket(&ticket); + return ret; + } else if(_kdc_maybe_version4(buf, len)){ + *prependlength = FALSE; /* elbitapmoc sdrawkcab XXX */ + _kdc_do_version4(context, config, buf, len, reply, from, + (struct sockaddr_in*)addr); + return 0; + } else if (config->enable_kaserver) { + ret = _kdc_do_kaserver(context, config, buf, len, reply, from, + (struct sockaddr_in*)addr); + return ret; + } + + return -1; +} + +/* + * handle the request in `buf, len', from `addr' (or `from' as a string), + * sending a reply in `reply'. + * + * This only processes krb5 requests + */ + +int +krb5_kdc_process_krb5_request(krb5_context context, + krb5_kdc_configuration *config, + unsigned char *buf, + size_t len, + krb5_data *reply, + const char *from, + struct sockaddr *addr) +{ + KDC_REQ req; + krb5_error_code ret; + size_t i; + + gettimeofday(&_kdc_now, NULL); + if(decode_AS_REQ(buf, len, &req, &i) == 0){ + ret = _kdc_as_rep(context, config, &req, reply, from, addr); + free_AS_REQ(&req); + return ret; + }else if(decode_TGS_REQ(buf, len, &req, &i) == 0){ + ret = _kdc_tgs_rep(context, config, &req, reply, from, addr); + free_TGS_REQ(&req); + return ret; + } + return -1; +} diff --git a/source4/heimdal/kdc/rx.h b/source4/heimdal/kdc/rx.h new file mode 100644 index 0000000000..ab8ec80523 --- /dev/null +++ b/source4/heimdal/kdc/rx.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 1997 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* $Id: rx.h,v 1.4 1999/12/02 17:05:00 joda Exp $ */ + +#ifndef __RX_H__ +#define __RX_H__ + +/* header of a RPC packet */ + +enum rx_header_type { + HT_DATA = 1, + HT_ACK = 2, + HT_BUSY = 3, + HT_ABORT = 4, + HT_ACKALL = 5, + HT_CHAL = 6, + HT_RESP = 7, + HT_DEBUG = 8 +}; + +/* For flags in header */ + +enum rx_header_flag { + HF_CLIENT_INITIATED = 1, + HF_REQ_ACK = 2, + HF_LAST = 4, + HF_MORE = 8 +}; + +struct rx_header { + u_int32_t epoch; + u_int32_t connid; /* And channel ID */ + u_int32_t callid; + u_int32_t seqno; + u_int32_t serialno; + u_char type; + u_char flags; + u_char status; + u_char secindex; + u_int16_t reserved; /* ??? verifier? */ + u_int16_t serviceid; +/* This should be the other way around according to everything but */ +/* tcpdump */ +}; + +#define RX_HEADER_SIZE 28 + +#endif /* __RX_H__ */ |