comparison ssl_ocsp.t @ 1570:0077b80ef745

Tests: ssl_ocsp tests.
author Sergey Kandaurov <pluknet@nginx.com>
date Wed, 20 May 2020 15:04:50 +0300
parents
children 804a7409bc63
comparison
equal deleted inserted replaced
1569:cd6abbe0f989 1570:0077b80ef745
1 #!/usr/bin/perl
2
3 # (C) Sergey Kandaurov
4 # (C) Nginx, Inc.
5
6 # Tests for OCSP with client certificates.
7
8 ###############################################################################
9
10 use warnings;
11 use strict;
12
13 use Test::More;
14
15 use MIME::Base64 qw/ decode_base64 /;
16
17 BEGIN { use FindBin; chdir($FindBin::Bin); }
18
19 use lib 'lib';
20 use Test::Nginx;
21
22 ###############################################################################
23
24 select STDERR; $| = 1;
25 select STDOUT; $| = 1;
26
27 eval {
28 require Net::SSLeay;
29 Net::SSLeay::load_error_strings();
30 Net::SSLeay::SSLeay_add_ssl_algorithms();
31 Net::SSLeay::randomize();
32 Net::SSLeay::SSLeay();
33 defined &Net::SSLeay::set_tlsext_status_type or die;
34 };
35 plan(skip_all => 'Net::SSLeay not installed or too old') if $@;
36
37 eval {
38 my $ctx = Net::SSLeay::CTX_new() or die;
39 my $ssl = Net::SSLeay::new($ctx) or die;
40 Net::SSLeay::set_tlsext_host_name($ssl, 'example.org') == 1 or die;
41 };
42 plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@;
43
44 my $t = Test::Nginx->new()->has(qw/http http_ssl sni/)->has_daemon('openssl');
45
46 plan(skip_all => 'no OCSP stapling') if $t->has_module('BoringSSL');
47
48 $t->write_file_expand('nginx.conf', <<'EOF');
49
50 %%TEST_GLOBALS%%
51
52 daemon off;
53
54 events {
55 }
56
57 http {
58 %%TEST_GLOBALS_HTTP%%
59
60 ssl_ocsp leaf;
61 ssl_verify_client on;
62 ssl_verify_depth 2;
63 ssl_client_certificate trusted.crt;
64
65 ssl_ciphers DEFAULT:ECCdraft;
66
67 ssl_certificate_key ec.key;
68 ssl_certificate ec.crt;
69
70 ssl_certificate_key rsa.key;
71 ssl_certificate rsa.crt;
72
73 ssl_session_cache shared:SSL:1m;
74 ssl_session_tickets off;
75
76 add_header X-Verify x${ssl_client_verify}:${ssl_session_reused}x always;
77
78 server {
79 listen 127.0.0.1:8443 ssl;
80 server_name localhost;
81 }
82
83 server {
84 listen 127.0.0.1:8443 ssl;
85 server_name sni;
86
87 ssl_ocsp_responder http://127.0.0.1:8082;
88 }
89
90 server {
91 listen 127.0.0.1:8444 ssl;
92 server_name localhost;
93
94 ssl_ocsp on;
95 }
96
97 server {
98 listen 127.0.0.1:8445 ssl;
99 server_name localhost;
100
101 ssl_ocsp_responder http://127.0.0.1:8082;
102 }
103
104 server {
105 listen 127.0.0.1:8446 ssl;
106 server_name localhost;
107
108 ssl_ocsp_cache shared:OCSP:1m;
109 }
110
111 server {
112 listen 127.0.0.1:8447 ssl;
113 server_name localhost;
114
115 ssl_ocsp_responder http://127.0.0.1:8082;
116 ssl_client_certificate root.crt;
117 }
118 }
119
120 EOF
121
122 my $d = $t->testdir();
123 my $p = port(8081);
124
125 $t->write_file('openssl.conf', <<EOF);
126 [ req ]
127 default_bits = 2048
128 encrypt_key = no
129 distinguished_name = req_distinguished_name
130 [ req_distinguished_name ]
131 EOF
132
133 $t->write_file('ca.conf', <<EOF);
134 [ ca ]
135 default_ca = myca
136
137 [ myca ]
138 new_certs_dir = $d
139 database = $d/certindex
140 default_md = sha256
141 policy = myca_policy
142 serial = $d/certserial
143 default_days = 1
144 x509_extensions = myca_extensions
145
146 [ myca_policy ]
147 commonName = supplied
148
149 [ myca_extensions ]
150 basicConstraints = critical,CA:TRUE
151 authorityInfoAccess = OCSP;URI:http://127.0.0.1:$p
152 EOF
153
154 foreach my $name ('root') {
155 system('openssl req -x509 -new '
156 . "-config $d/openssl.conf -subj /CN=$name/ "
157 . "-out $d/$name.crt -keyout $d/$name.key "
158 . ">>$d/openssl.out 2>&1") == 0
159 or die "Can't create certificate for $name: $!\n";
160 }
161
162 foreach my $name ('int', 'end') {
163 system("openssl req -new "
164 . "-config $d/openssl.conf -subj /CN=$name/ "
165 . "-out $d/$name.csr -keyout $d/$name.key "
166 . ">>$d/openssl.out 2>&1") == 0
167 or die "Can't create certificate for $name: $!\n";
168 }
169
170 foreach my $name ('ec-end') {
171 system("openssl ecparam -genkey -out $d/$name.key -name prime256v1 "
172 . ">>$d/openssl.out 2>&1") == 0
173 or die "Can't create EC param: $!\n";
174 system("openssl req -new -key $d/$name.key "
175 . "-config $d/openssl.conf -subj /CN=$name/ "
176 . "-out $d/$name.csr "
177 . ">>$d/openssl.out 2>&1") == 0
178 or die "Can't create certificate for $name: $!\n";
179 }
180
181 $t->write_file('certserial', '1000');
182 $t->write_file('certindex', '');
183
184 system("openssl ca -batch -config $d/ca.conf "
185 . "-keyfile $d/root.key -cert $d/root.crt "
186 . "-subj /CN=int/ -in $d/int.csr -out $d/int.crt "
187 . ">>$d/openssl.out 2>&1") == 0
188 or die "Can't sign certificate for int: $!\n";
189
190 system("openssl ca -batch -config $d/ca.conf "
191 . "-keyfile $d/int.key -cert $d/int.crt "
192 . "-subj /CN=ec-end/ -in $d/ec-end.csr -out $d/ec-end.crt "
193 . ">>$d/openssl.out 2>&1") == 0
194 or die "Can't sign certificate for ec-end: $!\n";
195
196 system("openssl ca -batch -config $d/ca.conf "
197 . "-keyfile $d/int.key -cert $d/int.crt "
198 . "-subj /CN=end/ -in $d/end.csr -out $d/end.crt "
199 . ">>$d/openssl.out 2>&1") == 0
200 or die "Can't sign certificate for end: $!\n";
201
202 # RFC 6960, serialNumber
203
204 system("openssl x509 -in $d/int.crt -serial -noout "
205 . ">>$d/serial_int 2>>$d/openssl.out") == 0
206 or die "Can't obtain serial for end: $!\n";
207
208 my $serial_int = pack("n2", 0x0202, hex $1)
209 if $t->read_file('serial_int') =~ /(\d+)/;
210
211 system("openssl x509 -in $d/end.crt -serial -noout "
212 . ">>$d/serial 2>>$d/openssl.out") == 0
213 or die "Can't obtain serial for end: $!\n";
214
215 my $serial = pack("n2", 0x0202, hex $1) if $t->read_file('serial') =~ /(\d+)/;
216
217 # ocsp end
218
219 system("openssl ocsp -issuer $d/int.crt -cert $d/end.crt "
220 . "-reqout $d/req.der >>$d/openssl.out 2>&1") == 0
221 or die "Can't create OCSP request: $!\n";
222
223 system("openssl ocsp -index $d/certindex -CA $d/int.crt "
224 . "-rsigner $d/int.crt -rkey $d/int.key "
225 . "-reqin $d/req.der -respout $d/resp.der -ndays 1 "
226 . ">>$d/openssl.out 2>&1") == 0
227 or die "Can't create OCSP response: $!\n";
228
229 system("openssl ocsp -issuer $d/int.crt -cert $d/ec-end.crt "
230 . "-reqout $d/ec-req.der >>$d/openssl.out 2>&1") == 0
231 or die "Can't create EC OCSP request: $!\n";
232
233 system("openssl ocsp -index $d/certindex -CA $d/int.crt "
234 . "-rsigner $d/root.crt -rkey $d/root.key "
235 . "-reqin $d/ec-req.der -respout $d/ec-resp.der -ndays 1 "
236 . ">>$d/openssl.out 2>&1") == 0
237 or die "Can't create EC OCSP response: $!\n";
238
239 $t->write_file('trusted.crt',
240 $t->read_file('int.crt') . $t->read_file('root.crt'));
241
242 # server cert/key
243
244 system("openssl ecparam -genkey -out $d/ec.key -name prime256v1 "
245 . ">>$d/openssl.out 2>&1") == 0 or die "Can't create EC pem: $!\n";
246 system("openssl genrsa -out $d/rsa.key 2048 >>$d/openssl.out 2>&1") == 0
247 or die "Can't create RSA pem: $!\n";
248
249 foreach my $name ('ec', 'rsa') {
250 system("openssl req -x509 -new -key $d/$name.key "
251 . "-config $d/openssl.conf -subj /CN=$name/ "
252 . "-out $d/$name.crt -keyout $d/$name.key "
253 . ">>$d/openssl.out 2>&1") == 0
254 or die "Can't create certificate for $name: $!\n";
255 }
256
257 $t->run_daemon(\&http_daemon, $t, port(8081));
258 $t->run_daemon(\&http_daemon, $t, port(8082));
259 $t->try_run('no ssl_ocsp')->plan(13);
260
261 $t->waitforsocket("127.0.0.1:" . port(8081));
262 $t->waitforsocket("127.0.0.1:" . port(8082));
263
264 my $version = get_version();
265
266 ###############################################################################
267
268 like(get('RSA', 'end'), qr/200 OK.*SUCCESS/s, 'ocsp leaf');
269
270 # demonstrate that ocsp int request is actually made by failing ocsp response
271
272 like(get('RSA', 'end', port => 8444),
273 qr/400 Bad.*FAILED:certificate status request failed/s,
274 'ocsp many failed');
275
276 # now prepare valid ocsp int response
277
278 system("openssl ocsp -issuer $d/root.crt -cert $d/int.crt "
279 . "-reqout $d/int-req.der >>$d/openssl.out 2>&1") == 0
280 or die "Can't create OCSP request: $!\n";
281
282 system("openssl ocsp -index $d/certindex -CA $d/root.crt "
283 . "-rsigner $d/root.crt -rkey $d/root.key "
284 . "-reqin $d/int-req.der -respout $d/int-resp.der -ndays 1 "
285 . ">>$d/openssl.out 2>&1") == 0
286 or die "Can't create OCSP response: $!\n";
287
288 like(get('RSA', 'end', port => 8444), qr/200 OK.*SUCCESS/s, 'ocsp many');
289
290 # store into ssl_ocsp_cache
291
292 like(get('RSA', 'end', port => 8446), qr/200 OK.*SUCCESS/s, 'cache store');
293
294 # revoke
295
296 system("openssl ca -config $d/ca.conf -revoke $d/end.crt "
297 . "-keyfile $d/root.key -cert $d/root.crt "
298 . ">>$d/openssl.out 2>&1") == 0
299 or die "Can't revoke end.crt: $!\n";
300
301 system("openssl ocsp -issuer $d/int.crt -cert $d/end.crt "
302 . "-reqout $d/req.der >>$d/openssl.out 2>&1") == 0
303 or die "Can't create OCSP request: $!\n";
304
305 system("openssl ocsp -index $d/certindex -CA $d/int.crt "
306 . "-rsigner $d/int.crt -rkey $d/int.key "
307 . "-reqin $d/req.der -respout $d/revoked.der -ndays 1 "
308 . ">>$d/openssl.out 2>&1") == 0
309 or die "Can't create OCSP response: $!\n";
310
311 like(get('RSA', 'end'), qr/400 Bad.*FAILED:certificate revoked/s, 'revoked');
312
313 # with different responder where it's still valid
314
315 like(get('RSA', 'end', port => 8445), qr/200 OK.*SUCCESS/s, 'ocsp responder');
316
317 # with different context to responder where it's still valid
318
319 like(get('RSA', 'end', sni => 'sni'), qr/200 OK.*SUCCESS/s, 'ocsp context');
320
321 # with cached ocsp response it's still valid
322
323 like(get('RSA', 'end', port => 8446), qr/200 OK.*SUCCESS/s, 'cache lookup');
324
325 # ocsp end response signed with invalid (root) cert, expect HTTP 400
326
327 like(get('ECDSA', 'ec-end'),
328 qr/400 Bad.*FAILED:certificate status request failed/s,
329 'root ca not trusted');
330
331 # now sign ocsp end response with valid int cert
332
333 system("openssl ocsp -index $d/certindex -CA $d/int.crt "
334 . "-rsigner $d/int.crt -rkey $d/int.key "
335 . "-reqin $d/ec-req.der -respout $d/ec-resp.der -ndays 1 "
336 . ">>$d/openssl.out 2>&1") == 0
337 or die "Can't create EC OCSP response: $!\n";
338
339 like(get('ECDSA', 'ec-end'), qr/200 OK.*SUCCESS/s, 'ocsp ecdsa');
340
341 my ($s, $ssl) = get('ECDSA', 'ec-end');
342 my $ses = Net::SSLeay::get_session($ssl);
343
344 like(get('ECDSA', 'ec-end', ses => $ses),
345 qr/200 OK.*SUCCESS:r/s, 'session reused');
346
347 # revoke with saved session
348
349 system("openssl ca -config $d/ca.conf -revoke $d/ec-end.crt "
350 . "-keyfile $d/root.key -cert $d/root.crt "
351 . ">>$d/openssl.out 2>&1") == 0
352 or die "Can't revoke end.crt: $!\n";
353
354 system("openssl ocsp -issuer $d/int.crt -cert $d/ec-end.crt "
355 . "-reqout $d/ec-req.der >>$d/openssl.out 2>&1") == 0
356 or die "Can't create OCSP request: $!\n";
357
358 system("openssl ocsp -index $d/certindex -CA $d/int.crt "
359 . "-rsigner $d/int.crt -rkey $d/int.key "
360 . "-reqin $d/ec-req.der -respout $d/ec-resp.der -ndays 1 "
361 . ">>$d/openssl.out 2>&1") == 0
362 or die "Can't create OCSP response: $!\n";
363
364 # reusing session with revoked certificate
365
366 like(get('ECDSA', 'ec-end', ses => $ses),
367 qr/400 Bad.*FAILED:certificate revoked:r/s, 'session reused - revoked');
368
369 # regression test for self-signed
370
371 like(get('RSA', 'root', port => 8447), qr/200 OK.*SUCCESS/s, 'ocsp one');
372
373 ###############################################################################
374
375 sub get {
376 my ($type, $cert, %extra) = @_;
377 $type = 'PSS' if $type eq 'RSA' && $version > 0x0303;
378 my ($s, $ssl) = get_ssl_socket($type, $cert, %extra);
379 my $cipher = Net::SSLeay::get_cipher($ssl);
380 Test::Nginx::log_core('||', "cipher: $cipher");
381 my $host = $extra{sni} ? $extra{sni} : 'localhost';
382 Net::SSLeay::write($ssl, "GET /serial HTTP/1.0\nHost: $host\n\n");
383 my $r = Net::SSLeay::read($ssl);
384 Test::Nginx::log_core($r);
385 $s->close();
386 return $r unless wantarray();
387 return ($s, $ssl);
388 }
389
390 sub get_ssl_socket {
391 my ($type, $cert, %extra) = @_;
392 my $ses = $extra{ses};
393 my $sni = $extra{sni};
394 my $port = $extra{port} || 8443;
395 my $s;
396
397 eval {
398 local $SIG{ALRM} = sub { die "timeout\n" };
399 local $SIG{PIPE} = sub { die "sigpipe\n" };
400 alarm(8);
401 $s = IO::Socket::INET->new('127.0.0.1:' . port($port));
402 alarm(0);
403 };
404 alarm(0);
405
406 if ($@) {
407 log_in("died: $@");
408 return undef;
409 }
410
411 my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!");
412
413 if (defined $type) {
414 my $ssleay = Net::SSLeay::SSLeay();
415 if ($ssleay < 0x1000200f || $ssleay == 0x20000000) {
416 Net::SSLeay::CTX_set_cipher_list($ctx, $type)
417 or die("Failed to set cipher list");
418 } else {
419 # SSL_CTRL_SET_SIGALGS_LIST
420 Net::SSLeay::CTX_ctrl($ctx, 98, 0, $type . '+SHA256')
421 or die("Failed to set sigalgs");
422 }
423 }
424
425 Net::SSLeay::set_cert_and_key($ctx, "$d/$cert.crt", "$d/$cert.key")
426 or die if $cert;
427 my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!");
428 Net::SSLeay::set_session($ssl, $ses) if defined $ses;
429 Net::SSLeay::set_tlsext_host_name($ssl, $sni) if $sni;
430 Net::SSLeay::set_fd($ssl, fileno($s));
431 Net::SSLeay::connect($ssl) or die("ssl connect");
432 return ($s, $ssl);
433 }
434
435 sub get_version {
436 my ($s, $ssl) = get_ssl_socket();
437 return Net::SSLeay::version($ssl);
438 }
439
440 ###############################################################################
441
442 sub http_daemon {
443 my ($t, $port) = @_;
444 my $server = IO::Socket::INET->new(
445 Proto => 'tcp',
446 LocalHost => "127.0.0.1:$port",
447 Listen => 5,
448 Reuse => 1
449 )
450 or die "Can't create listening socket: $!\n";
451
452 local $SIG{PIPE} = 'IGNORE';
453
454 while (my $client = $server->accept()) {
455 $client->autoflush(1);
456
457 my $headers = '';
458 my $uri = '';
459 my $resp;
460
461 while (<$client>) {
462 $headers .= $_;
463 last if (/^\x0d?\x0a?$/);
464 }
465
466 $uri = $1 if $headers =~ /^\S+\s+\/([^ ]+)\s+HTTP/i;
467 next unless $uri;
468
469 $uri =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
470 my $req = decode_base64($uri);
471
472 if (index($req, $serial_int) > 0) {
473 $resp = 'int-resp';
474
475 } elsif (index($req, $serial) > 0) {
476 $resp = 'resp';
477
478 # used to differentiate ssl_ocsp_responder
479
480 if ($port == port(8081) && -e "$d/revoked.der") {
481 $resp = 'revoked';
482 }
483
484 } else {
485 $resp = 'ec-resp';
486 }
487
488 # ocsp dummy handler
489
490 select undef, undef, undef, 0.02;
491
492 $headers = <<"EOF";
493 HTTP/1.1 200 OK
494 Connection: close
495 Content-Type: application/ocsp-response
496
497 EOF
498
499 print $client $headers . $t->read_file("$resp.der")
500 if -e "$d/$resp.der";
501 }
502 }
503
504 ###############################################################################