From a01c37d8805577c2833ced2bc02107cb5c2fd394 Mon Sep 17 00:00:00 2001 From: Tatsuo Ishii Date: Mon, 30 Mar 2020 12:23:37 +0900 Subject: [PATCH] Add support for SSL passphrase. Authors: Umar Hayat. Small modification and Japanese document by Tatsuo Ishii. Dicussion: https://www.pgpool.net/pipermail/pgpool-hackers/2020-March/003548.html --- doc.ja/src/sgml/ssl.sgml | 29 +++++ doc/src/sgml/ssl.sgml | 30 ++++++ src/config/pool_config_variables.c | 11 ++ src/include/pool_config.h | 1 + src/sample/pgpool.conf.sample-logical | 4 + src/sample/pgpool.conf.sample-raw | 4 + src/sample/pgpool.conf.sample-replication | 4 + src/sample/pgpool.conf.sample-slony | 4 + src/sample/pgpool.conf.sample-stream | 4 + src/utils/pool_process_reporting.c | 5 + src/utils/pool_ssl.c | 122 ++++++++++++++++++++-- 11 files changed, 211 insertions(+), 7 deletions(-) diff --git a/doc.ja/src/sgml/ssl.sgml b/doc.ja/src/sgml/ssl.sgml index 755d40f1b..bab07f429 100644 --- a/doc.ja/src/sgml/ssl.sgml +++ b/doc.ja/src/sgml/ssl.sgml @@ -354,6 +354,35 @@ + + ssl_passphrase_command (string) + + ssl_passphrase_command設定パラメータ + + + + + 秘密鍵などのSSLファイルを復号する際に、パスフレーズの入手が必要な時に起動される外部コマンドを設定します。 + デフォルトではこのパラメータは空文字で、この場合はパスフレーズが要求されてもSSLファイルはロードされません。 + + + このコマンドは、パスフレーズを標準出力に書き出し、コード0で終了しなければなりません。 + パラメータの値の%pはプロンプト文字列に置き換えられます。 + (%を使いたい場合は%%としてください。) + プロンプト文字列はおそらく空白文字を含むので、適切に引用符付けするように注意してください。 + 出力の最後に一個の改行があれば、削除されます。 + + + このコマンドは実際にはパスフレーズ用にユーザにプロンプトを表示する必要はありません。 + ファイルからパスフレーズが読めるなら、キーチェーン機構やその他から取得します。 + 選択された仕組みが適切にセキュアかどうかを確認するのはユーザ次第です。 + + + このパラメータはサーバ起動時にのみ設定可能です。 + + + + diff --git a/doc/src/sgml/ssl.sgml b/doc/src/sgml/ssl.sgml index 0b04b2448..e413463bb 100644 --- a/doc/src/sgml/ssl.sgml +++ b/doc/src/sgml/ssl.sgml @@ -239,6 +239,36 @@ + + ssl_passphrase_command (string) + + ssl_passphrase_command configuration parameter + + + + + Sets an external command to be invoked when a passphrase for decrypting + an SSL file such as a private key needs to be obtained. By default, + this parameter is empty, which means SSL file will not be loaded if passphrase is required. + + + The command must print the passphrase to the standard output and + exit with code 0. In the parameter value, %p is replaced by a prompt + string. (Write %% for a literal %.) Note that the prompt string will probably + contain whitespace, so be sure to quote adequately. A single newline is stripped + from the end of the output if present. + + + The command does not actually have to prompt the user for a passphrase. + It can read it from a file, obtain it from a keychain facility, or similar. + It is up to the user to make sure the chosen mechanism is adequately secure. + + + This parameter can only be set at server start. + + + + diff --git a/src/config/pool_config_variables.c b/src/config/pool_config_variables.c index 8d02c3189..809a05221 100644 --- a/src/config/pool_config_variables.c +++ b/src/config/pool_config_variables.c @@ -1109,6 +1109,17 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL, NULL }, + { + {"ssl_passphrase_command", CFGCXT_INIT, SSL_CONFIG, + "Path to the Diffie-Hellman parameters contained file", + CONFIG_VAR_TYPE_STRING, false, 0 + }, + &g_pool_config.ssl_passphrase_command, + "", + NULL, NULL, NULL, NULL + }, + + { {"memqcache_oiddir", CFGCXT_INIT, CACHE_CONFIG, "Tempory directory to record table oids.", diff --git a/src/include/pool_config.h b/src/include/pool_config.h index a14ead1c4..218842633 100644 --- a/src/include/pool_config.h +++ b/src/include/pool_config.h @@ -367,6 +367,7 @@ typedef struct bool ssl_prefer_server_ciphers; /*Use SSL cipher preferences, rather than the client's*/ char *ssl_ecdh_curve; /* the curve to use in ECDH key exchange */ char *ssl_dh_params_file; /* path to the Diffie-Hellman parameters contained file */ + char *ssl_passphrase_command; /* path to the Diffie-Hellman parameters contained file */ int64 relcache_expire; /* relation cache life time in seconds */ int relcache_size; /* number of relation cache life entry */ CHECK_TEMP_TABLE_OPTION check_temp_table; /* how to check temporary table */ diff --git a/src/sample/pgpool.conf.sample-logical b/src/sample/pgpool.conf.sample-logical index 6158ed89e..6eb146b63 100644 --- a/src/sample/pgpool.conf.sample-logical +++ b/src/sample/pgpool.conf.sample-logical @@ -144,6 +144,10 @@ ssl_ecdh_curve = 'prime256v1' ssl_dh_params_file = '' # Name of the file containing Diffie-Hellman parameters used # for so-called ephemeral DH family of SSL cipher. +#ssl_passphrase_command='' + # Sets an external command to be invoked when a passphrase + # for decrypting an SSL file needs to be obtained + # (change requires restart) #------------------------------------------------------------------------------ # POOLS diff --git a/src/sample/pgpool.conf.sample-raw b/src/sample/pgpool.conf.sample-raw index de25b5697..16cca78a4 100644 --- a/src/sample/pgpool.conf.sample-raw +++ b/src/sample/pgpool.conf.sample-raw @@ -145,6 +145,10 @@ ssl_ecdh_curve = 'prime256v1' ssl_dh_params_file = '' # Name of the file containing Diffie-Hellman parameters used # for so-called ephemeral DH family of SSL cipher. +#ssl_passphrase_command='' + # Sets an external command to be invoked when a passphrase + # for decrypting an SSL file needs to be obtained + # (change requires restart) #------------------------------------------------------------------------------ # POOLS diff --git a/src/sample/pgpool.conf.sample-replication b/src/sample/pgpool.conf.sample-replication index b17912918..0fc0ee274 100644 --- a/src/sample/pgpool.conf.sample-replication +++ b/src/sample/pgpool.conf.sample-replication @@ -140,6 +140,10 @@ ssl_ecdh_curve = 'prime256v1' ssl_dh_params_file = '' # Name of the file containing Diffie-Hellman parameters used # for so-called ephemeral DH family of SSL cipher. +#ssl_passphrase_command='' + # Sets an external command to be invoked when a passphrase + # for decrypting an SSL file needs to be obtained + # (change requires restart) #------------------------------------------------------------------------------ # POOLS diff --git a/src/sample/pgpool.conf.sample-slony b/src/sample/pgpool.conf.sample-slony index ef840938f..7abc58d40 100644 --- a/src/sample/pgpool.conf.sample-slony +++ b/src/sample/pgpool.conf.sample-slony @@ -141,6 +141,10 @@ ssl_ecdh_curve = 'prime256v1' ssl_dh_params_file = '' # Name of the file containing Diffie-Hellman parameters used # for so-called ephemeral DH family of SSL cipher. +#ssl_passphrase_command='' + # Sets an external command to be invoked when a passphrase + # for decrypting an SSL file needs to be obtained + # (change requires restart) #------------------------------------------------------------------------------ # POOLS diff --git a/src/sample/pgpool.conf.sample-stream b/src/sample/pgpool.conf.sample-stream index 2cc7bda59..26a9ff249 100644 --- a/src/sample/pgpool.conf.sample-stream +++ b/src/sample/pgpool.conf.sample-stream @@ -145,6 +145,10 @@ ssl_ecdh_curve = 'prime256v1' ssl_dh_params_file = '' # Name of the file containing Diffie-Hellman parameters used # for so-called ephemeral DH family of SSL cipher. +#ssl_passphrase_command='' + # Sets an external command to be invoked when a passphrase + # for decrypting an SSL file needs to be obtained + # (change requires restart) #------------------------------------------------------------------------------ # POOLS diff --git a/src/utils/pool_process_reporting.c b/src/utils/pool_process_reporting.c index 126de0e6c..54eb8b70c 100644 --- a/src/utils/pool_process_reporting.c +++ b/src/utils/pool_process_reporting.c @@ -284,6 +284,11 @@ get_config(int *nrows) StrNCpy(status[i].desc, "path to the Diffie-Hellman parameters contained file", POOLCONFIG_MAXDESCLEN); i++; + StrNCpy(status[i].name, "ssl_passphrase_command", POOLCONFIG_MAXNAMELEN); + snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%s", pool_config->ssl_passphrase_command); + StrNCpy(status[i].desc, "external command to be invoked when a passphrase for decrypting an SSL file such as a private key needs to be obtained", POOLCONFIG_MAXDESCLEN); + i++; + /* POOLS */ /* - Pool size - */ diff --git a/src/utils/pool_ssl.c b/src/utils/pool_ssl.c index 59f4dcf9a..cd99374c7 100644 --- a/src/utils/pool_ssl.c +++ b/src/utils/pool_ssl.c @@ -40,8 +40,9 @@ static SSL_CTX *SSL_frontend_context = NULL; static bool SSL_initialized = false; -static bool ssl_passwd_cb_called = false; -static int ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata); +static bool dummy_ssl_passwd_cb_called = false; +static int dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata); +static int ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata); static int verify_cb(int ok, X509_STORE_CTX *ctx); static const char *SSLerrmessage(unsigned long ecode); static void fetch_pool_ssl_cert(POOL_CONNECTION * cp); @@ -49,6 +50,7 @@ static DH *load_dh_file(char *filename); static DH *load_dh_buffer(const char *, size_t); static bool initialize_dh(SSL_CTX *context); static bool initialize_ecdh(SSL_CTX *context); +static int run_ssl_passphrase_command(const char *prompt, char *buf, int size); #define SSL_RETURN_VOID_IF(cond, msg) \ do { \ @@ -474,16 +476,30 @@ fetch_pool_ssl_cert(POOL_CONNECTION * cp) * function that just returns an empty passphrase, guaranteeing failure. */ static int -ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata) +dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata) { /* Set flag to change the error message we'll report */ - ssl_passwd_cb_called = true; + dummy_ssl_passwd_cb_called = true; /* And return empty string */ Assert(size > 0); buf[0] = '\0'; return 0; } +/* + * Passphrase collection callback using ssl_passphrase_command + */ +static int +ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata) +{ + /* same prompt as OpenSSL uses internally */ + const char *prompt = "Enter PEM pass phrase:"; + + Assert(rwflag == 0); + + return run_ssl_passphrase_command(prompt, buf, size); +} + /* * Certificate verification callback * @@ -557,7 +573,10 @@ SSL_ServerSide_init(void) /* * prompt for password for passphrase-protected files */ - SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb); + if(pool_config->ssl_passphrase_command && strlen(pool_config->ssl_passphrase_command)) + SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb); + else + SSL_CTX_set_default_passwd_cb(context, dummy_ssl_passwd_cb); /* * Load and verify server's certificate and private key @@ -611,13 +630,13 @@ SSL_ServerSide_init(void) /* * OK, try to load the private key file. */ - ssl_passwd_cb_called = false; + dummy_ssl_passwd_cb_called = false; if (SSL_CTX_use_PrivateKey_file(context, pool_config->ssl_key, SSL_FILETYPE_PEM) != 1) { - if (ssl_passwd_cb_called) + if (dummy_ssl_passwd_cb_called) ereport(WARNING, (errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase", pool_config->ssl_key))); @@ -905,6 +924,95 @@ load_dh_buffer(const char *buffer, size_t len) return dh; } +/* + * Run ssl_passphrase_command + * + * The result will be put in buffer buf, which is of size size. The return + * value is the length of the actual result. + */ +int +run_ssl_passphrase_command(const char *prompt, char *buf, int size) +{ + int loglevel = ERROR; + StringInfoData command; + char *p; + FILE *fh; + int pclose_rc; + size_t len = 0; + + Assert(prompt); + Assert(size > 0); + buf[0] = '\0'; + + initStringInfo(&command); + + for (p = pool_config->ssl_passphrase_command; *p; p++) + { + if (p[0] == '%') + { + switch (p[1]) + { + case 'p': + appendStringInfoString(&command, prompt); + p++; + break; + case '%': + appendStringInfoChar(&command, '%'); + p++; + break; + default: + appendStringInfoChar(&command, p[0]); + } + } + else + appendStringInfoChar(&command, p[0]); + } + + fh = popen(command.data, "r"); + if (fh == NULL) + { + ereport(loglevel, + (errmsg("could not execute command \"%s\": %m", + command.data))); + goto error; + } + + if (!fgets(buf, size, fh)) + { + if (ferror(fh)) + { + ereport(loglevel, + (errmsg("could not read from command \"%s\": %m", + command.data))); + goto error; + } + } + + pclose_rc = pclose(fh); + if (pclose_rc == -1) + { + ereport(loglevel, + (errmsg("could not close pipe to external command: %m"))); + goto error; + } + else if (pclose_rc != 0) + { + ereport(loglevel, + (errmsg("command \"%s\" failed", + command.data))); + goto error; + } + + /* strip trailing newline */ + len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') + buf[--len] = '\0'; + +error: + pfree(command.data); + return len; +} + #else /* USE_SSL: wrap / no-op ssl functionality if * it's not available */ -- 2.39.5