Compare commits

...

5 Commits

Author SHA1 Message Date
William Lallemand
3b58415bf1 MEDIUM: acme: use 'crt-base' to load the account key
Prefix the filename with the 'crt-base' before loading the account key,
in order to work like every other keypair in haproxy.
2025-04-28 18:05:36 +02:00
William Lallemand
334d54971e MEDIUM: acme: generate the account file when not found
Generate the private key on the account file when the file does not
exists. This generate a private key of the type and parameters
configured in the acme section.
2025-04-28 18:05:36 +02:00
William Lallemand
2d45e9656a MINOR: acme: failure when no directory is specified
The "directory" parameter of the acme section is mandatory. This patch
exits with an alert when this parameter is not found.
2025-04-28 18:05:36 +02:00
William Lallemand
81e0592af2 MINOR: acme: separate the code generating private keys
acme_EVP_PKEY_gen() generates private keys of specified <keytype>,
<curves> and <bits>. Only RSA and EC are supported for now.
2025-04-28 18:05:36 +02:00
William Lallemand
495116afdd BUG/MINOR: ssl/acme: free EVP_PKEY upon error
Free the EPV_PKEY upon error when the X509_REQ generation failed.

No backport needed.
2025-04-28 16:39:24 +02:00

View File

@ -45,6 +45,7 @@ enum acme_ret {
ACME_RET_FAIL = 2
};
static EVP_PKEY *acme_EVP_PKEY_gen(int keytype, int curves, int bits, char **errmsg);
/* Return an existing acme_cfg section */
struct acme_cfg *get_acme_cfg(const char *name)
@ -392,9 +393,12 @@ static int cfg_postsection_acme()
{
struct acme_cfg *cur_acme = acme_cfgs;
struct ckch_store *store;
EVP_PKEY *key = NULL;
BIO *bio = NULL;
int err_code = 0;
char *errmsg = NULL;
char *path;
char store_path[PATH_MAX]; /* complete path with crt_base */
struct stat st;
/* TODO: generate a key at startup and dumps on the filesystem
@ -410,7 +414,27 @@ static int cfg_postsection_acme()
}
}
path = cur_acme->account.file;
if (global_ssl.crt_base && *cur_acme->account.file != '/') {
int rv;
/* When no crt_store name, complete the name in the ckch_tree with 'crt-base' */
rv = snprintf(store_path, sizeof(store_path), "%s/%s", global_ssl.crt_base, cur_acme->account.file);
if (rv >= sizeof(store_path)) {
ha_alert(errmsg, "'%s/%s' : path too long", global_ssl.crt_base, cur_acme->account.file);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
path = store_path;
} else {
path = cur_acme->account.file;
}
if (!cur_acme->directory) {
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
ha_alert("acme: No directory defined in ACME section '%s'.\n", cur_acme->name);
goto out;
}
store = ckch_store_new(path);
if (!store) {
@ -418,6 +442,7 @@ static int cfg_postsection_acme()
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
goto out;
}
/* tries to open the account key */
if (stat(path, &st) == 0) {
if (ssl_sock_load_key_into_ckch(path, NULL, store->data, &errmsg)) {
@ -430,11 +455,28 @@ static int cfg_postsection_acme()
}
/* ha_notice("acme: reading account key '%s' for id '%s'.\n", path, cur_acme->name); */
} else {
ha_alert("%s '%s' is not present and can't be generated, please provide an account file.\n", errmsg, path);
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
goto out;
}
ha_notice("acme: generate account key '%s' for acme section '%s'.\n", path, cur_acme->name);
if ((key = acme_EVP_PKEY_gen(cur_acme->key.type, cur_acme->key.curves, cur_acme->key.bits, &errmsg)) == NULL) {
ha_alert("acme: %s\n", errmsg);
goto out;
}
if ((bio = BIO_new_file(store->path, "w+")) == NULL) {
ha_alert("acme: out of memory.\n");
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
goto out;
}
if ((PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) == 0) {
ha_alert("acme: cannot write account key '%s'.\n", cur_acme->account.file);
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
goto out;
}
store->data->key = key;
key = NULL;
}
if (store->data->key == NULL) {
ha_alert("acme: No Private Key found in '%s'.\n", path);
@ -443,6 +485,7 @@ static int cfg_postsection_acme()
}
cur_acme->account.pkey = store->data->key;
EVP_PKEY_up_ref(cur_acme->account.pkey);
trash.data = jws_thumbprint(cur_acme->account.pkey, trash.area, trash.size);
@ -457,6 +500,8 @@ static int cfg_postsection_acme()
ebst_insert(&ckchs_tree, &store->node);
out:
EVP_PKEY_free(key);
BIO_free_all(bio);
ha_free(&errmsg);
return err_code;
}
@ -1912,6 +1957,45 @@ error:
}
/* Return a new Generated private key of type <keytype> with <bits> and <curves> */
static EVP_PKEY *acme_EVP_PKEY_gen(int keytype, int curves, int bits, char **errmsg)
{
EVP_PKEY_CTX *pkey_ctx = NULL;
EVP_PKEY *pkey = NULL;
if ((pkey_ctx = EVP_PKEY_CTX_new_id(keytype, NULL)) == NULL) {
memprintf(errmsg, "%sCan't generate a private key.\n", *errmsg ? *errmsg : "");
goto err;
}
if (EVP_PKEY_keygen_init(pkey_ctx) <= 0) {
memprintf(errmsg, "%sCan't generate a private key.\n", *errmsg ? *errmsg : "");
goto err;
}
if (keytype == EVP_PKEY_EC) {
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pkey_ctx, curves) <= 0) {
memprintf(errmsg, "%sCan't set the curves on the new private key.\n", *errmsg ? *errmsg : "");
goto err;
}
} else if (keytype == EVP_PKEY_RSA) {
if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, bits) <= 0) {
memprintf(errmsg, "%sCan't set the bits on the new private key.\n", *errmsg ? *errmsg : "");
goto err;
}
}
if (EVP_PKEY_keygen(pkey_ctx, &pkey) <= 0) {
memprintf(errmsg, "%sCan't generate a private key.\n", *errmsg ? *errmsg : "");
goto err;
}
err:
EVP_PKEY_CTX_free(pkey_ctx);
return pkey;
}
static int cli_acme_renew_parse(char **args, char *payload, struct appctx *appctx, void *private)
{
char *err = NULL;
@ -1919,7 +2003,6 @@ static int cli_acme_renew_parse(char **args, char *payload, struct appctx *appct
struct task *task;
struct acme_ctx *ctx = NULL;
struct ckch_store *store = NULL, *newstore = NULL;
EVP_PKEY_CTX *pkey_ctx = NULL;
EVP_PKEY *pkey = NULL;
if (!*args[1]) {
@ -1980,39 +2063,14 @@ static int cli_acme_renew_parse(char **args, char *payload, struct appctx *appct
/* set the number of remaining retries when facing an error */
ctx->retries = ACME_RETRY;
if ((pkey_ctx = EVP_PKEY_CTX_new_id(cfg->key.type, NULL)) == NULL) {
memprintf(&err, "%sCan't generate a private key.\n", err ? err : "");
if ((pkey = acme_EVP_PKEY_gen(cfg->key.type, cfg->key.curves, cfg->key.bits, &err)) == NULL)
goto err;
}
if (EVP_PKEY_keygen_init(pkey_ctx) <= 0) {
memprintf(&err, "%sCan't generate a private key.\n", err ? err : "");
goto err;
}
if (cfg->key.type == EVP_PKEY_EC) {
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pkey_ctx, cfg->key.curves) <= 0) {
memprintf(&err, "%sCan't set the curves on the new private key.\n", err ? err : "");
goto err;
}
} else if (cfg->key.type == EVP_PKEY_RSA) {
if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, cfg->key.bits) <= 0) {
memprintf(&err, "%sCan't set the bits on the new private key.\n", err ? err : "");
goto err;
}
}
if (EVP_PKEY_keygen(pkey_ctx, &pkey) <= 0) {
memprintf(&err, "%sCan't generate a private key.\n", err ? err : "");
goto err;
}
EVP_PKEY_CTX_free(pkey_ctx);
EVP_PKEY_free(newstore->data->key);
newstore->data->key = pkey;
pkey = NULL;
ctx->req = acme_x509_req(pkey, store->conf.acme.domains);
ctx->req = acme_x509_req(newstore->data->key, store->conf.acme.domains);
if (!ctx->req) {
memprintf(&err, "%sCan't generate a CSR.\n", err ? err : "");
goto err;
@ -2028,8 +2086,8 @@ static int cli_acme_renew_parse(char **args, char *payload, struct appctx *appct
err:
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
EVP_PKEY_free(pkey);
ckch_store_free(newstore);
EVP_PKEY_CTX_free(pkey_ctx);
free(ctx);
memprintf(&err, "%sCan't start the ACME client.\n", err ? err : "");
return cli_dynerr(appctx, err);