aboutsummaryrefslogtreecommitdiff
path: root/src/test/ssl/t/001_ssltests.pl
blob: 598a5fe07acea0f11d16185df78baa5768cfefc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 40;
use ServerSetup;
use File::Copy;

# Like TestLib.pm, we use IPC::Run
BEGIN
{
	eval {
		require IPC::Run;
		import IPC::Run qw(run start);
		1;
	} or do
	{
		plan skip_all => "IPC::Run not available";
	  }
}

#### Some configuration

# 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';

# Define a couple of helper functions to test connecting to the server.

my $common_connstr;

sub run_test_psql
{
	my $connstr   = $_[0];
	my $logstring = $_[1];

	my $cmd = [
		'psql', '-X', '-A', '-t', '-c', "SELECT 'connected with $connstr'",
		'-d', "$connstr" ];

	my $result = run_log($cmd);
	return $result;
}

#
# The first argument is a (part of a) connection string, and it's also printed
# out as the test case name. It is appended to $common_connstr global variable,
# which also contains a libpq connection string.
#
# The second argument is a hostname to connect to.
sub test_connect_ok
{
	my $connstr = $_[0];

	my $result =
	  run_test_psql("$common_connstr $connstr", "(should succeed)");
	ok($result, $connstr);
}

sub test_connect_fails
{
	my $connstr = $_[0];

	my $result = run_test_psql("$common_connstr $connstr", "(should fail)");
	ok(!$result, "$connstr (should fail)");
}

# The client's private key must not be world-readable, so take a copy
# of the key stored in the code tree and update its permissions.
copy("ssl/client.key", "ssl/client_tmp.key");
chmod 0600, "ssl/client_tmp.key";

#### Part 0. Set up the server.

diag "setting up data directory...";
my $node = get_new_node('master');
$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;
configure_test_server_for_ssl($node, $SERVERHOSTADDR);
switch_server_cert($node, 'server-cn-only');

### Part 1. Run client-side tests.
###
### Test that libpq accepts/rejects the connection correctly, depending
### on sslmode and whether the server's certificate looks correct. No
### client certificate is used in these tests.

diag "running client tests...";

$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";

# The server should not accept non-SSL connections
diag "test that the server doesn't accept non-SSL connections";
test_connect_fails("sslmode=disable");

# Try without a root cert. In sslmode=require, this should work. In verify-ca
# or verify-full mode it should fail
diag "connect without server root cert";
test_connect_ok("sslrootcert=invalid sslmode=require");
test_connect_fails("sslrootcert=invalid sslmode=verify-ca");
test_connect_fails("sslrootcert=invalid sslmode=verify-full");

# Try with wrong root cert, should fail. (we're using the client CA as the
# root, but the server's key is signed by the server CA)
diag "connect without wrong server root cert";
test_connect_fails("sslrootcert=ssl/client_ca.crt sslmode=require");
test_connect_fails("sslrootcert=ssl/client_ca.crt sslmode=verify-ca");
test_connect_fails("sslrootcert=ssl/client_ca.crt sslmode=verify-full");

# Try with just the server CA's cert. This fails because the root file
# must contain the whole chain up to the root CA.
diag "connect with server CA cert, without root CA";
test_connect_fails("sslrootcert=ssl/server_ca.crt sslmode=verify-ca");

# And finally, with the correct root cert.
diag "connect with correct server CA cert file";
test_connect_ok("sslrootcert=ssl/root+server_ca.crt sslmode=require");
test_connect_ok("sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca");
test_connect_ok("sslrootcert=ssl/root+server_ca.crt sslmode=verify-full");

# Test with cert root file that contains two certificates. The client should
# be able to pick the right one, regardless of the order in the file.
test_connect_ok("sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca");
test_connect_ok("sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca");

diag "testing sslcrl option with a non-revoked cert";

# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");

# A CRL belonging to a different CA is not accepted, fails
test_connect_fails(
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");

# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
);

# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
diag "test mismatch between hostname and server certificate";
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";

test_connect_ok("sslmode=require host=wronghost.test");
test_connect_ok("sslmode=verify-ca host=wronghost.test");
test_connect_fails("sslmode=verify-full host=wronghost.test");

# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');

diag "test hostname matching with X509 Subject Alternative Names";
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";

test_connect_ok("host=dns1.alt-name.pg-ssltest.test");
test_connect_ok("host=dns2.alt-name.pg-ssltest.test");
test_connect_ok("host=foo.wildcard.pg-ssltest.test");

test_connect_fails("host=wronghost.alt-name.pg-ssltest.test");
test_connect_fails("host=deep.subdomain.wildcard.pg-ssltest.test");

# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
switch_server_cert($node, 'server-single-alt-name');

diag "test hostname matching with a single X509 Subject Alternative Name";
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";

test_connect_ok("host=single.alt-name.pg-ssltest.test");

test_connect_fails("host=wronghost.alt-name.pg-ssltest.test");
test_connect_fails("host=deep.subdomain.wildcard.pg-ssltest.test");

# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
switch_server_cert($node, 'server-cn-and-alt-names');

diag "test certificate with both a CN and SANs";
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";

test_connect_ok("host=dns1.alt-name.pg-ssltest.test");
test_connect_ok("host=dns2.alt-name.pg-ssltest.test");
test_connect_fails("host=common-name.pg-ssltest.test");

# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";

test_connect_ok("sslmode=verify-ca host=common-name.pg-ssltest.test");
test_connect_fails("sslmode=verify-full host=common-name.pg-ssltest.test");

# Test that the CRL works
diag "Testing client-side CRL";
switch_server_cert($node, 'server-revoked');

$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";

# Without the CRL, succeeds. With it, fails.
test_connect_ok("sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca");
test_connect_fails(
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
);

### Part 2. Server-side tests.
###
### Test certificate authorization.

diag "Testing certificate authorization...";
$common_connstr =
"sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";

# no client cert
test_connect_fails("user=ssltestuser sslcert=invalid");

# correct client cert
test_connect_ok(
	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key");

# client cert belonging to another user
test_connect_fails(
	"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key");

# revoked client cert
test_connect_fails(
"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
);

# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
"user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";

test_connect_ok("sslmode=require sslcert=ssl/client+client_ca.crt");
test_connect_fails("sslmode=require sslcert=ssl/client.crt");

# clean up
unlink "ssl/client_tmp.key";