Mercurial > hg > nginx-tests
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 ############################################################################### |