summaryrefslogtreecommitdiff
path: root/third_party/heimdal/kdc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/heimdal/kdc')
-rw-r--r--third_party/heimdal/kdc/Makefile.am15
-rw-r--r--third_party/heimdal/kdc/bx509d.c173
-rw-r--r--third_party/heimdal/kdc/fast.c6
-rw-r--r--third_party/heimdal/kdc/hpropd.c38
-rw-r--r--third_party/heimdal/kdc/httpkadmind.c33
-rw-r--r--third_party/heimdal/kdc/ipc_csr_authorizer.c291
-rw-r--r--third_party/heimdal/kdc/kdc-plugin.c66
-rw-r--r--third_party/heimdal/kdc/kdc-plugin.h28
-rw-r--r--third_party/heimdal/kdc/kdc-tester.c7
-rw-r--r--third_party/heimdal/kdc/kerberos5.c8
-rw-r--r--third_party/heimdal/kdc/krb5tgs.c94
-rw-r--r--third_party/heimdal/kdc/misc.c2
-rw-r--r--third_party/heimdal/kdc/mit_dump.c4
-rw-r--r--third_party/heimdal/kdc/mssfu.c20
-rw-r--r--third_party/heimdal/kdc/pkinit-ec.c350
-rw-r--r--third_party/heimdal/kdc/simple_csr_authorizer.c343
-rw-r--r--third_party/heimdal/kdc/test_csr_authorizer.c513
-rw-r--r--third_party/heimdal/kdc/test_token_validator.c2
18 files changed, 1420 insertions, 573 deletions
diff --git a/third_party/heimdal/kdc/Makefile.am b/third_party/heimdal/kdc/Makefile.am
index 48248d8248b..ca5835930dd 100644
--- a/third_party/heimdal/kdc/Makefile.am
+++ b/third_party/heimdal/kdc/Makefile.am
@@ -6,8 +6,9 @@ WFLAGS += $(WFLAGS_ENUM_CONV)
AM_CPPFLAGS += $(INCLUDE_libintl) $(INCLUDE_openssl_crypto) -I$(srcdir)/../lib/krb5
-lib_LTLIBRARIES = simple_csr_authorizer.la ipc_csr_authorizer.la \
- libkdc.la negotiate_token_validator.la
+lib_LTLIBRARIES = ipc_csr_authorizer.la \
+ negotiate_token_validator.la \
+ libkdc.la
if HAVE_CJWT
lib_LTLIBRARIES += cjwt_token_validator.la
@@ -97,8 +98,6 @@ endif
negotiate_token_validator_la_SOURCES = negotiate_token_validator.c
negotiate_token_validator_la_LDFLAGS = -module $(LIB_gssapi)
# CSR Authorizer plugins (for kdc/kx509 and bx509d)
-simple_csr_authorizer_la_SOURCES = simple_csr_authorizer.c
-simple_csr_authorizer_la_LDFLAGS = -module
ipc_csr_authorizer_la_SOURCES = ipc_csr_authorizer.c
ipc_csr_authorizer_la_LDFLAGS = -module \
$(top_builddir)/lib/krb5/libkrb5.la \
@@ -155,7 +154,6 @@ ALL_OBJECTS += $(digest_service_OBJECTS)
ALL_OBJECTS += $(bx509d_OBJECTS)
ALL_OBJECTS += $(httpkadmind_OBJECTS)
ALL_OBJECTS += $(cjwt_token_validator_la_OBJECTS)
-ALL_OBJECTS += $(simple_csr_authorizer_la_OBJECTS)
ALL_OBJECTS += $(test_token_validator_OBJECTS)
ALL_OBJECTS += $(test_csr_authorizer_OBJECTS)
ALL_OBJECTS += $(test_kdc_ca_OBJECTS)
@@ -237,7 +235,12 @@ digest_service_LDADD = \
kdc_replay_LDADD = libkdc.la $(LDADD) $(LIB_pidfile)
kdc_tester_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
test_token_validator_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase) $(LIB_gssapi)
-test_csr_authorizer_LDADD = libkdc.la $(top_builddir)/lib/hx509/libhx509.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
+test_csr_authorizer_LDADD = libkdc.la \
+ $(top_builddir)/lib/hx509/libhx509.la \
+ $(LDADD) \
+ $(LIB_pidfile) \
+ $(LIB_heimbase) \
+ $(top_builddir)/lib/ipc/libheim-ipcs.la
test_kdc_ca_LDADD = libkdc.la $(top_builddir)/lib/hx509/libhx509.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
include_HEADERS = kdc.h $(srcdir)/kdc-protos.h
diff --git a/third_party/heimdal/kdc/bx509d.c b/third_party/heimdal/kdc/bx509d.c
index 4d1b694a914..b7e9096f737 100644
--- a/third_party/heimdal/kdc/bx509d.c
+++ b/third_party/heimdal/kdc/bx509d.c
@@ -185,6 +185,7 @@ typedef struct bx509_request_desc {
const char *redir;
const char *method;
size_t post_data_size;
+ size_t san_idx; /* For /get-tgts */
enum k5_creds_kind cckind;
char *pkix_store;
char *tgts_filename;
@@ -260,6 +261,8 @@ get_krb5_context(krb5_context *contextp)
return 0;
if ((ret = krb5_init_context(contextp)))
return *contextp = NULL, ret;
+ if (logfac)
+ krb5_set_log_dest(*contextp, logfac);
(void) pthread_setspecific(k5ctx, *contextp);
return *contextp ? 0 : ENOMEM;
}
@@ -566,7 +569,6 @@ bad_reqv(struct bx509_request_desc *r,
va_list ap)
{
krb5_error_code ret;
- krb5_context context = NULL;
const char *k5msg = NULL;
const char *emsg = NULL;
char *formatted = NULL;
@@ -586,8 +588,10 @@ bad_reqv(struct bx509_request_desc *r,
if (code) {
if (r->context)
emsg = k5msg = krb5_get_error_message(r->context, code);
- else
+ else if (code > -1)
emsg = strerror(code);
+ else
+ emsg = "Unknown error";
}
ret = vasprintf(&formatted, fmt, ap);
@@ -600,10 +604,11 @@ bad_reqv(struct bx509_request_desc *r,
}
heim_audit_addreason((heim_svc_req_desc)r, "%s", msg);
audit_trail(r, code);
- krb5_free_error_message(context, k5msg);
+ if (r->context)
+ krb5_free_error_message(r->context, k5msg);
if (ret == -1 || msg == NULL) {
- if (context)
+ if (r->context)
krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, MHD_RESPMEM_PERSISTENT,
NULL, "Out of memory", sizeof("Out of memory") - 1, NULL);
@@ -882,7 +887,7 @@ do_CA(struct bx509_request_desc *r, const char *csr)
bytes = rk_base64_decode(csr2, d.data);
free(csr2);
if (bytes < 0)
- ret = errno;
+ ret = errno ? errno : EINVAL;
else
d.length = bytes;
if (ret) {
@@ -1360,10 +1365,12 @@ do_pkinit(struct bx509_request_desc *r, enum k5_creds_kind kind)
ret = krb5_append_addresses(r->context, &r->tgt_addresses,
&addr);
}
- if (ret == 0 && r->tgt_addresses.len == 0)
- ret = krb5_get_init_creds_opt_set_addressless(r->context, opt, 1);
- else
- krb5_get_init_creds_opt_set_address_list(opt, &r->tgt_addresses);
+ if (ret == 0) {
+ if (r->tgt_addresses.len == 0)
+ ret = krb5_get_init_creds_opt_set_addressless(r->context, opt, 1);
+ else
+ krb5_get_init_creds_opt_set_address_list(opt, &r->tgt_addresses);
+ }
if (ret == 0)
ret = krb5_get_init_creds_opt_set_pkinit(r->context, opt, p,
r->pkix_store,
@@ -1543,7 +1550,7 @@ k5_get_creds(struct bx509_request_desc *r, enum k5_creds_kind kind)
static void
acc_str(char **acc, char *adds, size_t addslen)
{
- char *tmp;
+ char *tmp = NULL;
int l = addslen <= INT_MAX ? (int)addslen : INT_MAX;
if (asprintf(&tmp, "%s%s%.*s",
@@ -1570,7 +1577,7 @@ fmt_gss_error(OM_uint32 code, gss_OID mech)
acc_str(&r, (char *)buf.value, buf.length);
gss_release_buffer(&minor, &buf);
} while (!GSS_ERROR(major) && more);
- return r ? r : "Out of memory while formatting GSS-API error";
+ return r;
}
static char *
@@ -1580,7 +1587,10 @@ fmt_gss_errors(const char *r, OM_uint32 major, OM_uint32 minor, gss_OID mech)
ma = fmt_gss_error(major, GSS_C_NO_OID);
mi = mech == GSS_C_NO_OID ? NULL : fmt_gss_error(minor, mech);
- if (asprintf(&s, "%s: %s%s%s", r, ma, mi ? ": " : "", mi ? mi : "") > -1 &&
+ if (asprintf(&s, "%s: %s%s%s", r,
+ ma ? ma : "Out of memory",
+ mi ? ": " : "",
+ mi ? mi : "") > -1 &&
s) {
free(ma);
free(mi);
@@ -1605,8 +1615,13 @@ bad_req_gss(struct bx509_request_desc *r,
if (major == GSS_S_BAD_NAME || major == GSS_S_BAD_NAMETYPE)
http_status_code = MHD_HTTP_BAD_REQUEST;
- ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY, NULL,
- msg, strlen(msg), NULL);
+ if (msg)
+ ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY, NULL,
+ msg, strlen(msg), NULL);
+ else
+ ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY, NULL,
+ "Out of memory while formatting GSS error message",
+ sizeof("Out of memory while formatting GSS error message") - 1, NULL);
free(msg);
return ret;
}
@@ -1839,9 +1854,7 @@ authorize_TGT_REQ(struct bx509_request_desc *r)
if (for_cname == r->cname || strcmp(r->cname, r->for_cname) == 0)
return 0;
- ret = krb5_parse_name(r->context, r->cname, &p);
- if (ret == 0)
- ret = hx509_request_init(r->context->hx509ctx, &r->req);
+ ret = hx509_request_init(r->context->hx509ctx, &r->req);
if (ret)
return bad_500(r, ret, "Out of resources");
heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
@@ -1852,9 +1865,12 @@ authorize_TGT_REQ(struct bx509_request_desc *r)
ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
for_cname);
if (ret == 0)
+ ret = krb5_parse_name(r->context, r->cname, &p);
+ if (ret == 0)
ret = kdc_authorize_csr(r->context, "get-tgt", r->req, p);
krb5_free_principal(r->context, p);
hx509_request_free(&r->req);
+ r->req = NULL;
if (ret)
return bad_403(r, ret, "Not authorized to requested TGT");
return ret;
@@ -1972,7 +1988,7 @@ get_tgts_accumulate_ccache_write_json(struct bx509_request_desc *r,
if (o && k && v)
ret = heim_dict_set_value(o, k, v);
else
- ret = errno;
+ ret = ENOMEM;
if (ret == 0) {
heim_release(v);
@@ -1991,10 +2007,13 @@ get_tgts_accumulate_ccache_write_json(struct bx509_request_desc *r,
ret = heim_dict_set_value(o, k, v);
}
if (ret == 0 && code != 0) {
+ const char *s = krb5_get_error_message(r->context, code);
+
heim_release(v);
heim_release(k);
k = heim_string_create("error");
- v = heim_string_create(krb5_get_error_message(r->context, code));
+ v = heim_string_create(s ? s : "Out of memory");
+ krb5_free_error_message(r->context, s);
if (k && v)
ret = heim_dict_set_value(o, k, v);
}
@@ -2120,17 +2139,79 @@ get_tgts_param_execute_cb(void *d,
const char *val)
{
struct bx509_request_desc *r = d;
- heim_mhd_result res = MHD_YES;
+ hx509_san_type san_type;
krb5_error_code ret;
+ size_t san_idx = r->san_idx++;
+ const char *save_for_cname = r->for_cname;
+ char *s = NULL;
+ int res;
- if (strcmp(key, "cname") == 0 && val) {
- /* Handled upstairs */
+ /* We expect only cname=principal q-params here */
+ if (strcmp(key, "cname") != 0 || val == NULL)
+ return MHD_YES;
+
+ /*
+ * We expect the `san_idx'th SAN in the `r->req' request checked by
+ * kdc_authorize_csr() to be the same as this cname. This happens
+ * naturally because we add these SANs to `r->req' in the same order as we
+ * visit them here (unless our HTTP library somehow went crazy).
+ *
+ * Still, we check that it's the same SAN.
+ */
+ ret = hx509_request_get_san(r->req, san_idx, &san_type, &s);
+ if (ret == HX509_NO_ITEM ||
+ san_type != HX509_SAN_TYPE_PKINIT ||
+ strcmp(s, val) != 0) {
+ /*
+ * If the cname and SAN don't match, it's some weird internal error
+ * (can't happen).
+ */
+ krb5_set_error_message(r->context, r->error_code = EACCES,
+ "PKINIT SAN not granted: %s (internal error)",
+ val);
+ ret = EACCES;
+ }
+
+ /*
+ * We're going to pretend to be this SAN for the purpose of acquring a TGT
+ * for it. So we "push" `r->for_cname'.
+ */
+ if (ret == 0)
r->for_cname = val;
+
+ /*
+ * Our authorizer supports partial authorization where the whole request is
+ * rejected but some features of it are permitted.
+ *
+ * (In most end-points we don't want partial authorization, but in
+ * /get-tgts we very much do.)
+ */
+ if (ret == 0 && !hx509_request_san_authorized_p(r->req, san_idx)) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "REJECT_krb5PrincipalName", "%s", val);
+ krb5_set_error_message(r->context, r->error_code = EACCES,
+ "PKINIT SAN denied: %s", val);
+ ret = EACCES;
+ }
+ if (ret == 0) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "ACCEPT_krb5PrincipalName", "%s", val);
ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
- res = get_tgts_accumulate_ccache(r, ret);
- } else {
- /* Handled upstairs */
+ if (ret == 0)
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "ISSUE_krb5PrincipalName", "%s", val);
}
+
+ /*
+ * If ret == 0 this will gather the TGT we acquired, else it will acquire
+ * the error we got.
+ */
+ res = get_tgts_accumulate_ccache(r, ret);
+
+ /* Now we "pop" `r->for_cname' */
+ r->for_cname = save_for_cname;
+
+ hx509_xfree(s);
return res;
}
@@ -2161,39 +2242,63 @@ get_tgts(struct bx509_request_desc *r)
r->error_code = 0;
res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
get_tgt_param_cb, r);
- if (r->response || res == MHD_NO)
+ if (r->response || res == MHD_NO) {
+ krb5_free_principal(r->context, p);
return res;
+ }
ret = r->error_code;
}
if (ret == 0) {
- /* Authorize requested client principal names (calls bad_req()) */
+ /*
+ * Check authorization of the authenticated client to the requested
+ * client principal names (calls bad_req()).
+ */
r->error_code = 0;
res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
get_tgts_param_authorize_cb, r);
- if (r->response || res == MHD_NO)
+ if (r->response || res == MHD_NO) {
+ krb5_free_principal(r->context, p);
return res;
+ }
ret = r->error_code;
if (ret == 0) {
+ /* Use the same configuration as /get-tgt (or should we?) */
ret = kdc_authorize_csr(r->context, "get-tgt", r->req, p);
+
+ /*
+ * We tolerate EACCES because we support partial approval.
+ *
+ * (KRB5_PLUGIN_NO_HANDLE means no plugin handled the authorization
+ * check.)
+ */
+ if (ret == EACCES || ret == KRB5_PLUGIN_NO_HANDLE)
+ ret = 0;
if (ret) {
krb5_free_principal(r->context, p);
return bad_403(r, ret, "Permission denied");
}
}
- hx509_request_free(&r->req);
}
if (ret == 0) {
- /* get_tgts_param_execute_cb() calls bad_req() */
+ /*
+ * Get the actual TGTs that were authorized.
+ *
+ * get_tgts_param_execute_cb() calls bad_req()
+ */
r->error_code = 0;
res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
get_tgts_param_execute_cb, r);
- if (r->response || res == MHD_NO)
+ if (r->response || res == MHD_NO) {
+ krb5_free_principal(r->context, p);
return res;
+ }
ret = r->error_code;
}
krb5_free_principal(r->context, p);
+ hx509_request_free(&r->req);
+ r->req = NULL;
/*
* get_tgts_param_execute_cb() will write its JSON response to the file
@@ -2322,7 +2427,7 @@ make_csrf_token(struct bx509_request_desc *r,
if (ret == 0 && data.length > INT_MAX)
ret = ERANGE;
if (ret == 0 &&
- (dlen = rk_base64_encode(data.data, data.length, token)) < 0)
+ rk_base64_encode(data.data, data.length, token) < 0)
ret = errno;
krb5_storage_free(sp);
krb5_data_free(&data);
@@ -2420,6 +2525,7 @@ ip(void *cls,
if (ftl == NULL || keydup == NULL || valdup == NULL) {
free(ftl);
free(keydup);
+ free(valdup);
return MHD_NO;
}
ftl->freeme1 = keydup;
@@ -2494,7 +2600,7 @@ route(void *cls,
* possibly multiple times.
*/
if ((ret = set_req_desc(connection, method, url, &r)))
- return bad_503(r, ret, "Could not initialize request state");
+ return MHD_NO;
*ctx = r;
/* All requests other than /health require authentication */
@@ -2816,6 +2922,7 @@ main(int argc, char **argv)
err(1, "Could not init krb5 context");
bx509_openlog(context, "bx509d", &logfac);
+ krb5_set_log_dest(context, logfac);
load_plugins(context);
if (allow_GET_flag == -1)
diff --git a/third_party/heimdal/kdc/fast.c b/third_party/heimdal/kdc/fast.c
index 392fc966050..e6c523ced95 100644
--- a/third_party/heimdal/kdc/fast.c
+++ b/third_party/heimdal/kdc/fast.c
@@ -605,9 +605,11 @@ fast_unwrap_request(astgs_request_t r,
ticket = tgs_ticket;
}
- krb5_unparse_name(r->context, ticket->client, &armor_client_principal_name);
+ (void) krb5_unparse_name(r->context, ticket->client, &armor_client_principal_name);
kdc_audit_addkv((kdc_request_t)r, 0, "armor_client_name", "%s",
- armor_client_principal_name ? armor_client_principal_name : "<unknown>");
+ armor_client_principal_name ?
+ armor_client_principal_name :
+ "<out of memory>");
if (ac->remote_subkey == NULL) {
krb5_auth_con_free(r->context, ac);
diff --git a/third_party/heimdal/kdc/hpropd.c b/third_party/heimdal/kdc/hpropd.c
index fa06a1fd401..255d60949cc 100644
--- a/third_party/heimdal/kdc/hpropd.c
+++ b/third_party/heimdal/kdc/hpropd.c
@@ -78,7 +78,7 @@ main(int argc, char **argv)
krb5_socket_t sock = rk_INVALID_SOCKET;
HDB *db = NULL;
int optidx = 0;
- char *tmp_db;
+ char *tmp_db = NULL;
krb5_log_facility *fac;
int nprincs;
@@ -208,20 +208,15 @@ main(int argc, char **argv)
krb5_err(context, 1, ret, "krb5_kt_close");
}
- if (!print_dump) {
- int aret;
+ if (asprintf(&tmp_db, "%s~", database) < 0 || tmp_db == NULL)
+ krb5_errx(context, 1, "hdb_create: out of memory");
- aret = asprintf(&tmp_db, "%s~", database);
- if (aret == -1)
- krb5_errx(context, 1, "hdb_create: out of memory");
-
- ret = hdb_create(context, &db, tmp_db);
- if (ret)
- krb5_err(context, 1, ret, "hdb_create(%s)", tmp_db);
- ret = db->hdb_open(context, db, O_RDWR | O_CREAT | O_TRUNC, 0600);
- if (ret)
- krb5_err(context, 1, ret, "hdb_open(%s)", tmp_db);
- }
+ ret = hdb_create(context, &db, tmp_db);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_create(%s)", tmp_db);
+ ret = db->hdb_open(context, db, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_open(%s)", tmp_db);
nprincs = 0;
while (1){
@@ -244,14 +239,6 @@ main(int argc, char **argv)
data.length = 0;
krb5_write_priv_message(context, ac, &sock, &data);
}
- if (!print_dump) {
- ret = db->hdb_close(context, db);
- if (ret)
- krb5_err(context, 1, ret, "db_close");
- ret = db->hdb_rename(context, db, database);
- if (ret)
- krb5_err(context, 1, ret, "db_rename");
- }
break;
}
memset(&entry, 0, sizeof(entry));
@@ -284,6 +271,13 @@ main(int argc, char **argv)
if (!print_dump)
krb5_log(context, fac, 0, "Received %d principals", nprincs);
+ ret = db->hdb_close(context, db);
+ if (ret)
+ krb5_err(context, 1, ret, "db_close");
+ ret = db->hdb_rename(context, db, database);
+ if (ret)
+ krb5_err(context, 1, ret, "db_rename");
+
if (inetd_flag == 0)
rk_closesocket(sock);
diff --git a/third_party/heimdal/kdc/httpkadmind.c b/third_party/heimdal/kdc/httpkadmind.c
index 068b5acbf90..3b57539cf4b 100644
--- a/third_party/heimdal/kdc/httpkadmind.c
+++ b/third_party/heimdal/kdc/httpkadmind.c
@@ -398,9 +398,6 @@ get_kadm_handle(krb5_context context,
set_conf(conf, realm, want_realm, KADM5_CONFIG_REALM);
set_conf(conf, dbname, hdb, KADM5_CONFIG_DBNAME);
set_conf(conf, stash_file, stash_file, KADM5_CONFIG_STASH_FILE);
- set_conf(conf, admin_server, writable_kadmin_server, KADM5_CONFIG_ADMIN_SERVER);
- set_conf(conf, readonly_admin_server, kadmin_server,
- KADM5_CONFIG_READONLY_ADMIN_SERVER);
/*
* If we have a local HDB we'll use it if we can. If the local HDB is
@@ -425,6 +422,11 @@ get_kadm_handle(krb5_context context,
*
* Note that kadmin_client_keytab can be an HDB: or HDBGET: keytab.
*/
+ if (writable_kadmin_server)
+ set_conf(conf, admin_server, writable_kadmin_server, KADM5_CONFIG_ADMIN_SERVER);
+ if (kadmin_server)
+ set_conf(conf, readonly_admin_server, kadmin_server,
+ KADM5_CONFIG_READONLY_ADMIN_SERVER);
ret = kadm5_c_init_with_skey_ctx(context,
kadmin_client_name,
kadmin_client_keytab,
@@ -761,13 +763,11 @@ bad_reqv(kadmin_request_desc r,
char *formatted = NULL;
char *msg = NULL;
- if (r && r->context)
- context = r->context;
- if (r && r->hcontext && r->kv)
+ context = r->context;
+ if (r->hcontext && r->kv)
heim_audit_setkv_number((heim_svc_req_desc)r, "http-status-code",
http_status_code);
- if (r)
- (void) gettimeofday(&r->tv_end, NULL);
+ (void) gettimeofday(&r->tv_end, NULL);
if (code == ENOMEM) {
if (context)
krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
@@ -790,7 +790,7 @@ bad_reqv(kadmin_request_desc r,
msg = formatted;
formatted = NULL;
}
- if (r && r->hcontext)
+ if (r->hcontext)
heim_audit_addreason((heim_svc_req_desc)r, "%s", formatted);
krb5_free_error_message(context, k5msg);
@@ -2034,7 +2034,7 @@ make_csrf_token(kadmin_request_desc r,
if (ret == 0 && data.length > INT_MAX)
ret = ERANGE;
if (ret == 0 &&
- (dlen = rk_base64_encode(data.data, data.length, token)) < 0)
+ rk_base64_encode(data.data, data.length, token) < 0)
ret = errno;
krb5_storage_free(sp);
krb5_data_free(&data);
@@ -2139,6 +2139,7 @@ ip(void *cls,
if (ftl == NULL || keydup == NULL || valdup == NULL) {
free(ftl);
free(keydup);
+ free(valdup);
return MHD_NO;
}
ftl->freeme1 = keydup;
@@ -2186,11 +2187,8 @@ route(void *cls,
* handling a POST then we'll also get called with upload_data != NULL,
* possibly multiple times.
*/
- if ((ret = set_req_desc(connection, method, url, &r))) {
- return
- bad_503(r, ret, "Could not initialize request state") == -1
- ? MHD_NO : MHD_YES;
- }
+ if ((ret = set_req_desc(connection, method, url, &r)))
+ return MHD_NO;
*ctx = r;
/*
@@ -2512,6 +2510,11 @@ main(int argc, char **argv)
if (port < 0)
errx(1, "Port number must be given");
+ if (writable_kadmin_server == NULL && kadmin_server == NULL &&
+ !local_hdb && !local_hdb_read_only)
+ errx(1, "One of --local or --local-read-only must be given, or a "
+ "remote kadmind must be given");
+
if (audiences.num_strings == 0) {
char localhost[MAXHOSTNAMELEN];
diff --git a/third_party/heimdal/kdc/ipc_csr_authorizer.c b/third_party/heimdal/kdc/ipc_csr_authorizer.c
index 7d77e7f812a..86717f6f9e0 100644
--- a/third_party/heimdal/kdc/ipc_csr_authorizer.c
+++ b/third_party/heimdal/kdc/ipc_csr_authorizer.c
@@ -197,8 +197,189 @@ cmd_append(struct rk_strpool **cmd, const char *s0, ...)
return ret;
}
+/* Like strpbrk(), but from the end of the string */
+static char *
+strrpbrk(char *s, const char *accept)
+{
+ char *last = NULL;
+ char *p = s;
+
+ do {
+ p = strpbrk(p, accept);
+ if (p != NULL) {
+ last = p;
+ p++;
+ }
+ } while (p != NULL);
+ return last;
+}
+
+/*
+ * For /get-tgts we need to support partial authorization of requests. The
+ * hx509_request APIs support that.
+ *
+ * Here we just step through the IPC server's response and mark the
+ * corresponding request elements authorized so that /get-tgts can issue or not
+ * issue TGTs according to which requested principals are authorized and which
+ * are not.
+ */
static int
-call_svc(krb5_context context, heim_ipc ipc, const char *cmd)
+mark_piecemeal_authorized(krb5_context context,
+ hx509_request csr,
+ heim_octet_string *rep)
+{
+ size_t san_idx = 0;
+ size_t eku_idx = 0;
+ char *s, *p, *rep2, *tok, *next = NULL;
+ int slow_path = 0;
+ int partial = 0;
+ int ret = 0;
+
+ /* We have a data, but we want a C string */
+ if ((rep2 = strndup(rep->data, rep->length)) == NULL)
+ return krb5_enomem(context);
+
+ /* The first token should be "denied"; skip it */
+ if ((s = strchr(rep2, ' ')) == NULL) {
+ free(rep2);
+ return EACCES;
+ }
+ s++;
+
+ while ((tok = strtok_r(s, ",", &next))) {
+ hx509_san_type san_type, san_type2;
+ char *s2 = NULL;
+
+ s = NULL; /* for strtok_r() */
+
+ if (strncmp(tok, "eku=", sizeof("eku=") -1) == 0) {
+ /*
+ * Very simplistic handling of partial authz for EKUs:
+ *
+ * - denial of an EKU -> deny the whole request
+ * - else below mark all EKUs approved
+ */
+ if (strstr(tok, ":denied")) {
+ krb5_set_error_message(context, EACCES, "CSR denied because "
+ "EKU denied: %s", tok);
+ ret = EACCES;
+ break;
+ }
+ continue;
+ }
+
+ /*
+ * For SANs we check that the nth SAN in the response matches the nth
+ * SAN in the hx509_request.
+ */
+
+ if (strncmp(tok, "san_pkinit=", sizeof("san_pkinit=") - 1) == 0) {
+ tok += sizeof("san_pkinit=") - 1;
+ san_type = HX509_SAN_TYPE_PKINIT;
+ } else if (strncmp(tok, "san_dnsname=", sizeof("san_dnsname=") -1) == 0) {
+ tok += sizeof("san_dnsname=") - 1;
+ san_type = HX509_SAN_TYPE_DNSNAME;
+ } else if (strncmp(tok, "san_email=", sizeof("san_email=") -1) == 0) {
+ tok += sizeof("san_email=") - 1;
+ san_type = HX509_SAN_TYPE_EMAIL;
+ } else if (strncmp(tok, "san_xmpp=", sizeof("san_xmpp=") -1) == 0) {
+ tok += sizeof("san_xmpp=") - 1;
+ san_type = HX509_SAN_TYPE_XMPP;
+ } else if (strncmp(tok, "san_ms_upn=", sizeof("san_ms_upn=") -1) == 0) {
+ tok += sizeof("san_ms_upn=") - 1;
+ san_type = HX509_SAN_TYPE_MS_UPN;
+ } else {
+ krb5_set_error_message(context, EACCES, "CSR denied because could "
+ "not parse token in response: %s", tok);
+ ret = EACCES;
+ break;
+ }
+
+ /*
+ * This token has to end in ":granted" or ":denied". Using our
+ * `strrpbrk()' means we can deal with principals names that have ':'
+ * in them.
+ */
+ if ((p = strrpbrk(tok, ":")) == NULL) {
+ san_idx++;
+ continue;
+ }
+ *(p++) = '\0';
+
+ /* Now we get the nth SAN from the authorization */
+ ret = hx509_request_get_san(csr, san_idx, &san_type2, &s2);
+ if (ret == HX509_NO_ITEM) {
+ /* See below */
+ slow_path = 1;
+ break;
+ }
+
+ /* And we check that it matches the SAN in this token */
+ if (ret == 0) {
+ if (san_type != san_type2 ||
+ strcmp(tok, s2) != 0) {
+ /*
+ * We expect the tokens in the reply to be in the same order as
+ * in the request. If not, we must take a slow path where we
+ * have to sort requests and responses then iterate them in
+ * order.
+ */
+ slow_path = 1;
+ hx509_xfree(s2);
+ break;
+ }
+ hx509_xfree(s2);
+
+ if (strcmp(p, "granted") == 0) {
+ ret = hx509_request_authorize_san(csr, san_idx);
+ } else {
+ partial = 1;
+ ret = hx509_request_reject_san(csr, san_idx);
+ }
+ if (ret)
+ break;
+ }
+ san_idx++;
+ }
+
+ if (slow_path) {
+ /*
+ * FIXME? Implement the slow path?
+ *
+ * Basically, we'd get all the SANs from the request into an array of
+ * {SAN, index} and sort that array, then all the SANs from the
+ * response into an array and sort it, then step a cursor through both,
+ * using the index from the first to mark SANs in the request
+ * authorized or rejected.
+ */
+ krb5_set_error_message(context, EACCES, "CSR denied because "
+ "authorizer service did not include all "
+ "piecemeal grants/denials in order");
+ ret = EACCES;
+ }
+
+ /* Mark all the EKUs authorized */
+ for (eku_idx = 0; ret == 0; eku_idx++)
+ ret = hx509_request_authorize_eku(csr, eku_idx);
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+ if (ret == 0 && partial) {
+ krb5_set_error_message(context, EACCES, "CSR partially authorized");
+ ret = EACCES;
+ }
+
+ free(rep2);
+ return ret;
+}
+
+static krb5_error_code mark_authorized(hx509_request);
+
+static int
+call_svc(krb5_context context,
+ heim_ipc ipc,
+ hx509_request csr,
+ const char *cmd,
+ int piecemeal_check_ok)
{
heim_octet_string req, resp;
int ret;
@@ -207,40 +388,66 @@ call_svc(krb5_context context, heim_ipc ipc, const char *cmd)
req.length = strlen(cmd);
resp.length = 0;
resp.data = NULL;
- if ((ret = heim_ipc_call(ipc, &req, &resp, NULL))) {
- if (resp.length && resp.length < INT_MAX) {
- krb5_set_error_message(context, ret, "CSR denied: %.*s",
- (int)resp.length, (const char *)resp.data);
- ret = EACCES;
- } else {
- krb5_set_error_message(context, EACCES, "CSR denied because could "
- "not reach CSR authorizer IPC service");
+ ret = heim_ipc_call(ipc, &req, &resp, NULL);
+
+ /* Check for all granted case */
+ if (ret == 0 &&
+ resp.length == sizeof("granted") - 1 &&
+ strncasecmp(resp.data, "granted", sizeof("granted") - 1) == 0) {
+ free(resp.data);
+ return mark_authorized(csr); /* Full approval */
+ }
+
+ /* Check for "denied ..." piecemeal authorization case */
+ if ((ret == 0 || ret == EACCES || ret == KRB5_PLUGIN_NO_HANDLE) &&
+ piecemeal_check_ok &&
+ resp.length > sizeof("denied") - 1 &&
+ strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
+ /* Piecemeal authorization */
+ ret = mark_piecemeal_authorized(context, csr, &resp);
+
+ /* mark_piecemeal_authorized() should return EACCES; just in case: */
+ if (ret == 0)
ret = EACCES;
- }
+ free(resp.data);
return ret;
}
+
+ /* All other failure cases */
+
if (resp.data == NULL || resp.length == 0) {
- free(resp.data);
krb5_set_error_message(context, ret, "CSR authorizer IPC service "
"failed silently");
+ free(resp.data);
return EACCES;
}
+
+ if (resp.length == sizeof("ignore") - 1 &&
+ strncasecmp(resp.data, "ignore", sizeof("ignore") - 1) == 0) {
+ /*
+ * In this case the server is saying "I can't handle this request, try
+ * some other authorizer plugin".
+ */
+ free(resp.data);
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
if (resp.length == sizeof("denied") - 1 &&
strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
- free(resp.data);
krb5_set_error_message(context, ret, "CSR authorizer rejected %s",
cmd);
- return EACCES;
- }
- if (resp.length == sizeof("granted") - 1 &&
- strncasecmp(resp.data, "granted", sizeof("granted") - 1) == 0) {
free(resp.data);
- return 0;
+ return EACCES;
}
- krb5_set_error_message(context, ret, "CSR authorizer failed %s: %.*s",
- cmd, resp.length < INT_MAX ? (int)resp.length : 0,
- resp.data);
- return EACCES;
+
+ if (resp.length > INT_MAX)
+ krb5_set_error_message(context, ret, "CSR authorizer rejected %s", cmd);
+ else
+ krb5_set_error_message(context, ret, "CSR authorizer rejected %s: %.*s",
+ cmd, resp.length, resp.data);
+
+ free(resp.data);
+ return ret;
}
static void
@@ -294,13 +501,25 @@ authorize(void *ctx,
char *princ = NULL;
char *s = NULL;
int do_check = 0;
+ int piecemeal_check_ok = 1;
- if ((svc = krb5_config_get_string(context, NULL, app ? app : "kdc",
- "ipc_csr_authorizer", "service", NULL))
- == NULL)
+ if ((svc = krb5_config_get_string_default(context, NULL,
+ "ANY:org.h5l.csr_authorizer",
+ app ? app : "kdc",
+ "ipc_csr_authorizer", "service",
+ NULL)) == NULL)
return KRB5_PLUGIN_NO_HANDLE;
if ((ret = heim_ipc_init_context(svc, &ipc))) {
+ /*
+ * If the IPC authorizer is optional, then fallback on whatever is
+ * next.
+ */
+ if (krb5_config_get_bool_default(context, NULL, FALSE,
+ app ? app : "kdc",
+ "ipc_csr_authorizer", "optional",
+ NULL))
+ return KRB5_PLUGIN_NO_HANDLE;
krb5_set_error_message(context, ret, "Could not set up IPC client "
"end-point for service %s", svc);
return ret;
@@ -318,10 +537,22 @@ authorize(void *ctx,
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
+ size_t p;
ret = hx509_request_get_san(csr, i, &san_type, &s);
if (ret)
break;
+
+ /*
+ * We cannot do a piecemeal check if any of the SANs could make the
+ * response ambiguous.
+ */
+ p = strcspn(s, ",= ");
+ if (s[p] != '\0')
+ piecemeal_check_ok = 0;
+ if (piecemeal_check_ok && strstr(s, ":granted") != NULL)
+ piecemeal_check_ok = 0;
+
switch (san_type) {
case HX509_SAN_TYPE_EMAIL:
if ((ret = cmd_append(&cmd, " san_email=", s, NULL)))
@@ -380,15 +611,13 @@ authorize(void *ctx,
hx509_request_authorize_ku(csr, ku);
if (do_check) {
- if ((s = rk_strpoolcollect(cmd)) == NULL)
- goto enomem;
+ s = rk_strpoolcollect(cmd);
cmd = NULL;
- if ((ret = call_svc(context, ipc, s)))
+ if (s == NULL)
+ goto enomem;
+ if ((ret = call_svc(context, ipc, csr, s, piecemeal_check_ok)))
goto out;
- } /* else -> permit */
-
- if ((ret = mark_authorized(csr)))
- goto out;
+ } /* else there was nothing to check -> permit */
*result = TRUE;
ret = 0;
diff --git a/third_party/heimdal/kdc/kdc-plugin.c b/third_party/heimdal/kdc/kdc-plugin.c
index 925c250597a..c575d7df479 100644
--- a/third_party/heimdal/kdc/kdc-plugin.c
+++ b/third_party/heimdal/kdc/kdc-plugin.c
@@ -51,7 +51,7 @@ static const char *kdc_plugin_deps[] = {
static struct heim_plugin_data kdc_plugin_data = {
"krb5",
"kdc",
- KRB5_PLUGIN_KDC_VERSION_10,
+ KRB5_PLUGIN_KDC_VERSION_11,
kdc_plugin_deps,
kdc_get_instance
};
@@ -145,7 +145,8 @@ struct verify_uc {
hdb_entry *client;
hdb_entry *server;
hdb_entry *krbtgt;
- krb5_pac *pac;
+ krb5_pac pac;
+ krb5_boolean *is_trusted;
};
static krb5_error_code KRB5_LIB_CALL
@@ -162,7 +163,8 @@ verify(krb5_context context, const void *plug, void *plugctx, void *userctx)
uc->r,
uc->client_principal,
uc->delegated_proxy_principal,
- uc->client, uc->server, uc->krbtgt, uc->pac);
+ uc->client, uc->server, uc->krbtgt, uc->pac,
+ uc->is_trusted);
return ret;
}
@@ -173,7 +175,8 @@ _kdc_pac_verify(astgs_request_t r,
hdb_entry *client,
hdb_entry *server,
hdb_entry *krbtgt,
- krb5_pac *pac)
+ krb5_pac pac,
+ krb5_boolean *is_trusted)
{
struct verify_uc uc;
@@ -187,11 +190,66 @@ _kdc_pac_verify(astgs_request_t r,
uc.server = server;
uc.krbtgt = krbtgt;
uc.pac = pac;
+ uc.is_trusted = is_trusted;
return _krb5_plugin_run_f(r->context, &kdc_plugin_data,
0, &uc, verify);
}
+struct update_uc {
+ astgs_request_t r;
+ krb5_principal client_principal;
+ krb5_principal delegated_proxy_principal;
+ hdb_entry *client;
+ hdb_entry *server;
+ hdb_entry *krbtgt;
+ krb5_pac *pac;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+update(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+ struct update_uc *uc = (struct update_uc *)userctx;
+ krb5_error_code ret;
+
+ if (ft->pac_update == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ ret = ft->pac_update((void *)plug,
+ uc->r,
+ uc->client_principal,
+ uc->delegated_proxy_principal,
+ uc->client, uc->server, uc->krbtgt, uc->pac);
+ return ret;
+}
+
+krb5_error_code
+_kdc_pac_update(astgs_request_t r,
+ const krb5_principal client_principal,
+ const krb5_principal delegated_proxy_principal,
+ hdb_entry *client,
+ hdb_entry *server,
+ hdb_entry *krbtgt,
+ krb5_pac *pac)
+{
+ struct update_uc uc;
+
+ if (!have_plugin)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ uc.r = r;
+ uc.client_principal = client_principal;
+ uc.delegated_proxy_principal = delegated_proxy_principal;
+ uc.client = client;
+ uc.server = server;
+ uc.krbtgt = krbtgt;
+ uc.pac = pac;
+
+ return _krb5_plugin_run_f(r->context, &kdc_plugin_data,
+ 0, &uc, update);
+}
+
static krb5_error_code KRB5_LIB_CALL
check(krb5_context context, const void *plug, void *plugctx, void *userctx)
{
diff --git a/third_party/heimdal/kdc/kdc-plugin.h b/third_party/heimdal/kdc/kdc-plugin.h
index 05286257bf7..4ec92a575b3 100644
--- a/third_party/heimdal/kdc/kdc-plugin.h
+++ b/third_party/heimdal/kdc/kdc-plugin.h
@@ -57,8 +57,9 @@ typedef krb5_error_code
/*
* Verify the PAC KDC signatures by fetching the appropriate TGS key
- * and calling krb5_pac_verify() with that key. Optionally update the
- * PAC buffers on success.
+ * and calling krb5_pac_verify() with that key. The possibly-NULL
+ * is_trusted may be set by the plugin to indicate that the PAC was
+ * issued by a trusted server, and not, for example, by an RODC.
*/
typedef krb5_error_code
@@ -69,7 +70,25 @@ typedef krb5_error_code
hdb_entry *,/* client */
hdb_entry *,/* server */
hdb_entry *,/* krbtgt */
- krb5_pac *);
+ krb5_pac, /* pac */
+ krb5_boolean *); /* is_trusted */
+
+/*
+ * Update the KDC PAC buffers. This function may be used after verifying the PAC
+ * with a call to krb5plugin_kdc_pac_verify(), and it resembles the latter
+ * function in the parameters it takes. The 'pac' parameter always points to a
+ * non-NULL PAC.
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_pac_update)(void *,
+ astgs_request_t,
+ const krb5_principal, /* new ticket client */
+ const krb5_principal, /* delegation proxy */
+ hdb_entry *,/* client */
+ hdb_entry *,/* server */
+ hdb_entry *,/* krbtgt */
+ krb5_pac *); /* pac */
/*
* Authorize the client principal's access to the Authentication Service (AS).
@@ -117,12 +136,13 @@ typedef krb5_error_code
* Plugins should carefully check API contract notes for changes
* between plugin API versions.
*/
-#define KRB5_PLUGIN_KDC_VERSION_10 10
+#define KRB5_PLUGIN_KDC_VERSION_11 11
typedef struct krb5plugin_kdc_ftable {
HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(krb5_context);
krb5plugin_kdc_pac_generate pac_generate;
krb5plugin_kdc_pac_verify pac_verify;
+ krb5plugin_kdc_pac_update pac_update;
krb5plugin_kdc_client_access client_access;
krb5plugin_kdc_referral_policy referral_policy;
krb5plugin_kdc_finalize_reply finalize_reply;
diff --git a/third_party/heimdal/kdc/kdc-tester.c b/third_party/heimdal/kdc/kdc-tester.c
index 65d52ec66a4..beb9e1f4a23 100644
--- a/third_party/heimdal/kdc/kdc-tester.c
+++ b/third_party/heimdal/kdc/kdc-tester.c
@@ -195,11 +195,14 @@ copy_keytab(krb5_context context, krb5_keytab from, krb5_keytab to)
ret = krb5_kt_start_seq_get(context, from, &cursor);
if (ret)
return ret;
- while((ret = krb5_kt_next_entry(context, from, &entry, &cursor)) == 0){
+ while ((ret = krb5_kt_next_entry(context, from, &entry, &cursor)) == 0){
krb5_kt_add_entry(context, to, &entry);
krb5_kt_free_entry(context, &entry);
}
- return krb5_kt_end_seq_get(context, from, &cursor);
+ (void) krb5_kt_end_seq_get(context, from, &cursor);
+ if (ret == KRB5_KT_END)
+ return 0;
+ return ret;
}
/*
diff --git a/third_party/heimdal/kdc/kerberos5.c b/third_party/heimdal/kdc/kerberos5.c
index e75686c625a..ecca52cdcdd 100644
--- a/third_party/heimdal/kdc/kerberos5.c
+++ b/third_party/heimdal/kdc/kerberos5.c
@@ -2079,13 +2079,11 @@ get_local_tgs(krb5_context context,
KRB5_TGS_NAME,
realm,
NULL);
- if (ret)
- return ret;
+ if (ret == 0)
+ ret = _kdc_db_fetch(context, config, tgs_name,
+ HDB_F_GET_KRBTGT, NULL, krbtgtdb, krbtgt);
- ret = _kdc_db_fetch(context, config, tgs_name,
- HDB_F_GET_KRBTGT, NULL, krbtgtdb, krbtgt);
krb5_free_principal(context, tgs_name);
-
return ret;
}
diff --git a/third_party/heimdal/kdc/krb5tgs.c b/third_party/heimdal/kdc/krb5tgs.c
index 71991c17975..0bad42aa3b7 100644
--- a/third_party/heimdal/kdc/krb5tgs.c
+++ b/third_party/heimdal/kdc/krb5tgs.c
@@ -96,6 +96,7 @@ _kdc_check_pac(astgs_request_t r,
krb5_pac pac = NULL;
krb5_error_code ret;
krb5_boolean signedticket;
+ krb5_boolean is_trusted = FALSE;
*kdc_issued = FALSE;
*ppac = NULL;
@@ -122,32 +123,25 @@ _kdc_check_pac(astgs_request_t r,
return ret;
}
- if (pac_canon_name) {
- ret = _krb5_pac_get_canon_principal(context, pac, pac_canon_name);
- if (ret && ret != ENOENT) {
- krb5_pac_free(context, pac);
- return ret;
- }
- }
- if (pac_attributes) {
- ret = _krb5_pac_get_attributes_info(context, pac, pac_attributes);
- if (ret && ret != ENOENT) {
- krb5_pac_free(context, pac);
- return ret;
- }
- if (ret == ENOENT)
- *pac_attributes = KRB5_PAC_WAS_GIVEN_IMPLICITLY;
- }
-
/* Verify the KDC signatures. */
ret = _kdc_pac_verify(r,
client_principal, delegated_proxy_principal,
- client, server, krbtgt, &pac);
+ client, server, krbtgt, pac, &is_trusted);
if (ret == 0) {
- if (pac == NULL) {
- /* the plugin may indicate no PAC should be generated */
- *pac_attributes = 0;
+ if (is_trusted) {
+ krb5_pac_set_trusted(pac, true);
}
+
+ if (pac_canon_name) {
+ ret = _krb5_pac_get_canon_principal(context, pac, pac_canon_name);
+ if (ret && ret != ENOENT) {
+ krb5_pac_free(context, pac);
+ return ret;
+ }
+ }
+ if (pac_attributes &&
+ _krb5_pac_get_attributes_info(context, pac, pac_attributes) != 0)
+ *pac_attributes = KRB5_PAC_WAS_GIVEN_IMPLICITLY;
} else if (ret == KRB5_PLUGIN_NO_HANDLE) {
/*
* We can't verify the KDC signatures if the ticket was issued by
@@ -163,6 +157,17 @@ _kdc_check_pac(astgs_request_t r,
}
}
+ if (pac_canon_name) {
+ ret = _krb5_pac_get_canon_principal(context, pac, pac_canon_name);
+ if (ret && ret != ENOENT) {
+ krb5_pac_free(context, pac);
+ return ret;
+ }
+ }
+ if (pac_attributes &&
+ _krb5_pac_get_attributes_info(context, pac, pac_attributes) != 0)
+ *pac_attributes = KRB5_PAC_WAS_GIVEN_IMPLICITLY;
+
/* Discard the PAC if the plugin didn't handle it */
krb5_pac_free(context, pac);
ret = krb5_pac_init(context, &pac);
@@ -1051,8 +1056,9 @@ next_kvno:
} else if (ret) {
char *str = NULL, *p = NULL;
- krb5_enctype_to_string(r->context, ap_req.ticket.enc_part.etype, &str);
- krb5_unparse_name(r->context, princ, &p);
+ /* We should implement the MIT `trace_format()' concept */
+ (void) krb5_enctype_to_string(r->context, ap_req.ticket.enc_part.etype, &str);
+ (void) krb5_unparse_name(r->context, princ, &p);
kdc_log(r->context, config, 4,
"No server key with enctype %s found for %s",
str ? str : "<unknown enctype>",
@@ -1327,6 +1333,7 @@ _kdc_db_fetch_client(krb5_context context,
krb5_error_code ret;
hdb_entry *client = NULL;
+ *clientdb = NULL;
*client_out = NULL;
ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT | flags,
@@ -1382,7 +1389,7 @@ tgs_build_reply(astgs_request_t priv,
char *user2user_name = NULL;
HDB *user2user_krbtgtdb;
hdb_entry *user2user_krbtgt = NULL;
- HDB *clientdb;
+ HDB *clientdb = NULL;
HDB *serverdb = NULL;
krb5_realm ref_realm = NULL;
EncTicketPart *tgt = &priv->ticket->ticket;
@@ -1896,9 +1903,16 @@ server_lookup:
cpn, our_realm, &clientdb, &priv->client);
if (ret)
goto out;
- flags &= ~HDB_F_SYNTHETIC_OK;
+ /* flags &= ~HDB_F_SYNTHETIC_OK; */ /* `flags' is not used again below */
priv->clientdb = clientdb;
+ /* Validate armor TGT before potentially including device claims */
+ if (priv->armor_ticket) {
+ ret = _kdc_fast_check_armor_pac(priv);
+ if (ret)
+ goto out;
+ }
+
ret = _kdc_check_pac(priv, priv->client_princ, NULL,
priv->client, priv->server,
priv->krbtgt, priv->krbtgt,
@@ -1915,6 +1929,29 @@ server_lookup:
goto out;
}
+ if (priv->pac != NULL) {
+ ret = _kdc_pac_update(priv, priv->client_princ, NULL,
+ priv->client, priv->server, priv->krbtgt,
+ &priv->pac);
+ if (ret == KRB5_PLUGIN_NO_HANDLE) {
+ ret = 0;
+ }
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_audit_addreason((kdc_request_t)priv, "PAC update failed");
+ kdc_log(context, config, 4,
+ "Update PAC failed for %s (%s) from %s with %s",
+ spn, cpn, from, msg);
+ krb5_free_error_message(context, msg);
+ goto out;
+ }
+
+ if (priv->pac == NULL) {
+ /* the plugin may indicate no PAC should be generated */
+ priv->pac_attributes = 0;
+ }
+ }
+
/*
* Process request
*/
@@ -2011,13 +2048,6 @@ server_lookup:
if (kdc_issued &&
!krb5_principal_is_krbtgt(context, priv->server->principal)) {
- /* Validate armor TGT before potentially including device claims */
- if (priv->armor_ticket) {
- ret = _kdc_fast_check_armor_pac(priv);
- if (ret)
- goto out;
- }
-
add_ticket_sig = TRUE;
}
diff --git a/third_party/heimdal/kdc/misc.c b/third_party/heimdal/kdc/misc.c
index eab8107935f..477e4fabfb2 100644
--- a/third_party/heimdal/kdc/misc.c
+++ b/third_party/heimdal/kdc/misc.c
@@ -139,6 +139,8 @@ _kdc_db_fetch(krb5_context context,
krb5_const_principal princ;
*h = NULL;
+ if (db)
+ *db = NULL;
if (!name_type_ok(context, config, principal))
return HDB_ERR_NOENTRY;
diff --git a/third_party/heimdal/kdc/mit_dump.c b/third_party/heimdal/kdc/mit_dump.c
index 32cf5dc65ce..af380bbe378 100644
--- a/third_party/heimdal/kdc/mit_dump.c
+++ b/third_party/heimdal/kdc/mit_dump.c
@@ -104,7 +104,7 @@ my_fgetln(FILE *f, char **bufp, size_t *szp, size_t *lenp)
size_t len;
size_t sz = *szp;
char *buf = *bufp;
- char *p, *n;
+ char *n;
if (!buf) {
buf = malloc(sz ? sz : 8192);
@@ -115,7 +115,7 @@ my_fgetln(FILE *f, char **bufp, size_t *szp, size_t *lenp)
}
len = 0;
- while ((p = fgets(&buf[len], sz-len, f)) != NULL) {
+ while (fgets(&buf[len], sz-len, f) != NULL) {
len += strlen(&buf[len]);
if (buf[len-1] == '\n')
break;
diff --git a/third_party/heimdal/kdc/mssfu.c b/third_party/heimdal/kdc/mssfu.c
index fda5a37b1c6..a88de097a9b 100644
--- a/third_party/heimdal/kdc/mssfu.c
+++ b/third_party/heimdal/kdc/mssfu.c
@@ -501,6 +501,26 @@ validate_constrained_delegation(astgs_request_t r)
goto out;
}
+ heim_assert(s4u_pac != NULL, "ad_kdc_issued implies the PAC is non-NULL");
+
+ ret = _kdc_pac_update(r, s4u_client_name, s4u_server_name,
+ s4u_client, r->server, r->krbtgt,
+ &s4u_pac);
+ if (ret == KRB5_PLUGIN_NO_HANDLE) {
+ ret = 0;
+ }
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ kdc_audit_addreason((kdc_request_t)r,
+ "Constrained delegation ticket PAC update failed");
+ kdc_log(r->context, r->config, 4,
+ "Update delegated PAC failed to %s for client"
+ "%s (%s) as %s from %s with %s",
+ r->sname, r->cname, s4usname, s4ucname, r->from, msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
/*
* If the evidence ticket PAC didn't include PAC_UPN_DNS_INFO with
* the canonical client name, but the user is local to our KDC, we
diff --git a/third_party/heimdal/kdc/pkinit-ec.c b/third_party/heimdal/kdc/pkinit-ec.c
index c718aa79962..31a5fe7dec5 100644
--- a/third_party/heimdal/kdc/pkinit-ec.c
+++ b/third_party/heimdal/kdc/pkinit-ec.c
@@ -52,10 +52,16 @@
*/
#ifdef HAVE_HCRYPTO_W_OPENSSL
-#include <openssl/ec.h>
-#include <openssl/ecdh.h>
#include <openssl/evp.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/rsa.h>
#include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/objects.h>
+#ifdef HAVE_OPENSSL_30
+#include <openssl/core_names.h>
+#endif
#define HEIM_NO_CRYPTO_HDRS
#endif /* HAVE_HCRYPTO_W_OPENSSL */
@@ -69,37 +75,101 @@
#include <pkinit_asn1.h>
#include <hx509.h>
-
-#ifdef HAVE_HCRYPTO_W_OPENSSL
-static void
-free_client_ec_param(krb5_context context,
- EC_KEY *ec_key_pk,
- EC_KEY *ec_key_key)
-{
- if (ec_key_pk != NULL)
- EC_KEY_free(ec_key_pk);
- if (ec_key_key != NULL)
- EC_KEY_free(ec_key_key);
-}
-#endif
+#include "../lib/hx509/hx_locl.h"
+#include <hx509-private.h>
void
_kdc_pk_free_client_ec_param(krb5_context context,
- void *ec_key_pk,
- void *ec_key_key)
+ void *k0,
+ void *k1)
{
#ifdef HAVE_HCRYPTO_W_OPENSSL
- free_client_ec_param(context, ec_key_pk, ec_key_key);
+#ifdef HAVE_OPENSSL_30
+ EVP_PKEY_free(k0);
+ EVP_PKEY_free(k1);
+#else
+ EC_KEY_free(k0);
+ EC_KEY_free(k1);
+#endif
#endif
}
#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+static krb5_error_code
+generate_ecdh_keyblock_ossl30(krb5_context context,
+ EVP_PKEY *ec_key_pub, /* the client's public key */
+ EVP_PKEY **ec_key_priv, /* the KDC's ephemeral private */
+ unsigned char **dh_gen_key, /* shared secret */
+ size_t *dh_gen_keylen)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY *ephemeral = NULL;
+ krb5_error_code ret = 0;
+ unsigned char *p = NULL;
+ size_t size = 0;
+
+ if (ec_key_pub == NULL)
+ /* XXX This seems like an internal error that should be impossible */
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Missing client ECDH key agreement public key");
+ if (ret == 0 &&
+ (ephemeral =
+ EVP_EC_gen(OSSL_EC_curve_nid2name(NID_X9_62_prime256v1))) == NULL)
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key");
+ if (ret == 0 &&
+ (pctx = EVP_PKEY_CTX_new(ephemeral, NULL)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 && EVP_PKEY_derive_init(pctx) != 1)
+ ret = krb5_enomem(context);
+ if (ret == 0 &&
+ EVP_PKEY_CTX_set_ecdh_kdf_type(pctx, EVP_PKEY_ECDH_KDF_NONE) != 1)
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_CTX_set_dh_kdf_type)");
+ if (ret == 0 &&
+ EVP_PKEY_derive_set_peer_ex(pctx, ec_key_pub, 1) != 1)
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_derive_set_peer_ex)");
+ if (ret == 0 &&
+ (EVP_PKEY_derive(pctx, NULL, &size) != 1 || size == 0))
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_derive)");
+ if (ret == 0 && (p = malloc(size)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 &&
+ (EVP_PKEY_derive(pctx, p, &size) != 1 || size == 0))
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_derive)");
+
+ if (ret) {
+ EVP_PKEY_free(ephemeral);
+ ephemeral = NULL;
+ free(p);
+ p = NULL;
+ size = 0;
+ }
+
+ *ec_key_priv = ephemeral;
+ *dh_gen_keylen = size;
+ *dh_gen_key = p;
+
+ EVP_PKEY_CTX_free(pctx);
+ return ret;
+}
+#else
+
+/* The empty line above is intentional to work around an mkproto bug */
static krb5_error_code
-generate_ecdh_keyblock(krb5_context context,
- EC_KEY *ec_key_pk, /* the client's public key */
- EC_KEY **ec_key_key, /* the KDC's ephemeral private */
- unsigned char **dh_gen_key, /* shared secret */
- size_t *dh_gen_keylen)
+generate_ecdh_keyblock_ossl11(krb5_context context,
+ EC_KEY *ec_key_pk, /* the client's public key */
+ EC_KEY **ec_key_key, /* the KDC's ephemeral private */
+ unsigned char **dh_gen_key, /* shared secret */
+ size_t *dh_gen_keylen)
{
const EC_GROUP *group;
EC_KEY *ephemeral;
@@ -136,7 +206,7 @@ generate_ecdh_keyblock(krb5_context context,
EC_KEY_set_group(ephemeral, group);
if (EC_KEY_generate_key(ephemeral) != 1) {
- EC_KEY_free(ephemeral);
+ EC_KEY_free(ephemeral);
return krb5_enomem(context);
}
@@ -165,6 +235,7 @@ generate_ecdh_keyblock(krb5_context context,
return 0;
}
+#endif
#endif /* HAVE_HCRYPTO_W_OPENSSL */
krb5_error_code
@@ -175,20 +246,128 @@ _kdc_generate_ecdh_keyblock(krb5_context context,
size_t *dh_gen_keylen)
{
#ifdef HAVE_HCRYPTO_W_OPENSSL
- return generate_ecdh_keyblock(context, ec_key_pk,
- (EC_KEY **)ec_key_key,
- dh_gen_key, dh_gen_keylen);
+#ifdef HAVE_OPENSSL_30
+ return generate_ecdh_keyblock_ossl30(context, ec_key_pk,
+ (EVP_PKEY **)ec_key_key,
+ dh_gen_key, dh_gen_keylen);
+#else
+ return generate_ecdh_keyblock_ossl11(context, ec_key_pk,
+ (EC_KEY **)ec_key_key,
+ dh_gen_key, dh_gen_keylen);
+#endif
#else
return ENOTSUP;
#endif /* HAVE_HCRYPTO_W_OPENSSL */
}
#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
static krb5_error_code
-get_ecdh_param(krb5_context context,
- krb5_kdc_configuration *config,
- SubjectPublicKeyInfo *dh_key_info,
- EC_KEY **out)
+get_ecdh_param_ossl30(krb5_context context,
+ krb5_kdc_configuration *config,
+ SubjectPublicKeyInfo *dh_key_info,
+ EVP_PKEY **out)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY *template = NULL;
+ EVP_PKEY *public = NULL;
+ OSSL_PARAM params[2];
+ krb5_error_code ret = 0;
+ ECParameters ecp;
+ const unsigned char *p;
+ const char *curve_sn = NULL;
+ size_t len;
+ char *curve_sn_dup = NULL;
+ int groupnid = NID_undef;
+
+ /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
+
+ /*
+ * In order for d2i_PublicKey() to work we need to create a template key
+ * that has the curve parameters for the subjectPublicKey.
+ *
+ * Or maybe we could learn to use the OSSL_DECODER(3) API. But this works,
+ * at least until OpenSSL deprecates d2i_PublicKey() and forces us to use
+ * OSSL_DECODER(3).
+ */
+
+ memset(&ecp, 0, sizeof(ecp));
+
+ if (dh_key_info->algorithm.parameters == NULL)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT missing algorithm parameter "
+ "in clientPublicValue");
+ if (ret == 0)
+ ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
+ dh_key_info->algorithm.parameters->length,
+ &ecp, &len);
+ if (ret == 0 && ecp.element != choice_ECParameters_namedCurve)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT client used an unnamed curve");
+ if (ret == 0 &&
+ (groupnid = _hx509_ossl_oid2nid(&ecp.u.namedCurve)) == NID_undef)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT client used an unsupported curve");
+ if (ret == 0 && (curve_sn = OBJ_nid2sn(groupnid)) == NULL)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "Could not resolve curve NID %d to its short name",
+ groupnid);
+ if (ret == 0 && (curve_sn_dup = strdup(curve_sn)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0) {
+ if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) != 0)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT client used an unsupported curve");
+ }
+ if (ret == 0) {
+ /*
+ * Apparently there's no error checking to be done here? Why does
+ * OSSL_PARAM_construct_utf8_string() want a non-const for the value?
+ * Is that a bug in OpenSSL?
+ */
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+ curve_sn_dup, 0);
+ params[1] = OSSL_PARAM_construct_end();
+
+ if ((pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL)
+ ret = krb5_enomem(context);
+ }
+ if (ret == 0 && EVP_PKEY_fromdata_init(pctx) != 1)
+ ret = krb5_enomem(context);
+ if (ret == 0 &&
+ EVP_PKEY_fromdata(pctx, &template, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ params) != 1)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "Could not set up to parse key for curve %s",
+ curve_sn);
+
+ p = dh_key_info->subjectPublicKey.data;
+ len = dh_key_info->subjectPublicKey.length / 8;
+ if (ret == 0 &&
+ (public = d2i_PublicKey(EVP_PKEY_EC, &template, &p, len)) == NULL)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "Could not decode PKINIT client ECDH key");
+
+ if (ret) {
+ EVP_PKEY_free(public);
+ public = NULL;
+ }
+
+ *out = public;
+
+ /* FYI the EVP_PKEY_CTX takes ownership of the `template' key */
+ EVP_PKEY_CTX_free(pctx);
+ free_ECParameters(&ecp);
+ free(curve_sn_dup);
+ return ret;
+}
+#else
+
+static krb5_error_code
+get_ecdh_param_ossl11(krb5_context context,
+ krb5_kdc_configuration *config,
+ SubjectPublicKeyInfo *dh_key_info,
+ EC_KEY **out)
{
ECParameters ecp;
EC_KEY *public = NULL;
@@ -198,30 +377,31 @@ get_ecdh_param(krb5_context context,
int nid;
if (dh_key_info->algorithm.parameters == NULL) {
- krb5_set_error_message(context, KRB5_BADMSGTYPE,
- "PKINIT missing algorithm parameter "
- "in clientPublicValue");
- return KRB5_BADMSGTYPE;
+ krb5_set_error_message(context, KRB5_BADMSGTYPE,
+ "PKINIT missing algorithm parameter "
+ "in clientPublicValue");
+ return KRB5_BADMSGTYPE;
}
+ /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
memset(&ecp, 0, sizeof(ecp));
ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
- dh_key_info->algorithm.parameters->length, &ecp, &len);
+ dh_key_info->algorithm.parameters->length, &ecp, &len);
if (ret)
- goto out;
+ goto out;
if (ecp.element != choice_ECParameters_namedCurve) {
- ret = KRB5_BADMSGTYPE;
- goto out;
+ ret = KRB5_BADMSGTYPE;
+ goto out;
}
if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) == 0)
- nid = NID_X9_62_prime256v1;
+ nid = NID_X9_62_prime256v1;
else {
- ret = KRB5_BADMSGTYPE;
- goto out;
- }
+ ret = KRB5_BADMSGTYPE;
+ goto out;
+ }
/* XXX verify group is ok */
@@ -230,20 +410,21 @@ get_ecdh_param(krb5_context context,
p = dh_key_info->subjectPublicKey.data;
len = dh_key_info->subjectPublicKey.length / 8;
if (o2i_ECPublicKey(&public, &p, len) == NULL) {
- ret = KRB5_BADMSGTYPE;
- krb5_set_error_message(context, ret,
- "PKINIT failed to decode ECDH key");
- goto out;
+ ret = KRB5_BADMSGTYPE;
+ krb5_set_error_message(context, ret,
+ "PKINIT failed to decode ECDH key");
+ goto out;
}
*out = public;
public = NULL;
out:
if (public)
- EC_KEY_free(public);
+ EC_KEY_free(public);
free_ECParameters(&ecp);
return ret;
}
+#endif
#endif /* HAVE_HCRYPTO_W_OPENSSL */
krb5_error_code
@@ -253,7 +434,11 @@ _kdc_get_ecdh_param(krb5_context context,
void **out)
{
#ifdef HAVE_HCRYPTO_W_OPENSSL
- return get_ecdh_param(context, config, dh_key_info, (EC_KEY **)out);
+#ifdef HAVE_OPENSSL_30
+ return get_ecdh_param_ossl30(context, config, dh_key_info, (EVP_PKEY **)out);
+#else
+ return get_ecdh_param_ossl11(context, config, dh_key_info, (EC_KEY **)out);
+#endif
#else
return ENOTSUP;
#endif /* HAVE_HCRYPTO_W_OPENSSL */
@@ -265,13 +450,51 @@ _kdc_get_ecdh_param(krb5_context context,
*/
#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
static krb5_error_code
-serialize_ecdh_key(krb5_context context,
- EC_KEY *key,
- unsigned char **out,
- size_t *out_len)
+serialize_ecdh_key_ossl30(krb5_context context,
+ EVP_PKEY *key,
+ unsigned char **out,
+ size_t *out_len)
+{
+ unsigned char *p;
+ int len;
+
+ *out = NULL;
+ *out_len = 0;
+
+ len = i2d_PublicKey(key, NULL);
+ if (len <= 0) {
+ krb5_set_error_message(context, EOVERFLOW,
+ "PKINIT failed to encode ECDH key");
+ return EOVERFLOW;
+ }
+
+ *out = malloc(len);
+ if (*out == NULL)
+ return krb5_enomem(context);
+
+ p = *out;
+ len = i2d_PublicKey(key, &p);
+ if (len <= 0) {
+ free(*out);
+ *out = NULL;
+ krb5_set_error_message(context, EINVAL /* XXX Better error please */,
+ "PKINIT failed to encode ECDH key");
+ return EINVAL;
+ }
+
+ *out_len = len * 8;
+ return 0;
+}
+#else
+
+static krb5_error_code
+serialize_ecdh_key_ossl11(krb5_context context,
+ EC_KEY *key,
+ unsigned char **out,
+ size_t *out_len)
{
- krb5_error_code ret = 0;
unsigned char *p;
int len;
@@ -279,8 +502,11 @@ serialize_ecdh_key(krb5_context context,
*out_len = 0;
len = i2o_ECPublicKey(key, NULL);
- if (len <= 0)
+ if (len <= 0) {
+ krb5_set_error_message(context, EOVERFLOW,
+ "PKINIT failed to encode ECDH key");
return EOVERFLOW;
+ }
*out = malloc(len);
if (*out == NULL)
@@ -291,16 +517,16 @@ serialize_ecdh_key(krb5_context context,
if (len <= 0) {
free(*out);
*out = NULL;
- ret = EINVAL; /* XXX Better error please */
- krb5_set_error_message(context, ret,
+ krb5_set_error_message(context, EINVAL /* XXX Better error please */,
"PKINIT failed to encode ECDH key");
- return ret;
+ return EINVAL;
}
*out_len = len * 8;
- return ret;
+ return 0;
}
#endif
+#endif
krb5_error_code
_kdc_serialize_ecdh_key(krb5_context context,
@@ -309,7 +535,11 @@ _kdc_serialize_ecdh_key(krb5_context context,
size_t *out_len)
{
#ifdef HAVE_HCRYPTO_W_OPENSSL
- return serialize_ecdh_key(context, key, out, out_len);
+#ifdef HAVE_OPENSSL_30
+ return serialize_ecdh_key_ossl30(context, key, out, out_len);
+#else
+ return serialize_ecdh_key_ossl11(context, key, out, out_len);
+#endif
#else
return ENOTSUP;
#endif
diff --git a/third_party/heimdal/kdc/simple_csr_authorizer.c b/third_party/heimdal/kdc/simple_csr_authorizer.c
deleted file mode 100644
index b46a8931ad3..00000000000
--- a/third_party/heimdal/kdc/simple_csr_authorizer.c
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (c) 2019 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.
- */
-
-/*
- * This plugin authorizes requested certificate SANs and EKUs by checking for
- * existence of files of the form:
- *
- *
- * /<path>/<princ>/<ext>-<value>
- *
- * where <path> is the value of:
- *
- * [kdc] simple_csr_authorizer_directory = PATH
- *
- * <princ> is a requesting client principal name with all characters other than
- * alphanumeric, '-', '_', and non-leading '.' URL-encoded.
- *
- * <ext> is one of:
- *
- * - pkinit (SAN)
- * - xmpp (SAN)
- * - email (SAN)
- * - ms-upn (SAN)
- * - dnsname (SAN)
- * - eku (EKU OID)
- *
- * and <value> is a display form of the SAN or EKU OID, with SANs URL-encoded
- * just like principal names (see above).
- *
- * OIDs are of the form "1.2.3.4.5".
- *
- * Only digitalSignature and nonRepudiation key usage values are permitted.
- */
-#define _GNU_SOURCE 1
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <ctype.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <roken.h>
-#include <krb5.h>
-#include <hx509.h>
-#include <kdc.h>
-#include <common_plugin.h>
-#include <csr_authorizer_plugin.h>
-
-/*
- * string_encode_sz() and string_encode() encode a string to be safe for use as
- * a file name. They function very much like URL encoders, but '~' also gets
- * encoded, and '@', '-', '_', and non-leading '.' do not.
- *
- * A corresponding decoder is not needed.
- */
-static size_t
-string_encode_sz(const char *in)
-{
- size_t sz = strlen(in);
- int first = 1;
-
- while (*in) {
- char c = *(in++);
-
- switch (c) {
- case '@':
- case '-':
- case '_':
- break;
- case '.':
- if (first)
- sz += 2;
- break;
- default:
- if (!isalnum(c))
- sz += 2;
- }
- first = 0;
- }
- return sz;
-}
-
-static char *
-string_encode(const char *in)
-{
- size_t len = strlen(in);
- size_t sz = string_encode_sz(in);
- size_t i, k;
- char *s;
- int first = 1;
-
- if ((s = malloc(sz + 1)) == NULL)
- return NULL;
- s[sz] = '\0';
-
- for (i = k = 0; i < len; i++, first = 0) {
- unsigned char c = ((const unsigned char *)in)[i];
-
- switch (c) {
- case '@':
- case '-':
- case '_':
- s[k++] = c;
- break;
- case '.':
- if (first) {
- s[k++] = '%';
- s[k++] = "0123456789abcdef"[(c&0xff)>>4];
- s[k++] = "0123456789abcdef"[(c&0x0f)];
- } else {
- s[k++] = c;
- }
- break;
- default:
- if (isalnum(c)) {
- s[k++] = c;
- } else {
- s[k++] = '%';
- s[k++] = "0123456789abcdef"[(c&0xff)>>4];
- s[k++] = "0123456789abcdef"[(c&0x0f)];
- }
- }
- }
- return s;
-}
-
-static void
-frees(char **s)
-{
- free(*s);
- *s = NULL;
-}
-
-static KRB5_LIB_CALL krb5_error_code
-authorize(void *ctx,
- krb5_context context,
- const char *app,
- hx509_request csr,
- krb5_const_principal client,
- krb5_boolean *result)
-{
- krb5_error_code ret;
- hx509_context hx509ctx = NULL;
- KeyUsage ku;
- const char *d;
- size_t i;
- char *princ = NULL;
- char *s = NULL;
-
- if ((d = krb5_config_get_string(context, NULL, app ? app : "kdc",
- "simple_csr_authorizer_directory",
- NULL)) == NULL)
- return KRB5_PLUGIN_NO_HANDLE;
-
- if ((ret = hx509_context_init(&hx509ctx)))
- return ret;
-
- if ((ret = krb5_unparse_name(context, client, &princ)))
- goto out;
-
- s = string_encode(princ);
- free(princ);
- princ = NULL;
- if (s == NULL)
- goto enomem;
-
- princ = s;
- s = NULL;
-
- for (i = 0; ret == 0; i++) {
- hx509_san_type san_type;
- struct stat st;
- const char *prefix;
- char *san;
- char *p;
-
- ret = hx509_request_get_san(csr, i, &san_type, &s);
- if (ret)
- break;
- switch (san_type) {
- case HX509_SAN_TYPE_EMAIL:
- prefix = "email";
- break;
- case HX509_SAN_TYPE_DNSNAME:
- prefix = "dnsname";
- break;
- case HX509_SAN_TYPE_XMPP:
- prefix = "xmpp";
- break;
- case HX509_SAN_TYPE_PKINIT:
- prefix = "pkinit";
- break;
- case HX509_SAN_TYPE_MS_UPN:
- prefix = "ms-upn";
- break;
- default:
- ret = ENOTSUP;
- break;
- }
- if (ret)
- break;
-
- if ((san = string_encode(s)) == NULL ||
- asprintf(&p, "%s/%s/%s-%s", d, princ, prefix, san) == -1 ||
- p == NULL) {
- free(san);
- goto enomem;
- }
- ret = stat(p, &st) == -1 ? errno : 0;
- free(san);
- free(p);
- frees(&s);
- if (ret)
- goto skip;
- ret = hx509_request_authorize_san(csr, i);
- }
- frees(&s);
- if (ret == HX509_NO_ITEM)
- ret = 0;
- if (ret)
- goto out;
-
- for (i = 0; ret == 0; i++) {
- struct stat st;
- char *p;
-
- ret = hx509_request_get_eku(csr, i, &s);
- if (ret)
- break;
- if (asprintf(&p, "%s/%s/eku-%s", d, princ, s) == -1 || p == NULL)
- goto enomem;
- ret = stat(p, &st) == -1 ? errno : 0;
- free(p);
- frees(&s);
- if (ret)
- goto skip;
- ret = hx509_request_authorize_eku(csr, i);
- }
- if (ret == HX509_NO_ITEM)
- ret = 0;
- if (ret)
- goto out;
-
- ku = int2KeyUsage(0);
- ku.digitalSignature = 1;
- ku.nonRepudiation = 1;
- hx509_request_authorize_ku(csr, ku);
-
- *result = TRUE;
- ret = 0;
- goto out;
-
-skip:
- /* Allow another plugin to get a crack at this */
- ret = KRB5_PLUGIN_NO_HANDLE;
- goto out;
-
-enomem:
- ret = krb5_enomem(context);
- goto out;
-
-out:
- hx509_context_free(&hx509ctx);
- free(princ);
- free(s);
- return ret;
-}
-
-static KRB5_LIB_CALL krb5_error_code
-simple_csr_authorizer_init(krb5_context context, void **c)
-{
- *c = NULL;
- return 0;
-}
-
-static KRB5_LIB_CALL void
-simple_csr_authorizer_fini(void *c)
-{
-}
-
-static krb5plugin_csr_authorizer_ftable plug_desc =
- { 1, simple_csr_authorizer_init, simple_csr_authorizer_fini, authorize };
-
-static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
-
-static uintptr_t
-simple_csr_authorizer_get_instance(const char *libname)
-{
- if (strcmp(libname, "krb5") == 0)
- return krb5_get_instance(libname);
- if (strcmp(libname, "kdc") == 0)
- return kdc_get_instance(libname);
- if (strcmp(libname, "hx509") == 0)
- return hx509_get_instance(libname);
- return 0;
-}
-
-krb5_plugin_load_ft kdc_csr_authorizer_plugin_load;
-
-krb5_error_code KRB5_CALLCONV
-kdc_csr_authorizer_plugin_load(heim_pcontext context,
- krb5_get_instance_func_t *get_instance,
- size_t *num_plugins,
- krb5_plugin_common_ftable_cp **plugins)
-{
- *get_instance = simple_csr_authorizer_get_instance;
- *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
- *plugins = (krb5_plugin_common_ftable_cp *)plugs;
- return 0;
-}
diff --git a/third_party/heimdal/kdc/test_csr_authorizer.c b/third_party/heimdal/kdc/test_csr_authorizer.c
index 1d526f77bb6..dbf4c421237 100644
--- a/third_party/heimdal/kdc/test_csr_authorizer.c
+++ b/third_party/heimdal/kdc/test_csr_authorizer.c
@@ -1,8 +1,84 @@
+/*
+ * Copyright (c) 2022 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 <heim-ipc.h>
+
+/*
+ * This program implements two things:
+ *
+ * - a utility for testing the `kdc_authorize_csr()' function and the plugins
+ * that uses,
+ *
+ * and
+ *
+ * - a server for the IPC authorizer.
+ *
+ * For the latter, requested certificate SANs and EKUs are authorized by
+ * checking for existence of files of the form:
+ *
+ * /<path>/<princ>/<ext>-<value>
+ *
+ * where <path> is given as an option.
+ *
+ * <princ> is a requesting client principal name with all characters other than
+ * alphanumeric, '-', '_', and non-leading '.' URL-encoded.
+ *
+ * <ext> is one of:
+ *
+ * - pkinit (SAN)
+ * - xmpp (SAN)
+ * - email (SAN)
+ * - ms-upn (SAN)
+ * - dnsname (SAN)
+ * - eku (EKU OID)
+ *
+ * and <value> is a display form of the SAN or EKU OID, with SANs URL-encoded
+ * just like principal names (see above).
+ *
+ * OIDs are of the form "1.2.3.4.5".
+ *
+ * Only digitalSignature and nonRepudiation key usage values are permitted.
+ */
static int help_flag;
static int version_flag;
+static int daemon_flag;
+static int daemon_child_flag = -1;
+static int ignore_flag = 0;
+static int server_flag = 0;
static const char *app_string = "kdc";
+static const char *socket_dir;
+static const char *authz_dir;
struct getargs args[] = {
{ "help", 'h', arg_flag, &help_flag,
@@ -11,6 +87,18 @@ struct getargs args[] = {
"Print version", NULL },
{ "app", 'a', arg_string, &app_string,
"App to test (kdc or bx509); default: kdc", "APPNAME" },
+ { "socket-dir", 'S', arg_string, &socket_dir,
+ "IPC socket directory", "DIR" },
+ { "authorization-dir", 'A', arg_string, &authz_dir,
+ "authorization directory", "DIR" },
+ { "server", '\0', arg_flag, &server_flag,
+ "Server mode", NULL },
+ { "ignore", 'I', arg_flag, &ignore_flag,
+ "ignore requests", NULL },
+ { "daemon", 'd', arg_flag, &daemon_flag,
+ "daemonize", NULL },
+ { "daemon-child", '\0', arg_flag, &daemon_child_flag,
+ "internal-use-only option", NULL },
};
size_t num_args = sizeof(args) / sizeof(args[0]);
@@ -19,9 +107,23 @@ usage(int e)
{
arg_printusage(args, num_args, NULL, "PATH-TO-DER-CSR PRINCIPAL");
fprintf(stderr,
- "\n\tExercise CSR authorization plugins for a given CSR for a\n"
- "\tgiven principal.\n"
- "\n\tExample: %s PKCS10:/tmp/csr.der foo@TEST.H5L.SE\n",
+ "\tExercise CSR authorization plugins for a given CSR for a\n"
+ "\tgiven principal.\n\n"
+ "\tServer-mode (--server) looks for files in the \n"
+ "\t--authorization-dir DIR directory named:\n"
+ "\n"
+ "\t\teku=OID\n"
+ "\t\tsan_pkinit=PRINCIPAL\n"
+ "\t\tsan_ms_upn=PRINCIPAL\n"
+ "\t\tsan_dnsname=DOMAINNAME\n"
+ "\t\tsan_xmpp=JABBER-ID\n"
+ "\t\tsan_email=EMAIL\n"
+ "\n"
+ "\tClient-mode positional arguments are:\n\n"
+ "\t\tPATH-TO-DER-CSR PRETEND-CLIENT-PRINCIPAL [...]\n\n"
+ "\twhere {...} are requested features that must be granted\n"
+ "\tif the request is only partially authorized.\n\n"
+ "\tClient example:\n\t\t%s PKCS10:/tmp/csr.der foo@TEST.H5L.SE\n",
getprogname());
exit(e);
return e;
@@ -58,6 +160,177 @@ load_plugins(krb5_context context)
#endif
}
+static char *string_encode(const char *);
+static int stat_authz(const char *, const char *);
+
+static krb5_error_code
+authorize(const char *subject, const char *thing)
+{
+ krb5_error_code ret;
+ char *s = NULL;
+
+ s = string_encode(subject);
+ if (s == NULL)
+ return ENOMEM;
+
+ ret = stat_authz(s, thing);
+ if (ret == ENOENT)
+ ret = stat_authz(s, "all");
+ if (ret == ENOENT)
+ ret = EACCES;
+ free(s);
+ return ret;
+}
+
+static void
+service(void *ctx,
+ const heim_octet_string *req,
+ const heim_icred cred,
+ heim_ipc_complete complete_cb,
+ heim_sipc_call complete_cb_data)
+{
+ krb5_error_code ret = 0;
+ struct rk_strpool *result = NULL;
+ krb5_data rep;
+ const char *subject;
+ char *cmd;
+ char *next = NULL;
+ char *res = NULL;
+ char *tok;
+ char *s;
+ int none_granted = 1;
+ int all_granted = 1;
+ int first = 1;
+
+ /*
+ * A krb5_context and log facility for logging would be nice, but this is
+ * all just for testing.
+ */
+
+ (void)ctx;
+
+ cmd = strndup(req->data, req->length);
+ if (cmd == NULL)
+ errx(1, "Out of memory");
+
+ if (strncmp(cmd, "check ", sizeof("check ") - 1) != 0) {
+ rep.data = "Invalid request command (must be \"check ...\")";
+ rep.length = sizeof("Invalid request command (must be \"check ...\")") - 1;
+ (*complete_cb)(complete_cb_data, EINVAL, &rep);
+ free(cmd);
+ return;
+ }
+
+ s = cmd + sizeof("check ") - 1;
+ subject = strtok_r(s, " ", &next);
+ s = NULL;
+
+ while ((tok = strtok_r(s, " ", &next))) {
+ int ret2;
+
+ ret2 = authorize(subject, tok);
+ result = rk_strpoolprintf(result, "%s%s:%s",
+ first ? "" : ",",
+ tok,
+ ret2 == 0 ? "granted" : "denied");
+ if (ret2 == 0)
+ none_granted = 0;
+ else
+ all_granted = 0;
+
+ if (ret2 != 0 && ret == 0)
+ ret = ret2;
+
+ first = 0;
+ }
+ free(cmd);
+
+ if (ret == 0 && all_granted) {
+ rk_strpoolfree(result);
+
+ rep.data = "granted";
+ rep.length = sizeof("granted") - 1;
+ (*complete_cb)(complete_cb_data, 0, &rep);
+ return;
+ }
+
+ if (none_granted && ignore_flag) {
+ rk_strpoolfree(result);
+
+ rep.data = "ignore";
+ rep.length = sizeof("ignore") - 1;
+ (*complete_cb)(complete_cb_data, KRB5_PLUGIN_NO_HANDLE, &rep);
+ return;
+ }
+
+ s = rk_strpoolcollect(result); /* frees `result' */
+ if (s == NULL) {
+ rep.data = "denied out-of-memory";
+ rep.length = sizeof("denied out-of-memory") - 1;
+ (*complete_cb)(complete_cb_data, KRB5_PLUGIN_NO_HANDLE, &rep);
+ return;
+ }
+
+ if (asprintf(&res, "denied %s", s) == -1)
+ errx(1, "Out of memory");
+ if (res == NULL)
+ errx(1, "Out of memory");
+
+ rep.data = res;
+ rep.length = strlen(res);
+
+ (*complete_cb)(complete_cb_data, ret, &rep);
+ free(res);
+ free(s);
+}
+
+static char *
+make_feature_argument(const char *kind,
+ hx509_san_type san_type,
+ const char *value)
+{
+ const char *san_type_str = NULL;
+ char *s = NULL;
+
+ if (strcmp(kind, "san") != 0) {
+ if (asprintf(&s, "%s=%s", kind, value) == -1 || s == NULL)
+ errx(1, "Out of memory");
+ return s;
+ }
+
+ switch (san_type) {
+ case HX509_SAN_TYPE_EMAIL:
+ san_type_str = "email";
+ break;
+ case HX509_SAN_TYPE_DNSNAME:
+ san_type_str = "dnsname";
+ break;
+ case HX509_SAN_TYPE_DN:
+ san_type_str = "dn";
+ break;
+ case HX509_SAN_TYPE_REGISTERED_ID:
+ san_type_str = "registered_id";
+ break;
+ case HX509_SAN_TYPE_XMPP:
+ san_type_str = "xmpp";
+ break;
+ case HX509_SAN_TYPE_PKINIT:
+ case HX509_SAN_TYPE_MS_UPN:
+ san_type_str = "pkinit";
+ break;
+ case HX509_SAN_TYPE_DNSSRV:
+ san_type_str = "dnssrv";
+ break;
+ default:
+ warnx("SAN type not supported");
+ return "";
+ }
+
+ if (asprintf(&s, "san_%s=%s", san_type_str, value) == -1 || s == NULL)
+ errx(1, "Out of memory");
+ return s;
+}
+
int
main(int argc, char **argv)
{
@@ -79,24 +352,143 @@ main(int argc, char **argv)
return 0;
}
- argc -= optidx;
- argv += optidx;
-
- if (argc != 2)
- usage(1);
-
if ((errno = krb5_init_context(&context)))
err(1, "Could not initialize krb5_context");
if ((ret = krb5_initlog(context, argv0, &logf)) ||
(ret = krb5_addlog_dest(context, logf, "0-5/STDERR")))
krb5_err(context, 1, ret, "Could not set up logging to stderr");
load_plugins(context);
+
+ if (server_flag && daemon_flag)
+ daemon_child_flag = roken_detach_prep(argc, argv, "--daemon-child");
+
+ argc -= optidx;
+ argv += optidx;
+
+ if (socket_dir)
+ setenv("HEIM_IPC_DIR", socket_dir, 1);
+
+ if (server_flag) {
+ const char *svc;
+ heim_sipc un;
+
+ rk_pidfile(NULL);
+
+ svc = krb5_config_get_string(context, NULL,
+ app_string ? app_string : "kdc",
+ "ipc_csr_authorizer", "service", NULL);
+ if (svc == NULL)
+ svc = "org.h5l.csr_authorizer";
+
+ /* `service' is our request handler; `argv' is its callback data */
+ ret = heim_sipc_service_unix(svc, service, NULL, &un);
+ if (ret)
+ krb5_err(context, 1, ret,
+ "Could not setup service on Unix domain socket "
+ "%s/.heim_%s-socket", socket_dir, svc);
+
+ roken_detach_finish(NULL, daemon_child_flag);
+
+ /* Enter the IPC event loop */
+ heim_ipc_main();
+ return 0;
+ }
+
+ /* Client mode */
+ if (argc < 2)
+ usage(1);
+
+ /* Parse the given CSR */
if ((ret = hx509_request_parse(context->hx509ctx, argv[0], &csr)))
krb5_err(context, 1, ret, "Could not parse PKCS#10 CSR from %s", argv[0]);
+
+ /*
+ * Parse the client principal that we'll pretend is an authenticated client
+ * principal.
+ */
if ((ret = krb5_parse_name(context, argv[1], &princ)))
krb5_err(context, 1, ret, "Could not parse principal %s", argv[1]);
- if ((ret = kdc_authorize_csr(context, app_string, csr, princ)))
- krb5_err(context, 1, ret, "Authorization failed");
+
+ /* Call the authorizer */
+ ret = kdc_authorize_csr(context, app_string, csr, princ);
+
+ if (ret) {
+ unsigned n = hx509_request_count_unauthorized(csr);
+ size_t i, k;
+ int ret2 = 0;
+ int good = -1;
+
+ /*
+ * Check partial approval of SANs.
+ *
+ * Iterate over the SANs in the request, and for each check if a) it
+ * was granted, b) it's on the remainder of our argv[].
+ */
+ for (i = 0; ret2 == 0; i++) {
+ hx509_san_type san_type;
+ char *feature = NULL;
+ char *san = NULL;
+ int granted;
+
+ ret2 = hx509_request_get_san(csr, i, &san_type, &san);
+ if (ret2)
+ break;
+
+ feature = make_feature_argument("san", san_type, san);
+
+ granted = hx509_request_san_authorized_p(csr, i);
+ for (k = 2; k < argc; k++) {
+ if (strcmp(feature, argv[k]) != 0)
+ continue;
+
+ /* The SAN is on our command line */
+ if (granted && good == -1)
+ good = 1;
+ else if (!granted)
+ good = 0;
+ break;
+ }
+
+ hx509_xfree(san);
+ }
+
+ /* Check partial approval of EKUs */
+ for (i = 0; ret2 == 0; i++) {
+ char *feature = NULL;
+ char *eku = NULL;
+ int granted;
+
+ ret2 = hx509_request_get_eku(csr, i, &eku);
+ if (ret2)
+ break;
+
+ feature = make_feature_argument("eku", 0, eku);
+
+ granted = hx509_request_eku_authorized_p(csr, i);
+ for (k = 2; k < argc; k++) {
+ if (strcmp(feature, argv[k]) != 0)
+ continue;
+
+ /* The SAN is on our command line */
+ if (granted && good == -1)
+ good = 1;
+ else if (!granted)
+ good = 0;
+ break;
+ }
+
+ hx509_xfree(eku);
+ }
+
+ if (good != 1) {
+ krb5_free_principal(context, princ);
+ _krb5_unload_plugins(context, "kdc");
+ hx509_request_free(&csr);
+ krb5_err(context, 1, ret,
+ "Authorization failed with %u rejected features", n);
+ }
+ }
+
printf("Authorized!\n");
krb5_free_principal(context, princ);
_krb5_unload_plugins(context, "kdc");
@@ -104,3 +496,102 @@ main(int argc, char **argv)
hx509_request_free(&csr);
return 0;
}
+
+/*
+ * string_encode_sz() and string_encode() encode a string to be safe for use as
+ * a file name. They function very much like URL encoders, but '~' also gets
+ * encoded, and '@', '-', '_', and non-leading '.' do not.
+ *
+ * A corresponding decoder is not needed.
+ */
+static size_t
+string_encode_sz(const char *in)
+{
+ size_t sz = strlen(in);
+ int first = 1;
+
+ while (*in) {
+ char c = *(in++);
+
+ switch (c) {
+ case '@':
+ case '-':
+ case '_':
+ break;
+ case '.':
+ if (first)
+ sz += 2;
+ break;
+ default:
+ if (!isalnum(c))
+ sz += 2;
+ }
+ first = 0;
+ }
+ return sz;
+}
+
+static char *
+string_encode(const char *in)
+{
+ size_t len = strlen(in);
+ size_t sz = string_encode_sz(in);
+ size_t i, k;
+ char *s;
+ int first = 1;
+
+ if ((s = malloc(sz + 1)) == NULL)
+ return NULL;
+ s[sz] = '\0';
+
+ for (i = k = 0; i < len; i++, first = 0) {
+ unsigned char c = ((const unsigned char *)in)[i];
+
+ switch (c) {
+ case '@':
+ case '-':
+ case '_':
+ s[k++] = c;
+ break;
+ case '.':
+ if (first) {
+ s[k++] = '%';
+ s[k++] = "0123456789abcdef"[(c&0xff)>>4];
+ s[k++] = "0123456789abcdef"[(c&0x0f)];
+ } else {
+ s[k++] = c;
+ }
+ break;
+ default:
+ if (isalnum(c)) {
+ s[k++] = c;
+ } else {
+ s[k++] = '%';
+ s[k++] = "0123456789abcdef"[(c&0xff)>>4];
+ s[k++] = "0123456789abcdef"[(c&0x0f)];
+ }
+ }
+ }
+ return s;
+}
+
+static int
+stat_authz(const char *subject,
+ const char *thing)
+{
+ struct stat st;
+ char *p = NULL;
+ int ret;
+
+ if (authz_dir == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ if (thing)
+ ret = asprintf(&p, "%s/%s/%s", authz_dir, subject, thing);
+ else
+ ret = asprintf(&p, "%s/%s", authz_dir, subject);
+ if (ret == -1 || p == NULL)
+ return ENOMEM;
+ ret = stat(p, &st);
+ free(p);
+ return ret == 0 ? 0 : errno;
+}
diff --git a/third_party/heimdal/kdc/test_token_validator.c b/third_party/heimdal/kdc/test_token_validator.c
index 10ea35aa242..2e4e9dca3dd 100644
--- a/third_party/heimdal/kdc/test_token_validator.c
+++ b/third_party/heimdal/kdc/test_token_validator.c
@@ -88,7 +88,7 @@ main(int argc, char **argv)
if (argc != 2)
usage(1);
- if ((ret = krb5_init_context(&context)))
+ if (krb5_init_context(&context))
err(1, "Could not initialize krb5_context");
load_plugins(context);