home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / Kex.pm < prev    next >
Encoding:
Perl POD Document  |  2003-12-03  |  8.9 KB  |  312 lines

  1. # $Id: Kex.pm,v 1.20 2003/12/03 15:35:21 autarch Exp $
  2.  
  3. package Net::SSH::Perl::Kex;
  4. use strict;
  5.  
  6. use Net::SSH::Perl::Kex::DH1;
  7. use Net::SSH::Perl::Cipher;
  8. use Net::SSH::Perl::Mac;
  9. use Net::SSH::Perl::Comp;
  10. use Net::SSH::Perl::Packet;
  11. use Net::SSH::Perl::Buffer;
  12. use Net::SSH::Perl::Constants qw(
  13.     :msg2
  14.     :kex
  15.     :proposal
  16.     :protocol
  17.     SSH_COMPAT_BUG_HMAC );
  18.  
  19. use Carp qw( croak );
  20. use Digest::SHA1 qw( sha1 );
  21. use Scalar::Util qw(weaken);
  22.  
  23. use vars qw( @PROPOSAL );
  24. @PROPOSAL = (
  25.     KEX_DEFAULT_KEX,
  26.     KEX_DEFAULT_PK_ALG,
  27.     KEX_DEFAULT_ENCRYPT,
  28.     KEX_DEFAULT_ENCRYPT,
  29.     KEX_DEFAULT_MAC,
  30.     KEX_DEFAULT_MAC,
  31.     KEX_DEFAULT_COMP,
  32.     KEX_DEFAULT_COMP,
  33.     KEX_DEFAULT_LANG,
  34.     KEX_DEFAULT_LANG,
  35. );
  36.  
  37. sub new {
  38.     my $class = shift;
  39.     my $ssh = shift;
  40.     my $kex = bless { ssh => $ssh }, $class;
  41.     weaken $kex->{ssh};
  42.     $kex;
  43. }
  44.  
  45. sub client_kexinit { $_[0]->{client_kexinit} }
  46. sub server_kexinit { $_[0]->{server_kexinit} }
  47.  
  48. sub send_cipher { $_[0]->{ciph}[1] }
  49. sub send_mac    { $_[0]->{mac} [1] }
  50. sub send_comp   { $_[0]->{comp}[1] }
  51.  
  52. sub receive_cipher { $_[0]->{ciph}[0] }
  53. sub receive_mac    { $_[0]->{mac} [0] }
  54. sub receive_comp   { $_[0]->{comp}[0] }
  55.  
  56. sub exchange {
  57.     my $kex = shift;
  58.     my $ssh = $kex->{ssh};
  59.     my $packet;
  60.  
  61.     my @proposal = @PROPOSAL;
  62.     if (!$ssh->config->get('ciphers')) {
  63.         if (my $c = $ssh->config->get('cipher')) {
  64.             $ssh->config->set('ciphers', $c);
  65.         }
  66.     }
  67.     if (my $cs = $ssh->config->get('ciphers')) {
  68.         $proposal[ PROPOSAL_CIPH_ALGS_CTOS ] =
  69.         $proposal[ PROPOSAL_CIPH_ALGS_STOC ] = $cs;
  70.     }
  71.     if ($ssh->config->get('compression')) {
  72.         $proposal[ PROPOSAL_COMP_ALGS_CTOS ] =
  73.         $proposal[ PROPOSAL_COMP_ALGS_STOC ] = "zlib";
  74.     }
  75.     else {
  76.         $proposal[ PROPOSAL_COMP_ALGS_CTOS ] =
  77.         $proposal[ PROPOSAL_COMP_ALGS_STOC ] = "none";
  78.     }
  79.     if ($ssh->config->get('host_key_algorithms')) {
  80.         $proposal[ PROPOSAL_SERVER_HOST_KEY_ALGS ] =
  81.             $ssh->config->get('host_key_algorithms');
  82.     }
  83.  
  84.     $kex->{client_kexinit} = $kex->kexinit(\@proposal);
  85.     my($sprop) = $kex->exchange_kexinit;
  86.  
  87.     $kex->choose_conf(\@proposal, $sprop);
  88.     $ssh->debug("Algorithms, c->s: " .
  89.         "$kex->{ciph_name}[0] $kex->{mac_name}[0] $kex->{comp_name}[0]");
  90.     $ssh->debug("Algorithms, s->c: " .
  91.         "$kex->{ciph_name}[1] $kex->{mac_name}[1] $kex->{comp_name}[1]");
  92.  
  93.     bless $kex, $kex->{class_name};
  94.     $kex->exchange;
  95.  
  96.     $ssh->debug("Waiting for NEWKEYS message.");
  97.     $packet = Net::SSH::Perl::Packet->read_expect($ssh, SSH2_MSG_NEWKEYS);
  98.  
  99.     $ssh->debug("Enabling incoming encryption/MAC/compression.");
  100.     for my $att (qw( mac ciph comp )) {
  101.         $kex->{$att}[0]->enable if $kex->{$att}[0];
  102.     }
  103.  
  104.     $ssh->debug("Send NEWKEYS, enable outgoing encryption/MAC/compression.");
  105.     $packet = $ssh->packet_start(SSH2_MSG_NEWKEYS);
  106.     $packet->send;
  107.  
  108.     for my $att (qw( mac ciph comp )) {
  109.         $kex->{$att}[1]->enable if $kex->{$att}[1];
  110.     }
  111. }
  112.  
  113. sub kexinit {
  114.     my $kex = shift;
  115.     my($proposal) = @_;
  116.  
  117.     my $b = Net::SSH::Perl::Buffer->new( MP => 'SSH2' );
  118.     my $cookie = join '', map chr rand 255, 1..16;
  119.     $b->put_chars($cookie);
  120.     $b->put_str($_) for @$proposal;
  121.     $b->put_int8(0);
  122.     $b->put_int32(0);
  123.     $b;
  124. }
  125.  
  126. sub exchange_kexinit {
  127.     my $kex = shift;
  128.     my $ssh = $kex->{ssh};
  129.     my $packet;
  130.  
  131.     $packet = $ssh->packet_start(SSH2_MSG_KEXINIT);
  132.     $packet->put_chars($kex->client_kexinit->bytes);
  133.     $packet->send;
  134.  
  135.     $ssh->debug("Sent key-exchange init (KEXINIT), wait response.");
  136.     $packet = Net::SSH::Perl::Packet->read_expect($ssh, SSH2_MSG_KEXINIT);
  137.     $kex->{server_kexinit} = $packet->data;
  138.  
  139.     $packet->get_char for 1..16;
  140.     my @s_props = map $packet->get_str, 1..10;
  141.     $packet->get_int8;
  142.     $packet->get_int32;
  143.  
  144.     \@s_props;
  145. }
  146.  
  147. sub derive_keys {
  148.     my $kex = shift;
  149.     my($hash, $shared_secret) = @_;
  150.     my @keys;
  151.     for my $i (0..5) {
  152.         push @keys,
  153.             derive_key(ord('A')+$i, $kex->{we_need}, $hash, $shared_secret);
  154.     }
  155.     my $is_ssh2 = $kex->{ssh}->protocol == PROTOCOL_SSH2;
  156.     for my $mode (0, 1) {
  157.         my $ctos = $mode == 1;
  158.         $kex->{ciph}[$mode]->init($keys[$ctos ? 2 : 3], $keys[$ctos ? 0 : 1],
  159.             $is_ssh2);
  160.         $kex->{mac}[$mode]->init($keys[$ctos ? 4 : 5]);
  161.         $kex->{comp}[$mode]->init(6) if $kex->{comp}[$mode];
  162.     }
  163. }
  164.  
  165. sub derive_key {
  166.     my($id, $need, $hash, $shared_secret) = @_;
  167.     my $b = Net::SSH::Perl::Buffer->new( MP => 'SSH2' );
  168.     $b->put_mp_int($shared_secret);
  169.     my $digest = sha1($b->bytes, $hash, chr($id), $hash);
  170.     for (my $have = 20; $need > $have; $have += 20) {
  171.         $digest .= sha1($b->bytes, $hash, $digest);
  172.     }
  173.     $digest;
  174. }
  175.  
  176. sub choose_conf {
  177.     my $kex = shift;
  178.     my($cprop, $sprop) = @_;
  179.     for my $mode (0, 1) {
  180.         my $ctos = $mode == 1;
  181.         my $nciph = $ctos ? PROPOSAL_CIPH_ALGS_CTOS : PROPOSAL_CIPH_ALGS_STOC;
  182.         my $nmac  = $ctos ? PROPOSAL_MAC_ALGS_CTOS  : PROPOSAL_MAC_ALGS_STOC;
  183.         my $ncomp = $ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC;
  184.         $kex->choose_ciph($mode, $cprop->[$nciph], $sprop->[$nciph]);
  185.         $kex->choose_mac ($mode, $cprop->[$nmac],  $sprop->[$nmac]);
  186.         $kex->choose_comp($mode, $cprop->[$ncomp], $sprop->[$ncomp]);
  187.     }
  188.     $kex->choose_kex($cprop->[PROPOSAL_KEX_ALGS], $sprop->[PROPOSAL_KEX_ALGS]);
  189.     $kex->choose_hostkeyalg($cprop->[PROPOSAL_SERVER_HOST_KEY_ALGS],
  190.         $sprop->[PROPOSAL_SERVER_HOST_KEY_ALGS]);
  191.  
  192.     my $need = 0;
  193.     for my $mode (0, 1) {
  194.         $need = $kex->{ciph}[$mode]->keysize
  195.             if $need < $kex->{ciph}[$mode]->keysize;
  196.         $need = $kex->{ciph}[$mode]->blocksize
  197.             if $need < $kex->{ciph}[$mode]->blocksize;
  198.         $need = $kex->{mac}[$mode]->len
  199.             if $need < $kex->{mac}[$mode]->len;
  200.     }
  201.     $kex->{we_need} = $need;
  202. }
  203.  
  204. sub choose_kex {
  205.     my $kex = shift;
  206.     my $name = _get_match(@_);
  207.     croak "No kex algorithm" unless $name;
  208.     $kex->{algorithm} = $name;
  209.     if ($name eq KEX_DH1) {
  210.         $kex->{class_name} = __PACKAGE__ . "::DH1";
  211.     }
  212.     else {
  213.         croak "Bad kex algorithm $name";
  214.     }
  215. }
  216.  
  217. sub choose_hostkeyalg {
  218.     my $kex = shift;
  219.     my $name = _get_match(@_);
  220.     croak "No hostkey algorithm" unless $name;
  221.     $kex->{hostkeyalg} = $name;
  222. }
  223.  
  224. sub choose_ciph {
  225.     my $kex = shift;
  226.     my $mode = shift;
  227.     my $name = _get_match(@_);
  228.     croak "No matching cipher found: client ", $_[0], " server ", $_[1]
  229.         unless $name;
  230.     $kex->{ciph_name}[$mode] = $name;
  231.     $kex->{ciph}[$mode] = Net::SSH::Perl::Cipher->new($name);
  232. }
  233.  
  234. sub choose_mac {
  235.     my $kex = shift;
  236.     my $mode = shift;
  237.     my $name = _get_match(@_);
  238.     croak "No matching mac found: client ", $_[0], " server ", $_[1]
  239.         unless $name;
  240.     $kex->{mac_name}[$mode] = $name;
  241.     my $mac = $kex->{mac}[$mode] = Net::SSH::Perl::Mac->new($name);
  242.     $mac->key_len(
  243.         $kex->{ssh}->{datafellows} & SSH_COMPAT_BUG_HMAC ? 16 : $mac->len);
  244. }
  245.  
  246. sub choose_comp {
  247.     my $kex = shift;
  248.     my $mode = shift;
  249.     my $name = _get_match(@_);
  250.     croak "No matching comp found: client ", $_[0], " server ", $_[1]
  251.         unless $name;
  252.     $kex->{comp_name}[$mode] = $name;
  253.     $kex->{comp}[$mode] = Net::SSH::Perl::Comp->new($name);
  254. }
  255.  
  256. sub _get_match {
  257.     my($c, $s) = @_;
  258.     my %sprop = map { $_ => 1 } split /,/, $s;
  259.     for my $cp (split /,/, $c) {
  260.         return $cp if $sprop{$cp};
  261.     }
  262. }
  263.  
  264. 1;
  265. __END__
  266.  
  267. =head1 NAME
  268.  
  269. Net::SSH::Perl::Kex - SSH2 Key Exchange
  270.  
  271. =head1 SYNOPSIS
  272.  
  273.     use Net::SSH::Perl::Kex;
  274.     my $kex = Net::SSH::Perl::Kex->new($ssh);
  275.     $kex->exchange;
  276.  
  277. =head1 DESCRIPTION
  278.  
  279. I<Net::SSH::Perl::Kex> implements base functionality for SSH2
  280. key exchange. The basic idea is this: Kex itself initializes
  281. the client algorithm proposal, sends it to the server, then
  282. waits for the server's proposal. From these proposals Kex
  283. chooses the algorithms that will be used in the communications
  284. between client and server (eg. encryption algorithm, MAC
  285. algorithm, etc.). Different algorithms can be used in each
  286. direction; for example, client to server communications could
  287. be encrypted using 3DES, and server to client could be encrypted
  288. using RC4.
  289.  
  290. The algorithm negotiation phase, as described above, includes
  291. negotiation for the key-exchange algorithm to be used.
  292. Currently, the only supported algorithm is Diffie-Hellman
  293. Group 1 key exchange, implemented in I<Net::SSH::Perl::Kex::DH1>.
  294. After algorithm negotiation, the Kex object is reblessed into
  295. the key exchange class (eg. 'Net::SSH::Perl::Kex::DH1'), and
  296. then the subclass's I<exchange> method is called to perform
  297. the key exchange.
  298.  
  299. Once control returns to Kex::exchange, the client waits for
  300. the I<SSH_MSG_NEWKEYS> message; once received, the client
  301. turns on its incoming encryption/MAC/compression algorithms,
  302. then sends an I<SSH_MSG_NEWKEYS> message to the server.
  303. Finally, it turns on its outgoing encryption/MAC/compression
  304. algorithms.
  305.  
  306. =head1 AUTHOR & COPYRIGHTS
  307.  
  308. Please see the Net::SSH::Perl manpage for author, copyright,
  309. and license information.
  310.  
  311. =cut
  312.