diff options
Diffstat (limited to 'third_party/heimdal/kdc')
-rw-r--r-- | third_party/heimdal/kdc/Makefile.am | 15 | ||||
-rw-r--r-- | third_party/heimdal/kdc/bx509d.c | 173 | ||||
-rw-r--r-- | third_party/heimdal/kdc/fast.c | 6 | ||||
-rw-r--r-- | third_party/heimdal/kdc/hpropd.c | 38 | ||||
-rw-r--r-- | third_party/heimdal/kdc/httpkadmind.c | 33 | ||||
-rw-r--r-- | third_party/heimdal/kdc/ipc_csr_authorizer.c | 291 | ||||
-rw-r--r-- | third_party/heimdal/kdc/kdc-plugin.c | 66 | ||||
-rw-r--r-- | third_party/heimdal/kdc/kdc-plugin.h | 28 | ||||
-rw-r--r-- | third_party/heimdal/kdc/kdc-tester.c | 7 | ||||
-rw-r--r-- | third_party/heimdal/kdc/kerberos5.c | 8 | ||||
-rw-r--r-- | third_party/heimdal/kdc/krb5tgs.c | 94 | ||||
-rw-r--r-- | third_party/heimdal/kdc/misc.c | 2 | ||||
-rw-r--r-- | third_party/heimdal/kdc/mit_dump.c | 4 | ||||
-rw-r--r-- | third_party/heimdal/kdc/mssfu.c | 20 | ||||
-rw-r--r-- | third_party/heimdal/kdc/pkinit-ec.c | 350 | ||||
-rw-r--r-- | third_party/heimdal/kdc/simple_csr_authorizer.c | 343 | ||||
-rw-r--r-- | third_party/heimdal/kdc/test_csr_authorizer.c | 513 | ||||
-rw-r--r-- | third_party/heimdal/kdc/test_token_validator.c | 2 |
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); |