File Coverage

blib/lib/Protocol/TLS/Client.pm
Criterion Covered Total %
statement 88 114 77.1
branch 10 32 31.2
condition 2 9 22.2
subroutine 16 16 100.0
pod 2 2 100.0
total 118 173 68.2


line stmt bran cond sub pod time code
1             package Protocol::TLS::Client;
2 1     1   170040 use strict;
  1         3  
  1         28  
3 1     1   6 use warnings;
  1         2  
  1         32  
4 1     1   6 use Carp;
  1         2  
  1         115  
5 1     1   522 use Protocol::TLS::Trace qw(tracer bin2hex);
  1         2  
  1         64  
6 1     1   547 use Protocol::TLS::Utils qw(load_cert load_priv_key);
  1         3  
  1         61  
7 1     1   584 use Protocol::TLS::Context;
  1         3  
  1         36  
8 1     1   540 use Protocol::TLS::Connection;
  1         3  
  1         38  
9 1         1332 use Protocol::TLS::Constants qw(const_name :state_types :end_types :c_types
10 1     1   6 :versions :hs_types :ciphers cipher_type :alert_desc :hash_alg :sign_alg);
  1         2  
11              
12             sub new {
13 2     2 1 42201 my ( $class, %opts ) = @_;
14 2         33 my $self = bless { %opts, sid => {} }, $class;
15 2 0 33     17 if ( exists $opts{cert_file} && exists $opts{key_file} ) {
16 0         0 $self->{cert} = load_cert( $opts{cert_file} );
17 0         0 $self->{key} = load_priv_key( $opts{key_file} );
18             }
19 2         10 $self;
20             }
21              
22             sub new_connection {
23 3     3 1 4384 my ( $self, $server_name, %opts ) = @_;
24 3 50       24 croak "Specify server name of host" unless defined $server_name;
25              
26 3         51 my $ctx = Protocol::TLS::Context->new( type => CLIENT );
27 3 50       19 $ctx->{key} = $self->{key} if exists $self->{key};
28 3 50       11 $ctx->{cert} = $self->{cert} if exists $self->{cert};
29              
30 3         48 my $con = Protocol::TLS::Connection->new($ctx);
31              
32             # Grab random session_id from cache (if exists)
33 3 100       21 if ( exists $self->{sid}->{$server_name} ) {
34 1         8 my $s = $self->{sid}->{$server_name};
35 1         9 my $sid = ( keys %$s )[0];
36              
37             $ctx->{proposed} = {
38             session_id => $sid,
39             tls_version => $s->{$sid}->{tls_version},
40             ciphers => [ $s->{$sid}->{cipher} ],
41 1         12 compression => [ $s->{$sid}->{compression} ],
42             };
43             }
44             else {
45             $ctx->{proposed} = {
46 2         33 session_id => '',
47             ciphers => [
48             TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_NULL_SHA256,
49             TLS_RSA_WITH_NULL_SHA,
50             ],
51             tls_version => TLS_v12,
52             compression => [0],
53             };
54             }
55              
56 3 50       12 if ( exists $opts{on_data} ) {
57 3         8 $ctx->{on_data} = $opts{on_data};
58             }
59              
60 3         20 $ctx->enqueue( [ CTYPE_HANDSHAKE, HSTYPE_CLIENT_HELLO, $ctx->{proposed} ] );
61              
62             $ctx->{on_change_state} = sub {
63 11     11   26 my ( $ctx, $prev_state, $new_state ) = @_;
64 11         36 tracer->debug( "State changed from "
65             . const_name( 'state_types', $prev_state ) . " to "
66             . const_name( 'state_types', $new_state ) );
67 3         49 };
68              
69             # New session
70             $ctx->state_cb(
71             STATE_HS_HALF,
72             sub {
73 2     2   4 my $ctx = shift;
74 2         5 my $p = $ctx->{pending};
75 2         4 my $pro = $ctx->{proposed};
76 2         4 my $sp = $p->{securityParameters};
77 2         8 my $crypto = $ctx->crypto;
78              
79             # Server invalidate our session
80 2 50       11 if ( $pro->{session_id} ne '' ) {
81 0         0 delete $self->{sid}->{$server_name}->{ $pro->{session_id} };
82             }
83              
84 2         18 my $pub_key = $crypto->cert_pubkey( $p->{cert}->[0] );
85              
86 2 50       74 if ( exists $p->{client_cert} ) {
87             $ctx->enqueue(
88             [
89             CTYPE_HANDSHAKE,
90             HSTYPE_CERTIFICATE,
91             exists $ctx->{cert} ? $ctx->{cert} : ()
92 0 0       0 ]
93             );
94             }
95              
96 2         21 my ( $da, $ca, $mac ) = cipher_type( $p->{cipher} );
97              
98 2 50       16 if ( $da eq 'RSA' ) {
99             my $preMasterSecret =
100 2         25 pack( "n", $p->{tls_version} ) . $crypto->random(46);
101              
102             $sp->{master_secret} = $crypto->PRF(
103             $preMasterSecret,
104             "master secret",
105 2         83 $sp->{client_random} . $sp->{server_random}, 48
106             );
107              
108 2         12 my $encoded =
109             $crypto->rsa_encrypt( $pub_key, $preMasterSecret );
110 2         1001 $ctx->enqueue(
111             [ CTYPE_HANDSHAKE, HSTYPE_CLIENT_KEY_EXCHANGE, $encoded ] );
112             }
113             else {
114 0         0 die "not implemented";
115             }
116              
117 2 0 33     10 if ( exists $p->{client_cert} && exists $ctx->{cert} ) {
118 0         0 my ( $sign, $hash_n, $alg_n, $hash, $alg );
119              
120 0         0 $alg = $crypto->cert_pubkeyalg( $ctx->{cert} );
121              
122 0 0 0     0 if ( $alg && exists &{"SIGN_$alg"} ) {
  0 0       0  
123 1     1   6 no strict 'refs';
  1         2  
  1         742  
124 0         0 $alg_n = &{"SIGN_$alg"};
  0         0  
125             }
126             elsif ($alg) {
127 0         0 die "algotithm $alg not implemented";
128             }
129             else {
130 0         0 die "cert error";
131             }
132              
133 0         0 my $sah = $p->{client_cert}->{sah};
134              
135 0         0 for my $i ( 0 .. @$sah / 2 - 1 ) {
136 0 0       0 if ( $sah->[ $i * 2 + 1 ] == $alg_n ) {
137 0         0 $hash_n = $sah->[ $i * 2 ];
138 0         0 $hash = const_name( 'hash_alg', $sah->[ $i * 2 ] );
139 0         0 $hash =~ s/HASH_//;
140 0         0 tracer->debug("Selected $alg, $hash");
141 0         0 last;
142             }
143             }
144              
145 0 0       0 if ( $alg eq 'RSA' ) {
146             $sign = $crypto->rsa_sign( $ctx->{key}, $hash,
147 0         0 join '', @{ $p->{hs_messages} } );
  0         0  
148             }
149             else {
150 0         0 die "algotithm $alg not implemented";
151             }
152              
153 0         0 $ctx->enqueue(
154             [
155             CTYPE_HANDSHAKE, HSTYPE_CERTIFICATE_VERIFY,
156             $hash_n, $alg_n,
157             $sign
158             ]
159             );
160             }
161              
162 2         19 $ctx->enqueue( [CTYPE_CHANGE_CIPHER_SPEC],
163             [ CTYPE_HANDSHAKE, HSTYPE_FINISHED, $ctx->finished ] );
164             }
165 3         54 );
166              
167             $ctx->state_cb( STATE_SESS_RESUME,
168             sub {
169 1     1   4 my $ctx = shift;
170 1         12 my $p = $ctx->{pending};
171              
172             #my $pro = $ctx->{proposed};
173 1         6 my $sp = $p->{securityParameters};
174              
175 1         5 my $s = $self->{sid}->{$server_name}->{ $p->{session_id} };
176 1         2 $p->{tls_version} = $s->{tls_version};
177 1         3 $p->{cipher} = $s->{cipher};
178             $sp->{$_} = $s->{securityParameters}->{$_}
179 1         4 for keys %{ $s->{securityParameters} },
  1         10  
180              
181             tracer->debug( "Resume session: " . bin2hex( $p->{session_id} ) );
182             }
183 3         44 );
184              
185             $ctx->state_cb( STATE_HS_RESUME,
186             sub {
187 1     1   2 my $ctx = shift;
188 1         5 $ctx->enqueue( [CTYPE_CHANGE_CIPHER_SPEC],
189             [ CTYPE_HANDSHAKE, HSTYPE_FINISHED, $ctx->finished ] );
190             }
191 3         42 );
192              
193             $ctx->state_cb( STATE_OPEN,
194             sub {
195 3     3   6 my $ctx = shift;
196 3         8 my $p = $ctx->{pending};
197              
198             # add sid to client's cache
199             $self->{sid}->{$server_name}->{ $p->{session_id} } =
200 3         15 $ctx->copy_pending;
201 3         14 tracer->debug( "Saved sid:\n" . bin2hex( $p->{session_id} ) );
202 3         27 $ctx->{session_id} = $p->{session_id};
203 3         6 $ctx->{tls_version} = $p->{tls_version};
204 3         15 $ctx->clear_pending;
205              
206             # Handle callbacks
207 3 50       14 if ( exists $opts{on_handshake_finish} ) {
208 3         15 $opts{on_handshake_finish}->($ctx);
209             }
210             }
211 3         27 );
212              
213 3         11 $con;
214             }
215              
216             1
217             __END__