Add bx509d

This commit is contained in:
Nicolas Williams
2019-10-09 20:18:01 -05:00
parent 4d4c7078cd
commit 575c67806b
41 changed files with 5794 additions and 684 deletions

View File

@@ -4,13 +4,17 @@ include $(top_srcdir)/Makefile.am.common
AM_CPPFLAGS += $(INCLUDE_libintl) $(INCLUDE_openssl_crypto) -I$(srcdir)/../lib/krb5
lib_LTLIBRARIES = libkdc.la
lib_LTLIBRARIES = simple_csr_authorizer.la \
ipc_csr_authorizer.la \
libkdc.la cjwt_token_validator.la \
negotiate_token_validator.la
bin_PROGRAMS = string2key
sbin_PROGRAMS = kstash
libexec_PROGRAMS = hprop hpropd kdc digest-service
libexec_PROGRAMS = hprop hpropd kdc digest-service \
test_token_validator test_csr_authorizer test_kdc_ca
noinst_PROGRAMS = kdc-replay kdc-tester
@@ -23,6 +27,21 @@ kstash_SOURCES = kstash.c headers.h
string2key_SOURCES = string2key.c headers.h
if HAVE_MICROHTTPD
bx509d_SOURCES = bx509d.c
bx509d_AM_CPPFLAGS = $(AM_CPPFLAGS) $(MICROHTTPD_CFLAGS)
bx509d_LDADD = -ldl \
libkdc.la \
$(MICROHTTPD_LIBS) \
$(LIB_roken) \
$(top_builddir)/lib/sl/libsl.la \
$(top_builddir)/lib/asn1/libasn1.la \
$(top_builddir)/lib/krb5/libkrb5.la \
$(top_builddir)/lib/hx509/libhx509.la \
$(top_builddir)/lib/gssapi/libgssapi.la
libexec_PROGRAMS += bx509d
endif
digest_service_SOURCES = \
digest-service.c
@@ -35,8 +54,29 @@ kdc_tester_SOURCES = \
config.c \
kdc-tester.c
test_token_validator_SOURCES = test_token_validator.c
test_csr_authorizer_SOURCES = test_csr_authorizer.c
test_kdc_ca_SOURCES = test_kdc_ca.c
# Token plugins (for bx509d)
cjwt_token_validator_la_SOURCES = cjwt_token_validator.c
cjwt_token_validator_la_AM_CPPFLAGS = $(CJSON_FLAGS) $(CJWT_FLAGS)
cjwt_token_validator_la_LDFLAGS = -module $(CJSON_LIBS) $(CJWT_LIBS)
negotiate_token_validator_la_SOURCES = negotiate_token_validator.c
negotiate_token_validator_la_LDFLAGS = -module $(top_builddir)/lib/gssapi/libgssapi.la
# 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 \
$(top_builddir)/lib/hx509/libhx509.la \
$(top_builddir)/lib/ipc/libheim-ipcc.la \
$(top_builddir)/lib/roken/libroken.la
libkdc_la_SOURCES = \
default_config.c \
ca.c \
set_dbinfo.c \
digest.c \
fast.c \
@@ -48,6 +88,8 @@ libkdc_la_SOURCES = \
log.c \
misc.c \
kx509.c \
token_validator.c \
csr_authorizer.c \
process.c \
windc.c \
rx.h
@@ -57,6 +99,9 @@ KDC_PROTOS = $(srcdir)/kdc-protos.h $(srcdir)/kdc-private.h
ALL_OBJECTS = $(kdc_OBJECTS)
ALL_OBJECTS += $(kdc_replay_OBJECTS)
ALL_OBJECTS += $(kdc_tester_OBJECTS)
ALL_OBJECTS += $(test_token_validator_OBJECTS)
ALL_OBJECTS += $(test_csr_authorizer_OBJECTS)
ALL_OBJECTS += $(test_kdc_ca_OBJECTS)
ALL_OBJECTS += $(libkdc_la_OBJECTS)
ALL_OBJECTS += $(string2key_OBJECTS)
ALL_OBJECTS += $(kstash_OBJECTS)
@@ -135,13 +180,16 @@ digest_service_LDADD = \
$(LDADD) $(LIB_pidfile)
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)
test_csr_authorizer_LDADD = libkdc.la $(top_builddir)/lib/hx509/libhx509.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
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
noinst_HEADERS = $(srcdir)/kdc-private.h
krb5dir = $(includedir)/krb5
krb5_HEADERS = windc_plugin.h
krb5_HEADERS = windc_plugin.h token_validator_plugin.h csr_authorizer_plugin.h
build_HEADERZ = $(krb5_HEADERS) # XXX

View File

@@ -92,17 +92,21 @@ $(LIBEXECDIR)\kdc.exe: \
LIBKDC_OBJS=\
$(OBJ)\default_config.obj \
$(OBJ)\set_dbinfo.obj \
$(OBJ)\digest.obj \
$(OBJ)\fast.obj \
$(OBJ)\kerberos5.obj \
$(OBJ)\krb5tgs.obj \
$(OBJ)\pkinit.obj \
$(OBJ)\pkinit-ec.obj \
$(OBJ)\log.obj \
$(OBJ)\misc.obj \
$(OBJ)\kx509.obj \
$(OBJ)\process.obj \
$(OBJ)\ca.obj \
$(OBJ)\kx509.obj \
$(OBJ)\set_dbinfo.obj \
$(OBJ)\digest.obj \
$(OBJ)\fast.obj \
$(OBJ)\kerberos5.obj \
$(OBJ)\krb5tgs.obj \
$(OBJ)\pkinit.obj \
$(OBJ)\pkinit-ec.obj \
$(OBJ)\log.obj \
$(OBJ)\misc.obj \
$(OBJ)\kx509.obj \
$(OBJ)\token_validator.obj \
$(OBJ)\csr_authorizer.obj \
$(OBJ)\process.obj \
$(OBJ)\windc.obj
LIBKDC_LIBS=\
@@ -126,9 +130,10 @@ clean::
libkdc_la_SOURCES = \
default_config.c \
ca.c \
set_dbinfo.c \
digest.c \
fast.c \
fast.c \
kdc_locl.h \
kerberos5.c \
krb5tgs.c \
@@ -137,6 +142,8 @@ libkdc_la_SOURCES = \
log.c \
misc.c \
kx509.c \
token_validator.c \
csr_authorizer.c \
process.c \
windc.c \
rx.h

1722
kdc/bx509d.c Normal file

File diff suppressed because it is too large Load Diff

727
kdc/ca.c Normal file
View File

@@ -0,0 +1,727 @@
/*
* 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.
*/
#include "kdc_locl.h"
#include <hex.h>
#include <rfc2459_asn1.h>
#include <hx509.h>
#include <hx509_err.h>
#include <stdarg.h>
/*
* This file implements a singular utility function `kdc_issue_certificate()'
* for certificate issuance for kx509 and bx509, which takes a principal name,
* an `hx509_request' resulting from parsing a CSR and possibly adding
* SAN/EKU/KU extensions, the start/end times of request's authentication
* method, and whether to include a full certificate chain in the result.
*/
typedef enum {
CERT_NOTSUP = 0,
CERT_CLIENT = 1,
CERT_SERVER = 2,
CERT_MIXED = 3
} cert_type;
static void
frees(char **s)
{
free(*s);
*s = NULL;
}
static krb5_error_code
count_sans(hx509_request req, size_t *n)
{
size_t i;
char *s = NULL;
int ret = 0;
*n = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
frees(&s);
ret = hx509_request_get_san(req, i, &san_type, &s);
if (ret)
break;
switch (san_type) {
case HX509_SAN_TYPE_DNSNAME:
case HX509_SAN_TYPE_EMAIL:
case HX509_SAN_TYPE_XMPP:
case HX509_SAN_TYPE_PKINIT:
case HX509_SAN_TYPE_MS_UPN:
(*n)++;
break;
default:
ret = ENOTSUP;
}
frees(&s);
}
return ret == HX509_NO_ITEM ? 0 : ret;
}
static int
has_sans(hx509_request req)
{
hx509_san_type san_type;
char *s = NULL;
int ret = hx509_request_get_san(req, 0, &san_type, &s);
frees(&s);
return ret == HX509_NO_ITEM ? 0 : 1;
}
static cert_type
characterize_cprinc(krb5_context context,
krb5_principal cprinc)
{
unsigned int ncomp = krb5_principal_get_num_comp(context, cprinc);
const char *comp1 = krb5_principal_get_comp_string(context, cprinc, 1);
switch (ncomp) {
case 1:
return CERT_CLIENT;
case 2:
if (strchr(comp1, '.') == NULL)
return CERT_CLIENT;
return CERT_SERVER;
case 3:
if (strchr(comp1, '.'))
return CERT_SERVER;
return CERT_NOTSUP;
default:
return CERT_NOTSUP;
}
}
/* Characterize request as client or server cert req */
static cert_type
characterize(krb5_context context,
krb5_principal cprinc,
hx509_request req)
{
krb5_error_code ret = 0;
cert_type res = CERT_NOTSUP;
size_t i;
char *s = NULL;
int want_ekus = 0;
if (!has_sans(req))
return characterize_cprinc(context, cprinc);
for (i = 0; ret == 0; i++) {
heim_oid oid;
frees(&s);
ret = hx509_request_get_eku(req, i, &s);
if (ret)
break;
want_ekus = 1;
ret = der_parse_heim_oid(s, ".", &oid);
if (ret)
break;
/*
* If the client wants only a server certificate, then we'll be
* willing to issue one that may be longer-lived than the client's
* ticket/token.
*
* There may be other server EKUs, but these are the ones we know
* of.
*/
if (der_heim_oid_cmp(&asn1_oid_id_pkix_kp_serverAuth, &oid) &&
der_heim_oid_cmp(&asn1_oid_id_pkix_kp_OCSPSigning, &oid) &&
der_heim_oid_cmp(&asn1_oid_id_pkix_kp_secureShellServer, &oid))
res |= CERT_CLIENT;
else
res |= CERT_SERVER;
der_free_oid(&oid);
}
frees(&s);
if (ret == HX509_NO_ITEM)
ret = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
frees(&s);
ret = hx509_request_get_san(req, i, &san_type, &s);
if (ret)
break;
switch (san_type) {
case HX509_SAN_TYPE_DNSNAME:
if (!want_ekus)
res |= CERT_SERVER;
break;
case HX509_SAN_TYPE_EMAIL:
case HX509_SAN_TYPE_XMPP:
case HX509_SAN_TYPE_PKINIT:
case HX509_SAN_TYPE_MS_UPN:
if (!want_ekus)
res |= CERT_CLIENT;
break;
default:
ret = ENOTSUP;
}
if (ret)
break;
}
frees(&s);
if (ret == HX509_NO_ITEM)
ret = 0;
return ret ? CERT_NOTSUP : res;
}
/*
* Get a configuration sub-tree for kx509 based on what's being requested and
* by whom.
*
* We have a number of cases:
*
* - default certificate (no CSR used, or no certificate extensions requested)
* - for client principals
* - for service principals
* - client certificate requested (CSR used and client-y SANs/EKUs requested)
* - server certificate requested (CSR used and server-y SANs/EKUs requested)
* - mixed client/server certificate requested (...)
*/
static const krb5_config_binding *
get_cf(krb5_context context,
const char *toplevel,
hx509_request req,
krb5_principal cprinc)
{
krb5_error_code ret;
const krb5_config_binding *cf = NULL;
unsigned int ncomp = krb5_principal_get_num_comp(context, cprinc);
const char *realm = krb5_principal_get_realm(context, cprinc);
const char *comp0 = krb5_principal_get_comp_string(context, cprinc, 0);
const char *comp1 = krb5_principal_get_comp_string(context, cprinc, 1);
const char *label = NULL;
const char *svc = NULL;
const char *def = NULL;
cert_type certtype = CERT_NOTSUP;
size_t nsans = 0;
if (ncomp == 0) {
krb5_set_error_message(context, ENOTSUP,
"Client principal has no components!");
return NULL;
}
if ((ret = count_sans(req, &nsans)) ||
(certtype = characterize(context, cprinc, req)) == CERT_NOTSUP) {
krb5_set_error_message(context, ret,
"Could not characterize CSR");
return NULL;
}
if (nsans) {
def = "custom";
/* Client requested some certificate extension, a SAN or EKU */
switch (certtype) {
case CERT_MIXED: label = "mixed"; break;
case CERT_CLIENT: label = "client"; break;
case CERT_SERVER: label = "server"; break;
default: return NULL;
}
} else {
def = "default";
/* Default certificate desired */
if (ncomp == 1) {
label = "user";
} else if (ncomp == 2 && strcmp(comp1, "root") == 0) {
label = "root_user";
} else if (ncomp == 2 && strcmp(comp1, "admin") == 0) {
label = "admin_user";
} else if (strchr(comp1, '.')) {
label = "hostbased_service";
svc = comp0;
} else {
label = "other";
}
}
if (strcmp(toplevel, "kdc") == 0)
cf = krb5_config_get_list(context, NULL, toplevel, "realms", realm,
"kx509", label, svc, NULL);
else
cf = krb5_config_get_list(context, NULL, toplevel, "realms", realm,
label, svc, NULL);
if (cf == NULL)
krb5_set_error_message(context, ENOTSUP,
"No %s configuration for %s %s certificates [%s] realm "
"-> %s -> kx509 -> %s%s%s",
strcmp(toplevel, "bx509") == 0 ? "bx509" : "kx509",
def, label, toplevel, realm, label,
svc ? " -> " : "", svc ? svc : "");
return cf;
}
/*
* Find and set a certificate template using a configuration sub-tree
* appropriate to the requesting principal.
*
* This allows for the specification of the following in configuration:
*
* - certificates as templates, with ${var} tokens in subjectName attribute
* values that will be expanded later
* - a plain string with ${var} tokens to use as the subjectName
* - EKUs
* - whether to include a PKINIT SAN
*/
static krb5_error_code
set_template(krb5_context context,
const krb5_config_binding *cf,
hx509_ca_tbs tbs)
{
krb5_error_code ret = 0;
const char *cert_template = NULL;
const char *subj_name = NULL;
char **ekus = NULL;
if (cf == NULL)
return KRB5KDC_ERR_POLICY; /* Can't happen */
cert_template = krb5_config_get_string(context, cf, "template_cert", NULL);
subj_name = krb5_config_get_string(context, cf, "subject_name", NULL);
ekus = krb5_config_get_strings(context, cf, "ekus", NULL);
if (cert_template) {
hx509_certs certs;
hx509_cert template;
ret = hx509_certs_init(context->hx509ctx, cert_template, 0,
NULL, &certs);
if (ret == 0)
ret = hx509_get_one_cert(context->hx509ctx, certs, &template);
hx509_certs_free(&certs);
if (ret) {
krb5_set_error_message(context, KRB5KDC_ERR_POLICY,
"Failed to load certificate template from "
"%s", cert_template);
return ret;
}
/*
* Only take the subjectName, the keyUsage, and EKUs from the template
* certificate.
*/
ret = hx509_ca_tbs_set_template(context->hx509ctx, tbs,
HX509_CA_TEMPLATE_SUBJECT |
HX509_CA_TEMPLATE_KU |
HX509_CA_TEMPLATE_EKU,
template);
hx509_cert_free(template);
if (ret)
return ret;
}
if (subj_name) {
hx509_name dn = NULL;
ret = hx509_parse_name(context->hx509ctx, subj_name, &dn);
if (ret == 0)
ret = hx509_ca_tbs_set_subject(context->hx509ctx, tbs, dn);
hx509_name_free(&dn);
if (ret)
return ret;
}
if (cert_template == NULL && subj_name == NULL) {
hx509_name dn = NULL;
ret = hx509_empty_name(context->hx509ctx, &dn);
if (ret == 0)
ret = hx509_ca_tbs_set_subject(context->hx509ctx, tbs, dn);
hx509_name_free(&dn);
if (ret)
return ret;
}
if (ekus) {
size_t i;
for (i = 0; ret == 0 && ekus[i]; i++) {
heim_oid oid = { 0, 0 };
if ((ret = der_find_or_parse_heim_oid(ekus[i], ".", &oid)) == 0)
ret = hx509_ca_tbs_add_eku(context->hx509ctx, tbs, &oid);
der_free_oid(&oid);
}
krb5_config_free_strings(ekus);
}
/*
* XXX A KeyUsage template would be nice, but it needs some smarts to
* remove, e.g., encipherOnly, decipherOnly, keyEncipherment, if the SPKI
* algorithm does not support encryption. The same logic should be added
* to hx509_ca_tbs_set_template()'s HX509_CA_TEMPLATE_KU functionality.
*/
return ret;
}
/*
* Find and set a certificate template, set "variables" in `env', and add add
* default SANs/EKUs as appropriate.
*
* TODO:
* - lookup a template for the client principal in its HDB entry
* - lookup subjectName, SANs for a principal in its HDB entry
* - lookup a host-based client principal's HDB entry and add its canonical
* name / aliases as dNSName SANs
* (this would have to be if requested by the client, perhaps)
*/
static krb5_error_code
set_tbs(krb5_context context,
const krb5_config_binding *cf,
hx509_request req,
krb5_principal cprinc,
hx509_env *env,
hx509_ca_tbs tbs)
{
krb5_error_code ret;
unsigned int ncomp = krb5_principal_get_num_comp(context, cprinc);
const char *realm = krb5_principal_get_realm(context, cprinc);
const char *comp0 = krb5_principal_get_comp_string(context, cprinc, 0);
const char *comp1 = krb5_principal_get_comp_string(context, cprinc, 1);
const char *comp2 = krb5_principal_get_comp_string(context, cprinc, 2);
char *princ_no_realm = NULL;
char *princ = NULL;
ret = krb5_unparse_name_flags(context, cprinc, 0, &princ);
if (ret == 0)
ret = krb5_unparse_name_flags(context, cprinc,
KRB5_PRINCIPAL_UNPARSE_NO_REALM,
&princ_no_realm);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env,
"principal-name-without-realm", princ_no_realm);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-name", princ);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-name-realm",
realm);
/* Populate requested certificate extensions from CSR/CSRPlus if allowed */
ret = hx509_ca_tbs_set_from_csr(context->hx509ctx, tbs, req);
if (ret == 0)
ret = set_template(context, cf, tbs);
/*
* Optionally add PKINIT SAN.
*
* Adding an id-pkinit-san means the client can use the certificate to
* initiate PKINIT. That might seem odd, but it enables a sort of PKIX
* credential delegation by allowing forwarded Kerberos tickets to be
* used to acquire PKIX credentials. Thus this can work:
*
* PKIX (w/ HW token) -> Kerberos ->
* PKIX (w/ softtoken) -> Kerberos ->
* PKIX (w/ softtoken) -> Kerberos ->
* ...
*
* Note that we may not have added the PKINIT EKU -- that depends on the
* template, and host-based service templates might well not include it.
*/
if (ret == 0 && !has_sans(req) &&
krb5_config_get_bool_default(context, cf, FALSE, "include_pkinit_san",
NULL)) {
ret = hx509_ca_tbs_add_san_pkinit(context->hx509ctx, tbs, princ);
}
if (ret)
goto out;
if (ncomp == 1) {
const char *email_domain;
ret = hx509_env_add(context->hx509ctx, env, "principal-component0",
princ_no_realm);
/*
* If configured, include an rfc822Name that's just the client's
* principal name sans realm @ configured email domain.
*/
if (ret == 0 && !has_sans(req) &&
(email_domain = krb5_config_get_string(context, cf, "email_domain",
NULL))) {
char *email;
if (asprintf(&email, "%s@%s", princ_no_realm, email_domain) == -1 ||
email == NULL)
goto enomem;
ret = hx509_ca_tbs_add_san_rfc822name(context->hx509ctx, tbs, email);
free(email);
}
goto out;
} else if (ncomp == 2 || ncomp == 3) {
/*
* 2- and 3-component principal name.
*
* We do not have a reliable name-type indicator. If the second
* component has a '.' in it then we'll assume that the name is a
* host-based (2-component) or domain-based (3-component) service
* principal name. Else we'll assume it's a two-component admin-style
* username.
*/
ret = hx509_env_add(context->hx509ctx, env, "principal-component0",
comp0);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-component1",
comp1);
if (ret == 0 && ncomp == 3)
ret = hx509_env_add(context->hx509ctx, env, "principal-component2",
comp2);
if (ret)
goto out;
if (ret == 0 && strchr(comp1, '.')) {
/* Looks like host-based or domain-based service */
ret = hx509_env_add(context->hx509ctx, env,
"principal-service-name", comp0);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-host-name", comp1);
if (ret == 0 && ncomp == 3)
ret = hx509_env_add(context->hx509ctx, env, "principal-domain-name", comp2);
if (ret == 0 && !has_sans(req) &&
krb5_config_get_bool_default(context, cf, FALSE,
"include_dnsname_san", NULL)) {
ret = hx509_ca_tbs_add_san_hostname(context->hx509ctx, tbs, comp1);
}
}
} else {
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"kx509/bx509 client %s has too many "
"components!", princ);
}
out:
krb5_xfree(princ_no_realm);
krb5_xfree(princ);
return ret;
enomem:
ret = krb5_enomem(context);
goto out;
}
static krb5_error_code
tbs_set_times(krb5_context context,
const krb5_config_binding *cf,
krb5_times *auth_times,
time_t req_life,
hx509_ca_tbs tbs)
{
time_t now = time(NULL);
time_t endtime = auth_times->endtime;
time_t starttime = auth_times->starttime ?
auth_times->starttime : now - 5 * 60;
time_t fudge =
krb5_config_get_time_default(context, cf, 5 * 24 * 3600,
"force_cert_lifetime", NULL);
time_t clamp =
krb5_config_get_time_default(context, cf, 0, "max_cert_lifetime",
NULL);
if (fudge && now + fudge > endtime)
endtime = now + fudge;
if (req_life && req_life < endtime - now)
endtime = now + req_life;
if (clamp && clamp < endtime - now)
endtime = now + clamp;
hx509_ca_tbs_set_notAfter(context->hx509ctx, tbs, endtime);
hx509_ca_tbs_set_notBefore(context->hx509ctx, tbs, starttime);
return 0;
}
/*
* Build a certifate for `principal' and its CSR.
*/
krb5_error_code
kdc_issue_certificate(krb5_context context,
const krb5_kdc_configuration *config,
hx509_request req,
krb5_principal cprinc,
krb5_times *auth_times,
int send_chain,
hx509_certs *out)
{
const krb5_config_binding *cf;
krb5_error_code ret;
const char *kx509_ca;
hx509_ca_tbs tbs = NULL;
hx509_certs chain = NULL;
hx509_cert signer = NULL;
hx509_cert cert = NULL;
hx509_env env = NULL;
KeyUsage ku;
*out = NULL;
/* Force KU */
ku = int2KeyUsage(0);
ku.digitalSignature = 1;
hx509_request_authorize_ku(req, ku);
/* Get configuration */
if ((cf = get_cf(context, config->app, req, cprinc)) == NULL)
return KRB5KDC_ERR_POLICY;
if ((kx509_ca = krb5_config_get_string(context, cf, "ca", NULL)) == NULL) {
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"No kx509 CA issuer credential specified");
return ret;
}
ret = hx509_ca_tbs_init(context->hx509ctx, &tbs);
if (ret)
return ret;
/* Lookup a template and set things in `env' and `tbs' as appropriate */
if (ret == 0)
ret = set_tbs(context, cf, req, cprinc, &env, tbs);
/* Populate generic template "env" variables */
/*
* The `tbs' and `env' are now complete as to naming and EKUs.
*
* We check that the `tbs' is not name-less, after which all remaining
* failures here will not be policy failures. So we also log the intent to
* issue a certificate now.
*/
if (ret == 0 && hx509_name_is_null_p(hx509_ca_tbs_get_name(tbs)) &&
!has_sans(req))
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"Not issuing certificate because it "
"would have no names");
if (ret)
goto out;
/*
* Still to be done below:
*
* - set certificate spki
* - set certificate validity
* - expand variables in certificate subject name template
* - sign certificate
* - encode certificate and chain
*/
/* Load the issuer certificate and private key */
{
hx509_certs certs;
hx509_query *q;
ret = hx509_certs_init(context->hx509ctx, kx509_ca, 0, NULL, &certs);
if (ret) {
krb5_set_error_message(context, ret, "Failed to load CA %s",
kx509_ca);
goto out;
}
ret = hx509_query_alloc(context->hx509ctx, &q);
if (ret) {
hx509_certs_free(&certs);
goto out;
}
hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY);
hx509_query_match_option(q, HX509_QUERY_OPTION_KU_KEYCERTSIGN);
ret = hx509_certs_find(context->hx509ctx, certs, q, &signer);
hx509_query_free(context->hx509ctx, q);
hx509_certs_free(&certs);
if (ret) {
krb5_set_error_message(context, ret, "Failed to find a CA in %s",
kx509_ca);
goto out;
}
}
/* Populate the subject public key in the TBS context */
{
SubjectPublicKeyInfo spki;
ret = hx509_request_get_SubjectPublicKeyInfo(context->hx509ctx,
req, &spki);
if (ret == 0)
ret = hx509_ca_tbs_set_spki(context->hx509ctx, tbs, &spki);
free_SubjectPublicKeyInfo(&spki);
if (ret)
goto out;
}
/* Work out cert expiration */
if (ret == 0)
ret = tbs_set_times(context, cf, auth_times, 0 /* XXX req_life */, tbs);
/* Expand the subjectName template in the TBS using the env */
if (ret == 0)
ret = hx509_ca_tbs_subject_expand(context->hx509ctx, tbs, env);
hx509_env_free(&env);
/* All done with the TBS, sign/issue the certificate */
ret = hx509_ca_sign(context->hx509ctx, tbs, signer, &cert);
if (ret)
goto out;
/* Gather the certificate and chain into a MEMORY store */
ret = hx509_certs_init(context->hx509ctx, "MEMORY:certs", 0, NULL, out);
if (ret == 0)
ret = hx509_certs_add(context->hx509ctx, *out, cert);
if (ret == 0 && send_chain) {
ret = hx509_certs_init(context->hx509ctx, kx509_ca, 0, NULL, &chain);
if (ret == 0)
ret = hx509_certs_merge(context->hx509ctx, *out, chain);
}
out:
hx509_certs_free(&chain);
if (env)
hx509_env_free(&env);
if (tbs)
hx509_ca_tbs_free(&tbs);
if (cert)
hx509_cert_free(cert);
if (signer)
hx509_cert_free(signer);
if (ret)
hx509_certs_free(out);
return ret;
}

330
kdc/cjwt_token_validator.c Normal file
View File

@@ -0,0 +1,330 @@
/*
* 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 is a plugin by which bx509d can validate JWT Bearer tokens using the
* cjwt library.
*
* Configuration:
*
* [kdc]
* realm = {
* A.REALM.NAME = {
* cjwt_jqk = PATH-TO-JWK-PEM-FILE
* }
* }
*
* where AUDIENCE-FOR-KDC is the value of the "audience" (i.e., the target) of
* the token.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <krb5.h>
#include <common_plugin.h>
#include <hdb.h>
#include <roken.h>
#include <token_validator_plugin.h>
#include <cjwt/cjwt.h>
#ifdef HAVE_CJSON
#include <cjson/cJSON.h>
#endif
static const char *
get_kv(krb5_context context, const char *realm, const char *k, const char *k2)
{
return krb5_config_get_string(context, NULL, "bx509", "realms", realm,
k, k2, NULL);
}
static krb5_error_code
get_issuer_pubkeys(krb5_context context,
const char *realm,
krb5_data *previous,
krb5_data *current,
krb5_data *next)
{
krb5_error_code save_ret = 0;
krb5_error_code ret;
const char *v;
size_t nkeys = 0;
previous->data = current->data = next->data = 0;
previous->length = current->length = next->length = 0;
if ((v = get_kv(context, realm, "cjwt_jwk_next", NULL)) &&
(++nkeys) &&
(ret = rk_undumpdata(v, &next->data, &next->length)))
save_ret = ret;
if ((v = get_kv(context, realm, "cjwt_jwk_previous", NULL)) &&
(++nkeys) &&
(ret = rk_undumpdata(v, &previous->data, &previous->length)) &&
save_ret == 0)
save_ret = ret;
if ((v = get_kv(context, realm, "cjwt_jwk_current", NULL)) &&
(++nkeys) &&
(ret = rk_undumpdata(v, &current->data, &current->length)) &&
save_ret == 0)
save_ret = ret;
if (nkeys == 0)
krb5_set_error_message(context, EINVAL, "jwk issuer key not specified in "
"[bx509]->realm->%s->cjwt_jwk_{previous,current,next}",
realm);
if (!previous->length && !current->length && !next->length)
krb5_set_error_message(context, save_ret,
"Could not read jwk issuer public key files");
if (current->length == next->length &&
memcmp(current->data, next->data, next->length) == 0) {
free(next->data);
next->data = 0;
next->length = 0;
}
if (current->length == previous->length &&
memcmp(current->data, previous->data, previous->length) == 0) {
free(previous->data);
previous->data = 0;
previous->length = 0;
}
return 0;
}
static krb5_error_code
check_audience(krb5_context context,
const char *realm,
cjwt_t *jwt,
const char * const *audiences,
size_t naudiences)
{
size_t i, k;
if (!jwt->aud) {
krb5_set_error_message(context, EACCES, "JWT bearer token has no "
"audience");
return EACCES;
}
for (i = 0; i < jwt->aud->count; i++)
for (k = 0; k < naudiences; k++)
if (strcasecmp(audiences[k], jwt->aud->names[i]) == 0)
return 0;
krb5_set_error_message(context, EACCES, "JWT bearer token's audience "
"does not match any expected audience");
return EACCES;
}
static krb5_error_code
get_princ(krb5_context context,
const char *realm,
cjwt_t *jwt,
krb5_principal *actual_principal)
{
krb5_error_code ret;
const char *force_realm = NULL;
const char *domain;
#ifdef HAVE_CJSON
if (jwt->private_claims) {
cJSON *jval;
if ((jval = cJSON_GetObjectItem(jwt->private_claims, "authz_sub")))
return krb5_parse_name(context, jval->valuestring, actual_principal);
}
#endif
if (jwt->sub == NULL) {
krb5_set_error_message(context, EACCES, "JWT token lacks 'sub' "
"(subject name)!");
return EACCES;
}
if ((domain = strchr(jwt->sub, '@'))) {
force_realm = get_kv(context, realm, "cjwt_force_realm", ++domain);
ret = krb5_parse_name(context, jwt->sub, actual_principal);
} else {
ret = krb5_parse_name_flags(context, jwt->sub,
KRB5_PRINCIPAL_PARSE_NO_REALM,
actual_principal);
}
if (ret)
krb5_set_error_message(context, ret, "JWT token 'sub' not a valid "
"principal name: %s", jwt->sub);
else if (force_realm)
ret = krb5_principal_set_realm(context, *actual_principal, realm);
else if (domain == NULL)
ret = krb5_principal_set_realm(context, *actual_principal, realm);
/* else leave the domain as the realm */
return ret;
}
static KRB5_LIB_CALL krb5_error_code
validate(void *ctx,
krb5_context context,
const char *realm,
const char *token_type,
krb5_data *token,
const char * const *audiences,
size_t naudiences,
krb5_boolean *result,
krb5_principal *actual_principal,
krb5_times *token_times)
{
heim_octet_string jwk_previous;
heim_octet_string jwk_current;
heim_octet_string jwk_next;
cjwt_t *jwt = NULL;
char *tokstr = NULL;
char *defrealm = NULL;
int ret;
if (strcmp(token_type, "Bearer") != 0)
return KRB5_PLUGIN_NO_HANDLE; /* Not us */
if ((tokstr = calloc(1, token->length + 1)) == NULL)
return ENOMEM;
memcpy(tokstr, token->data, token->length);
if (realm == NULL) {
ret = krb5_get_default_realm(context, &defrealm);
if (ret) {
krb5_set_error_message(context, ret, "could not determine default "
"realm");
free(tokstr);
return ret;
}
realm = defrealm;
}
ret = get_issuer_pubkeys(context, realm, &jwk_previous, &jwk_current,
&jwk_next);
if (ret) {
free(defrealm);
free(tokstr);
return ret;
}
if ((ret = cjwt_decode(tokstr, 0, &jwt, jwk_current.data,
jwk_current.length)) == -2 &&
(ret = cjwt_decode(tokstr, 0, &jwt, jwk_next.data,
jwk_next.length)) == -2)
ret = cjwt_decode(tokstr, 0, &jwt, jwk_previous.data,
jwk_previous.length);
free(jwk_previous.data);
free(jwk_current.data);
free(jwk_next.data);
jwk_previous.data = jwk_current.data = jwk_next.data = NULL;
free(tokstr);
tokstr = NULL;
switch (ret) {
case 0:
if (jwt->header.alg == alg_none) {
krb5_set_error_message(context, EINVAL, "JWT signature algorithm "
"not supported");
free(defrealm);
return EPERM;
}
break;
case -1:
krb5_set_error_message(context, EINVAL, "invalid JWT format");
free(defrealm);
return EINVAL;
case -2:
krb5_set_error_message(context, EINVAL, "JWT signature validation "
"failed (wrong issuer?)");
free(defrealm);
return EPERM;
default:
krb5_set_error_message(context, ret, "misc token validation error");
free(defrealm);
return ret;
}
/* Success; check audience */
if ((ret = check_audience(context, realm, jwt, audiences, naudiences))) {
cjwt_destroy(&jwt);
free(defrealm);
return EACCES;
}
/* Success; extract principal name */
if ((ret = get_princ(context, realm, jwt, actual_principal)) == 0) {
token_times->authtime = jwt->iat.tv_sec;
token_times->starttime = jwt->nbf.tv_sec;
token_times->endtime = jwt->exp.tv_sec;
token_times->renew_till = jwt->exp.tv_sec;
*result = TRUE;
}
cjwt_destroy(&jwt);
free(defrealm);
return ret;
}
static KRB5_LIB_CALL krb5_error_code
hcjwt_init(krb5_context context, void **c)
{
*c = NULL;
return 0;
}
static KRB5_LIB_CALL void
hcjwt_fini(void *c)
{
}
static krb5plugin_token_validator_ftable plug_desc =
{ 1, hcjwt_init, hcjwt_fini, validate };
static krb5plugin_token_validator_ftable *plugs[] = { &plug_desc };
static uintptr_t
hcjwt_get_instance(const char *libname)
{
if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
return 0;
}
krb5_plugin_load_ft kdc_token_validator_plugin_load;
krb5_error_code KRB5_CALLCONV
kdc_token_validator_plugin_load(krb5_context context,
krb5_get_instance_func_t *get_instance,
size_t *num_plugins,
krb5_plugin_common_ftable_cp **plugins)
{
*get_instance = hcjwt_get_instance;
*num_plugins = sizeof(plugs) / sizeof(plugs[0]);
*plugins = (krb5_plugin_common_ftable_cp *)plugs;
return 0;
}

91
kdc/csr_authorizer.c Normal file
View File

@@ -0,0 +1,91 @@
/*
* 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.
*/
#include "kdc_locl.h"
#include "csr_authorizer_plugin.h"
struct plctx {
krb5_kdc_configuration *config;
hx509_request csr;
krb5_const_principal client;
krb5_boolean result;
};
static krb5_error_code KRB5_LIB_CALL
plcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
{
const krb5plugin_csr_authorizer_ftable *authorizer = plug;
struct plctx *plctx = userctx;
return authorizer->authorize(plugctx, context, plctx->config, plctx->csr,
plctx->client, &plctx->result);
}
static const char *plugin_deps[] = { "krb5", NULL };
static struct krb5_plugin_data csr_authorizer_data = {
"kdc",
KDC_CSR_AUTHORIZER,
1,
plugin_deps,
krb5_get_instance
};
/*
* Invoke a plugin to validate a JWT/SAML/OIDC token and partially-evaluate
* access control.
*/
krb5_error_code
kdc_authorize_csr(krb5_context context,
krb5_kdc_configuration *config,
hx509_request csr,
krb5_const_principal client)
{
krb5_error_code ret;
struct plctx ctx;
ctx.config = config;
ctx.csr = csr;
ctx.client = client;
ctx.result = FALSE;
ret = _krb5_plugin_run_f(context, &csr_authorizer_data, 0, &ctx,
plcallback);
if (ret)
krb5_prepend_error_message(context, ret, "Authorization of requested "
"certificate extensions failed");
else if (!ctx.result)
krb5_set_error_message(context, ret, "Authorization of requested "
"certificate extensions failed");
return ret;
}

View File

@@ -0,0 +1,76 @@
/*
* 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.
*/
#ifndef HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H
#define HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H 1
#define KDC_CSR_AUTHORIZER "kdc_csr_authorizer"
#define KDC_CSR_AUTHORIZER_VERSION_0 0
/*
* @param init Plugin initialization function (see krb5-plugin(7))
* @param minor_version The plugin minor version number (0)
* @param fini Plugin finalization function
* @param authorize Plugin CSR authorization function
*
* The authorize field is the plugin entry point that performs authorization of
* CSRs for kx509 however the plugin desires. It is invoked in no particular
* order relative to other CSR authorization plugins. The plugin authorize
* function must return KRB5_PLUGIN_NO_HANDLE if the rule is not applicable to
* it.
*
* The plugin authorize function has the following arguments, in this
* order:
*
* -# plug_ctx, the context value output by the plugin's init function
* -# context, a krb5_context
* -# config, a krb5_kdc_configuration *
* -# csr, a hx509_request
* -# client, a krb5_const_principal
* -# authorization_result, a pointer to a krb5_boolean
*
* @ingroup krb5_support
*/
typedef struct krb5plugin_csr_authorizer_ftable_desc {
int minor_version;
krb5_error_code (KRB5_LIB_CALL *init)(krb5_context, void **);
void (KRB5_LIB_CALL *fini)(void *);
krb5_error_code (KRB5_LIB_CALL *authorize)(void *, /*plug_ctx*/
krb5_context,
krb5_kdc_configuration *,
hx509_request, /*CSR*/
krb5_const_principal,/*client*/
krb5_boolean *); /*authorized*/
} krb5plugin_csr_authorizer_ftable;
#endif /* HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H */

View File

@@ -37,17 +37,53 @@
#include <getarg.h>
#include <parse_bytes.h>
static const char *sysplugin_dirs[] = {
#ifdef _WIN32
"$ORIGIN",
#else
"$ORIGIN/../lib/plugin/kdc",
#endif
#ifdef __APPLE__
LIBDIR "/plugin/kdc",
#endif
NULL
};
static void
load_kdc_plugins_once(void *ctx)
{
krb5_context context = ctx;
const char * const *dirs = sysplugin_dirs;
#ifndef _WIN32
char **cfdirs;
cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
if (cfdirs)
dirs = (const char * const *)cfdirs;
#endif
_krb5_load_plugins(context, "kdc", (const char **)dirs);
#ifndef _WIN32
krb5_config_free_strings(cfdirs);
#endif
}
krb5_error_code
krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
{
static heim_base_once_t load_kdc_plugins = HEIM_BASE_ONCE_INIT;
krb5_kdc_configuration *c;
heim_base_once_f(&load_kdc_plugins, context, load_kdc_plugins_once);
c = calloc(1, sizeof(*c));
if (c == NULL) {
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
return ENOMEM;
}
c->app = "kdc";
c->num_kdc_processes = -1;
c->require_preauth = TRUE;
c->kdc_warn_pwexpire = 0;
@@ -111,17 +147,7 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
c->enable_kx509 =
krb5_config_get_bool_default(context, NULL,
FALSE,
"kdc", "enable-kx509", NULL);
if (c->enable_kx509) {
/* These are global defaults. There are also per-realm defaults. */
c->kx509_template =
krb5_config_get_string(context, NULL,
"kdc", "kx509_template", NULL);
c->kx509_ca =
krb5_config_get_string(context, NULL,
"kdc", "kx509_ca", NULL);
}
"kdc", "enable_kx509", NULL);
#endif
c->tgt_use_strongest_session_key =

443
kdc/ipc_csr_authorizer.c Normal file
View File

@@ -0,0 +1,443 @@
/*
* 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 calling a
* service over IPC (Unix domain sockets on Linux/BSD/Illumos).
*
* The IPC protocol is request/response, with requests and responses sent as
*
* <length><string>
*
* where the <length> is 4 bytes, unsigned binary in network byte order, and
* <string> is an array of <length> bytes and does NOT include a NUL
* terminator.
*
* Requests are of the form:
*
* check <princ> <exttype>=<extvalue> ...
*
* where <princ> is a URL-escaped principal name, <exttype> is one of:
*
* - san_pkinit
* - san_xmpp
* - san_email
* - san_ms_upn
* - san_dnsname
* - eku
*
* and <extvalue> is a URL-escaped string representation of the SAN or OID.
*
* OIDs are in the form 1.2.3.4.5.6.
*
* Only characters other than alphanumeric, '@', '.', '-', '_', and '/' are
* URL-encoded.
*
* Responses are any of:
*
* - granted
* - denied
* - error message
*
* Example:
*
* C->S: check jane@TEST.H5L.SE san_dnsname=jane.foo.test.h5l.se eku=1.3.6.1.5.5.7.3.1
* S->C: granted
*
* Only digitalSignature and nonRepudiation key usages are allowed. Requested
* key usages are not sent to the CSR authorizer IPC server.
*/
#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 <heim-ipc.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 principal names and such to be
* safe for use in our IPC text messages. They function very much like URL
* encoders, but '~' also gets encoded, and '.' and '@' do not.
*
* An unescaper is not needed here.
*/
static size_t
string_encode_sz(const char *in)
{
size_t sz = strlen(in);
while (*in) {
char c = *(in++);
switch (c) {
case '@':
case '.':
case '-':
case '_':
case '/':
continue;
default:
if (isalnum(c))
continue;
sz += 2;
}
}
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;
if ((s = malloc(sz + 1)) == NULL)
return NULL;
s[sz] = '\0';
for (i = k = 0; i < len; i++) {
unsigned char c = ((const unsigned char *)in)[i];
switch (c) {
case '@':
case '.':
case '-':
case '_':
case '/':
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
cmd_append(struct rk_strpool **cmd, const char *s0, ...)
{
va_list ap;
const char *arg;
if ((*cmd = rk_strpoolprintf(*cmd, "%s", s0)) == NULL)
return ENOMEM;
va_start(ap, s0);
while ((arg = va_arg(ap, const char *))) {
char *s;
if ((s = string_encode(arg)) == NULL)
return rk_strpoolfree(*cmd), *cmd = NULL, ENOMEM;
*cmd = rk_strpoolprintf(*cmd, "%s", s);
free(s);
if (*cmd == NULL)
return ENOMEM;
}
return 0;
}
static int
call_svc(krb5_context context, heim_ipc ipc, const char *cmd)
{
heim_octet_string req, resp;
int ret;
req.data = (void *)(uintptr_t)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 = EACCES;
}
return ret;
}
if (resp.data == NULL || resp.length == 0) {
free(resp.data);
krb5_set_error_message(context, ret, "CSR authorizer IPC service "
"failed silently");
return EACCES;
}
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;
}
krb5_set_error_message(context, ret, "CSR authorizer failed %s: %.*s",
cmd, resp.length < INT_MAX ? (int)resp.length : 0,
resp.data);
return EACCES;
}
static void
frees(char **s)
{
free(*s);
*s = NULL;
}
static krb5_error_code
mark_authorized(hx509_request csr)
{
size_t i;
char *s;
int ret = 0;
for (i = 0; ret == 0; i++) {
ret = hx509_request_get_eku(csr, i, &s);
if (ret == 0)
hx509_request_authorize_eku(csr, i);
frees(&s);
}
if (ret == HX509_NO_ITEM)
ret = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
ret = hx509_request_get_san(csr, i, &san_type, &s);
if (ret == 0)
hx509_request_authorize_eku(csr, i);
frees(&s);
}
return ret == HX509_NO_ITEM ? 0 : ret;
}
static KRB5_LIB_CALL krb5_error_code
authorize(void *ctx,
krb5_context context,
krb5_kdc_configuration *config,
hx509_request csr,
krb5_const_principal client,
krb5_boolean *result)
{
struct rk_strpool *cmd = NULL;
krb5_error_code ret;
hx509_context hx509ctx = NULL;
heim_ipc ipc = NULL;
const char *svc;
KeyUsage ku;
size_t i;
char *princ = NULL;
char *s = NULL;
int do_check = 0;
if ((svc = krb5_config_get_string(context, NULL,
config->app ? config->app : "kdc",
"ipc_csr_authorizer", "service",
NULL)) == NULL)
return KRB5_PLUGIN_NO_HANDLE;
if ((ret = heim_ipc_init_context(svc, &ipc))) {
krb5_set_error_message(context, ret, "Could not set up IPC client "
"end-point for service %s", svc);
return ret;
}
if ((ret = hx509_context_init(&hx509ctx)))
goto out;
if ((ret = krb5_unparse_name(context, client, &princ)))
goto out;
if ((ret = cmd_append(&cmd, "check ", princ, NULL)))
goto enomem;
frees(&princ);
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
ret = hx509_request_get_san(csr, i, &san_type, &s);
if (ret)
break;
switch (san_type) {
case HX509_SAN_TYPE_EMAIL:
if ((ret = cmd_append(&cmd, " san_email=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_DNSNAME:
if ((ret = cmd_append(&cmd, " san_dnsname=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_XMPP:
if ((ret = cmd_append(&cmd, " san_xmpp=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_PKINIT:
if ((ret = cmd_append(&cmd, " san_pkinit=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_MS_UPN:
if ((ret = cmd_append(&cmd, " san_ms_upn=", s, NULL)))
goto enomem;
do_check = 1;
break;
default:
if ((ret = hx509_request_reject_san(csr, i)))
goto out;
break;
}
frees(&s);
}
if (ret == HX509_NO_ITEM)
ret = 0;
if (ret)
goto out;
for (i = 0; ret == 0; i++) {
ret = hx509_request_get_eku(csr, i, &s);
if (ret)
break;
if ((ret = cmd_append(&cmd, " eku=", s, NULL)))
goto enomem;
do_check = 1;
frees(&s);
}
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);
if (do_check) {
if ((s = rk_strpoolcollect(cmd)) == NULL)
goto enomem;
cmd = NULL;
if ((ret = call_svc(context, ipc, s)))
goto out;
} /* else -> permit */
if ((ret = mark_authorized(csr)))
goto out;
*result = TRUE;
ret = 0;
goto out;
enomem:
ret = krb5_enomem(context);
goto out;
out:
heim_ipc_free_context(ipc);
hx509_context_free(&hx509ctx);
if (cmd)
rk_strpoolfree(cmd);
free(princ);
free(s);
return ret;
}
static KRB5_LIB_CALL krb5_error_code
ipc_csr_authorizer_init(krb5_context context, void **c)
{
*c = NULL;
return 0;
}
static KRB5_LIB_CALL void
ipc_csr_authorizer_fini(void *c)
{
}
static krb5plugin_csr_authorizer_ftable plug_desc =
{ 1, ipc_csr_authorizer_init, ipc_csr_authorizer_fini, authorize };
static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
static uintptr_t
ipc_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(krb5_context context,
krb5_get_instance_func_t *get_instance,
size_t *num_plugins,
krb5_plugin_common_ftable_cp **plugins)
{
*get_instance = ipc_csr_authorizer_get_instance;
*num_plugins = sizeof(plugs) / sizeof(plugs[0]);
*plugins = (krb5_plugin_common_ftable_cp *)plugs;
return 0;
}

View File

@@ -93,13 +93,12 @@ typedef struct krb5_kdc_configuration {
size_t max_datagram_reply_length;
int enable_kx509;
const char *kx509_template;
const char *kx509_ca;
krb5_boolean enable_derived_keys;
int derived_keys_ndots;
int derived_keys_maxdots;
const char *app;
} krb5_kdc_configuration;
struct krb5_kdc_service {

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,12 @@
EXPORTS
kdc_authorize_csr
kdc_get_instance
kdc_issue_certificate
kdc_log
kdc_log_msg
kdc_log_msg_va
kdc_openlog
kdc_validate_token
krb5_kdc_windc_init
krb5_kdc_get_config
krb5_kdc_pkinit_config

View File

@@ -0,0 +1,322 @@
/*
* 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 is a plugin by which bx509d can validate Negotiate tokens.
*
* [kdc]
* negotiate_token_validator = {
* keytab = ...
* }
*/
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <base64.h>
#include <roken.h>
#include <krb5.h>
#include <common_plugin.h>
#include <gssapi/gssapi.h>
#include <token_validator_plugin.h>
static int
display_status(krb5_context context,
OM_uint32 major,
OM_uint32 minor,
gss_cred_id_t acred,
gss_ctx_id_t gctx,
gss_OID mech_type)
{
gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
OM_uint32 dmaj, dmin;
OM_uint32 more = 0;
char *gmmsg = NULL;
char *gmsg = NULL;
char *s = NULL;
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_GSS_CODE, GSS_C_NO_OID,
&more, &buf);
if (GSS_ERROR(dmaj) ||
buf.length >= INT_MAX ||
asprintf(&s, "%s%s%.*s", gmsg ? gmsg : "", gmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmsg);
gmsg = NULL;
break;
}
gmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
if (mech_type != GSS_C_NO_OID) {
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_MECH_CODE, mech_type,
&more, &buf);
if (GSS_ERROR(dmaj) ||
asprintf(&s, "%s%s%.*s", gmmsg ? gmmsg : "", gmmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmmsg);
gmmsg = NULL;
break;
}
gmmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
}
if (gmsg == NULL)
krb5_set_error_message(context, ENOMEM, "Error displaying GSS-API "
"status");
else
krb5_set_error_message(context, EACCES, "%s%s%s%s", gmmsg,
gmmsg ? " (" : "", gmmsg ? gmmsg : "",
gmmsg ? ")" : "");
if (acred && gctx)
krb5_prepend_error_message(context, EACCES, "Failed to validate "
"Negotiate token due to error examining "
"GSS-API security context");
else if (acred)
krb5_prepend_error_message(context, EACCES, "Failed to validate "
"Negotiate token due to error accepting "
"GSS-API security context token");
else
krb5_prepend_error_message(context, EACCES, "Failed to validate "
"Negotiate token due to error acquiring "
"GSS-API default acceptor credential");
return EACCES;
}
static KRB5_LIB_CALL krb5_error_code
validate(void *ctx,
krb5_context context,
const char *realm,
const char *token_type,
krb5_data *token,
const char * const *audiences,
size_t naudiences,
krb5_boolean *result,
krb5_principal *actual_principal,
krb5_times *token_times)
{
gss_buffer_desc adisplay_name = GSS_C_EMPTY_BUFFER;
gss_buffer_desc idisplay_name = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc input_token;
gss_cred_id_t acred = GSS_C_NO_CREDENTIAL;
gss_ctx_id_t gctx = GSS_C_NO_CONTEXT;
gss_name_t aname = GSS_C_NO_NAME;
gss_name_t iname = GSS_C_NO_NAME;
gss_OID mech_type = GSS_C_NO_OID;
const char *kt = krb5_config_get_string(context, NULL, "kdc",
"negotiate_token_validator",
"keytab", NULL);
OM_uint32 major, minor, ret_flags, time_rec;
size_t i;
char *token_decoded = NULL;
void *token_copy = NULL;
char *princ_str = NULL;
int ret = 0;
if (strcmp(token_type, "Negotiate") != 0)
return KRB5_PLUGIN_NO_HANDLE;
if (kt) {
gss_key_value_element_desc store_keytab_kv;
gss_key_value_set_desc store;
gss_OID_desc mech_set[2] = { *GSS_KRB5_MECHANISM, *GSS_SPNEGO_MECHANISM };
gss_OID_set_desc mechs = { 2, mech_set };
store_keytab_kv.key = "keytab";
store_keytab_kv.value = kt;
store.elements = &store_keytab_kv;
store.count = 1;
major = gss_acquire_cred_from(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
&mechs, GSS_C_ACCEPT, &store, &acred, NULL,
NULL);
if (major != GSS_S_COMPLETE)
return display_status(context, major, minor, acred, gctx, mech_type);
mechs.count = 1;
major = gss_set_neg_mechs(&minor, acred, &mechs);
if (major != GSS_S_COMPLETE)
return display_status(context, major, minor, acred, gctx, mech_type);
} /* else we'll use the default credential */
if ((token_decoded = malloc(token->length)) == NULL ||
(token_copy = calloc(1, token->length + 1)) == NULL)
goto enomem;
memcpy(token_copy, token->data, token->length);
if ((ret = rk_base64_decode(token_copy, token_decoded)) <= 0) {
krb5_set_error_message(context, EACCES, "Negotiate token malformed");
ret = EACCES;
goto out;
}
input_token.value = token_decoded;
input_token.length = ret;
major = gss_accept_sec_context(&minor, &gctx, acred, &input_token, NULL,
&iname, &mech_type, &output_token,
&ret_flags, &time_rec, NULL);
if (mech_type == GSS_C_NO_OID ||
!gss_oid_equal(mech_type, GSS_KRB5_MECHANISM)) {
krb5_set_error_message(context, ret = EACCES, "Negotiate token used "
"non-Kerberos mechanism");
goto out;
}
if (major != GSS_S_COMPLETE) {
ret = display_status(context, major, minor, acred, gctx, mech_type);
if (ret == 0)
ret = EINVAL;
goto out;
}
major = gss_inquire_context(&minor, gctx, NULL, &aname, NULL, NULL,
NULL, NULL, NULL);
if (major == GSS_S_COMPLETE)
major = gss_display_name(&minor, aname, &adisplay_name, NULL);
if (major == GSS_S_COMPLETE)
major = gss_display_name(&minor, iname, &idisplay_name, NULL);
if (major != GSS_S_COMPLETE) {
ret = display_status(context, major, minor, acred, gctx, mech_type);
if (ret == 0)
ret = EINVAL;
goto out;
}
for (i = 0; i < naudiences; i++) {
const char *s = adisplay_name.value;
size_t slen = adisplay_name.length;
size_t len = strlen(audiences[i]);
if (slen >= sizeof("HTTP/") - 1 &&
slen >= sizeof("HTTP/") - 1 + len &&
memcmp(s, "HTTP/", sizeof("HTTP/") - 1) == 0 &&
memcmp(s + sizeof("HTTP/") - 1, audiences[i], len) == 0 &&
s[sizeof("HTTP/") - 1 + len] == '@')
break;
}
if (i == naudiences) {
/* This handles the case where naudiences == 0 as an error */
krb5_set_error_message(context, EACCES, "Negotiate token used "
"wrong HTTP service host acceptor name");
goto out;
}
if ((princ_str = calloc(1, idisplay_name.length + 1)) == NULL)
goto enomem;
memcpy(princ_str, idisplay_name.value, idisplay_name.length);
if ((ret = krb5_parse_name(context, princ_str, actual_principal)))
goto out;
/* XXX Need name attributes to get authtime/starttime/renew_till */
token_times->authtime = 0;
token_times->starttime = time(NULL) - 300;
token_times->endtime = token_times->starttime + 300 + time_rec;
token_times->renew_till = 0;
*result = TRUE;
goto out;
enomem:
ret = krb5_enomem(context);
out:
gss_delete_sec_context(&minor, &gctx, NULL);
gss_release_buffer(&minor, &adisplay_name);
gss_release_buffer(&minor, &idisplay_name);
gss_release_buffer(&minor, &output_token);
gss_release_cred(&minor, &acred);
gss_release_name(&minor, &aname);
gss_release_name(&minor, &iname);
free(token_decoded);
free(token_copy);
free(princ_str);
return ret;
}
static KRB5_LIB_CALL krb5_error_code
negotiate_init(krb5_context context, void **c)
{
*c = NULL;
return 0;
}
static KRB5_LIB_CALL void
negotiate_fini(void *c)
{
}
static krb5plugin_token_validator_ftable plug_desc =
{ 1, negotiate_init, negotiate_fini, validate };
static krb5plugin_token_validator_ftable *plugs[] = { &plug_desc };
static uintptr_t
negotiate_get_instance(const char *libname)
{
if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
/* XXX gss_get_instance() doesn't exist :( */
return 0;
}
krb5_plugin_load_ft kdc_token_validator_plugin_load;
krb5_error_code KRB5_CALLCONV
kdc_token_validator_plugin_load(krb5_context context,
krb5_get_instance_func_t *get_instance,
size_t *num_plugins,
krb5_plugin_common_ftable_cp **plugins)
{
*get_instance = negotiate_get_instance;
*num_plugins = sizeof(plugs) / sizeof(plugs[0]);
*plugins = (krb5_plugin_common_ftable_cp *)plugs;
return 0;
}

337
kdc/simple_csr_authorizer.c Normal file
View File

@@ -0,0 +1,337 @@
/*
* 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)
* - xmpt (SAN)
* - emailt (SAN)
* - ms-upt (SAN)
* - dnsnamt (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 KRB5_LIB_CALL krb5_error_code
authorize(void *ctx,
krb5_context context,
krb5_kdc_configuration *config,
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, "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)
goto enomem;
ret = stat(p, &st) == -1 ? errno : 0;
free(san);
free(p);
free(s);
s = NULL;
if (ret)
goto skip;
ret = hx509_request_authorize_san(csr, i);
}
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) {
free(princ);
free(s);
}
ret = stat(p, &st) == -1 ? errno : 0;
free(p);
free(s);
s = NULL;
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(krb5_context 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;
}

79
kdc/test_csr_authorizer.c Normal file
View File

@@ -0,0 +1,79 @@
#include "kdc_locl.h"
static int help_flag;
static int version_flag;
static const char *app_string = "kdc";
struct getargs args[] = {
{ "help", 'h', arg_flag, &help_flag,
"Print usage message", NULL },
{ "version", 'v', arg_flag, &version_flag,
"Print version", NULL },
{ "app", 'a', arg_string, &app_string,
"App to test (kdc or bx509); default: kdc", "APPNAME" },
};
size_t num_args = sizeof(args) / sizeof(args[0]);
static int
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",
getprogname());
exit(e);
return e;
}
int
main(int argc, char **argv)
{
krb5_kdc_configuration *config;
krb5_error_code ret;
krb5_context context;
hx509_request csr;
krb5_principal princ = NULL;
const char *argv0 = argv[0];
int optidx = 0;
setprogname(argv[0]);
if (getarg(args, num_args, argc, argv, &optidx))
return usage(1);
if (help_flag)
return usage(0);
if (version_flag) {
print_version(argv[0]);
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_kdc_get_config(context, &config)))
krb5_err(context, 1, ret, "Could not get KDC configuration");
config->app = app_string;
if ((ret = krb5_initlog(context, argv0, &config->logf)) ||
(ret = krb5_addlog_dest(context, config->logf, "0-5/STDERR")))
krb5_err(context, 1, ret, "Could not set up logging to stderr");
if ((ret = krb5_kdc_set_dbinfo(context, config)))
krb5_err(context, 1, ret, "Could not get KDC configuration (HDB)");
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]);
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, config, csr, princ)))
krb5_err(context, 1, ret, "Authorization failed");
printf("Authorized!\n");
krb5_free_principal(context, princ);
krb5_free_context(context);
hx509_request_free(&csr);
/* FIXME There's no free function for config yet */
return 0;
}

146
kdc/test_kdc_ca.c Normal file
View File

@@ -0,0 +1,146 @@
#include "kdc_locl.h"
static int authorized_flag;
static int help_flag;
static const char *app_string = "kdc";
static int version_flag;
struct getargs args[] = {
{ "authorized", 'A', arg_flag, &authorized_flag,
"Assume CSR is authorized", NULL },
{ "help", 'h', arg_flag, &help_flag,
"Print usage message", NULL },
{ "app", 'a', arg_string, &app_string,
"Application name (kdc or bx509); default: kdc", "APPNAME" },
{ "version", 'v', arg_flag, &version_flag,
"Print version", NULL }
};
size_t num_args = sizeof(args) / sizeof(args[0]);
static int
usage(int e)
{
arg_printusage(args, num_args, NULL,
"PRINC PKCS10:/path/to/der/CSR [HX509-STORE]");
fprintf(stderr,
"\n\tTest kx509/bx509 online CA issuer functionality.\n"
"\n\tIf --authorized / -A not given, then authorizer plugins\n"
"\twill be invoked.\n"
"\n\tUse --app kdc to test the kx509 configuration.\n"
"\tUse --app bx509 to test the bx509 configuration.\n\n\t"
"Example: %s foo@TEST.H5L.SE PKCS10:/tmp/csr PEM-FILE:/tmp/cert\n",
getprogname());
exit(e);
return e;
}
int
main(int argc, char **argv)
{
krb5_kdc_configuration *config;
krb5_error_code ret;
krb5_principal p = NULL;
krb5_context context;
krb5_times t;
hx509_request req = NULL;
hx509_certs store = NULL;
hx509_certs certs = NULL;
const char *argv0 = argv[0];
const char *out = "MEMORY:junk-it";
int optidx = 0;
setprogname(argv[0]);
if (getarg(args, num_args, argc, argv, &optidx))
return usage(1);
if (help_flag)
return usage(0);
if (version_flag) {
print_version(argv[0]);
return 0;
}
argc -= optidx;
argv += optidx;
if (argc < 3 || argc > 4)
usage(1);
if ((errno = krb5_init_context(&context)))
err(1, "Could not initialize krb5_context");
if ((ret = krb5_kdc_get_config(context, &config)))
krb5_err(context, 1, ret, "Could not get KDC configuration");
if ((ret = krb5_initlog(context, argv0, &config->logf)) ||
(ret = krb5_addlog_dest(context, config->logf, "0-5/STDERR")))
krb5_err(context, 1, ret, "Could not set up logging to stderr");
#if 0
if ((ret = krb5_kdc_set_dbinfo(context, config)))
krb5_err(context, 1, ret, "Could not get KDC configuration (HDB)");
#endif
if ((ret = krb5_parse_name(context, argv[0], &p)))
krb5_err(context, 1, ret, "Could not parse principal %s", argv[0]);
if ((ret = hx509_request_parse(context->hx509ctx, argv[1], &req)))
krb5_err(context, 1, ret, "Could not parse PKCS#10 CSR from %s", argv[1]);
if (authorized_flag) {
KeyUsage ku = int2KeyUsage(0);
size_t i;
char *s;
/* Mark all the things authorized */
ku.digitalSignature = 1;
hx509_request_authorize_ku(req, ku);
for (i = 0; ret == 0; i++) {
ret = hx509_request_get_eku(req, i, &s);
free(s); s = NULL;
if (ret == 0)
hx509_request_authorize_eku(req, i);
}
if (ret == HX509_NO_ITEM)
ret = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
ret = hx509_request_get_san(req, i, &san_type, &s);
free(s); s = NULL;
if (ret == 0)
hx509_request_authorize_san(req, i);
}
if (ret == HX509_NO_ITEM)
ret = 0;
} else if ((ret = kdc_authorize_csr(context, config, req, p))) {
krb5_err(context, 1, ret,
"Requested certificate extensions rejected by policy");
}
memset(&t, 0, sizeof(t));
t.starttime = time(NULL);
t.endtime = t.starttime + 3600;
if ((ret = kdc_issue_certificate(context, config, req, p, &t, 1,
&certs)))
krb5_err(context, 1, ret, "Certificate issuance failed");
if (argv[2])
out = argv[2];
if ((ret = hx509_certs_init(context->hx509ctx, out, HX509_CERTS_CREATE,
NULL, &store)) ||
(ret = hx509_certs_merge(context->hx509ctx, store, certs)) ||
(ret = hx509_certs_store(context->hx509ctx, store, 0, NULL)))
/*
* If the store is a MEMORY store, say, we're really not being asked to
* store -- we're just testing the online CA functionality without
* wanting to inspect the result.
*/
if (ret != HX509_UNSUPPORTED_OPERATION)
krb5_err(context, 1, ret,
"Could not store certificate and chain in %s", out);
krb5_free_principal(context, p);
krb5_free_context(context);
hx509_request_free(&req);
hx509_certs_free(&store);
hx509_certs_free(&certs);
/* FIXME There's no free function for config yet */
return 0;
}

View File

@@ -0,0 +1,86 @@
#include "kdc_locl.h"
static int help_flag;
static int version_flag;
static char *realm;
static struct getarg_strings audiences;
struct getargs args[] = {
{ "help", 'h', arg_flag, &help_flag,
"Print usage message", NULL },
{ NULL, 'r', arg_string, &realm,
"Realm name for plugin configuration", "REALM" },
{ NULL, 'a', arg_strings, &audiences,
"expected token acceptor audience (hostname)", "ACCEPTOR-HOSTNAME" },
{ "version", 'v', arg_flag, &version_flag, "Print version", NULL }
};
size_t num_args = sizeof(args) / sizeof(args[0]);
static int
usage(int e)
{
arg_printusage(args, num_args, NULL, "TOKEN-TYPE TOKEN");
exit(e);
return e;
}
int
main(int argc, char **argv)
{
krb5_kdc_configuration *config;
krb5_error_code ret;
krb5_context context;
krb5_data token;
const char *token_type;
krb5_principal actual_princ = NULL;
krb5_times token_times;
size_t bufsz = 0;
char *buf = NULL;
char *s = NULL;
int optidx = 0;
setprogname(argv[0]);
if (getarg(args, num_args, argc, argv, &optidx))
return usage(1);
if (help_flag)
return usage(0);
if (version_flag) {
print_version(argv[0]);
return 0;
}
argc -= optidx;
argv += optidx;
if (argc != 2)
usage(1);
if ((ret = krb5_init_context(&context)))
err(1, "Could not initialize krb5_context");
if ((ret = krb5_kdc_get_config(context, &config)))
krb5_err(context, 1, ret, "Could not get KDC configuration");
token_type = argv[0];
token.data = argv[1];
if (strcmp(token.data, "-") == 0) {
if (getline(&buf, &bufsz, stdin) < 0)
err(1, "Could not read token from stdin");
token.length = bufsz;
token.data = buf;
} else {
token.length = strlen(token.data);
}
if ((ret = kdc_validate_token(context, realm, token_type, &token,
(const char * const *)audiences.strings,
audiences.num_strings, &actual_princ,
&token_times)))
krb5_err(context, 1, ret, "Could not validate %s token", token_type);
if (actual_princ && (ret = krb5_unparse_name(context, actual_princ, &s)))
krb5_err(context, 1, ret, "Could not display principal name");
if (s)
printf("Token is valid. Actual principal: %s\n", s);
else
printf("Token is valid.");
krb5_free_principal(context, actual_princ);
return 0;
}

122
kdc/token_validator.c Normal file
View File

@@ -0,0 +1,122 @@
/*
* 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.
*/
#include "kdc_locl.h"
#include "token_validator_plugin.h"
struct plctx {
const char *realm;
const char *token_kind;
krb5_data *token;
const char * const *audiences;
size_t naudiences;
krb5_boolean result;
krb5_principal actual_principal;
krb5_times token_times;
};
static krb5_error_code KRB5_LIB_CALL
plcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
{
const krb5plugin_token_validator_ftable *validator = plug;
krb5_error_code ret;
struct plctx *plctx = userctx;
ret = validator->validate(plugctx, context, plctx->realm,
plctx->token_kind, plctx->token,
plctx->audiences, plctx->naudiences,
&plctx->result, &plctx->actual_principal,
&plctx->token_times);
if (ret) {
krb5_free_principal(context, plctx->actual_principal);
plctx->actual_principal = NULL;
}
return ret;
}
static const char *plugin_deps[] = { "krb5", NULL };
static struct krb5_plugin_data token_validator_data = {
"kdc",
KDC_PLUGIN_BEARER,
1,
plugin_deps,
krb5_get_instance
};
/*
* Invoke a plugin to validate a JWT/SAML/OIDC token and partially-evaluate
* access control.
*/
krb5_error_code
kdc_validate_token(krb5_context context,
const char *realm,
const char *token_kind,
krb5_data *token,
const char * const *audiences,
size_t naudiences,
krb5_principal *actual_principal,
krb5_times *token_times)
{
krb5_error_code ret;
struct plctx ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.realm = realm;
ctx.token_kind = token_kind;
ctx.token = token;
ctx.audiences = audiences;
ctx.naudiences = naudiences;
ctx.result = FALSE;
ctx.actual_principal = NULL;
krb5_clear_error_message(context);
ret = _krb5_plugin_run_f(context, &token_validator_data, 0, &ctx,
plcallback);
if (ret == 0 && ctx.result && actual_principal) {
*actual_principal = ctx.actual_principal;
ctx.actual_principal = NULL;
}
if (token_times)
*token_times = ctx.token_times;
krb5_free_principal(context, ctx.actual_principal);
if (ret)
krb5_prepend_error_message(context, ret, "bearer token validation "
"failed: ");
else if (!ctx.result)
krb5_set_error_message(context, ret = EACCES,
"bearer token validation failed");
return ret;
}

View File

@@ -0,0 +1,85 @@
/*
* 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.
*/
#ifndef HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H
#define HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H 1
#define KDC_PLUGIN_BEARER "kdc_token_validator"
#define KDC_PLUGIN_BEARER_VERSION_0 0
/*
* @param init Plugin initialization function (see krb5-plugin(7))
* @param minor_version The plugin minor version number (0)
* @param fini Plugin finalization function
* @param validate Plugin token validation function
*
* The validate field is the plugin entry point that performs the bearer token
* validation operation however the plugin desires. It is invoked in no
* particular order relative to other bearer token validator plugins. The
* plugin validate function must return KRB5_PLUGIN_NO_HANDLE if the rule is
* not applicable to it.
*
* The plugin validate function has the following arguments, in this
* order:
*
* -# plug_ctx, the context value output by the plugin's init function
* -# context, a krb5_context
* -# realm, a const char *
* -# token_type, a const char *
* -# token, a krb5_data *
* -# audiences, a const pointer to an array of const char * containing
* expected audiences of the token (aka, acceptor names)
* -# naudiences, a size_t count of audiences
* -# requested_principal, a krb5_const_principal
* -# validation result, a pointer to a krb5_boolean
* -# actual principal, a krb5_principal * output parameter (optional)
*
* @ingroup krb5_support
*/
typedef struct krb5plugin_token_validator_ftable_desc {
int minor_version;
krb5_error_code (KRB5_LIB_CALL *init)(krb5_context, void **);
void (KRB5_LIB_CALL *fini)(void *);
krb5_error_code (KRB5_LIB_CALL *validate)(void *, /*plug_ctx*/
krb5_context,
const char *, /*realm*/
const char *, /*token_type*/
krb5_data *, /*token*/
const char * const *, /*audiences*/
size_t, /*naudiences*/
krb5_boolean *, /*valid*/
krb5_principal *, /*actual_principal*/
krb5_times *); /*token_times*/
} krb5plugin_token_validator_ftable;
#endif /* HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H */

View File

@@ -2,12 +2,15 @@
HEIMDAL_KDC_1.0 {
global:
kdc_authorize_csr;
kdc_get_instance;
kdc_issue_certificate;
kdc_log;
kdc_log_msg;
kdc_log_msg_va;
kdc_openlog;
kdc_check_flags;
kdc_validate_token;
krb5_kdc_windc_init;
krb5_kdc_get_config;
krb5_kdc_pkinit_config;