File Coverage

lib/Net/BitTorrent/Protocol/MSE.pm
Criterion Covered Total %
statement 17 17 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 23 23 100.0


line stmt bran cond sub pod time code
1 20     20   196547 use v5.40;
  20         79  
2 20     20   135 use feature 'class';
  20         40  
  20         2984  
3 20     20   128 no warnings 'experimental::class';
  20         44  
  20         1104  
4 20     20   639 use Net::BitTorrent::Emitter;
  20         48  
  20         2232  
5             class Net::BitTorrent::Protocol::MSE v2.0.0 : isa(Net::BitTorrent::Emitter) {
6 20     20   11027 use Net::BitTorrent::Protocol::MSE::KeyExchange;
  20         82  
  20         1172  
7 20     20   231 use Digest::SHA qw[sha1];
  20         43  
  20         64462  
8             #
9             field $infohash : param = undef;
10             field $is_initiator : param = 0;
11             field $on_infohash_probe : param = undef;
12             field $allow_plaintext : param = 1;
13             field $kx;
14             field $state : reader;
15             field $buffer_in : reader = '';
16             field $buffer_out : reader = '';
17             field $wait_len = 0;
18             field $crypto_select = 0;
19             #
20             my $VC = "\0" x 8;
21             my $CRYPTO_PLAINTEXT = 0x01;
22             my $CRYPTO_RC4 = 0x02;
23             #
24             method supported () { 1; }
25             ADJUST {
26             $kx = Net::BitTorrent::Protocol::MSE::KeyExchange->new( infohash => $infohash, is_initiator => $is_initiator );
27             if ($is_initiator) {
28             $buffer_out .= $kx->public_key;
29             $buffer_out .= $self->_random_pad();
30             $state = 'A_WAIT_PUBKEY';
31             }
32             else {
33             $state = 'B_WAIT_PUBKEY';
34             }
35             }
36              
37             method _random_pad () {
38             my $len = int( rand(513) );
39             return pack( 'C*', map { int( rand(256) ) } 1 .. $len );
40             }
41              
42             method write_buffer () {
43             my $tmp = $buffer_out;
44             $buffer_out = '';
45             return $tmp;
46             }
47              
48             method decrypt_data ($data) {
49             return $self->receive_data($data);
50             }
51              
52             method encrypt_data ($data) {
53             return $data unless $state eq 'PAYLOAD';
54             return $kx->encrypt_rc4->crypt($data);
55             }
56              
57             method receive_data ($data) {
58             if ( $state eq 'PAYLOAD' ) {
59             return $kx->decrypt_rc4->crypt($data);
60             }
61             $buffer_in .= $data;
62             my $continue = 1;
63             while ( $continue && $state ne 'PAYLOAD' && $state ne 'FAILED' && $state ne 'PLAINTEXT_FALLBACK' ) {
64             $continue = 0;
65             if ( $state eq 'A_WAIT_PUBKEY' ) { $continue = $self->_a_wait_pubkey() }
66             elsif ( $state eq 'A_WAIT_VC' ) { $continue = $self->_a_wait_vc() }
67             elsif ( $state eq 'A_WAIT_SELECT' ) { $continue = $self->_a_wait_select() }
68             elsif ( $state eq 'A_WAIT_PADD' ) { $continue = $self->_a_wait_padd() }
69             elsif ( $state eq 'B_WAIT_PUBKEY' ) { $continue = $self->_b_wait_pubkey() }
70             elsif ( $state eq 'B_WAIT_REQS' ) { $continue = $self->_b_wait_reqs() }
71             elsif ( $state eq 'B_WAIT_VC' ) { $continue = $self->_b_wait_vc() }
72             elsif ( $state eq 'B_WAIT_PROVIDE' ) { $continue = $self->_b_wait_provide() }
73             elsif ( $state eq 'B_WAIT_PADC' ) { $continue = $self->_b_wait_padc() }
74             elsif ( $state eq 'B_WAIT_IA_LEN' ) { $continue = $self->_b_wait_ia_len() }
75             elsif ( $state eq 'B_WAIT_IA' ) { $continue = $self->_b_wait_ia() }
76             }
77             if ( $state eq 'PAYLOAD' && length($buffer_in) > 0 ) {
78             my $decrypted = $kx->decrypt_rc4->crypt($buffer_in);
79             $buffer_in = '';
80             return $decrypted;
81             }
82             return undef;
83             }
84              
85             method _a_wait_pubkey () {
86             if ( $allow_plaintext && length($buffer_in) >= 1 && ord( substr( $buffer_in, 0, 1 ) ) == 19 ) {
87             $state = 'PLAINTEXT_FALLBACK';
88             return 0;
89             }
90             return 0 if length($buffer_in) < 96;
91             my $remote_pub = substr( $buffer_in, 0, 96, '' );
92             $kx->compute_secret($remote_pub);
93             my ( $req1, $xor_part ) = $kx->get_sync_data();
94             $buffer_out .= $req1 . $xor_part;
95             $kx->init_rc4($infohash);
96             my $payload = $VC;
97             $payload .= pack( 'N', $CRYPTO_RC4 | $CRYPTO_PLAINTEXT );
98             $payload .= pack( 'n', 0 );
99             $payload .= pack( 'n', 0 );
100             $buffer_out .= $kx->encrypt_rc4->crypt($payload);
101             $state = 'A_WAIT_VC';
102             return 1;
103             }
104              
105             method _a_wait_vc () {
106             return 0 if length($buffer_in) < 8;
107             my $pad_len = $kx->scan_for_vc($buffer_in);
108             if ( $pad_len >= 0 ) {
109             substr( $buffer_in, 0, $pad_len, '' );
110             my $vc_enc = substr( $buffer_in, 0, 8, '' );
111             $kx->decrypt_rc4->crypt($vc_enc);
112             $state = 'A_WAIT_SELECT';
113             return 1;
114             }
115             if ( length($buffer_in) > 600 ) {
116             $state = 'FAILED';
117             }
118             return 0;
119             }
120              
121             method _a_wait_select () {
122             return 0 if length($buffer_in) < 6;
123             my $dec = $kx->decrypt_rc4->crypt( substr( $buffer_in, 0, 6, '' ) );
124             my $select = unpack( 'N', substr( $dec, 0, 4 ) );
125             my $pad_len = unpack( 'n', substr( $dec, 4, 2 ) );
126             if ( !( $select & $CRYPTO_RC4 ) ) {
127             $state = 'FAILED';
128             return 0;
129             }
130             $wait_len = $pad_len;
131             $state = 'A_WAIT_PADD';
132             return 1;
133             }
134              
135             method _a_wait_padd () {
136             return 0 if length($buffer_in) < $wait_len;
137             if ( $wait_len > 0 ) {
138             $kx->decrypt_rc4->crypt( substr( $buffer_in, 0, $wait_len, '' ) );
139             }
140             $self->_emit( 'infohash_identified', $infohash );
141             $state = 'PAYLOAD';
142             return 0;
143             }
144              
145             method _b_wait_pubkey () {
146             return 0 if length($buffer_in) < 96;
147             my $pub_a = substr( $buffer_in, 0, 96, '' );
148             $kx->compute_secret($pub_a);
149             $buffer_out .= $kx->public_key;
150             $buffer_out .= $self->_random_pad();
151             $state = 'B_WAIT_REQS';
152             return 1;
153             }
154              
155             method _b_wait_reqs () {
156             my $s = $kx->get_secret;
157             my $req1_hash = sha1( 'req1' . $s );
158             my $idx = index( $buffer_in, $req1_hash );
159             if ( $idx == -1 ) {
160             if ( length($buffer_in) > 600 ) { $state = 'FAILED'; }
161             return 0;
162             }
163             substr( $buffer_in, 0, $idx + 20, '' );
164             if ( length($buffer_in) < 20 ) { return 0; }
165             my $xor_block = substr( $buffer_in, 0, 20, '' );
166             if ( defined $infohash ) {
167             if ( !$kx->verify_skey( $xor_block, $infohash ) ) {
168             $state = 'FAILED';
169             return 0;
170             }
171             }
172             elsif ($on_infohash_probe) {
173             my $req3_hash = sha1( 'req3' . $s );
174              
175             # FIX: Use ^. for string XOR
176             my $target = $xor_block^.$req3_hash;
177             $infohash = $on_infohash_probe->( $self, $target );
178             if ( !$infohash ) {
179             $state = 'FAILED';
180             return 0;
181             }
182             }
183             else {
184             $state = 'FAILED';
185             return 0;
186             }
187             $kx->init_rc4($infohash);
188             $self->_emit( 'infohash_identified', $infohash );
189             $state = 'B_WAIT_VC';
190             return 1;
191             }
192              
193             method _b_wait_vc () {
194             return 0 if length($buffer_in) < 8;
195             my $vc_check = $kx->decrypt_rc4->crypt( substr( $buffer_in, 0, 8, '' ) );
196             if ( $vc_check ne $VC ) {
197             $state = 'FAILED';
198             return 0;
199             }
200             $state = 'B_WAIT_PROVIDE';
201             return 1;
202             }
203              
204             method _b_wait_provide () {
205             return 0 if length($buffer_in) < 6;
206             my $dec = $kx->decrypt_rc4->crypt( substr( $buffer_in, 0, 6, '' ) );
207             my $provide = unpack( 'N', substr( $dec, 0, 4 ) );
208             my $len = unpack( 'n', substr( $dec, 4, 2 ) );
209             unless ( $provide & $CRYPTO_RC4 ) {
210             $state = 'FAILED';
211             return 0;
212             }
213             $wait_len = $len;
214             $state = 'B_WAIT_PADC';
215             return 1;
216             }
217              
218             method _b_wait_padc () {
219             return 0 if length($buffer_in) < $wait_len;
220             if ( $wait_len > 0 ) {
221             $kx->decrypt_rc4->crypt( substr( $buffer_in, 0, $wait_len, '' ) );
222             }
223             $state = 'B_WAIT_IA_LEN';
224             return 1;
225             }
226              
227             method _b_wait_ia_len () {
228             return 0 if length($buffer_in) < 2;
229             my $dec = $kx->decrypt_rc4->crypt( substr( $buffer_in, 0, 2, '' ) );
230             $wait_len = unpack( 'n', $dec );
231             $state = 'B_WAIT_IA';
232             return 1;
233             }
234              
235             method _b_wait_ia () {
236             return 0 if length($buffer_in) < $wait_len;
237             if ( $wait_len > 0 ) {
238             $kx->decrypt_rc4->crypt( substr( $buffer_in, 0, $wait_len, '' ) );
239             }
240             my $res = $VC;
241             $res .= pack( 'N', $CRYPTO_RC4 );
242             $res .= pack( 'n', 0 );
243             $buffer_out .= $kx->encrypt_rc4->crypt($res);
244             $state = 'PAYLOAD';
245             return 0;
246             }
247             } 1;