195 lines
5.9 KiB
Perl
195 lines
5.9 KiB
Perl
|
|
# Copyright (c) 2021-2024, PostgreSQL Global Development Group
|
|
|
|
use strict;
|
|
use warnings FATAL => 'all';
|
|
use PostgreSQL::Test::Cluster;
|
|
use PostgreSQL::Test::Utils;
|
|
use Test::More;
|
|
|
|
use File::Copy;
|
|
|
|
use FindBin;
|
|
use lib $FindBin::RealBin;
|
|
|
|
use SSL::Server;
|
|
|
|
if ($ENV{with_ssl} ne 'openssl')
|
|
{
|
|
plan skip_all => 'OpenSSL not supported by this build';
|
|
}
|
|
elsif (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\bssl\b/)
|
|
{
|
|
plan skip_all =>
|
|
'Potentially unsafe test SSL not enabled in PG_TEST_EXTRA';
|
|
}
|
|
|
|
#### Some configuration
|
|
my $ssl_server = SSL::Server->new();
|
|
|
|
sub sslkey
|
|
{
|
|
return $ssl_server->sslkey(@_);
|
|
}
|
|
|
|
sub switch_server_cert
|
|
{
|
|
$ssl_server->switch_server_cert(@_);
|
|
}
|
|
|
|
# This is the hostname used to connect to the server. This cannot be a
|
|
# hostname, because the server certificate is always for the domain
|
|
# postgresql-ssl-regression.test.
|
|
my $SERVERHOSTADDR = '127.0.0.1';
|
|
# This is the pattern to use in pg_hba.conf to match incoming connections.
|
|
my $SERVERHOSTCIDR = '127.0.0.1/32';
|
|
|
|
# Determine whether build supports sslcertmode=require.
|
|
my $supports_sslcertmode_require =
|
|
check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
|
|
|
|
# Allocation of base connection string shared among multiple tests.
|
|
my $common_connstr;
|
|
|
|
#### Set up the server.
|
|
|
|
note "setting up data directory";
|
|
my $node = PostgreSQL::Test::Cluster->new('primary');
|
|
$node->init;
|
|
|
|
# PGHOST is enforced here to set up the node, subsequent connections
|
|
# will use a dedicated connection string.
|
|
$ENV{PGHOST} = $node->host;
|
|
$ENV{PGPORT} = $node->port;
|
|
$node->start;
|
|
|
|
$ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR,
|
|
$SERVERHOSTCIDR, 'trust', extensions => [qw(sslinfo)]);
|
|
|
|
# We aren't using any CRL's in this suite so we can keep using server-revoked
|
|
# as server certificate for simple client.crt connection much like how the
|
|
# 001 test does.
|
|
switch_server_cert($node, certfile => 'server-revoked');
|
|
|
|
# Set of default settings for SSL parameters in connection string. This
|
|
# makes the tests protected against any defaults the environment may have
|
|
# in ~/.postgresql/.
|
|
my $default_ssl_connstr =
|
|
"sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
|
|
|
|
$common_connstr =
|
|
"$default_ssl_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR host=localhost "
|
|
. "user=ssltestuser sslcert=ssl/client_ext.crt "
|
|
. sslkey('client_ext.key');
|
|
|
|
# Make sure we can connect even though previous test suites have established this
|
|
$node->connect_ok(
|
|
$common_connstr,
|
|
"certificate authorization succeeds with correct client cert in PEM format",
|
|
);
|
|
|
|
my $result;
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT ssl_is_used();",
|
|
connstr => $common_connstr);
|
|
is($result, 't', "ssl_is_used() for TLS connection");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT ssl_version();",
|
|
connstr => $common_connstr
|
|
. " ssl_min_protocol_version=TLSv1.2 "
|
|
. "ssl_max_protocol_version=TLSv1.2");
|
|
is($result, 'TLSv1.2', "ssl_version() correctly returning TLS protocol");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT ssl_cipher() = cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
|
|
connstr => $common_connstr);
|
|
is($result, 't', "ssl_cipher() compared with pg_stat_ssl");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT ssl_client_cert_present();",
|
|
connstr => $common_connstr);
|
|
is($result, 't', "ssl_client_cert_present() for connection with cert");
|
|
|
|
$result = $node->safe_psql(
|
|
"trustdb",
|
|
"SELECT ssl_client_cert_present();",
|
|
connstr =>
|
|
"$default_ssl_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require "
|
|
. "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser host=localhost"
|
|
);
|
|
is($result, 'f', "ssl_client_cert_present() for connection without cert");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT ssl_client_serial() = client_serial FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
|
|
connstr => $common_connstr);
|
|
is($result, 't', "ssl_client_serial() compared with pg_stat_ssl");
|
|
|
|
# Must not use safe_psql since we expect an error here
|
|
$result = $node->psql(
|
|
"certdb",
|
|
"SELECT ssl_client_dn_field('invalid');",
|
|
connstr => $common_connstr);
|
|
is($result, '3', "ssl_client_dn_field() for an invalid field");
|
|
|
|
$result = $node->safe_psql(
|
|
"trustdb",
|
|
"SELECT ssl_client_dn_field('commonName');",
|
|
connstr =>
|
|
"$default_ssl_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require "
|
|
. "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser host=localhost"
|
|
);
|
|
is($result, '', "ssl_client_dn_field() for connection without cert");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
|
|
connstr => $common_connstr);
|
|
is($result, 't', "ssl_client_dn_field() for commonName");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
|
|
connstr => $common_connstr);
|
|
is($result, 't', "ssl_issuer_dn() for connection with cert");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
|
|
connstr => $common_connstr);
|
|
is($result, 't', "ssl_issuer_field() for commonName");
|
|
|
|
$result = $node->safe_psql(
|
|
"certdb",
|
|
"SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
|
|
connstr => $common_connstr);
|
|
is($result, 'CA:FALSE|t', 'extract extension from cert');
|
|
|
|
# Sanity tests for sslcertmode, using ssl_client_cert_present()
|
|
my @cases = (
|
|
{ opts => "sslcertmode=allow", present => 't' },
|
|
{ opts => "sslcertmode=allow sslcert=invalid", present => 'f' },
|
|
{ opts => "sslcertmode=disable", present => 'f' },);
|
|
if ($supports_sslcertmode_require)
|
|
{
|
|
push(@cases, { opts => "sslcertmode=require", present => 't' });
|
|
}
|
|
|
|
foreach my $c (@cases)
|
|
{
|
|
$result = $node->safe_psql(
|
|
"trustdb",
|
|
"SELECT ssl_client_cert_present();",
|
|
connstr => "$common_connstr dbname=trustdb $c->{'opts'}");
|
|
is($result, $c->{'present'},
|
|
"ssl_client_cert_present() for $c->{'opts'}");
|
|
}
|
|
|
|
done_testing();
|