File Coverage

blib/lib/Protocol/TLS/Handshake.pm
Criterion Covered Total %
statement 96 154 62.3
branch 19 48 39.5
condition 3 12 25.0
subroutine 18 27 66.6
pod 0 22 0.0
total 136 263 51.7


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