File Coverage

blib/lib/Protocol/TLS/Handshake.pm
Criterion Covered Total %
statement 95 137 69.3
branch 19 44 43.1
condition 3 12 25.0
subroutine 18 26 69.2
pod 0 21 0.0
total 135 240 56.2


line stmt bran cond sub pod time code
1             package Protocol::TLS::Handshake;
2 2     2   10 use strict;
  2         3  
  2         78  
3 2     2   11 use warnings;
  2         3  
  2         53  
4 2     2   11 use Carp;
  2         2  
  2         131  
5 2     2   11 use Protocol::TLS::Trace qw(tracer);
  2         3  
  2         115  
6             use Protocol::TLS::Constants
7 2     2   11 qw(const_name :versions :hs_types :c_types :alert_desc);
  2         3  
  2         4714  
8              
9             my %handshake_types = (
10             &HSTYPE_HELLO_REQUEST => 'hello_request',
11             &HSTYPE_CLIENT_HELLO => 'client_hello',
12             &HSTYPE_SERVER_HELLO => 'server_hello',
13             &HSTYPE_CERTIFICATE => 'certificate',
14             &HSTYPE_SERVER_KEY_EXCHANGE => 'server_key_exchange',
15             &HSTYPE_CERTIFICATE_REQUEST => 'certificate_request',
16             &HSTYPE_SERVER_HELLO_DONE => 'server_hello_done',
17             &HSTYPE_CERTIFICATE_VERIFY => 'certificate_verify',
18             &HSTYPE_CLIENT_KEY_EXCHANGE => 'client_key_exchange',
19             &HSTYPE_FINISHED => 'finished',
20             );
21              
22             my %decoder =
23             map { $_ => \&{ $handshake_types{$_} . '_decode' } }
24             keys %handshake_types;
25              
26             my %encoder =
27             map { $_ => \&{ $handshake_types{$_} . '_encode' } }
28             keys %handshake_types;
29              
30             sub decode {
31 11     11 0 14 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
32 11 50       31 return 0 if length($$buf_ref) - $buf_offset < 4;
33 11         44 my ( $type, $length_high, $length_low ) = unpack "x${buf_offset}CCn",
34             $$buf_ref;
35              
36             # Incorrect handshake record length
37 11 50       44 if ( $length_high * 256**3 + $length_low != $length - 4 ) {
38 0         0 tracer->debug( "Incorrect handshake record length: "
39             . ( $length_high * 256**3 + $length_low )
40             . " (expected $length)\n" );
41 0         0 $ctx->error(DECODE_ERROR);
42 0         0 return undef;
43             }
44              
45             # Unknown handshake type
46 11 50       32 if ( !exists $handshake_types{$type} ) {
47 0         0 tracer->debug("Unknown handshake type: $type\n");
48 0         0 $ctx->error(DECODE_ERROR);
49 0         0 return undef;
50             }
51 11         31 tracer->debug( 'Got ' . const_name( 'hs_types', $type ) . "\n" );
52              
53 11         60 my $len = $decoder{$type}->( $ctx, $buf_ref, $buf_offset + 4, $length - 4 );
54 11 50       29 return undef unless defined $len;
55              
56             # Save handshake data
57 11 50       25 push @{ $ctx->{pending}->{hs_messages} }, substr $$buf_ref, $buf_offset,
  11         40  
58             $length
59             if $type != HSTYPE_HELLO_REQUEST;
60              
61             # Arrived record may change state of stream
62 11         41 $ctx->state_machine( 'recv', CTYPE_HANDSHAKE, $type );
63              
64 11         46 return $length;
65             }
66              
67             sub encode {
68 11     11 0 23 my ( $ctx, $type ) = splice @_, 0, 2;
69 11         81 my $encoded = pack 'CC n/a*', $type, 0, $encoder{$type}->( $ctx, @_ );
70 11 50       46 push @{ $ctx->{pending}->{hs_messages} }, $encoded
  11         34  
71             if $type != HSTYPE_HELLO_REQUEST;
72 11         69 $encoded;
73             }
74              
75             sub hello_request_decode {
76 0     0 0 0 0;
77             }
78              
79             sub hello_request_encode {
80 0     0 0 0 '';
81             }
82              
83             sub client_hello_decode {
84 1     1 0 3 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
85 1         9 my ( $tls_version, $random, $session_id, $ciphers_l ) =
86             unpack "x$buf_offset na32 C/a n", $$buf_ref;
87              
88 1   50     5 my $sess_l = length($session_id) || 0;
89              
90             # Length error
91 1 50       5 if ( $sess_l > 32 ) {
92 0         0 tracer->debug("Session_id length error: $sess_l\n");
93 0         0 $ctx->error(DECODE_ERROR);
94 0         0 return undef;
95             }
96              
97             # Ciphers error
98 1 50 33     10 if ( !$ciphers_l || $ciphers_l % 2 ) {
99 0         0 tracer->debug("Cipher suites length error\n");
100 0         0 $ctx->error(DECODE_ERROR);
101 0         0 return undef;
102             }
103              
104 1         3 my $offset = 37 + $sess_l;
105              
106 1         24 my @ciphers = unpack 'x' . ( $buf_offset + $offset ) . 'n' . $ciphers_l / 2,
107             $$buf_ref;
108              
109 1         2 $offset += $ciphers_l;
110              
111 1         7 my @compr = unpack 'x' . ( $buf_offset + $offset ) . 'C/C*', $$buf_ref;
112              
113             # Compression length error
114 1 50       5 if ( !@compr ) {
115 0         0 tracer->debug("Compression methods not defined\n");
116 0         0 $ctx->error(DECODE_ERROR);
117 0         0 return undef;
118             }
119 1         4 $offset += 1 + @compr;
120              
121             # Extensions
122 1         1 my $ext_result;
123 1 50       6 if ( $length > $offset ) {
124 1         16 my $len = $ctx->ext_decode(
125             \$ext_result, $buf_ref,
126             $buf_offset + $offset,
127             $length - $offset
128             );
129 1 50       4 return undef unless defined $len;
130 1         3 $offset += $len;
131             }
132              
133             # TODO: need sane result handling
134 1         11 my $res = $ctx->validate_client_hello(
135             ciphers => \@ciphers,
136             compression => \@compr,
137             session_id => $session_id,
138             tls_version => $tls_version,
139             random => $random,
140             extensions => $ext_result,
141             );
142              
143 1 50       9 return $res ? $offset : undef;
144             }
145              
146             sub client_hello_encode {
147 3     3 0 5 my ( $ctx, $data_ref ) = @_;
148              
149 3         13 my $ext = '';
150 3 50       11 if ( exists $data_ref->{extensions} ) {
151              
152             # TODO extenions
153             }
154              
155             pack(
156 3         16 'na32 C/a* n'
157 3         12 . ( @{ $data_ref->{ciphers} } + 1 ) . 'C'
158 3         7 . ( @{ $data_ref->{compression} } + 1 ),
159             $data_ref->{tls_version},
160             $ctx->{pending}->{securityParameters}->{client_random},
161             $data_ref->{session_id},
162 3         5 2 * @{ $data_ref->{ciphers} },
163 3         5 @{ $data_ref->{ciphers} },
164 3         34 scalar @{ $data_ref->{compression} },
165 3         6 @{ $data_ref->{compression} }
166             ) . $ext;
167             }
168              
169             sub server_hello_decode {
170 3     3 0 5 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
171 3         19 my ( $version, $rand, $sess_id, $cipher, $compr ) =
172             unpack "x$buf_offset n a32 C/a n C", $$buf_ref;
173              
174 3         9 my $offset = 35 + length($sess_id) + 2 + 1;
175              
176             # Extensions
177 3         3 my $ext_result;
178 3 50       9 if ( $length > $offset ) {
179 0         0 my $len = $ctx->ext_decode(
180             \$ext_result, $buf_ref,
181             $buf_offset + $offset,
182             $length - $offset
183             );
184 0 0       0 return undef unless defined $len;
185 0         0 $offset += $len;
186             }
187              
188             # TODO: need sane result handling
189 3         64 my $res = $ctx->validate_server_hello(
190             cipher => $cipher,
191             compression => $compr,
192             session_id => $sess_id,
193             version => $version,
194             random => $rand,
195             extensions => $ext_result,
196             );
197              
198 3 50       10 return $res ? $offset : undef;
199             }
200              
201             sub server_hello_encode {
202 1     1 0 14 my ( $ctx, $data_ref ) = @_;
203              
204 1         4 my $ext = '';
205 1 50       17 if ( exists $data_ref->{extensions} ) {
206              
207             # TODO extenions
208             }
209              
210 1         15 pack( "n a32 C/a* n C",
211             $data_ref->{tls_version}, $data_ref->{server_random},
212             $data_ref->{session_id}, $data_ref->{cipher},
213             $data_ref->{compression} )
214             . $ext;
215             }
216              
217             sub certificate_decode {
218 2     2 0 4 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
219 2         9 my $list_len = unpack 'N', "\0" . substr $$buf_ref, $buf_offset, 3;
220 2         3 my $offset = 3;
221              
222 2 50       7 if ( $list_len > $length - $offset ) {
223 0         0 tracer->debug("list too long: $list_len\n");
224 0         0 $ctx->error(DECODE_ERROR);
225 0         0 return undef;
226             }
227              
228 2         7 while ( $offset < $list_len ) {
229 2         6 my $cert_len = unpack 'N', "\0" . substr $$buf_ref,
230             $buf_offset + $offset, 3;
231 2 50       10 if ( $cert_len > $length - $offset - 3 ) {
232 0         0 tracer->debug("cert length too long: $cert_len\n");
233 0         0 $ctx->error(DECODE_ERROR);
234 0         0 return undef;
235             }
236 2   50     30 $ctx->{pending}->{cert} ||= [];
237 2         3 push @{ $ctx->{pending}->{cert} }, substr $$buf_ref,
  2         12  
238             $buf_offset + $offset + 3, $cert_len;
239 2         5 $offset += 3 + $cert_len;
240             }
241              
242 2         4 return $offset;
243             }
244              
245             sub certificate_encode {
246 1     1 0 3 my $ctx = shift;
247              
248 1         2 my $res = '';
249 1         3 for my $cert (@_) {
250 1         13 $res .= pack 'C n/a*', 0, $cert;
251             }
252              
253 1         7 pack( 'Cn', 0, length($res) ) . $res;
254             }
255              
256             sub server_key_exchange_decode {
257 0     0 0 0 die "not implemented";
258             }
259              
260             sub server_key_exchange_encode {
261 0     0 0 0 die "not implemented";
262             }
263              
264             sub certificate_request_decode {
265 0     0 0 0 die "not implemented";
266             }
267              
268             sub certificate_request_encode {
269 0     0 0 0 die "not implemented";
270             }
271              
272             sub server_hello_done_decode {
273 2     2 0 4 0;
274             }
275              
276             sub server_hello_done_encode {
277 1     1 0 6 '';
278             }
279              
280             sub certificate_verify {
281 0     0 0 0 die "not implemented";
282             }
283              
284             sub client_key_exchange_decode {
285 0     0 0 0 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
286 0         0 my ($encoded_pkey) = unpack "x$buf_offset n/a", $$buf_ref;
287 0 0 0     0 unless ( defined $encoded_pkey && length($encoded_pkey) == $length - 2 ) {
288 0   0     0 tracer->error( "broken key length: "
289             . ( $length - 2 ) . " vs "
290             . ( length($encoded_pkey) || 0 )
291             . "\n" );
292 0         0 $ctx->error(DECODE_ERROR);
293 0         0 return undef;
294             }
295              
296 0 0       0 unless ( $ctx->validate_client_key($encoded_pkey) ) {
297 0         0 tracer->error("client key validation failed");
298 0         0 $ctx->error(DECODE_ERROR);
299 0         0 return undef;
300             }
301 0         0 $length;
302             }
303              
304             sub client_key_exchange_encode {
305 2     2 0 16 pack 'n/a*', $_[1];
306             }
307              
308             sub finished_encode {
309 3     3 0 15 $_[1];
310             }
311              
312             sub finished_decode {
313 3     3 0 5 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
314 3         7 my $message = substr $$buf_ref, $buf_offset, $length;
315 3 50       12 return $ctx->validate_finished($message) ? $length : undef;
316             }
317              
318             1