File Coverage

blib/lib/Protocol/TLS/Context.pm
Criterion Covered Total %
statement 185 247 74.9
branch 81 130 62.3
condition 14 27 51.8
subroutine 30 33 90.9
pod 0 23 0.0
total 310 460 67.3


line stmt bran cond sub pod time code
1             package Protocol::TLS::Context;
2 2     2   57 use 5.008001;
  2         7  
  2         81  
3 2     2   10 use strict;
  2         3  
  2         69  
4 2     2   10 use warnings;
  2         2  
  2         58  
5 2     2   14 use Carp;
  2         5  
  2         151  
6 2     2   17 use Protocol::TLS::Trace qw(tracer bin2hex);
  2         4  
  2         102  
7 2     2   885 use Protocol::TLS::RecordLayer;
  2         7  
  2         74  
8 2     2   871 use Protocol::TLS::Extension;
  2         5  
  2         64  
9 2     2   859 use Protocol::TLS::Crypto;
  2         4  
  2         79  
10             use Protocol::TLS::Constants
11 2         7466 qw(:end_types :state_types :alert_types :alert_desc :versions
12 2     2   11 :c_types :hs_types is_tls_version cipher_type const_name);
  2         8  
13              
14             # Mixin
15             our @ISA = qw(Protocol::TLS::RecordLayer Protocol::TLS::Extension);
16              
17             my %sp = (
18             connectionEnd => undef, # CLIENT, SERVER
19             PRFAlgorithm => undef, # tls_prf_sha256
20             BulkCipherAlgorithm => undef, # null, rc4, 3des, aes
21             CipherType => undef, # stream, block, aead
22             enc_key_length => undef,
23             block_length => undef,
24             fixed_iv_length => undef,
25             record_iv_length => undef,
26             MACAlgorithm => undef, # sha1, sha256
27             mac_length => undef,
28             mac_key_length => undef,
29             CompressionMethod => undef, # null
30             master_secret => ' ' x 48,
31             client_random => ' ' x 32,
32             server_random => ' ' x 32,
33             );
34              
35             my %kb = (
36             client_write_MAC_key => undef,
37             server_write_MAC_key => undef,
38             client_write_encryption_key => undef,
39             server_write_encryption_key => undef,
40             client_write_IV => undef,
41             server_write_IV => undef,
42             );
43              
44             sub copy_pending {
45 3     3 0 36 my $ctx = shift;
46 3         8 my $p = $ctx->{pending};
47 3         48 my $copy = {
48             cipher => $p->{cipher},
49 3         6 securityParameters => { %{ $p->{securityParameters} } },
50             tls_version => $p->{tls_version},
51             session_id => $p->{session_id},
52             compression => $p->{compression},
53             };
54 3         11 delete $copy->{securityParameters}->{client_random};
55 3         5 delete $copy->{securityParameters}->{server_random};
56 3         18 $copy;
57             }
58              
59             sub clear_pending {
60 7     7 0 13 my $ctx = shift;
61 7         173 $ctx->{pending} = {
62             securityParameters => {%sp},
63             key_block => {%kb},
64             tls_version => undef,
65             session_id => undef,
66             cipher => undef,
67             hs_messages => [],
68             compression => undef,
69             };
70 7         31 $ctx->{pending}->{securityParameters}->{connectionEnd} = $ctx->{type};
71             }
72              
73             sub new {
74 4     4 0 19 my ( $class, %args ) = @_;
75 4 50 66     49 croak "Connection end type must be specified: CLIENT or SERVER"
      33        
76             unless exists $args{type}
77             && ( $args{type} == CLIENT
78             || $args{type} == SERVER );
79              
80 4         40 my $self = bless {
81             type => $args{type},
82             crypto => Protocol::TLS::Crypto->new,
83             proposed => {},
84             pending => {},
85             current_decode => {},
86             current_encode => {},
87             session_id => undef,
88             tls_version => undef,
89             seq_read => 0, # 2^64-1
90             seq_write => 0, # 2^64-1
91             queue => [],
92             state => STATE_IDLE,
93             }, $class;
94 4         17 $self->clear_pending;
95 4         35 $self->load_extensions('ServerName');
96              
97 4         18 $self->{pending}->{securityParameters}->{connectionEnd} = $args{type};
98 4 100       57 $self->{pending}->{securityParameters}
99             ->{ $args{type} == SERVER ? 'server_random' : 'client_random' } =
100             pack( 'N', time ) . $self->crypto->random(28);
101 4         441 $self;
102             }
103              
104             # Crypto backend object
105             sub crypto {
106 65     65 0 189 shift->{crypto};
107             }
108              
109             sub error {
110 0     0 0 0 my $self = shift;
111 0         0 tracer->debug("called error: @_\n");
112 0 0 0     0 if ( @_ && !$self->{shutdown} ) {
113 0         0 $self->{error} = shift;
114 0 0       0 $self->{on_error}->( $self->{error} ) if exists $self->{on_error};
115 0         0 $self->finish;
116             }
117 0         0 $self->{error};
118             }
119              
120             sub finish {
121 0     0 0 0 my $self = shift;
122 0 0       0 $self->enqueue( [ CTYPE_ALERT, FATAL, $self->{error} ] )
123             unless $self->shutdown;
124 0         0 $self->shutdown(1);
125             }
126              
127             sub close {
128 5     5 0 1959 my $self = shift;
129 5 100       10 $self->enqueue( [ CTYPE_ALERT, FATAL, CLOSE_NOTIFY ] )
130             unless $self->shutdown;
131 5         13 $self->shutdown(1);
132             }
133              
134             sub shutdown {
135 24     24 0 30 my $self = shift;
136 24 100       53 $self->{shutdown} = shift if @_;
137 24         65 $self->{shutdown};
138             }
139              
140             sub enqueue {
141 15     15 0 32 my ( $self, @records ) = @_;
142 15         30 for (@records) {
143 20 100       114 tracer->debug(
144             "enqueue "
145             . const_name( 'c_types', $_->[0] )
146             . (
147             $_->[0] == CTYPE_HANDSHAKE
148             ? "/" . const_name( 'hs_types', $_->[1] )
149             : ''
150             )
151             . "\n"
152             );
153 20         30 push @{ $self->{queue} }, $self->record_encode( TLS_v12, @$_ );
  20         115  
154 20 100       118 $self->state_machine( 'send', $_->[0],
155             $_->[0] == CTYPE_HANDSHAKE ? $_->[1] : () );
156             }
157             }
158              
159             sub dequeue {
160 34     34 0 42 my $self = shift;
161 34         35 shift @{ $self->{queue} };
  34         85  
162             }
163              
164             sub application_data {
165 3     3 0 6 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
166 3 50 33     15 if ( exists $ctx->{on_data} && $ctx->state == STATE_OPEN ) {
167 3         15 $ctx->{on_data}->( $ctx, substr $$buf_ref, $buf_offset, $length );
168             }
169 3         982 $length;
170             }
171              
172             sub send {
173 3     3 0 1694 my ( $ctx, $data ) = @_;
174 3 50       13 if ( $ctx->state == STATE_OPEN ) {
175 3         15 $ctx->enqueue( [ CTYPE_APPLICATION_DATA, $data ] );
176             }
177             }
178              
179             sub state_machine {
180 37     37 0 67 my ( $ctx, $action, $c_type, $hs_type ) = @_;
181 37         97 my $prev_state = $ctx->state;
182              
183 37 100       207 if ( $c_type == CTYPE_ALERT ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    0          
184              
185             }
186             elsif ( $c_type == CTYPE_APPLICATION_DATA ) {
187 6 50       44 if ( $prev_state != STATE_OPEN ) {
188 0         0 tracer->error("Handshake was not complete\n");
189 0         0 $ctx->error(UNEXPECTED_MESSAGE);
190             }
191             }
192              
193             # IDLE state (waiting for ClientHello)
194             elsif ( $prev_state == STATE_IDLE ) {
195 4 50 33     22 if ( $c_type != CTYPE_HANDSHAKE && $hs_type != HSTYPE_CLIENT_HELLO ) {
196 0         0 tracer->error("Only ClientHello allowed in IDLE state\n");
197 0         0 $ctx->error(UNEXPECTED_MESSAGE);
198             }
199             else {
200 4         10 $ctx->state(STATE_HS_START);
201             }
202             }
203              
204             # Start Handshake (waiting for ServerHello)
205             elsif ( $prev_state == STATE_HS_START ) {
206 4 50 33     54 if ( $c_type != CTYPE_HANDSHAKE && $hs_type != HSTYPE_SERVER_HELLO ) {
    100 100        
207 0         0 tracer->error(
208             "Only ServerHello allowed at Handshake Start state\n");
209 0         0 $ctx->error(UNEXPECTED_MESSAGE);
210             }
211             elsif ( defined $ctx->{proposed}->{session_id}
212             && $ctx->{proposed}->{session_id} eq $ctx->{pending}->{session_id} )
213             {
214 1         8 $ctx->state(STATE_SESS_RESUME);
215             }
216             else {
217 3         12 $ctx->state(STATE_SESS_NEW);
218             }
219             }
220              
221             # STATE_SESS_RESUME
222             elsif ( $prev_state == STATE_SESS_RESUME ) {
223 2 100       9 if ( $c_type == CTYPE_HANDSHAKE ) {
    50          
224 1 50       5 if ( $hs_type == HSTYPE_FINISHED ) {
225 1         6 $ctx->state(STATE_HS_RESUME);
226             }
227             }
228             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
229 1         37 $ctx->change_cipher_spec($action);
230             }
231             else {
232 0         0 tracer->error("Unexpected Handshake type\n");
233 0         0 $ctx->error(UNEXPECTED_MESSAGE);
234             }
235             }
236              
237             # STATE_HS_RESUME
238             elsif ( $prev_state == STATE_HS_RESUME ) {
239 2 100 66     21 if ( $c_type == CTYPE_HANDSHAKE && $hs_type == HSTYPE_FINISHED ) {
    50          
240 1         2 $ctx->state(STATE_OPEN);
241             }
242             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
243 1         4 $ctx->change_cipher_spec($action);
244             }
245             else {
246 0         0 tracer->error("Unexpected Handshake type\n");
247 0         0 $ctx->error(UNEXPECTED_MESSAGE);
248             }
249             }
250              
251             # STATE_SESS_NEW
252             elsif ( $prev_state == STATE_SESS_NEW ) {
253 6 50       11 if ( $c_type == CTYPE_HANDSHAKE ) {
254 6 100       18 if ( $hs_type == HSTYPE_SERVER_HELLO_DONE ) {
255 3         9 $ctx->state(STATE_HS_HALF);
256             }
257             }
258             else {
259 0         0 tracer->error("Unexpected Handshake type\n");
260 0         0 $ctx->error(UNEXPECTED_MESSAGE);
261             }
262             }
263              
264             # STATE_HS_HALF
265             elsif ( $prev_state == STATE_HS_HALF ) {
266 6 100       17 if ( $c_type == CTYPE_HANDSHAKE ) {
    50          
267 4 100       16 if ( $hs_type == HSTYPE_FINISHED ) {
268 2         4 $ctx->state(STATE_HS_FULL);
269             }
270             }
271             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
272 2         20 $ctx->change_cipher_spec($action);
273             }
274             else {
275 0         0 tracer->error("Unexpected Handshake type\n");
276 0         0 $ctx->error(UNEXPECTED_MESSAGE);
277             }
278             }
279              
280             # STATE_HS_FULL
281             elsif ( $prev_state == STATE_HS_FULL ) {
282 4 100       13 if ( $c_type == CTYPE_HANDSHAKE ) {
    50          
283 2 50       6 if ( $hs_type == HSTYPE_FINISHED ) {
284 2         5 $ctx->state(STATE_OPEN);
285             }
286             }
287             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
288 2         9 $ctx->change_cipher_spec($action);
289             }
290             else {
291 0         0 tracer->error("Unexpected Handshake type\n");
292 0         0 $ctx->error(UNEXPECTED_MESSAGE);
293             }
294             }
295              
296             # TODO: ReNegotiation
297             elsif ( $prev_state == STATE_OPEN ) {
298 0         0 tracer->warning("ReNegotiation is not yet supported\n");
299             }
300             }
301              
302             sub generate_key_block {
303 3     3 0 4 my $ctx = shift;
304 3         8 my $sp = $ctx->{pending}->{securityParameters};
305 3         5 my $kb = $ctx->{pending}->{key_block};
306 3         12 ( my $da, $sp->{BulkCipherAlgorithm}, $sp->{MACAlgorithm} ) =
307             cipher_type( $ctx->{pending}->{cipher} );
308              
309 3         10 tracer->debug( "Generating key block for cipher "
310             . const_name( 'ciphers', $ctx->{pending}->{cipher} ) );
311              
312 3 0       12 $sp->{mac_length} = $sp->{mac_key_length} =
    0          
    50          
313             $sp->{MACAlgorithm} eq 'SHA' ? 20
314             : $sp->{MACAlgorithm} eq 'SHA256' ? 32
315             : $sp->{MACAlgorithm} eq 'MD5' ? 16
316             : 0;
317              
318             (
319 3 0       18 $sp->{CipherType}, $sp->{enc_key_length},
    0          
    0          
    50          
320             $sp->{fixed_iv_length}, $sp->{block_length}
321             )
322             =
323             $sp->{BulkCipherAlgorithm} eq 'AES_128_CBC' ? ( 'block', 16, 16, 16 )
324             : $sp->{BulkCipherAlgorithm} eq 'AES_256_CBC' ? ( 'block', 32, 16, 16 )
325             : $sp->{BulkCipherAlgorithm} eq '3DES_EDE_CBC' ? ( 'block', 24, 8, 8 )
326             : $sp->{BulkCipherAlgorithm} eq 'RC4_128' ? ( 'stream', 16, 0, undef )
327             : ( 'stream', 0, 0, undef );
328              
329             (
330 3         27 $kb->{client_write_MAC_key},
331             $kb->{server_write_MAC_key},
332             $kb->{client_write_encryption_key},
333             $kb->{server_write_encryption_key},
334             $kb->{client_write_IV},
335             $kb->{server_write_IV}
336             )
337             = unpack sprintf(
338             'a%i' x 6,
339             ( $sp->{mac_key_length} ) x 2,
340             ( $sp->{enc_key_length} ) x 2,
341             ( $sp->{fixed_iv_length} ) x 2,
342             ),
343             $ctx->crypto->PRF(
344             $sp->{master_secret},
345             "key expansion",
346             $sp->{server_random} . $sp->{client_random},
347             $sp->{mac_key_length} * 2 +
348             $sp->{enc_key_length} * 2 +
349             $sp->{fixed_iv_length} * 2
350             );
351              
352 3         9 ();
353             }
354              
355             sub change_cipher_spec {
356 6     6 0 13 my ( $ctx, $action ) = @_;
357 6         17 tracer->debug("Apply cipher spec $action...\n");
358              
359 6         14 my $sp = $ctx->{pending}->{securityParameters};
360 6         12 my $kb = $ctx->{pending}->{key_block};
361 6 100       25 $ctx->generate_key_block unless defined $kb->{client_write_MAC_key};
362 6 100       21 my $cur =
363             $action eq 'recv' ? $ctx->{current_decode} : $ctx->{current_encode};
364 6         135 $cur->{securityParameters}->{$_} = $sp->{$_} for keys %$sp;
365 6         115 $cur->{key_block}->{$_} = $kb->{$_} for keys %$kb;
366             }
367              
368             sub state {
369 60     60 0 66 my $ctx = shift;
370 60 100       116 if (@_) {
371 17         25 my $state = shift;
372 17 100       97 $ctx->{on_change_state}->( $ctx, $ctx->{state}, $state )
373             if exists $ctx->{on_change_state};
374              
375 17         31 $ctx->{state} = $state;
376              
377             # Exec callbacks for new state
378 17 100 100     123 if ( exists $ctx->{cb} && exists $ctx->{cb}->{$state} ) {
379 8         9 for my $cb ( @{ $ctx->{cb}->{$state} } ) {
  8         27  
380 8         31 $cb->($ctx);
381             }
382             }
383             }
384 60         165 $ctx->{state};
385             }
386              
387             sub state_cb {
388 15     15 0 25 my ( $ctx, $state, $cb ) = @_;
389 15         14 push @{ $ctx->{cb}->{$state} }, $cb;
  15         69  
390             }
391              
392             sub validate_server_hello {
393 3     3 0 26 my ( $ctx, %h ) = @_;
394 3         13 my $tls_v = is_tls_version( $h{version} );
395 3 50       13 if ( !defined $tls_v ) {
396 0         0 tracer->error("server TLS version $h{version} not recognized\n");
397 0         0 $ctx->error(HANDSHAKE_FAILURE);
398 0         0 return undef;
399             }
400 3         6 my $p = $ctx->{pending};
401 3         7 my $pro = $ctx->{proposed};
402              
403 3 50       9 if ( $tls_v < $pro->{tls_version} ) {
404 0         0 tracer->error("server TLS version $tls_v is not supported\n");
405 0         0 $ctx->error(PROTOCOL_VERSION);
406 0         0 return undef;
407             }
408              
409 3 50       8 if ( !grep { $h{compression} == $_ } @{ $pro->{compression} } ) {
  3         16  
  3         10  
410 0         0 tracer->error("server compression not supported\n");
411 0         0 $ctx->error(HANDSHAKE_FAILURE);
412 0         0 return undef;
413             }
414              
415 3 50       4 if ( !grep { $h{cipher} == $_ } @{ $pro->{ciphers} } ) {
  7         16  
  3         8  
416 0         0 tracer->error("server cipher not accepted\n");
417 0         0 $ctx->error(HANDSHAKE_FAILURE);
418 0         0 return undef;
419             }
420              
421 3         7 $p->{tls_version} = $tls_v;
422 3         11 $p->{securityParameters}->{server_random} = $h{random};
423 3         6 $p->{session_id} = $h{session_id};
424 3         44 $p->{securityParameters}->{CompressionMethod} = $p->{compression} =
425             $h{compression};
426 3         6 $p->{cipher} = $h{cipher};
427 3         11 1;
428             }
429              
430             sub validate_client_hello {
431 1     1 0 17 my ( $ctx, %h ) = @_;
432 1         11 my $tls_v = is_tls_version( $h{tls_version} );
433 1 50       5 if ( !defined $tls_v ) {
434 0         0 tracer->error(
435             "client's TLS version $h{tls_version} is not recognized\n");
436 0         0 $ctx->error(HANDSHAKE_FAILURE);
437 0         0 return undef;
438             }
439 1         3 my $p = $ctx->{pending};
440 1         3 my $pro = $ctx->{proposed};
441              
442 1 50       5 if ( $tls_v < $pro->{tls_version} ) {
443 0         0 tracer->error("client's TLS version $tls_v is not supported\n");
444 0         0 $ctx->error(PROTOCOL_VERSION);
445 0         0 return undef;
446             }
447              
448 1         2 for my $c ( @{ $pro->{compression} } ) {
  1         4  
449 1 50       2 next unless grep { $c == $_ } @{ $h{compression} };
  1         6  
  1         3  
450 1         4 $p->{securityParameters}->{CompressionMethod} = $c;
451 1         2 last;
452             }
453              
454 1 50       5 if ( !exists $p->{securityParameters}->{CompressionMethod} ) {
455 0         0 tracer->error("client's compression not supported\n");
456 0         0 $ctx->error(HANDSHAKE_FAILURE);
457 0         0 return undef;
458             }
459              
460 1         2 $p->{tls_version} = $tls_v;
461 1         4 $p->{securityParameters}->{client_random} = $h{random};
462 1         2 $p->{session_id} = $h{session_id};
463              
464             # Choose first defined cipher
465 1         2 for my $cipher ( @{ $pro->{ciphers} } ) {
  1         4  
466 1 50       2 next unless grep { $cipher == $_ } @{ $h{ciphers} };
  6         18  
  1         3  
467 1         4 $p->{cipher} = $cipher;
468 1         3 last;
469             }
470              
471 1 50       4 if ( !exists $p->{cipher} ) {
472 0         0 tracer->error("client's ciphers not supported\n");
473 0         0 $ctx->error(HANDSHAKE_FAILURE);
474 0         0 return undef;
475             }
476              
477 1         6 1;
478             }
479              
480             sub validate_client_key {
481 0     0 0 0 my ( $ctx, $pkey ) = @_;
482 0         0 my $p = $ctx->{pending};
483 0         0 my $sp = $p->{securityParameters};
484 0         0 my ( $da, $ca, $mac ) = cipher_type( $p->{cipher} );
485              
486 0 0       0 if ( $da eq 'RSA' ) {
487 0         0 my $preMasterSecret = $ctx->crypto->rsa_decrypt( $ctx->{key}, $pkey );
488              
489 0         0 $sp->{master_secret} = $ctx->crypto->PRF(
490             $preMasterSecret,
491             "master secret",
492             $sp->{client_random} . $sp->{server_random}, 48
493             );
494              
495             }
496             else {
497 0         0 die "not implemented";
498             }
499              
500             }
501              
502             sub peer_finished {
503 3     3 0 4 my $ctx = shift;
504 3 50       17 $ctx->_finished( $ctx->{type} == CLIENT ? SERVER : CLIENT );
505             }
506              
507             sub finished {
508 3     3 0 9 my $ctx = shift;
509 3 50       18 $ctx->_finished( $ctx->{type} == CLIENT ? CLIENT : SERVER );
510             }
511              
512             sub _finished {
513 6     6   12 my ( $ctx, $type ) = @_;
514 6         55 $ctx->crypto->PRF(
515             $ctx->{pending}->{securityParameters}->{master_secret},
516             ( $type == CLIENT ? 'client' : 'server' ) . ' finished',
517 6 100       14 $ctx->crypto->PRF_hash( join '', @{ $ctx->{pending}->{hs_messages} } ),
518             12
519             );
520             }
521              
522             sub validate_finished {
523 3     3 0 5 my ( $ctx, $message ) = @_;
524              
525 3         5 my $p = $ctx->{pending};
526 3         7 my $sp = $p->{securityParameters};
527 3         48 my $crypto = $ctx->crypto;
528              
529 3         11 my $finished = $ctx->peer_finished;
530 3         14 tracer->debug( "finished expected: " . bin2hex($finished) );
531 3         13 tracer->debug( "finished received: " . bin2hex($message) );
532              
533 3 50       12 if ( $finished ne $message ) {
534 0         0 tracer->error("finished not match");
535 0         0 $ctx->error(HANDSHAKE_FAILURE);
536 0         0 return;
537             }
538 3         12 1;
539             }
540              
541             1