postgresql/src/test/ssl/t/003_sslinfo.pl

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();