diff --git a/doc/configuration.txt b/doc/configuration.txt index 52c2bf60b..754102a55 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 + 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 + 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 ---------- diff --git a/include/haproxy/ssl_ckch-t.h b/include/haproxy/ssl_ckch-t.h index dc6d98857..f2024f082 100644 --- a/include/haproxy/ssl_ckch-t.h +++ b/include/haproxy/ssl_ckch-t.h @@ -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; }; /* diff --git a/include/haproxy/ssl_gencert.h b/include/haproxy/ssl_gencert.h index 686b2d5bc..39c0b3c7f 100644 --- a/include/haproxy/ssl_gencert.h +++ b/include/haproxy/ssl_gencert.h @@ -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 */ diff --git a/src/acme.c b/src/acme.c index b8ad8df67..10fb6baa3 100644 --- a/src/acme.c +++ b/src/acme.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -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 with and */ -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; + return NULL; - /* 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; - -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); diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c index 7753fb718..96020eb8f 100644 --- a/src/ssl_ckch.c +++ b/src/ssl_ckch.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -4763,18 +4764,69 @@ 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) { + if (s->conf.gencrt.key.type || + s->conf.gencrt.key.bits || + s->conf.gencrt.key.curves) { + memprintf(err, "keyword 'keytype', 'bits' and 'curves' require" + " 'generate-dummy' set to 'on'\n"); + err_code |= ERR_FATAL; + goto out; + } + } + + 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, "keyword 'keytype' requires either 'RSA' or 'ECDSA'."); + 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 '%s'.", s->conf.gencrt.key.curves); + 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."); + 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 +4885,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, } }; diff --git a/src/ssl_gencert.c b/src/ssl_gencert.c index ca4b3c530..9ff219ff8 100644 --- a/src/ssl_gencert.c +++ b/src/ssl_gencert.c @@ -475,5 +475,115 @@ static void __ssl_gencert_deinit(void) } #endif } + +/* Return a new Generated private key of type with and */ +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 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);