]> 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>
Thu, 29 Jan 2026 19:35:50 +0000 (20:35 +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().

doc/configuration.txt
include/haproxy/ssl_ckch-t.h
include/haproxy/ssl_gencert.h
src/acme.c
src/ssl_ckch.c
src/ssl_gencert.c

index 52c2bf60bffb3aa77409aff52d8f5a0df2423386..754102a559b237ff79fdb618cdb758c0d51a7e02 100644 (file)
@@ -31718,6 +31718,30 @@ jwt [ off | on ]
   This option can be changed during runtime via the "add ssl jwt" and "del ssl
   jwt" CLI commands. See also "show ssl jwt" CLI command.
 
+generate-dummy [ off | on ]
+  Allow the generation of a private key and its self-signed certificate at
+  parsing time when set to 'on'. This may be useful if one does not have a
+  certificate at disposal during testing phase for instance. In this case,
+  "keytype", "bits" and "curves" may be used to customize the private key. When
+  not used, the default value is 'off'. (also see "keytype", "bits" and
+  "curves").
+
+keytype [ RSA | ECDSA ]
+  Allow the selection of the private key type used to generate at parsing time
+  a self-signed certificate. This is the case if "generate-dummy" is set to 'on'
+  for this certificate. When not used, the default is 'RSA'.
+  (also see "generate-dummy").
+
+bits <number>
+  Configure the number of bits to generate an RSA self-signed certificate when
+  "generate-dummy" is set to 'on' for this self-signed certificate and "keytype"
+  is set to 'RSA'. When not used, the default is 2048. (also see
+  "generate-dummy").
+
+curves <string>
+  Configure the curves when "generate-dummy" is set to 'on' and "keytype" is
+  set to 'ECDSA" for this self-signed certificate. The default is 'P-384'.
+
 12.8. ACME
 ----------
 
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 686b2d5bc3211543f1402ca48c692930d772aa4c..39c0b3c7f22104eac1b6f396c1de84e3ba9a7cc2 100644 (file)
@@ -32,6 +32,8 @@ int ssl_sock_set_generated_cert(SSL_CTX *ctx, unsigned int key, struct bind_conf
 unsigned int ssl_sock_generated_cert_key(const void *data, size_t len);
 int ssl_sock_gencert_load_ca(struct bind_conf *bind_conf);
 void ssl_sock_gencert_free_ca(struct bind_conf *bind_conf);
+EVP_PKEY *ssl_gen_EVP_PKEY(int keytype, int curves, int bits, char **errmsg);
+X509 *ssl_gen_x509(EVP_PKEY *pkey);
 
 #endif /* USE_OPENSSL */
 #endif /* _HAPROXY_SSL_GENCERT_H */
index b8ad8df671782beb2bd25c04c5e3bba950fdc645..10fb6baa369ebc7f1b6979378aca697f3f5c6015 100644 (file)
@@ -30,6 +30,7 @@
 #include <haproxy/pattern.h>
 #include <haproxy/sink.h>
 #include <haproxy/ssl_ckch.h>
+#include <haproxy/ssl_gencert.h>
 #include <haproxy/ssl_sock.h>
 #include <haproxy/ssl_utils.h>
 #include <haproxy/tools.h>
@@ -157,7 +158,6 @@ enum acme_ret {
        ACME_RET_FAIL = 2
 };
 
-static 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);
 
@@ -698,7 +698,7 @@ static int cfg_postsection_acme()
        } else {
                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) {
+               if ((key = ssl_gen_EVP_PKEY(cur_acme->key.type, cur_acme->key.curves, cur_acme->key.bits, &errmsg)) == NULL) {
                        ha_alert("acme: %s\n", errmsg);
                        goto out;
                }
@@ -2587,45 +2587,6 @@ 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 ? *errmsg : "");
-               goto err;
-       }
-
-       if (EVP_PKEY_keygen_init(pkey_ctx) <= 0) {
-               memprintf(errmsg, "%sCan't generate a private key.\n", errmsg && *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 ? *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 ? *errmsg : "");
-                       goto err;
-               }
-       }
-
-       if (EVP_PKEY_keygen(pkey_ctx, &pkey) <= 0) {
-               memprintf(errmsg, "%sCan't generate a private key.\n", errmsg && *errmsg ? *errmsg : "");
-               goto err;
-       }
-
-err:
-       EVP_PKEY_CTX_free(pkey_ctx);
-       return pkey;
-}
-
 /*
  * Generate a temporary expired X509  or reuse the one generated.
  * Use tmp_pkey to generate
@@ -2634,82 +2595,19 @@ err:
  */
 X509 *acme_gen_tmp_x509()
 {
-       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()))
-               goto mkcert_error;
-
-       /* Set version number for the certificate (X509v3) and the serial
-        * number */
-       if (X509_set_version(newcrt, 2L) != 1)
-               goto mkcert_error;
-
-       /* Generate an expired certificate */
-       if (!X509_gmtime_adj(X509_getm_notBefore(newcrt), (long)-60*60*48) ||
-           !X509_gmtime_adj(X509_getm_notAfter(newcrt),(long)-60*60*24))
-               goto mkcert_error;
-
-       /* set public key in the certificate */
-       if (X509_set_pubkey(newcrt, pkey) != 1)
-               goto mkcert_error;
-
-       if ((name = X509_NAME_new()) == NULL)
-               goto mkcert_error;
-
-       /* Set the subject name using the servername but the CN */
-       if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"expired",
-                                      -1, -1, 0) != 1) {
-               X509_NAME_free(name);
-               goto mkcert_error;
-       }
-       if (X509_set_subject_name(newcrt, name) != 1) {
-               X509_NAME_free(name);
-               goto mkcert_error;
-       }
-       /* Set issuer name as itself */
-       if (X509_set_issuer_name(newcrt, name) != 1)
-               goto mkcert_error;
-       X509_NAME_free(name);
-
-       /* Autosign the certificate with the private key */
-       key_type = EVP_PKEY_base_id(pkey);
-
-       if (key_type == EVP_PKEY_RSA)
-               digest = EVP_sha256();
-       else if (key_type == EVP_PKEY_EC)
-               digest = EVP_sha256();
-       else
-               goto mkcert_error;
-
-       if (!(X509_sign(newcrt, pkey, digest)))
-               goto mkcert_error;
-
-       tmp_x509 = newcrt;
-
-       return newcrt;
+               return NULL;
 
-mkcert_error:
-       if (ctmp) NCONF_free(ctmp);
-       if (newcrt)  X509_free(newcrt);
-       return NULL;
+       tmp_x509 = ssl_gen_x509(tmp_pkey);
 
+       return tmp_x509;
 }
 
-
 /*
  * Generate a temporary RSA2048 pkey or reuse the one generated.
  *
@@ -2723,7 +2621,7 @@ EVP_PKEY *acme_gen_tmp_pkey()
                return tmp_pkey;
        }
 
-       tmp_pkey = acme_EVP_PKEY_gen(EVP_PKEY_RSA, 0, 2048, NULL);
+       tmp_pkey = ssl_gen_EVP_PKEY(EVP_PKEY_RSA, 0, 2048, NULL);
 
        return tmp_pkey;
 }
@@ -2782,7 +2680,7 @@ static int acme_start_task(struct ckch_store *store, char **errmsg)
        ctx->retries = ACME_RETRY;
 
        if (!cfg->reuse_key) {
-               if ((pkey = acme_EVP_PKEY_gen(cfg->key.type, cfg->key.curves, cfg->key.bits, errmsg)) == NULL)
+               if ((pkey = ssl_gen_EVP_PKEY(cfg->key.type, cfg->key.curves, cfg->key.bits, errmsg)) == NULL)
                        goto err;
 
                EVP_PKEY_free(newstore->data->key);
index 7753fb718ca72de0e5a35d8edffe877ce359d432..c0f08af422ee5b248182c98830497be620fd2f80 100644 (file)
@@ -36,6 +36,7 @@
 #include <haproxy/proxy.h>
 #include <haproxy/sc_strm.h>
 #include <haproxy/ssl_ckch.h>
+#include <haproxy/ssl_gencert.h>
 #include <haproxy/ssl_sock.h>
 #include <haproxy/ssl_ocsp.h>
 #include <haproxy/ssl_utils.h>
@@ -4763,18 +4764,58 @@ 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)) {
-               /* generate they key and the certificate */
+       if (s->conf.gencrt.on == 1) {
+               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, "P-384" 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 : "P-384";
+
+               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 = ssl_gen_EVP_PKEY(type, nid, bits, err);
+               if (!s->data->key) {
+                       err_code |= ERR_FATAL;
+                       goto out;
+               }
+
+               s->data->cert = ssl_gen_x509(s->data->key);
+               if (!s->data->cert) {
+                       memprintf(err, "Couldn't generate a keypair for '%s'\n", path);
+                       err_code |= ERR_FATAL;
+                       goto out;
+               }
+       }
+       else if (s->conf.acme.id && (stat(path, &sb) == -1 && errno == ENOENT)) {
+               /* if ACME is enabled and the file does not exists, generate the PEM */
                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->data->key || !s->data->cert) {
                        memprintf(err, "Couldn't generate a temporary keypair for '%s'\n", path);
                        err_code |= ERR_FATAL;
                        goto out;
                }
-
        } else
 #endif
        {
@@ -4833,6 +4874,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-dummy", 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,                           }
 };
 
index ca4b3c530f4e09501c20770e319efb981899587c..9ff219ff875672ec9607ede67f9ee7ca3516734d 100644 (file)
@@ -475,5 +475,115 @@ static void __ssl_gencert_deinit(void)
        }
 #endif
 }
+
+/* Return a new Generated private key of type <keytype> with <bits> and <curves> */
+EVP_PKEY *ssl_gen_EVP_PKEY(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 ? *errmsg : "");
+               goto err;
+       }
+
+       if (EVP_PKEY_keygen_init(pkey_ctx) <= 0) {
+               memprintf(errmsg, "%sCan't generate a private key.\n", errmsg && *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 ? *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 ? *errmsg : "");
+                       goto err;
+               }
+       }
+
+       if (EVP_PKEY_keygen(pkey_ctx, &pkey) <= 0) {
+               memprintf(errmsg, "%sCan't generate a private key.\n", errmsg && *errmsg ? *errmsg : "");
+               goto err;
+       }
+
+err:
+       EVP_PKEY_CTX_free(pkey_ctx);
+       return pkey;
+}
+
+/*
+ * 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 *ssl_gen_x509(EVP_PKEY *pkey)
+{
+       X509         *newcrt  = NULL;
+       X509_NAME    *name;
+       const EVP_MD *digest = NULL;
+       CONF         *ctmp    = NULL;
+       int           key_type;
+
+       /* Create the certificate */
+       if (!(newcrt = X509_new()))
+               goto mkcert_error;
+
+       /* Set version number for the certificate (X509v3) and the serial
+        * number */
+       if (X509_set_version(newcrt, 2L) != 1)
+               goto mkcert_error;
+
+       /* Generate an expired certificate */
+       if (!X509_gmtime_adj(X509_getm_notBefore(newcrt), (long)-60*60*48) ||
+           !X509_gmtime_adj(X509_getm_notAfter(newcrt),(long)-60*60*24))
+               goto mkcert_error;
+
+       /* set public key in the certificate */
+       if (X509_set_pubkey(newcrt, pkey) != 1)
+               goto mkcert_error;
+
+       if ((name = X509_NAME_new()) == NULL)
+               goto mkcert_error;
+
+       /* Set the subject name using the servername but the CN */
+       if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"expired",
+                                      -1, -1, 0) != 1) {
+               X509_NAME_free(name);
+               goto mkcert_error;
+       }
+       if (X509_set_subject_name(newcrt, name) != 1) {
+               X509_NAME_free(name);
+               goto mkcert_error;
+       }
+       /* Set issuer name as itself */
+       if (X509_set_issuer_name(newcrt, name) != 1)
+               goto mkcert_error;
+       X509_NAME_free(name);
+
+       /* Autosign the certificate with the private key */
+       key_type = EVP_PKEY_base_id(pkey);
+
+       if (key_type == EVP_PKEY_RSA)
+               digest = EVP_sha256();
+       else if (key_type == EVP_PKEY_EC)
+               digest = EVP_sha256();
+       else
+               goto mkcert_error;
+
+       if (!(X509_sign(newcrt, pkey, digest)))
+               goto mkcert_error;
+
+       return newcrt;
+
+mkcert_error:
+       if (ctmp) NCONF_free(ctmp);
+       if (newcrt)  X509_free(newcrt);
+       return NULL;
+
+}
+
 REGISTER_POST_DEINIT(__ssl_gencert_deinit);