]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl/ckch: certificates generation from "load" "crt-store" directive
authorFrederic Lecaille <flecaille@haproxy.com>
Mon, 26 Jan 2026 15:05:35 +0000 (16:05 +0100)
committerFrederic Lecaille <flecaille@haproxy.com>
Wed, 28 Jan 2026 15:09:40 +0000 (16:09 +0100)
Add "generate" on/off type keyword to "load" directive to automatically generate
certificates as this is done for ACME from ckch_conf_load_pem_or_generate()
function which is called if a "crt" keyword is also provide for this directive.

Also implement "keytype" to specify the key type used for these certificates.
Only "RSA" or "ECDSA" is accepted. This patch also implements "bits" keyword
for the "load" directive to specify the private key size used for RSA. For
ECDSA, a new "curves" keyword is also provided by this patch to specify the curves
to be used for the EDCSA private keys generation.

ACME code has been modified to use these new parameters. acme_gen_x509()
new function is implemented from acme_gen_tmp_x509() to took an EVP_KEY parameter
as unique parameter contraty to acme_gen_tmp_x509() which directly used <tmp_key>
global EVP_KEY variable initialized by ACME as temporary key before retreiving its
own keys. <tmp_key> is generated by acme_EVP_PKEY_gen() as an 2048 bits RSA key.
This latter function is used by ckch_conf_load_pem_or_generate() with the parameters
provided by "keytype", "bits" and "curves" to generate the private key before
generating the X509 certificate calling acme_gen_x509().

include/haproxy/acme.h
include/haproxy/ssl_ckch-t.h
src/acme.c
src/ssl_ckch.c

index 763a4692af54ea2078bdb906e432232109b98697..4098d4d66c502496f183684eb632635ced15e944 100644 (file)
@@ -7,6 +7,8 @@
 int ckch_conf_acme_init(void *value, char *buf, struct ckch_store *s, int cli, const char *filename, int linenum, char **err);
 EVP_PKEY *acme_gen_tmp_pkey();
 X509 *acme_gen_tmp_x509();
+EVP_PKEY *acme_EVP_PKEY_gen(int keytype, int curves, int bits, char **err);
+X509 *acme_gen_x509(EVP_PKEY *pkey);
 
 
 #endif
index dc6d988572d846b1c4f4848b95541d5ccb97b4b2..f2024f082ceb424816eb6ed981f920057180e5a9 100644 (file)
@@ -73,6 +73,14 @@ struct ckch_conf {
                char *id;
                char **domains;
        } acme;
+       struct {
+               struct {
+                       char *type;   /* "RSA" or "ECSDA" */
+                       int bits;     /* bits for RSA */
+                       char *curves; /* NID of curves for ECDSA*/
+               } key;
+               int on;
+       } gencrt;
 };
 
 /*
index b8ad8df671782beb2bd25c04c5e3bba950fdc645..f43ed142739e3a9efc0837259a6b2766ced98430 100644 (file)
@@ -157,7 +157,7 @@ enum acme_ret {
        ACME_RET_FAIL = 2
 };
 
-static EVP_PKEY *acme_EVP_PKEY_gen(int keytype, int curves, int bits, char **errmsg);
+EVP_PKEY *acme_EVP_PKEY_gen(int keytype, int curves, int bits, char **errmsg);
 static int acme_start_task(struct ckch_store *store, char **errmsg);
 static struct task *acme_scheduler(struct task *task, void *context, unsigned int state);
 
@@ -2588,7 +2588,7 @@ 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 *acme_EVP_PKEY_gen(int keytype, int curves, int bits, char **errmsg)
 {
 
        EVP_PKEY_CTX *pkey_ctx = NULL;
@@ -2627,27 +2627,16 @@ err:
 }
 
 /*
- * Generate a temporary expired X509  or reuse the one generated.
- * Use tmp_pkey to generate
- *
- * Increment the refcount when returning the existing one
+ * Generate an expired X509 from <pkey> private key which must be initialized.
+ * Return a pointer to the created X509 object if succeeded, NULL if not.
  */
-X509 *acme_gen_tmp_x509()
+X509 *acme_gen_x509(EVP_PKEY *pkey)
 {
        X509         *newcrt  = NULL;
        X509_NAME    *name;
        const EVP_MD *digest = NULL;
        CONF         *ctmp    = NULL;
        int           key_type;
-       EVP_PKEY *pkey = tmp_pkey;
-
-       if (tmp_x509) {
-               X509_up_ref(tmp_x509);
-               return tmp_x509;
-       }
-
-       if (!tmp_pkey)
-               goto mkcert_error;
 
        /* Create the certificate */
        if (!(newcrt = X509_new()))
@@ -2698,8 +2687,6 @@ X509 *acme_gen_tmp_x509()
        if (!(X509_sign(newcrt, pkey, digest)))
                goto mkcert_error;
 
-       tmp_x509 = newcrt;
-
        return newcrt;
 
 mkcert_error:
@@ -2709,6 +2696,26 @@ mkcert_error:
 
 }
 
+/*
+ * Generate a temporary expired X509  or reuse the one generated.
+ * Use tmp_pkey to generate
+ *
+ * Increment the refcount when returning the existing one
+ */
+X509 *acme_gen_tmp_x509()
+{
+       if (tmp_x509) {
+               X509_up_ref(tmp_x509);
+               return tmp_x509;
+       }
+
+       if (!tmp_pkey)
+               return NULL;
+
+       tmp_x509 = acme_gen_x509(tmp_pkey);
+
+       return tmp_x509;
+}
 
 /*
  * Generate a temporary RSA2048 pkey or reuse the one generated.
index 900e89de3404e341587750ad13ca66cb42686be1..31759c993b2cec1066c754644c3157b8fa1d725d 100644 (file)
@@ -4742,11 +4742,50 @@ static int ckch_conf_load_pem_or_generate(void *value, char *buf, struct ckch_st
 #ifdef HAVE_ACME
        errno = 0;
        /* if ACME is enabled and the file does not exists, generate the PEM */
-       if (s->conf.acme.id && (stat(path, &sb) == -1 && errno == ENOENT)) {
+       if ((s->conf.acme.id || s->conf.gencrt.on == 1) && (stat(path, &sb) == -1 && errno == ENOENT)) {
                /* generate they key and the certificate */
-               ha_notice("No certificate available for '%s', generating a temporary key pair before getting the ACME certificate\n", path);
-               s->data->key = acme_gen_tmp_pkey();
-               s->data->cert = acme_gen_tmp_x509();
+               if (s->conf.gencrt.on != 1) {
+                       ha_notice("No certificate available for '%s', generating a temporary key pair before getting the ACME certificate\n", path);
+                       s->data->key = acme_gen_tmp_pkey();
+                       s->data->cert = acme_gen_tmp_x509();
+               }
+               else {
+                       int type, bits, nid = -1;
+                       char *curves;
+
+                       if (!s->conf.gencrt.key.type)
+                               type = EVP_PKEY_RSA;
+                       else {
+                               if (!strcmp(s->conf.gencrt.key.type, "RSA"))
+                                       type = EVP_PKEY_RSA;
+                               else if (!strcmp(s->conf.gencrt.key.type, "ECDSA"))
+                                       type = EVP_PKEY_EC;
+                               else {
+                                       memprintf(err, "Couldn't automatically generate a keypair\n");
+                                       err_code |= ERR_FATAL;
+                                       goto out;
+                               }
+                       }
+
+                       /* default values: 2048 bits for RSA key, "secp384r1" curves for ECDSA key */
+                       bits = s->conf.gencrt.key.bits ? s->conf.gencrt.key.bits : 2048;
+                       curves = s->conf.gencrt.key.curves ? s->conf.gencrt.key.curves : "secp384r1";
+
+                       if (type == EVP_PKEY_EC && (nid = curves2nid(curves)) == -1) {
+                               memprintf(err, "unsupported curves for '%s'\n", path);
+                               err_code |= ERR_FATAL;
+                               goto out;
+                       }
+
+                       s->data->key = acme_EVP_PKEY_gen(type, nid, bits, err);
+                       if (!s->data->key) {
+                               err_code |= ERR_FATAL;
+                               goto out;
+                       }
+
+                       s->data->cert = acme_gen_x509(s->data->key);
+               }
+
                if (!s->data->key || !s->data->cert) {
                        memprintf(err, "Couldn't generate a temporary keypair for '%s'\n", path);
                        err_code |= ERR_FATAL;
@@ -4811,6 +4850,10 @@ struct ckch_conf_kws ckch_conf_kws[] = {
        { "acme",         offsetof(struct ckch_conf, acme.id),          PARSE_TYPE_STR,   ckch_conf_acme_init,            },
 #endif
        { "domains",      offsetof(struct ckch_conf, acme.domains),     PARSE_TYPE_ARRAY_SUBSTR,   NULL,            },
+       { "generate",     offsetof(struct ckch_conf, gencrt.on),        PARSE_TYPE_ONOFF, NULL,                           },
+       { "keytype",      offsetof(struct ckch_conf, gencrt.key.type),  PARSE_TYPE_STR,   NULL,                           },
+       { "bits",         offsetof(struct ckch_conf, gencrt.key.bits),  PARSE_TYPE_INT,   NULL,                           },
+       { "curves",       offsetof(struct ckch_conf, gencrt.key.curves), PARSE_TYPE_STR,  NULL,                           },
        { NULL,          -1,                                            PARSE_TYPE_STR,   NULL,                           }
 };