File Coverage

blib/lib/Parse/SNI.pm
Criterion Covered Total %
statement 53 59 89.8
branch 21 38 55.2
condition 6 12 50.0
subroutine 4 4 100.0
pod 1 1 100.0
total 85 114 74.5


line stmt bran cond sub pod time code
1             package Parse::SNI;
2              
3             =pod
4              
5             =encoding utf8
6              
7             =head1 NAME
8              
9             Parse::SNI - parse Server Name Indication from TLS handshake
10              
11             =head1 SYNOPSIS
12              
13             use Parse::SNI;
14             use IO::Socket;
15              
16             my $srv = IO::Socket::INET->new( LocalAddr => 'localhost', LocalPort => 443, Listen => 1 ) or die $@;
17              
18             while ( my $cli = $srv->accept() ) {
19             $cli->sysread( my $buf, 4096 ) or next;
20             my $sni = parse_sni($buf);
21             }
22              
23             =cut
24              
25 1     1   243364 use strict;
  1         1  
  1         44  
26 1     1   3 use Exporter 'import';
  1         1  
  1         47  
27              
28             our $VERSION = '0.10';
29              
30             use constant {
31 1         496 TLS_HEADER_LEN => 5,
32             TLS_HANDSHAKE_CONTENT_TYPE => 0x16,
33             TLS_HANDSHAKE_TYPE_CLIENT_HELLO => 0x01,
34 1     1   3 };
  1         1  
35              
36             our @EXPORT = qw(parse_sni);
37              
38             =head1 FUNCTIONS
39              
40             =head2 parse_sni($data)
41              
42             Tries to parse SNI from the passed data string, which should contain complete initial TLS handshake record from the client.
43             On success returns SNI string in scalar context and C<(SNI string, start position of SNI in $data)> in list context.
44             On error dies with human readable message. One of the usefull error message to parse is C.
45             This may occure when u didn't read all of initial handshake from the client. You should catch it, read remaining message from the client and
46             try again.
47              
48             This function exported by default.
49              
50             =cut
51              
52             # this code was adopted from https://github.com/dlundquist/sniproxy/
53              
54             sub parse_sni {
55 11     11 1 6070 my @data = unpack('C*', $_[0]);
56              
57 11 50       83 die 'Too short data' if @data < TLS_HEADER_LEN;
58              
59 11 50 33     26 if ( $data[0] & 0x80 && $data[2] == 1 ) {
60 0         0 die 'Received SSL 2.0 Client Hello which can not support SNI.';
61             }
62              
63 11 100       17 if ( $data[0] != TLS_HANDSHAKE_CONTENT_TYPE ) {
64 1         11 die 'Request did not begin with TLS handshake.';
65             }
66              
67 10         12 my $tls_version_major = $data[1];
68 10         9 my $tls_version_minor = $data[2];
69              
70 10 50       14 if ( $tls_version_major < 3 ) {
71 0         0 die "Received SSL v$tls_version_major.$tls_version_minor handshake which can not support SNI.";
72             }
73              
74 10         14 my $len = ($data[3] << 8) + $data[4] + TLS_HEADER_LEN;
75 10 100       192 die "Incomplete TLS record: expected $len bytes, got " . @data if $len > @data;
76              
77 5         5 my $pos = TLS_HEADER_LEN;
78 5 50       8 die 'Incorrect TLS header length (1)' if $pos + 1 > @data;
79 5 50       8 die 'Not a client hello' if $data[$pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO;
80              
81 5         6 $pos += 38;
82              
83 5 50       7 die 'Incorrect TLS header length (2)' if $pos + 1 > @data;
84 5         5 $len = $data[$pos];
85 5         4 $pos += 1 + $len;
86              
87 5 50       8 die 'Incorrect TLS header length (3)' if $pos + 2 > @data;
88              
89 5         6 $len = ($data[$pos] << 8) + $data[$pos + 1];
90 5         5 $pos += 2 + $len;
91              
92 5 50       7 die 'Incorrect TLS header length (4)' if $pos + 1 > @data;
93              
94 5         4 $len = $data[$pos];
95 5         6 $pos += 1 + $len;
96              
97 5 0 33     8 if ( $pos == @data && $tls_version_major == 3 && $tls_version_minor == 0 ) {
      33        
98 0         0 die 'Received SSL 3.0 handshake without extensions';
99             }
100              
101 5 50       8 die 'Incorrect TLS header length (5)' if $pos + 2 > @data;
102              
103 5         5 $len = ($data[$pos] << 8) + $data[$pos+1];
104 5         5 $pos += 2;
105              
106 5 50       6 die 'Incorrect TLS header length (6)' if $pos + $len > @data;
107              
108 5         7 my $end = $pos + $len;
109 5         9 while ( $pos + 4 <= $end ) {
110 23         18 my $ext_len = ($data[$pos + 2] << 8) + $data[$pos + 3];
111              
112 23 100 100     63 if ( $data[$pos] == 0x00 && $data[$pos + 1] == 0x00 ) {
113 5 50       9 die 'Incorrect TLS header length (7)' if $pos + 4 + $ext_len > $end;
114              
115 5         5 $pos += 4;
116 5         5 my $end = $pos + $ext_len;
117 5         8 $pos += 2;
118 5         8 while ( $pos + 3 < $end ) {
119 5         6 my $len = ($data[$pos+1] << 8) + $data[$pos+2];
120 5 50       6 die 'Incorrect TLS header length (8)' if $pos + 3 + $len > $end;
121              
122 5 50       6 if ( $data[$pos] == 0x00 ) {
123 5         27 my $sni = join '', map { chr } @data[$pos+3..$pos+2+$len];
  70         102  
124 5 100       327 return wantarray ? ( $sni, $pos+3 ) : $sni;
125             }
126              
127 0         0 $pos += 3 + $len;
128             }
129             }
130              
131 18         23 $pos += 4 + $ext_len;
132             }
133              
134 0 0         die 'Incorrect TLS header length (9)' if $pos != $end;
135 0           die 'No Host header included in this request';
136             }
137              
138             1;
139              
140             =head1 SEE ALSO
141              
142             L
143              
144             =head1 COPYRIGHT
145              
146             Copyright Oleg G .
147              
148             This library is free software; you can redistribute it and/or
149             modify it under the same terms as Perl itself.
150              
151             =cut