diff --git a/include/haproxy/cfgparse.h b/include/haproxy/cfgparse.h index adcabb397..3a769d5d8 100644 --- a/include/haproxy/cfgparse.h +++ b/include/haproxy/cfgparse.h @@ -36,6 +36,7 @@ struct acl_cond; #define CFG_USERLIST 3 #define CFG_PEERS 4 #define CFG_CRTLIST 5 +#define CFG_CRTSTORE 6 /* various keyword modifiers */ enum kw_mod { diff --git a/include/haproxy/ssl_ckch-t.h b/include/haproxy/ssl_ckch-t.h index 0002b84d9..6562232ee 100644 --- a/include/haproxy/ssl_ckch-t.h +++ b/include/haproxy/ssl_ckch-t.h @@ -58,6 +58,15 @@ struct ckch_data { int ocsp_update_mode; }; +/* configuration for the ckch_store */ +struct ckch_conf { + char *crt; + char *key; + char *ocsp; + char *issuer; + char *sctl; +}; + /* * this is used to store 1 to SSL_SOCK_NUM_KEYTYPES cert_key_and_chain and * metadata. @@ -71,6 +80,7 @@ struct ckch_store { struct ckch_data *data; struct list ckch_inst; /* list of ckch_inst which uses this ckch_node */ struct list crtlist_entry; /* list of entries which use this store */ + struct ckch_conf conf; struct ebmb_node node; char path[VAR_ARRAY]; }; @@ -157,5 +167,21 @@ struct cert_exts { /* add a parsing callback */ }; +/* argument types */ +enum parse_type_t { + PARSE_TYPE_INT = 0, + PARSE_TYPE_STR, /* string which is strdup() */ + PARSE_TYPE_ONOFF, /* "on" or "off" keyword */ +}; + +struct ckch_conf_kws { + const char *name; + size_t offset; + enum parse_type_t type; + int (*func)(const char *path, char *buf, struct ckch_data *d, char **err); +}; + +extern struct ckch_conf_kws ckch_conf_kws[]; + #endif /* USE_OPENSSL */ #endif /* _HAPROXY_SSL_CKCH_T_H */ diff --git a/include/haproxy/ssl_ckch.h b/include/haproxy/ssl_ckch.h index 94c53b301..809ad8f08 100644 --- a/include/haproxy/ssl_ckch.h +++ b/include/haproxy/ssl_ckch.h @@ -43,6 +43,7 @@ struct ckch_store *ckchs_dup(const struct ckch_store *src); struct ckch_store *ckch_store_new(const char *filename); void ckch_store_free(struct ckch_store *store); void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs); +int ckch_store_load_files(struct ckch_conf *f, struct ckch_store *c, char **err); /* ckch_inst functions */ void ckch_inst_free(struct ckch_inst *inst); diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c index b69caacb3..7a3376569 100644 --- a/src/ssl_ckch.c +++ b/src/ssl_ckch.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -911,6 +912,13 @@ void ckch_store_free(struct ckch_store *store) ssl_sock_free_cert_key_and_chain_contents(store->data); ha_free(&store->data); + /* free the ckch_conf content */ + free(store->conf.crt); + free(store->conf.key); + free(store->conf.ocsp); + free(store->conf.issuer); + free(store->conf.sctl); + free(store); } @@ -3988,3 +3996,229 @@ static struct cli_kw_list cli_kws = {{ },{ INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); +struct ckch_conf_kws ckch_conf_kws[] = { + { "crt", offsetof(struct ckch_conf, crt), PARSE_TYPE_STR, ssl_sock_load_pem_into_ckch }, + { "key", offsetof(struct ckch_conf, key), PARSE_TYPE_STR, ssl_sock_load_key_into_ckch }, + { "ocsp", offsetof(struct ckch_conf, ocsp), PARSE_TYPE_STR, ssl_sock_load_ocsp_response_from_file }, + { "issuer", offsetof(struct ckch_conf, issuer), PARSE_TYPE_STR, ssl_sock_load_issuer_file_into_ckch }, + { "sctl", offsetof(struct ckch_conf, sctl), PARSE_TYPE_STR, ssl_sock_load_sctl_from_file }, + { NULL, 0, PARSE_TYPE_STR, NULL } +}; + +/* crt-store does not try to find files, but use the stored filename */ +int ckch_store_load_files(struct ckch_conf *f, struct ckch_store *c, char **err) +{ + int i; + int err_code = 0; + int rc = 0; + struct ckch_data *d = c->data; + + /* crt */ + if (!f->crt) { + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + for (i = 0; ckch_conf_kws[i].name; i++) { + if (*(char **)((intptr_t)f + (ptrdiff_t)ckch_conf_kws[i].offset)) { + if (!ckch_conf_kws[i].func) + continue; + rc = ckch_conf_kws[i].func(*(char **)((intptr_t)f + (ptrdiff_t)ckch_conf_kws[i].offset), NULL, d, err); + if (rc) { + err_code |= ERR_ALERT | ERR_FATAL; + memprintf(err, "%s '%s' cannot be read or parsed.", + err && *err ? *err : "", *(char **)((intptr_t)f + (ptrdiff_t)ckch_conf_kws[i].offset)); + goto out; + } + } + } + +out: + if (err_code & ERR_FATAL) + ssl_sock_free_cert_key_and_chain_contents(d); + ERR_clear_error(); + + return err_code; +} + +static int crtstore_parse_load(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, + const char *file, int linenum, char **err) +{ + int i; + int err_code = 0; + int cur_arg = 0; + struct ckch_conf f = {}; + struct ckch_store *c = NULL; + + cur_arg++; /* skip "load" */ + + while (*(args[cur_arg])) { + int found = 0; + + for (i = 0; ckch_conf_kws[i].name != NULL; i++) { + if (strcmp(ckch_conf_kws[i].name, args[cur_arg]) == 0) { + void *target; + found = 1; + target = (char **)((intptr_t)&f + (ptrdiff_t)ckch_conf_kws[i].offset); + + if (ckch_conf_kws[i].type == PARSE_TYPE_STR) { + char **t = target; + + *t = strdup(args[cur_arg + 1]); + if (!*t) + goto alloc_error; + } else if (ckch_conf_kws[i].type == PARSE_TYPE_INT) { + int *t = target; + char *stop; + + *t = strtol(args[cur_arg + 1], &stop, 10); + if (*stop != '\0') { + memprintf(err, "parsing [%s:%d] : cannot parse '%s' value '%s', an integer is expected.\n", + file, linenum, args[cur_arg], args[cur_arg + 1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } else if (ckch_conf_kws[i].type == PARSE_TYPE_ONOFF) { + int *t = target; + + if (strcmp(args[cur_arg + 1], "on") == 0) { + *t = 1; + } else if (strcmp(args[cur_arg + 1], "off") == 0) { + *t = 0; + } else { + memprintf(err, "parsing [%s:%d] : cannot parse '%s' value '%s', 'on' or 'off' is expected.\n", + file, linenum, args[cur_arg], args[cur_arg + 1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + break; + } + + } + if (!found) { + memprintf(err,"parsing [%s:%d] : '%s %s' in section 'crt-store': unknown keyword '%s'.", + file, linenum, args[0], args[cur_arg],args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + cur_arg += 2; + } + + if (!f.crt) { + memprintf(err,"parsing [%s:%d] : '%s' in section 'crt-store': mandatory 'crt' parameter not found.", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* process and insert the ckch_store */ + c = ckch_store_new(f.crt); + if (!c) + goto alloc_error; + + err_code |= ckch_store_load_files(&f, c, err); + if (err_code & ERR_FATAL) + goto out; + + c->conf = f; + + if (ebst_insert(&ckchs_tree, &c->node) != &c->node) { + memprintf(err,"parsing [%s:%d] : '%s' in section 'crt-store': store '%s' was already defined.", + file, linenum, args[0], c->path); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + +out: + /* free ckch_conf content */ + if (err_code & ERR_FATAL) + ckch_store_free(c); + return err_code; + +alloc_error: + ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; +} + +/* + * Parse "crt-store" section and create corresponding ckch_stores. + * + * The function returns 0 in success case, otherwise, it returns error + * flags. + */ +static int cfg_parse_crtstore(const char *file, int linenum, char **args, int kwm) +{ + struct cfg_kw_list *kwl; + const char *best; + int index; + int rc = 0; + int err_code = 0; + char *errmsg = NULL; + + if (strcmp(args[0], "crt-store") == 0) { /* new crt-store section */ + if (*args[1]) { + ha_alert("parsing [%s:%d] : 'crt-store' section does not support an argument.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT; + goto out; + } + goto out; + } + + list_for_each_entry(kwl, &cfg_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if (kwl->kw[index].section != CFG_CRTSTORE) + continue; + if (strcmp(kwl->kw[index].kw, args[0]) == 0) { + if (check_kw_experimental(&kwl->kw[index], file, linenum, &errmsg)) { + ha_alert("%s\n", errmsg); + err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT; + goto out; + } + + /* prepare error message just in case */ + rc = kwl->kw[index].parse(args, CFG_CRTSTORE, NULL, NULL, file, linenum, &errmsg); + if (rc & ERR_ALERT) { + ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= rc; + goto out; + } + else if (rc & ERR_WARN) { + ha_warning("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= rc; + goto out; + } + goto out; + } + } + } + + best = cfg_find_best_match(args[0], &cfg_keywords.list, CFG_CRTSTORE, NULL); + if (best) + ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section; did you mean '%s' maybe ?\n", file, linenum, args[0], cursection, best); + else + ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + +out: + if (err_code & ERR_FATAL) + err_code |= ERR_ABORT; + free(errmsg); + return err_code; + +alloc_error: + ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + +} + +REGISTER_CONFIG_SECTION("crt-store", cfg_parse_crtstore, NULL); + +static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_CRTSTORE, "load", crtstore_parse_load }, + { 0, NULL, NULL }, +}}; +INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);