mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-11-08 12:31:21 +01:00
MEDIUM: ssl: Add certificate password callback that calls external command
When a certificate is protected by a password, we can provide the password via the dedicated pem_password_cb param provided to PEM_read_bio_PrivateKey. HAProxy will fetch the password automatically during init by calling a user-defined external command that should dump the right password on its standard output (see new 'ssl-passphrase-cmd' global option).
This commit is contained in:
parent
a011683622
commit
478dd7bad0
@ -3339,6 +3339,18 @@ ssl-dh-param-file <file>
|
|||||||
"openssl dhparam <size>", where size should be at least 2048, as 1024-bit DH
|
"openssl dhparam <size>", where size should be at least 2048, as 1024-bit DH
|
||||||
parameters should not be considered secure anymore.
|
parameters should not be considered secure anymore.
|
||||||
|
|
||||||
|
ssl-passphrase-cmd <cmd> <args> ...
|
||||||
|
This settings is only available when support for OpenSSL was built in. It
|
||||||
|
allows to define a full command line that will be called when an encrypted
|
||||||
|
certificate is loaded during init. The command could be a script or any other
|
||||||
|
program. It will be provided with the encrypted private key path as first
|
||||||
|
parameter and the user-defined "args" parameters then and should dump the
|
||||||
|
passphrase that allows to decode the encrypted private key on the standard
|
||||||
|
output.
|
||||||
|
For every new encrypted private key loaded during init, HAProxy will first
|
||||||
|
try every other already known passphrase to decode the private key and will
|
||||||
|
ultimately call the passphrase command again if none works.
|
||||||
|
|
||||||
ssl-propquery <query>
|
ssl-propquery <query>
|
||||||
This setting is only available when support for OpenSSL was built in and when
|
This setting is only available when support for OpenSSL was built in and when
|
||||||
OpenSSL's version is at least 3.0. It allows to define a default property
|
OpenSSL's version is at least 3.0. It allows to define a default property
|
||||||
|
|||||||
@ -336,6 +336,8 @@ struct global_ssl {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int renegotiate; /* Renegotiate mode (SSL_RENEGOTIATE_ flag) */
|
int renegotiate; /* Renegotiate mode (SSL_RENEGOTIATE_ flag) */
|
||||||
|
char **passphrase_cmd;
|
||||||
|
int passphrase_cmd_args_cnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The order here matters for picking a default context,
|
/* The order here matters for picking a default context,
|
||||||
@ -355,5 +357,10 @@ struct ssl_counters {
|
|||||||
long long failed_ocsp_staple;
|
long long failed_ocsp_staple;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct passphrase_cb_data {
|
||||||
|
const char *path;
|
||||||
|
int passphrase_idx;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* USE_OPENSSL */
|
#endif /* USE_OPENSSL */
|
||||||
#endif /* _HAPROXY_SSL_SOCK_T_H */
|
#endif /* _HAPROXY_SSL_SOCK_T_H */
|
||||||
|
|||||||
@ -132,6 +132,7 @@ struct issuer_chain* ssl_get0_issuer_chain(X509 *cert);
|
|||||||
int ssl_load_global_issuer_from_BIO(BIO *in, char *fp, char **err);
|
int ssl_load_global_issuer_from_BIO(BIO *in, char *fp, char **err);
|
||||||
int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, int is_default, char **err);
|
int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, int is_default, char **err);
|
||||||
int ssl_sock_load_srv_cert(char *path, struct server *server, int create_if_none, char **err);
|
int ssl_sock_load_srv_cert(char *path, struct server *server, int create_if_none, char **err);
|
||||||
|
int ssl_sock_passwd_cb(char *buf, int size, int rwflag, void *userdata);
|
||||||
void ssl_free_global_issuers(void);
|
void ssl_free_global_issuers(void);
|
||||||
int ssl_initialize_random(void);
|
int ssl_initialize_random(void);
|
||||||
int ssl_sock_load_cert_list_file(char *file, int dir, struct bind_conf *bind_conf, struct proxy *curproxy, char **err);
|
int ssl_sock_load_cert_list_file(char *file, int dir, struct bind_conf *bind_conf, struct proxy *curproxy, char **err);
|
||||||
|
|||||||
@ -676,6 +676,65 @@ static int ssl_parse_global_extra_noext(char **args, int section_type, struct pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* parse 'ssl-passphrase-cmd' */
|
||||||
|
static int ssl_parse_global_passphrase_cmd(char **args, int section_type, struct proxy *curpx,
|
||||||
|
const struct proxy *defpx, const char *file, int line,
|
||||||
|
char **err)
|
||||||
|
{
|
||||||
|
int arg_cnt = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!*args[1]) {
|
||||||
|
memprintf(err, "global statement '%s' expects a command line to a passphrase-providing tool (script/binary...) and its arguments.", args[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; *args[arg_cnt + 2]; ++arg_cnt)
|
||||||
|
;
|
||||||
|
|
||||||
|
/* The first argument, by convention, should point to the filename
|
||||||
|
* associated with the file being executed. The array of pointers must
|
||||||
|
* be terminated by a null pointer.
|
||||||
|
* The certificate path will also be passed as first arg so we must
|
||||||
|
* leave enough space .
|
||||||
|
*/
|
||||||
|
global_ssl.passphrase_cmd_args_cnt = arg_cnt + 1 + 1 + 1;
|
||||||
|
|
||||||
|
global_ssl.passphrase_cmd = calloc(global_ssl.passphrase_cmd_args_cnt, sizeof(*global_ssl.passphrase_cmd));
|
||||||
|
if (!global_ssl.passphrase_cmd) {
|
||||||
|
memprintf(err, "'%s' : Could not allocate memory", args[0]);
|
||||||
|
return ERR_ALERT | ERR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
global_ssl.passphrase_cmd[0] = strdup(args[1]);
|
||||||
|
|
||||||
|
if (!global_ssl.passphrase_cmd[0]) {
|
||||||
|
memprintf(err, "'%s' : Could not allocate memory", args[0]);
|
||||||
|
goto err_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < arg_cnt; ++i) {
|
||||||
|
/* The first two slots have a special use, they will contain the
|
||||||
|
* command path and the certificate path. */
|
||||||
|
global_ssl.passphrase_cmd[i + 2] = strdup(args[i + 2]);
|
||||||
|
if (!global_ssl.passphrase_cmd[i + 2]) {
|
||||||
|
memprintf(err, "'%s' : Could not allocate memory (command line)", args[0]);
|
||||||
|
goto err_alloc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_alloc:
|
||||||
|
for (i = 0; i < arg_cnt; ++i) {
|
||||||
|
ha_free(&global_ssl.passphrase_cmd[i]);
|
||||||
|
}
|
||||||
|
ha_free(&global_ssl.passphrase_cmd);
|
||||||
|
|
||||||
|
return ERR_ALERT | ERR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***************************** Bind keyword Parsing ********************************************/
|
/***************************** Bind keyword Parsing ********************************************/
|
||||||
|
|
||||||
/* for ca-file and ca-verify-file */
|
/* for ca-file and ca-verify-file */
|
||||||
@ -2715,6 +2774,8 @@ static struct cfg_kw_list cfg_kws = {ILH, {
|
|||||||
|
|
||||||
{ CFG_LISTEN, "ssl-f-use", proxy_parse_ssl_f_use },
|
{ CFG_LISTEN, "ssl-f-use", proxy_parse_ssl_f_use },
|
||||||
|
|
||||||
|
{ CFG_GLOBAL, "ssl-passphrase-cmd", ssl_parse_global_passphrase_cmd },
|
||||||
|
|
||||||
{ 0, NULL, NULL },
|
{ 0, NULL, NULL },
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|||||||
@ -593,6 +593,7 @@ int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct ckch_data *d
|
|||||||
BIO *in = NULL;
|
BIO *in = NULL;
|
||||||
int ret = 1;
|
int ret = 1;
|
||||||
EVP_PKEY *key = NULL;
|
EVP_PKEY *key = NULL;
|
||||||
|
struct passphrase_cb_data cb_data = { path, 0 };
|
||||||
|
|
||||||
if (buf) {
|
if (buf) {
|
||||||
/* reading from a buffer */
|
/* reading from a buffer */
|
||||||
@ -613,7 +614,7 @@ int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct ckch_data *d
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Read Private Key */
|
/* Read Private Key */
|
||||||
key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
|
key = PEM_read_bio_PrivateKey(in, NULL, ssl_sock_passwd_cb, &cb_data);
|
||||||
if (key == NULL) {
|
if (key == NULL) {
|
||||||
memprintf(err, "%sunable to load private key from file '%s'.\n",
|
memprintf(err, "%sunable to load private key from file '%s'.\n",
|
||||||
err && *err ? *err : "", path);
|
err && *err ? *err : "", path);
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
@ -150,6 +151,8 @@ struct global_ssl global_ssl = {
|
|||||||
.acme_scheduler = 1,
|
.acme_scheduler = 1,
|
||||||
#endif
|
#endif
|
||||||
.renegotiate = SSL_RENEGOTIATE_DFLT,
|
.renegotiate = SSL_RENEGOTIATE_DFLT,
|
||||||
|
.passphrase_cmd = NULL,
|
||||||
|
.passphrase_cmd_args_cnt = 0,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3650,6 +3653,93 @@ out:
|
|||||||
return cfgerr;
|
return cfgerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Certificate password callback. The password will be provided by the external
|
||||||
|
* program defined in global section (see 'ssl-passphrase-cmd'). It will be
|
||||||
|
* called in a separate fork and it should dump the password on standard output.
|
||||||
|
*/
|
||||||
|
int ssl_sock_passwd_cb(char *buf, int size, int rwflag, void *userdata)
|
||||||
|
{
|
||||||
|
int pass_len;
|
||||||
|
int read_len;
|
||||||
|
pid_t pid = -1;
|
||||||
|
int wstatus = 0;
|
||||||
|
|
||||||
|
int fd[2];
|
||||||
|
|
||||||
|
struct passphrase_cb_data *data = userdata;
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!global_ssl.passphrase_cmd) {
|
||||||
|
ha_alert("Trying to load a passphrase-protected private key without an 'ssl-passphrase-cmd' defined.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* From execvp manpage : "The first argument, by convention, should
|
||||||
|
* point to the filename associated with the file being executed."
|
||||||
|
* The second argument will be the certificate key path.
|
||||||
|
*/
|
||||||
|
ha_free(&global_ssl.passphrase_cmd[1]);
|
||||||
|
global_ssl.passphrase_cmd[1] = strdup(data->path);
|
||||||
|
|
||||||
|
if (!global_ssl.passphrase_cmd[1]) {
|
||||||
|
ha_alert("ssl_sock_passwd_cb: allocation failure\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe(fd) < 0) {
|
||||||
|
ha_alert("ssl_sock_passwd_cb: pipe error");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
|
||||||
|
switch(pid) {
|
||||||
|
case -1:
|
||||||
|
ha_alert("ssl_sock_passwd_cb: could not fork");
|
||||||
|
goto error;
|
||||||
|
case 0:
|
||||||
|
/* In child process, need to call external tool via execv to get
|
||||||
|
* passphrase */
|
||||||
|
close(0);
|
||||||
|
dup2(fd[1], 1);
|
||||||
|
|
||||||
|
execvp(global_ssl.passphrase_cmd[0], global_ssl.passphrase_cmd);
|
||||||
|
exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* in parent */
|
||||||
|
/* Close write side of pipe, it won't be used by the parent */
|
||||||
|
close(fd[1]);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
read_len = read(fd[0], buf, size);
|
||||||
|
if (read_len <= 0)
|
||||||
|
break;
|
||||||
|
pass_len = read_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close read side of pipe */
|
||||||
|
close(fd[0]);
|
||||||
|
waitpid(pid, &wstatus, 0);
|
||||||
|
if (WEXITSTATUS(wstatus) != 0) {
|
||||||
|
ha_alert("ssl_sock_passwd_cb: external tool error (%d)\n", WEXITSTATUS(wstatus));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pass_len;
|
||||||
|
|
||||||
|
error:
|
||||||
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Create an initial CTX used to start the SSL connection before switchctx */
|
/* Create an initial CTX used to start the SSL connection before switchctx */
|
||||||
static int
|
static int
|
||||||
ssl_sock_initial_ctx(struct bind_conf *bind_conf)
|
ssl_sock_initial_ctx(struct bind_conf *bind_conf)
|
||||||
@ -8001,6 +8091,14 @@ static void ssl_free_global(void)
|
|||||||
ha_free(&global_ssl.listen_default_client_sigalgs);
|
ha_free(&global_ssl.listen_default_client_sigalgs);
|
||||||
ha_free(&global_ssl.connect_default_client_sigalgs);
|
ha_free(&global_ssl.connect_default_client_sigalgs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (global_ssl.passphrase_cmd) {
|
||||||
|
int i = 0;
|
||||||
|
for (; i < global_ssl.passphrase_cmd_args_cnt; ++i) {
|
||||||
|
ha_free(&global_ssl.passphrase_cmd[i]);
|
||||||
|
}
|
||||||
|
ha_free(&global_ssl.passphrase_cmd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __ssl_sock_init(void)
|
static void __ssl_sock_init(void)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user