1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-02 09:02:37 +03:00

Allow to use system CA pool for certificate verification

This adds a new option to libpq's sslrootcert, "system", which will load
the system trusted CA roots for certificate verification. This is a more
convenient way to achieve this than pointing to the system CA roots
manually since the location can differ by installation and be locally
adjusted by env vars in OpenSSL.

When sslrootcert is set to system, sslmode is forced to be verify-full
as weaker modes aren't providing much security for public CAs.

Changing the location of the system roots by setting environment vars is
not supported by LibreSSL so the tests will use a heuristic to determine
if the system being tested is LibreSSL or OpenSSL.

The workaround in .cirrus.yml is required to handle a strange interaction
between homebrew and the openssl@3 formula; hopefully this can be removed
in the near future.

The original patch was written by Thomas Habets, which was later revived
by Jacob Champion.

Author: Jacob Champion <jchampion@timescale.com>
Author: Thomas Habets <thomas@habets.se>
Reviewed-by: Jelte Fennema <postgres@jeltef.nl>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Reviewed-by: Magnus Hagander <magnus@hagander.net>
Discussion: https://www.postgresql.org/message-id/flat/CA%2BkHd%2BcJwCUxVb-Gj_0ptr3_KZPwi3%2B67vK6HnLFBK9MzuYrLA%40mail.gmail.com
This commit is contained in:
Daniel Gustafsson
2023-04-05 23:22:17 +02:00
parent 12f3867f55
commit 8eda731465
9 changed files with 247 additions and 9 deletions

View File

@ -1465,6 +1465,23 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
#ifndef USE_SSL
/*
* sslrootcert=system is not supported. Since setting this changes the
* default sslmode, check this _before_ we validate sslmode, to avoid
* confusing the user with errors for an option they may not have set.
*/
if (conn->sslrootcert
&& strcmp(conn->sslrootcert, "system") == 0)
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "sslrootcert value \"%s\" invalid when SSL support is not compiled in",
conn->sslrootcert);
return false;
}
#endif
/*
* validate sslmode option
*/
@ -1511,6 +1528,22 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
#ifdef USE_SSL
/*
* If sslrootcert=system, make sure our chosen sslmode is compatible.
*/
if (conn->sslrootcert
&& strcmp(conn->sslrootcert, "system") == 0
&& strcmp(conn->sslmode, "verify-full") != 0)
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "weak sslmode \"%s\" may not be used with sslrootcert=system (use verify-full)",
conn->sslmode);
return false;
}
#endif
/*
* Validate TLS protocol versions for ssl_min_protocol_version and
* ssl_max_protocol_version.
@ -6236,6 +6269,8 @@ static bool
conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
{
PQconninfoOption *option;
PQconninfoOption *sslmode_default = NULL,
*sslrootcert = NULL;
char *tmp;
/*
@ -6252,6 +6287,9 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
*/
for (option = options; option->keyword != NULL; option++)
{
if (strcmp(option->keyword, "sslrootcert") == 0)
sslrootcert = option; /* save for later */
if (option->val != NULL)
continue; /* Value was in conninfo or service */
@ -6294,6 +6332,13 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
}
continue;
}
/*
* sslmode is not specified. Let it be filled in with the compiled
* default for now, but if sslrootcert=system, we'll override the
* default later before returning.
*/
sslmode_default = option;
}
/*
@ -6326,6 +6371,27 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
}
}
/*
* Special handling for sslrootcert=system with no sslmode explicitly
* defined. In this case we want to strengthen the default sslmode to
* verify-full.
*/
if (sslmode_default && sslrootcert)
{
if (sslrootcert->val && strcmp(sslrootcert->val, "system") == 0)
{
free(sslmode_default->val);
sslmode_default->val = strdup("verify-full");
if (!sslmode_default->val)
{
if (errorMessage)
libpq_append_error(errorMessage, "out of memory");
return false;
}
}
}
return true;
}

View File

@ -1060,8 +1060,29 @@ initialize_SSL(PGconn *conn)
else
fnbuf[0] = '\0';
if (fnbuf[0] != '\0' &&
stat(fnbuf, &buf) == 0)
if (strcmp(fnbuf, "system") == 0)
{
/*
* The "system" sentinel value indicates that we should load whatever
* root certificates are installed for use by OpenSSL; these locations
* differ by platform. Note that the default system locations may be
* further overridden by the SSL_CERT_DIR and SSL_CERT_FILE
* environment variables.
*/
if (SSL_CTX_set_default_verify_paths(SSL_context) != 1)
{
char *err = SSLerrmessage(ERR_get_error());
libpq_append_conn_error(conn, "could not load system root certificate paths: %s",
err);
SSLerrfree(err);
SSL_CTX_free(SSL_context);
return -1;
}
have_rootcert = true;
}
else if (fnbuf[0] != '\0' &&
stat(fnbuf, &buf) == 0)
{
X509_STORE *cvstore;
@ -1122,10 +1143,10 @@ initialize_SSL(PGconn *conn)
*/
if (fnbuf[0] == '\0')
libpq_append_conn_error(conn, "could not get home directory to locate root certificate file\n"
"Either provide the file or change sslmode to disable server certificate verification.");
"Either provide the file, use the system's trusted roots with sslrootcert=system, or change sslmode to disable server certificate verification.");
else
libpq_append_conn_error(conn, "root certificate file \"%s\" does not exist\n"
"Either provide the file or change sslmode to disable server certificate verification.", fnbuf);
"Either provide the file, use the system's trusted roots with sslrootcert=system, or change sslmode to disable server certificate verification.", fnbuf);
SSL_CTX_free(SSL_context);
return -1;
}

View File

@ -8,7 +8,9 @@ use IPC::Run;
# List of URIs tests. For each test the first element is the input string, the
# second the expected stdout and the third the expected stderr.
# second the expected stdout and the third the expected stderr. Optionally,
# additional arguments may specify key/value pairs which will override
# environment variables for the duration of the test.
my @tests = (
[
q{postgresql://uri-user:secret@host:12345/db},
@ -209,20 +211,44 @@ my @tests = (
q{postgres://%2Fvar%2Flib%2Fpostgresql/dbname},
q{dbname='dbname' host='/var/lib/postgresql' (local)},
q{},
],
# Usually the default sslmode is 'prefer' (for libraries with SSL) or
# 'disable' (for those without). This default changes to 'verify-full' if
# the system CA store is in use.
[
q{postgresql://host?sslmode=disable},
q{host='host' sslmode='disable' (inet)},
q{},
PGSSLROOTCERT => "system",
],
[
q{postgresql://host?sslmode=prefer},
q{host='host' sslmode='prefer' (inet)},
q{},
PGSSLROOTCERT => "system",
],
[
q{postgresql://host?sslmode=verify-full},
q{host='host' (inet)},
q{},
PGSSLROOTCERT => "system",
]);
# test to run for each of the above test definitions
sub test_uri
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
local %ENV = %ENV;
my $uri;
my %expect;
my %envvars;
my %result;
($uri, $expect{stdout}, $expect{stderr}) = @$_;
($uri, $expect{stdout}, $expect{stderr}, %envvars) = @$_;
$expect{'exit'} = $expect{stderr} eq '';
%ENV = (%ENV, %envvars);
my $cmd = [ 'libpq_uri_regress', $uri ];
$result{exit} = IPC::Run::run $cmd, '>', \$result{stdout}, '2>',