kdc: Modernize kx509 logging too

This commit is contained in:
Nicolas Williams
2019-12-11 11:44:26 -06:00
parent 608c2876d4
commit 1d5062b167
9 changed files with 329 additions and 233 deletions

View File

@@ -219,7 +219,7 @@ characterize(krb5_context context,
*/ */
static const krb5_config_binding * static const krb5_config_binding *
get_cf(krb5_context context, get_cf(krb5_context context,
const char *toplevel, krb5_kdc_configuration *config,
hx509_request req, hx509_request req,
krb5_principal cprinc) krb5_principal cprinc)
{ {
@@ -236,6 +236,7 @@ get_cf(krb5_context context,
size_t nsans = 0; size_t nsans = 0;
if (ncomp == 0) { if (ncomp == 0) {
kdc_log(context, config, 5, "Client principal has no components!");
krb5_set_error_message(context, ENOTSUP, krb5_set_error_message(context, ENOTSUP,
"Client principal has no components!"); "Client principal has no components!");
return NULL; return NULL;
@@ -243,8 +244,8 @@ get_cf(krb5_context context,
if ((ret = count_sans(req, &nsans)) || if ((ret = count_sans(req, &nsans)) ||
(certtype = characterize(context, cprinc, req)) == CERT_NOTSUP) { (certtype = characterize(context, cprinc, req)) == CERT_NOTSUP) {
krb5_set_error_message(context, ret, kdc_log(context, config, 5, "Could not characterize CSR");
"Could not characterize CSR"); krb5_set_error_message(context, ret, "Could not characterize CSR");
return NULL; return NULL;
} }
@@ -274,19 +275,26 @@ get_cf(krb5_context context,
} }
} }
if (strcmp(toplevel, "kdc") == 0) if (strcmp(config->app, "kdc") == 0)
cf = krb5_config_get_list(context, NULL, toplevel, "realms", realm, cf = krb5_config_get_list(context, NULL, config->app, "realms", realm,
"kx509", label, svc, NULL); "kx509", label, svc, NULL);
else else
cf = krb5_config_get_list(context, NULL, toplevel, "realms", realm, cf = krb5_config_get_list(context, NULL, config->app, "realms", realm,
label, svc, NULL); label, svc, NULL);
if (cf == NULL) if (cf == NULL) {
krb5_set_error_message(context, ENOTSUP, kdc_log(context, config, 3,
"No %s configuration for %s %s certificates [%s] realm " "No %s configuration for %s %s certificates [%s] realm "
"-> %s -> kx509 -> %s%s%s", "-> %s -> kx509 -> %s%s%s",
strcmp(toplevel, "bx509") == 0 ? "bx509" : "kx509", strcmp(config->app, "bx509") == 0 ? "bx509" : "kx509",
def, label, toplevel, realm, label, def, label, config->app, realm, label,
svc ? " -> " : "", svc ? svc : ""); svc ? " -> " : "", svc ? svc : "");
krb5_set_error_message(context, KRB5KDC_ERR_POLICY,
"No %s configuration for %s %s certificates [%s] realm "
"-> %s -> kx509 -> %s%s%s",
strcmp(config->app, "bx509") == 0 ? "bx509" : "kx509",
def, label, config->app, realm, label,
svc ? " -> " : "", svc ? svc : "");
}
return cf; return cf;
} }
@@ -304,6 +312,7 @@ get_cf(krb5_context context,
*/ */
static krb5_error_code static krb5_error_code
set_template(krb5_context context, set_template(krb5_context context,
krb5_kdc_configuration *config,
const krb5_config_binding *cf, const krb5_config_binding *cf,
hx509_ca_tbs tbs) hx509_ca_tbs tbs)
{ {
@@ -329,6 +338,9 @@ set_template(krb5_context context,
ret = hx509_get_one_cert(context->hx509ctx, certs, &template); ret = hx509_get_one_cert(context->hx509ctx, certs, &template);
hx509_certs_free(&certs); hx509_certs_free(&certs);
if (ret) { if (ret) {
kdc_log(context, config, 1,
"Failed to load certificate template from %s",
cert_template);
krb5_set_error_message(context, KRB5KDC_ERR_POLICY, krb5_set_error_message(context, KRB5KDC_ERR_POLICY,
"Failed to load certificate template from " "Failed to load certificate template from "
"%s", cert_template); "%s", cert_template);
@@ -406,6 +418,7 @@ set_template(krb5_context context,
*/ */
static krb5_error_code static krb5_error_code
set_tbs(krb5_context context, set_tbs(krb5_context context,
krb5_kdc_configuration *config,
const krb5_config_binding *cf, const krb5_config_binding *cf,
hx509_request req, hx509_request req,
krb5_principal cprinc, krb5_principal cprinc,
@@ -438,7 +451,7 @@ set_tbs(krb5_context context,
/* Populate requested certificate extensions from CSR/CSRPlus if allowed */ /* Populate requested certificate extensions from CSR/CSRPlus if allowed */
ret = hx509_ca_tbs_set_from_csr(context->hx509ctx, tbs, req); ret = hx509_ca_tbs_set_from_csr(context->hx509ctx, tbs, req);
if (ret == 0) if (ret == 0)
ret = set_template(context, cf, tbs); ret = set_template(context, config, cf, tbs);
/* /*
* Optionally add PKINIT SAN. * Optionally add PKINIT SAN.
@@ -520,17 +533,23 @@ set_tbs(krb5_context context,
} }
} }
} else { } else {
kdc_log(context, config, 5, "kx509/bx509 client %s has too many "
"components!", princ);
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY, krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"kx509/bx509 client %s has too many " "kx509/bx509 client %s has too many "
"components!", princ); "components!", princ);
} }
out: out:
if (ret == ENOMEM)
goto enomem;
krb5_xfree(princ_no_realm); krb5_xfree(princ_no_realm);
krb5_xfree(princ); krb5_xfree(princ);
return ret; return ret;
enomem: enomem:
kdc_log(context, config, 0,
"Could not set up TBSCertificate: Out of memory");
ret = krb5_enomem(context); ret = krb5_enomem(context);
goto out; goto out;
} }
@@ -572,7 +591,7 @@ tbs_set_times(krb5_context context,
*/ */
krb5_error_code krb5_error_code
kdc_issue_certificate(krb5_context context, kdc_issue_certificate(krb5_context context,
const krb5_kdc_configuration *config, krb5_kdc_configuration *config,
hx509_request req, hx509_request req,
krb5_principal cprinc, krb5_principal cprinc,
krb5_times *auth_times, krb5_times *auth_times,
@@ -596,21 +615,25 @@ kdc_issue_certificate(krb5_context context,
hx509_request_authorize_ku(req, ku); hx509_request_authorize_ku(req, ku);
/* Get configuration */ /* Get configuration */
if ((cf = get_cf(context, config->app, req, cprinc)) == NULL) if ((cf = get_cf(context, config, req, cprinc)) == NULL)
return KRB5KDC_ERR_POLICY; return KRB5KDC_ERR_POLICY;
if ((ca = krb5_config_get_string(context, cf, "ca", NULL)) == NULL) { if ((ca = krb5_config_get_string(context, cf, "ca", NULL)) == NULL) {
kdc_log(context, config, 3, "No kx509 CA issuer credential specified");
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY, krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"No kx509 CA issuer credential specified"); "No kx509 CA issuer credential specified");
return ret; return ret;
} }
ret = hx509_ca_tbs_init(context->hx509ctx, &tbs); ret = hx509_ca_tbs_init(context->hx509ctx, &tbs);
if (ret) if (ret) {
kdc_log(context, config, 0,
"Failed to create certificate: Out of memory");
return ret; return ret;
}
/* Lookup a template and set things in `env' and `tbs' as appropriate */ /* Lookup a template and set things in `env' and `tbs' as appropriate */
if (ret == 0) if (ret == 0)
ret = set_tbs(context, cf, req, cprinc, &env, tbs); ret = set_tbs(context, config, cf, req, cprinc, &env, tbs);
/* Populate generic template "env" variables */ /* Populate generic template "env" variables */
@@ -622,10 +645,13 @@ kdc_issue_certificate(krb5_context context,
* issue a certificate now. * issue a certificate now.
*/ */
if (ret == 0 && hx509_name_is_null_p(hx509_ca_tbs_get_name(tbs)) && if (ret == 0 && hx509_name_is_null_p(hx509_ca_tbs_get_name(tbs)) &&
!has_sans(req)) !has_sans(req)) {
kdc_log(context, config, 3,
"Not issuing certificate because it would have no names");
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY, krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"Not issuing certificate because it " "Not issuing certificate because it "
"would have no names"); "would have no names");
}
if (ret) if (ret)
goto out; goto out;
@@ -646,7 +672,10 @@ kdc_issue_certificate(krb5_context context,
ret = hx509_certs_init(context->hx509ctx, ca, 0, NULL, &certs); ret = hx509_certs_init(context->hx509ctx, ca, 0, NULL, &certs);
if (ret) { if (ret) {
krb5_set_error_message(context, ret, "Failed to load CA %s", ca); kdc_log(context, config, 1,
"Failed to load CA certificate and private key %s", ca);
krb5_set_error_message(context, ret, "Failed to load CA "
"certificate and private key %s", ca);
goto out; goto out;
} }
ret = hx509_query_alloc(context->hx509ctx, &q); ret = hx509_query_alloc(context->hx509ctx, &q);
@@ -662,7 +691,11 @@ kdc_issue_certificate(krb5_context context,
hx509_query_free(context->hx509ctx, q); hx509_query_free(context->hx509ctx, q);
hx509_certs_free(&certs); hx509_certs_free(&certs);
if (ret) { if (ret) {
krb5_set_error_message(context, ret, "Failed to find a CA in %s", ca); kdc_log(context, config, 1,
"Failed to find a CA certificate in %s", ca);
krb5_set_error_message(context, ret,
"Failed to find a CA certificate in %s",
ca);
goto out; goto out;
} }
} }

View File

@@ -93,12 +93,8 @@
#include <parse_units.h> #include <parse_units.h>
#include <krb5.h> #include <krb5.h>
#include <krb5_locl.h> #include <krb5_locl.h>
#ifdef DIGEST
#include <digest_asn1.h> #include <digest_asn1.h>
#endif
#ifdef KX509
#include <kx509_asn1.h> #include <kx509_asn1.h>
#endif
#include <hdb.h> #include <hdb.h>
#include <hdb_err.h> #include <hdb_err.h>
#include <der.h> #include <der.h>

View File

@@ -43,6 +43,7 @@
#include <hdb.h> #include <hdb.h>
#include <krb5.h> #include <krb5.h>
#include <kx509_asn1.h>
enum krb5_kdc_trpolicy { enum krb5_kdc_trpolicy {
TRPOLICY_ALWAYS_CHECK, TRPOLICY_ALWAYS_CHECK,
@@ -103,6 +104,7 @@ typedef struct krb5_kdc_configuration {
typedef struct kdc_request_desc *kdc_request_t; typedef struct kdc_request_desc *kdc_request_t;
typedef struct astgs_request_desc *astgs_request_t; typedef struct astgs_request_desc *astgs_request_t;
typedef struct kx509_req_context_desc *kx509_req_context;
struct krb5_kdc_service { struct krb5_kdc_service {
unsigned int flags; unsigned int flags;

View File

@@ -41,8 +41,6 @@
#include "headers.h" #include "headers.h"
typedef struct pk_client_params pk_client_params; typedef struct pk_client_params pk_client_params;
struct DigestREQ;
struct Kx509Request;
#include <kdc-private.h> #include <kdc-private.h>
@@ -117,6 +115,21 @@ struct astgs_request_desc {
KDCFastState fast; KDCFastState fast;
}; };
typedef struct kx509_req_context_desc {
KDC_REQUEST_DESC_COMMON_ELEMENTS;
struct Kx509Request req;
Kx509CSRPlus csr_plus;
krb5_auth_context ac;
const char *realm; /* XXX Confusion: is this crealm or srealm? */
krb5_keyblock *key;
hx509_request csr;
krb5_times ticket_times;
unsigned int send_chain:1; /* Client expects a full chain */
unsigned int have_csr:1; /* Client sent a CSR */
} *kx509_req_context;
extern sig_atomic_t exit_flag; extern sig_atomic_t exit_flag;
extern size_t max_request_udp; extern size_t max_request_udp;
extern size_t max_request_tcp; extern size_t max_request_tcp;

View File

@@ -100,31 +100,14 @@
static const unsigned char version_2_0[4] = {0 , 0, 2, 0}; static const unsigned char version_2_0[4] = {0 , 0, 2, 0};
typedef struct kx509_req_context {
krb5_kdc_configuration *config;
const struct Kx509Request *req;
Kx509CSRPlus csr_plus;
krb5_auth_context ac;
const char *realm; /* XXX Confusion: is this crealm or srealm? */
char *sname;
char *cname;
struct sockaddr *addr;
const char *from;
krb5_keyblock *key;
hx509_request csr;
krb5_data *reply;
krb5_times ticket_times;
unsigned int send_chain:1; /* Client expects a full chain */
unsigned int have_csr:1; /* Client sent a CSR */
} *kx509_req_context;
/* /*
* Taste the request to see if it's a kx509 request. * Taste the request to see if it's a kx509 request.
*/ */
krb5_error_code krb5_error_code
_kdc_try_kx509_request(void *ptr, size_t len, struct Kx509Request *req) _kdc_try_kx509_request(kx509_req_context r)
{ {
const unsigned char *p = (const void *)(uintptr_t)ptr; const unsigned char *p = (const void *)(uintptr_t)r->request.data;
size_t len = r->request.length;
size_t sz; size_t sz;
if (len < sizeof(version_2_0)) if (len < sizeof(version_2_0))
@@ -135,7 +118,8 @@ _kdc_try_kx509_request(void *ptr, size_t len, struct Kx509Request *req)
len -= sizeof(version_2_0); len -= sizeof(version_2_0);
if (len == 0) if (len == 0)
return -1; return -1;
return decode_Kx509Request(p, len, req, &sz); memset(&r->req, 0, sizeof(r->req));
return decode_Kx509Request(p, len, &r->req, &sz);
} }
static krb5_boolean static krb5_boolean
@@ -194,37 +178,6 @@ verify_req_hash(krb5_context context,
return 0; return 0;
} }
/* Wrapper around kdc_log() that adds contextual information */
static void
kx509_log(krb5_context context,
kx509_req_context reqctx,
int level,
const char *fmt,
...)
{
va_list ap;
char *msg;
va_start(ap, fmt);
if (vasprintf(&msg, fmt, ap) == -1 || msg == NULL) {
kdc_log(context, reqctx->config, level,
"Out of memory while formatting log message");
va_end(ap);
va_start(ap, fmt);
kdc_vlog(context, reqctx->config, level, fmt, ap);
va_end(ap);
return;
}
va_end(ap);
kdc_log(context, reqctx->config, level,
"kx509 %s (from %s for %s, service %s)", msg,
reqctx->from ? reqctx->from : "<unknown>",
reqctx->cname ? reqctx->cname : "<unknown-client-principal>",
reqctx->sname ? reqctx->sname : "<unknown-service-principal>");
free(msg);
}
/* /*
* Set the HMAC in the response. * Set the HMAC in the response.
*/ */
@@ -243,8 +196,7 @@ calculate_reply_hash(krb5_context context,
ret = krb5_data_alloc(rep->hash, HMAC_size(&ctx)); ret = krb5_data_alloc(rep->hash, HMAC_size(&ctx));
if (ret) { if (ret) {
HMAC_CTX_cleanup(&ctx); HMAC_CTX_cleanup(&ctx);
krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); return krb5_enomem(context);
return ENOMEM;
} }
HMAC_Update(&ctx, version_2_0, sizeof(version_2_0)); HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
@@ -347,7 +299,8 @@ kdc_kx509_verify_service_principal(krb5_context context,
KRB5_TGS_NAME) == 0) { KRB5_TGS_NAME) == 0) {
const char *r = krb5_principal_get_comp_string(context, sprincipal, 1); const char *r = krb5_principal_get_comp_string(context, sprincipal, 1);
if ((ret = is_local_realm(context, reqctx, r))) if ((ret = is_local_realm(context, reqctx, r)))
kx509_log(context, reqctx, 2, "client used wrong krbtgt for kx509"); _kdc_audit_addreason((kdc_request_t)reqctx,
"Client used wrong krbtgt for kx509");
goto out; goto out;
} }
@@ -355,8 +308,9 @@ kdc_kx509_verify_service_principal(krb5_context context,
ret = gethostname(localhost, sizeof(localhost) - 1); ret = gethostname(localhost, sizeof(localhost) - 1);
if (ret != 0) { if (ret != 0) {
ret = errno; ret = errno;
krb5_set_error_message(context, ret, kdc_log(context, reqctx->config, 0, "Failed to get local hostname");
N_("Failed to get local hostname", "")); _kdc_audit_addreason((kdc_request_t)reqctx,
"Failed to get local hostname");
return ret; return ret;
} }
localhost[sizeof(localhost) - 1] = '\0'; localhost[sizeof(localhost) - 1] = '\0';
@@ -375,8 +329,8 @@ err:
goto out; goto out;
ret = KRB5KDC_ERR_SERVER_NOMATCH; ret = KRB5KDC_ERR_SERVER_NOMATCH;
kx509_log(context, reqctx, 2, "client used wrong kx509 service principal " _kdc_audit_addreason((kdc_request_t)reqctx, "Client used wrong kx509 "
"(expected %s)", expected); "service principal (expected %s)", expected);
out: out:
krb5_xfree(expected); krb5_xfree(expected);
@@ -397,10 +351,8 @@ encode_reply(krb5_context context,
reqctx->reply->data = NULL; reqctx->reply->data = NULL;
reqctx->reply->length = 0; reqctx->reply->length = 0;
ASN1_MALLOC_ENCODE(Kx509Response, data.data, data.length, r, &size, ret); ASN1_MALLOC_ENCODE(Kx509Response, data.data, data.length, r, &size, ret);
if (ret) { if (ret)
kdc_log(context, reqctx->config, 1, "Failed to encode kx509 reply");
return ret; return ret;
}
if (size != data.length) if (size != data.length)
krb5_abortx(context, "ASN1 internal error"); krb5_abortx(context, "ASN1 internal error");
@@ -418,6 +370,7 @@ encode_reply(krb5_context context,
static krb5_error_code static krb5_error_code
mk_error_response(krb5_context context, mk_error_response(krb5_context context,
kx509_req_context reqctx, kx509_req_context reqctx,
int level,
int32_t code, int32_t code,
const char *fmt, const char *fmt,
...) ...)
@@ -430,6 +383,21 @@ mk_error_response(krb5_context context,
char *freeme1 = NULL; char *freeme1 = NULL;
va_list ap; va_list ap;
if (code != 0) {
/* Log errors where _kdc_audit_trail() is not enough */
if (code == ENOMEM)
level = 0;
if (level < 3) {
va_start(ap, fmt);
kdc_vlog(context, reqctx->config, level, fmt, ap);
va_end(ap);
}
va_start(ap, fmt);
_kdc_audit_vaddreason((kdc_request_t)reqctx, fmt, ap);
va_end(ap);
}
if (!reqctx->config->enable_kx509) if (!reqctx->config->enable_kx509)
code = KRB5KDC_ERR_POLICY; code = KRB5KDC_ERR_POLICY;
@@ -460,8 +428,6 @@ mk_error_response(krb5_context context,
msg = freeme1; msg = freeme1;
} }
kdc_log(context, reqctx->config, 1, "%s", msg);
rep.hash = NULL; rep.hash = NULL;
rep.certificate = NULL; rep.certificate = NULL;
rep.error_code = code; rep.error_code = code;
@@ -569,9 +535,14 @@ update_csr(krb5_context context, kx509_req_context reqctx, Extensions *exts)
free_GeneralNames(&san); free_GeneralNames(&san);
} }
} }
if (ret) if (ret) {
kx509_log(context, reqctx, 2, kdc_log(context, reqctx->config, 1,
"request has bad desired certificate extensions"); "Error handling requested extensions: %s",
krb5_get_error_message(context, ret));
_kdc_audit_addreason((kdc_request_t)reqctx,
"Error handling requested extensions: %s",
krb5_get_error_message(context, ret));
}
return ret; return ret;
} }
@@ -585,7 +556,7 @@ get_csr(krb5_context context, kx509_req_context reqctx)
{ {
krb5_error_code ret; krb5_error_code ret;
RSAPublicKey rsapkey; RSAPublicKey rsapkey;
heim_octet_string pk_key = reqctx->req->pk_key; heim_octet_string pk_key = reqctx->req.pk_key;
size_t size; size_t size;
ret = decode_Kx509CSRPlus(pk_key.data, pk_key.length, &reqctx->csr_plus, ret = decode_Kx509CSRPlus(pk_key.data, pk_key.length, &reqctx->csr_plus,
@@ -597,38 +568,50 @@ get_csr(krb5_context context, kx509_req_context reqctx)
/* Parse CSR */ /* Parse CSR */
ret = hx509_request_parse_der(context->hx509ctx, &reqctx->csr_plus.csr, ret = hx509_request_parse_der(context->hx509ctx, &reqctx->csr_plus.csr,
&reqctx->csr); &reqctx->csr);
if (ret)
kx509_log(context, reqctx, 2, "invalid CSR");
/* /*
* Handle any additional Certificate Extensions requested out of band * Handle any additional Certificate Extensions requested out of band
* of the CSR. * of the CSR.
*/ */
if (ret == 0) if (ret == 0)
return update_csr(context, reqctx, reqctx->csr_plus.exts); return update_csr(context, reqctx, reqctx->csr_plus.exts);
_kdc_audit_addreason((kdc_request_t)reqctx, "Invalid CSR");
return ret; return ret;
} }
reqctx->send_chain = 0; reqctx->send_chain = 0;
reqctx->have_csr = 0; reqctx->have_csr = 0;
/* Check if proof of possession is required by configuration */ /* Check if proof of possession is required by configuration */
if (!get_bool_param(context, FALSE, reqctx->realm, "require_csr")) if (!get_bool_param(context, FALSE, reqctx->realm, "require_csr")) {
return mk_error_response(context, reqctx, KX509_STATUS_CLIENT_USE_CSR, _kdc_audit_addreason((kdc_request_t)reqctx,
"CSRs required but client did not send one"); "CSRs required but client did not send one");
krb5_set_error_message(context, KX509_STATUS_CLIENT_USE_CSR,
"CSRs required but kx509 client did not send "
"one");
return KX509_STATUS_CLIENT_USE_CSR;
}
/* Attempt to decode pk_key as RSAPublicKey */ /* Attempt to decode pk_key as RSAPublicKey */
ret = decode_RSAPublicKey(reqctx->req->pk_key.data, ret = decode_RSAPublicKey(reqctx->req.pk_key.data,
reqctx->req->pk_key.length, reqctx->req.pk_key.length,
&rsapkey, &size); &rsapkey, &size);
free_RSAPublicKey(&rsapkey); free_RSAPublicKey(&rsapkey);
if (ret == 0 && size == reqctx->req->pk_key.length) if (ret == 0 && size == reqctx->req.pk_key.length)
return make_csr(context, reqctx, &pk_key); /* Make pretend CSR */ return make_csr(context, reqctx, &pk_key); /* Make pretend CSR */
/* Not an RSAPublicKey or garbage follows it */ /* Not an RSAPublicKey or garbage follows it */
if (ret == 0) if (ret == 0) {
kx509_log(context, reqctx, 2, "request has garbage after key"); ret = KRB5KDC_ERR_NULL_KEY;
return mk_error_response(context, reqctx, KRB5KDC_ERR_NULL_KEY, _kdc_audit_addreason((kdc_request_t)reqctx,
"Request has garbage after key");
krb5_set_error_message(context, ret, "Request has garbage after key");
return ret;
}
_kdc_audit_addreason((kdc_request_t)reqctx,
"Could not decode CSR or RSA subject public key"); "Could not decode CSR or RSA subject public key");
krb5_set_error_message(context, ret,
"Could not decode CSR or RSA subject public key");
return ret;
} }
/* /*
@@ -664,6 +647,7 @@ check_authz(krb5_context context,
const char *comp0 = krb5_principal_get_comp_string(context, cprincipal, 0); const char *comp0 = krb5_principal_get_comp_string(context, cprincipal, 0);
const char *comp1 = krb5_principal_get_comp_string(context, cprincipal, 1); const char *comp1 = krb5_principal_get_comp_string(context, cprincipal, 1);
unsigned int ncomp = krb5_principal_get_num_comp(context, cprincipal); unsigned int ncomp = krb5_principal_get_num_comp(context, cprincipal);
hx509_san_type san_type;
KeyUsage ku, ku_allowed; KeyUsage ku, ku_allowed;
size_t i; size_t i;
const heim_oid *eku_whitelist[] = { const heim_oid *eku_whitelist[] = {
@@ -683,23 +667,46 @@ check_authz(krb5_context context,
return 0; return 0;
ret = kdc_authorize_csr(context, reqctx->config, reqctx->csr, cprincipal); ret = kdc_authorize_csr(context, reqctx->config, reqctx->csr, cprincipal);
if (ret == 0) { if (ret == 0) {
kx509_log(context, reqctx, 0, "Requested extensions authorized " _kdc_audit_addkv((kdc_request_t)reqctx, 0, "authorized", "true");
"by plugin");
ret = hx509_request_get_san(reqctx->csr, 0, &san_type, &s);
if (ret == 0) {
const char *san_type_s;
/* This should be an hx509 function... */
switch (san_type) {
case HX509_SAN_TYPE_EMAIL: san_type_s = "rfc822Name"; break;
case HX509_SAN_TYPE_DNSNAME: san_type_s = "dNSName"; break;
case HX509_SAN_TYPE_DN: san_type_s = "DN"; break;
case HX509_SAN_TYPE_REGISTERED_ID: san_type_s = "registeredID"; break;
case HX509_SAN_TYPE_XMPP: san_type_s = "xMPPName"; break;
case HX509_SAN_TYPE_PKINIT: san_type_s = "krb5PrincipalName"; break;
case HX509_SAN_TYPE_MS_UPN: san_type_s = "ms-UPN"; break;
default: san_type_s = "unknown"; break;
}
_kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0_type", "%s",
san_type_s);
_kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0", "%s", s);
free(s);
}
ret = hx509_request_get_eku(reqctx->csr, 0, &s);
if (ret == 0) {
_kdc_audit_addkv((kdc_request_t)reqctx, 0, "eku0", "%s", s);
free(s);
}
return 0; return 0;
} }
if (ret != KRB5_PLUGIN_NO_HANDLE) { if (ret != KRB5_PLUGIN_NO_HANDLE) {
kx509_log(context, reqctx, 0, "Requested extensions rejected " _kdc_audit_addreason((kdc_request_t)reqctx,
"by plugin"); "Requested extensions rejected by plugin");
return ret; return ret;
} }
/* Default authz */ /* Default authz */
if ((ret = krb5_unparse_name(context, cprincipal, &cprinc))) if ((ret = krb5_unparse_name(context, cprincipal, &cprinc)))
return ret; return ret;
for (i = 0; ret == 0; i++) { for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
frees(&s); frees(&s);
ret = hx509_request_get_san(reqctx->csr, i, &san_type, &s); ret = hx509_request_get_san(reqctx->csr, i, &san_type, &s);
@@ -710,22 +717,28 @@ check_authz(krb5_context context,
if (ncomp != 2 || strcasecmp(comp1, s) != 0 || if (ncomp != 2 || strcasecmp(comp1, s) != 0 ||
strchr(s, '.') == NULL || strchr(s, '.') == NULL ||
!check_authz_svc_ok(context, comp0)) { !check_authz_svc_ok(context, comp0)) {
kx509_log(context, reqctx, 0, "Requested extensions rejected " _kdc_audit_addreason((kdc_request_t)reqctx,
"by default policy (dNSName SAN %s does not match " "Requested extensions rejected by "
"client %s)", s, cprinc); "default policy (dNSName SAN "
"does not match client)");
goto eacces; goto eacces;
} }
break; break;
case HX509_SAN_TYPE_PKINIT: case HX509_SAN_TYPE_PKINIT:
if (strcmp(cprinc, s) != 0) { if (strcmp(cprinc, s) != 0) {
kx509_log(context, reqctx, 0, "Requested extensions rejected " _kdc_audit_addreason((kdc_request_t)reqctx,
"by default policy (PKINIT SAN %s does not match " "Requested extensions rejected by "
"client %s)", s, cprinc); "default policy (PKINIT SAN "
"does not match client)");
goto eacces; goto eacces;
} }
break; break;
default: default:
ret = ENOTSUP; _kdc_audit_addreason((kdc_request_t)reqctx,
"Requested extensions rejected by "
"default policy (non-default SAN "
"requested)");
goto eacces;
} }
} }
frees(&s); frees(&s);
@@ -753,9 +766,12 @@ check_authz(krb5_context context,
break; break;
} }
der_free_oid(&oid); der_free_oid(&oid);
if (k == sizeof(eku_whitelist)/sizeof(eku_whitelist[0])) if (k == sizeof(eku_whitelist)/sizeof(eku_whitelist[0])) {
_kdc_audit_addreason((kdc_request_t)reqctx,
"Requested EKU rejected by default policy");
goto eacces; goto eacces;
} }
}
if (ret == HX509_NO_ITEM) if (ret == HX509_NO_ITEM)
ret = 0; ret = 0;
if (ret) if (ret)
@@ -770,11 +786,18 @@ check_authz(krb5_context context,
if (KeyUsage2int(ku) != (KeyUsage2int(ku) & KeyUsage2int(ku_allowed))) if (KeyUsage2int(ku) != (KeyUsage2int(ku) & KeyUsage2int(ku_allowed)))
goto eacces; goto eacces;
_kdc_audit_addkv((kdc_request_t)reqctx, 0, "authorized", "true");
return 0; return 0;
eacces: eacces:
ret = EACCES; ret = EACCES;
goto out2;
out: out:
/* XXX Display error code */
_kdc_audit_addreason((kdc_request_t)reqctx,
"Error handling requested extensions");
out2:
free(cprinc); free(cprinc);
free(s); free(s);
return ret; return ret;
@@ -825,10 +848,7 @@ encode_cert_and_chain(hx509_context hx509ctx,
*/ */
krb5_error_code krb5_error_code
_kdc_do_kx509(krb5_context context, _kdc_do_kx509(kx509_req_context r)
krb5_kdc_configuration *config,
const struct Kx509Request *req, krb5_data *reply,
const char *from, struct sockaddr *addr)
{ {
krb5_error_code ret = 0; krb5_error_code ret = 0;
krb5_ticket *ticket = NULL; krb5_ticket *ticket = NULL;
@@ -838,33 +858,26 @@ _kdc_do_kx509(krb5_context context,
krb5_keytab id = NULL; krb5_keytab id = NULL;
Kx509Response rep; Kx509Response rep;
hx509_certs certs = NULL; hx509_certs certs = NULL;
struct kx509_req_context reqctx;
int is_probe = 0; int is_probe = 0;
memset(&reqctx, 0, sizeof(reqctx)); r->csr_plus.csr.data = NULL;
reqctx.csr_plus.csr.data = NULL; r->csr_plus.exts = NULL;
reqctx.csr_plus.exts = NULL; r->sname = NULL;
reqctx.config = config; r->cname = NULL;
reqctx.sname = NULL; r->realm = NULL;
reqctx.cname = NULL; r->key = NULL;
reqctx.realm = NULL; r->csr = NULL;
reqctx.reply = reply; r->ac = NULL;
reqctx.from = from;
reqctx.addr = addr;
reqctx.key = NULL;
reqctx.csr = NULL;
reqctx.req = req;
reqctx.ac = NULL;
/* /*
* In order to support authenticated error messages we defer checking * In order to support authenticated error messages we defer checking
* whether the kx509 service is enabled until after accepting the AP-REQ. * whether the kx509 service is enabled until after accepting the AP-REQ.
*/ */
krb5_data_zero(reply); krb5_data_zero(r->reply);
memset(&rep, 0, sizeof(rep)); memset(&rep, 0, sizeof(rep));
if (req->authenticator.length == 0) { if (r->req.authenticator.length == 0) {
/* /*
* Unauthenticated kx509 service availability probe. * Unauthenticated kx509 service availability probe.
* *
@@ -872,77 +885,79 @@ _kdc_do_kx509(krb5_context context,
* possibly change the error code and message. * possibly change the error code and message.
*/ */
is_probe = 1; is_probe = 1;
kx509_log(context, &reqctx, 4, "unauthenticated probe request"); _kdc_audit_addkv((kdc_request_t)r, 0, "probe", "unauthenticated");
ret = mk_error_response(context, &reqctx, KRB5KDC_ERR_NULL_KEY, ret = mk_error_response(r->context, r, 4, 0,
"kx509 service is available"); "kx509 service is available");
goto out; goto out;
} }
/* Authenticate the request (consume the AP-REQ) */ /* Authenticate the request (consume the AP-REQ) */
ret = krb5_kt_resolve(context, "HDBGET:", &id); ret = krb5_kt_resolve(r->context, "HDBGET:", &id);
if (ret) { if (ret) {
ret = mk_error_response(context, &reqctx, ret = mk_error_response(r->context, r, 1,
KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN,
"Can't open HDB/keytab for kx509"); "Can't open HDB/keytab for kx509: %s",
krb5_get_error_message(r->context, ret));
goto out; goto out;
} }
ret = krb5_rd_req(context, ret = krb5_rd_req(r->context,
&reqctx.ac, &r->ac,
&req->authenticator, &r->req.authenticator,
NULL, NULL,
id, id,
&ap_req_options, &ap_req_options,
&ticket); &ticket);
if (ret == 0) if (ret == 0)
ret = krb5_auth_con_getkey(context, reqctx.ac, &reqctx.key); ret = krb5_auth_con_getkey(r->context, r->ac, &r->key);
if (ret == 0 && reqctx.key == NULL) if (ret == 0 && r->key == NULL)
ret = KRB5KDC_ERR_NULL_KEY; ret = KRB5KDC_ERR_NULL_KEY;
/* /*
* Provided we got the session key, errors past this point will be * Provided we got the session key, errors past this point will be
* authenticated. * authenticated.
*/ */
if (ret == 0) if (ret == 0)
ret = krb5_ticket_get_client(context, ticket, &cprincipal); ret = krb5_ticket_get_client(r->context, ticket, &cprincipal);
/* Optional: check if Ticket is INITIAL */ /* Optional: check if Ticket is INITIAL */
if (ret == 0 && if (ret == 0 &&
!ticket->ticket.flags.initial && !ticket->ticket.flags.initial &&
!get_bool_param(context, TRUE, !get_bool_param(r->context, TRUE,
krb5_principal_get_realm(context, cprincipal), krb5_principal_get_realm(r->context, cprincipal),
"require_initial_kca_tickets")) { "require_initial_kca_tickets")) {
ret = mk_error_response(context, &reqctx, KRB5KDC_ERR_POLICY, ret = mk_error_response(r->context, r, 4, KRB5KDC_ERR_POLICY,
"client used non-INITIAL tickets, but kx509" "Client used non-INITIAL tickets, but kx509 "
"kx509 service is configured to require " "service is configured to require INITIAL "
"INITIAL tickets"); "tickets");
goto out; goto out;
} }
ret = krb5_unparse_name(context, cprincipal, &reqctx.cname); ret = krb5_unparse_name(r->context, cprincipal, &r->cname);
/* Check that the service name is a valid kx509 service name */ /* Check that the service name is a valid kx509 service name */
if (ret == 0) if (ret == 0)
ret = krb5_ticket_get_server(context, ticket, &sprincipal); ret = krb5_ticket_get_server(r->context, ticket, &sprincipal);
if (ret == 0) if (ret == 0)
reqctx.realm = krb5_principal_get_realm(context, sprincipal); r->realm = krb5_principal_get_realm(r->context, sprincipal);
if (ret == 0) if (ret == 0)
ret = krb5_unparse_name(context, sprincipal, &reqctx.sname); ret = krb5_unparse_name(r->context, sprincipal, &r->sname);
if (ret == 0) if (ret == 0)
ret = kdc_kx509_verify_service_principal(context, &reqctx, sprincipal); ret = kdc_kx509_verify_service_principal(r->context, r, sprincipal);
if (ret) { if (ret) {
mk_error_response(context, &reqctx, ret, ret = mk_error_response(r->context, r, 4, ret,
"client used incorrect service name"); "kx509 client used incorrect service name");
goto out; goto out;
} }
/* Authenticate the rest of the request */ /* Authenticate the rest of the request */
ret = verify_req_hash(context, req, reqctx.key); ret = verify_req_hash(r->context, &r->req, r->key);
if (ret) { if (ret) {
mk_error_response(context, &reqctx, ret, "Incorrect request HMAC"); ret = mk_error_response(r->context, r, 4, ret,
"Incorrect request HMAC on kx509 request");
goto out; goto out;
} }
if (req->pk_key.length == 0) { if (r->req.pk_key.length == 0) {
/* /*
* The request is an authenticated kx509 service availability probe. * The request is an authenticated kx509 service availability probe.
* *
@@ -950,20 +965,27 @@ _kdc_do_kx509(krb5_context context,
* possibly change the error code and message. * possibly change the error code and message.
*/ */
is_probe = 1; is_probe = 1;
ret = mk_error_response(context, &reqctx, 0, _kdc_audit_addkv((kdc_request_t)r, 0, "probe", "authenticated");
ret = mk_error_response(r->context, r, 4, 0,
"kx509 authenticated probe request"); "kx509 authenticated probe request");
goto out; goto out;
} }
/* Extract and parse CSR or a DER-encoded RSA public key */ /* Extract and parse CSR or a DER-encoded RSA public key */
ret = get_csr(context, &reqctx); ret = get_csr(r->context, r);
if (ret) if (ret) {
ret = mk_error_response(r->context, r, 3, ret,
"Failed to parse CSR: %s",
krb5_get_error_message(r->context, ret));
goto out; goto out;
}
/* Authorize the request */ /* Authorize the request */
ret = check_authz(context, &reqctx, cprincipal); ret = check_authz(r->context, r, cprincipal);
if (ret) { if (ret) {
ret = mk_error_response(context, &reqctx, ret, "rejected by policy"); ret = mk_error_response(r->context, r, 3, ret,
"Rejected by policy: %s",
krb5_get_error_message(r->context, ret));
goto out; goto out;
} }
@@ -971,68 +993,71 @@ _kdc_do_kx509(krb5_context context,
ALLOC(rep.hash); ALLOC(rep.hash);
ALLOC(rep.certificate); ALLOC(rep.certificate);
if (rep.certificate == NULL || rep.hash == NULL) { if (rep.certificate == NULL || rep.hash == NULL) {
ret = mk_error_response(context, &reqctx, ENOMEM, ret = mk_error_response(r->context, r, 0, ENOMEM,
"could allocate memory for response"); "Could allocate memory for response");
goto out; goto out;
} }
krb5_data_zero(rep.hash); krb5_data_zero(rep.hash);
krb5_data_zero(rep.certificate); krb5_data_zero(rep.certificate);
krb5_ticket_get_times(context, ticket, &reqctx.ticket_times); krb5_ticket_get_times(r->context, ticket, &r->ticket_times);
ret = kdc_issue_certificate(context, reqctx.config, reqctx.csr, cprincipal, ret = kdc_issue_certificate(r->context, r->config, r->csr, cprincipal,
&reqctx.ticket_times, reqctx.send_chain, &r->ticket_times, r->send_chain, &certs);
&certs);
if (ret) { if (ret) {
mk_error_response(context, &reqctx, ret, "Certificate isuance failed"); int level = 1;
if (ret == KRB5KDC_ERR_POLICY)
level = 4; /* _kdc_audit_trail() logs at level 3 */
ret = mk_error_response(r->context, r, level, ret,
"Certificate isuance failed: %s",
krb5_get_error_message(r->context, ret));
goto out; goto out;
} }
ret = encode_cert_and_chain(context->hx509ctx, certs, rep.certificate); ret = encode_cert_and_chain(r->context->hx509ctx, certs, rep.certificate);
if (ret) { if (ret) {
mk_error_response(context, &reqctx, ret, ret = mk_error_response(r->context, r, 1, ret,
"Could not encode certificate and chain"); "Could not encode certificate and chain: %s",
krb5_get_error_message(r->context, ret));
goto out; goto out;
} }
/* Authenticate the response */ /* Authenticate the response */
ret = calculate_reply_hash(context, reqctx.key, &rep); ret = calculate_reply_hash(r->context, r->key, &rep);
if (ret) { if (ret) {
mk_error_response(context, &reqctx, ret, ret = mk_error_response(r->context, r, 1, ret,
"Failed to compute response HMAC"); "Failed to compute response HMAC");
goto out; goto out;
} }
/* Encode and output reply */ /* Encode and output reply */
ret = encode_reply(context, &reqctx, &rep); ret = encode_reply(r->context, r, &rep);
if (ret) if (ret)
/* Can't send an error message either in this case, surely */ /* Can't send an error message either in this case, surely */
kx509_log(context, &reqctx, 1, "Could not encode response"); _kdc_audit_addreason((kdc_request_t)r, "Could not encode response");
out: out:
hx509_certs_free(&certs); hx509_certs_free(&certs);
if (ret == 0 && !is_probe) if (ret == 0 && !is_probe)
kx509_log(context, &reqctx, 3, "Issued certificate"); _kdc_audit_addkv((kdc_request_t)r, 0, "cert_issued", "true");
else else
kx509_log(context, &reqctx, 2, "Did not issue certificate"); _kdc_audit_addkv((kdc_request_t)r, 0, "cert_issued", "false");
if (reqctx.ac) if (r->ac)
krb5_auth_con_free(context, reqctx.ac); krb5_auth_con_free(r->context, r->ac);
if (ticket) if (ticket)
krb5_free_ticket(context, ticket); krb5_free_ticket(r->context, ticket);
if (id) if (id)
krb5_kt_close(context, id); krb5_kt_close(r->context, id);
if (sprincipal) if (sprincipal)
krb5_free_principal(context, sprincipal); krb5_free_principal(r->context, sprincipal);
if (cprincipal) if (cprincipal)
krb5_free_principal(context, cprincipal); krb5_free_principal(r->context, cprincipal);
if (reqctx.key) if (r->key)
krb5_free_keyblock (context, reqctx.key); krb5_free_keyblock (r->context, r->key);
if (reqctx.sname) hx509_request_free(&r->csr);
free(reqctx.sname); free_Kx509CSRPlus(&r->csr_plus);
if (reqctx.cname)
free(reqctx.cname);
hx509_request_free(&reqctx.csr);
free_Kx509CSRPlus(&reqctx.csr_plus);
free_Kx509Response(&rep); free_Kx509Response(&rep);
free_Kx509Request(&r->req);
return ret; return ret;
} }

View File

@@ -18,4 +18,6 @@ EXPORTS
krb5_kdc_pk_initialize krb5_kdc_pk_initialize
_kdc_audit_addkv _kdc_audit_addkv
_kdc_audit_addreason _kdc_audit_addreason
_kdc_audit_vaddkv
_kdc_audit_vaddreason
_kdc_audit_trail _kdc_audit_trail

View File

@@ -87,24 +87,44 @@ fmtkv(int flags, const char *k, const char *fmt, va_list ap)
} }
void void
_kdc_audit_addreason(kdc_request_t r, const char *fmt, ...) _kdc_audit_vaddreason(kdc_request_t r, const char *fmt, va_list ap)
__attribute__ ((__format__ (__printf__, 2, 3))) __attribute__ ((__format__ (__printf__, 2, 0)))
{ {
va_list ap;
heim_string_t str; heim_string_t str;
va_start(ap, fmt);
str = fmtkv(KDC_AUDIT_VISLAST, "reason", fmt, ap); str = fmtkv(KDC_AUDIT_VISLAST, "reason", fmt, ap);
va_end(ap);
if (!str) { if (!str) {
kdc_log(r->context, r->config, 1, "failed to add reason"); kdc_log(r->context, r->config, 1, "failed to add reason");
return; return;
} }
kdc_log(r->context, r->config, 7, "_kdc_audit_addkv(): adding " kdc_log(r->context, r->config, 7, "_kdc_audit_addreason(): adding "
"kv pair %s", heim_string_get_utf8(str)); "reason %s", heim_string_get_utf8(str));
if (r->reason) {
heim_string_t str2;
str2 = heim_string_create_with_format("%s: %s",
heim_string_get_utf8(str),
heim_string_get_utf8(r->reason));
if (str2) {
heim_release(r->reason); heim_release(r->reason);
heim_release(str);
r->reason = str; r->reason = str;
} /* else the earlier reason is likely better than the newer one */
return;
}
r->reason = str;
}
void
_kdc_audit_addreason(kdc_request_t r, const char *fmt, ...)
__attribute__ ((__format__ (__printf__, 2, 3)))
{
va_list ap;
va_start(ap, fmt);
_kdc_audit_vaddreason(r, fmt, ap);
va_end(ap);
} }
/* /*
@@ -114,16 +134,13 @@ _kdc_audit_addreason(kdc_request_t r, const char *fmt, ...)
*/ */
void void
_kdc_audit_addkv(kdc_request_t r, int flags, const char *k, _kdc_audit_vaddkv(kdc_request_t r, int flags, const char *k,
const char *fmt, ...) const char *fmt, va_list ap)
__attribute__ ((__format__ (__printf__, 4, 5))) __attribute__ ((__format__ (__printf__, 4, 0)))
{ {
va_list ap;
heim_string_t str; heim_string_t str;
va_start(ap, fmt);
str = fmtkv(flags, k, fmt, ap); str = fmtkv(flags, k, fmt, ap);
va_end(ap);
if (!str) { if (!str) {
kdc_log(r->context, r->config, 1, "failed to add kv pair"); kdc_log(r->context, r->config, 1, "failed to add kv pair");
return; return;
@@ -135,6 +152,18 @@ _kdc_audit_addkv(kdc_request_t r, int flags, const char *k,
heim_release(str); heim_release(str);
} }
void
_kdc_audit_addkv(kdc_request_t r, int flags, const char *k,
const char *fmt, ...)
__attribute__ ((__format__ (__printf__, 4, 5)))
{
va_list ap;
va_start(ap, fmt);
_kdc_audit_vaddkv(r, flags, k, fmt, ap);
va_end(ap);
}
void void
_kdc_audit_addkv_timediff(kdc_request_t r, const char *k, _kdc_audit_addkv_timediff(kdc_request_t r, const char *k,
const struct timeval *start, const struct timeval *start,
@@ -347,27 +376,21 @@ kdc_digest(kdc_request_t *rptr, int *claim)
static krb5_error_code static krb5_error_code
kdc_kx509(kdc_request_t *rptr, int *claim) kdc_kx509(kdc_request_t *rptr, int *claim)
{ {
kdc_request_t r = *rptr; kx509_req_context r;
krb5_context context = r->context;
krb5_kdc_configuration *config = r->config;
krb5_data *req_buffer = &r->request;
krb5_data *reply = r->reply;
const char *from = r->from;
struct sockaddr *addr = r->addr;
Kx509Request kx509req;
krb5_error_code ret; krb5_error_code ret;
ret = _kdc_try_kx509_request(req_buffer->data, req_buffer->length, /* We must free things in the extensions */
&kx509req); EXTEND_REQUEST_T(*rptr, r);
ret = _kdc_try_kx509_request(r);
if (ret) if (ret)
return ret; return ret;
r->use_request_t = 0; r->use_request_t = 1;
r->reqtype = "KX509";
*claim = 1; *claim = 1;
ret = _kdc_do_kx509(context, config, &kx509req, reply, from, addr); return _kdc_do_kx509(r); /* Must clean up the req struct extensions */
free_Kx509Request(&kx509req);
return ret;
} }
#endif #endif

View File

@@ -22,6 +22,8 @@ HEIMDAL_KDC_1.0 {
krb5_kdc_pk_initialize; krb5_kdc_pk_initialize;
_kdc_audit_addkv; _kdc_audit_addkv;
_kdc_audit_addreason; _kdc_audit_addreason;
_kdc_audit_vaddkv;
_kdc_audit_vaddreason;
_kdc_audit_trail; _kdc_audit_trail;
# needed for digest-service # needed for digest-service

View File

@@ -272,12 +272,12 @@ $hxtool acert --expr="%{certificate.subject} == \"OU=Users,CN=KDC,$DCs\"" \
if ! which curl; then if ! which curl; then
echo "curl is not available -- not testing bx509d" echo "curl is not available -- not testing bx509d"
exit 0 exit 77
fi fi
if ! test -x ${objdir}/../../kdc/bx509d; then if ! test -x ${objdir}/../../kdc/bx509d; then
echo "Configured w/o libmicrohttpd -- not testing bx509d" echo "Configured w/o libmicrohttpd -- not testing bx509d"
exit 0 exit 77
fi fi
echo "Creating database" echo "Creating database"