# Copyright (c) 2021-2023, PostgreSQL Global Development Group use strict; use warnings; 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} !~ /\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'; # 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'); done_testing();