| | 1 | #!/usr/bin/perl |
| | 2 | |
| | 3 | # (C) Maxim Dounin |
| | 4 | # (C) Valentin Bartenev |
| | 5 | |
| | 6 | # Tests for Server Name Indication (SNI) TLS extension |
| | 7 | |
| | 8 | ############################################################################### |
| | 9 | |
| | 10 | use warnings; |
| | 11 | use strict; |
| | 12 | |
| | 13 | use Test::More; |
| | 14 | |
| | 15 | BEGIN { use FindBin; chdir($FindBin::Bin); } |
| | 16 | |
| | 17 | use lib 'lib'; |
| | 18 | use Test::Nginx; |
| | 19 | |
| | 20 | ############################################################################### |
| | 21 | |
| | 22 | select STDERR; $| = 1; |
| | 23 | select STDOUT; $| = 1; |
| | 24 | |
| | 25 | eval "use IO::Socket::SSL 1.56"; |
| | 26 | Test::More::plan(skip_all => "IO::Socket::SSL >= 1.56 not found") if $@; |
| | 27 | |
| | 28 | my $t = Test::Nginx->new()->has(qw/http ssl sni rewrite/) |
| | 29 | ->has_daemon('openssl')->plan(6) |
| | 30 | ->write_file_expand('nginx.conf', <<'EOF'); |
| | 31 | |
| | 32 | %%TEST_GLOBALS%% |
| | 33 | |
| | 34 | daemon off; |
| | 35 | |
| | 36 | events { |
| | 37 | } |
| | 38 | |
| | 39 | http { |
| | 40 | %%TEST_GLOBALS_HTTP%% |
| | 41 | |
| | 42 | ssl_certificate_key test.key; |
| | 43 | |
| | 44 | server { |
| | 45 | listen 127.0.0.1:4433 ssl; |
| | 46 | server_name localhost; |
| | 47 | |
| | 48 | ssl_certificate localhost.crt; |
| | 49 | |
| | 50 | underscores_in_headers off; |
| | 51 | |
| | 52 | location / { |
| | 53 | return 200 $server_name; |
| | 54 | } |
| | 55 | } |
| | 56 | |
| | 57 | server { |
| | 58 | listen 127.0.0.1:4433; |
| | 59 | server_name example.com; |
| | 60 | |
| | 61 | ssl_certificate example.com.crt; |
| | 62 | |
| | 63 | underscores_in_headers on; |
| | 64 | |
| | 65 | location / { |
| | 66 | return 200 $server_name; |
| | 67 | } |
| | 68 | |
| | 69 | location /header { |
| | 70 | return 200 $http_special_header; |
| | 71 | } |
| | 72 | } |
| | 73 | } |
| | 74 | |
| | 75 | EOF |
| | 76 | |
| | 77 | my $d = $t->testdir(); |
| | 78 | |
| | 79 | system('openssl', 'genrsa', '-out', "$d/test.key", '2048') == 0 |
| | 80 | or die "system() failed: $?\n"; |
| | 81 | |
| | 82 | foreach my $crt ('localhost', 'example.com'){ |
| | 83 | system('openssl', 'req', '-new', |
| | 84 | '-subj', '/C=RU/ST=Moscow/L=Moscow/O=test/OU=none' |
| | 85 | . "/CN=$crt/emailAddress=test\@$crt/", |
| | 86 | '-key', "$d/test.key", '-out', "$d/$crt.csr") == 0 |
| | 87 | or die "system() failed: $?\n"; |
| | 88 | |
| | 89 | system('openssl', 'x509', '-req', '-days', '7', '-in', "$d/$crt.csr", |
| | 90 | '-signkey', "$d/test.key", '-out', "$d/$crt.crt") == 0 |
| | 91 | or die "system() failed: $?\n"; |
| | 92 | } |
| | 93 | |
| | 94 | $t->run(); |
| | 95 | |
| | 96 | ############################################################################### |
| | 97 | |
| | 98 | is(get_cert_cn(), 'localhost', 'default cert'); |
| | 99 | is(get_cert_cn('example.com'), 'example.com', 'sni cert'); |
| | 100 | |
| | 101 | like(https_get_host('example.com'), qr!example.com!, |
| | 102 | 'host exists, sni exists, and host is equal sni'); |
| | 103 | |
| | 104 | like(https_get_host('example.com', 'example.org'), qr!example.com!, |
| | 105 | 'host exists, sni not found'); |
| | 106 | |
| | 107 | TODO: { |
| | 108 | local $TODO = 'sni restrictions'; |
| | 109 | |
| | 110 | like(https_get_host('example.com', 'localhost'), qr!400 Bad Request!, |
| | 111 | 'host exists, sni exists, and host is not equal sni'); |
| | 112 | |
| | 113 | like(https_get_host('example.org', 'example.com'), qr!400 Bad Request!, |
| | 114 | 'host not found, sni exists'); |
| | 115 | |
| | 116 | } |
| | 117 | |
| | 118 | ############################################################################### |
| | 119 | |
| | 120 | sub get_ssl_socket { |
| | 121 | my $s = eval { |
| | 122 | local $SIG{ALRM} = sub { die "timeout\n" }; |
| | 123 | local $SIG{PIPE} = sub { die "sigpipe\n" }; |
| | 124 | my ($host) = @_; |
| | 125 | alarm(2); |
| | 126 | return IO::Socket::SSL->new( |
| | 127 | Proto => 'tcp', |
| | 128 | PeerAddr => '127.0.0.1:4433', |
| | 129 | SSL_hostname => $host, |
| | 130 | SSL_error_trap => sub { die $_[1] } |
| | 131 | ); |
| | 132 | }; |
| | 133 | alarm(0); |
| | 134 | if ($@) { |
| | 135 | log_in("died: $@"); |
| | 136 | return undef; |
| | 137 | } |
| | 138 | return $s; |
| | 139 | } |
| | 140 | |
| | 141 | sub get_cert_cn { |
| | 142 | my ($host) = @_; |
| | 143 | my $s = get_ssl_socket($host) or return undef; |
| | 144 | $s->dump_peer_certificate() =~ qr!/CN=([^/]+)/!; |
| | 145 | return $1; |
| | 146 | } |
| | 147 | |
| | 148 | sub https_get_host { |
| | 149 | my ($host, $sni) = @_; |
| | 150 | my $s = get_ssl_socket($sni ? $sni : $host) or return undef; |
| | 151 | return http(<<EOF, socket => $s); |
| | 152 | GET / HTTP/1.0 |
| | 153 | Host: $host |
| | 154 | |
| | 155 | EOF |
| | 156 | } |
| | 157 | |
| | 158 | ############################################################################### |