bx509d: Allow requesting longer cert lifetimes

Add a `lifetime=NUMunit` query parameter.

Also add a krb5.conf parameter to indicate whether this is allowed.
We already have a max lifetime configuration parameter.
This commit is contained in:
Nicolas Williams
2021-03-07 22:20:06 -06:00
parent 00e0475ce2
commit fbb1a4e3ec
7 changed files with 105 additions and 14 deletions

View File

@@ -113,6 +113,45 @@ Uses a thread per-client instead of as many threads as there are CPUs.
.Xc
verbose
.El
.Sh API
This service provides an HTTP-based Certification Authority (CA).
The protocol consists of a
.Ar GET
of
.Ar /bx509
with the base-63 encoding of a DER encoding of a PKCS#10
.Ar CertificationRequest
(Certificate Signing Request, or CSR) in a
.Ar csr
required query parameter.
In a successful query, the response body will contain a PEM
encoded end entity certificate and certification chain.
.Pp
Authentication is required.
Unauthenticated requests will elicit a 401 response.
.Pp
Subject Alternative Names (SANs) and Extended Key Usage values
may be requested, both in-band in the CSR as a requested
extensions attribute, and/or via optional query parameters.
.Pp
Supported query parameters (separated by ampersands)
.Bl -tag -width Ds -offset indent
.It Li csr = Va <base64-encoded-DER-encoded-CSR>
.It Li dNSName = Va <hostname>
.It Li rfc822Name = Va <email-address>
.It Li xMPPName = Va <XMPP-address>
.It Li krb5PrincipalName = Va <Kerberos-principal-name>
.It Li ms-upn = Va <UPN>
.It Li eku = Va <OID>
.It Li lifetime = Va <lifetime>
.El
.Pp
More than one name or EKU may be requested.
.Pp
Certificate lifetimes are expressed as a decimal number and
an optional unit (which defaults to
.Dq day
).
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev KRB5_CONFIG
@@ -122,6 +161,8 @@ the default being
.Pa /etc/krb5.conf .
.El
.Sh FILES
Configuration parameters are specified in
.Ar /etc/krb5.conf .
.Bl -tag -width Ds
.It Pa /etc/krb5.conf
.El

View File

@@ -117,6 +117,7 @@ typedef struct bx509_request_desc {
struct MHD_Connection *connection;
krb5_times token_times;
time_t req_life;
hx509_request req;
const char *target;
const char *redir;
@@ -667,6 +668,8 @@ bx509_param_cb(void *d,
} else if (strcmp(key, "csr") == 0 && val) {
heim_audit_addkv((heim_svc_req_desc)a->r, 0, "requested_csr", "true");
a->ret = 0; /* Handled upstairs */
} else if (strcmp(key, "lifetime") == 0 && val) {
a->r->req_life = parse_time(val, "day");
} else {
/* Produce error for unknown params */
heim_audit_addkv((heim_svc_req_desc)a->r, 0, "requested_unknown", "true");
@@ -798,7 +801,8 @@ do_CA(struct bx509_request_desc *r, const char *csr)
/* Issue the certificate */
ret = kdc_issue_certificate(r->context, "bx509", logfac, r->req, p,
&r->token_times, 1 /* send_chain */, &certs);
&r->token_times, r->req_life,
1 /* send_chain */, &certs);
krb5_free_principal(r->context, p);
if (ret) {
if (ret == KRB5KDC_ERR_POLICY || ret == EACCES)
@@ -872,6 +876,7 @@ set_req_desc(struct MHD_Connection *connection,
r->cname = NULL;
r->addr = NULL;
r->req = NULL;
r->req_life = 0;
r->kv = heim_array_create();
ci = MHD_get_connection_info(connection,
MHD_CONNECTION_INFO_CLIENT_ADDRESS);
@@ -1268,8 +1273,8 @@ bnegotiate_do_CA(struct bx509_request_desc *r)
/* Issue the certificate */
if (ret == 0)
ret = kdc_issue_certificate(r->context, "bx509", logfac, req, p,
&r->token_times, 1 /* send_chain */,
&certs);
&r->token_times, 0,
1 /* send_chain */, &certs);
krb5_free_principal(r->context, p);
hx509_request_free(&req);
p = NULL;

View File

@@ -104,6 +104,7 @@ kdc_issue_certificate(krb5_context context,
hx509_request req,
krb5_principal cprinc,
krb5_times *auth_times,
time_t req_life,
int send_chain,
hx509_certs *out)
{
@@ -122,7 +123,9 @@ kdc_issue_certificate(krb5_context context,
(const heim_config_binding *)cf,
logf, req, &cprinc2,
auth_times->starttime,
auth_times->endtime, send_chain,
auth_times->endtime,
req_life,
send_chain,
out);
if (ret == EACCES)
ret = KRB5KDC_ERR_POLICY;

View File

@@ -1005,8 +1005,8 @@ _kdc_do_kx509(kx509_req_context r)
krb5_data_zero(rep.certificate);
krb5_ticket_get_times(r->context, ticket, &r->ticket_times);
ret = kdc_issue_certificate(r->context, r->config->app, r->logf, r->csr,
cprincipal, &r->ticket_times, r->send_chain,
&certs);
cprincipal, &r->ticket_times, 0 /*req_life*/,
r->send_chain, &certs);
if (ret) {
int level = 1;
const char *msg = krb5_get_error_message(r->context, ret);

View File

@@ -2,12 +2,15 @@
static int authorized_flag;
static int help_flag;
static char *lifetime_string;
static const char *app_string = "kdc";
static int version_flag;
struct getargs args[] = {
{ "authorized", 'A', arg_flag, &authorized_flag,
"Assume CSR is authorized", NULL },
{ "lifetime", 'l', arg_string, &lifetime_string,
"Certificate lifetime desired", "TIME" },
{ "help", 'h', arg_flag, &help_flag,
"Print usage message", NULL },
{ "app", 'a', arg_string, &app_string,
@@ -78,6 +81,7 @@ main(int argc, char **argv)
hx509_certs certs = NULL;
const char *argv0 = argv[0];
const char *out = "MEMORY:junk-it";
time_t req_life = 0;
int optidx = 0;
setprogname(argv[0]);
@@ -143,8 +147,9 @@ main(int argc, char **argv)
memset(&t, 0, sizeof(t));
t.starttime = time(NULL);
t.endtime = t.starttime + 3600;
if ((ret = kdc_issue_certificate(context, app_string, logf, req, p, &t, 1,
&certs)))
req_life = lifetime_string ? parse_time(lifetime_string, "day") : 0;
if ((ret = kdc_issue_certificate(context, app_string, logf, req, p, &t,
req_life, 1, &certs)))
krb5_err(context, 1, ret, "Certificate issuance failed");
if (argv[2])

View File

@@ -2835,6 +2835,7 @@ enomem:
static heim_error_code
tbs_set_times(hx509_context context,
const heim_config_binding *cf,
heim_log_facility *logf,
time_t starttime,
time_t endtime,
time_t req_life,
@@ -2847,15 +2848,29 @@ tbs_set_times(hx509_context context,
time_t clamp =
heim_config_get_time_default(context->hcontext, cf, 0,
"max_cert_lifetime", NULL);
int allow_more =
heim_config_get_bool_default(context->hcontext, cf, FALSE,
"allow_extra_lifetime", NULL);
if (!allow_more && fudge && now + fudge > endtime)
allow_more = 1;
starttime = starttime ? starttime : now - 5 * 60;
if (fudge && now + fudge > endtime)
endtime = now + fudge;
if (req_life && req_life < endtime - now)
if (req_life > 0 && req_life < endtime - now)
endtime = now + req_life;
if (clamp && clamp < endtime - now)
endtime = now + clamp;
if (endtime < now) {
heim_log_msg(context->hcontext, logf, 3, NULL,
"Endtime would be in the past");
hx509_set_error_string(context, 0, ERANGE,
"Endtime would be in the past");
return ERANGE;
}
hx509_ca_tbs_set_notAfter(context, tbs, endtime);
hx509_ca_tbs_set_notBefore(context, tbs, starttime);
return 0;
@@ -2874,6 +2889,7 @@ _hx509_ca_issue_certificate(hx509_context context,
KRB5PrincipalName *cprinc,
time_t starttime,
time_t endtime,
time_t req_life,
int send_chain,
hx509_certs *out)
{
@@ -2995,8 +3011,8 @@ _hx509_ca_issue_certificate(hx509_context context,
/* Work out cert expiration */
if (ret == 0)
ret = tbs_set_times(context, cf, starttime, endtime,
0 /* XXX req_life */, tbs);
ret = tbs_set_times(context, cf, logf, starttime, endtime, req_life,
tbs);
/* Expand the subjectName template in the TBS using the env */
if (ret == 0)

View File

@@ -827,9 +827,30 @@ for non-default client and server certificates.
and where the parameters are as follows:
.Bl -tag -width "xxx" -offset indent
.It Li ca = Va file
Specifies the PEM credentials for the kx509 certification
authority. If not specified for any specific use-case, then that
use-case will be disabled.
Specifies the PEM credentials for the kx509 / bx509d certification
authority.
If not specified for any specific use-case, then that use-case
will be disabled.
.It Li max_cert_lifetime = Va NUMunit
Specifies the maximum certificate lifetime as a decimal number
and an optional unit (the default unit is
.Dq day
).
.It Li force_cert_lifetime = Va NUMunit
Specifies a minimum certificate lifetime as a decimal number and
an optional unit (the default unit is
.Dq day
).
.It Li allow_extra_lifetime = Va boolean
Indicates whether a client may request longer lifetimes than
their authentication credentials.
Defaults to false.
If a
.Li force_cert_lifetime
is specified, then
.Li allow_extra_lifetime
is implicitly forced to
.Va true .
.It Li require_initial_kca_tickets = Va boolean
Specified whether to require that tickets for the
.Li kca_service